Add serial configuration support to Gateway Modbus
- Introduced GatewayModbusSerialConfig structure to encapsulate serial communication settings. - Added clamping functions for integer and size values to ensure valid configuration ranges. - Updated GatewayModbusConfigFromValue to parse serial configuration from JSON input. - Implemented transport type checking functions for TCP, RTU, ASCII, and Serial. - Enhanced GatewayModbusConfigToValue to include serial configuration in output. Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
@@ -2,6 +2,7 @@ set(GATEWAY_BRIDGE_REQUIRES
|
||||
dali_domain
|
||||
dali_cpp
|
||||
espressif__cjson
|
||||
esp_driver_uart
|
||||
freertos
|
||||
gateway_cache
|
||||
gateway_modbus
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "gateway_modbus.hpp"
|
||||
|
||||
namespace gateway {
|
||||
|
||||
@@ -24,6 +26,9 @@ struct GatewayBridgeServiceConfig {
|
||||
bool cloud_startup_enabled{false};
|
||||
uint32_t modbus_task_stack_size{6144};
|
||||
UBaseType_t modbus_task_priority{4};
|
||||
std::optional<GatewayModbusConfig> default_modbus_config;
|
||||
bool allow_modbus_uart0{false};
|
||||
std::vector<int> reserved_uart_ports;
|
||||
uint32_t bacnet_task_stack_size{8192};
|
||||
UBaseType_t bacnet_task_priority{5};
|
||||
};
|
||||
|
||||
@@ -16,12 +16,16 @@
|
||||
#include "gateway_provisioning.hpp"
|
||||
|
||||
#include "cJSON.h"
|
||||
#include "driver/uart.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "lwip/inet.h"
|
||||
#include "lwip/sockets.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cctype>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
@@ -29,6 +33,7 @@
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string_view>
|
||||
#include <sys/time.h>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -48,6 +53,7 @@ constexpr uint32_t kBacnetGeneratedBinaryInputChannelStride = 32768;
|
||||
constexpr uint32_t kBacnetMaxObjectInstance = 4194303;
|
||||
constexpr uint32_t kBacnetReliabilityNoFaultDetected = 0;
|
||||
constexpr uint32_t kBacnetReliabilityCommunicationFailure = 12;
|
||||
constexpr const char* kModbusManagementPrefix = "@DALIGW";
|
||||
|
||||
struct GatewayBridgeStoredConfig {
|
||||
BridgeRuntimeConfig bridge;
|
||||
@@ -1014,10 +1020,106 @@ bool SendModbusFrame(int sock, const uint8_t* mbap, const std::vector<uint8_t>&
|
||||
return SendAll(sock, frame.data(), frame.size());
|
||||
}
|
||||
|
||||
bool SendModbusException(int sock, const uint8_t* mbap, uint8_t function_code,
|
||||
uint8_t exception_code) {
|
||||
const std::vector<uint8_t> pdu{static_cast<uint8_t>(function_code | 0x80), exception_code};
|
||||
return SendModbusFrame(sock, mbap, pdu);
|
||||
std::vector<uint8_t> ModbusExceptionPdu(uint8_t function_code, uint8_t exception_code) {
|
||||
return {static_cast<uint8_t>(function_code | 0x80), exception_code};
|
||||
}
|
||||
|
||||
uint16_t ModbusCrc16(const uint8_t* data, size_t len) {
|
||||
uint16_t crc = 0xFFFF;
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
crc ^= data[i];
|
||||
for (int bit = 0; bit < 8; ++bit) {
|
||||
if ((crc & 0x0001) != 0) {
|
||||
crc = static_cast<uint16_t>((crc >> 1) ^ 0xA001);
|
||||
} else {
|
||||
crc = static_cast<uint16_t>(crc >> 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
uint8_t ModbusAsciiLrc(const uint8_t* data, size_t len) {
|
||||
uint8_t sum = 0;
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
sum = static_cast<uint8_t>(sum + data[i]);
|
||||
}
|
||||
return static_cast<uint8_t>(-sum);
|
||||
}
|
||||
|
||||
std::optional<uint8_t> HexNibble(char ch) {
|
||||
if (ch >= '0' && ch <= '9') {
|
||||
return static_cast<uint8_t>(ch - '0');
|
||||
}
|
||||
if (ch >= 'A' && ch <= 'F') {
|
||||
return static_cast<uint8_t>(ch - 'A' + 10);
|
||||
}
|
||||
if (ch >= 'a' && ch <= 'f') {
|
||||
return static_cast<uint8_t>(ch - 'a' + 10);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::vector<uint8_t>> DecodeModbusAsciiLine(std::string_view line) {
|
||||
while (!line.empty() && (line.back() == '\r' || line.back() == '\n')) {
|
||||
line.remove_suffix(1);
|
||||
}
|
||||
if (line.size() < 7 || line.front() != ':' || ((line.size() - 1) % 2) != 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
std::vector<uint8_t> bytes;
|
||||
bytes.reserve((line.size() - 1) / 2);
|
||||
for (size_t i = 1; i + 1 < line.size(); i += 2) {
|
||||
const auto high = HexNibble(line[i]);
|
||||
const auto low = HexNibble(line[i + 1]);
|
||||
if (!high.has_value() || !low.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
bytes.push_back(static_cast<uint8_t>((high.value() << 4) | low.value()));
|
||||
}
|
||||
uint8_t sum = 0;
|
||||
for (const auto byte : bytes) {
|
||||
sum = static_cast<uint8_t>(sum + byte);
|
||||
}
|
||||
if (sum != 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
std::string EncodeModbusAsciiLine(const std::vector<uint8_t>& bytes) {
|
||||
constexpr char kHex[] = "0123456789ABCDEF";
|
||||
std::string out;
|
||||
out.reserve(1 + bytes.size() * 2 + 2);
|
||||
out.push_back(':');
|
||||
for (const auto byte : bytes) {
|
||||
out.push_back(kHex[(byte >> 4) & 0x0F]);
|
||||
out.push_back(kHex[byte & 0x0F]);
|
||||
}
|
||||
out.append("\r\n");
|
||||
return out;
|
||||
}
|
||||
|
||||
bool LineStartsWith(std::string_view line, std::string_view prefix) {
|
||||
return line.size() >= prefix.size() && line.substr(0, prefix.size()) == prefix;
|
||||
}
|
||||
|
||||
uart_word_length_t UartWordLength(int bits) {
|
||||
return bits <= 7 ? UART_DATA_7_BITS : UART_DATA_8_BITS;
|
||||
}
|
||||
|
||||
uart_parity_t UartParity(const std::string& parity) {
|
||||
if (parity == "even") {
|
||||
return UART_PARITY_EVEN;
|
||||
}
|
||||
if (parity == "odd") {
|
||||
return UART_PARITY_ODD;
|
||||
}
|
||||
return UART_PARITY_DISABLE;
|
||||
}
|
||||
|
||||
uart_stop_bits_t UartStopBits(int bits) {
|
||||
return bits >= 2 ? UART_STOP_BITS_2 : UART_STOP_BITS_1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -1062,6 +1164,12 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
bool modbus_started{false};
|
||||
bool bacnet_started{false};
|
||||
TaskHandle_t modbus_task_handle{nullptr};
|
||||
std::atomic_bool modbus_stop_requested{false};
|
||||
std::atomic_bool modbus_restart_requested{false};
|
||||
int modbus_listen_sock{-1};
|
||||
int modbus_client_sock{-1};
|
||||
int modbus_uart_port{-1};
|
||||
std::string modbus_last_error;
|
||||
|
||||
struct DiagnosticSnapshotCacheEntry {
|
||||
DaliDomainSnapshot snapshot;
|
||||
@@ -1709,10 +1817,29 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
if (modbus_json != nullptr) {
|
||||
cJSON_AddBoolToObject(modbus_json, "enabled", service_config.modbus_enabled);
|
||||
cJSON_AddBoolToObject(modbus_json, "started", modbus_started);
|
||||
cJSON_AddStringToObject(modbus_json, "lastError", modbus_last_error.c_str());
|
||||
if (modbus_config.has_value()) {
|
||||
cJSON_AddStringToObject(modbus_json, "transport", modbus_config->transport.c_str());
|
||||
cJSON_AddNumberToObject(modbus_json, "port", modbus_config->port);
|
||||
cJSON_AddNumberToObject(modbus_json, "unitID", modbus_config->unit_id);
|
||||
if (GatewayModbusTransportIsSerial(modbus_config->transport)) {
|
||||
cJSON* serial_json = cJSON_CreateObject();
|
||||
if (serial_json != nullptr) {
|
||||
cJSON_AddNumberToObject(serial_json, "uartPort", modbus_config->serial.uart_port);
|
||||
cJSON_AddNumberToObject(serial_json, "txPin", modbus_config->serial.tx_pin);
|
||||
cJSON_AddNumberToObject(serial_json, "rxPin", modbus_config->serial.rx_pin);
|
||||
cJSON_AddNumberToObject(serial_json, "baudrate", modbus_config->serial.baudrate);
|
||||
cJSON_AddStringToObject(serial_json, "parity", modbus_config->serial.parity.c_str());
|
||||
cJSON_AddNumberToObject(serial_json, "stopBits", modbus_config->serial.stop_bits);
|
||||
cJSON* rs485_json = cJSON_CreateObject();
|
||||
if (rs485_json != nullptr) {
|
||||
cJSON_AddBoolToObject(rs485_json, "enabled", modbus_config->serial.rs485.enabled);
|
||||
cJSON_AddNumberToObject(rs485_json, "dePin", modbus_config->serial.rs485.de_pin);
|
||||
cJSON_AddItemToObject(serial_json, "rs485", rs485_json);
|
||||
}
|
||||
cJSON_AddItemToObject(modbus_json, "serial", serial_json);
|
||||
}
|
||||
}
|
||||
}
|
||||
cJSON_AddItemToObject(root, "modbus", modbus_json);
|
||||
}
|
||||
@@ -2404,7 +2531,190 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
return result.ok;
|
||||
}
|
||||
|
||||
esp_err_t startModbus(std::set<uint16_t>* used_ports = nullptr) {
|
||||
std::optional<GatewayModbusConfig> activeModbusConfigLocked() const {
|
||||
if (modbus_config.has_value()) {
|
||||
return modbus_config;
|
||||
}
|
||||
return service_config.default_modbus_config;
|
||||
}
|
||||
|
||||
std::optional<GatewayModbusConfig> activeModbusConfig() const {
|
||||
LockGuard guard(lock);
|
||||
return activeModbusConfigLocked();
|
||||
}
|
||||
|
||||
bool isReservedUartLocked(int uart_port) const {
|
||||
return std::find(service_config.reserved_uart_ports.begin(),
|
||||
service_config.reserved_uart_ports.end(), uart_port) !=
|
||||
service_config.reserved_uart_ports.end();
|
||||
}
|
||||
|
||||
esp_err_t validateSerialModbusConfigLocked(const GatewayModbusConfig& config) const {
|
||||
const int uart_port = config.serial.uart_port;
|
||||
if (uart_port < 0 || uart_port > 2) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (uart_port == 0 && !service_config.allow_modbus_uart0) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
if (isReservedUartLocked(uart_port)) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t saveModbusConfig(const GatewayModbusConfig& config) {
|
||||
LockGuard guard(lock);
|
||||
BridgeProvisioningStore store(bridgeNamespace());
|
||||
const esp_err_t err = store.saveObject(
|
||||
kBridgeConfigKey,
|
||||
GatewayBridgeStoredConfigToValue(bridge_config, config, bacnet_server_config));
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
modbus_config = config;
|
||||
bridge_config_loaded = true;
|
||||
if (modbus != nullptr) {
|
||||
modbus->setConfig(config);
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> processModbusPdu(const GatewayModbusConfig& config,
|
||||
uint8_t unit_id,
|
||||
const std::vector<uint8_t>& pdu) {
|
||||
if (pdu.empty()) {
|
||||
return {};
|
||||
}
|
||||
if (config.unit_id != 0 && unit_id != config.unit_id) {
|
||||
return ModbusExceptionPdu(pdu[0], 0x0B);
|
||||
}
|
||||
|
||||
if ((pdu[0] == 0x01 || pdu[0] == 0x02) && pdu.size() == 5) {
|
||||
const auto space = GatewayModbusReadSpaceForFunction(pdu[0]);
|
||||
const uint16_t start_address = ReadBe16(&pdu[1]);
|
||||
const uint16_t quantity = ReadBe16(&pdu[3]);
|
||||
if (!space.has_value() || quantity == 0 || quantity > kGatewayModbusMaxReadBits) {
|
||||
return ModbusExceptionPdu(pdu[0], 0x03);
|
||||
}
|
||||
const uint8_t byte_count = static_cast<uint8_t>((quantity + 7U) / 8U);
|
||||
std::vector<uint8_t> response(2 + byte_count, 0);
|
||||
response[0] = pdu[0];
|
||||
response[1] = byte_count;
|
||||
for (uint16_t index = 0; index < quantity; ++index) {
|
||||
const auto human_address = static_cast<uint16_t>(
|
||||
GatewayModbusHumanAddressFromWire(space.value(), start_address + index));
|
||||
const auto value = readModbusBoolPoint(space.value(), human_address);
|
||||
if (!value.has_value()) {
|
||||
return ModbusExceptionPdu(pdu[0], 0x02);
|
||||
}
|
||||
if (value.value()) {
|
||||
response[2 + (index / 8)] |= static_cast<uint8_t>(1U << (index % 8));
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
if ((pdu[0] == 0x03 || pdu[0] == 0x04) && pdu.size() == 5) {
|
||||
const auto space = GatewayModbusReadSpaceForFunction(pdu[0]);
|
||||
const uint16_t start_address = ReadBe16(&pdu[1]);
|
||||
const uint16_t quantity = ReadBe16(&pdu[3]);
|
||||
if (!space.has_value() || quantity == 0 || quantity > kGatewayModbusMaxReadRegisters) {
|
||||
return ModbusExceptionPdu(pdu[0], 0x03);
|
||||
}
|
||||
std::vector<uint8_t> response(2 + quantity * 2);
|
||||
response[0] = pdu[0];
|
||||
response[1] = static_cast<uint8_t>(quantity * 2);
|
||||
for (uint16_t index = 0; index < quantity; ++index) {
|
||||
const auto human_address = static_cast<uint16_t>(
|
||||
GatewayModbusHumanAddressFromWire(space.value(), start_address + index));
|
||||
const auto value = readModbusRegisterPoint(space.value(), human_address);
|
||||
if (!value.has_value()) {
|
||||
return ModbusExceptionPdu(pdu[0], 0x02);
|
||||
}
|
||||
WriteBe16(&response[2 + index * 2], value.value());
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
if (pdu[0] == 0x05 && pdu.size() == 5) {
|
||||
const uint16_t wire_address = ReadBe16(&pdu[1]);
|
||||
const uint16_t raw_value = ReadBe16(&pdu[3]);
|
||||
if (raw_value != 0x0000 && raw_value != 0xFF00) {
|
||||
return ModbusExceptionPdu(pdu[0], 0x03);
|
||||
}
|
||||
const auto coil = static_cast<uint16_t>(GatewayModbusHumanAddressFromWire(
|
||||
GatewayModbusSpace::kCoil, wire_address));
|
||||
if (!writeModbusCoilPoint(coil, raw_value == 0xFF00)) {
|
||||
return ModbusExceptionPdu(pdu[0], 0x04);
|
||||
}
|
||||
return pdu;
|
||||
}
|
||||
|
||||
if (pdu[0] == 0x06 && pdu.size() == 5) {
|
||||
const uint16_t wire_register = ReadBe16(&pdu[1]);
|
||||
const uint16_t value = ReadBe16(&pdu[3]);
|
||||
const auto holding_register = static_cast<uint16_t>(GatewayModbusHumanAddressFromWire(
|
||||
GatewayModbusSpace::kHoldingRegister, wire_register));
|
||||
if (!writeModbusRegisterPoint(holding_register, value)) {
|
||||
return ModbusExceptionPdu(pdu[0], 0x04);
|
||||
}
|
||||
return pdu;
|
||||
}
|
||||
|
||||
if (pdu[0] == 0x0F && pdu.size() >= 6) {
|
||||
const uint16_t start_address = ReadBe16(&pdu[1]);
|
||||
const uint16_t quantity = ReadBe16(&pdu[3]);
|
||||
const uint8_t byte_count = pdu[5];
|
||||
if (quantity == 0 || quantity > kGatewayModbusMaxWriteBits ||
|
||||
pdu.size() != static_cast<size_t>(6 + byte_count) ||
|
||||
byte_count != static_cast<uint8_t>((quantity + 7U) / 8U)) {
|
||||
return ModbusExceptionPdu(pdu[0], 0x03);
|
||||
}
|
||||
for (uint16_t index = 0; index < quantity; ++index) {
|
||||
const bool value = (pdu[6 + (index / 8)] & (1U << (index % 8))) != 0;
|
||||
const auto coil = static_cast<uint16_t>(GatewayModbusHumanAddressFromWire(
|
||||
GatewayModbusSpace::kCoil, start_address + index));
|
||||
if (!writeModbusCoilPoint(coil, value)) {
|
||||
return ModbusExceptionPdu(pdu[0], 0x04);
|
||||
}
|
||||
}
|
||||
std::vector<uint8_t> response(5);
|
||||
response[0] = pdu[0];
|
||||
WriteBe16(&response[1], start_address);
|
||||
WriteBe16(&response[3], quantity);
|
||||
return response;
|
||||
}
|
||||
|
||||
if (pdu[0] == 0x10 && pdu.size() >= 6) {
|
||||
const uint16_t start_register = ReadBe16(&pdu[1]);
|
||||
const uint16_t quantity = ReadBe16(&pdu[3]);
|
||||
const uint8_t byte_count = pdu[5];
|
||||
if (quantity == 0 || quantity > kGatewayModbusMaxWriteRegisters ||
|
||||
pdu.size() != static_cast<size_t>(6 + byte_count) || byte_count != quantity * 2) {
|
||||
return ModbusExceptionPdu(pdu[0], 0x03);
|
||||
}
|
||||
for (uint16_t index = 0; index < quantity; ++index) {
|
||||
const size_t offset = 6 + (index * 2);
|
||||
const uint16_t value = ReadBe16(&pdu[offset]);
|
||||
const auto holding_register = static_cast<uint16_t>(GatewayModbusHumanAddressFromWire(
|
||||
GatewayModbusSpace::kHoldingRegister, start_register + index));
|
||||
if (!writeModbusRegisterPoint(holding_register, value)) {
|
||||
return ModbusExceptionPdu(pdu[0], 0x04);
|
||||
}
|
||||
}
|
||||
std::vector<uint8_t> response(5);
|
||||
response[0] = pdu[0];
|
||||
WriteBe16(&response[1], start_register);
|
||||
WriteBe16(&response[3], quantity);
|
||||
return response;
|
||||
}
|
||||
|
||||
return ModbusExceptionPdu(pdu[0], 0x01);
|
||||
}
|
||||
|
||||
esp_err_t startModbus(std::set<uint16_t>* used_ports = nullptr,
|
||||
std::set<int>* used_uarts = nullptr) {
|
||||
LockGuard guard(lock);
|
||||
if (!service_config.modbus_enabled) {
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
@@ -2412,19 +2722,43 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
if (modbus_started || modbus_task_handle != nullptr) {
|
||||
return ESP_OK;
|
||||
}
|
||||
if (!modbus_config.has_value()) {
|
||||
const auto config = activeModbusConfigLocked();
|
||||
if (!config.has_value()) {
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
const uint16_t port = modbus_config->port == 0 ? kGatewayModbusDefaultTcpPort
|
||||
: modbus_config->port;
|
||||
if (used_ports != nullptr) {
|
||||
if (GatewayModbusTransportIsSerial(config->transport)) {
|
||||
const esp_err_t serial_err = validateSerialModbusConfigLocked(config.value());
|
||||
if (serial_err != ESP_OK) {
|
||||
modbus_last_error = "invalid or reserved Modbus serial UART";
|
||||
return serial_err;
|
||||
}
|
||||
if (used_uarts != nullptr) {
|
||||
const int uart_port = config->serial.uart_port;
|
||||
if (used_uarts->find(uart_port) != used_uarts->end()) {
|
||||
ESP_LOGW(kTag, "gateway=%u skips duplicate Modbus serial UART%d", channel.gateway_id,
|
||||
uart_port);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
used_uarts->insert(uart_port);
|
||||
}
|
||||
}
|
||||
const uint16_t port = config->port == 0 ? kGatewayModbusDefaultTcpPort : config->port;
|
||||
if (GatewayModbusTransportIsTcp(config->transport) && used_ports != nullptr) {
|
||||
if (used_ports->find(port) != used_ports->end()) {
|
||||
ESP_LOGW(kTag, "gateway=%u skips duplicate Modbus TCP port %u", channel.gateway_id, port);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
used_ports->insert(port);
|
||||
}
|
||||
const BaseType_t created = xTaskCreate(&ChannelRuntime::ModbusTaskEntry, "gw_modbus_tcp",
|
||||
modbus_stop_requested = false;
|
||||
modbus_restart_requested = false;
|
||||
modbus_last_error.clear();
|
||||
const char* task_name = GatewayModbusTransportIsTcp(config->transport)
|
||||
? "gw_modbus_tcp"
|
||||
: (GatewayModbusTransportIsAscii(config->transport)
|
||||
? "gw_modbus_ascii"
|
||||
: "gw_modbus_rtu");
|
||||
const BaseType_t created = xTaskCreate(&ChannelRuntime::ModbusTaskEntry, task_name,
|
||||
service_config.modbus_task_stack_size, this,
|
||||
service_config.modbus_task_priority,
|
||||
&modbus_task_handle);
|
||||
@@ -2436,21 +2770,74 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t stopModbus() {
|
||||
LockGuard guard(lock);
|
||||
if (!modbus_started && modbus_task_handle == nullptr) {
|
||||
return ESP_OK;
|
||||
}
|
||||
modbus_stop_requested = true;
|
||||
modbus_restart_requested = false;
|
||||
if (modbus_client_sock >= 0) {
|
||||
shutdown(modbus_client_sock, SHUT_RDWR);
|
||||
}
|
||||
if (modbus_listen_sock >= 0) {
|
||||
shutdown(modbus_listen_sock, SHUT_RDWR);
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void modbusTaskLoop() {
|
||||
const uint16_t port = modbus_config.has_value() && modbus_config->port != 0
|
||||
? modbus_config->port
|
||||
: kGatewayModbusDefaultTcpPort;
|
||||
const auto config = activeModbusConfig();
|
||||
if (!config.has_value()) {
|
||||
modbus_last_error = "missing Modbus config";
|
||||
finishModbusTask();
|
||||
return;
|
||||
}
|
||||
if (GatewayModbusTransportIsTcp(config->transport)) {
|
||||
modbusTcpTaskLoop(config.value());
|
||||
} else if (GatewayModbusTransportIsSerial(config->transport)) {
|
||||
modbusSerialTaskLoop(config.value());
|
||||
} else {
|
||||
modbus_last_error = "unsupported Modbus transport";
|
||||
ESP_LOGE(kTag, "gateway=%u unsupported Modbus transport %s", channel.gateway_id,
|
||||
config->transport.c_str());
|
||||
}
|
||||
finishModbusTask();
|
||||
}
|
||||
|
||||
void finishModbusTask() {
|
||||
const bool restart = modbus_restart_requested.exchange(false);
|
||||
{
|
||||
LockGuard guard(lock);
|
||||
modbus_started = false;
|
||||
modbus_task_handle = nullptr;
|
||||
modbus_stop_requested = false;
|
||||
modbus_listen_sock = -1;
|
||||
modbus_client_sock = -1;
|
||||
modbus_uart_port = -1;
|
||||
}
|
||||
if (restart) {
|
||||
startModbus();
|
||||
}
|
||||
vTaskDelete(nullptr);
|
||||
}
|
||||
|
||||
void modbusTcpTaskLoop(const GatewayModbusConfig& config) {
|
||||
const uint16_t port = config.port != 0 ? config.port : kGatewayModbusDefaultTcpPort;
|
||||
const int listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
|
||||
if (listen_sock < 0) {
|
||||
ESP_LOGE(kTag, "gateway=%u failed to create Modbus socket", channel.gateway_id);
|
||||
modbus_started = false;
|
||||
modbus_task_handle = nullptr;
|
||||
vTaskDelete(nullptr);
|
||||
modbus_last_error = "failed to create Modbus TCP socket";
|
||||
return;
|
||||
}
|
||||
modbus_listen_sock = listen_sock;
|
||||
|
||||
int reuse = 1;
|
||||
setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
|
||||
timeval timeout{};
|
||||
timeout.tv_sec = 1;
|
||||
timeout.tv_usec = 0;
|
||||
setsockopt(listen_sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
|
||||
|
||||
sockaddr_in address = {};
|
||||
address.sin_family = AF_INET;
|
||||
@@ -2460,15 +2847,13 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
if (bind(listen_sock, reinterpret_cast<sockaddr*>(&address), sizeof(address)) != 0 ||
|
||||
listen(listen_sock, 2) != 0) {
|
||||
ESP_LOGE(kTag, "gateway=%u failed to bind Modbus TCP port %u", channel.gateway_id, port);
|
||||
modbus_last_error = "failed to bind Modbus TCP port";
|
||||
close(listen_sock);
|
||||
modbus_started = false;
|
||||
modbus_task_handle = nullptr;
|
||||
vTaskDelete(nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(kTag, "gateway=%u Modbus TCP listening on port %u", channel.gateway_id, port);
|
||||
while (true) {
|
||||
while (!modbus_stop_requested) {
|
||||
sockaddr_in client_address = {};
|
||||
socklen_t client_len = sizeof(client_address);
|
||||
const int client_sock = accept(listen_sock, reinterpret_cast<sockaddr*>(&client_address),
|
||||
@@ -2476,14 +2861,19 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
if (client_sock < 0) {
|
||||
continue;
|
||||
}
|
||||
handleModbusClient(client_sock);
|
||||
setsockopt(client_sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
|
||||
modbus_client_sock = client_sock;
|
||||
handleModbusClient(config, client_sock);
|
||||
modbus_client_sock = -1;
|
||||
close(client_sock);
|
||||
}
|
||||
close(listen_sock);
|
||||
modbus_listen_sock = -1;
|
||||
}
|
||||
|
||||
void handleModbusClient(int client_sock) {
|
||||
void handleModbusClient(const GatewayModbusConfig& config, int client_sock) {
|
||||
uint8_t header[7] = {};
|
||||
while (RecvAll(client_sock, header, sizeof(header))) {
|
||||
while (!modbus_stop_requested && RecvAll(client_sock, header, sizeof(header))) {
|
||||
const uint16_t protocol_id = ReadBe16(&header[2]);
|
||||
const uint16_t length = ReadBe16(&header[4]);
|
||||
if (protocol_id != 0 || length < 2 || length > kGatewayModbusMaxPduBytes) {
|
||||
@@ -2494,175 +2884,265 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
if (!RecvAll(client_sock, pdu.data(), pdu.size()) || pdu.empty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (modbus_config.has_value() && modbus_config->unit_id != 0 &&
|
||||
header[6] != modbus_config->unit_id) {
|
||||
SendModbusException(client_sock, header, pdu[0], 0x0B);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((pdu[0] == 0x01 || pdu[0] == 0x02) && pdu.size() == 5) {
|
||||
const auto space = GatewayModbusReadSpaceForFunction(pdu[0]);
|
||||
const uint16_t start_address = ReadBe16(&pdu[1]);
|
||||
const uint16_t quantity = ReadBe16(&pdu[3]);
|
||||
if (!space.has_value() || quantity == 0 || quantity > kGatewayModbusMaxReadBits) {
|
||||
SendModbusException(client_sock, header, pdu[0], 0x03);
|
||||
continue;
|
||||
}
|
||||
const uint8_t byte_count = static_cast<uint8_t>((quantity + 7U) / 8U);
|
||||
std::vector<uint8_t> response(2 + byte_count, 0);
|
||||
response[0] = pdu[0];
|
||||
response[1] = byte_count;
|
||||
bool ok = true;
|
||||
for (uint16_t index = 0; index < quantity; ++index) {
|
||||
const auto human_address = static_cast<uint16_t>(
|
||||
GatewayModbusHumanAddressFromWire(space.value(), start_address + index));
|
||||
const auto value = readModbusBoolPoint(space.value(), human_address);
|
||||
if (!value.has_value()) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
if (value.value()) {
|
||||
response[2 + (index / 8)] |= static_cast<uint8_t>(1U << (index % 8));
|
||||
}
|
||||
}
|
||||
if (!ok) {
|
||||
SendModbusException(client_sock, header, pdu[0], 0x02);
|
||||
continue;
|
||||
}
|
||||
SendModbusFrame(client_sock, header, response);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((pdu[0] == 0x03 || pdu[0] == 0x04) && pdu.size() == 5) {
|
||||
const auto space = GatewayModbusReadSpaceForFunction(pdu[0]);
|
||||
const uint16_t start_address = ReadBe16(&pdu[1]);
|
||||
const uint16_t quantity = ReadBe16(&pdu[3]);
|
||||
if (!space.has_value() || quantity == 0 || quantity > kGatewayModbusMaxReadRegisters) {
|
||||
SendModbusException(client_sock, header, pdu[0], 0x03);
|
||||
continue;
|
||||
}
|
||||
std::vector<uint8_t> response(2 + quantity * 2);
|
||||
response[0] = pdu[0];
|
||||
response[1] = static_cast<uint8_t>(quantity * 2);
|
||||
bool ok = true;
|
||||
for (uint16_t index = 0; index < quantity; ++index) {
|
||||
const auto human_address = static_cast<uint16_t>(
|
||||
GatewayModbusHumanAddressFromWire(space.value(), start_address + index));
|
||||
const auto value = readModbusRegisterPoint(space.value(), human_address);
|
||||
if (!value.has_value()) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
WriteBe16(&response[2 + index * 2], value.value());
|
||||
}
|
||||
if (!ok) {
|
||||
SendModbusException(client_sock, header, pdu[0], 0x02);
|
||||
continue;
|
||||
}
|
||||
SendModbusFrame(client_sock, header, response);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pdu[0] == 0x05 && pdu.size() == 5) {
|
||||
const uint16_t wire_address = ReadBe16(&pdu[1]);
|
||||
const uint16_t raw_value = ReadBe16(&pdu[3]);
|
||||
if (raw_value != 0x0000 && raw_value != 0xFF00) {
|
||||
SendModbusException(client_sock, header, pdu[0], 0x03);
|
||||
continue;
|
||||
}
|
||||
const auto coil = static_cast<uint16_t>(GatewayModbusHumanAddressFromWire(
|
||||
GatewayModbusSpace::kCoil, wire_address));
|
||||
if (!writeModbusCoilPoint(coil, raw_value == 0xFF00)) {
|
||||
SendModbusException(client_sock, header, pdu[0], 0x04);
|
||||
continue;
|
||||
}
|
||||
SendModbusFrame(client_sock, header, pdu);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pdu[0] == 0x06 && pdu.size() == 5) {
|
||||
const uint16_t wire_register = ReadBe16(&pdu[1]);
|
||||
const uint16_t value = ReadBe16(&pdu[3]);
|
||||
const auto holding_register = static_cast<uint16_t>(GatewayModbusHumanAddressFromWire(
|
||||
GatewayModbusSpace::kHoldingRegister, wire_register));
|
||||
if (!writeModbusRegisterPoint(holding_register, value)) {
|
||||
SendModbusException(client_sock, header, pdu[0], 0x04);
|
||||
continue;
|
||||
}
|
||||
SendModbusFrame(client_sock, header, pdu);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pdu[0] == 0x0F && pdu.size() >= 6) {
|
||||
const uint16_t start_address = ReadBe16(&pdu[1]);
|
||||
const uint16_t quantity = ReadBe16(&pdu[3]);
|
||||
const uint8_t byte_count = pdu[5];
|
||||
if (quantity == 0 || quantity > kGatewayModbusMaxWriteBits ||
|
||||
pdu.size() != static_cast<size_t>(6 + byte_count) ||
|
||||
byte_count != static_cast<uint8_t>((quantity + 7U) / 8U)) {
|
||||
SendModbusException(client_sock, header, pdu[0], 0x03);
|
||||
continue;
|
||||
}
|
||||
bool ok = true;
|
||||
for (uint16_t index = 0; index < quantity; ++index) {
|
||||
const bool value = (pdu[6 + (index / 8)] & (1U << (index % 8))) != 0;
|
||||
const auto coil = static_cast<uint16_t>(GatewayModbusHumanAddressFromWire(
|
||||
GatewayModbusSpace::kCoil, start_address + index));
|
||||
if (!writeModbusCoilPoint(coil, value)) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!ok) {
|
||||
SendModbusException(client_sock, header, pdu[0], 0x04);
|
||||
continue;
|
||||
}
|
||||
std::vector<uint8_t> response(5);
|
||||
response[0] = pdu[0];
|
||||
WriteBe16(&response[1], start_address);
|
||||
WriteBe16(&response[3], quantity);
|
||||
SendModbusFrame(client_sock, header, response);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pdu[0] == 0x10 && pdu.size() >= 6) {
|
||||
const uint16_t start_register = ReadBe16(&pdu[1]);
|
||||
const uint16_t quantity = ReadBe16(&pdu[3]);
|
||||
const uint8_t byte_count = pdu[5];
|
||||
if (quantity == 0 || quantity > kGatewayModbusMaxWriteRegisters ||
|
||||
pdu.size() != static_cast<size_t>(6 + byte_count) ||
|
||||
byte_count != quantity * 2) {
|
||||
SendModbusException(client_sock, header, pdu[0], 0x03);
|
||||
continue;
|
||||
}
|
||||
bool ok = true;
|
||||
for (uint16_t index = 0; index < quantity; ++index) {
|
||||
const size_t offset = 6 + (index * 2);
|
||||
const uint16_t value = ReadBe16(&pdu[offset]);
|
||||
const auto holding_register = static_cast<uint16_t>(GatewayModbusHumanAddressFromWire(
|
||||
GatewayModbusSpace::kHoldingRegister, start_register + index));
|
||||
if (!writeModbusRegisterPoint(holding_register, value)) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!ok) {
|
||||
SendModbusException(client_sock, header, pdu[0], 0x04);
|
||||
continue;
|
||||
}
|
||||
std::vector<uint8_t> response(5);
|
||||
response[0] = pdu[0];
|
||||
WriteBe16(&response[1], start_register);
|
||||
WriteBe16(&response[3], quantity);
|
||||
SendModbusFrame(client_sock, header, response);
|
||||
continue;
|
||||
}
|
||||
|
||||
SendModbusException(client_sock, header, pdu[0], 0x01);
|
||||
SendModbusFrame(client_sock, header, processModbusPdu(config, header[6], pdu));
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t installSerialModbus(const GatewayModbusConfig& config) {
|
||||
const auto uart_port = static_cast<uart_port_t>(config.serial.uart_port);
|
||||
uart_config_t uart_config{};
|
||||
uart_config.baud_rate = static_cast<int>(config.serial.baudrate);
|
||||
uart_config.data_bits = UartWordLength(config.serial.data_bits);
|
||||
uart_config.parity = UartParity(config.serial.parity);
|
||||
uart_config.stop_bits = UartStopBits(config.serial.stop_bits);
|
||||
uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
|
||||
uart_config.source_clk = UART_SCLK_DEFAULT;
|
||||
|
||||
esp_err_t err = uart_param_config(uart_port, &uart_config);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
const int rts_pin = config.serial.rs485.enabled ? config.serial.rs485.de_pin
|
||||
: UART_PIN_NO_CHANGE;
|
||||
err = uart_set_pin(uart_port, config.serial.tx_pin, config.serial.rx_pin, rts_pin,
|
||||
UART_PIN_NO_CHANGE);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
err = uart_driver_install(uart_port, static_cast<int>(config.serial.rx_buffer_size),
|
||||
static_cast<int>(config.serial.tx_buffer_size), 0, nullptr, 0);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
if (config.serial.rs485.enabled) {
|
||||
err = uart_set_mode(uart_port, UART_MODE_RS485_HALF_DUPLEX);
|
||||
if (err != ESP_OK) {
|
||||
uart_driver_delete(uart_port);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
modbus_uart_port = config.serial.uart_port;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void modbusSerialTaskLoop(const GatewayModbusConfig& config) {
|
||||
const esp_err_t err = installSerialModbus(config);
|
||||
if (err != ESP_OK) {
|
||||
modbus_last_error = "failed to install Modbus serial UART";
|
||||
ESP_LOGE(kTag, "gateway=%u failed to install Modbus serial UART%d: %s",
|
||||
channel.gateway_id, config.serial.uart_port, esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
ESP_LOGI(kTag, "gateway=%u Modbus %s listening on UART%d baud=%lu", channel.gateway_id,
|
||||
GatewayModbusTransportIsAscii(config.transport) ? "ASCII" : "RTU",
|
||||
config.serial.uart_port, static_cast<unsigned long>(config.serial.baudrate));
|
||||
if (GatewayModbusTransportIsAscii(config.transport)) {
|
||||
modbusAsciiTaskLoop(config);
|
||||
} else {
|
||||
modbusRtuTaskLoop(config);
|
||||
}
|
||||
uart_driver_delete(static_cast<uart_port_t>(config.serial.uart_port));
|
||||
}
|
||||
|
||||
void modbusRtuTaskLoop(const GatewayModbusConfig& config) {
|
||||
const auto uart_port = static_cast<uart_port_t>(config.serial.uart_port);
|
||||
std::vector<uint8_t> frame;
|
||||
std::array<uint8_t, 128> read_buffer{};
|
||||
const TickType_t timeout = pdMS_TO_TICKS(config.serial.response_timeout_ms);
|
||||
while (!modbus_stop_requested) {
|
||||
const int read_len = uart_read_bytes(uart_port, read_buffer.data(), read_buffer.size(), timeout);
|
||||
if (read_len > 0) {
|
||||
frame.insert(frame.end(), read_buffer.begin(), read_buffer.begin() + read_len);
|
||||
if (!frame.empty() && frame.front() == '@' &&
|
||||
std::find(frame.begin(), frame.end(), '\n') != frame.end()) {
|
||||
const std::string line(frame.begin(), frame.end());
|
||||
handleModbusManagementLine(config.serial.uart_port, line);
|
||||
frame.clear();
|
||||
} else if (frame.size() > 512) {
|
||||
frame.clear();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (frame.empty() || frame.front() == '@') {
|
||||
continue;
|
||||
}
|
||||
handleModbusRtuFrame(config, frame);
|
||||
frame.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void handleModbusRtuFrame(const GatewayModbusConfig& config, const std::vector<uint8_t>& frame) {
|
||||
if (frame.size() < 4) {
|
||||
return;
|
||||
}
|
||||
const uint16_t received_crc = static_cast<uint16_t>(frame[frame.size() - 2] |
|
||||
(frame[frame.size() - 1] << 8));
|
||||
if (ModbusCrc16(frame.data(), frame.size() - 2) != received_crc) {
|
||||
return;
|
||||
}
|
||||
const uint8_t unit_id = frame[0];
|
||||
if (unit_id == 0) {
|
||||
return;
|
||||
}
|
||||
const std::vector<uint8_t> pdu(frame.begin() + 1, frame.end() - 2);
|
||||
const auto response_pdu = processModbusPdu(config, unit_id, pdu);
|
||||
if (response_pdu.empty()) {
|
||||
return;
|
||||
}
|
||||
std::vector<uint8_t> response;
|
||||
response.reserve(1 + response_pdu.size() + 2);
|
||||
response.push_back(unit_id);
|
||||
response.insert(response.end(), response_pdu.begin(), response_pdu.end());
|
||||
const uint16_t crc = ModbusCrc16(response.data(), response.size());
|
||||
response.push_back(static_cast<uint8_t>(crc & 0xFF));
|
||||
response.push_back(static_cast<uint8_t>((crc >> 8) & 0xFF));
|
||||
uart_write_bytes(static_cast<uart_port_t>(config.serial.uart_port), response.data(),
|
||||
response.size());
|
||||
}
|
||||
|
||||
void modbusAsciiTaskLoop(const GatewayModbusConfig& config) {
|
||||
const auto uart_port = static_cast<uart_port_t>(config.serial.uart_port);
|
||||
std::string line;
|
||||
std::array<uint8_t, 128> read_buffer{};
|
||||
const TickType_t timeout = pdMS_TO_TICKS(config.serial.response_timeout_ms);
|
||||
while (!modbus_stop_requested) {
|
||||
const int read_len = uart_read_bytes(uart_port, read_buffer.data(), read_buffer.size(), timeout);
|
||||
if (read_len <= 0) {
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < read_len; ++i) {
|
||||
const char ch = static_cast<char>(read_buffer[i]);
|
||||
if (line.empty()) {
|
||||
if (ch != ':' && ch != '@') {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
line.push_back(ch);
|
||||
if (ch == '\n') {
|
||||
if (LineStartsWith(line, kModbusManagementPrefix)) {
|
||||
handleModbusManagementLine(config.serial.uart_port, line);
|
||||
} else if (!line.empty() && line.front() == ':') {
|
||||
handleModbusAsciiFrame(config, line);
|
||||
}
|
||||
line.clear();
|
||||
} else if (line.size() > 1024) {
|
||||
line.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void handleModbusAsciiFrame(const GatewayModbusConfig& config, std::string_view line) {
|
||||
const auto decoded = DecodeModbusAsciiLine(line);
|
||||
if (!decoded.has_value() || decoded->size() < 4) {
|
||||
return;
|
||||
}
|
||||
const uint8_t unit_id = decoded->front();
|
||||
if (unit_id == 0) {
|
||||
return;
|
||||
}
|
||||
const std::vector<uint8_t> pdu(decoded->begin() + 1, decoded->end() - 1);
|
||||
const auto response_pdu = processModbusPdu(config, unit_id, pdu);
|
||||
if (response_pdu.empty()) {
|
||||
return;
|
||||
}
|
||||
std::vector<uint8_t> response;
|
||||
response.reserve(1 + response_pdu.size() + 1);
|
||||
response.push_back(unit_id);
|
||||
response.insert(response.end(), response_pdu.begin(), response_pdu.end());
|
||||
response.push_back(ModbusAsciiLrc(response.data(), response.size()));
|
||||
const std::string encoded = EncodeModbusAsciiLine(response);
|
||||
uart_write_bytes(static_cast<uart_port_t>(config.serial.uart_port), encoded.data(),
|
||||
encoded.size());
|
||||
}
|
||||
|
||||
void handleModbusManagementLine(int uart_port, std::string_view line) {
|
||||
while (!line.empty() && (line.back() == '\r' || line.back() == '\n')) {
|
||||
line.remove_suffix(1);
|
||||
}
|
||||
if (!LineStartsWith(line, kModbusManagementPrefix)) {
|
||||
return;
|
||||
}
|
||||
line.remove_prefix(std::char_traits<char>::length(kModbusManagementPrefix));
|
||||
while (!line.empty() && line.front() == ' ') {
|
||||
line.remove_prefix(1);
|
||||
}
|
||||
cJSON* root = line.empty() ? cJSON_CreateObject() : cJSON_ParseWithLength(line.data(), line.size());
|
||||
if (root == nullptr || !cJSON_IsObject(root)) {
|
||||
cJSON_Delete(root);
|
||||
writeModbusManagementResponse(uart_port, false, "unknown", "invalid JSON");
|
||||
return;
|
||||
}
|
||||
const auto gateway_id = JsonGatewayId(root);
|
||||
if (gateway_id.has_value() && gateway_id.value() != channel.gateway_id) {
|
||||
cJSON_Delete(root);
|
||||
writeModbusManagementResponse(uart_port, false, "unknown", "gateway id mismatch");
|
||||
return;
|
||||
}
|
||||
const char* action_raw = JsonString(root, "action");
|
||||
const std::string action = action_raw == nullptr ? "modbus_status" : action_raw;
|
||||
if (action == "modbus_config") {
|
||||
const cJSON* modbus_node = cJSON_GetObjectItemCaseSensitive(root, "modbus");
|
||||
if (modbus_node == nullptr) {
|
||||
modbus_node = root;
|
||||
}
|
||||
const DaliValue modbus_value = FromCjson(modbus_node);
|
||||
const auto parsed = GatewayModbusConfigFromValue(&modbus_value);
|
||||
if (!parsed.has_value()) {
|
||||
cJSON_Delete(root);
|
||||
writeModbusManagementResponse(uart_port, false, action.c_str(), "invalid modbus config");
|
||||
return;
|
||||
}
|
||||
const esp_err_t err = saveModbusConfig(parsed.value());
|
||||
cJSON_Delete(root);
|
||||
if (err != ESP_OK) {
|
||||
writeModbusManagementResponse(uart_port, false, action.c_str(), esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
writeModbusManagementResponse(uart_port, true, action.c_str(), nullptr);
|
||||
modbus_restart_requested = true;
|
||||
modbus_stop_requested = true;
|
||||
return;
|
||||
}
|
||||
if (action == "modbus_stop") {
|
||||
cJSON_Delete(root);
|
||||
writeModbusManagementResponse(uart_port, true, action.c_str(), nullptr);
|
||||
modbus_stop_requested = true;
|
||||
return;
|
||||
}
|
||||
if (action == "modbus_start" || action == "modbus_status") {
|
||||
cJSON_Delete(root);
|
||||
writeModbusManagementResponse(uart_port, true, action.c_str(), nullptr);
|
||||
return;
|
||||
}
|
||||
cJSON_Delete(root);
|
||||
writeModbusManagementResponse(uart_port, false, action.c_str(), "unknown action");
|
||||
}
|
||||
|
||||
void writeModbusManagementResponse(int uart_port, const bool ok, const char* action,
|
||||
const char* error) const {
|
||||
cJSON* root = cJSON_CreateObject();
|
||||
cJSON_AddBoolToObject(root, "ok", ok);
|
||||
cJSON_AddNumberToObject(root, "gw", channel.gateway_id);
|
||||
cJSON_AddStringToObject(root, "action", action == nullptr ? "unknown" : action);
|
||||
if (const auto config = activeModbusConfig()) {
|
||||
cJSON_AddStringToObject(root, "transport", config->transport.c_str());
|
||||
cJSON_AddNumberToObject(root, "unitID", config->unit_id);
|
||||
if (GatewayModbusTransportIsSerial(config->transport)) {
|
||||
cJSON_AddNumberToObject(root, "uartPort", config->serial.uart_port);
|
||||
cJSON_AddNumberToObject(root, "baudrate", config->serial.baudrate);
|
||||
} else {
|
||||
cJSON_AddNumberToObject(root, "port", config->port);
|
||||
}
|
||||
}
|
||||
if (error != nullptr) {
|
||||
cJSON_AddStringToObject(root, "error", error);
|
||||
}
|
||||
const std::string body = "@DALIGW " + PrintJson(root) + "\n";
|
||||
cJSON_Delete(root);
|
||||
uart_write_bytes(static_cast<uart_port_t>(uart_port), body.data(), body.size());
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
GatewayBridgeService::GatewayBridgeService(DaliDomainService& dali_domain,
|
||||
@@ -2696,8 +3176,9 @@ esp_err_t GatewayBridgeService::start() {
|
||||
|
||||
if (config_.modbus_enabled && config_.modbus_startup_enabled) {
|
||||
std::set<uint16_t> used_ports;
|
||||
std::set<int> used_uarts;
|
||||
for (const auto& runtime : runtimes_) {
|
||||
const esp_err_t err = runtime->startModbus(&used_ports);
|
||||
const esp_err_t err = runtime->startModbus(&used_ports, &used_uarts);
|
||||
if (err != ESP_OK && err != ESP_ERR_NOT_FOUND && err != ESP_ERR_NOT_SUPPORTED) {
|
||||
ESP_LOGW(kTag, "gateway=%u Modbus startup skipped: %s", runtime->channel.gateway_id,
|
||||
esp_err_to_name(err));
|
||||
@@ -2981,7 +3462,14 @@ GatewayBridgeHttpResponse GatewayBridgeService::handlePost(
|
||||
if (action == "modbus_start") {
|
||||
const esp_err_t err = runtime->startModbus();
|
||||
if (err != ESP_OK) {
|
||||
return ErrorResponse(err, "failed to start Modbus TCP bridge");
|
||||
return ErrorResponse(err, "failed to start Modbus bridge");
|
||||
}
|
||||
return handleGet("modbus", gateway_id.value());
|
||||
}
|
||||
if (action == "modbus_stop") {
|
||||
const esp_err_t err = runtime->stopModbus();
|
||||
if (err != ESP_OK) {
|
||||
return ErrorResponse(err, "failed to stop Modbus bridge");
|
||||
}
|
||||
return handleGet("modbus", gateway_id.value());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user