Files
gateway/components/gateway_modbus/src/gateway_modbus.cpp
T
Tony 34d2d9caa0 Add serial configuration support to Gateway Modbus
- Introduced GatewayModbusSerialConfig structure to encapsulate serial communication settings.
- Added clamping functions for integer and size values to ensure valid configuration ranges.
- Updated GatewayModbusConfigFromValue to parse serial configuration from JSON input.
- Implemented transport type checking functions for TCP, RTU, ASCII, and Serial.
- Enhanced GatewayModbusConfigToValue to include serial configuration in output.

Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-04 14:34:05 +08:00

777 lines
40 KiB
C++

#include "gateway_modbus.hpp"
#include <algorithm>
#include <array>
#include <cstdio>
#include <map>
#include <utility>
namespace gateway {
namespace {
constexpr uint16_t kCoilBase = 1;
constexpr uint16_t kDiscreteInputBase = 10001;
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};
uint16_t address{0};
bool operator<(const PointKey& other) const {
if (space != other.space) {
return static_cast<uint8_t>(space) < static_cast<uint8_t>(other.space);
}
return address < other.address;
}
};
struct GeneratedPointSpec {
uint16_t offset;
GatewayModbusSpace space;
GatewayModbusAccess access;
GatewayModbusGeneratedKind kind;
const char* suffix;
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"},
{1, GatewayModbusSpace::kCoil, GatewayModbusAccess::kWriteOnly,
GatewayModbusGeneratedKind::kShortOff, "off", "off"},
{2, GatewayModbusSpace::kCoil, GatewayModbusAccess::kWriteOnly,
GatewayModbusGeneratedKind::kShortRecallMax, "recall_max", "recall max"},
{3, GatewayModbusSpace::kCoil, GatewayModbusAccess::kWriteOnly,
GatewayModbusGeneratedKind::kShortRecallMin, "recall_min", "recall min"},
}};
constexpr std::array<GeneratedPointSpec, 19> kGeneratedDiscreteInputs{{
{0, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
GatewayModbusGeneratedKind::kShortDiscovered, "discovered", "discovered"},
{1, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
GatewayModbusGeneratedKind::kShortOnline, "online", "online"},
{2, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
GatewayModbusGeneratedKind::kShortSupportsDt1, "supports_dt1", "supports DT1"},
{3, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
GatewayModbusGeneratedKind::kShortSupportsDt4, "supports_dt4", "supports DT4"},
{4, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
GatewayModbusGeneratedKind::kShortSupportsDt5, "supports_dt5", "supports DT5"},
{5, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
GatewayModbusGeneratedKind::kShortSupportsDt6, "supports_dt6", "supports DT6"},
{6, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
GatewayModbusGeneratedKind::kShortSupportsDt8, "supports_dt8", "supports DT8"},
{7, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
GatewayModbusGeneratedKind::kShortGroupMaskKnown, "group_mask_known", "group mask known"},
{8, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
GatewayModbusGeneratedKind::kShortActualLevelKnown, "actual_level_known", "actual level known"},
{9, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
GatewayModbusGeneratedKind::kShortSceneKnown, "scene_known", "scene known"},
{10, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
GatewayModbusGeneratedKind::kShortSettingsKnown, "settings_known", "settings known"},
{16, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
GatewayModbusGeneratedKind::kShortControlGearPresent, "control_gear_present",
"control gear present"},
{17, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
GatewayModbusGeneratedKind::kShortLampFailure, "lamp_failure", "lamp failure"},
{18, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
GatewayModbusGeneratedKind::kShortLampPowerOn, "lamp_power_on", "lamp power on"},
{19, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
GatewayModbusGeneratedKind::kShortLimitError, "limit_error", "limit error"},
{20, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
GatewayModbusGeneratedKind::kShortFadingCompleted, "fading_completed", "fading completed"},
{21, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
GatewayModbusGeneratedKind::kShortResetState, "reset_state", "reset state"},
{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, 9> kGeneratedHoldingRegisters{{
{0, GatewayModbusSpace::kHoldingRegister, GatewayModbusAccess::kReadWrite,
GatewayModbusGeneratedKind::kShortBrightness, "brightness", "brightness"},
{1, GatewayModbusSpace::kHoldingRegister, GatewayModbusAccess::kReadWrite,
GatewayModbusGeneratedKind::kShortColorTemperature, "color_temperature",
"color temperature"},
{2, GatewayModbusSpace::kHoldingRegister, GatewayModbusAccess::kReadWrite,
GatewayModbusGeneratedKind::kShortGroupMask, "group_mask", "group mask"},
{3, GatewayModbusSpace::kHoldingRegister, GatewayModbusAccess::kReadWrite,
GatewayModbusGeneratedKind::kShortPowerOnLevel, "power_on_level", "power-on level"},
{4, GatewayModbusSpace::kHoldingRegister, GatewayModbusAccess::kReadWrite,
GatewayModbusGeneratedKind::kShortSystemFailureLevel, "system_failure_level",
"system-failure level"},
{5, GatewayModbusSpace::kHoldingRegister, GatewayModbusAccess::kReadWrite,
GatewayModbusGeneratedKind::kShortMinLevel, "min_level", "minimum level"},
{6, GatewayModbusSpace::kHoldingRegister, GatewayModbusAccess::kReadWrite,
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{{
{0, GatewayModbusSpace::kInputRegister, GatewayModbusAccess::kReadOnly,
GatewayModbusGeneratedKind::kShortInventoryState, "inventory_state", "inventory state"},
{1, GatewayModbusSpace::kInputRegister, GatewayModbusAccess::kReadOnly,
GatewayModbusGeneratedKind::kShortPrimaryType, "primary_type", "primary type"},
{2, GatewayModbusSpace::kInputRegister, GatewayModbusAccess::kReadOnly,
GatewayModbusGeneratedKind::kShortTypeMask, "type_mask", "device type mask"},
{3, GatewayModbusSpace::kInputRegister, GatewayModbusAccess::kReadOnly,
GatewayModbusGeneratedKind::kShortActualLevel, "actual_level", "actual level"},
{4, GatewayModbusSpace::kInputRegister, GatewayModbusAccess::kReadOnly,
GatewayModbusGeneratedKind::kShortSceneId, "scene_id", "scene id"},
{5, GatewayModbusSpace::kInputRegister, GatewayModbusAccess::kReadOnly,
GatewayModbusGeneratedKind::kShortRawStatus, "raw_status", "raw status"},
{6, GatewayModbusSpace::kInputRegister, GatewayModbusAccess::kReadOnly,
GatewayModbusGeneratedKind::kShortGroupMask, "group_mask", "group mask"},
{7, GatewayModbusSpace::kInputRegister, GatewayModbusAccess::kReadOnly,
GatewayModbusGeneratedKind::kShortPowerOnLevel, "power_on_level", "power-on level"},
{8, GatewayModbusSpace::kInputRegister, GatewayModbusAccess::kReadOnly,
GatewayModbusGeneratedKind::kShortSystemFailureLevel, "system_failure_level",
"system-failure level"},
{9, GatewayModbusSpace::kInputRegister, GatewayModbusAccess::kReadOnly,
GatewayModbusGeneratedKind::kShortMinLevel, "min_level", "minimum level"},
{10, GatewayModbusSpace::kInputRegister, GatewayModbusAccess::kReadOnly,
GatewayModbusGeneratedKind::kShortMaxLevel, "max_level", "maximum level"},
{11, GatewayModbusSpace::kInputRegister, GatewayModbusAccess::kReadOnly,
GatewayModbusGeneratedKind::kShortFadeTime, "fade_time", "fade time"},
{12, GatewayModbusSpace::kInputRegister, GatewayModbusAccess::kReadOnly,
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:
return kCoilBase;
case GatewayModbusSpace::kDiscreteInput:
return kDiscreteInputBase;
case GatewayModbusSpace::kInputRegister:
return kInputRegisterBase;
case GatewayModbusSpace::kHoldingRegister:
return kHoldingRegisterBase;
}
return kHoldingRegisterBase;
}
std::optional<GatewayModbusSpace> spaceForObjectType(BridgeObjectType type) {
switch (type) {
case BridgeObjectType::coil:
return GatewayModbusSpace::kCoil;
case BridgeObjectType::discreteInput:
return GatewayModbusSpace::kDiscreteInput;
case BridgeObjectType::inputRegister:
return GatewayModbusSpace::kInputRegister;
case BridgeObjectType::holdingRegister:
return GatewayModbusSpace::kHoldingRegister;
default:
return std::nullopt;
}
}
GatewayModbusAccess accessForSpace(GatewayModbusSpace space) {
switch (space) {
case GatewayModbusSpace::kCoil:
case GatewayModbusSpace::kHoldingRegister:
return GatewayModbusAccess::kReadWrite;
case GatewayModbusSpace::kDiscreteInput:
case GatewayModbusSpace::kInputRegister:
return GatewayModbusAccess::kReadOnly;
}
return GatewayModbusAccess::kReadOnly;
}
std::string generatedId(uint8_t short_address, const char* suffix) {
char buffer[64];
std::snprintf(buffer, sizeof(buffer), "dali_%02u_%s", static_cast<unsigned>(short_address),
suffix == nullptr ? "point" : suffix);
return buffer;
}
std::string generatedName(uint8_t short_address, const char* name) {
char buffer[96];
std::snprintf(buffer, sizeof(buffer), "DALI %u %s", static_cast<unsigned>(short_address),
name == nullptr ? "point" : name);
return buffer;
}
void addGeneratedPoint(std::map<PointKey, GatewayModbusPoint>* points, uint8_t short_address,
const GeneratedPointSpec& spec) {
if (points == nullptr) {
return;
}
const uint16_t address = static_cast<uint16_t>(baseForSpace(spec.space) +
short_address * kShortStride + spec.offset);
GatewayModbusPoint point;
point.space = spec.space;
point.access = spec.access;
point.address = address;
point.id = generatedId(short_address, spec.suffix);
point.name = generatedName(short_address, spec.name);
point.generated = true;
point.generated_kind = spec.kind;
point.short_address = short_address;
(*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,
point.address,
point.id,
point.name,
point.generated,
point.generated_kind,
point.short_address,
point.access,
point.bit_index,
point.diagnostic_snapshot,
point.diagnostic_bool,
point.diagnostic_device_type};
}
int clampedInt(const DaliValue::Object& json, const std::string& key, int fallback,
int min_value, int max_value) {
const int value = getObjectInt(json, key).value_or(fallback);
return std::clamp(value, min_value, max_value);
}
uint32_t clampedU32(const DaliValue::Object& json, const std::string& key, uint32_t fallback,
uint32_t min_value, uint32_t max_value) {
const int value = getObjectInt(json, key).value_or(static_cast<int>(fallback));
return static_cast<uint32_t>(std::clamp(value, static_cast<int>(min_value),
static_cast<int>(max_value)));
}
size_t clampedSize(const DaliValue::Object& json, const std::string& key, size_t fallback,
size_t min_value, size_t max_value) {
const int value = getObjectInt(json, key).value_or(static_cast<int>(fallback));
return static_cast<size_t>(std::clamp(value, static_cast<int>(min_value),
static_cast<int>(max_value)));
}
} // namespace
bool GatewayModbusTransportIsTcp(const std::string& transport) {
return transport.empty() || transport == "tcp" || transport == "tcp-server";
}
bool GatewayModbusTransportIsRtu(const std::string& transport) {
return transport == "rtu" || transport == "rtu-server" || transport == "modbus-rtu";
}
bool GatewayModbusTransportIsAscii(const std::string& transport) {
return transport == "ascii" || transport == "ascii-server" || transport == "modbus-ascii";
}
bool GatewayModbusTransportIsSerial(const std::string& transport) {
return GatewayModbusTransportIsRtu(transport) || GatewayModbusTransportIsAscii(transport);
}
std::optional<GatewayModbusConfig> GatewayModbusConfigFromValue(const DaliValue* value) {
if (value == nullptr || value->asObject() == nullptr) {
return std::nullopt;
}
const auto& json = *value->asObject();
GatewayModbusConfig config;
config.transport = getObjectString(json, "transport").value_or("tcp-server");
config.host = getObjectString(json, "host").value_or("");
config.port = static_cast<uint16_t>(
getObjectInt(json, "port").value_or(kGatewayModbusDefaultTcpPort));
config.unit_id = static_cast<uint8_t>(getObjectInt(json, "unitID").value_or(
getObjectInt(json, "unitId").value_or(getObjectInt(json, "unit_id").value_or(1))));
if (const auto* serial_value = getObjectValue(json, "serial")) {
if (const auto* serial = serial_value->asObject()) {
config.serial.uart_port = clampedInt(*serial, "uartPort", config.serial.uart_port, 0, 2);
config.serial.tx_pin = clampedInt(*serial, "txPin", config.serial.tx_pin, -1, 48);
config.serial.rx_pin = clampedInt(*serial, "rxPin", config.serial.rx_pin, -1, 48);
config.serial.baudrate = clampedU32(*serial, "baudrate", config.serial.baudrate,
1200, 921600);
config.serial.data_bits = clampedInt(*serial, "dataBits", config.serial.data_bits, 7, 8);
config.serial.parity = getObjectString(*serial, "parity").value_or(config.serial.parity);
config.serial.stop_bits = clampedInt(*serial, "stopBits", config.serial.stop_bits, 1, 2);
config.serial.rx_buffer_size = clampedSize(*serial, "rxBufferBytes",
config.serial.rx_buffer_size, 128, 4096);
config.serial.tx_buffer_size = clampedSize(*serial, "txBufferBytes",
config.serial.tx_buffer_size, 0, 4096);
config.serial.response_timeout_ms = clampedU32(*serial, "responseTimeoutMs",
config.serial.response_timeout_ms, 1, 1000);
config.serial.inter_frame_gap_us = clampedU32(*serial, "interFrameGapUs",
config.serial.inter_frame_gap_us, 1000,
100000);
if (const auto* rs485_value = getObjectValue(*serial, "rs485")) {
if (const auto* rs485 = rs485_value->asObject()) {
config.serial.rs485.enabled = getObjectBool(*rs485, "enabled")
.value_or(config.serial.rs485.enabled);
config.serial.rs485.de_pin = clampedInt(*rs485, "dePin",
config.serial.rs485.de_pin, -1, 48);
}
}
}
}
return config;
}
DaliValue GatewayModbusConfigToValue(const GatewayModbusConfig& config) {
DaliValue::Object out;
out["transport"] = config.transport;
out["host"] = config.host;
out["port"] = static_cast<int>(config.port);
out["unitID"] = static_cast<int>(config.unit_id);
DaliValue::Object serial;
serial["uartPort"] = config.serial.uart_port;
serial["txPin"] = config.serial.tx_pin;
serial["rxPin"] = config.serial.rx_pin;
serial["baudrate"] = static_cast<int>(config.serial.baudrate);
serial["dataBits"] = config.serial.data_bits;
serial["parity"] = config.serial.parity;
serial["stopBits"] = config.serial.stop_bits;
serial["rxBufferBytes"] = static_cast<int>(config.serial.rx_buffer_size);
serial["txBufferBytes"] = static_cast<int>(config.serial.tx_buffer_size);
serial["responseTimeoutMs"] = static_cast<int>(config.serial.response_timeout_ms);
serial["interFrameGapUs"] = static_cast<int>(config.serial.inter_frame_gap_us);
DaliValue::Object rs485;
rs485["enabled"] = config.serial.rs485.enabled;
rs485["dePin"] = config.serial.rs485.de_pin;
serial["rs485"] = std::move(rs485);
out["serial"] = std::move(serial);
return DaliValue(std::move(out));
}
const char* GatewayModbusSpaceToString(GatewayModbusSpace space) {
switch (space) {
case GatewayModbusSpace::kCoil:
return "coil";
case GatewayModbusSpace::kDiscreteInput:
return "discrete_input";
case GatewayModbusSpace::kHoldingRegister:
return "holding_register";
case GatewayModbusSpace::kInputRegister:
return "input_register";
}
return "unknown";
}
const char* GatewayModbusAccessToString(GatewayModbusAccess access) {
switch (access) {
case GatewayModbusAccess::kReadOnly:
return "read_only";
case GatewayModbusAccess::kWriteOnly:
return "write_only";
case GatewayModbusAccess::kReadWrite:
return "read_write";
}
return "unknown";
}
const char* GatewayModbusGeneratedKindToString(GatewayModbusGeneratedKind kind) {
switch (kind) {
case GatewayModbusGeneratedKind::kShortOn:
return "short_on";
case GatewayModbusGeneratedKind::kShortOff:
return "short_off";
case GatewayModbusGeneratedKind::kShortRecallMax:
return "short_recall_max";
case GatewayModbusGeneratedKind::kShortRecallMin:
return "short_recall_min";
case GatewayModbusGeneratedKind::kShortDiscovered:
return "short_discovered";
case GatewayModbusGeneratedKind::kShortOnline:
return "short_online";
case GatewayModbusGeneratedKind::kShortSupportsDt1:
return "short_supports_dt1";
case GatewayModbusGeneratedKind::kShortSupportsDt4:
return "short_supports_dt4";
case GatewayModbusGeneratedKind::kShortSupportsDt5:
return "short_supports_dt5";
case GatewayModbusGeneratedKind::kShortSupportsDt6:
return "short_supports_dt6";
case GatewayModbusGeneratedKind::kShortSupportsDt8:
return "short_supports_dt8";
case GatewayModbusGeneratedKind::kShortGroupMaskKnown:
return "short_group_mask_known";
case GatewayModbusGeneratedKind::kShortActualLevelKnown:
return "short_actual_level_known";
case GatewayModbusGeneratedKind::kShortSceneKnown:
return "short_scene_known";
case GatewayModbusGeneratedKind::kShortSettingsKnown:
return "short_settings_known";
case GatewayModbusGeneratedKind::kShortControlGearPresent:
return "short_control_gear_present";
case GatewayModbusGeneratedKind::kShortLampFailure:
return "short_lamp_failure";
case GatewayModbusGeneratedKind::kShortLampPowerOn:
return "short_lamp_power_on";
case GatewayModbusGeneratedKind::kShortLimitError:
return "short_limit_error";
case GatewayModbusGeneratedKind::kShortFadingCompleted:
return "short_fading_completed";
case GatewayModbusGeneratedKind::kShortResetState:
return "short_reset_state";
case GatewayModbusGeneratedKind::kShortMissingShortAddress:
return "short_missing_short_address";
case GatewayModbusGeneratedKind::kShortPowerSupplyFault:
return "short_power_supply_fault";
case GatewayModbusGeneratedKind::kShortBrightness:
return "short_brightness";
case GatewayModbusGeneratedKind::kShortColorTemperature:
return "short_color_temperature";
case GatewayModbusGeneratedKind::kShortGroupMask:
return "short_group_mask";
case GatewayModbusGeneratedKind::kShortPowerOnLevel:
return "short_power_on_level";
case GatewayModbusGeneratedKind::kShortSystemFailureLevel:
return "short_system_failure_level";
case GatewayModbusGeneratedKind::kShortMinLevel:
return "short_min_level";
case GatewayModbusGeneratedKind::kShortMaxLevel:
return "short_max_level";
case GatewayModbusGeneratedKind::kShortFadeTime:
return "short_fade_time";
case GatewayModbusGeneratedKind::kShortFadeRate:
return "short_fade_rate";
case GatewayModbusGeneratedKind::kShortInventoryState:
return "short_inventory_state";
case GatewayModbusGeneratedKind::kShortPrimaryType:
return "short_primary_type";
case GatewayModbusGeneratedKind::kShortTypeMask:
return "short_type_mask";
case GatewayModbusGeneratedKind::kShortActualLevel:
return "short_actual_level";
case GatewayModbusGeneratedKind::kShortSceneId:
return "short_scene_id";
case GatewayModbusGeneratedKind::kShortRawStatus:
return "short_raw_status";
case GatewayModbusGeneratedKind::kShortDiagnosticBit:
return "short_diagnostic_bit";
case GatewayModbusGeneratedKind::kNone:
default:
return "none";
}
}
int GatewayModbusHumanAddressFromWire(GatewayModbusSpace space, uint16_t zero_based_address) {
return baseForSpace(space) + static_cast<int>(zero_based_address);
}
std::optional<GatewayModbusSpace> GatewayModbusReadSpaceForFunction(uint8_t function_code) {
switch (function_code) {
case 0x01:
return GatewayModbusSpace::kCoil;
case 0x02:
return GatewayModbusSpace::kDiscreteInput;
case 0x03:
return GatewayModbusSpace::kHoldingRegister;
case 0x04:
return GatewayModbusSpace::kInputRegister;
default:
return std::nullopt;
}
}
std::optional<GatewayModbusSpace> GatewayModbusWriteSpaceForFunction(uint8_t function_code) {
switch (function_code) {
case 0x05:
case 0x0F:
return GatewayModbusSpace::kCoil;
case 0x06:
case 0x10:
return GatewayModbusSpace::kHoldingRegister;
default:
return std::nullopt;
}
}
GatewayModbusBridge::GatewayModbusBridge(DaliBridgeEngine& engine) : engine_(engine) {
rebuildMap();
}
void GatewayModbusBridge::setConfig(const GatewayModbusConfig& config) { config_ = config; }
const GatewayModbusConfig& GatewayModbusBridge::config() const { return config_; }
void GatewayModbusBridge::rebuildMap() {
std::map<PointKey, GatewayModbusPoint> next;
for (uint8_t short_address = 0; short_address < kShortAddressCount; ++short_address) {
for (const auto& spec : kGeneratedCoils) {
addGeneratedPoint(&next, short_address, spec);
}
for (const auto& spec : kGeneratedDiscreteInputs) {
addGeneratedPoint(&next, short_address, spec);
}
for (const auto& spec : kGeneratedHoldingRegisters) {
addGeneratedPoint(&next, short_address, spec);
}
for (const auto& spec : kGeneratedInputRegisters) {
addGeneratedPoint(&next, short_address, spec);
}
for (const auto& spec : kGeneratedDiagnosticBits) {
addGeneratedDiagnosticPoint(&next, short_address, spec);
}
for (const auto& spec : kGeneratedDiagnosticBitsTail) {
addGeneratedDiagnosticPoint(&next, short_address, spec);
}
}
for (const auto& model : engine_.listModels()) {
if (model.protocol != BridgeProtocolKind::modbus || !model.external.registerAddress.has_value()) {
continue;
}
const auto space = spaceForObjectType(model.external.objectType);
if (!space.has_value()) {
continue;
}
GatewayModbusPoint point;
point.space = space.value();
point.access = accessForSpace(space.value());
point.address = static_cast<uint16_t>(model.external.registerAddress.value());
point.id = model.id;
point.name = model.displayName();
point.generated = false;
point.generated_kind = GatewayModbusGeneratedKind::kNone;
point.model_id = model.id;
point.operation = model.operation;
point.bit_index = model.external.bitIndex;
if (model.dali.kind == BridgeDaliTargetKind::shortAddress && model.dali.shortAddress.has_value()) {
point.short_address = model.dali.shortAddress.value();
}
next[PointKey{point.space, point.address}] = std::move(point);
}
points_.clear();
points_.reserve(next.size());
for (auto& entry : next) {
points_.push_back(std::move(entry.second));
}
}
std::optional<GatewayModbusPoint> GatewayModbusBridge::findPoint(GatewayModbusSpace space,
uint16_t address) const {
const auto found = std::find_if(points_.begin(), points_.end(), [space, address](const auto& point) {
return point.space == space && point.address == address;
});
if (found == points_.end()) {
return std::nullopt;
}
return *found;
}
std::vector<GatewayModbusPointBinding> GatewayModbusBridge::describePoints() const {
std::vector<GatewayModbusPointBinding> bindings;
bindings.reserve(points_.size());
for (const auto& point : points_) {
bindings.push_back(toBinding(point));
}
return bindings;
}
std::vector<GatewayModbusPointBinding> GatewayModbusBridge::describeHoldingRegisters() const {
std::vector<GatewayModbusPointBinding> bindings;
for (const auto& point : points_) {
if (point.space == GatewayModbusSpace::kHoldingRegister) {
bindings.push_back(toBinding(point));
}
}
return bindings;
}
DaliBridgeResult GatewayModbusBridge::readModelPoint(const GatewayModbusPoint& point) const {
return executeModelPoint(point, std::nullopt);
}
DaliBridgeResult GatewayModbusBridge::writeRegisterPoint(const GatewayModbusPoint& point,
uint16_t value) const {
return executeModelPoint(point, static_cast<int>(value));
}
DaliBridgeResult GatewayModbusBridge::writeCoilPoint(const GatewayModbusPoint& point,
bool value) const {
return executeModelPoint(point, value ? 1 : 0);
}
DaliBridgeResult GatewayModbusBridge::executeModelPoint(const GatewayModbusPoint& point,
std::optional<int> value) const {
DaliBridgeRequest request;
request.sequence = "modbus-" + std::to_string(point.address);
request.modelID = point.model_id;
if (value.has_value()) {
request.value = value.value();
}
if (point.model_id.empty()) {
DaliBridgeResult result;
result.sequence = request.sequence;
result.error = "generated Modbus point requires gateway handler";
return result;
}
return engine_.execute(request);
}
} // namespace gateway