Add diagnostic bit support to Gateway Modbus

- Introduced new enum value `kShortDiagnosticBit` to `GatewayModbusGeneratedKind`.
- Enhanced `GatewayModbusPoint` and `GatewayModbusPointBinding` structures to include diagnostic snapshot, boolean key, and device type.
- Added new diagnostic bit specifications and updated the corresponding arrays for generated discrete inputs and holding registers.
- Implemented `addGeneratedDiagnosticPoint` function to handle the creation of diagnostic points.
- Updated `rebuildMap` method to include generated diagnostic points during the map rebuilding process.

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
Tony
2026-05-04 02:26:09 +08:00
parent 694217eb2c
commit 7424b43bdd
13 changed files with 1133 additions and 40 deletions
@@ -79,6 +79,7 @@ enum class GatewayModbusGeneratedKind : uint8_t {
kShortActualLevel,
kShortSceneId,
kShortRawStatus,
kShortDiagnosticBit,
};
struct GatewayModbusPoint {
@@ -93,6 +94,9 @@ struct GatewayModbusPoint {
std::string model_id;
BridgeOperation operation{BridgeOperation::unknown};
std::optional<int> bit_index;
std::string diagnostic_snapshot;
std::string diagnostic_bool;
int diagnostic_device_type{-1};
};
struct GatewayModbusPointBinding {
@@ -105,6 +109,10 @@ struct GatewayModbusPointBinding {
GatewayModbusGeneratedKind generated_kind{GatewayModbusGeneratedKind::kNone};
int short_address{-1};
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);
@@ -15,6 +15,9 @@ constexpr uint16_t kInputRegisterBase = 30001;
constexpr uint16_t kHoldingRegisterBase = 40001;
constexpr uint16_t kShortAddressCount = 64;
constexpr uint16_t kShortStride = 32;
constexpr uint16_t kDiagnosticDiscreteInputBase = kDiscreteInputBase +
kShortAddressCount * kShortStride;
constexpr uint16_t kDiagnosticStride = 128;
struct PointKey {
GatewayModbusSpace space{GatewayModbusSpace::kHoldingRegister};
@@ -37,6 +40,15 @@ struct GeneratedPointSpec {
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{{
{0, GatewayModbusSpace::kCoil, GatewayModbusAccess::kWriteOnly,
GatewayModbusGeneratedKind::kShortOn, "on", "recall max"},
@@ -48,7 +60,7 @@ constexpr std::array<GeneratedPointSpec, 4> kGeneratedCoils{{
GatewayModbusGeneratedKind::kShortRecallMin, "recall_min", "recall min"},
}};
constexpr std::array<GeneratedPointSpec, 18> kGeneratedDiscreteInputs{{
constexpr std::array<GeneratedPointSpec, 19> kGeneratedDiscreteInputs{{
{0, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
GatewayModbusGeneratedKind::kShortDiscovered, "discovered", "discovered"},
{1, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
@@ -87,9 +99,12 @@ constexpr std::array<GeneratedPointSpec, 18> kGeneratedDiscreteInputs{{
{22, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
GatewayModbusGeneratedKind::kShortMissingShortAddress, "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,
GatewayModbusGeneratedKind::kShortBrightness, "brightness", "brightness"},
{1, GatewayModbusSpace::kHoldingRegister, GatewayModbusAccess::kReadWrite,
@@ -108,6 +123,8 @@ constexpr std::array<GeneratedPointSpec, 8> kGeneratedHoldingRegisters{{
GatewayModbusGeneratedKind::kShortMaxLevel, "max_level", "maximum level"},
{7, GatewayModbusSpace::kHoldingRegister, GatewayModbusAccess::kReadWrite,
GatewayModbusGeneratedKind::kShortFadeTime, "fade_time", "fade time"},
{8, GatewayModbusSpace::kHoldingRegister, GatewayModbusAccess::kReadWrite,
GatewayModbusGeneratedKind::kShortFadeRate, "fade_rate", "fade rate"},
}};
constexpr std::array<GeneratedPointSpec, 13> kGeneratedInputRegisters{{
@@ -140,6 +157,132 @@ constexpr std::array<GeneratedPointSpec, 13> kGeneratedInputRegisters{{
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) {
switch (space) {
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);
}
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) {
return GatewayModbusPointBinding{point.model_id,
point.space,
@@ -223,7 +389,11 @@ GatewayModbusPointBinding toBinding(const GatewayModbusPoint& point) {
point.generated,
point.generated_kind,
point.short_address,
point.access};
point.access,
point.bit_index,
point.diagnostic_snapshot,
point.diagnostic_bool,
point.diagnostic_device_type};
}
} // namespace
@@ -356,6 +526,8 @@ const char* GatewayModbusGeneratedKindToString(GatewayModbusGeneratedKind kind)
return "short_scene_id";
case GatewayModbusGeneratedKind::kShortRawStatus:
return "short_raw_status";
case GatewayModbusGeneratedKind::kShortDiagnosticBit:
return "short_diagnostic_bit";
case GatewayModbusGeneratedKind::kNone:
default:
return "none";
@@ -417,6 +589,12 @@ void GatewayModbusBridge::rebuildMap() {
for (const auto& spec : kGeneratedInputRegisters) {
addGeneratedPoint(&next, short_address, spec);
}
for (const auto& spec : kGeneratedDiagnosticBits) {
addGeneratedDiagnosticPoint(&next, short_address, spec);
}
for (const auto& spec : kGeneratedDiagnosticBitsTail) {
addGeneratedDiagnosticPoint(&next, short_address, spec);
}
}
for (const auto& model : engine_.listModels()) {