#include "gateway_modbus.hpp" #include #include #include #include #include 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(space) < static_cast(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 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 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 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 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 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(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(short_address), name == nullptr ? "point" : name); return buffer; } void addGeneratedPoint(std::map* points, uint8_t short_address, const GeneratedPointSpec& spec) { if (points == nullptr) { return; } const uint16_t address = static_cast(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* points, uint8_t short_address, const GeneratedDiagnosticBitSpec& spec) { if (points == nullptr) { return; } const uint16_t address = static_cast(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(fallback)); return static_cast(std::clamp(value, static_cast(min_value), static_cast(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(fallback)); return static_cast(std::clamp(value, static_cast(min_value), static_cast(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 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( getObjectInt(json, "port").value_or(kGatewayModbusDefaultTcpPort)); config.unit_id = static_cast(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(config.port); out["unitID"] = static_cast(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(config.serial.baudrate); serial["dataBits"] = config.serial.data_bits; serial["parity"] = config.serial.parity; serial["stopBits"] = config.serial.stop_bits; serial["rxBufferBytes"] = static_cast(config.serial.rx_buffer_size); serial["txBufferBytes"] = static_cast(config.serial.tx_buffer_size); serial["responseTimeoutMs"] = static_cast(config.serial.response_timeout_ms); serial["interFrameGapUs"] = static_cast(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(zero_based_address); } std::optional 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 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 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(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 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 GatewayModbusBridge::describePoints() const { std::vector bindings; bindings.reserve(points_.size()); for (const auto& point : points_) { bindings.push_back(toBinding(point)); } return bindings; } std::vector GatewayModbusBridge::describeHoldingRegisters() const { std::vector 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(value)); } DaliBridgeResult GatewayModbusBridge::writeCoilPoint(const GatewayModbusPoint& point, bool value) const { return executeModelPoint(point, value ? 1 : 0); } DaliBridgeResult GatewayModbusBridge::executeModelPoint(const GatewayModbusPoint& point, std::optional 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