Add diagnostic bit support to Gateway Modbus
- Introduced new enum value `kShortDiagnosticBit` to `GatewayModbusGeneratedKind`. - Enhanced `GatewayModbusPoint` and `GatewayModbusPointBinding` structures to include diagnostic snapshot, boolean key, and device type. - Added new diagnostic bit specifications and updated the corresponding arrays for generated discrete inputs and holding registers. - Implemented `addGeneratedDiagnosticPoint` function to handle the creation of diagnostic points. - Updated `rebuildMap` method to include generated diagnostic points during the map rebuilding process. Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
#include "gateway_bridge.hpp"
|
||||
|
||||
#include "gateway_bacnet_bridge.hpp"
|
||||
#if defined(CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED)
|
||||
#include "bacnet_bridge.hpp"
|
||||
#include "gateway_bacnet.hpp"
|
||||
#endif
|
||||
#include "bridge.hpp"
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "lwip/sockets.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
@@ -40,12 +41,18 @@ constexpr const char* kBridgeConfigKey = "bridge_cfg";
|
||||
constexpr const char* kDiscoveryInventoryKey = "bridge_disc";
|
||||
constexpr int kMaxDaliShortAddress = 63;
|
||||
constexpr uint16_t kModbusUnknownRegister = 0xFFFF;
|
||||
constexpr uint16_t kModbusDiscreteInputBase = 10001;
|
||||
constexpr uint32_t kDiagnosticSnapshotCacheTtlMs = 500;
|
||||
constexpr uint32_t kBacnetGeneratedBinaryInputBase = 1000000;
|
||||
constexpr uint32_t kBacnetGeneratedBinaryInputChannelStride = 32768;
|
||||
constexpr uint32_t kBacnetMaxObjectInstance = 4194303;
|
||||
constexpr uint32_t kBacnetReliabilityNoFaultDetected = 0;
|
||||
constexpr uint32_t kBacnetReliabilityCommunicationFailure = 12;
|
||||
|
||||
struct GatewayBridgeStoredConfig {
|
||||
BridgeRuntimeConfig bridge;
|
||||
std::optional<GatewayModbusConfig> modbus;
|
||||
std::optional<GatewayBacnetBridgeConfig> bacnet_server;
|
||||
};
|
||||
|
||||
struct BridgeDiscoveryEntry {
|
||||
@@ -243,6 +250,18 @@ bool SnapshotHasDeviceType(const DaliDomainSnapshot& snapshot, int device_type)
|
||||
types->second.end();
|
||||
}
|
||||
|
||||
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::optional<int> SnapshotIntValue(const DaliDomainSnapshot& snapshot,
|
||||
const std::string& key) {
|
||||
const auto found = snapshot.ints.find(key);
|
||||
return found == snapshot.ints.end() ? std::nullopt : std::optional<int>(found->second);
|
||||
}
|
||||
|
||||
bool OperationRequiresDt1(BridgeOperation operation) {
|
||||
switch (operation) {
|
||||
case BridgeOperation::getEmergencyLevel:
|
||||
@@ -268,6 +287,22 @@ bool OperationRequiresDt8(BridgeOperation operation) {
|
||||
}
|
||||
}
|
||||
|
||||
bool BridgeOperationReadable(BridgeOperation operation) {
|
||||
switch (operation) {
|
||||
case BridgeOperation::query:
|
||||
case BridgeOperation::getBrightness:
|
||||
case BridgeOperation::getStatus:
|
||||
case BridgeOperation::getColorTemperature:
|
||||
case BridgeOperation::getColorStatus:
|
||||
case BridgeOperation::getEmergencyLevel:
|
||||
case BridgeOperation::getEmergencyStatus:
|
||||
case BridgeOperation::getEmergencyFailureStatus:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<int> BridgeTargetValue(const BridgeDaliTarget& target) {
|
||||
switch (target.kind) {
|
||||
case BridgeDaliTargetKind::shortAddress:
|
||||
@@ -753,27 +788,53 @@ cJSON* ToCjson(const DaliValue& value) {
|
||||
|
||||
DaliValue::Object GatewayBridgeStoredConfigToValue(
|
||||
const BridgeRuntimeConfig& bridge_config,
|
||||
const std::optional<GatewayModbusConfig>& modbus_config) {
|
||||
const std::optional<GatewayModbusConfig>& modbus_config,
|
||||
const std::optional<GatewayBacnetBridgeConfig>& bacnet_server_config) {
|
||||
DaliValue::Object out = bridge_config.toJson();
|
||||
if (modbus_config.has_value()) {
|
||||
out["modbus"] = GatewayModbusConfigToValue(modbus_config.value());
|
||||
}
|
||||
if (bacnet_server_config.has_value()) {
|
||||
DaliValue::Object bacnet;
|
||||
bacnet["deviceInstance"] = static_cast<int64_t>(bacnet_server_config->deviceInstance);
|
||||
bacnet["localAddress"] = bacnet_server_config->localAddress;
|
||||
bacnet["udpPort"] = static_cast<int>(bacnet_server_config->udpPort);
|
||||
out["bacnetServer"] = std::move(bacnet);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string GatewayBridgeStoredConfigToJson(
|
||||
const BridgeRuntimeConfig& bridge_config,
|
||||
const std::optional<GatewayModbusConfig>& modbus_config) {
|
||||
cJSON* root = ToCjson(DaliValue(GatewayBridgeStoredConfigToValue(bridge_config, modbus_config)));
|
||||
const std::optional<GatewayModbusConfig>& modbus_config,
|
||||
const std::optional<GatewayBacnetBridgeConfig>& bacnet_server_config) {
|
||||
cJSON* root = ToCjson(DaliValue(GatewayBridgeStoredConfigToValue(
|
||||
bridge_config, modbus_config, bacnet_server_config)));
|
||||
const std::string body = PrintJson(root);
|
||||
cJSON_Delete(root);
|
||||
return body;
|
||||
}
|
||||
|
||||
std::optional<GatewayBacnetBridgeConfig> GatewayBacnetBridgeConfigFromValue(
|
||||
const DaliValue* value) {
|
||||
if (value == nullptr || value->asObject() == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto& json = *value->asObject();
|
||||
GatewayBacnetBridgeConfig config;
|
||||
config.deviceInstance = static_cast<uint32_t>(
|
||||
getObjectInt(json, "deviceInstance").value_or(4194303));
|
||||
config.localAddress = getObjectString(json, "localAddress").value_or("");
|
||||
config.udpPort = static_cast<uint16_t>(getObjectInt(json, "udpPort").value_or(47808));
|
||||
return config;
|
||||
}
|
||||
|
||||
GatewayBridgeStoredConfig GatewayBridgeStoredConfigFromValue(const DaliValue::Object& object) {
|
||||
GatewayBridgeStoredConfig config;
|
||||
config.bridge = BridgeRuntimeConfig::fromJson(object);
|
||||
config.modbus = GatewayModbusConfigFromValue(getObjectValue(object, "modbus"));
|
||||
config.bacnet_server = GatewayBacnetBridgeConfigFromValue(
|
||||
getObjectValue(object, "bacnetServer"));
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -938,11 +999,12 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
std::unique_ptr<DaliBridgeEngine> engine;
|
||||
std::unique_ptr<GatewayModbusBridge> modbus;
|
||||
#if defined(CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED)
|
||||
std::unique_ptr<DaliBacnetBridge> bacnet;
|
||||
std::unique_ptr<GatewayBacnetBridgeAdapter> bacnet;
|
||||
#endif
|
||||
std::unique_ptr<DaliCloudBridge> cloud;
|
||||
BridgeRuntimeConfig bridge_config;
|
||||
std::optional<GatewayModbusConfig> modbus_config;
|
||||
std::optional<GatewayBacnetBridgeConfig> bacnet_server_config;
|
||||
BridgeDiscoveryInventory discovery_inventory;
|
||||
std::optional<GatewayCloudConfig> cloud_config;
|
||||
bool bridge_config_loaded{false};
|
||||
@@ -953,6 +1015,12 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
bool bacnet_started{false};
|
||||
TaskHandle_t modbus_task_handle{nullptr};
|
||||
|
||||
struct DiagnosticSnapshotCacheEntry {
|
||||
DaliDomainSnapshot snapshot;
|
||||
TickType_t captured_ticks{0};
|
||||
};
|
||||
std::map<std::string, DiagnosticSnapshotCacheEntry> diagnostic_snapshot_cache;
|
||||
|
||||
static void ModbusTaskEntry(void* arg) {
|
||||
static_cast<ChannelRuntime*>(arg)->modbusTaskLoop();
|
||||
}
|
||||
@@ -982,6 +1050,7 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
const auto stored_config = GatewayBridgeStoredConfigFromValue(bridge_object);
|
||||
bridge_config = stored_config.bridge;
|
||||
modbus_config = stored_config.modbus;
|
||||
bacnet_server_config = stored_config.bacnet_server;
|
||||
bridge_config_loaded = true;
|
||||
}
|
||||
DaliValue::Object discovery_object;
|
||||
@@ -1018,9 +1087,9 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
|
||||
#if defined(CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED)
|
||||
if (service_config.bacnet_enabled) {
|
||||
bacnet = std::make_unique<DaliBacnetBridge>(*engine);
|
||||
if (bridge_config.bacnet.has_value()) {
|
||||
bacnet->setConfig(bridge_config.bacnet.value());
|
||||
bacnet = std::make_unique<GatewayBacnetBridgeAdapter>(*engine);
|
||||
if (bacnet_server_config.has_value()) {
|
||||
bacnet->setConfig(bacnet_server_config.value());
|
||||
}
|
||||
} else {
|
||||
bacnet.reset();
|
||||
@@ -1029,6 +1098,62 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
|
||||
applyCloudModelsLocked();
|
||||
bacnet_started = false;
|
||||
diagnostic_snapshot_cache.clear();
|
||||
}
|
||||
|
||||
std::optional<DaliDomainSnapshot> diagnosticSnapshotLocked(int short_address,
|
||||
const std::string& kind) {
|
||||
if (!ValidShortAddress(short_address) || kind.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const std::string key = kind + ":" + std::to_string(short_address);
|
||||
const TickType_t now = xTaskGetTickCount();
|
||||
const auto cached = diagnostic_snapshot_cache.find(key);
|
||||
if (cached != diagnostic_snapshot_cache.end() &&
|
||||
(now - cached->second.captured_ticks) <= pdMS_TO_TICKS(kDiagnosticSnapshotCacheTtlMs)) {
|
||||
return cached->second.snapshot;
|
||||
}
|
||||
|
||||
std::optional<DaliDomainSnapshot> snapshot;
|
||||
if (kind == "base_status") {
|
||||
snapshot = domain.baseStatusSnapshot(channel.gateway_id, short_address);
|
||||
} else if (kind == "dt1") {
|
||||
snapshot = domain.dt1Snapshot(channel.gateway_id, short_address);
|
||||
} else if (kind == "dt4") {
|
||||
snapshot = domain.dt4Snapshot(channel.gateway_id, short_address);
|
||||
} else if (kind == "dt5") {
|
||||
snapshot = domain.dt5Snapshot(channel.gateway_id, short_address);
|
||||
} else if (kind == "dt6") {
|
||||
snapshot = domain.dt6Snapshot(channel.gateway_id, short_address);
|
||||
} else if (kind == "dt8_status") {
|
||||
snapshot = domain.dt8StatusSnapshot(channel.gateway_id, short_address);
|
||||
}
|
||||
|
||||
if (snapshot.has_value()) {
|
||||
diagnostic_snapshot_cache[key] = DiagnosticSnapshotCacheEntry{snapshot.value(), now};
|
||||
}
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
std::optional<bool> readSnapshotBoolLocked(int short_address, const std::string& kind,
|
||||
const std::string& bool_key) {
|
||||
const auto snapshot = diagnosticSnapshotLocked(short_address, kind);
|
||||
if (!snapshot.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return SnapshotBoolValue(snapshot.value(), bool_key);
|
||||
}
|
||||
|
||||
std::optional<bool> readDiagnosticBoolPointLocked(const GatewayModbusPoint& point) {
|
||||
if (point.diagnostic_device_type > 0) {
|
||||
const auto* discovery = findDiscoveryEntryLocked(point.short_address);
|
||||
if (discovery == nullptr ||
|
||||
!SnapshotHasDeviceType(discovery->discovery, point.diagnostic_device_type)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return readSnapshotBoolLocked(point.short_address, point.diagnostic_snapshot,
|
||||
point.diagnostic_bool);
|
||||
}
|
||||
|
||||
esp_err_t saveDiscoveryInventoryLocked() const {
|
||||
@@ -1117,6 +1242,10 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
[this](BridgeObjectType object_type, uint32_t object_instance,
|
||||
const std::string& property, const DaliValue& value) {
|
||||
return handleBacnetWrite(object_type, object_instance, property, value);
|
||||
},
|
||||
[this](BridgeObjectType object_type, uint32_t object_instance,
|
||||
const std::string& property) -> std::optional<DaliValue> {
|
||||
return readBacnetValue(object_type, object_instance, property);
|
||||
});
|
||||
#else
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
@@ -1142,13 +1271,15 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
BridgeProvisioningStore store(bridgeNamespace());
|
||||
const esp_err_t err = store.saveObject(
|
||||
kBridgeConfigKey,
|
||||
GatewayBridgeStoredConfigToValue(parsed->bridge, parsed->modbus));
|
||||
GatewayBridgeStoredConfigToValue(parsed->bridge, parsed->modbus,
|
||||
parsed->bacnet_server));
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
LockGuard guard(lock);
|
||||
bridge_config = parsed->bridge;
|
||||
modbus_config = parsed->modbus;
|
||||
bacnet_server_config = parsed->bacnet_server;
|
||||
bridge_config_loaded = true;
|
||||
applyBridgeConfigLocked();
|
||||
return ESP_OK;
|
||||
@@ -1163,6 +1294,7 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
LockGuard guard(lock);
|
||||
bridge_config = BridgeRuntimeConfig{};
|
||||
modbus_config.reset();
|
||||
bacnet_server_config.reset();
|
||||
bridge_config_loaded = false;
|
||||
applyBridgeConfigLocked();
|
||||
return ESP_OK;
|
||||
@@ -1244,7 +1376,7 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
return model == bridge_config.models.end() ? model_id : model->displayName();
|
||||
}
|
||||
|
||||
bool shouldPublishBacnetBindingLocked(const BacnetObjectBinding& binding) {
|
||||
bool shouldPublishBacnetBindingLocked(const GatewayBacnetModelBinding& binding) {
|
||||
if (binding.objectInstance < 0) {
|
||||
return false;
|
||||
}
|
||||
@@ -1270,8 +1402,8 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<BacnetObjectBinding> effectiveBacnetObjectsLocked() {
|
||||
std::vector<BacnetObjectBinding> bindings;
|
||||
std::vector<GatewayBacnetModelBinding> effectiveBacnetObjectsLocked() {
|
||||
std::vector<GatewayBacnetModelBinding> bindings;
|
||||
if (bacnet == nullptr) {
|
||||
return bindings;
|
||||
}
|
||||
@@ -1290,15 +1422,104 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
: channel.name;
|
||||
config.task_stack_size = service_config.bacnet_task_stack_size;
|
||||
config.task_priority = service_config.bacnet_task_priority;
|
||||
if (bacnet != nullptr) {
|
||||
const auto& bridge_config = bacnet->config();
|
||||
config.device_instance = bridge_config.deviceInstance;
|
||||
config.local_address = bridge_config.localAddress;
|
||||
config.udp_port = bridge_config.udpPort;
|
||||
if (bacnet_server_config.has_value()) {
|
||||
config.device_instance = bacnet_server_config->deviceInstance;
|
||||
config.local_address = bacnet_server_config->localAddress;
|
||||
config.udp_port = bacnet_server_config->udpPort;
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
std::optional<uint32_t> generatedBacnetBinaryInputInstance(uint16_t modbus_address) const {
|
||||
if (modbus_address < kModbusDiscreteInputBase) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const uint32_t relative = static_cast<uint32_t>(modbus_address - kModbusDiscreteInputBase);
|
||||
if (relative >= kBacnetGeneratedBinaryInputChannelStride) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const uint32_t instance = kBacnetGeneratedBinaryInputBase +
|
||||
static_cast<uint32_t>(channel.channel_index) *
|
||||
kBacnetGeneratedBinaryInputChannelStride +
|
||||
relative;
|
||||
if (instance > kBacnetMaxObjectInstance) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
std::optional<GatewayModbusPoint> generatedBacnetPointForObjectLocked(
|
||||
BridgeObjectType object_type, uint32_t object_instance) const {
|
||||
if (object_type != BridgeObjectType::binaryInput || modbus == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const uint32_t channel_base = kBacnetGeneratedBinaryInputBase +
|
||||
static_cast<uint32_t>(channel.channel_index) *
|
||||
kBacnetGeneratedBinaryInputChannelStride;
|
||||
if (object_instance < channel_base ||
|
||||
object_instance >= channel_base + kBacnetGeneratedBinaryInputChannelStride) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const uint32_t relative = object_instance - channel_base;
|
||||
if (relative > UINT16_MAX - kModbusDiscreteInputBase) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto point = modbus->findPoint(
|
||||
GatewayModbusSpace::kDiscreteInput,
|
||||
static_cast<uint16_t>(kModbusDiscreteInputBase + relative));
|
||||
if (!point.has_value() || !point->generated) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return point;
|
||||
}
|
||||
|
||||
bool shouldPublishGeneratedBacnetPointLocked(const GatewayModbusPointBinding& point) {
|
||||
if (!point.generated || point.space != GatewayModbusSpace::kDiscreteInput ||
|
||||
point.access != GatewayModbusAccess::kReadOnly ||
|
||||
!ValidShortAddress(point.short_address)) {
|
||||
return false;
|
||||
}
|
||||
const auto* discovery = findDiscoveryEntryLocked(point.short_address);
|
||||
if (discovery == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (point.diagnostic_device_type > 0 &&
|
||||
!SnapshotHasDeviceType(discovery->discovery, point.diagnostic_device_type)) {
|
||||
return false;
|
||||
}
|
||||
return generatedBacnetBinaryInputInstance(point.address).has_value();
|
||||
}
|
||||
|
||||
std::vector<GatewayBacnetObjectBinding> generatedBacnetObjectBindingsLocked() {
|
||||
std::vector<GatewayBacnetObjectBinding> bindings;
|
||||
if (modbus == nullptr) {
|
||||
return bindings;
|
||||
}
|
||||
for (const auto& point : modbus->describePoints()) {
|
||||
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 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;
|
||||
}
|
||||
|
||||
std::vector<GatewayBacnetObjectBinding> bacnetObjectBindingsLocked() {
|
||||
std::vector<GatewayBacnetObjectBinding> bindings;
|
||||
for (const auto& binding : effectiveBacnetObjectsLocked()) {
|
||||
@@ -1324,8 +1545,11 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
binding.property.empty() ? "presentValue"
|
||||
: binding.property,
|
||||
out_of_service,
|
||||
reliability});
|
||||
reliability,
|
||||
BridgeOperationReadable(binding.operation)});
|
||||
}
|
||||
auto generated = generatedBacnetObjectBindingsLocked();
|
||||
bindings.insert(bindings.end(), generated.begin(), generated.end());
|
||||
return bindings;
|
||||
}
|
||||
|
||||
@@ -1344,6 +1568,47 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
return result.ok;
|
||||
}
|
||||
|
||||
std::optional<DaliValue> readBacnetValue(BridgeObjectType object_type,
|
||||
uint32_t object_instance,
|
||||
const std::string& property) {
|
||||
LockGuard guard(lock);
|
||||
if (const auto point = generatedBacnetPointForObjectLocked(object_type, object_instance)) {
|
||||
const auto value = readGeneratedBoolPointLocked(point.value());
|
||||
return value.has_value() ? std::optional<DaliValue>(DaliValue(value.value()))
|
||||
: std::nullopt;
|
||||
}
|
||||
if (bacnet == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto binding = bacnet->findObject(object_type, static_cast<int>(object_instance),
|
||||
property.empty() ? "presentValue" : property);
|
||||
if (!binding.has_value() || !BridgeOperationReadable(binding->operation)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const DaliBridgeResult result = bacnet->readProperty(
|
||||
object_type, static_cast<int>(object_instance),
|
||||
property.empty() ? "presentValue" : property);
|
||||
if (!result.ok || !result.data.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
if ((object_type == BridgeObjectType::binaryInput ||
|
||||
object_type == BridgeObjectType::binaryValue ||
|
||||
object_type == BridgeObjectType::binaryOutput) &&
|
||||
binding->bitIndex.has_value()) {
|
||||
const int bit = binding->bitIndex.value();
|
||||
if (bit < 0 || bit >= 32) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return DaliValue(((result.data.value() >> bit) & 0x1) != 0);
|
||||
}
|
||||
if (object_type == BridgeObjectType::binaryInput ||
|
||||
object_type == BridgeObjectType::binaryValue ||
|
||||
object_type == BridgeObjectType::binaryOutput) {
|
||||
return DaliValue(result.data.value() != 0);
|
||||
}
|
||||
return DaliValue(result.data.value());
|
||||
}
|
||||
|
||||
esp_err_t startBacnet() {
|
||||
LockGuard guard(lock);
|
||||
if (!service_config.bacnet_enabled) {
|
||||
@@ -1420,10 +1685,12 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
cJSON_AddBoolToObject(bacnet_json, "serverStarted", false);
|
||||
cJSON_AddNumberToObject(bacnet_json, "serverObjectCount", 0);
|
||||
#endif
|
||||
if (bridge_config.bacnet.has_value()) {
|
||||
cJSON_AddNumberToObject(bacnet_json, "deviceInstance", bridge_config.bacnet->deviceInstance);
|
||||
cJSON_AddStringToObject(bacnet_json, "localAddress", bridge_config.bacnet->localAddress.c_str());
|
||||
cJSON_AddNumberToObject(bacnet_json, "udpPort", bridge_config.bacnet->udpPort);
|
||||
if (bacnet_server_config.has_value()) {
|
||||
cJSON_AddNumberToObject(bacnet_json, "deviceInstance",
|
||||
bacnet_server_config->deviceInstance);
|
||||
cJSON_AddStringToObject(bacnet_json, "localAddress",
|
||||
bacnet_server_config->localAddress.c_str());
|
||||
cJSON_AddNumberToObject(bacnet_json, "udpPort", bacnet_server_config->udpPort);
|
||||
}
|
||||
cJSON_AddItemToObject(root, "bacnet", bacnet_json);
|
||||
}
|
||||
@@ -1446,7 +1713,8 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
|
||||
GatewayBridgeHttpResponse configJson() const {
|
||||
return GatewayBridgeHttpResponse{ESP_OK,
|
||||
GatewayBridgeStoredConfigToJson(bridge_config, modbus_config)};
|
||||
GatewayBridgeStoredConfigToJson(bridge_config, modbus_config,
|
||||
bacnet_server_config)};
|
||||
}
|
||||
|
||||
GatewayBridgeHttpResponse inventoryJson() const {
|
||||
@@ -1623,6 +1891,20 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
if (binding.short_address >= 0) {
|
||||
cJSON_AddNumberToObject(item, "shortAddress", binding.short_address);
|
||||
}
|
||||
if (binding.bit_index.has_value()) {
|
||||
cJSON_AddNumberToObject(item, "bitIndex", binding.bit_index.value());
|
||||
}
|
||||
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);
|
||||
}
|
||||
cJSON_AddItemToArray(bindings, item);
|
||||
}
|
||||
}
|
||||
@@ -1677,6 +1959,50 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
}
|
||||
cJSON_AddItemToArray(bindings, item);
|
||||
}
|
||||
if (modbus != nullptr) {
|
||||
for (const auto& point : modbus->describePoints()) {
|
||||
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;
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
cJSON_AddItemToObject(root, "bindings", bindings);
|
||||
@@ -1747,14 +2073,23 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
case GatewayModbusGeneratedKind::kShortSettingsKnown:
|
||||
return state.settings.anyKnown();
|
||||
case GatewayModbusGeneratedKind::kShortControlGearPresent:
|
||||
return readSnapshotBoolLocked(point.short_address, "base_status", "controlGearPresent");
|
||||
case GatewayModbusGeneratedKind::kShortLampFailure:
|
||||
return readSnapshotBoolLocked(point.short_address, "base_status", "lampFailure");
|
||||
case GatewayModbusGeneratedKind::kShortLampPowerOn:
|
||||
return readSnapshotBoolLocked(point.short_address, "base_status", "lampPowerOn");
|
||||
case GatewayModbusGeneratedKind::kShortLimitError:
|
||||
return readSnapshotBoolLocked(point.short_address, "base_status", "limitError");
|
||||
case GatewayModbusGeneratedKind::kShortFadingCompleted:
|
||||
return readSnapshotBoolLocked(point.short_address, "base_status", "fadingCompleted");
|
||||
case GatewayModbusGeneratedKind::kShortResetState:
|
||||
return readSnapshotBoolLocked(point.short_address, "base_status", "resetState");
|
||||
case GatewayModbusGeneratedKind::kShortMissingShortAddress:
|
||||
return readSnapshotBoolLocked(point.short_address, "base_status", "missingShortAddress");
|
||||
case GatewayModbusGeneratedKind::kShortPowerSupplyFault:
|
||||
return false;
|
||||
return readSnapshotBoolLocked(point.short_address, "base_status", "powerSupplyFault");
|
||||
case GatewayModbusGeneratedKind::kShortDiagnosticBit:
|
||||
return readDiagnosticBoolPointLocked(point);
|
||||
default:
|
||||
return std::nullopt;
|
||||
}
|
||||
@@ -1794,8 +2129,15 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
return state.status.scene_id.has_value()
|
||||
? static_cast<uint16_t>(state.status.scene_id.value())
|
||||
: kModbusUnknownRegister;
|
||||
case GatewayModbusGeneratedKind::kShortRawStatus:
|
||||
case GatewayModbusGeneratedKind::kShortRawStatus: {
|
||||
const auto snapshot = diagnosticSnapshotLocked(point.short_address, "base_status");
|
||||
if (snapshot.has_value()) {
|
||||
const auto raw_status = SnapshotIntValue(snapshot.value(), "rawStatus");
|
||||
return raw_status.has_value() ? static_cast<uint16_t>(raw_status.value() & 0xFF)
|
||||
: kModbusUnknownRegister;
|
||||
}
|
||||
return kModbusUnknownRegister;
|
||||
}
|
||||
case GatewayModbusGeneratedKind::kShortGroupMask:
|
||||
return state.group_mask_known ? state.group_mask : kModbusUnknownRegister;
|
||||
case GatewayModbusGeneratedKind::kShortPowerOnLevel:
|
||||
@@ -2427,11 +2769,24 @@ GatewayBridgeHttpResponse GatewayBridgeService::handleGet(
|
||||
}
|
||||
return ErrorResponse(ESP_ERR_NOT_FOUND, "device did not respond to type discovery");
|
||||
}
|
||||
if (action == "dt4" || action == "dt5" || action == "dt6") {
|
||||
if (action == "base_status" || action == "status_bits") {
|
||||
const auto address = QueryInt(query, "addr", "address");
|
||||
if (!address.has_value() || !ValidDaliAddress(address.value())) {
|
||||
return ErrorResponse(ESP_ERR_INVALID_ARG, "valid addr is required");
|
||||
}
|
||||
return SnapshotResponse(dali_domain_.baseStatusSnapshot(gateway_id.value(), address.value()),
|
||||
"base status snapshot is unavailable");
|
||||
}
|
||||
if (action == "dt1" || action == "dt4" || action == "dt5" || action == "dt6" ||
|
||||
action == "dt8_status") {
|
||||
const auto address = QueryInt(query, "addr", "address");
|
||||
if (!address.has_value() || !ValidDaliAddress(address.value())) {
|
||||
return ErrorResponse(ESP_ERR_INVALID_ARG, "valid addr is required");
|
||||
}
|
||||
if (action == "dt1") {
|
||||
return SnapshotResponse(dali_domain_.dt1Snapshot(gateway_id.value(), address.value()),
|
||||
"DT1 snapshot is unavailable");
|
||||
}
|
||||
if (action == "dt4") {
|
||||
return SnapshotResponse(dali_domain_.dt4Snapshot(gateway_id.value(), address.value()),
|
||||
"DT4 snapshot is unavailable");
|
||||
@@ -2440,8 +2795,12 @@ GatewayBridgeHttpResponse GatewayBridgeService::handleGet(
|
||||
return SnapshotResponse(dali_domain_.dt5Snapshot(gateway_id.value(), address.value()),
|
||||
"DT5 snapshot is unavailable");
|
||||
}
|
||||
return SnapshotResponse(dali_domain_.dt6Snapshot(gateway_id.value(), address.value()),
|
||||
"DT6 snapshot is unavailable");
|
||||
if (action == "dt6") {
|
||||
return SnapshotResponse(dali_domain_.dt6Snapshot(gateway_id.value(), address.value()),
|
||||
"DT6 snapshot is unavailable");
|
||||
}
|
||||
return SnapshotResponse(dali_domain_.dt8StatusSnapshot(gateway_id.value(), address.value()),
|
||||
"DT8 status snapshot is unavailable");
|
||||
}
|
||||
if (action == "dt8_scene") {
|
||||
const auto address = QueryInt(query, "addr", "address");
|
||||
|
||||
Reference in New Issue
Block a user