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:
@@ -13,7 +13,7 @@ This folder hosts the native ESP-IDF C++ rewrite of the Lua DALI gateway.
|
|||||||
- `gateway_cache/`: DALI scene/group/settings/runtime cache used by controller reconciliation and protocol bridges.
|
- `gateway_cache/`: DALI scene/group/settings/runtime cache used by controller reconciliation and protocol bridges.
|
||||||
- `gateway_bridge/`: per-channel bridge provisioning, command execution, protocol startup, and HTTP bridge actions.
|
- `gateway_bridge/`: per-channel bridge provisioning, command execution, protocol startup, and HTTP bridge actions.
|
||||||
- `gateway_modbus/`: gateway-owned Modbus TCP config, generated DALI point tables, and provisioned Modbus model override dispatch.
|
- `gateway_modbus/`: gateway-owned Modbus TCP config, generated DALI point tables, and provisioned Modbus model override dispatch.
|
||||||
- `gateway_bacnet/`: BACnet/IP server adapter backed by bacnet-stack.
|
- `gateway_bacnet/`: BACnet/IP server adapter backed by bacnet-stack, including the gateway-owned BACnet bridge model adapter.
|
||||||
- `gateway_ble/`: NimBLE GATT bridge for BLE transport parity on `FFF1`/`FFF2`/`FFF3`, including raw DALI notifications.
|
- `gateway_ble/`: NimBLE GATT bridge for BLE transport parity on `FFF1`/`FFF2`/`FFF3`, including raw DALI notifications.
|
||||||
- `gateway_controller/`: Lua-compatible gateway command dispatcher, internal scene/group state, and notification fan-out.
|
- `gateway_controller/`: Lua-compatible gateway command dispatcher, internal scene/group state, and notification fan-out.
|
||||||
- `gateway_network/`: HTTP `/info`, `/dali/cmd`, `/led/1`, `/led/0`, `/jq.js`, UDP port `2020` command/notify routing, Wi-Fi STA lifecycle, ESP-Touch smartconfig, setup AP mode, ESP-NOW setup ingress, and BOOT-button Wi-Fi reset for the native gateway.
|
- `gateway_network/`: HTTP `/info`, `/dali/cmd`, `/led/1`, `/led/0`, `/jq.js`, UDP port `2020` command/notify routing, Wi-Fi STA lifecycle, ESP-Touch smartconfig, setup AP mode, ESP-NOW setup ingress, and BOOT-button Wi-Fi reset for the native gateway.
|
||||||
@@ -35,4 +35,24 @@ The first generated map slice creates stable points for every DALI short address
|
|||||||
- Holding registers: writable brightness, color temperature, group mask, power-on level, system-failure level, min/max level, and fade time.
|
- Holding registers: writable brightness, color temperature, group mask, power-on level, system-failure level, min/max level, and fade time.
|
||||||
- Input registers: read-only inventory state, primary type, type mask, cached actual level, scene id, raw status placeholder, group mask, and cached settings.
|
- Input registers: read-only inventory state, primary type, type mask, cached actual level, scene id, raw status placeholder, group mask, and cached settings.
|
||||||
|
|
||||||
Unknown numeric values read as `0xFFFF`; booleans read as false unless inventory or cache state proves otherwise. Provisioned Modbus models still work as overrides at their configured Modbus point, and normal generated reads prefer gateway cache state to avoid DALI bus polling.
|
Unknown numeric values read as `0xFFFF`; booleans read as false unless inventory or cache state proves otherwise. Provisioned Modbus models still work as overrides at their configured Modbus point, and normal generated reads prefer gateway cache state to avoid DALI bus polling.
|
||||||
|
|
||||||
|
An extension discrete-input range starts immediately after the legacy `0-63` short-address block. It publishes decoded DALI status, failure, and feature bits as individual booleans for base status, DT1 emergency, DT4/5/6 control-gear feature/failure status, and DT8 color status/features. This keeps existing Modbus addresses stable while making bit-level diagnostics readable without consumers masking packed status registers.
|
||||||
|
|
||||||
|
## BACnet/IP
|
||||||
|
|
||||||
|
BACnet/IP is owned by `gateway/components/gateway_bacnet` and is started through the per-channel bridge service. Runtime BACnet server settings live under top-level `bacnetServer` in bridge config:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"bacnetServer": {
|
||||||
|
"deviceInstance": 4194303,
|
||||||
|
"localAddress": "",
|
||||||
|
"udpPort": 47808
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Provisioned BACnet models still use generic `BridgeModel` fields such as object type, object instance, property, and optional `bitIndex`. Query-style models refresh BACnet `Present_Value` from live DALI reads, and binary models with `bitIndex` expose a single packed status bit.
|
||||||
|
|
||||||
|
For discovered DALI short addresses, the gateway also mirrors the generated Modbus discrete diagnostics as BACnet binary-input objects. Object instances are allocated in a gateway-owned generated range using the channel index plus the generated Modbus discrete-input offset, so generated objects stay deterministic while avoiding the provisioned-object address space in normal deployments.
|
||||||
@@ -134,9 +134,14 @@ class DaliDomainService {
|
|||||||
std::optional<DaliDomainSnapshot> discoverDeviceTypes(
|
std::optional<DaliDomainSnapshot> discoverDeviceTypes(
|
||||||
uint8_t gateway_id, int short_address, const std::vector<int>& fallback_types = {},
|
uint8_t gateway_id, int short_address, const std::vector<int>& fallback_types = {},
|
||||||
int max_next_types = 16) const;
|
int max_next_types = 16) const;
|
||||||
|
std::optional<DaliDomainSnapshot> baseStatusSnapshot(uint8_t gateway_id,
|
||||||
|
int short_address) const;
|
||||||
|
std::optional<DaliDomainSnapshot> dt1Snapshot(uint8_t gateway_id, int short_address) const;
|
||||||
std::optional<DaliDomainSnapshot> dt4Snapshot(uint8_t gateway_id, int short_address) const;
|
std::optional<DaliDomainSnapshot> dt4Snapshot(uint8_t gateway_id, int short_address) const;
|
||||||
std::optional<DaliDomainSnapshot> dt5Snapshot(uint8_t gateway_id, int short_address) const;
|
std::optional<DaliDomainSnapshot> dt5Snapshot(uint8_t gateway_id, int short_address) const;
|
||||||
std::optional<DaliDomainSnapshot> dt6Snapshot(uint8_t gateway_id, int short_address) const;
|
std::optional<DaliDomainSnapshot> dt6Snapshot(uint8_t gateway_id, int short_address) const;
|
||||||
|
std::optional<DaliDomainSnapshot> dt8StatusSnapshot(uint8_t gateway_id,
|
||||||
|
int short_address) const;
|
||||||
std::optional<DaliDomainSnapshot> dt8SceneColorReport(uint8_t gateway_id, int short_address,
|
std::optional<DaliDomainSnapshot> dt8SceneColorReport(uint8_t gateway_id, int short_address,
|
||||||
int scene) const;
|
int scene) const;
|
||||||
std::optional<DaliDomainSnapshot> dt8PowerOnLevelColorReport(uint8_t gateway_id,
|
std::optional<DaliDomainSnapshot> dt8PowerOnLevelColorReport(uint8_t gateway_id,
|
||||||
|
|||||||
@@ -466,6 +466,113 @@ std::optional<DaliDomainSnapshot> DaliDomainService::discoverDeviceTypes(
|
|||||||
return snapshot;
|
return snapshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<DaliDomainSnapshot> DaliDomainService::baseStatusSnapshot(
|
||||||
|
uint8_t gateway_id, int short_address) const {
|
||||||
|
const auto* channel = findChannelByGateway(gateway_id);
|
||||||
|
if (channel == nullptr || channel->dali == nullptr) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
const auto raw_status = channel->dali->base.getStatus(short_address);
|
||||||
|
if (!raw_status.has_value()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
const DaliStatus status = DaliStatus::fromByte(static_cast<uint8_t>(raw_status.value()));
|
||||||
|
auto snapshot = MakeSnapshot(gateway_id, short_address, "base_status");
|
||||||
|
snapshot.ints["rawStatus"] = raw_status.value() & 0xFF;
|
||||||
|
snapshot.bools["controlGearPresent"] = status.controlGearPresent;
|
||||||
|
snapshot.bools["lampFailure"] = status.lampFailure;
|
||||||
|
snapshot.bools["lampPowerOn"] = status.lampPowerOn;
|
||||||
|
snapshot.bools["limitError"] = status.limitError;
|
||||||
|
snapshot.bools["fadingCompleted"] = status.fadingCompleted;
|
||||||
|
snapshot.bools["resetState"] = status.resetState;
|
||||||
|
snapshot.bools["missingShortAddress"] = status.missingShortAddress;
|
||||||
|
snapshot.bools["powerSupplyFault"] = status.psFault;
|
||||||
|
snapshot.bools["psFault"] = status.psFault;
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<DaliDomainSnapshot> DaliDomainService::dt1Snapshot(uint8_t gateway_id,
|
||||||
|
int short_address) const {
|
||||||
|
const auto* channel = findChannelByGateway(gateway_id);
|
||||||
|
if (channel == nullptr || channel->dali == nullptr) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
const auto detailed = channel->dali->dt1.getDT1TestStatusDetailed(short_address);
|
||||||
|
if (!detailed.has_value()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto snapshot = MakeSnapshot(gateway_id, short_address, "dt1");
|
||||||
|
PutOptionalInt(snapshot, "failureStatusRaw", detailed->failureStatus);
|
||||||
|
PutOptionalInt(snapshot, "emergencyStatusRaw", detailed->emergencyStatus);
|
||||||
|
PutOptionalInt(snapshot, "emergencyModeRaw", detailed->emergencyMode);
|
||||||
|
PutOptionalInt(snapshot, "featuresRaw", detailed->feature);
|
||||||
|
PutOptionalInt(snapshot, "deviceStatusRaw", detailed->deviceStatus);
|
||||||
|
|
||||||
|
snapshot.bools["testInProgress"] = detailed->testInProgress;
|
||||||
|
snapshot.bools["lampFailure"] = detailed->lampFailure;
|
||||||
|
snapshot.bools["batteryFailure"] = detailed->batteryFailure;
|
||||||
|
snapshot.bools["functionTestActive"] = detailed->functionTestActive;
|
||||||
|
snapshot.bools["durationTestActive"] = detailed->durationTestActive;
|
||||||
|
snapshot.bools["testDone"] = detailed->testDone;
|
||||||
|
snapshot.bools["identifyActive"] = detailed->identifyActive;
|
||||||
|
snapshot.bools["physicalSelectionActive"] = detailed->physicalSelectionActive;
|
||||||
|
snapshot.bools["circuitFailure"] = detailed->circuitFailure;
|
||||||
|
snapshot.bools["batteryDurationFailure"] = detailed->batteryDurationFailure;
|
||||||
|
snapshot.bools["emergencyLampFailure"] = detailed->emergencyLampFailure;
|
||||||
|
snapshot.bools["functionTestMaxDelayExceeded"] = detailed->functionTestMaxDelayExceeded;
|
||||||
|
snapshot.bools["durationTestMaxDelayExceeded"] = detailed->durationTestMaxDelayExceeded;
|
||||||
|
snapshot.bools["functionTestFailed"] = detailed->functionTestFailed;
|
||||||
|
snapshot.bools["durationTestFailed"] = detailed->durationTestFailed;
|
||||||
|
snapshot.bools["functionTestResultValid"] = detailed->functionTestResultValid;
|
||||||
|
snapshot.bools["durationTestResultValid"] = detailed->durationTestResultValid;
|
||||||
|
snapshot.bools["batteryFullyCharged"] = detailed->batteryFullyCharged;
|
||||||
|
snapshot.bools["functionTestPending"] = detailed->functionTestPending;
|
||||||
|
snapshot.bools["durationTestPending"] = detailed->durationTestPending;
|
||||||
|
snapshot.bools["restModeActive"] = detailed->restModeActive;
|
||||||
|
snapshot.bools["normalModeActive"] = detailed->normalModeActive;
|
||||||
|
snapshot.bools["emergencyModeActive"] = detailed->emergencyModeActive;
|
||||||
|
snapshot.bools["extendedEmergencyModeActive"] = detailed->extendedEmergencyModeActive;
|
||||||
|
snapshot.bools["hardwiredInhibitActive"] = detailed->hardwiredInhibitActive;
|
||||||
|
snapshot.bools["hardwiredSwitchOn"] = detailed->hardwiredSwitchOn;
|
||||||
|
snapshot.bools["supportsAutoTest"] = detailed->supportsAutoTest;
|
||||||
|
snapshot.bools["supportsAdjustableEmergencyLevel"] = detailed->supportsAdjustableEmergencyLevel;
|
||||||
|
|
||||||
|
if (detailed->emergencyStatus.has_value()) {
|
||||||
|
const DaliDT1EmergencyStatus status(detailed->emergencyStatus.value());
|
||||||
|
snapshot.bools["inhibitMode"] = status.inhibitMode();
|
||||||
|
snapshot.bools["functionTestRequestPending"] = status.functionTestRequestPending();
|
||||||
|
snapshot.bools["durationTestRequestPending"] = status.durationTestRequestPending();
|
||||||
|
snapshot.bools["identificationActive"] = status.identificationActive();
|
||||||
|
snapshot.bools["physicallySelected"] = status.physicallySelected();
|
||||||
|
}
|
||||||
|
if (detailed->emergencyMode.has_value()) {
|
||||||
|
const DaliDT1EmergencyMode mode(detailed->emergencyMode.value());
|
||||||
|
snapshot.bools["functionTestInProgress"] = mode.functionTestInProgress();
|
||||||
|
snapshot.bools["durationTestInProgress"] = mode.durationTestInProgress();
|
||||||
|
}
|
||||||
|
if (detailed->feature.has_value()) {
|
||||||
|
const DaliDT1Features features(detailed->feature.value());
|
||||||
|
snapshot.bools["integralEmergencyControlGear"] = features.integralEmergencyControlGear();
|
||||||
|
snapshot.bools["maintainedControlGear"] = features.maintainedControlGear();
|
||||||
|
snapshot.bools["switchedMaintainedControlGear"] = features.switchedMaintainedControlGear();
|
||||||
|
snapshot.bools["autoTestCapability"] = features.autoTestCapability();
|
||||||
|
snapshot.bools["adjustableEmergencyLevel"] = features.adjustableEmergencyLevel();
|
||||||
|
snapshot.bools["hardwiredInhibitSupported"] = features.hardwiredInhibitSupported();
|
||||||
|
snapshot.bools["physicalSelectionSupported"] = features.physicalSelectionSupported();
|
||||||
|
snapshot.bools["relightInRestModeSupported"] = features.relightInRestModeSupported();
|
||||||
|
}
|
||||||
|
if (detailed->deviceStatus.has_value()) {
|
||||||
|
const DaliDT1DeviceStatus status(detailed->deviceStatus.value());
|
||||||
|
snapshot.bools["controlGearFailure"] = status.controlGearFailure();
|
||||||
|
snapshot.bools["controlGearOk"] = status.controlGearOk();
|
||||||
|
snapshot.bools["lampPoweredByEmergencyGear"] = status.lampPoweredByEmergencyGear();
|
||||||
|
snapshot.bools["deviceResetState"] = status.resetState();
|
||||||
|
snapshot.bools["deviceMissingShortAddress"] = status.missingShortAddress();
|
||||||
|
}
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
std::optional<DaliDomainSnapshot> DaliDomainService::dt4Snapshot(uint8_t gateway_id,
|
std::optional<DaliDomainSnapshot> DaliDomainService::dt4Snapshot(uint8_t gateway_id,
|
||||||
int short_address) const {
|
int short_address) const {
|
||||||
const auto* channel = findChannelByGateway(gateway_id);
|
const auto* channel = findChannelByGateway(gateway_id);
|
||||||
@@ -506,15 +613,27 @@ std::optional<DaliDomainSnapshot> DaliDomainService::dt4Snapshot(uint8_t gateway
|
|||||||
snapshot.ints["featuresRaw2"] = features->raw2();
|
snapshot.ints["featuresRaw2"] = features->raw2();
|
||||||
snapshot.ints["featuresRaw3"] = features->raw3();
|
snapshot.ints["featuresRaw3"] = features->raw3();
|
||||||
snapshot.ints["dimmingMethodCode"] = features->dimmingMethodCode();
|
snapshot.ints["dimmingMethodCode"] = features->dimmingMethodCode();
|
||||||
|
snapshot.bools["canQueryLoadOverCurrentShutdown"] =
|
||||||
|
features->canQueryLoadOverCurrentShutdown();
|
||||||
|
snapshot.bools["canQueryOpenCircuitDetection"] = features->canQueryOpenCircuitDetection();
|
||||||
|
snapshot.bools["canQueryLoadDecrease"] = features->canQueryLoadDecrease();
|
||||||
|
snapshot.bools["canQueryLoadIncrease"] = features->canQueryLoadIncrease();
|
||||||
|
snapshot.bools["canQueryThermalShutdown"] = features->canQueryThermalShutdown();
|
||||||
|
snapshot.bools["canQueryThermalOverloadReduction"] =
|
||||||
|
features->canQueryThermalOverloadReduction();
|
||||||
snapshot.bools["canQueryTemperature"] = features->canQueryTemperature();
|
snapshot.bools["canQueryTemperature"] = features->canQueryTemperature();
|
||||||
snapshot.bools["canQuerySupplyVoltage"] = features->canQuerySupplyVoltage();
|
snapshot.bools["canQuerySupplyVoltage"] = features->canQuerySupplyVoltage();
|
||||||
|
snapshot.bools["canQuerySupplyFrequency"] = features->canQuerySupplyFrequency();
|
||||||
snapshot.bools["canQueryLoadVoltage"] = features->canQueryLoadVoltage();
|
snapshot.bools["canQueryLoadVoltage"] = features->canQueryLoadVoltage();
|
||||||
snapshot.bools["canQueryLoadCurrent"] = features->canQueryLoadCurrent();
|
snapshot.bools["canQueryLoadCurrent"] = features->canQueryLoadCurrent();
|
||||||
snapshot.bools["canQueryRealLoadPower"] = features->canQueryRealLoadPower();
|
snapshot.bools["canQueryRealLoadPower"] = features->canQueryRealLoadPower();
|
||||||
snapshot.bools["canQueryLoadRating"] = features->canQueryLoadRating();
|
snapshot.bools["canQueryLoadRating"] = features->canQueryLoadRating();
|
||||||
|
snapshot.bools["canQueryCurrentOverloadReduction"] =
|
||||||
|
features->canQueryCurrentOverloadReduction();
|
||||||
snapshot.bools["physicalSelectionSupported"] = features->physicalSelectionSupported();
|
snapshot.bools["physicalSelectionSupported"] = features->physicalSelectionSupported();
|
||||||
snapshot.bools["canSelectNonLogarithmicDimmingCurve"] =
|
snapshot.bools["canSelectNonLogarithmicDimmingCurve"] =
|
||||||
features->canSelectNonLogarithmicDimmingCurve();
|
features->canSelectNonLogarithmicDimmingCurve();
|
||||||
|
snapshot.bools["canQueryUnsuitableLoad"] = features->canQueryUnsuitableLoad();
|
||||||
}
|
}
|
||||||
if (const auto failure = dt4.getFailureStatus(short_address)) {
|
if (const auto failure = dt4.getFailureStatus(short_address)) {
|
||||||
snapshot.ints["failureRaw1"] = failure->raw1();
|
snapshot.ints["failureRaw1"] = failure->raw1();
|
||||||
@@ -526,7 +645,10 @@ std::optional<DaliDomainSnapshot> DaliDomainService::dt4Snapshot(uint8_t gateway
|
|||||||
snapshot.bools["thermalShutdown"] = failure->thermalShutdown();
|
snapshot.bools["thermalShutdown"] = failure->thermalShutdown();
|
||||||
snapshot.bools["thermalOverloadReduction"] = failure->thermalOverloadReduction();
|
snapshot.bools["thermalOverloadReduction"] = failure->thermalOverloadReduction();
|
||||||
snapshot.bools["referenceMeasurementFailedStatus"] = failure->referenceMeasurementFailed();
|
snapshot.bools["referenceMeasurementFailedStatus"] = failure->referenceMeasurementFailed();
|
||||||
|
snapshot.bools["loadUnsuitableForSelectedMethod"] =
|
||||||
|
failure->loadUnsuitableForSelectedMethod();
|
||||||
snapshot.bools["supplyVoltageOutOfLimits"] = failure->supplyVoltageOutOfLimits();
|
snapshot.bools["supplyVoltageOutOfLimits"] = failure->supplyVoltageOutOfLimits();
|
||||||
|
snapshot.bools["supplyFrequencyOutOfLimits"] = failure->supplyFrequencyOutOfLimits();
|
||||||
snapshot.bools["loadVoltageOutOfLimits"] = failure->loadVoltageOutOfLimits();
|
snapshot.bools["loadVoltageOutOfLimits"] = failure->loadVoltageOutOfLimits();
|
||||||
snapshot.bools["loadCurrentOverloadReduction"] = failure->loadCurrentOverloadReduction();
|
snapshot.bools["loadCurrentOverloadReduction"] = failure->loadCurrentOverloadReduction();
|
||||||
}
|
}
|
||||||
@@ -637,6 +759,40 @@ std::optional<DaliDomainSnapshot> DaliDomainService::dt6Snapshot(uint8_t gateway
|
|||||||
return snapshot;
|
return snapshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<DaliDomainSnapshot> DaliDomainService::dt8StatusSnapshot(
|
||||||
|
uint8_t gateway_id, int short_address) const {
|
||||||
|
const auto* channel = findChannelByGateway(gateway_id);
|
||||||
|
if (channel == nullptr || channel->dali == nullptr) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto snapshot = MakeSnapshot(gateway_id, short_address, "dt8_status");
|
||||||
|
bool has_data = false;
|
||||||
|
if (const auto status = channel->dali->dt8.getColorStatus(short_address)) {
|
||||||
|
has_data = true;
|
||||||
|
snapshot.ints["colorStatusRaw"] = status->raw();
|
||||||
|
snapshot.bools["xyOutOfRange"] = status->xyOutOfRange();
|
||||||
|
snapshot.bools["ctOutOfRange"] = status->ctOutOfRange();
|
||||||
|
snapshot.bools["autoCalibrationActive"] = status->autoCalibrationActive();
|
||||||
|
snapshot.bools["autoCalibrationSuccess"] = status->autoCalibrationSuccess();
|
||||||
|
snapshot.bools["xyActive"] = status->xyActive();
|
||||||
|
snapshot.bools["ctActive"] = status->ctActive();
|
||||||
|
snapshot.bools["primaryNActive"] = status->primaryNActive();
|
||||||
|
snapshot.bools["rgbwafActive"] = status->rgbwafActive();
|
||||||
|
}
|
||||||
|
if (const auto features = channel->dali->dt8.getColorTypeFeature(short_address)) {
|
||||||
|
has_data = true;
|
||||||
|
snapshot.ints["colorTypeFeaturesRaw"] = features->features();
|
||||||
|
snapshot.ints["primaryCount"] = features->primaryCount();
|
||||||
|
snapshot.ints["rgbwafChannels"] = features->rgbwafChannels();
|
||||||
|
snapshot.bools["xyCapable"] = features->xyCapable();
|
||||||
|
snapshot.bools["ctCapable"] = features->ctCapable();
|
||||||
|
snapshot.bools["primaryNCapable"] = features->primaryNCapable();
|
||||||
|
snapshot.bools["rgbwafCapable"] = features->rgbwafCapable();
|
||||||
|
}
|
||||||
|
return has_data ? std::optional<DaliDomainSnapshot>(std::move(snapshot)) : std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
std::optional<DaliDomainSnapshot> DaliDomainService::dt8SceneColorReport(
|
std::optional<DaliDomainSnapshot> DaliDomainService::dt8SceneColorReport(
|
||||||
uint8_t gateway_id, int short_address, int scene) const {
|
uint8_t gateway_id, int short_address, int scene) const {
|
||||||
const auto* channel = findChannelByGateway(gateway_id);
|
const auto* channel = findChannelByGateway(gateway_id);
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ set(BACNET_PORT_SRCS
|
|||||||
|
|
||||||
idf_component_register(
|
idf_component_register(
|
||||||
SRCS
|
SRCS
|
||||||
|
"src/gateway_bacnet_bridge.cpp"
|
||||||
"src/gateway_bacnet.cpp"
|
"src/gateway_bacnet.cpp"
|
||||||
"src/gateway_bacnet_stack_port.c"
|
"src/gateway_bacnet_stack_port.c"
|
||||||
"src/bip_socket_lwip.cpp"
|
"src/bip_socket_lwip.cpp"
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -32,6 +33,7 @@ struct GatewayBacnetObjectBinding {
|
|||||||
std::string property{"presentValue"};
|
std::string property{"presentValue"};
|
||||||
bool out_of_service{false};
|
bool out_of_service{false};
|
||||||
uint32_t reliability{0};
|
uint32_t reliability{0};
|
||||||
|
bool readable{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct GatewayBacnetServerStatus {
|
struct GatewayBacnetServerStatus {
|
||||||
@@ -46,13 +48,19 @@ using GatewayBacnetWriteCallback =
|
|||||||
std::function<bool(BridgeObjectType object_type, uint32_t object_instance,
|
std::function<bool(BridgeObjectType object_type, uint32_t object_instance,
|
||||||
const std::string& property, const DaliValue& value)>;
|
const std::string& property, const DaliValue& value)>;
|
||||||
|
|
||||||
|
using GatewayBacnetReadCallback =
|
||||||
|
std::function<std::optional<DaliValue>(BridgeObjectType object_type,
|
||||||
|
uint32_t object_instance,
|
||||||
|
const std::string& property)>;
|
||||||
|
|
||||||
class GatewayBacnetServer {
|
class GatewayBacnetServer {
|
||||||
public:
|
public:
|
||||||
static GatewayBacnetServer& instance();
|
static GatewayBacnetServer& instance();
|
||||||
|
|
||||||
esp_err_t registerChannel(uint8_t gateway_id, const GatewayBacnetServerConfig& config,
|
esp_err_t registerChannel(uint8_t gateway_id, const GatewayBacnetServerConfig& config,
|
||||||
std::vector<GatewayBacnetObjectBinding> bindings,
|
std::vector<GatewayBacnetObjectBinding> bindings,
|
||||||
GatewayBacnetWriteCallback write_callback);
|
GatewayBacnetWriteCallback write_callback,
|
||||||
|
GatewayBacnetReadCallback read_callback = nullptr);
|
||||||
GatewayBacnetServerStatus status() const;
|
GatewayBacnetServerStatus status() const;
|
||||||
bool configCompatible(const GatewayBacnetServerConfig& config) const;
|
bool configCompatible(const GatewayBacnetServerConfig& config) const;
|
||||||
bool handleWrite(BridgeObjectType object_type, uint32_t object_instance,
|
bool handleWrite(BridgeObjectType object_type, uint32_t object_instance,
|
||||||
@@ -70,6 +78,7 @@ class GatewayBacnetServer {
|
|||||||
|
|
||||||
esp_err_t startStackLocked(const GatewayBacnetServerConfig& config);
|
esp_err_t startStackLocked(const GatewayBacnetServerConfig& config);
|
||||||
esp_err_t rebuildObjectsLocked();
|
esp_err_t rebuildObjectsLocked();
|
||||||
|
void refreshPresentValues();
|
||||||
|
|
||||||
static void TaskEntry(void* arg);
|
static void TaskEntry(void* arg);
|
||||||
void taskLoop();
|
void taskLoop();
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "bridge.hpp"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace gateway {
|
||||||
|
|
||||||
|
struct GatewayBacnetBridgeConfig {
|
||||||
|
uint32_t deviceInstance{4194303};
|
||||||
|
std::string localAddress;
|
||||||
|
uint16_t udpPort{47808};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GatewayBacnetModelBinding {
|
||||||
|
std::string modelID;
|
||||||
|
BridgeObjectType objectType{BridgeObjectType::unknown};
|
||||||
|
int objectInstance{-1};
|
||||||
|
std::string property;
|
||||||
|
BridgeOperation operation{BridgeOperation::unknown};
|
||||||
|
BridgeDaliTarget target;
|
||||||
|
std::optional<int> bitIndex;
|
||||||
|
};
|
||||||
|
|
||||||
|
class GatewayBacnetBridgeAdapter {
|
||||||
|
public:
|
||||||
|
explicit GatewayBacnetBridgeAdapter(DaliBridgeEngine& engine);
|
||||||
|
|
||||||
|
void setConfig(const GatewayBacnetBridgeConfig& config);
|
||||||
|
const GatewayBacnetBridgeConfig& config() const;
|
||||||
|
|
||||||
|
DaliBridgeResult handlePropertyWrite(BridgeObjectType object_type,
|
||||||
|
int object_instance,
|
||||||
|
const std::string& property,
|
||||||
|
const DaliValue& value) const;
|
||||||
|
DaliBridgeResult readProperty(BridgeObjectType object_type,
|
||||||
|
int object_instance,
|
||||||
|
const std::string& property) const;
|
||||||
|
std::optional<GatewayBacnetModelBinding> findObject(BridgeObjectType object_type,
|
||||||
|
int object_instance,
|
||||||
|
const std::string& property) const;
|
||||||
|
std::vector<GatewayBacnetModelBinding> describeObjects() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
DaliBridgeResult executeBinding(const GatewayBacnetModelBinding& binding,
|
||||||
|
const std::string& sequence,
|
||||||
|
const DaliValue* value) const;
|
||||||
|
|
||||||
|
DaliBridgeEngine& engine_;
|
||||||
|
GatewayBacnetBridgeConfig config_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace gateway
|
||||||
@@ -52,9 +52,20 @@ bool gateway_bacnet_stack_upsert_object(
|
|||||||
gateway_bacnet_object_kind_t object_kind,
|
gateway_bacnet_object_kind_t object_kind,
|
||||||
uint32_t object_instance,
|
uint32_t object_instance,
|
||||||
const char* object_name,
|
const char* object_name,
|
||||||
const char* description,
|
const char* description,
|
||||||
bool out_of_service,
|
bool out_of_service,
|
||||||
uint32_t reliability);
|
uint32_t reliability);
|
||||||
|
|
||||||
|
bool gateway_bacnet_stack_set_object_state(
|
||||||
|
gateway_bacnet_object_kind_t object_kind,
|
||||||
|
uint32_t object_instance,
|
||||||
|
bool out_of_service,
|
||||||
|
uint32_t reliability);
|
||||||
|
|
||||||
|
bool gateway_bacnet_stack_set_present_value(
|
||||||
|
gateway_bacnet_object_kind_t object_kind,
|
||||||
|
uint32_t object_instance,
|
||||||
|
const gateway_bacnet_write_value_t* value);
|
||||||
|
|
||||||
bool gateway_bacnet_stack_clear_objects(void);
|
bool gateway_bacnet_stack_clear_objects(void);
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ namespace {
|
|||||||
|
|
||||||
constexpr const char* kTag = "gateway_bacnet";
|
constexpr const char* kTag = "gateway_bacnet";
|
||||||
constexpr TickType_t kPollDelayTicks = pdMS_TO_TICKS(10);
|
constexpr TickType_t kPollDelayTicks = pdMS_TO_TICKS(10);
|
||||||
|
constexpr TickType_t kValueRefreshTicks = pdMS_TO_TICKS(2000);
|
||||||
|
constexpr uint32_t kReliabilityCommunicationFailure = 12;
|
||||||
|
|
||||||
class LockGuard {
|
class LockGuard {
|
||||||
public:
|
public:
|
||||||
@@ -125,6 +127,59 @@ DaliValue StackWriteValueToDali(const gateway_bacnet_write_value_t& value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool DaliValueToStackPresentValue(BridgeObjectType object_type, const DaliValue& value,
|
||||||
|
gateway_bacnet_write_value_t* out) {
|
||||||
|
if (out == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
switch (ToBacnetKind(object_type)) {
|
||||||
|
case GW_BACNET_OBJECT_ANALOG_INPUT:
|
||||||
|
case GW_BACNET_OBJECT_ANALOG_VALUE:
|
||||||
|
case GW_BACNET_OBJECT_ANALOG_OUTPUT: {
|
||||||
|
const auto parsed = value.asDouble();
|
||||||
|
if (!parsed.has_value()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*out = gateway_bacnet_write_value_t{GW_BACNET_WRITE_VALUE_REAL,
|
||||||
|
parsed.value(),
|
||||||
|
false,
|
||||||
|
0};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case GW_BACNET_OBJECT_BINARY_INPUT:
|
||||||
|
case GW_BACNET_OBJECT_BINARY_VALUE:
|
||||||
|
case GW_BACNET_OBJECT_BINARY_OUTPUT: {
|
||||||
|
const auto parsed = value.asBool();
|
||||||
|
if (!parsed.has_value()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*out = gateway_bacnet_write_value_t{GW_BACNET_WRITE_VALUE_BOOLEAN,
|
||||||
|
0.0,
|
||||||
|
parsed.value(),
|
||||||
|
0};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case GW_BACNET_OBJECT_MULTI_STATE_INPUT:
|
||||||
|
case GW_BACNET_OBJECT_MULTI_STATE_VALUE:
|
||||||
|
case GW_BACNET_OBJECT_MULTI_STATE_OUTPUT: {
|
||||||
|
const auto parsed = value.asInt();
|
||||||
|
if (!parsed.has_value()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const uint32_t unsigned_value = parsed.value() <= 0
|
||||||
|
? 1
|
||||||
|
: static_cast<uint32_t>(parsed.value());
|
||||||
|
*out = gateway_bacnet_write_value_t{GW_BACNET_WRITE_VALUE_UNSIGNED,
|
||||||
|
0.0,
|
||||||
|
false,
|
||||||
|
unsigned_value};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void HandleStackWrite(gateway_bacnet_object_kind_t object_kind, uint32_t object_instance,
|
void HandleStackWrite(gateway_bacnet_object_kind_t object_kind, uint32_t object_instance,
|
||||||
const gateway_bacnet_write_value_t* value, void*) {
|
const gateway_bacnet_write_value_t* value, void*) {
|
||||||
if (g_server == nullptr || value == nullptr) {
|
if (g_server == nullptr || value == nullptr) {
|
||||||
@@ -140,6 +195,7 @@ struct GatewayBacnetServer::ChannelRegistration {
|
|||||||
GatewayBacnetServerConfig config;
|
GatewayBacnetServerConfig config;
|
||||||
std::vector<GatewayBacnetObjectBinding> bindings;
|
std::vector<GatewayBacnetObjectBinding> bindings;
|
||||||
GatewayBacnetWriteCallback write_callback;
|
GatewayBacnetWriteCallback write_callback;
|
||||||
|
GatewayBacnetReadCallback read_callback;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct GatewayBacnetServer::RuntimeBinding {
|
struct GatewayBacnetServer::RuntimeBinding {
|
||||||
@@ -150,7 +206,9 @@ struct GatewayBacnetServer::RuntimeBinding {
|
|||||||
std::string property{"presentValue"};
|
std::string property{"presentValue"};
|
||||||
bool out_of_service{false};
|
bool out_of_service{false};
|
||||||
uint32_t reliability{0};
|
uint32_t reliability{0};
|
||||||
|
bool readable{false};
|
||||||
GatewayBacnetWriteCallback write_callback;
|
GatewayBacnetWriteCallback write_callback;
|
||||||
|
GatewayBacnetReadCallback read_callback;
|
||||||
};
|
};
|
||||||
|
|
||||||
GatewayBacnetServer& GatewayBacnetServer::instance() {
|
GatewayBacnetServer& GatewayBacnetServer::instance() {
|
||||||
@@ -187,7 +245,8 @@ GatewayBacnetServerStatus GatewayBacnetServer::status() const {
|
|||||||
esp_err_t GatewayBacnetServer::registerChannel(
|
esp_err_t GatewayBacnetServer::registerChannel(
|
||||||
uint8_t gateway_id, const GatewayBacnetServerConfig& config,
|
uint8_t gateway_id, const GatewayBacnetServerConfig& config,
|
||||||
std::vector<GatewayBacnetObjectBinding> bindings,
|
std::vector<GatewayBacnetObjectBinding> bindings,
|
||||||
GatewayBacnetWriteCallback write_callback) {
|
GatewayBacnetWriteCallback write_callback,
|
||||||
|
GatewayBacnetReadCallback read_callback) {
|
||||||
if (write_callback == nullptr) {
|
if (write_callback == nullptr) {
|
||||||
return ESP_ERR_INVALID_ARG;
|
return ESP_ERR_INVALID_ARG;
|
||||||
}
|
}
|
||||||
@@ -210,7 +269,7 @@ esp_err_t GatewayBacnetServer::registerChannel(
|
|||||||
return ESP_ERR_NOT_FOUND;
|
return ESP_ERR_NOT_FOUND;
|
||||||
}
|
}
|
||||||
ChannelRegistration registration{gateway_id, config, std::move(bindings),
|
ChannelRegistration registration{gateway_id, config, std::move(bindings),
|
||||||
std::move(write_callback)};
|
std::move(write_callback), std::move(read_callback)};
|
||||||
if (channel == channels_.end()) {
|
if (channel == channels_.end()) {
|
||||||
channels_.push_back(std::move(registration));
|
channels_.push_back(std::move(registration));
|
||||||
} else {
|
} else {
|
||||||
@@ -297,7 +356,9 @@ esp_err_t GatewayBacnetServer::rebuildObjectsLocked() {
|
|||||||
: binding.property,
|
: binding.property,
|
||||||
binding.out_of_service,
|
binding.out_of_service,
|
||||||
binding.reliability,
|
binding.reliability,
|
||||||
channel.write_callback});
|
binding.readable,
|
||||||
|
channel.write_callback,
|
||||||
|
channel.read_callback});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,12 +399,47 @@ bool GatewayBacnetServer::handleWrite(BridgeObjectType object_type, uint32_t obj
|
|||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GatewayBacnetServer::refreshPresentValues() {
|
||||||
|
std::vector<RuntimeBinding> bindings;
|
||||||
|
{
|
||||||
|
LockGuard guard(lock_);
|
||||||
|
bindings = runtime_bindings_;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& binding : bindings) {
|
||||||
|
const auto object_kind = ToBacnetKind(binding.object_type);
|
||||||
|
if (object_kind == GW_BACNET_OBJECT_UNKNOWN) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!binding.readable || binding.read_callback == nullptr) {
|
||||||
|
LockGuard guard(lock_);
|
||||||
|
gateway_bacnet_stack_set_object_state(object_kind, binding.object_instance,
|
||||||
|
binding.out_of_service, binding.reliability);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
gateway_bacnet_write_value_t stack_value = {};
|
||||||
|
const auto value = binding.read_callback(binding.object_type, binding.object_instance,
|
||||||
|
binding.property);
|
||||||
|
const bool converted = value.has_value() &&
|
||||||
|
DaliValueToStackPresentValue(binding.object_type, value.value(),
|
||||||
|
&stack_value);
|
||||||
|
LockGuard guard(lock_);
|
||||||
|
const bool ok = converted && gateway_bacnet_stack_set_present_value(
|
||||||
|
object_kind, binding.object_instance, &stack_value);
|
||||||
|
gateway_bacnet_stack_set_object_state(
|
||||||
|
object_kind, binding.object_instance, binding.out_of_service || !ok,
|
||||||
|
ok ? binding.reliability : kReliabilityCommunicationFailure);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void GatewayBacnetServer::TaskEntry(void* arg) {
|
void GatewayBacnetServer::TaskEntry(void* arg) {
|
||||||
static_cast<GatewayBacnetServer*>(arg)->taskLoop();
|
static_cast<GatewayBacnetServer*>(arg)->taskLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GatewayBacnetServer::taskLoop() {
|
void GatewayBacnetServer::taskLoop() {
|
||||||
TickType_t last_timer = xTaskGetTickCount();
|
TickType_t last_timer = xTaskGetTickCount();
|
||||||
|
TickType_t last_refresh = last_timer;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const TickType_t now = xTaskGetTickCount();
|
const TickType_t now = xTaskGetTickCount();
|
||||||
@@ -353,9 +449,17 @@ void GatewayBacnetServer::taskLoop() {
|
|||||||
elapsed_ms = static_cast<uint16_t>(elapsed * portTICK_PERIOD_MS);
|
elapsed_ms = static_cast<uint16_t>(elapsed * portTICK_PERIOD_MS);
|
||||||
last_timer = now;
|
last_timer = now;
|
||||||
}
|
}
|
||||||
|
bool refresh_due = false;
|
||||||
{
|
{
|
||||||
LockGuard guard(lock_);
|
LockGuard guard(lock_);
|
||||||
gateway_bacnet_stack_poll(elapsed_ms);
|
gateway_bacnet_stack_poll(elapsed_ms);
|
||||||
|
if ((now - last_refresh) >= kValueRefreshTicks) {
|
||||||
|
refresh_due = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (refresh_due) {
|
||||||
|
refreshPresentValues();
|
||||||
|
last_refresh = now;
|
||||||
}
|
}
|
||||||
vTaskDelay(kPollDelayTicks);
|
vTaskDelay(kPollDelayTicks);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
#include "gateway_bacnet_bridge.hpp"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace gateway {
|
||||||
|
|
||||||
|
GatewayBacnetBridgeAdapter::GatewayBacnetBridgeAdapter(DaliBridgeEngine& engine)
|
||||||
|
: engine_(engine) {}
|
||||||
|
|
||||||
|
void GatewayBacnetBridgeAdapter::setConfig(const GatewayBacnetBridgeConfig& config) {
|
||||||
|
config_ = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
const GatewayBacnetBridgeConfig& GatewayBacnetBridgeAdapter::config() const { return config_; }
|
||||||
|
|
||||||
|
DaliBridgeResult GatewayBacnetBridgeAdapter::handlePropertyWrite(
|
||||||
|
BridgeObjectType object_type, int object_instance, const std::string& property,
|
||||||
|
const DaliValue& value) const {
|
||||||
|
const auto binding = findObject(object_type, object_instance, property);
|
||||||
|
if (!binding.has_value()) {
|
||||||
|
DaliBridgeResult result;
|
||||||
|
result.sequence = "bacnet-" + std::to_string(object_instance);
|
||||||
|
result.error = "unmapped bacnet object";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
const std::string sequence = "bacnet-" + std::to_string(object_instance);
|
||||||
|
return executeBinding(binding.value(), sequence, &value);
|
||||||
|
}
|
||||||
|
|
||||||
|
DaliBridgeResult GatewayBacnetBridgeAdapter::readProperty(BridgeObjectType object_type,
|
||||||
|
int object_instance,
|
||||||
|
const std::string& property) const {
|
||||||
|
const auto binding = findObject(object_type, object_instance, property);
|
||||||
|
if (!binding.has_value()) {
|
||||||
|
DaliBridgeResult result;
|
||||||
|
result.sequence = "bacnet-read-" + std::to_string(object_instance);
|
||||||
|
result.error = "unmapped bacnet object";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
const std::string sequence = "bacnet-read-" + std::to_string(object_instance);
|
||||||
|
return executeBinding(binding.value(), sequence, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<GatewayBacnetModelBinding> GatewayBacnetBridgeAdapter::findObject(
|
||||||
|
BridgeObjectType object_type, int object_instance, const std::string& property) const {
|
||||||
|
for (const auto& model : engine_.listModels()) {
|
||||||
|
if (model.protocol != BridgeProtocolKind::bacnet) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (model.external.objectType != object_type) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (model.external.objectInstance.value_or(-1) != object_instance) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!model.external.property.empty() && model.external.property != property) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const std::string binding_property = model.external.property.empty() ? property
|
||||||
|
: model.external.property;
|
||||||
|
return GatewayBacnetModelBinding{model.id,
|
||||||
|
object_type,
|
||||||
|
object_instance,
|
||||||
|
binding_property,
|
||||||
|
model.operation,
|
||||||
|
model.dali,
|
||||||
|
model.external.bitIndex};
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<GatewayBacnetModelBinding> GatewayBacnetBridgeAdapter::describeObjects() const {
|
||||||
|
std::vector<GatewayBacnetModelBinding> bindings;
|
||||||
|
for (const auto& model : engine_.listModels()) {
|
||||||
|
if (model.protocol != BridgeProtocolKind::bacnet || !model.external.objectInstance.has_value()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
bindings.push_back(GatewayBacnetModelBinding{model.id,
|
||||||
|
model.external.objectType,
|
||||||
|
model.external.objectInstance.value(),
|
||||||
|
model.external.property,
|
||||||
|
model.operation,
|
||||||
|
model.dali,
|
||||||
|
model.external.bitIndex});
|
||||||
|
}
|
||||||
|
return bindings;
|
||||||
|
}
|
||||||
|
|
||||||
|
DaliBridgeResult GatewayBacnetBridgeAdapter::executeBinding(
|
||||||
|
const GatewayBacnetModelBinding& binding, const std::string& sequence,
|
||||||
|
const DaliValue* value) const {
|
||||||
|
DaliBridgeRequest request;
|
||||||
|
request.sequence = sequence;
|
||||||
|
request.modelID = binding.modelID;
|
||||||
|
if (value != nullptr) {
|
||||||
|
request.value = *value;
|
||||||
|
}
|
||||||
|
return engine_.execute(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace gateway
|
||||||
@@ -522,6 +522,91 @@ bool gateway_bacnet_stack_upsert_object(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool gateway_bacnet_stack_set_object_state(
|
||||||
|
gateway_bacnet_object_kind_t object_kind,
|
||||||
|
uint32_t object_instance,
|
||||||
|
bool out_of_service,
|
||||||
|
uint32_t reliability)
|
||||||
|
{
|
||||||
|
BACNET_RELIABILITY object_reliability = (BACNET_RELIABILITY)reliability;
|
||||||
|
|
||||||
|
switch (object_kind) {
|
||||||
|
case GW_BACNET_OBJECT_ANALOG_VALUE:
|
||||||
|
set_analog_value_state(object_instance, out_of_service, object_reliability);
|
||||||
|
return true;
|
||||||
|
case GW_BACNET_OBJECT_ANALOG_OUTPUT:
|
||||||
|
set_analog_output_state(object_instance, out_of_service, object_reliability);
|
||||||
|
return true;
|
||||||
|
case GW_BACNET_OBJECT_BINARY_VALUE:
|
||||||
|
set_binary_value_state(object_instance, out_of_service, object_reliability);
|
||||||
|
return true;
|
||||||
|
case GW_BACNET_OBJECT_BINARY_OUTPUT:
|
||||||
|
set_binary_output_state(object_instance, out_of_service, object_reliability);
|
||||||
|
return true;
|
||||||
|
case GW_BACNET_OBJECT_MULTI_STATE_VALUE:
|
||||||
|
set_multistate_value_state(object_instance, out_of_service, object_reliability);
|
||||||
|
return true;
|
||||||
|
case GW_BACNET_OBJECT_ANALOG_INPUT:
|
||||||
|
set_analog_input_state(object_instance, out_of_service, object_reliability);
|
||||||
|
return true;
|
||||||
|
case GW_BACNET_OBJECT_BINARY_INPUT:
|
||||||
|
set_binary_input_state(object_instance, out_of_service, object_reliability);
|
||||||
|
return true;
|
||||||
|
case GW_BACNET_OBJECT_MULTI_STATE_INPUT:
|
||||||
|
set_multistate_input_state(object_instance, out_of_service, object_reliability);
|
||||||
|
return true;
|
||||||
|
case GW_BACNET_OBJECT_MULTI_STATE_OUTPUT:
|
||||||
|
set_multistate_output_state(object_instance, out_of_service, object_reliability);
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool gateway_bacnet_stack_set_present_value(
|
||||||
|
gateway_bacnet_object_kind_t object_kind,
|
||||||
|
uint32_t object_instance,
|
||||||
|
const gateway_bacnet_write_value_t* value)
|
||||||
|
{
|
||||||
|
if (!value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (object_kind) {
|
||||||
|
case GW_BACNET_OBJECT_ANALOG_VALUE:
|
||||||
|
return Analog_Value_Present_Value_Set(
|
||||||
|
object_instance, (float)value->real_value, BACNET_NO_PRIORITY);
|
||||||
|
case GW_BACNET_OBJECT_ANALOG_OUTPUT:
|
||||||
|
return Analog_Output_Present_Value_Set(
|
||||||
|
object_instance, (float)value->real_value, BACNET_MAX_PRIORITY);
|
||||||
|
case GW_BACNET_OBJECT_ANALOG_INPUT:
|
||||||
|
Analog_Input_Present_Value_Set(object_instance, (float)value->real_value);
|
||||||
|
return true;
|
||||||
|
case GW_BACNET_OBJECT_BINARY_VALUE:
|
||||||
|
return Binary_Value_Present_Value_Set(
|
||||||
|
object_instance, value->boolean_value ? BINARY_ACTIVE : BINARY_INACTIVE);
|
||||||
|
case GW_BACNET_OBJECT_BINARY_OUTPUT:
|
||||||
|
return Binary_Output_Present_Value_Set(
|
||||||
|
object_instance, value->boolean_value ? BINARY_ACTIVE : BINARY_INACTIVE,
|
||||||
|
BACNET_MAX_PRIORITY);
|
||||||
|
case GW_BACNET_OBJECT_BINARY_INPUT:
|
||||||
|
return Binary_Input_Present_Value_Set(
|
||||||
|
object_instance, value->boolean_value ? BINARY_ACTIVE : BINARY_INACTIVE);
|
||||||
|
case GW_BACNET_OBJECT_MULTI_STATE_VALUE:
|
||||||
|
return Multistate_Value_Present_Value_Set(
|
||||||
|
object_instance, value->unsigned_value == 0 ? 1 : value->unsigned_value);
|
||||||
|
case GW_BACNET_OBJECT_MULTI_STATE_OUTPUT:
|
||||||
|
return Multistate_Output_Present_Value_Set(
|
||||||
|
object_instance, value->unsigned_value == 0 ? 1 : value->unsigned_value,
|
||||||
|
BACNET_MAX_PRIORITY);
|
||||||
|
case GW_BACNET_OBJECT_MULTI_STATE_INPUT:
|
||||||
|
return Multistate_Input_Present_Value_Set(
|
||||||
|
object_instance, value->unsigned_value == 0 ? 1 : value->unsigned_value);
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool gateway_bacnet_stack_clear_objects(void)
|
bool gateway_bacnet_stack_clear_objects(void)
|
||||||
{
|
{
|
||||||
return clear_analog_value_objects() &&
|
return clear_analog_value_objects() &&
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#include "gateway_bridge.hpp"
|
#include "gateway_bridge.hpp"
|
||||||
|
|
||||||
|
#include "gateway_bacnet_bridge.hpp"
|
||||||
#if defined(CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED)
|
#if defined(CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED)
|
||||||
#include "bacnet_bridge.hpp"
|
|
||||||
#include "gateway_bacnet.hpp"
|
#include "gateway_bacnet.hpp"
|
||||||
#endif
|
#endif
|
||||||
#include "bridge.hpp"
|
#include "bridge.hpp"
|
||||||
@@ -22,6 +22,7 @@
|
|||||||
#include "lwip/sockets.h"
|
#include "lwip/sockets.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cstdint>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <map>
|
#include <map>
|
||||||
@@ -40,12 +41,18 @@ constexpr const char* kBridgeConfigKey = "bridge_cfg";
|
|||||||
constexpr const char* kDiscoveryInventoryKey = "bridge_disc";
|
constexpr const char* kDiscoveryInventoryKey = "bridge_disc";
|
||||||
constexpr int kMaxDaliShortAddress = 63;
|
constexpr int kMaxDaliShortAddress = 63;
|
||||||
constexpr uint16_t kModbusUnknownRegister = 0xFFFF;
|
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 kBacnetReliabilityNoFaultDetected = 0;
|
||||||
constexpr uint32_t kBacnetReliabilityCommunicationFailure = 12;
|
constexpr uint32_t kBacnetReliabilityCommunicationFailure = 12;
|
||||||
|
|
||||||
struct GatewayBridgeStoredConfig {
|
struct GatewayBridgeStoredConfig {
|
||||||
BridgeRuntimeConfig bridge;
|
BridgeRuntimeConfig bridge;
|
||||||
std::optional<GatewayModbusConfig> modbus;
|
std::optional<GatewayModbusConfig> modbus;
|
||||||
|
std::optional<GatewayBacnetBridgeConfig> bacnet_server;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct BridgeDiscoveryEntry {
|
struct BridgeDiscoveryEntry {
|
||||||
@@ -243,6 +250,18 @@ bool SnapshotHasDeviceType(const DaliDomainSnapshot& snapshot, int device_type)
|
|||||||
types->second.end();
|
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) {
|
bool OperationRequiresDt1(BridgeOperation operation) {
|
||||||
switch (operation) {
|
switch (operation) {
|
||||||
case BridgeOperation::getEmergencyLevel:
|
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) {
|
std::optional<int> BridgeTargetValue(const BridgeDaliTarget& target) {
|
||||||
switch (target.kind) {
|
switch (target.kind) {
|
||||||
case BridgeDaliTargetKind::shortAddress:
|
case BridgeDaliTargetKind::shortAddress:
|
||||||
@@ -753,27 +788,53 @@ cJSON* ToCjson(const DaliValue& value) {
|
|||||||
|
|
||||||
DaliValue::Object GatewayBridgeStoredConfigToValue(
|
DaliValue::Object GatewayBridgeStoredConfigToValue(
|
||||||
const BridgeRuntimeConfig& bridge_config,
|
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();
|
DaliValue::Object out = bridge_config.toJson();
|
||||||
if (modbus_config.has_value()) {
|
if (modbus_config.has_value()) {
|
||||||
out["modbus"] = GatewayModbusConfigToValue(modbus_config.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;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string GatewayBridgeStoredConfigToJson(
|
std::string GatewayBridgeStoredConfigToJson(
|
||||||
const BridgeRuntimeConfig& bridge_config,
|
const BridgeRuntimeConfig& bridge_config,
|
||||||
const std::optional<GatewayModbusConfig>& modbus_config) {
|
const std::optional<GatewayModbusConfig>& modbus_config,
|
||||||
cJSON* root = ToCjson(DaliValue(GatewayBridgeStoredConfigToValue(bridge_config, 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);
|
const std::string body = PrintJson(root);
|
||||||
cJSON_Delete(root);
|
cJSON_Delete(root);
|
||||||
return body;
|
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 GatewayBridgeStoredConfigFromValue(const DaliValue::Object& object) {
|
||||||
GatewayBridgeStoredConfig config;
|
GatewayBridgeStoredConfig config;
|
||||||
config.bridge = BridgeRuntimeConfig::fromJson(object);
|
config.bridge = BridgeRuntimeConfig::fromJson(object);
|
||||||
config.modbus = GatewayModbusConfigFromValue(getObjectValue(object, "modbus"));
|
config.modbus = GatewayModbusConfigFromValue(getObjectValue(object, "modbus"));
|
||||||
|
config.bacnet_server = GatewayBacnetBridgeConfigFromValue(
|
||||||
|
getObjectValue(object, "bacnetServer"));
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -938,11 +999,12 @@ struct GatewayBridgeService::ChannelRuntime {
|
|||||||
std::unique_ptr<DaliBridgeEngine> engine;
|
std::unique_ptr<DaliBridgeEngine> engine;
|
||||||
std::unique_ptr<GatewayModbusBridge> modbus;
|
std::unique_ptr<GatewayModbusBridge> modbus;
|
||||||
#if defined(CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED)
|
#if defined(CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED)
|
||||||
std::unique_ptr<DaliBacnetBridge> bacnet;
|
std::unique_ptr<GatewayBacnetBridgeAdapter> bacnet;
|
||||||
#endif
|
#endif
|
||||||
std::unique_ptr<DaliCloudBridge> cloud;
|
std::unique_ptr<DaliCloudBridge> cloud;
|
||||||
BridgeRuntimeConfig bridge_config;
|
BridgeRuntimeConfig bridge_config;
|
||||||
std::optional<GatewayModbusConfig> modbus_config;
|
std::optional<GatewayModbusConfig> modbus_config;
|
||||||
|
std::optional<GatewayBacnetBridgeConfig> bacnet_server_config;
|
||||||
BridgeDiscoveryInventory discovery_inventory;
|
BridgeDiscoveryInventory discovery_inventory;
|
||||||
std::optional<GatewayCloudConfig> cloud_config;
|
std::optional<GatewayCloudConfig> cloud_config;
|
||||||
bool bridge_config_loaded{false};
|
bool bridge_config_loaded{false};
|
||||||
@@ -953,6 +1015,12 @@ struct GatewayBridgeService::ChannelRuntime {
|
|||||||
bool bacnet_started{false};
|
bool bacnet_started{false};
|
||||||
TaskHandle_t modbus_task_handle{nullptr};
|
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 void ModbusTaskEntry(void* arg) {
|
||||||
static_cast<ChannelRuntime*>(arg)->modbusTaskLoop();
|
static_cast<ChannelRuntime*>(arg)->modbusTaskLoop();
|
||||||
}
|
}
|
||||||
@@ -982,6 +1050,7 @@ struct GatewayBridgeService::ChannelRuntime {
|
|||||||
const auto stored_config = GatewayBridgeStoredConfigFromValue(bridge_object);
|
const auto stored_config = GatewayBridgeStoredConfigFromValue(bridge_object);
|
||||||
bridge_config = stored_config.bridge;
|
bridge_config = stored_config.bridge;
|
||||||
modbus_config = stored_config.modbus;
|
modbus_config = stored_config.modbus;
|
||||||
|
bacnet_server_config = stored_config.bacnet_server;
|
||||||
bridge_config_loaded = true;
|
bridge_config_loaded = true;
|
||||||
}
|
}
|
||||||
DaliValue::Object discovery_object;
|
DaliValue::Object discovery_object;
|
||||||
@@ -1018,9 +1087,9 @@ struct GatewayBridgeService::ChannelRuntime {
|
|||||||
|
|
||||||
#if defined(CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED)
|
#if defined(CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED)
|
||||||
if (service_config.bacnet_enabled) {
|
if (service_config.bacnet_enabled) {
|
||||||
bacnet = std::make_unique<DaliBacnetBridge>(*engine);
|
bacnet = std::make_unique<GatewayBacnetBridgeAdapter>(*engine);
|
||||||
if (bridge_config.bacnet.has_value()) {
|
if (bacnet_server_config.has_value()) {
|
||||||
bacnet->setConfig(bridge_config.bacnet.value());
|
bacnet->setConfig(bacnet_server_config.value());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
bacnet.reset();
|
bacnet.reset();
|
||||||
@@ -1029,6 +1098,62 @@ struct GatewayBridgeService::ChannelRuntime {
|
|||||||
|
|
||||||
applyCloudModelsLocked();
|
applyCloudModelsLocked();
|
||||||
bacnet_started = false;
|
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 {
|
esp_err_t saveDiscoveryInventoryLocked() const {
|
||||||
@@ -1117,6 +1242,10 @@ struct GatewayBridgeService::ChannelRuntime {
|
|||||||
[this](BridgeObjectType object_type, uint32_t object_instance,
|
[this](BridgeObjectType object_type, uint32_t object_instance,
|
||||||
const std::string& property, const DaliValue& value) {
|
const std::string& property, const DaliValue& value) {
|
||||||
return handleBacnetWrite(object_type, object_instance, property, 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
|
#else
|
||||||
return ESP_ERR_NOT_SUPPORTED;
|
return ESP_ERR_NOT_SUPPORTED;
|
||||||
@@ -1142,13 +1271,15 @@ struct GatewayBridgeService::ChannelRuntime {
|
|||||||
BridgeProvisioningStore store(bridgeNamespace());
|
BridgeProvisioningStore store(bridgeNamespace());
|
||||||
const esp_err_t err = store.saveObject(
|
const esp_err_t err = store.saveObject(
|
||||||
kBridgeConfigKey,
|
kBridgeConfigKey,
|
||||||
GatewayBridgeStoredConfigToValue(parsed->bridge, parsed->modbus));
|
GatewayBridgeStoredConfigToValue(parsed->bridge, parsed->modbus,
|
||||||
|
parsed->bacnet_server));
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
LockGuard guard(lock);
|
LockGuard guard(lock);
|
||||||
bridge_config = parsed->bridge;
|
bridge_config = parsed->bridge;
|
||||||
modbus_config = parsed->modbus;
|
modbus_config = parsed->modbus;
|
||||||
|
bacnet_server_config = parsed->bacnet_server;
|
||||||
bridge_config_loaded = true;
|
bridge_config_loaded = true;
|
||||||
applyBridgeConfigLocked();
|
applyBridgeConfigLocked();
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
@@ -1163,6 +1294,7 @@ struct GatewayBridgeService::ChannelRuntime {
|
|||||||
LockGuard guard(lock);
|
LockGuard guard(lock);
|
||||||
bridge_config = BridgeRuntimeConfig{};
|
bridge_config = BridgeRuntimeConfig{};
|
||||||
modbus_config.reset();
|
modbus_config.reset();
|
||||||
|
bacnet_server_config.reset();
|
||||||
bridge_config_loaded = false;
|
bridge_config_loaded = false;
|
||||||
applyBridgeConfigLocked();
|
applyBridgeConfigLocked();
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
@@ -1244,7 +1376,7 @@ struct GatewayBridgeService::ChannelRuntime {
|
|||||||
return model == bridge_config.models.end() ? model_id : model->displayName();
|
return model == bridge_config.models.end() ? model_id : model->displayName();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool shouldPublishBacnetBindingLocked(const BacnetObjectBinding& binding) {
|
bool shouldPublishBacnetBindingLocked(const GatewayBacnetModelBinding& binding) {
|
||||||
if (binding.objectInstance < 0) {
|
if (binding.objectInstance < 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1270,8 +1402,8 @@ struct GatewayBridgeService::ChannelRuntime {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<BacnetObjectBinding> effectiveBacnetObjectsLocked() {
|
std::vector<GatewayBacnetModelBinding> effectiveBacnetObjectsLocked() {
|
||||||
std::vector<BacnetObjectBinding> bindings;
|
std::vector<GatewayBacnetModelBinding> bindings;
|
||||||
if (bacnet == nullptr) {
|
if (bacnet == nullptr) {
|
||||||
return bindings;
|
return bindings;
|
||||||
}
|
}
|
||||||
@@ -1290,15 +1422,104 @@ struct GatewayBridgeService::ChannelRuntime {
|
|||||||
: channel.name;
|
: channel.name;
|
||||||
config.task_stack_size = service_config.bacnet_task_stack_size;
|
config.task_stack_size = service_config.bacnet_task_stack_size;
|
||||||
config.task_priority = service_config.bacnet_task_priority;
|
config.task_priority = service_config.bacnet_task_priority;
|
||||||
if (bacnet != nullptr) {
|
if (bacnet_server_config.has_value()) {
|
||||||
const auto& bridge_config = bacnet->config();
|
config.device_instance = bacnet_server_config->deviceInstance;
|
||||||
config.device_instance = bridge_config.deviceInstance;
|
config.local_address = bacnet_server_config->localAddress;
|
||||||
config.local_address = bridge_config.localAddress;
|
config.udp_port = bacnet_server_config->udpPort;
|
||||||
config.udp_port = bridge_config.udpPort;
|
|
||||||
}
|
}
|
||||||
return config;
|
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> bacnetObjectBindingsLocked() {
|
||||||
std::vector<GatewayBacnetObjectBinding> bindings;
|
std::vector<GatewayBacnetObjectBinding> bindings;
|
||||||
for (const auto& binding : effectiveBacnetObjectsLocked()) {
|
for (const auto& binding : effectiveBacnetObjectsLocked()) {
|
||||||
@@ -1324,8 +1545,11 @@ struct GatewayBridgeService::ChannelRuntime {
|
|||||||
binding.property.empty() ? "presentValue"
|
binding.property.empty() ? "presentValue"
|
||||||
: binding.property,
|
: binding.property,
|
||||||
out_of_service,
|
out_of_service,
|
||||||
reliability});
|
reliability,
|
||||||
|
BridgeOperationReadable(binding.operation)});
|
||||||
}
|
}
|
||||||
|
auto generated = generatedBacnetObjectBindingsLocked();
|
||||||
|
bindings.insert(bindings.end(), generated.begin(), generated.end());
|
||||||
return bindings;
|
return bindings;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1344,6 +1568,47 @@ struct GatewayBridgeService::ChannelRuntime {
|
|||||||
return result.ok;
|
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() {
|
esp_err_t startBacnet() {
|
||||||
LockGuard guard(lock);
|
LockGuard guard(lock);
|
||||||
if (!service_config.bacnet_enabled) {
|
if (!service_config.bacnet_enabled) {
|
||||||
@@ -1420,10 +1685,12 @@ struct GatewayBridgeService::ChannelRuntime {
|
|||||||
cJSON_AddBoolToObject(bacnet_json, "serverStarted", false);
|
cJSON_AddBoolToObject(bacnet_json, "serverStarted", false);
|
||||||
cJSON_AddNumberToObject(bacnet_json, "serverObjectCount", 0);
|
cJSON_AddNumberToObject(bacnet_json, "serverObjectCount", 0);
|
||||||
#endif
|
#endif
|
||||||
if (bridge_config.bacnet.has_value()) {
|
if (bacnet_server_config.has_value()) {
|
||||||
cJSON_AddNumberToObject(bacnet_json, "deviceInstance", bridge_config.bacnet->deviceInstance);
|
cJSON_AddNumberToObject(bacnet_json, "deviceInstance",
|
||||||
cJSON_AddStringToObject(bacnet_json, "localAddress", bridge_config.bacnet->localAddress.c_str());
|
bacnet_server_config->deviceInstance);
|
||||||
cJSON_AddNumberToObject(bacnet_json, "udpPort", bridge_config.bacnet->udpPort);
|
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);
|
cJSON_AddItemToObject(root, "bacnet", bacnet_json);
|
||||||
}
|
}
|
||||||
@@ -1446,7 +1713,8 @@ struct GatewayBridgeService::ChannelRuntime {
|
|||||||
|
|
||||||
GatewayBridgeHttpResponse configJson() const {
|
GatewayBridgeHttpResponse configJson() const {
|
||||||
return GatewayBridgeHttpResponse{ESP_OK,
|
return GatewayBridgeHttpResponse{ESP_OK,
|
||||||
GatewayBridgeStoredConfigToJson(bridge_config, modbus_config)};
|
GatewayBridgeStoredConfigToJson(bridge_config, modbus_config,
|
||||||
|
bacnet_server_config)};
|
||||||
}
|
}
|
||||||
|
|
||||||
GatewayBridgeHttpResponse inventoryJson() const {
|
GatewayBridgeHttpResponse inventoryJson() const {
|
||||||
@@ -1623,6 +1891,20 @@ struct GatewayBridgeService::ChannelRuntime {
|
|||||||
if (binding.short_address >= 0) {
|
if (binding.short_address >= 0) {
|
||||||
cJSON_AddNumberToObject(item, "shortAddress", binding.short_address);
|
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);
|
cJSON_AddItemToArray(bindings, item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1677,6 +1959,50 @@ struct GatewayBridgeService::ChannelRuntime {
|
|||||||
}
|
}
|
||||||
cJSON_AddItemToArray(bindings, item);
|
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
|
#endif
|
||||||
cJSON_AddItemToObject(root, "bindings", bindings);
|
cJSON_AddItemToObject(root, "bindings", bindings);
|
||||||
@@ -1747,14 +2073,23 @@ struct GatewayBridgeService::ChannelRuntime {
|
|||||||
case GatewayModbusGeneratedKind::kShortSettingsKnown:
|
case GatewayModbusGeneratedKind::kShortSettingsKnown:
|
||||||
return state.settings.anyKnown();
|
return state.settings.anyKnown();
|
||||||
case GatewayModbusGeneratedKind::kShortControlGearPresent:
|
case GatewayModbusGeneratedKind::kShortControlGearPresent:
|
||||||
|
return readSnapshotBoolLocked(point.short_address, "base_status", "controlGearPresent");
|
||||||
case GatewayModbusGeneratedKind::kShortLampFailure:
|
case GatewayModbusGeneratedKind::kShortLampFailure:
|
||||||
|
return readSnapshotBoolLocked(point.short_address, "base_status", "lampFailure");
|
||||||
case GatewayModbusGeneratedKind::kShortLampPowerOn:
|
case GatewayModbusGeneratedKind::kShortLampPowerOn:
|
||||||
|
return readSnapshotBoolLocked(point.short_address, "base_status", "lampPowerOn");
|
||||||
case GatewayModbusGeneratedKind::kShortLimitError:
|
case GatewayModbusGeneratedKind::kShortLimitError:
|
||||||
|
return readSnapshotBoolLocked(point.short_address, "base_status", "limitError");
|
||||||
case GatewayModbusGeneratedKind::kShortFadingCompleted:
|
case GatewayModbusGeneratedKind::kShortFadingCompleted:
|
||||||
|
return readSnapshotBoolLocked(point.short_address, "base_status", "fadingCompleted");
|
||||||
case GatewayModbusGeneratedKind::kShortResetState:
|
case GatewayModbusGeneratedKind::kShortResetState:
|
||||||
|
return readSnapshotBoolLocked(point.short_address, "base_status", "resetState");
|
||||||
case GatewayModbusGeneratedKind::kShortMissingShortAddress:
|
case GatewayModbusGeneratedKind::kShortMissingShortAddress:
|
||||||
|
return readSnapshotBoolLocked(point.short_address, "base_status", "missingShortAddress");
|
||||||
case GatewayModbusGeneratedKind::kShortPowerSupplyFault:
|
case GatewayModbusGeneratedKind::kShortPowerSupplyFault:
|
||||||
return false;
|
return readSnapshotBoolLocked(point.short_address, "base_status", "powerSupplyFault");
|
||||||
|
case GatewayModbusGeneratedKind::kShortDiagnosticBit:
|
||||||
|
return readDiagnosticBoolPointLocked(point);
|
||||||
default:
|
default:
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
@@ -1794,8 +2129,15 @@ struct GatewayBridgeService::ChannelRuntime {
|
|||||||
return state.status.scene_id.has_value()
|
return state.status.scene_id.has_value()
|
||||||
? static_cast<uint16_t>(state.status.scene_id.value())
|
? static_cast<uint16_t>(state.status.scene_id.value())
|
||||||
: kModbusUnknownRegister;
|
: 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;
|
return kModbusUnknownRegister;
|
||||||
|
}
|
||||||
case GatewayModbusGeneratedKind::kShortGroupMask:
|
case GatewayModbusGeneratedKind::kShortGroupMask:
|
||||||
return state.group_mask_known ? state.group_mask : kModbusUnknownRegister;
|
return state.group_mask_known ? state.group_mask : kModbusUnknownRegister;
|
||||||
case GatewayModbusGeneratedKind::kShortPowerOnLevel:
|
case GatewayModbusGeneratedKind::kShortPowerOnLevel:
|
||||||
@@ -2427,11 +2769,24 @@ GatewayBridgeHttpResponse GatewayBridgeService::handleGet(
|
|||||||
}
|
}
|
||||||
return ErrorResponse(ESP_ERR_NOT_FOUND, "device did not respond to type discovery");
|
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");
|
const auto address = QueryInt(query, "addr", "address");
|
||||||
if (!address.has_value() || !ValidDaliAddress(address.value())) {
|
if (!address.has_value() || !ValidDaliAddress(address.value())) {
|
||||||
return ErrorResponse(ESP_ERR_INVALID_ARG, "valid addr is required");
|
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") {
|
if (action == "dt4") {
|
||||||
return SnapshotResponse(dali_domain_.dt4Snapshot(gateway_id.value(), address.value()),
|
return SnapshotResponse(dali_domain_.dt4Snapshot(gateway_id.value(), address.value()),
|
||||||
"DT4 snapshot is unavailable");
|
"DT4 snapshot is unavailable");
|
||||||
@@ -2440,8 +2795,12 @@ GatewayBridgeHttpResponse GatewayBridgeService::handleGet(
|
|||||||
return SnapshotResponse(dali_domain_.dt5Snapshot(gateway_id.value(), address.value()),
|
return SnapshotResponse(dali_domain_.dt5Snapshot(gateway_id.value(), address.value()),
|
||||||
"DT5 snapshot is unavailable");
|
"DT5 snapshot is unavailable");
|
||||||
}
|
}
|
||||||
return SnapshotResponse(dali_domain_.dt6Snapshot(gateway_id.value(), address.value()),
|
if (action == "dt6") {
|
||||||
"DT6 snapshot is unavailable");
|
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") {
|
if (action == "dt8_scene") {
|
||||||
const auto address = QueryInt(query, "addr", "address");
|
const auto address = QueryInt(query, "addr", "address");
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ enum class GatewayModbusGeneratedKind : uint8_t {
|
|||||||
kShortActualLevel,
|
kShortActualLevel,
|
||||||
kShortSceneId,
|
kShortSceneId,
|
||||||
kShortRawStatus,
|
kShortRawStatus,
|
||||||
|
kShortDiagnosticBit,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct GatewayModbusPoint {
|
struct GatewayModbusPoint {
|
||||||
@@ -93,6 +94,9 @@ struct GatewayModbusPoint {
|
|||||||
std::string model_id;
|
std::string model_id;
|
||||||
BridgeOperation operation{BridgeOperation::unknown};
|
BridgeOperation operation{BridgeOperation::unknown};
|
||||||
std::optional<int> bit_index;
|
std::optional<int> bit_index;
|
||||||
|
std::string diagnostic_snapshot;
|
||||||
|
std::string diagnostic_bool;
|
||||||
|
int diagnostic_device_type{-1};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct GatewayModbusPointBinding {
|
struct GatewayModbusPointBinding {
|
||||||
@@ -105,6 +109,10 @@ struct GatewayModbusPointBinding {
|
|||||||
GatewayModbusGeneratedKind generated_kind{GatewayModbusGeneratedKind::kNone};
|
GatewayModbusGeneratedKind generated_kind{GatewayModbusGeneratedKind::kNone};
|
||||||
int short_address{-1};
|
int short_address{-1};
|
||||||
GatewayModbusAccess access{GatewayModbusAccess::kReadWrite};
|
GatewayModbusAccess access{GatewayModbusAccess::kReadWrite};
|
||||||
|
std::optional<int> bit_index;
|
||||||
|
std::string diagnostic_snapshot;
|
||||||
|
std::string diagnostic_bool;
|
||||||
|
int diagnostic_device_type{-1};
|
||||||
};
|
};
|
||||||
|
|
||||||
std::optional<GatewayModbusConfig> GatewayModbusConfigFromValue(const DaliValue* value);
|
std::optional<GatewayModbusConfig> GatewayModbusConfigFromValue(const DaliValue* value);
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ constexpr uint16_t kInputRegisterBase = 30001;
|
|||||||
constexpr uint16_t kHoldingRegisterBase = 40001;
|
constexpr uint16_t kHoldingRegisterBase = 40001;
|
||||||
constexpr uint16_t kShortAddressCount = 64;
|
constexpr uint16_t kShortAddressCount = 64;
|
||||||
constexpr uint16_t kShortStride = 32;
|
constexpr uint16_t kShortStride = 32;
|
||||||
|
constexpr uint16_t kDiagnosticDiscreteInputBase = kDiscreteInputBase +
|
||||||
|
kShortAddressCount * kShortStride;
|
||||||
|
constexpr uint16_t kDiagnosticStride = 128;
|
||||||
|
|
||||||
struct PointKey {
|
struct PointKey {
|
||||||
GatewayModbusSpace space{GatewayModbusSpace::kHoldingRegister};
|
GatewayModbusSpace space{GatewayModbusSpace::kHoldingRegister};
|
||||||
@@ -37,6 +40,15 @@ struct GeneratedPointSpec {
|
|||||||
const char* name;
|
const char* name;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct GeneratedDiagnosticBitSpec {
|
||||||
|
uint16_t offset;
|
||||||
|
int device_type;
|
||||||
|
const char* snapshot;
|
||||||
|
const char* bool_key;
|
||||||
|
const char* suffix;
|
||||||
|
const char* name;
|
||||||
|
};
|
||||||
|
|
||||||
constexpr std::array<GeneratedPointSpec, 4> kGeneratedCoils{{
|
constexpr std::array<GeneratedPointSpec, 4> kGeneratedCoils{{
|
||||||
{0, GatewayModbusSpace::kCoil, GatewayModbusAccess::kWriteOnly,
|
{0, GatewayModbusSpace::kCoil, GatewayModbusAccess::kWriteOnly,
|
||||||
GatewayModbusGeneratedKind::kShortOn, "on", "recall max"},
|
GatewayModbusGeneratedKind::kShortOn, "on", "recall max"},
|
||||||
@@ -48,7 +60,7 @@ constexpr std::array<GeneratedPointSpec, 4> kGeneratedCoils{{
|
|||||||
GatewayModbusGeneratedKind::kShortRecallMin, "recall_min", "recall min"},
|
GatewayModbusGeneratedKind::kShortRecallMin, "recall_min", "recall min"},
|
||||||
}};
|
}};
|
||||||
|
|
||||||
constexpr std::array<GeneratedPointSpec, 18> kGeneratedDiscreteInputs{{
|
constexpr std::array<GeneratedPointSpec, 19> kGeneratedDiscreteInputs{{
|
||||||
{0, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
|
{0, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
|
||||||
GatewayModbusGeneratedKind::kShortDiscovered, "discovered", "discovered"},
|
GatewayModbusGeneratedKind::kShortDiscovered, "discovered", "discovered"},
|
||||||
{1, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
|
{1, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
|
||||||
@@ -87,9 +99,12 @@ constexpr std::array<GeneratedPointSpec, 18> kGeneratedDiscreteInputs{{
|
|||||||
{22, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
|
{22, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
|
||||||
GatewayModbusGeneratedKind::kShortMissingShortAddress, "missing_short_address",
|
GatewayModbusGeneratedKind::kShortMissingShortAddress, "missing_short_address",
|
||||||
"missing short address"},
|
"missing short address"},
|
||||||
|
{23, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
|
||||||
|
GatewayModbusGeneratedKind::kShortPowerSupplyFault, "power_supply_fault",
|
||||||
|
"power supply fault"},
|
||||||
}};
|
}};
|
||||||
|
|
||||||
constexpr std::array<GeneratedPointSpec, 8> kGeneratedHoldingRegisters{{
|
constexpr std::array<GeneratedPointSpec, 9> kGeneratedHoldingRegisters{{
|
||||||
{0, GatewayModbusSpace::kHoldingRegister, GatewayModbusAccess::kReadWrite,
|
{0, GatewayModbusSpace::kHoldingRegister, GatewayModbusAccess::kReadWrite,
|
||||||
GatewayModbusGeneratedKind::kShortBrightness, "brightness", "brightness"},
|
GatewayModbusGeneratedKind::kShortBrightness, "brightness", "brightness"},
|
||||||
{1, GatewayModbusSpace::kHoldingRegister, GatewayModbusAccess::kReadWrite,
|
{1, GatewayModbusSpace::kHoldingRegister, GatewayModbusAccess::kReadWrite,
|
||||||
@@ -108,6 +123,8 @@ constexpr std::array<GeneratedPointSpec, 8> kGeneratedHoldingRegisters{{
|
|||||||
GatewayModbusGeneratedKind::kShortMaxLevel, "max_level", "maximum level"},
|
GatewayModbusGeneratedKind::kShortMaxLevel, "max_level", "maximum level"},
|
||||||
{7, GatewayModbusSpace::kHoldingRegister, GatewayModbusAccess::kReadWrite,
|
{7, GatewayModbusSpace::kHoldingRegister, GatewayModbusAccess::kReadWrite,
|
||||||
GatewayModbusGeneratedKind::kShortFadeTime, "fade_time", "fade time"},
|
GatewayModbusGeneratedKind::kShortFadeTime, "fade_time", "fade time"},
|
||||||
|
{8, GatewayModbusSpace::kHoldingRegister, GatewayModbusAccess::kReadWrite,
|
||||||
|
GatewayModbusGeneratedKind::kShortFadeRate, "fade_rate", "fade rate"},
|
||||||
}};
|
}};
|
||||||
|
|
||||||
constexpr std::array<GeneratedPointSpec, 13> kGeneratedInputRegisters{{
|
constexpr std::array<GeneratedPointSpec, 13> kGeneratedInputRegisters{{
|
||||||
@@ -140,6 +157,132 @@ constexpr std::array<GeneratedPointSpec, 13> kGeneratedInputRegisters{{
|
|||||||
GatewayModbusGeneratedKind::kShortFadeRate, "fade_rate", "fade rate"},
|
GatewayModbusGeneratedKind::kShortFadeRate, "fade_rate", "fade rate"},
|
||||||
}};
|
}};
|
||||||
|
|
||||||
|
constexpr GeneratedDiagnosticBitSpec kGeneratedDiagnosticBits[] = {
|
||||||
|
{0, 1, "dt1", "circuitFailure", "dt1_circuit_failure", "DT1 circuit failure"},
|
||||||
|
{1, 1, "dt1", "batteryDurationFailure", "dt1_battery_duration_failure", "DT1 battery duration failure"},
|
||||||
|
{2, 1, "dt1", "batteryFailure", "dt1_battery_failure", "DT1 battery failure"},
|
||||||
|
{3, 1, "dt1", "emergencyLampFailure", "dt1_emergency_lamp_failure", "DT1 emergency lamp failure"},
|
||||||
|
{4, 1, "dt1", "functionTestMaxDelayExceeded", "dt1_function_test_delay_exceeded", "DT1 function test delay exceeded"},
|
||||||
|
{5, 1, "dt1", "durationTestMaxDelayExceeded", "dt1_duration_test_delay_exceeded", "DT1 duration test delay exceeded"},
|
||||||
|
{6, 1, "dt1", "functionTestFailed", "dt1_function_test_failed", "DT1 function test failed"},
|
||||||
|
{7, 1, "dt1", "durationTestFailed", "dt1_duration_test_failed", "DT1 duration test failed"},
|
||||||
|
{8, 1, "dt1", "inhibitMode", "dt1_inhibit_mode", "DT1 inhibit mode"},
|
||||||
|
{9, 1, "dt1", "functionTestResultValid", "dt1_function_result_valid", "DT1 function result valid"},
|
||||||
|
{10, 1, "dt1", "durationTestResultValid", "dt1_duration_result_valid", "DT1 duration result valid"},
|
||||||
|
{11, 1, "dt1", "batteryFullyCharged", "dt1_battery_fully_charged", "DT1 battery fully charged"},
|
||||||
|
{12, 1, "dt1", "functionTestRequestPending", "dt1_function_request_pending", "DT1 function request pending"},
|
||||||
|
{13, 1, "dt1", "durationTestRequestPending", "dt1_duration_request_pending", "DT1 duration request pending"},
|
||||||
|
{14, 1, "dt1", "identificationActive", "dt1_identification_active", "DT1 identification active"},
|
||||||
|
{15, 1, "dt1", "physicallySelected", "dt1_physically_selected", "DT1 physically selected"},
|
||||||
|
{16, 1, "dt1", "restModeActive", "dt1_rest_mode_active", "DT1 rest mode active"},
|
||||||
|
{17, 1, "dt1", "normalModeActive", "dt1_normal_mode_active", "DT1 normal mode active"},
|
||||||
|
{18, 1, "dt1", "emergencyModeActive", "dt1_emergency_mode_active", "DT1 emergency mode active"},
|
||||||
|
{19, 1, "dt1", "extendedEmergencyModeActive", "dt1_extended_mode_active", "DT1 extended emergency mode active"},
|
||||||
|
{20, 1, "dt1", "functionTestInProgress", "dt1_function_test_in_progress", "DT1 function test in progress"},
|
||||||
|
{21, 1, "dt1", "durationTestInProgress", "dt1_duration_test_in_progress", "DT1 duration test in progress"},
|
||||||
|
{22, 1, "dt1", "hardwiredInhibitActive", "dt1_hardwired_inhibit_active", "DT1 hardwired inhibit active"},
|
||||||
|
{23, 1, "dt1", "hardwiredSwitchOn", "dt1_hardwired_switch_on", "DT1 hardwired switch on"},
|
||||||
|
{24, 1, "dt1", "integralEmergencyControlGear", "dt1_integral_emergency", "DT1 integral emergency gear"},
|
||||||
|
{25, 1, "dt1", "maintainedControlGear", "dt1_maintained", "DT1 maintained gear"},
|
||||||
|
{26, 1, "dt1", "switchedMaintainedControlGear", "dt1_switched_maintained", "DT1 switched maintained gear"},
|
||||||
|
{27, 1, "dt1", "autoTestCapability", "dt1_auto_test_capability", "DT1 auto test capability"},
|
||||||
|
{28, 1, "dt1", "adjustableEmergencyLevel", "dt1_adjustable_level", "DT1 adjustable emergency level"},
|
||||||
|
{29, 1, "dt1", "hardwiredInhibitSupported", "dt1_hardwired_inhibit_supported", "DT1 hardwired inhibit supported"},
|
||||||
|
{30, 1, "dt1", "physicalSelectionSupported", "dt1_physical_selection_supported", "DT1 physical selection supported"},
|
||||||
|
{31, 1, "dt1", "relightInRestModeSupported", "dt1_relight_rest_supported", "DT1 relight in rest mode supported"},
|
||||||
|
{32, 4, "dt4", "leadingEdgeModeRunning", "dt4_leading_edge_running", "DT4 leading edge running"},
|
||||||
|
{33, 4, "dt4", "trailingEdgeModeRunning", "dt4_trailing_edge_running", "DT4 trailing edge running"},
|
||||||
|
{34, 4, "dt4", "referenceMeasurementRunning", "dt4_reference_running", "DT4 reference measurement running"},
|
||||||
|
{35, 4, "dt4", "nonLogarithmicDimmingCurveActive", "dt4_non_log_curve_active", "DT4 non-log curve active"},
|
||||||
|
{36, 4, "dt4", "canQueryLoadOverCurrentShutdown", "dt4_can_load_over_current_shutdown", "DT4 can query over-current shutdown"},
|
||||||
|
{37, 4, "dt4", "canQueryOpenCircuitDetection", "dt4_can_open_circuit", "DT4 can query open circuit"},
|
||||||
|
{38, 4, "dt4", "canQueryLoadDecrease", "dt4_can_load_decrease", "DT4 can query load decrease"},
|
||||||
|
{39, 4, "dt4", "canQueryLoadIncrease", "dt4_can_load_increase", "DT4 can query load increase"},
|
||||||
|
{40, 4, "dt4", "canQueryThermalShutdown", "dt4_can_thermal_shutdown", "DT4 can query thermal shutdown"},
|
||||||
|
{41, 4, "dt4", "canQueryThermalOverloadReduction", "dt4_can_thermal_overload", "DT4 can query thermal overload"},
|
||||||
|
{42, 4, "dt4", "physicalSelectionSupported", "dt4_physical_selection_supported", "DT4 physical selection supported"},
|
||||||
|
{43, 4, "dt4", "canQueryTemperature", "dt4_can_temperature", "DT4 can query temperature"},
|
||||||
|
{44, 4, "dt4", "canQuerySupplyVoltage", "dt4_can_supply_voltage", "DT4 can query supply voltage"},
|
||||||
|
{45, 4, "dt4", "canQuerySupplyFrequency", "dt4_can_supply_frequency", "DT4 can query supply frequency"},
|
||||||
|
{46, 4, "dt4", "canQueryLoadVoltage", "dt4_can_load_voltage", "DT4 can query load voltage"},
|
||||||
|
{47, 4, "dt4", "canQueryLoadCurrent", "dt4_can_load_current", "DT4 can query load current"},
|
||||||
|
{48, 4, "dt4", "canQueryRealLoadPower", "dt4_can_load_power", "DT4 can query load power"},
|
||||||
|
{49, 4, "dt4", "canQueryLoadRating", "dt4_can_load_rating", "DT4 can query load rating"},
|
||||||
|
{50, 4, "dt4", "canQueryCurrentOverloadReduction", "dt4_can_current_overload", "DT4 can query current overload"},
|
||||||
|
{51, 4, "dt4", "canSelectNonLogarithmicDimmingCurve", "dt4_can_non_log_curve", "DT4 can select non-log curve"},
|
||||||
|
{52, 4, "dt4", "canQueryUnsuitableLoad", "dt4_can_unsuitable_load", "DT4 can query unsuitable load"},
|
||||||
|
{53, 4, "dt4", "loadOverCurrentShutdown", "dt4_load_over_current_shutdown", "DT4 load over-current shutdown"},
|
||||||
|
{54, 4, "dt4", "openCircuitDetected", "dt4_open_circuit_detected", "DT4 open circuit detected"},
|
||||||
|
{55, 4, "dt4", "loadDecreaseDetected", "dt4_load_decrease_detected", "DT4 load decrease detected"},
|
||||||
|
{56, 4, "dt4", "loadIncreaseDetected", "dt4_load_increase_detected", "DT4 load increase detected"},
|
||||||
|
{57, 4, "dt4", "thermalShutdown", "dt4_thermal_shutdown", "DT4 thermal shutdown"},
|
||||||
|
{58, 4, "dt4", "thermalOverloadReduction", "dt4_thermal_overload", "DT4 thermal overload reduction"},
|
||||||
|
{59, 4, "dt4", "referenceMeasurementFailedStatus", "dt4_reference_failed", "DT4 reference failed"},
|
||||||
|
{60, 4, "dt4", "loadUnsuitableForSelectedMethod", "dt4_unsuitable_load", "DT4 unsuitable load"},
|
||||||
|
{61, 4, "dt4", "supplyVoltageOutOfLimits", "dt4_supply_voltage_limits", "DT4 supply voltage out of limits"},
|
||||||
|
{62, 4, "dt4", "supplyFrequencyOutOfLimits", "dt4_supply_frequency_limits", "DT4 supply frequency out of limits"},
|
||||||
|
{63, 4, "dt4", "loadVoltageOutOfLimits", "dt4_load_voltage_limits", "DT4 load voltage out of limits"},
|
||||||
|
{64, 4, "dt4", "loadCurrentOverloadReduction", "dt4_load_current_overload", "DT4 load current overload"},
|
||||||
|
{65, 5, "dt5", "outputRange0To10VSelectable", "dt5_output_range_selectable", "DT5 output range selectable"},
|
||||||
|
{66, 5, "dt5", "internalPullUpSelectable", "dt5_pullup_selectable", "DT5 pull-up selectable"},
|
||||||
|
{67, 5, "dt5", "outputFaultDetectionSelectable", "dt5_fault_detection_selectable", "DT5 fault detection selectable"},
|
||||||
|
{68, 5, "dt5", "mainsRelay", "dt5_mains_relay", "DT5 mains relay"},
|
||||||
|
{69, 5, "dt5", "outputLevelQueryable", "dt5_output_level_queryable", "DT5 output level queryable"},
|
||||||
|
{70, 5, "dt5", "nonLogarithmicDimmingCurveSupported", "dt5_non_log_supported", "DT5 non-log curve supported"},
|
||||||
|
{71, 5, "dt5", "physicalSelectionByOutputLossSupported", "dt5_output_loss_selection", "DT5 output-loss selection supported"},
|
||||||
|
{72, 5, "dt5", "physicalSelectionSwitchSupported", "dt5_selection_switch", "DT5 selection switch supported"},
|
||||||
|
{73, 5, "dt5", "outputFaultDetected", "dt5_output_fault", "DT5 output fault detected"},
|
||||||
|
{74, 5, "dt5", "zeroToTenVoltOperation", "dt5_zero_to_ten", "DT5 0-10V operation"},
|
||||||
|
{75, 5, "dt5", "internalPullUpOn", "dt5_pullup_on", "DT5 pull-up on"},
|
||||||
|
{76, 5, "dt5", "nonLogarithmicDimmingCurveActive", "dt5_non_log_active", "DT5 non-log curve active"},
|
||||||
|
{77, 6, "dt6", "ledPowerSupplyIntegrated", "dt6_power_supply_integrated", "DT6 power supply integrated"},
|
||||||
|
{78, 6, "dt6", "ledModuleIntegrated", "dt6_module_integrated", "DT6 LED module integrated"},
|
||||||
|
{79, 6, "dt6", "acSupplyPossible", "dt6_ac_supply_possible", "DT6 AC supply possible"},
|
||||||
|
{80, 6, "dt6", "dcSupplyPossible", "dt6_dc_supply_possible", "DT6 DC supply possible"},
|
||||||
|
{81, 6, "dt6", "pwmModePossible", "dt6_pwm_possible", "DT6 PWM possible"},
|
||||||
|
{82, 6, "dt6", "amModePossible", "dt6_am_possible", "DT6 AM possible"},
|
||||||
|
{83, 6, "dt6", "currentControlledOutputPossible", "dt6_current_control_possible", "DT6 current control possible"},
|
||||||
|
{84, 6, "dt6", "highCurrentPulseModePossible", "dt6_high_current_possible", "DT6 high current possible"},
|
||||||
|
{85, 6, "dt6", "canQueryShortCircuit", "dt6_can_short_circuit", "DT6 can query short circuit"},
|
||||||
|
{86, 6, "dt6", "canQueryOpenCircuit", "dt6_can_open_circuit", "DT6 can query open circuit"},
|
||||||
|
{87, 6, "dt6", "canQueryLoadDecrease", "dt6_can_load_decrease", "DT6 can query load decrease"},
|
||||||
|
{88, 6, "dt6", "canQueryLoadIncrease", "dt6_can_load_increase", "DT6 can query load increase"},
|
||||||
|
{89, 6, "dt6", "canQueryCurrentProtector", "dt6_can_current_protector", "DT6 can query current protector"},
|
||||||
|
{90, 6, "dt6", "canQueryThermalShutdown", "dt6_can_thermal_shutdown", "DT6 can query thermal shutdown"},
|
||||||
|
{91, 6, "dt6", "canQueryThermalOverloadReduction", "dt6_can_thermal_overload", "DT6 can query thermal overload"},
|
||||||
|
{92, 6, "dt6", "shortCircuit", "dt6_short_circuit", "DT6 short circuit"},
|
||||||
|
{93, 6, "dt6", "openCircuit", "dt6_open_circuit", "DT6 open circuit"},
|
||||||
|
{94, 6, "dt6", "loadDecrease", "dt6_load_decrease", "DT6 load decrease"},
|
||||||
|
{95, 6, "dt6", "loadIncrease", "dt6_load_increase", "DT6 load increase"},
|
||||||
|
{96, 6, "dt6", "currentProtectorActive", "dt6_current_protector_active", "DT6 current protector active"},
|
||||||
|
{97, 6, "dt6", "thermalShutdown", "dt6_thermal_shutdown", "DT6 thermal shutdown"},
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr GeneratedDiagnosticBitSpec kGeneratedDiagnosticBitsTail[] = {
|
||||||
|
{98, 6, "dt6", "thermalOverloadReduction", "dt6_thermal_overload", "DT6 thermal overload"},
|
||||||
|
{99, 6, "dt6", "referenceMeasurementFailed", "dt6_reference_failed", "DT6 reference failed"},
|
||||||
|
{100, 6, "dt6", "pwmModeActive", "dt6_pwm_active", "DT6 PWM active"},
|
||||||
|
{101, 6, "dt6", "amModeActive", "dt6_am_active", "DT6 AM active"},
|
||||||
|
{102, 6, "dt6", "currentControlledOutput", "dt6_current_controlled_output", "DT6 current controlled output"},
|
||||||
|
{103, 6, "dt6", "highCurrentPulseModeActive", "dt6_high_current_active", "DT6 high current active"},
|
||||||
|
{104, 6, "dt6", "nonLogarithmicDimmingCurveActive", "dt6_non_log_active", "DT6 non-log curve active"},
|
||||||
|
{105, 8, "dt8_status", "xyOutOfRange", "dt8_xy_out_of_range", "DT8 xy out of range"},
|
||||||
|
{106, 8, "dt8_status", "ctOutOfRange", "dt8_ct_out_of_range", "DT8 CT out of range"},
|
||||||
|
{107, 8, "dt8_status", "autoCalibrationActive", "dt8_auto_cal_active", "DT8 auto calibration active"},
|
||||||
|
{108, 8, "dt8_status", "autoCalibrationSuccess", "dt8_auto_cal_success", "DT8 auto calibration success"},
|
||||||
|
{109, 8, "dt8_status", "xyActive", "dt8_xy_active", "DT8 xy active"},
|
||||||
|
{110, 8, "dt8_status", "ctActive", "dt8_ct_active", "DT8 CT active"},
|
||||||
|
{111, 8, "dt8_status", "primaryNActive", "dt8_primary_active", "DT8 primary-N active"},
|
||||||
|
{112, 8, "dt8_status", "rgbwafActive", "dt8_rgbwaf_active", "DT8 RGBWAF active"},
|
||||||
|
{113, 8, "dt8_status", "xyCapable", "dt8_xy_capable", "DT8 xy capable"},
|
||||||
|
{114, 8, "dt8_status", "ctCapable", "dt8_ct_capable", "DT8 CT capable"},
|
||||||
|
{115, 8, "dt8_status", "primaryNCapable", "dt8_primary_capable", "DT8 primary-N capable"},
|
||||||
|
{116, 8, "dt8_status", "rgbwafCapable", "dt8_rgbwaf_capable", "DT8 RGBWAF capable"},
|
||||||
|
{117, 6, "dt6", "physicalSelectionSupported", "dt6_physical_selection_supported", "DT6 physical selection supported"},
|
||||||
|
{118, 6, "dt6", "currentProtectorEnabled", "dt6_current_protector_enabled", "DT6 current protector enabled"},
|
||||||
|
{119, 1, "dt1", "controlGearFailure", "dt1_control_gear_failure", "DT1 control gear failure"},
|
||||||
|
};
|
||||||
|
|
||||||
uint16_t baseForSpace(GatewayModbusSpace space) {
|
uint16_t baseForSpace(GatewayModbusSpace space) {
|
||||||
switch (space) {
|
switch (space) {
|
||||||
case GatewayModbusSpace::kCoil:
|
case GatewayModbusSpace::kCoil:
|
||||||
@@ -214,6 +357,29 @@ void addGeneratedPoint(std::map<PointKey, GatewayModbusPoint>* points, uint8_t s
|
|||||||
(*points)[PointKey{spec.space, address}] = std::move(point);
|
(*points)[PointKey{spec.space, address}] = std::move(point);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void addGeneratedDiagnosticPoint(std::map<PointKey, GatewayModbusPoint>* points,
|
||||||
|
uint8_t short_address,
|
||||||
|
const GeneratedDiagnosticBitSpec& spec) {
|
||||||
|
if (points == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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.short_address = short_address;
|
||||||
|
point.diagnostic_snapshot = spec.snapshot == nullptr ? "" : spec.snapshot;
|
||||||
|
point.diagnostic_bool = spec.bool_key == nullptr ? "" : spec.bool_key;
|
||||||
|
point.diagnostic_device_type = spec.device_type;
|
||||||
|
(*points)[PointKey{point.space, address}] = std::move(point);
|
||||||
|
}
|
||||||
|
|
||||||
GatewayModbusPointBinding toBinding(const GatewayModbusPoint& point) {
|
GatewayModbusPointBinding toBinding(const GatewayModbusPoint& point) {
|
||||||
return GatewayModbusPointBinding{point.model_id,
|
return GatewayModbusPointBinding{point.model_id,
|
||||||
point.space,
|
point.space,
|
||||||
@@ -223,7 +389,11 @@ GatewayModbusPointBinding toBinding(const GatewayModbusPoint& point) {
|
|||||||
point.generated,
|
point.generated,
|
||||||
point.generated_kind,
|
point.generated_kind,
|
||||||
point.short_address,
|
point.short_address,
|
||||||
point.access};
|
point.access,
|
||||||
|
point.bit_index,
|
||||||
|
point.diagnostic_snapshot,
|
||||||
|
point.diagnostic_bool,
|
||||||
|
point.diagnostic_device_type};
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
@@ -356,6 +526,8 @@ const char* GatewayModbusGeneratedKindToString(GatewayModbusGeneratedKind kind)
|
|||||||
return "short_scene_id";
|
return "short_scene_id";
|
||||||
case GatewayModbusGeneratedKind::kShortRawStatus:
|
case GatewayModbusGeneratedKind::kShortRawStatus:
|
||||||
return "short_raw_status";
|
return "short_raw_status";
|
||||||
|
case GatewayModbusGeneratedKind::kShortDiagnosticBit:
|
||||||
|
return "short_diagnostic_bit";
|
||||||
case GatewayModbusGeneratedKind::kNone:
|
case GatewayModbusGeneratedKind::kNone:
|
||||||
default:
|
default:
|
||||||
return "none";
|
return "none";
|
||||||
@@ -417,6 +589,12 @@ void GatewayModbusBridge::rebuildMap() {
|
|||||||
for (const auto& spec : kGeneratedInputRegisters) {
|
for (const auto& spec : kGeneratedInputRegisters) {
|
||||||
addGeneratedPoint(&next, short_address, spec);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& model : engine_.listModels()) {
|
for (const auto& model : engine_.listModels()) {
|
||||||
|
|||||||
Reference in New Issue
Block a user