feat(gateway): Update SDK configuration and add 485 control bridge
- Changed flash size configuration from 16MB to 4MB and updated partition table filename. - Introduced two gateway channels with UART configurations for communication. - Added support for gateway cache and startup services including BLE and Wi-Fi. - Enabled SPI RAM and configured its parameters for better memory management. - Enhanced the gateway bridge service to handle generated Modbus points more efficiently. - Refactored the gateway Modbus component to improve point management and added new methods for point description and generation. - Implemented a new Gateway485ControlBridge for handling 485 control communication with UART. - Added necessary files for the 485 control bridge including configuration and implementation. Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
idf_component_register(
|
||||
SRCS "src/gateway_485_control.cpp"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES esp_driver_uart freertos gateway_controller log
|
||||
)
|
||||
|
||||
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
namespace gateway {
|
||||
|
||||
class GatewayController;
|
||||
|
||||
struct Gateway485ControlBridgeConfig {
|
||||
bool enabled{false};
|
||||
int tx_pin{-1};
|
||||
int rx_pin{-1};
|
||||
uint32_t baudrate{9600};
|
||||
size_t rx_buffer_size{256};
|
||||
size_t tx_buffer_size{256};
|
||||
uint32_t read_timeout_ms{20};
|
||||
uint32_t write_timeout_ms{20};
|
||||
uint32_t task_stack_size{4096};
|
||||
UBaseType_t task_priority{4};
|
||||
};
|
||||
|
||||
class Gateway485ControlBridge {
|
||||
public:
|
||||
Gateway485ControlBridge(GatewayController& controller,
|
||||
Gateway485ControlBridgeConfig config = {});
|
||||
|
||||
esp_err_t start();
|
||||
|
||||
private:
|
||||
static void TaskEntry(void* arg);
|
||||
void taskLoop();
|
||||
void handleBytes(const uint8_t* data, size_t len);
|
||||
void handleGatewayNotification(const std::vector<uint8_t>& frame);
|
||||
|
||||
GatewayController& controller_;
|
||||
Gateway485ControlBridgeConfig config_;
|
||||
TaskHandle_t task_handle_{nullptr};
|
||||
bool started_{false};
|
||||
};
|
||||
|
||||
} // namespace gateway
|
||||
@@ -0,0 +1,134 @@
|
||||
#include "gateway_485_control.hpp"
|
||||
|
||||
#include "gateway_controller.hpp"
|
||||
|
||||
#include "driver/uart.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace gateway {
|
||||
|
||||
namespace {
|
||||
constexpr const char* kTag = "gateway_485";
|
||||
constexpr uart_port_t kControlUart = UART_NUM_0;
|
||||
constexpr size_t kCommandFrameMinLen = 7;
|
||||
|
||||
int EffectivePin(int pin) {
|
||||
return pin >= 0 ? pin : UART_PIN_NO_CHANGE;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Gateway485ControlBridge::Gateway485ControlBridge(GatewayController& controller,
|
||||
Gateway485ControlBridgeConfig config)
|
||||
: controller_(controller), config_(config) {}
|
||||
|
||||
esp_err_t Gateway485ControlBridge::start() {
|
||||
if (started_) {
|
||||
return ESP_OK;
|
||||
}
|
||||
if (!config_.enabled) {
|
||||
ESP_LOGI(kTag, "UART0 control bridge disabled");
|
||||
return ESP_OK;
|
||||
}
|
||||
if (uart_is_driver_installed(kControlUart)) {
|
||||
ESP_LOGE(kTag, "UART0 driver already installed; move console or other users off UART0");
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
uart_config_t uart_config{};
|
||||
uart_config.baud_rate = static_cast<int>(config_.baudrate);
|
||||
uart_config.data_bits = UART_DATA_8_BITS;
|
||||
uart_config.parity = UART_PARITY_DISABLE;
|
||||
uart_config.stop_bits = UART_STOP_BITS_1;
|
||||
uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
|
||||
uart_config.source_clk = UART_SCLK_DEFAULT;
|
||||
|
||||
esp_err_t err = uart_param_config(kControlUart, &uart_config);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(kTag, "failed to configure UART0: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
err = uart_set_pin(kControlUart, EffectivePin(config_.tx_pin), EffectivePin(config_.rx_pin),
|
||||
UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(kTag, "failed to set UART0 pins: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
err = uart_driver_install(kControlUart, static_cast<int>(config_.rx_buffer_size),
|
||||
static_cast<int>(config_.tx_buffer_size), 0, nullptr, 0);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(kTag, "failed to install UART0 driver: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
controller_.addNotificationSink(
|
||||
[this](const std::vector<uint8_t>& frame) { handleGatewayNotification(frame); });
|
||||
|
||||
const BaseType_t created = xTaskCreate(&Gateway485ControlBridge::TaskEntry,
|
||||
"gateway_485_ctrl",
|
||||
static_cast<uint32_t>(config_.task_stack_size), this,
|
||||
config_.task_priority, &task_handle_);
|
||||
if (created != pdPASS) {
|
||||
uart_driver_delete(kControlUart);
|
||||
task_handle_ = nullptr;
|
||||
ESP_LOGE(kTag, "failed to create 485 control task");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
started_ = true;
|
||||
ESP_LOGI(kTag, "485 control bridge started baud=%lu", static_cast<unsigned long>(config_.baudrate));
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void Gateway485ControlBridge::TaskEntry(void* arg) {
|
||||
static_cast<Gateway485ControlBridge*>(arg)->taskLoop();
|
||||
}
|
||||
|
||||
void Gateway485ControlBridge::taskLoop() {
|
||||
std::vector<uint8_t> read_buffer(std::max<size_t>(config_.rx_buffer_size, 64));
|
||||
std::vector<uint8_t> pending;
|
||||
pending.reserve(std::max<size_t>(config_.rx_buffer_size, 64));
|
||||
const TickType_t timeout = pdMS_TO_TICKS(config_.read_timeout_ms);
|
||||
|
||||
while (true) {
|
||||
const int read_len = uart_read_bytes(kControlUart, read_buffer.data(), read_buffer.size(), timeout);
|
||||
if (read_len > 0) {
|
||||
pending.insert(pending.end(), read_buffer.begin(), read_buffer.begin() + read_len);
|
||||
continue;
|
||||
}
|
||||
if (!pending.empty()) {
|
||||
handleBytes(pending.data(), pending.size());
|
||||
pending.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Gateway485ControlBridge::handleBytes(const uint8_t* data, size_t len) {
|
||||
if (data == nullptr || len < kCommandFrameMinLen) {
|
||||
return;
|
||||
}
|
||||
if (data[0] != 0x28 || data[1] != 0x01) {
|
||||
ESP_LOGD(kTag, "ignored non-gateway UART0 burst len=%u", static_cast<unsigned>(len));
|
||||
return;
|
||||
}
|
||||
|
||||
controller_.enqueueCommandFrame(std::vector<uint8_t>(data, data + len));
|
||||
}
|
||||
|
||||
void Gateway485ControlBridge::handleGatewayNotification(const std::vector<uint8_t>& frame) {
|
||||
if (!started_ || frame.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int written = uart_write_bytes(kControlUart, frame.data(), frame.size());
|
||||
if (written < 0 || static_cast<size_t>(written) != frame.size()) {
|
||||
ESP_LOGW(kTag, "failed to write UART0 notification len=%u", static_cast<unsigned>(frame.size()));
|
||||
return;
|
||||
}
|
||||
if (uart_wait_tx_done(kControlUart, pdMS_TO_TICKS(config_.write_timeout_ms)) != ESP_OK) {
|
||||
ESP_LOGW(kTag, "timed out flushing UART0 notification len=%u", static_cast<unsigned>(frame.size()));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace gateway
|
||||
@@ -257,9 +257,13 @@ bool SnapshotHasDeviceType(const DaliDomainSnapshot& snapshot, int device_type)
|
||||
}
|
||||
|
||||
std::optional<bool> SnapshotBoolValue(const DaliDomainSnapshot& snapshot,
|
||||
const std::string& key) {
|
||||
const auto found = snapshot.bools.find(key);
|
||||
return found == snapshot.bools.end() ? std::nullopt : std::optional<bool>(found->second);
|
||||
std::string_view key) {
|
||||
for (const auto& entry : snapshot.bools) {
|
||||
if (std::string_view(entry.first) == key) {
|
||||
return entry.second;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<int> SnapshotIntValue(const DaliDomainSnapshot& snapshot,
|
||||
@@ -1258,11 +1262,13 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
}
|
||||
|
||||
std::optional<DaliDomainSnapshot> diagnosticSnapshotLocked(int short_address,
|
||||
const std::string& kind) {
|
||||
std::string_view kind) {
|
||||
if (!ValidShortAddress(short_address) || kind.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const std::string key = kind + ":" + std::to_string(short_address);
|
||||
std::string key(kind.data(), kind.size());
|
||||
key += ":";
|
||||
key += std::to_string(short_address);
|
||||
const TickType_t now = xTaskGetTickCount();
|
||||
const auto cached = diagnostic_snapshot_cache.find(key);
|
||||
if (cached != diagnostic_snapshot_cache.end() &&
|
||||
@@ -1291,8 +1297,8 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
std::optional<bool> readSnapshotBoolLocked(int short_address, const std::string& kind,
|
||||
const std::string& bool_key) {
|
||||
std::optional<bool> readSnapshotBoolLocked(int short_address, std::string_view kind,
|
||||
std::string_view bool_key) {
|
||||
const auto snapshot = diagnosticSnapshotLocked(short_address, kind);
|
||||
if (!snapshot.has_value()) {
|
||||
return std::nullopt;
|
||||
@@ -1629,7 +1635,7 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
return point;
|
||||
}
|
||||
|
||||
bool shouldPublishGeneratedBacnetPointLocked(const GatewayModbusPointBinding& point) {
|
||||
bool shouldPublishGeneratedBacnetPointLocked(const GatewayModbusPoint& point) {
|
||||
if (!point.generated || point.space != GatewayModbusSpace::kDiscreteInput ||
|
||||
point.access != GatewayModbusAccess::kReadOnly ||
|
||||
!ValidShortAddress(point.short_address)) {
|
||||
@@ -1651,27 +1657,38 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
if (modbus == nullptr) {
|
||||
return bindings;
|
||||
}
|
||||
for (const auto& point : modbus->describePoints()) {
|
||||
if (!shouldPublishGeneratedBacnetPointLocked(point)) {
|
||||
std::vector<GatewayModbusPoint> generated_points;
|
||||
generated_points.reserve(192);
|
||||
for (const auto& inventory_entry : discovery_inventory) {
|
||||
if (!ValidShortAddress(inventory_entry.first)) {
|
||||
continue;
|
||||
}
|
||||
const auto* discovery = findDiscoveryEntryLocked(point.short_address);
|
||||
const auto object_instance = generatedBacnetBinaryInputInstance(point.address);
|
||||
if (discovery == nullptr || !object_instance.has_value()) {
|
||||
continue;
|
||||
generated_points.clear();
|
||||
modbus->appendGeneratedPointsForShortAddress(
|
||||
static_cast<uint8_t>(inventory_entry.first), &generated_points);
|
||||
for (const auto& point : generated_points) {
|
||||
if (!shouldPublishGeneratedBacnetPointLocked(point)) {
|
||||
continue;
|
||||
}
|
||||
const auto* discovery = findDiscoveryEntryLocked(point.short_address);
|
||||
const auto object_instance = generatedBacnetBinaryInputInstance(point.address);
|
||||
if (discovery == nullptr || !object_instance.has_value()) {
|
||||
continue;
|
||||
}
|
||||
const auto binding = modbus->describePoint(point);
|
||||
const bool out_of_service = !discovery->online;
|
||||
bindings.push_back(GatewayBacnetObjectBinding{channel.gateway_id,
|
||||
binding.id,
|
||||
binding.name,
|
||||
BridgeObjectType::binaryInput,
|
||||
object_instance.value(),
|
||||
"presentValue",
|
||||
out_of_service,
|
||||
out_of_service
|
||||
? kBacnetReliabilityCommunicationFailure
|
||||
: kBacnetReliabilityNoFaultDetected,
|
||||
true});
|
||||
}
|
||||
const bool out_of_service = !discovery->online;
|
||||
bindings.push_back(GatewayBacnetObjectBinding{channel.gateway_id,
|
||||
point.id,
|
||||
point.name,
|
||||
BridgeObjectType::binaryInput,
|
||||
object_instance.value(),
|
||||
"presentValue",
|
||||
out_of_service,
|
||||
out_of_service
|
||||
? kBacnetReliabilityCommunicationFailure
|
||||
: kBacnetReliabilityNoFaultDetected,
|
||||
true});
|
||||
}
|
||||
return bindings;
|
||||
}
|
||||
@@ -2135,47 +2152,59 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
cJSON_AddItemToArray(bindings, item);
|
||||
}
|
||||
if (modbus != nullptr) {
|
||||
for (const auto& point : modbus->describePoints()) {
|
||||
if (!shouldPublishGeneratedBacnetPointLocked(point)) {
|
||||
std::vector<GatewayModbusPoint> generated_points;
|
||||
generated_points.reserve(192);
|
||||
for (const auto& inventory_entry : discovery_inventory) {
|
||||
if (!ValidShortAddress(inventory_entry.first)) {
|
||||
continue;
|
||||
}
|
||||
const auto object_instance = generatedBacnetBinaryInputInstance(point.address);
|
||||
const auto* discovery = findDiscoveryEntryLocked(point.short_address);
|
||||
if (!object_instance.has_value() || discovery == nullptr) {
|
||||
continue;
|
||||
generated_points.clear();
|
||||
modbus->appendGeneratedPointsForShortAddress(
|
||||
static_cast<uint8_t>(inventory_entry.first), &generated_points);
|
||||
for (const auto& point : generated_points) {
|
||||
if (!shouldPublishGeneratedBacnetPointLocked(point)) {
|
||||
continue;
|
||||
}
|
||||
const auto object_instance = generatedBacnetBinaryInputInstance(point.address);
|
||||
const auto* discovery = findDiscoveryEntryLocked(point.short_address);
|
||||
if (!object_instance.has_value() || discovery == nullptr) {
|
||||
continue;
|
||||
}
|
||||
cJSON* item = cJSON_CreateObject();
|
||||
if (item == nullptr) {
|
||||
continue;
|
||||
}
|
||||
const auto binding = modbus->describePoint(point);
|
||||
cJSON_AddStringToObject(item, "model", binding.id.c_str());
|
||||
cJSON_AddStringToObject(item, "name", binding.name.c_str());
|
||||
cJSON_AddStringToObject(item, "objectType", "binaryInput");
|
||||
cJSON_AddNumberToObject(item, "objectInstance", object_instance.value());
|
||||
cJSON_AddStringToObject(item, "property", "presentValue");
|
||||
cJSON_AddBoolToObject(item, "generated", true);
|
||||
cJSON_AddStringToObject(item, "generatedKind",
|
||||
GatewayModbusGeneratedKindToString(binding.generated_kind));
|
||||
cJSON_AddNumberToObject(item, "shortAddress", binding.short_address);
|
||||
if (!binding.diagnostic_snapshot.empty()) {
|
||||
cJSON_AddStringToObject(item, "diagnosticSnapshot",
|
||||
binding.diagnostic_snapshot.c_str());
|
||||
}
|
||||
if (!binding.diagnostic_bool.empty()) {
|
||||
cJSON_AddStringToObject(item, "diagnosticBool", binding.diagnostic_bool.c_str());
|
||||
}
|
||||
if (binding.diagnostic_device_type >= 0) {
|
||||
cJSON_AddNumberToObject(item, "diagnosticDeviceType",
|
||||
binding.diagnostic_device_type);
|
||||
}
|
||||
const bool out_of_service = !discovery->online;
|
||||
cJSON_AddBoolToObject(item, "outOfService", out_of_service);
|
||||
cJSON_AddStringToObject(item, "reliability",
|
||||
BacnetReliabilityToString(out_of_service
|
||||
? kBacnetReliabilityCommunicationFailure
|
||||
: kBacnetReliabilityNoFaultDetected));
|
||||
cJSON_AddStringToObject(item, "inventoryState",
|
||||
DiscoveryStateString(discovery->online));
|
||||
cJSON_AddItemToArray(bindings, item);
|
||||
}
|
||||
cJSON* item = cJSON_CreateObject();
|
||||
if (item == nullptr) {
|
||||
continue;
|
||||
}
|
||||
cJSON_AddStringToObject(item, "model", point.id.c_str());
|
||||
cJSON_AddStringToObject(item, "name", point.name.c_str());
|
||||
cJSON_AddStringToObject(item, "objectType", "binaryInput");
|
||||
cJSON_AddNumberToObject(item, "objectInstance", object_instance.value());
|
||||
cJSON_AddStringToObject(item, "property", "presentValue");
|
||||
cJSON_AddBoolToObject(item, "generated", true);
|
||||
cJSON_AddStringToObject(item, "generatedKind",
|
||||
GatewayModbusGeneratedKindToString(point.generated_kind));
|
||||
cJSON_AddNumberToObject(item, "shortAddress", point.short_address);
|
||||
if (!point.diagnostic_snapshot.empty()) {
|
||||
cJSON_AddStringToObject(item, "diagnosticSnapshot",
|
||||
point.diagnostic_snapshot.c_str());
|
||||
}
|
||||
if (!point.diagnostic_bool.empty()) {
|
||||
cJSON_AddStringToObject(item, "diagnosticBool", point.diagnostic_bool.c_str());
|
||||
}
|
||||
if (point.diagnostic_device_type >= 0) {
|
||||
cJSON_AddNumberToObject(item, "diagnosticDeviceType",
|
||||
point.diagnostic_device_type);
|
||||
}
|
||||
const bool out_of_service = !discovery->online;
|
||||
cJSON_AddBoolToObject(item, "outOfService", out_of_service);
|
||||
cJSON_AddStringToObject(item, "reliability",
|
||||
BacnetReliabilityToString(out_of_service
|
||||
? kBacnetReliabilityCommunicationFailure
|
||||
: kBacnetReliabilityNoFaultDetected));
|
||||
cJSON_AddStringToObject(item, "inventoryState", DiscoveryStateString(discovery->online));
|
||||
cJSON_AddItemToArray(bindings, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,12 +119,14 @@ struct GatewayModbusPoint {
|
||||
std::string name;
|
||||
bool generated{false};
|
||||
GatewayModbusGeneratedKind generated_kind{GatewayModbusGeneratedKind::kNone};
|
||||
const char* generated_suffix{"point"};
|
||||
const char* generated_name{"point"};
|
||||
int short_address{-1};
|
||||
std::string model_id;
|
||||
BridgeOperation operation{BridgeOperation::unknown};
|
||||
std::optional<int> bit_index;
|
||||
std::string diagnostic_snapshot;
|
||||
std::string diagnostic_bool;
|
||||
const char* diagnostic_snapshot{""};
|
||||
const char* diagnostic_bool{""};
|
||||
int diagnostic_device_type{-1};
|
||||
};
|
||||
|
||||
@@ -165,8 +167,12 @@ class GatewayModbusBridge {
|
||||
void rebuildMap();
|
||||
std::optional<GatewayModbusPoint> findPoint(GatewayModbusSpace space,
|
||||
uint16_t address) const;
|
||||
GatewayModbusPointBinding describePoint(const GatewayModbusPoint& point) const;
|
||||
void appendGeneratedPointsForShortAddress(uint8_t short_address,
|
||||
std::vector<GatewayModbusPoint>* points) const;
|
||||
std::vector<GatewayModbusPointBinding> describePoints() const;
|
||||
std::vector<GatewayModbusPointBinding> describeHoldingRegisters() const;
|
||||
const std::vector<GatewayModbusPoint>& points() const;
|
||||
|
||||
DaliBridgeResult readModelPoint(const GatewayModbusPoint& point) const;
|
||||
DaliBridgeResult writeRegisterPoint(const GatewayModbusPoint& point, uint16_t value) const;
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <map>
|
||||
#include <utility>
|
||||
|
||||
namespace gateway {
|
||||
@@ -283,6 +282,16 @@ constexpr GeneratedDiagnosticBitSpec kGeneratedDiagnosticBitsTail[] = {
|
||||
{119, 1, "dt1", "controlGearFailure", "dt1_control_gear_failure", "DT1 control gear failure"},
|
||||
};
|
||||
|
||||
constexpr size_t kGeneratedDiagnosticBitCount =
|
||||
sizeof(kGeneratedDiagnosticBits) / sizeof(kGeneratedDiagnosticBits[0]) +
|
||||
sizeof(kGeneratedDiagnosticBitsTail) / sizeof(kGeneratedDiagnosticBitsTail[0]);
|
||||
constexpr size_t kGeneratedPointsPerShort = kGeneratedCoils.size() +
|
||||
kGeneratedDiscreteInputs.size() +
|
||||
kGeneratedHoldingRegisters.size() +
|
||||
kGeneratedInputRegisters.size() +
|
||||
kGeneratedDiagnosticBitCount;
|
||||
constexpr size_t kGeneratedPointCount = kShortAddressCount * kGeneratedPointsPerShort;
|
||||
|
||||
uint16_t baseForSpace(GatewayModbusSpace space) {
|
||||
switch (space) {
|
||||
case GatewayModbusSpace::kCoil:
|
||||
@@ -338,62 +347,165 @@ std::string generatedName(uint8_t short_address, const char* name) {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void addGeneratedPoint(std::map<PointKey, GatewayModbusPoint>* points, uint8_t short_address,
|
||||
const GeneratedPointSpec& spec) {
|
||||
if (points == nullptr) {
|
||||
return;
|
||||
const char* literalOrEmpty(const char* value) {
|
||||
return value == nullptr ? "" : value;
|
||||
}
|
||||
|
||||
PointKey keyForPoint(const GatewayModbusPoint& point) {
|
||||
return PointKey{point.space, point.address};
|
||||
}
|
||||
|
||||
bool pointLess(const GatewayModbusPoint& lhs, const GatewayModbusPoint& rhs) {
|
||||
return keyForPoint(lhs) < keyForPoint(rhs);
|
||||
}
|
||||
|
||||
bool pointKeyEqual(const GatewayModbusPoint& lhs, const GatewayModbusPoint& rhs) {
|
||||
return lhs.space == rhs.space && lhs.address == rhs.address;
|
||||
}
|
||||
|
||||
bool pointKeyEqual(const GatewayModbusPoint& point, const PointKey& key) {
|
||||
return point.space == key.space && point.address == key.address;
|
||||
}
|
||||
|
||||
std::vector<GatewayModbusPoint>::const_iterator findStoredPoint(
|
||||
const std::vector<GatewayModbusPoint>& points, PointKey key) {
|
||||
const auto found = std::lower_bound(
|
||||
points.begin(), points.end(), key,
|
||||
[](const GatewayModbusPoint& point, const PointKey& value) {
|
||||
return keyForPoint(point) < value;
|
||||
});
|
||||
if (found == points.end() || !pointKeyEqual(*found, key)) {
|
||||
return points.end();
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
GatewayModbusPoint makeGeneratedPoint(uint8_t short_address,
|
||||
const GeneratedPointSpec& spec) {
|
||||
const uint16_t address = static_cast<uint16_t>(baseForSpace(spec.space) +
|
||||
short_address * kShortStride + spec.offset);
|
||||
GatewayModbusPoint point;
|
||||
point.space = spec.space;
|
||||
point.access = spec.access;
|
||||
point.address = address;
|
||||
point.id = generatedId(short_address, spec.suffix);
|
||||
point.name = generatedName(short_address, spec.name);
|
||||
point.generated = true;
|
||||
point.generated_kind = spec.kind;
|
||||
point.generated_suffix = spec.suffix == nullptr ? "point" : spec.suffix;
|
||||
point.generated_name = spec.name == nullptr ? "point" : spec.name;
|
||||
point.short_address = short_address;
|
||||
(*points)[PointKey{spec.space, address}] = std::move(point);
|
||||
return point;
|
||||
}
|
||||
|
||||
void addGeneratedDiagnosticPoint(std::map<PointKey, GatewayModbusPoint>* points,
|
||||
uint8_t short_address,
|
||||
const GeneratedDiagnosticBitSpec& spec) {
|
||||
if (points == nullptr) {
|
||||
return;
|
||||
}
|
||||
GatewayModbusPoint makeGeneratedDiagnosticPoint(uint8_t short_address,
|
||||
const GeneratedDiagnosticBitSpec& spec) {
|
||||
const uint16_t address = static_cast<uint16_t>(kDiagnosticDiscreteInputBase +
|
||||
short_address * kDiagnosticStride + spec.offset);
|
||||
GatewayModbusPoint point;
|
||||
point.space = GatewayModbusSpace::kDiscreteInput;
|
||||
point.access = GatewayModbusAccess::kReadOnly;
|
||||
point.address = address;
|
||||
point.id = generatedId(short_address, spec.suffix);
|
||||
point.name = generatedName(short_address, spec.name);
|
||||
point.generated = true;
|
||||
point.generated_kind = GatewayModbusGeneratedKind::kShortDiagnosticBit;
|
||||
point.generated_suffix = spec.suffix == nullptr ? "point" : spec.suffix;
|
||||
point.generated_name = spec.name == nullptr ? "point" : spec.name;
|
||||
point.short_address = short_address;
|
||||
point.diagnostic_snapshot = spec.snapshot == nullptr ? "" : spec.snapshot;
|
||||
point.diagnostic_bool = spec.bool_key == nullptr ? "" : spec.bool_key;
|
||||
point.diagnostic_snapshot = literalOrEmpty(spec.snapshot);
|
||||
point.diagnostic_bool = literalOrEmpty(spec.bool_key);
|
||||
point.diagnostic_device_type = spec.device_type;
|
||||
(*points)[PointKey{point.space, address}] = std::move(point);
|
||||
return point;
|
||||
}
|
||||
|
||||
void appendIfNotOverridden(const std::vector<GatewayModbusPoint>& stored_points,
|
||||
std::vector<GatewayModbusPoint>* points,
|
||||
GatewayModbusPoint point) {
|
||||
if (points == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (findStoredPoint(stored_points, keyForPoint(point)) != stored_points.end()) {
|
||||
return;
|
||||
}
|
||||
points->push_back(std::move(point));
|
||||
}
|
||||
|
||||
std::optional<GatewayModbusPoint> generatedPointForAddress(GatewayModbusSpace space,
|
||||
uint16_t address) {
|
||||
if (space == GatewayModbusSpace::kDiscreteInput &&
|
||||
address >= kDiagnosticDiscreteInputBase) {
|
||||
const uint16_t relative = static_cast<uint16_t>(address - kDiagnosticDiscreteInputBase);
|
||||
const uint8_t short_address = static_cast<uint8_t>(relative / kDiagnosticStride);
|
||||
const uint16_t offset = static_cast<uint16_t>(relative % kDiagnosticStride);
|
||||
if (short_address >= kShortAddressCount) {
|
||||
return std::nullopt;
|
||||
}
|
||||
for (const auto& spec : kGeneratedDiagnosticBits) {
|
||||
if (spec.offset == offset) {
|
||||
return makeGeneratedDiagnosticPoint(short_address, spec);
|
||||
}
|
||||
}
|
||||
for (const auto& spec : kGeneratedDiagnosticBitsTail) {
|
||||
if (spec.offset == offset) {
|
||||
return makeGeneratedDiagnosticPoint(short_address, spec);
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const uint16_t base = baseForSpace(space);
|
||||
if (address < base) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const uint16_t relative = static_cast<uint16_t>(address - base);
|
||||
const uint8_t short_address = static_cast<uint8_t>(relative / kShortStride);
|
||||
const uint16_t offset = static_cast<uint16_t>(relative % kShortStride);
|
||||
if (short_address >= kShortAddressCount) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto find_regular_point = [short_address, offset](const auto& specs)
|
||||
-> std::optional<GatewayModbusPoint> {
|
||||
for (const auto& spec : specs) {
|
||||
if (spec.offset == offset) {
|
||||
return makeGeneratedPoint(short_address, spec);
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
};
|
||||
|
||||
switch (space) {
|
||||
case GatewayModbusSpace::kCoil:
|
||||
return find_regular_point(kGeneratedCoils);
|
||||
case GatewayModbusSpace::kDiscreteInput:
|
||||
return find_regular_point(kGeneratedDiscreteInputs);
|
||||
case GatewayModbusSpace::kHoldingRegister:
|
||||
return find_regular_point(kGeneratedHoldingRegisters);
|
||||
case GatewayModbusSpace::kInputRegister:
|
||||
return find_regular_point(kGeneratedInputRegisters);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
GatewayModbusPointBinding toBinding(const GatewayModbusPoint& point) {
|
||||
return GatewayModbusPointBinding{point.model_id,
|
||||
point.space,
|
||||
point.address,
|
||||
point.id,
|
||||
point.name,
|
||||
point.generated,
|
||||
point.generated_kind,
|
||||
point.short_address,
|
||||
point.access,
|
||||
point.bit_index,
|
||||
point.diagnostic_snapshot,
|
||||
point.diagnostic_bool,
|
||||
point.diagnostic_device_type};
|
||||
GatewayModbusPointBinding binding;
|
||||
binding.model_id = point.model_id;
|
||||
binding.space = point.space;
|
||||
binding.address = point.address;
|
||||
if (point.generated) {
|
||||
const auto short_address = static_cast<uint8_t>(point.short_address < 0 ? 0 : point.short_address);
|
||||
binding.id = generatedId(short_address, point.generated_suffix);
|
||||
binding.name = generatedName(short_address, point.generated_name);
|
||||
} else {
|
||||
binding.id = point.id;
|
||||
binding.name = point.name;
|
||||
}
|
||||
binding.generated = point.generated;
|
||||
binding.generated_kind = point.generated_kind;
|
||||
binding.short_address = point.short_address;
|
||||
binding.access = point.access;
|
||||
binding.bit_index = point.bit_index;
|
||||
binding.diagnostic_snapshot = literalOrEmpty(point.diagnostic_snapshot);
|
||||
binding.diagnostic_bool = literalOrEmpty(point.diagnostic_bool);
|
||||
binding.diagnostic_device_type = point.diagnostic_device_type;
|
||||
return binding;
|
||||
}
|
||||
|
||||
int clampedInt(const DaliValue::Object& json, const std::string& key, int fallback,
|
||||
@@ -657,29 +769,11 @@ void GatewayModbusBridge::setConfig(const GatewayModbusConfig& config) { config_
|
||||
const GatewayModbusConfig& GatewayModbusBridge::config() const { return config_; }
|
||||
|
||||
void GatewayModbusBridge::rebuildMap() {
|
||||
std::map<PointKey, GatewayModbusPoint> next;
|
||||
for (uint8_t short_address = 0; short_address < kShortAddressCount; ++short_address) {
|
||||
for (const auto& spec : kGeneratedCoils) {
|
||||
addGeneratedPoint(&next, short_address, spec);
|
||||
}
|
||||
for (const auto& spec : kGeneratedDiscreteInputs) {
|
||||
addGeneratedPoint(&next, short_address, spec);
|
||||
}
|
||||
for (const auto& spec : kGeneratedHoldingRegisters) {
|
||||
addGeneratedPoint(&next, short_address, spec);
|
||||
}
|
||||
for (const auto& spec : kGeneratedInputRegisters) {
|
||||
addGeneratedPoint(&next, short_address, spec);
|
||||
}
|
||||
for (const auto& spec : kGeneratedDiagnosticBits) {
|
||||
addGeneratedDiagnosticPoint(&next, short_address, spec);
|
||||
}
|
||||
for (const auto& spec : kGeneratedDiagnosticBitsTail) {
|
||||
addGeneratedDiagnosticPoint(&next, short_address, spec);
|
||||
}
|
||||
}
|
||||
auto models = engine_.listModels();
|
||||
points_.clear();
|
||||
points_.reserve(models.size());
|
||||
|
||||
for (const auto& model : engine_.listModels()) {
|
||||
for (const auto& model : models) {
|
||||
if (model.protocol != BridgeProtocolKind::modbus || !model.external.registerAddress.has_value()) {
|
||||
continue;
|
||||
}
|
||||
@@ -701,46 +795,117 @@ void GatewayModbusBridge::rebuildMap() {
|
||||
if (model.dali.kind == BridgeDaliTargetKind::shortAddress && model.dali.shortAddress.has_value()) {
|
||||
point.short_address = model.dali.shortAddress.value();
|
||||
}
|
||||
next[PointKey{point.space, point.address}] = std::move(point);
|
||||
points_.push_back(std::move(point));
|
||||
}
|
||||
|
||||
points_.clear();
|
||||
points_.reserve(next.size());
|
||||
for (auto& entry : next) {
|
||||
points_.push_back(std::move(entry.second));
|
||||
std::stable_sort(points_.begin(), points_.end(), pointLess);
|
||||
auto write = points_.begin();
|
||||
for (auto read = points_.begin(); read != points_.end();) {
|
||||
auto next = read + 1;
|
||||
while (next != points_.end() && pointKeyEqual(*read, *next)) {
|
||||
++next;
|
||||
}
|
||||
if (write != next - 1) {
|
||||
*write = std::move(*(next - 1));
|
||||
}
|
||||
++write;
|
||||
read = next;
|
||||
}
|
||||
points_.erase(write, points_.end());
|
||||
}
|
||||
|
||||
std::optional<GatewayModbusPoint> GatewayModbusBridge::findPoint(GatewayModbusSpace space,
|
||||
uint16_t address) const {
|
||||
const auto found = std::find_if(points_.begin(), points_.end(), [space, address](const auto& point) {
|
||||
return point.space == space && point.address == address;
|
||||
});
|
||||
if (found == points_.end()) {
|
||||
return std::nullopt;
|
||||
const PointKey key{space, address};
|
||||
const auto found = findStoredPoint(points_, key);
|
||||
if (found != points_.end()) {
|
||||
return *found;
|
||||
}
|
||||
return generatedPointForAddress(space, address);
|
||||
}
|
||||
|
||||
GatewayModbusPointBinding GatewayModbusBridge::describePoint(
|
||||
const GatewayModbusPoint& point) const {
|
||||
return toBinding(point);
|
||||
}
|
||||
|
||||
void GatewayModbusBridge::appendGeneratedPointsForShortAddress(
|
||||
uint8_t short_address, std::vector<GatewayModbusPoint>* points) const {
|
||||
if (points == nullptr || short_address >= kShortAddressCount) {
|
||||
return;
|
||||
}
|
||||
for (const auto& spec : kGeneratedCoils) {
|
||||
appendIfNotOverridden(points_, points, makeGeneratedPoint(short_address, spec));
|
||||
}
|
||||
for (const auto& spec : kGeneratedDiscreteInputs) {
|
||||
appendIfNotOverridden(points_, points, makeGeneratedPoint(short_address, spec));
|
||||
}
|
||||
for (const auto& spec : kGeneratedHoldingRegisters) {
|
||||
appendIfNotOverridden(points_, points, makeGeneratedPoint(short_address, spec));
|
||||
}
|
||||
for (const auto& spec : kGeneratedInputRegisters) {
|
||||
appendIfNotOverridden(points_, points, makeGeneratedPoint(short_address, spec));
|
||||
}
|
||||
for (const auto& spec : kGeneratedDiagnosticBits) {
|
||||
appendIfNotOverridden(points_, points, makeGeneratedDiagnosticPoint(short_address, spec));
|
||||
}
|
||||
for (const auto& spec : kGeneratedDiagnosticBitsTail) {
|
||||
appendIfNotOverridden(points_, points, makeGeneratedDiagnosticPoint(short_address, spec));
|
||||
}
|
||||
return *found;
|
||||
}
|
||||
|
||||
std::vector<GatewayModbusPointBinding> GatewayModbusBridge::describePoints() const {
|
||||
std::vector<GatewayModbusPointBinding> bindings;
|
||||
bindings.reserve(points_.size());
|
||||
bindings.reserve(kGeneratedPointCount + points_.size());
|
||||
std::vector<GatewayModbusPoint> generated_points;
|
||||
generated_points.reserve(kGeneratedPointsPerShort);
|
||||
for (uint8_t short_address = 0; short_address < kShortAddressCount; ++short_address) {
|
||||
generated_points.clear();
|
||||
appendGeneratedPointsForShortAddress(short_address, &generated_points);
|
||||
for (const auto& point : generated_points) {
|
||||
bindings.push_back(toBinding(point));
|
||||
}
|
||||
}
|
||||
for (const auto& point : points_) {
|
||||
bindings.push_back(toBinding(point));
|
||||
}
|
||||
std::sort(bindings.begin(), bindings.end(), [](const auto& lhs, const auto& rhs) {
|
||||
if (lhs.space != rhs.space) {
|
||||
return static_cast<uint8_t>(lhs.space) < static_cast<uint8_t>(rhs.space);
|
||||
}
|
||||
return lhs.address < rhs.address;
|
||||
});
|
||||
return bindings;
|
||||
}
|
||||
|
||||
std::vector<GatewayModbusPointBinding> GatewayModbusBridge::describeHoldingRegisters() const {
|
||||
std::vector<GatewayModbusPointBinding> bindings;
|
||||
std::vector<GatewayModbusPoint> generated_points;
|
||||
generated_points.reserve(kGeneratedPointsPerShort);
|
||||
for (uint8_t short_address = 0; short_address < kShortAddressCount; ++short_address) {
|
||||
generated_points.clear();
|
||||
appendGeneratedPointsForShortAddress(short_address, &generated_points);
|
||||
for (const auto& point : generated_points) {
|
||||
if (point.space == GatewayModbusSpace::kHoldingRegister) {
|
||||
bindings.push_back(toBinding(point));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto& point : points_) {
|
||||
if (point.space == GatewayModbusSpace::kHoldingRegister) {
|
||||
bindings.push_back(toBinding(point));
|
||||
}
|
||||
}
|
||||
std::sort(bindings.begin(), bindings.end(), [](const auto& lhs, const auto& rhs) {
|
||||
return lhs.address < rhs.address;
|
||||
});
|
||||
return bindings;
|
||||
}
|
||||
|
||||
const std::vector<GatewayModbusPoint>& GatewayModbusBridge::points() const {
|
||||
return points_;
|
||||
}
|
||||
|
||||
DaliBridgeResult GatewayModbusBridge::readModelPoint(const GatewayModbusPoint& point) const {
|
||||
return executeModelPoint(point, std::nullopt);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user