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:
Tony
2026-05-04 02:26:09 +08:00
parent 694217eb2c
commit 7424b43bdd
13 changed files with 1133 additions and 40 deletions
+387 -28
View File
@@ -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");