diff --git a/ActiveProtect.csproj b/ActiveProtect.csproj index d579445..08dcf2c 100644 --- a/ActiveProtect.csproj +++ b/ActiveProtect.csproj @@ -4,8 +4,19 @@ Exe net6.0 9.0 - disable + disable enable + false + false + + + + + + + + + diff --git a/ActiveProtect.sln b/ActiveProtect.sln index 99af9fe..98e9ec4 100644 --- a/ActiveProtect.sln +++ b/ActiveProtect.sln @@ -1,29 +1,33 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.5.002.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ActiveProtect", "ActiveProtect.csproj", "{7F47A2C7-1087-440B-82F7-A1BF50D30A1B}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {7F47A2C7-1087-440B-82F7-A1BF50D30A1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7F47A2C7-1087-440B-82F7-A1BF50D30A1B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7F47A2C7-1087-440B-82F7-A1BF50D30A1B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7F47A2C7-1087-440B-82F7-A1BF50D30A1B}.Release|Any CPU.Build.0 = Release|Any CPU - {644AFA6F-7DEE-4438-85C3-1D380B052E3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {644AFA6F-7DEE-4438-85C3-1D380B052E3D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {644AFA6F-7DEE-4438-85C3-1D380B052E3D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {644AFA6F-7DEE-4438-85C3-1D380B052E3D}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {73773200-50D4-40ED-803B-FC16B131FFD2} - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ActiveProtect", "ActiveProtect.csproj", "{7138CDD4-EC75-4803-8050-D72A32B381CA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{88B204F0-6636-4265-ACFB-52D889A56413}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ActiveProtect.Tests", "src\Tests\ActiveProtect.Tests.csproj", "{55F5BDBE-025D-465B-BB34-2262369715BB}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7138CDD4-EC75-4803-8050-D72A32B381CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7138CDD4-EC75-4803-8050-D72A32B381CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7138CDD4-EC75-4803-8050-D72A32B381CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7138CDD4-EC75-4803-8050-D72A32B381CA}.Release|Any CPU.Build.0 = Release|Any CPU + {55F5BDBE-025D-465B-BB34-2262369715BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {55F5BDBE-025D-465B-BB34-2262369715BB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {55F5BDBE-025D-465B-BB34-2262369715BB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {55F5BDBE-025D-465B-BB34-2262369715BB}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {55F5BDBE-025D-465B-BB34-2262369715BB} = {88B204F0-6636-4265-ACFB-52D889A56413} + EndGlobalSection +EndGlobal diff --git a/src/Models/Wanner/InfraredWarner.cs b/src/Models/Wanner/InfraredWarner.cs deleted file mode 100644 index d4ab7e9..0000000 --- a/src/Models/Wanner/InfraredWarner.cs +++ /dev/null @@ -1,51 +0,0 @@ -using ActiveProtect.Simulation; -using ActiveProtect.Utility; - -namespace ActiveProtect.Models -{ - /// - /// 红外告警器类,用于检测红外辐射并发出警报 - /// - public class InfraredWarner : WarnerBase - { - /// - /// 红外告警器灵敏度阈值 - /// - private readonly double sensitivityThreshold; - - public InfraredWarner(string id, Vector3D position, Orientation orientation, - ISimulationManager manager, string tankId, double warningDuration, double sensitivityThreshold) - : base(id, position, orientation, manager, tankId, warningDuration) - { - this.sensitivityThreshold = sensitivityThreshold; - SimulationManager.SubscribeToEvent(OnInfraredDetection); - } - - private void OnInfraredDetection(InfraredDetectionEvent evt) - { - if (evt?.TargetId == TankId && evt.Intensity >= sensitivityThreshold) - { - StartWarning(); - } - } - - public override string GetWarningStatus() - { - return $"红外告警器 {Id}:\n" + - $" 告警状态: {(IsWarning ? "告警中" : "正常")}\n" + - $" 告警持续时间: {CurrentWarningTime:F2}/{WarningDuration:F2}"; - } - - public override void Activate() - { - base.Activate(); - SimulationManager.SubscribeToEvent(OnInfraredDetection); - } - - public override void Deactivate() - { - base.Deactivate(); - SimulationManager.UnsubscribeFromEvent(OnInfraredDetection); - } - } -} \ No newline at end of file diff --git a/src/Models/Wanner/LaserWarner.cs b/src/Models/Wanner/LaserWarner.cs deleted file mode 100644 index e73f99b..0000000 --- a/src/Models/Wanner/LaserWarner.cs +++ /dev/null @@ -1,160 +0,0 @@ -using ActiveProtect.Simulation; -using System; -using ActiveProtect.Utility; - -namespace ActiveProtect.Models -{ - /// - /// 激光告警器类,用于检测激光照射并发出警报 - /// - public class LaserWarner : SimulationElement - { - /// - /// 是否正在发出警报 - /// - public bool IsAlarming { get; private set; } - - /// - /// 警报持续时间(秒) - /// - private readonly double alarmDuration; - - /// - /// 当前警报计时器 - /// - private double alarmTimer = 0; - - /// - /// 被监视实体的ID - /// - public string MonitoredEntityId { get; private set; } - - /// - /// 构造函数 - /// - /// 激光告警器ID - /// 初始位置 - /// 初始朝向 - /// 仿真管理器 - /// 被监视实体的ID - /// 激光告警器配置 - public LaserWarner(string id, Vector3D position, Orientation orientation, ISimulationManager simulationManager, string monitoredEntityId, LaserWarnerConfig config) - : base(id, position, orientation, 0, simulationManager) - { - MonitoredEntityId = monitoredEntityId; - IsAlarming = false; - alarmDuration = config.AlarmDuration; - base.SimulationManager.SubscribeToEvent(OnLaserIlluminationStart); - base.SimulationManager.SubscribeToEvent(OnLaserIlluminationStop); - } - - /// - /// 更新激光告警器状态 - /// - /// 时间步长 - public override void Update(double deltaTime) - { - if (!IsActive) return; - - var tank = SimulationManager.GetEntityById(MonitoredEntityId) as Tank; - if (tank != null) - { - Position = tank.Position; - Orientation = tank.Orientation; - } - - if (IsAlarming) - { - alarmTimer += deltaTime; - if (alarmTimer >= alarmDuration) - { - StopAlarm(); - } - } - } - - /// - /// 处理激光照射事件 - /// - /// 激光照射事件 - private void OnLaserIlluminationStart(LaserIlluminationStartEvent evt) - { - if (evt?.Target != null && evt.Target.Id == MonitoredEntityId) - { - TriggerAlarm(); - } - } - - /// - /// 处理激光照射停止事件 - /// - /// 激光照射停止事件 - private void OnLaserIlluminationStop(LaserIlluminationStopEvent evt) - { - if (evt?.Target != null && evt.Target.Id == MonitoredEntityId) - { - StopAlarm(); - } - } - - /// - /// 触发警报 - /// - public void TriggerAlarm() - { - if (!IsAlarming) - { - IsAlarming = true; - alarmTimer = 0; - PublishEvent(new LaserWarnerAlarmEvent() { TargetId = MonitoredEntityId }); - Console.WriteLine($"激光告警器 {Id} 发出警报!"); - } - } - - /// - /// 停止警报 - /// - public void StopAlarm() - { - if (IsAlarming) - { - IsAlarming = false; - alarmTimer = 0; - PublishEvent(new LaserWarnerAlarmStopEvent() { TargetId = MonitoredEntityId }); - Console.WriteLine($"激光告警器 {Id} 停止警报"); - } - } - - /// - /// 获取激光告警器状态信息 - /// - /// 状态信息字符串 - public override string GetStatus() - { - return $"激光告警器 {Id}:\n" + - $" 监视实体: {MonitoredEntityId}\n" + - $" 警报状态: {(IsAlarming ? "警报中" : "正常")}\n" + - $" 警报持续时间: {(IsAlarming ? alarmTimer : 0):F2}/{alarmDuration:F2}"; - } - - /// - /// 激活激光告警器 - /// - public override void Activate() - { - base.Activate(); - SimulationManager.SubscribeToEvent(OnLaserIlluminationStart); - SimulationManager.SubscribeToEvent(OnLaserIlluminationStop); - } - - /// - /// 停用激光告警器 - /// - public override void Deactivate() - { - base.Deactivate(); - SimulationManager.UnsubscribeFromEvent(OnLaserIlluminationStart); - SimulationManager.UnsubscribeFromEvent(OnLaserIlluminationStop); - } - } -} diff --git a/src/Models/Wanner/MillimeterWaveWarner.cs b/src/Models/Wanner/MillimeterWaveWarner.cs deleted file mode 100644 index e8b65f3..0000000 --- a/src/Models/Wanner/MillimeterWaveWarner.cs +++ /dev/null @@ -1,62 +0,0 @@ -using ActiveProtect.Simulation; -using ActiveProtect.Utility; - -namespace ActiveProtect.Models -{ - /// - /// 毫米波告警器类,用于检测毫米波辐射并发出警报 - /// - public class MillimeterWaveWarner : WarnerBase - { - /// - /// 毫米波告警器灵敏度阈值 - /// - private readonly double sensitivityThreshold; - - /// - /// 频率范围(GHz) - /// - private readonly (double Min, double Max) frequencyRange; - - public MillimeterWaveWarner(string id, Vector3D position, Orientation orientation, - ISimulationManager manager, string tankId, double warningDuration, - double sensitivityThreshold, (double Min, double Max) frequencyRange) - : base(id, position, orientation, manager, tankId, warningDuration) - { - this.sensitivityThreshold = sensitivityThreshold; - this.frequencyRange = frequencyRange; - SimulationManager.SubscribeToEvent(OnMillimeterWaveDetection); - } - - private void OnMillimeterWaveDetection(MillimeterWaveDetectionEvent evt) - { - if (evt?.TargetId == TankId && - evt.Intensity >= sensitivityThreshold && - evt.Frequency >= frequencyRange.Min && - evt.Frequency <= frequencyRange.Max) - { - StartWarning(); - } - } - - public override string GetWarningStatus() - { - return $"毫米波告警器 {Id}:\n" + - $" 告警状态: {(IsWarning ? "告警中" : "正常")}\n" + - $" 告警持续时间: {CurrentWarningTime:F2}/{WarningDuration:F2}\n" + - $" 频率范围: {frequencyRange.Min}-{frequencyRange.Max} GHz"; - } - - public override void Activate() - { - base.Activate(); - SimulationManager.SubscribeToEvent(OnMillimeterWaveDetection); - } - - public override void Deactivate() - { - base.Deactivate(); - SimulationManager.UnsubscribeFromEvent(OnMillimeterWaveDetection); - } - } -} \ No newline at end of file diff --git a/src/Models/Wanner/IWarner.cs b/src/Models/Warner/IWarner.cs similarity index 100% rename from src/Models/Wanner/IWarner.cs rename to src/Models/Warner/IWarner.cs diff --git a/src/Models/Warner/InfraredWarner.cs b/src/Models/Warner/InfraredWarner.cs new file mode 100644 index 0000000..298d80c --- /dev/null +++ b/src/Models/Warner/InfraredWarner.cs @@ -0,0 +1,71 @@ +using System; +using ActiveProtect.Simulation; +using ActiveProtect.Utility; + +namespace ActiveProtect.Models +{ + /// + /// 红外告警器类,用于检测红外辐射并发出警报 + /// + public class InfraredWarner : WarnerBase + { + private readonly double _sensitivityThreshold; + private readonly double _wavelengthMin; + private readonly double _wavelengthMax; + + public InfraredWarner(string id, Vector3D position, Orientation orientation, + ISimulationManager manager, string tankId, InfraredWarnerConfig config) + : base(id, position, orientation, manager, tankId, config.SensitivityThreshold, + (config.WavelengthMin, config.WavelengthMax), config.AlarmDuration) + { + _sensitivityThreshold = config.SensitivityThreshold; + _wavelengthMin = config.WavelengthMin; + _wavelengthMax = config.WavelengthMax; + SimulationManager.SubscribeToEvent(OnInfraredDetection); + } + + private void OnInfraredDetection(InfraredDetectionEvent evt) + { + if (evt?.TargetId == TankId && evt.Intensity >= _sensitivityThreshold) + { + StartWarning(); + } + } + + public override string GetWarningStatus() + { + return $"红外告警器 {Id}:\n" + + $" 告警状态: {(IsWarning ? "告警中" : "正常")}\n" + + $" 告警持续时间: {CurrentWarningTime:F2}/{WarningDuration:F2}\n" + + $" 波长范围: {_wavelengthMin:F2}-{_wavelengthMax:F2}微米"; + } + + public override void Update(double deltaTime) + { + base.Update(deltaTime); + + if (IsWarning && IsActive) + { + // 发布告警事件 + SimulationManager.PublishEvent(new InfraredWarnerAlarmEvent + { + SenderId = Id, + Timestamp = SimulationManager.CurrentTime, + TargetId = TankId + }); + } + } + + public override void Activate() + { + base.Activate(); + SimulationManager.SubscribeToEvent(OnInfraredDetection); + } + + public override void Deactivate() + { + base.Deactivate(); + SimulationManager.UnsubscribeFromEvent(OnInfraredDetection); + } + } +} \ No newline at end of file diff --git a/src/Models/Warner/LaserWarner.cs b/src/Models/Warner/LaserWarner.cs new file mode 100644 index 0000000..90fa9eb --- /dev/null +++ b/src/Models/Warner/LaserWarner.cs @@ -0,0 +1,94 @@ +using System; +using ActiveProtect.Simulation; +using ActiveProtect.Utility; + +namespace ActiveProtect.Models +{ + /// + /// 激光告警器类,用于检测激光照射并发出警报 + /// + public class LaserWarner : WarnerBase + { + /// + /// 构造函数 + /// + public LaserWarner(string id, Vector3D position, Orientation orientation, + ISimulationManager manager, string tankId, LaserWarnerConfig config) + : base(id, position, orientation, manager, tankId, config.SensitivityThreshold, + (config.WavelengthMin, config.WavelengthMax), config.AlarmDuration) + { + SimulationManager.SubscribeToEvent(OnLaserIlluminationStart); + SimulationManager.SubscribeToEvent(OnLaserIlluminationStop); + } + + /// + /// 处理激光照射事件 + /// + private void OnLaserIlluminationStart(LaserIlluminationStartEvent evt) + { + // 添加日志以帮助调试 + Console.WriteLine($"收到激光照射事件: TargetId={evt?.Target?.Id}, ExpectedId={TankId}"); + + if (evt?.Target != null && evt.Target.Id == TankId) + { + Console.WriteLine("开始告警"); + StartWarning(); + } + } + + /// + /// 处理激光照射停止事件 + /// + private void OnLaserIlluminationStop(LaserIlluminationStopEvent evt) + { + // 添加日志以帮助调试 + Console.WriteLine($"收到激光照射停止事件: TargetId={evt?.Target?.Id}, ExpectedId={TankId}"); + + if (evt?.Target != null && evt.Target.Id == TankId) + { + Console.WriteLine("停止告警"); + StopWarning(); + } + } + + /// + /// 获取告警状态信息 + /// + public override string GetWarningStatus() + { + return $"激光告警器 {Id}:\n" + + $" 告警状态: {(IsWarning ? "告警中" : "正常")}\n" + + $" 告警持续时间: {CurrentWarningTime:F2}/{WarningDuration:F2}"; + } + + public override void Activate() + { + base.Activate(); + SimulationManager.SubscribeToEvent(OnLaserIlluminationStart); + SimulationManager.SubscribeToEvent(OnLaserIlluminationStop); + } + + public override void Deactivate() + { + base.Deactivate(); + SimulationManager.UnsubscribeFromEvent(OnLaserIlluminationStart); + SimulationManager.UnsubscribeFromEvent(OnLaserIlluminationStop); + } + + public override void Update(double deltaTime) + { + base.Update(deltaTime); + + if (IsWarning) + { + // 发布告警事件 + SimulationManager.PublishEvent(new LaserWarnerAlarmEvent + { + SenderId = Id, + Timestamp = SimulationManager.CurrentTime, + TargetId = TankId + }); + } + } + } +} \ No newline at end of file diff --git a/src/Models/Warner/MillimeterWaveWarner.cs b/src/Models/Warner/MillimeterWaveWarner.cs new file mode 100644 index 0000000..410ab2c --- /dev/null +++ b/src/Models/Warner/MillimeterWaveWarner.cs @@ -0,0 +1,75 @@ +using System; +using ActiveProtect.Simulation; +using ActiveProtect.Utility; + +namespace ActiveProtect.Models +{ + /// + /// 毫米波告警器类,用于检测毫米波辐射并发出警报 + /// + public class MillimeterWaveWarner : WarnerBase + { + private readonly double _sensitivityThreshold; + private readonly double _wavelengthMin; + private readonly double _wavelengthMax; + + public MillimeterWaveWarner(string id, Vector3D position, Orientation orientation, + ISimulationManager manager, string tankId, MillimeterWaveWarnerConfig config) + : base(id, position, orientation, manager, tankId, config.SensitivityThreshold, + (config.WavelengthMin, config.WavelengthMax), config.AlarmDuration) + { + _sensitivityThreshold = config.SensitivityThreshold; + _wavelengthMin = config.WavelengthMin; + _wavelengthMax = config.WavelengthMax; + SimulationManager.SubscribeToEvent(OnMillimeterWaveDetection); + } + + private void OnMillimeterWaveDetection(MillimeterWaveDetectionEvent evt) + { + if (evt?.TargetId == TankId && + evt.Intensity >= _sensitivityThreshold && + evt.Frequency >= _wavelengthMin && + evt.Frequency <= _wavelengthMax) + { + StartWarning(); + } + } + + public override string GetWarningStatus() + { + return $"毫米波告警器 {Id}:\n" + + $" 告警状态: {(IsWarning ? "告警中" : "正常")}\n" + + $" 告警持续时间: {CurrentWarningTime:F2}/{WarningDuration:F2}\n" + + $" 频率范围: {_wavelengthMin:F2}-{_wavelengthMax:F2}GHz"; + } + + public override void Update(double deltaTime) + { + base.Update(deltaTime); + + if (IsWarning && IsActive) + { + // 发布告警事件 + SimulationManager.PublishEvent(new MillimeterWaveWarnerAlarmEvent + { + SenderId = Id, + Timestamp = SimulationManager.CurrentTime, + TargetId = TankId, + DetectedFrequency = (_wavelengthMin + _wavelengthMax) / 2 // 使用中心频率 + }); + } + } + + public override void Activate() + { + base.Activate(); + SimulationManager.SubscribeToEvent(OnMillimeterWaveDetection); + } + + public override void Deactivate() + { + base.Deactivate(); + SimulationManager.UnsubscribeFromEvent(OnMillimeterWaveDetection); + } + } +} \ No newline at end of file diff --git a/src/Models/Wanner/UltravioletWarner.cs b/src/Models/Warner/UltravioletWarner.cs similarity index 50% rename from src/Models/Wanner/UltravioletWarner.cs rename to src/Models/Warner/UltravioletWarner.cs index 4d169f3..f79945f 100644 --- a/src/Models/Wanner/UltravioletWarner.cs +++ b/src/Models/Warner/UltravioletWarner.cs @@ -1,3 +1,4 @@ +using System; using ActiveProtect.Simulation; using ActiveProtect.Utility; @@ -8,22 +9,24 @@ namespace ActiveProtect.Models /// public class UltravioletWarner : WarnerBase { - /// - /// 紫外告警器灵敏度阈值 - /// - private readonly double sensitivityThreshold; + private readonly double _sensitivityThreshold; + private readonly double _wavelengthMin; + private readonly double _wavelengthMax; public UltravioletWarner(string id, Vector3D position, Orientation orientation, - ISimulationManager manager, string tankId, double warningDuration, double sensitivityThreshold) - : base(id, position, orientation, manager, tankId, warningDuration) + ISimulationManager manager, string tankId, UltravioletWarnerConfig config) + : base(id, position, orientation, manager, tankId, config.SensitivityThreshold, + (config.WavelengthMin, config.WavelengthMax), config.AlarmDuration) { - this.sensitivityThreshold = sensitivityThreshold; + _sensitivityThreshold = config.SensitivityThreshold; + _wavelengthMin = config.WavelengthMin; + _wavelengthMax = config.WavelengthMax; SimulationManager.SubscribeToEvent(OnUltravioletDetection); } private void OnUltravioletDetection(UltravioletDetectionEvent evt) { - if (evt?.TargetId == TankId && evt.Intensity >= sensitivityThreshold) + if (evt?.TargetId == TankId && evt.Intensity >= _sensitivityThreshold) { StartWarning(); } @@ -33,7 +36,24 @@ namespace ActiveProtect.Models { return $"紫外告警器 {Id}:\n" + $" 告警状态: {(IsWarning ? "告警中" : "正常")}\n" + - $" 告警持续时间: {CurrentWarningTime:F2}/{WarningDuration:F2}"; + $" 告警持续时间: {CurrentWarningTime:F2}/{WarningDuration:F2}\n" + + $" 波长范围: {_wavelengthMin:F2}-{_wavelengthMax:F2}纳米"; + } + + public override void Update(double deltaTime) + { + base.Update(deltaTime); + + if (IsWarning && IsActive) + { + // 发布告警事件 + SimulationManager.PublishEvent(new UltravioletWarnerAlarmEvent + { + SenderId = Id, + Timestamp = SimulationManager.CurrentTime, + TargetId = TankId + }); + } } public override void Activate() diff --git a/src/Models/Wanner/WarnerBase.cs b/src/Models/Warner/WarnerBase.cs similarity index 82% rename from src/Models/Wanner/WarnerBase.cs rename to src/Models/Warner/WarnerBase.cs index 2e21a4c..093fdbf 100644 --- a/src/Models/Wanner/WarnerBase.cs +++ b/src/Models/Warner/WarnerBase.cs @@ -14,6 +14,16 @@ namespace ActiveProtect.Models /// public bool IsWarning { get; protected set; } + /// + /// 告警灵敏度阈值 + /// + protected double SensitivityThreshold { get; set; } + + /// + /// 告警频率范围 + /// + protected (double Min, double Max) FrequencyRange { get; set; } + /// /// 告警持续时间(秒) /// @@ -30,10 +40,12 @@ namespace ActiveProtect.Models protected string TankId { get; set; } protected WarnerBase(string id, Vector3D position, Orientation orientation, - ISimulationManager manager, string tankId, double warningDuration) + ISimulationManager manager, string tankId, double sensitivityThreshold, (double Min, double Max) frequencyRange, double warningDuration) : base(id, position, orientation, 0, manager) { TankId = tankId; + SensitivityThreshold = sensitivityThreshold; + FrequencyRange = frequencyRange; WarningDuration = warningDuration; CurrentWarningTime = 0; IsWarning = false; diff --git a/src/Simulation/SimulationConfig.cs b/src/Simulation/SimulationConfig.cs index 754502c..e924cff 100644 --- a/src/Simulation/SimulationConfig.cs +++ b/src/Simulation/SimulationConfig.cs @@ -165,18 +165,165 @@ namespace ActiveProtect.Simulation /// public string Id { get; set; } + /// + /// 告警灵敏度阈值 + /// + public double SensitivityThreshold { get; set; } + /// /// 警报持续时间(秒) /// public double AlarmDuration { get; set; } + /// + /// 最小波长(纳米) + /// + public double WavelengthMin { get; set; } + + /// + /// 最大波长(纳米) + /// + public double WavelengthMax { get; set; } + /// /// 构造函数,设置默认值 /// public LaserWarnerConfig() { Id = ""; + SensitivityThreshold = 0; AlarmDuration = 5.0; // 默认警报持续5秒 + WavelengthMin = 0; + WavelengthMax = 0; + } + } + + /// + /// 紫外线告警器配置类 + /// + public class UltravioletWarnerConfig + { + /// + /// 紫外线告警器ID + /// + public string Id { get; set; } + + /// + /// 告警灵敏度阈值 + /// + public double SensitivityThreshold { get; set; } + + /// + /// 警报持续时间(秒) + /// + public double AlarmDuration { get; set; } + + /// + /// 波长范围(纳米) + /// + public double WavelengthMin { get; set; } + + /// + /// 最大波长(纳米) + /// + public double WavelengthMax { get; set; } + + /// + /// 构造函数,设置默认值 + /// + public UltravioletWarnerConfig() + { + Id = ""; + SensitivityThreshold = 0; + AlarmDuration = 5.0; // 默认警报持续5秒 + WavelengthMin = 0; + WavelengthMax = 0; + } + } + + /// + /// 红外告警器配置类 + /// + public class InfraredWarnerConfig + { + /// + /// 红外告警器ID + /// + public string Id { get; set; } + + /// + /// 告警灵敏度阈值 + /// + public double SensitivityThreshold { get; set; } + + /// + /// 警报持续时间(秒) + /// + public double AlarmDuration { get; set; } + + /// + /// 最小波长(纳米) + /// + public double WavelengthMin { get; set; } + + /// + /// 最大波长(纳米) + /// + public double WavelengthMax { get; set; } + + /// + /// 构造函数,设置默认值 + /// + public InfraredWarnerConfig() + { + Id = ""; + SensitivityThreshold = 0; + AlarmDuration = 5.0; // 默认警报持续5秒 + WavelengthMin = 0; + WavelengthMax = 0; + } + } + + /// + /// 毫米波告警器配置类 + /// + public class MillimeterWaveWarnerConfig + { + /// + /// 毫米波告警器ID + /// + public string Id { get; set; } + + /// + /// 告警灵敏度阈值 + /// + public double SensitivityThreshold { get; set; } + + /// + /// 警报持续时间(秒) + /// + public double AlarmDuration { get; set; } + + /// + /// 波长范围(纳米) + /// + public double WavelengthMin { get; set; } + + /// + /// 最大波长(纳米) + /// + public double WavelengthMax { get; set; } + + /// + /// 构造函数,设置默认值 + /// + public MillimeterWaveWarnerConfig() + { + Id = ""; + SensitivityThreshold = 0; + AlarmDuration = 5.0; // 默认警报持续5秒 + WavelengthMin = 0; + WavelengthMax = 0; } } diff --git a/src/Tests/ActiveProtect.Tests.csproj b/src/Tests/ActiveProtect.Tests.csproj new file mode 100644 index 0000000..c10d712 --- /dev/null +++ b/src/Tests/ActiveProtect.Tests.csproj @@ -0,0 +1,37 @@ + + + + net6.0 + 9.0 + disable + enable + false + false + false + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Tests/Unit/Models/Jammer/InfraredJammerTests.cs b/src/Tests/Unit/Models/Jammer/InfraredJammerTests.cs new file mode 100644 index 0000000..8dae26e --- /dev/null +++ b/src/Tests/Unit/Models/Jammer/InfraredJammerTests.cs @@ -0,0 +1,167 @@ +using Xunit; +using Moq; +using System; +using System.Linq; +using ActiveProtect.Models; +using ActiveProtect.Simulation; +using ActiveProtect.Utility; + +namespace ActiveProtect.Tests.Unit.Models.Jammer +{ + public class InfraredJammerTests : IDisposable + { + private readonly Mock _mockSimManager; + private readonly InfraredJammer _jammer; + private readonly string _jammerId = "TestJammer"; + private readonly string _tankId = "TestTank"; + + public InfraredJammerTests() + { + // 设置测试环境 + _mockSimManager = new Mock(); + _mockSimManager.Setup(m => m.CurrentTime).Returns(0.0); + + // 设置事件发布 + _mockSimManager.Setup(m => m.PublishEvent(It.IsAny())); + + _jammer = new InfraredJammer( + _jammerId, + new Vector3D(0, 0, 0), + new Orientation(), + _mockSimManager.Object, + _tankId, + maxJammingPower: 1000.0, + initialJammingPower: 400.0, + powerIncreaseRate: 100.0, + maxCooldownTime: 5.0, + wavelengthRange: (3.0, 5.0) + ); + } + + public void Dispose() + { + // 清理资源 + _jammer.Deactivate(); + } + + [Fact] + public void Constructor_InitializesCorrectly() + { + Assert.Equal(_jammerId, _jammer.Id); + Assert.False(_jammer.IsJamming); + Assert.Equal(400.0, _jammer.CurrentJammingPower); + } + + [Fact] + public void StartJamming_WhenNotJamming_StartsJamming() + { + // Act + _jammer.StartJamming(); + + // Assert + Assert.True(_jammer.IsJamming); + } + + [Fact] + public void StopJamming_WhenJamming_StopsAndEntersCooldown() + { + // Arrange + _jammer.StartJamming(); + + // Act + _jammer.StopJamming(); + + // Assert + Assert.False(_jammer.IsJamming); + Assert.Equal(400.0, _jammer.CurrentJammingPower); // 重置为初始功率 + } + + [Theory] + [InlineData(1.0, 500.0)] // 1秒后功率增加100 + [InlineData(2.0, 600.0)] // 2秒后功率增加200 + [InlineData(10.0, 1000.0)] // 10秒后达到最大功率1000 + public void Update_WhenJamming_IncreasesPowerCorrectly(double deltaTime, double expectedPower) + { + // Arrange + _jammer.StartJamming(); + _jammer.Activate(); // 确保激活状态 + + // Act + _jammer.Update(deltaTime); + + // Assert + Assert.Equal(expectedPower, _jammer.CurrentJammingPower, 1); // 允许1瓦特的误差 + } + + [Fact] + public void OnInfraredWarnerAlarm_StartsJamming() + { + // Arrange + Action? eventHandler = null; + _mockSimManager.Setup(m => m.SubscribeToEvent(It.IsAny>())) + .Callback>(handler => eventHandler = handler); + + // 激活干扰器以触发事件订阅 + _jammer.Activate(); + + // 确保事件处理程序被设置 + Assert.NotNull(eventHandler); + + // Act + eventHandler?.Invoke(new InfraredWarnerAlarmEvent { TargetId = _tankId }); + + // Assert + Assert.True(_jammer.IsJamming); + } + + [Fact] + public void Update_PublishesJammingEvent() + { + // Arrange + _jammer.StartJamming(); + _jammer.Activate(); + + // Act + _jammer.Update(1.0); + + // Assert + _mockSimManager.Verify(m => m.PublishEvent(It.Is(e => + e.TargetId == _tankId && + Math.Abs(e.JammingPower - 500.0) < 1 && + e.WavelengthMin == 3.0 && + e.WavelengthMax == 5.0 + )), Times.Once); + } + + [Fact] + public void GetJammingStatus_ReturnsCorrectFormat() + { + // Arrange + _jammer.StartJamming(); + + // Act + string status = _jammer.GetJammingStatus(); + + // Assert + Assert.Contains("红外干扰器", status); + Assert.Contains("干扰中", status); + Assert.Contains("400.00/1000.00", status); + Assert.Contains("3.00-5.00微米", status); + } + + [Fact] + public void Deactivate_UnsubscribesFromEvents() + { + // Act + _jammer.Deactivate(); + + // Assert + _mockSimManager.Verify( + m => m.UnsubscribeFromEvent(It.IsAny>()), + Times.Once); + _mockSimManager.Verify( + m => m.UnsubscribeFromEvent(It.IsAny>()), + Times.Once); + } + } +} \ No newline at end of file diff --git a/src/Tests/Unit/Models/Warner/InfraredWarnerTests.cs b/src/Tests/Unit/Models/Warner/InfraredWarnerTests.cs new file mode 100644 index 0000000..c6ec793 --- /dev/null +++ b/src/Tests/Unit/Models/Warner/InfraredWarnerTests.cs @@ -0,0 +1,161 @@ +using Xunit; +using Moq; +using System; +using ActiveProtect.Models; +using ActiveProtect.Simulation; +using ActiveProtect.Utility; + +namespace ActiveProtect.Tests.Unit.Models.Warner +{ + public class InfraredWarnerTests : IDisposable + { + private readonly Mock _mockSimManager; + private readonly InfraredWarner _warner; + private readonly string _warnerId = "TestInfraredWarner"; + private readonly string _tankId = "TestTank"; + private readonly InfraredWarnerConfig _config; + + public InfraredWarnerTests() + { + _mockSimManager = new Mock(); + _mockSimManager.Setup(m => m.CurrentTime).Returns(0.0); + _mockSimManager.Setup(m => m.PublishEvent(It.IsAny())); + + _config = new InfraredWarnerConfig + { + AlarmDuration = 5.0, + SensitivityThreshold = 0.1, + WavelengthMin = 3.0, + WavelengthMax = 5.0 + }; + + _warner = new InfraredWarner( + _warnerId, + new Vector3D(0, 0, 0), + new Orientation(0, 0, 0), + _mockSimManager.Object, + _tankId, + _config + ); + } + + public void Dispose() + { + _warner.Deactivate(); + } + + [Fact] + public void Constructor_InitializesCorrectly() + { + Assert.Equal(_warnerId, _warner.Id); + Assert.False(_warner.IsWarning); + } + + [Fact] + public void StartWarning_WhenNotWarning_StartsWarning() + { + // Act + _warner.StartWarning(); + + // Assert + Assert.True(_warner.IsWarning); + } + + [Fact] + public void StopWarning_WhenWarning_StopsWarning() + { + // Arrange + _warner.StartWarning(); + + // Act + _warner.StopWarning(); + + // Assert + Assert.False(_warner.IsWarning); + } + + [Fact] + public void Update_WhenWarningTimeExceeded_StopsWarning() + { + // Arrange + _warner.Activate(); + _warner.StartWarning(); + + // Act + _warner.Update(6.0); // 超过警报持续时间 + + // Assert + Assert.False(_warner.IsWarning); + } + + [Theory] + [InlineData(0.2, true)] // 高于阈值,应该触发 + [InlineData(0.05, false)] // 低于阈值,不应该触发 + public void OnInfraredDetection_RespondsToIntensityThreshold(double intensity, bool shouldTrigger) + { + // Arrange + Action? eventHandler = null; + _mockSimManager.Setup(m => m.SubscribeToEvent(It.IsAny>())) + .Callback>(handler => eventHandler = handler); + + _warner.Activate(); + Assert.NotNull(eventHandler); + + var detectionEvent = new InfraredDetectionEvent + { + TargetId = _tankId, + Intensity = intensity + }; + + // Act + eventHandler?.Invoke(detectionEvent); + + // Assert + Assert.Equal(shouldTrigger, _warner.IsWarning); + } + + [Fact] + public void GetWarningStatus_ReturnsCorrectFormat() + { + // Arrange + _warner.StartWarning(); + + // Act + string status = _warner.GetWarningStatus(); + + // Assert + Assert.Contains("红外告警器", status); + Assert.Contains("告警中", status); + Assert.Contains("3.00-5.00微米", status); + } + + [Fact] + public void Update_PublishesWarningEvent_WhenWarning() + { + // Arrange + _warner.StartWarning(); + _warner.Activate(); + + // Act + _warner.Update(1.0); + + // Assert + _mockSimManager.Verify(m => m.PublishEvent(It.Is(e => + e.TargetId == _tankId + )), Times.Once); + } + + [Fact] + public void Deactivate_UnsubscribesFromEvents() + { + // Act + _warner.Deactivate(); + + // Assert + _mockSimManager.Verify( + m => m.UnsubscribeFromEvent( + It.IsAny>()), + Times.Once); + } + } +} \ No newline at end of file diff --git a/src/Tests/Unit/Models/Warner/LaserWarnerTests.cs b/src/Tests/Unit/Models/Warner/LaserWarnerTests.cs new file mode 100644 index 0000000..27afe11 --- /dev/null +++ b/src/Tests/Unit/Models/Warner/LaserWarnerTests.cs @@ -0,0 +1,204 @@ +using Xunit; +using Moq; +using System; +using ActiveProtect.Models; +using ActiveProtect.Simulation; +using ActiveProtect.Utility; + +namespace ActiveProtect.Tests.Unit.Models.Warner +{ + public class LaserWarnerTests : IDisposable + { + private readonly Mock _mockSimManager; + private readonly LaserWarner _warner; + private readonly string _warnerId = "TestLaserWarner"; + private readonly string _tankId = "TestTank"; + private readonly LaserWarnerConfig _config; + + public LaserWarnerTests() + { + _mockSimManager = new Mock(); + _mockSimManager.Setup(m => m.CurrentTime).Returns(0.0); + _mockSimManager.Setup(m => m.PublishEvent(It.IsAny())); + + _config = new LaserWarnerConfig + { + AlarmDuration = 5.0, + }; + + _warner = new LaserWarner( + _warnerId, + new Vector3D(0, 0, 0), + new Orientation(0, 0, 0), + _mockSimManager.Object, + _tankId, + _config + ); + } + + public void Dispose() + { + _warner.Deactivate(); + } + + [Fact] + public void Constructor_InitializesCorrectly() + { + Assert.Equal(_warnerId, _warner.Id); + Assert.False(_warner.IsWarning); + } + + [Fact] + public void StartWarning_WhenNotWarning_StartsWarning() + { + // Act + _warner.StartWarning(); + + // Assert + Assert.True(_warner.IsWarning); + } + + [Fact] + public void StopWarning_WhenWarning_StopsWarning() + { + // Arrange + _warner.StartWarning(); + + // Act + _warner.StopWarning(); + + // Assert + Assert.False(_warner.IsWarning); + } + + [Fact] + public void Update_WhenWarningTimeExceeded_StopsWarning() + { + // Arrange + _warner.Activate(); + _warner.StartWarning(); + + // Act + _warner.Update(6.0); // 超过警报持续时间 + + // Assert + Assert.False(_warner.IsWarning); + } + + [Fact] + public void OnLaserIllumination_StartsWarning() + { + // Arrange + Action? eventHandler = null; + _mockSimManager.Setup(m => m.SubscribeToEvent(It.IsAny>())) + .Callback>(handler => eventHandler = handler); + + _warner.Activate(); + Assert.NotNull(eventHandler); + + // 创建一个模拟的目标 + var mockTarget = new Mock(_tankId, + new Vector3D(0, 0, 0), + new Orientation(0, 0, 0), + 0, + _mockSimManager.Object); + + // 设置模拟对象的 Id 属性 + mockTarget.Setup(t => t.Id).Returns(_tankId); + + var illuminationEvent = new LaserIlluminationStartEvent + { + Target = mockTarget.Object, + SenderId = "TestLaser" // 添加发送者ID + }; + + // Act + eventHandler?.Invoke(illuminationEvent); + + // Assert + Assert.True(_warner.IsWarning); + } + + [Fact] + public void OnLaserIlluminationStop_StopsWarning() + { + // Arrange + Action? eventHandler = null; + _mockSimManager.Setup(m => m.SubscribeToEvent(It.IsAny>())) + .Callback>(handler => eventHandler = handler); + + _warner.Activate(); + _warner.StartWarning(); + Assert.NotNull(eventHandler); + + // 创建一个模拟的目标 + var mockTarget = new Mock(_tankId, + new Vector3D(0, 0, 0), + new Orientation(0, 0, 0), + 0, + _mockSimManager.Object); + + // 设置模拟对象的 Id 属性 + mockTarget.Setup(t => t.Id).Returns(_tankId); + + var illuminationStopEvent = new LaserIlluminationStopEvent + { + Target = mockTarget.Object, + SenderId = "TestLaser" // 添加发送者ID + }; + + // Act + eventHandler?.Invoke(illuminationStopEvent); + + // Assert + Assert.False(_warner.IsWarning); + } + + [Fact] + public void GetWarningStatus_ReturnsCorrectFormat() + { + // Arrange + _warner.StartWarning(); + + // Act + string status = _warner.GetWarningStatus(); + + // Assert + Assert.Contains("激光告警器", status); + Assert.Contains("告警中", status); + } + + [Fact] + public void Update_PublishesWarningEvent_WhenWarning() + { + // Arrange + _warner.StartWarning(); + _warner.Activate(); + + // Act + _warner.Update(1.0); + + // Assert + _mockSimManager.Verify(m => m.PublishEvent(It.Is(e => + e.TargetId == _tankId + )), Times.Once); + } + + [Fact] + public void Deactivate_UnsubscribesFromEvents() + { + // Act + _warner.Deactivate(); + + // Assert + _mockSimManager.Verify( + m => m.UnsubscribeFromEvent( + It.IsAny>()), + Times.Once); + _mockSimManager.Verify( + m => m.UnsubscribeFromEvent( + It.IsAny>()), + Times.Once); + } + } +} \ No newline at end of file diff --git a/src/Tests/Unit/Models/Warner/MillimeterWaveWarnerTests.cs b/src/Tests/Unit/Models/Warner/MillimeterWaveWarnerTests.cs new file mode 100644 index 0000000..54007e1 --- /dev/null +++ b/src/Tests/Unit/Models/Warner/MillimeterWaveWarnerTests.cs @@ -0,0 +1,150 @@ +using Xunit; +using Moq; +using System; +using ActiveProtect.Models; +using ActiveProtect.Simulation; +using ActiveProtect.Utility; + +namespace ActiveProtect.Tests.Unit.Models.Warner +{ + public class MillimeterWaveWarnerTests : IDisposable + { + private readonly Mock _mockSimManager; + private readonly MillimeterWaveWarner _warner; + private readonly string _warnerId = "TestMMWWarner"; + private readonly string _tankId = "TestTank"; + private readonly MillimeterWaveWarnerConfig _config; + + public MillimeterWaveWarnerTests() + { + _mockSimManager = new Mock(); + _mockSimManager.Setup(m => m.CurrentTime).Returns(0.0); + _mockSimManager.Setup(m => m.PublishEvent(It.IsAny())); + + _config = new MillimeterWaveWarnerConfig + { + AlarmDuration = 5.0, + SensitivityThreshold = 0.1, + WavelengthMin = 30.0, // 30 GHz + WavelengthMax = 300.0 // 300 GHz + }; + + _warner = new MillimeterWaveWarner( + _warnerId, + new Vector3D(0, 0, 0), + new Orientation(0, 0, 0), + _mockSimManager.Object, + _tankId, + _config + ); + } + + public void Dispose() + { + _warner.Deactivate(); + } + + [Fact] + public void Constructor_InitializesCorrectly() + { + Assert.Equal(_warnerId, _warner.Id); + Assert.False(_warner.IsWarning); + } + + [Fact] + public void StartWarning_WhenNotWarning_StartsWarning() + { + // Act + _warner.StartWarning(); + + // Assert + Assert.True(_warner.IsWarning); + } + + [Fact] + public void StopWarning_WhenWarning_StopsWarning() + { + // Arrange + _warner.StartWarning(); + + // Act + _warner.StopWarning(); + + // Assert + Assert.False(_warner.IsWarning); + } + + [Theory] + [InlineData(0.2, 100.0, true)] // 高于阈值,频率在范围内 + [InlineData(0.05, 100.0, false)] // 低于阈值,频率在范围内 + [InlineData(0.2, 400.0, false)] // 高于阈值,频率超出范围 + public void OnMillimeterWaveDetection_RespondsToThresholds( + double intensity, double frequency, bool shouldTrigger) + { + // Arrange + Action? eventHandler = null; + _mockSimManager.Setup(m => m.SubscribeToEvent(It.IsAny>())) + .Callback>(handler => eventHandler = handler); + + _warner.Activate(); + Assert.NotNull(eventHandler); + + var detectionEvent = new MillimeterWaveDetectionEvent + { + TargetId = _tankId, + Intensity = intensity, + Frequency = frequency + }; + + // Act + eventHandler?.Invoke(detectionEvent); + + // Assert + Assert.Equal(shouldTrigger, _warner.IsWarning); + } + + [Fact] + public void GetWarningStatus_ReturnsCorrectFormat() + { + // Arrange + _warner.StartWarning(); + + // Act + string status = _warner.GetWarningStatus(); + + // Assert + Assert.Contains("毫米波告警器", status); + Assert.Contains("告警中", status); + Assert.Contains("30.00-300.00GHz", status); + } + + [Fact] + public void Update_PublishesWarningEvent_WhenWarning() + { + // Arrange + _warner.StartWarning(); + _warner.Activate(); + + // Act + _warner.Update(1.0); + + // Assert + _mockSimManager.Verify(m => m.PublishEvent(It.Is(e => + e.TargetId == _tankId + )), Times.Once); + } + + [Fact] + public void Deactivate_UnsubscribesFromEvents() + { + // Act + _warner.Deactivate(); + + // Assert + _mockSimManager.Verify( + m => m.UnsubscribeFromEvent( + It.IsAny>()), + Times.Once); + } + } +} \ No newline at end of file diff --git a/src/Tests/Unit/Models/Warner/UltravioletWarnerTests.cs b/src/Tests/Unit/Models/Warner/UltravioletWarnerTests.cs new file mode 100644 index 0000000..52489e7 --- /dev/null +++ b/src/Tests/Unit/Models/Warner/UltravioletWarnerTests.cs @@ -0,0 +1,161 @@ +using Xunit; +using Moq; +using System; +using ActiveProtect.Models; +using ActiveProtect.Simulation; +using ActiveProtect.Utility; + +namespace ActiveProtect.Tests.Unit.Models.Warner +{ + public class UltravioletWarnerTests : IDisposable + { + private readonly Mock _mockSimManager; + private readonly UltravioletWarner _warner; + private readonly string _warnerId = "TestUVWarner"; + private readonly string _tankId = "TestTank"; + private readonly UltravioletWarnerConfig _config; + + public UltravioletWarnerTests() + { + _mockSimManager = new Mock(); + _mockSimManager.Setup(m => m.CurrentTime).Returns(0.0); + _mockSimManager.Setup(m => m.PublishEvent(It.IsAny())); + + _config = new UltravioletWarnerConfig + { + AlarmDuration = 5.0, + SensitivityThreshold = 0.1, + WavelengthMin = 200.0, + WavelengthMax = 280.0 + }; + + _warner = new UltravioletWarner( + _warnerId, + new Vector3D(0, 0, 0), + new Orientation(0, 0, 0), + _mockSimManager.Object, + _tankId, + _config + ); + } + + public void Dispose() + { + _warner.Deactivate(); + } + + [Fact] + public void Constructor_InitializesCorrectly() + { + Assert.Equal(_warnerId, _warner.Id); + Assert.False(_warner.IsWarning); + } + + [Fact] + public void StartWarning_WhenNotWarning_StartsWarning() + { + // Act + _warner.StartWarning(); + + // Assert + Assert.True(_warner.IsWarning); + } + + [Fact] + public void StopWarning_WhenWarning_StopsWarning() + { + // Arrange + _warner.StartWarning(); + + // Act + _warner.StopWarning(); + + // Assert + Assert.False(_warner.IsWarning); + } + + [Fact] + public void Update_WhenWarningTimeExceeded_StopsWarning() + { + // Arrange + _warner.Activate(); + _warner.StartWarning(); + + // Act + _warner.Update(6.0); // 超过警报持续时间 + + // Assert + Assert.False(_warner.IsWarning); + } + + [Theory] + [InlineData(0.2, true)] // 高于阈值,应该触发 + [InlineData(0.05, false)] // 低于阈值,不应该触发 + public void OnUltravioletDetection_RespondsToIntensityThreshold(double intensity, bool shouldTrigger) + { + // Arrange + Action? eventHandler = null; + _mockSimManager.Setup(m => m.SubscribeToEvent(It.IsAny>())) + .Callback>(handler => eventHandler = handler); + + _warner.Activate(); + Assert.NotNull(eventHandler); + + var detectionEvent = new UltravioletDetectionEvent + { + TargetId = _tankId, + Intensity = intensity + }; + + // Act + eventHandler?.Invoke(detectionEvent); + + // Assert + Assert.Equal(shouldTrigger, _warner.IsWarning); + } + + [Fact] + public void GetWarningStatus_ReturnsCorrectFormat() + { + // Arrange + _warner.StartWarning(); + + // Act + string status = _warner.GetWarningStatus(); + + // Assert + Assert.Contains("紫外告警器", status); + Assert.Contains("告警中", status); + Assert.Contains("200.00-280.00纳米", status); + } + + [Fact] + public void Update_PublishesWarningEvent_WhenWarning() + { + // Arrange + _warner.StartWarning(); + _warner.Activate(); + + // Act + _warner.Update(1.0); + + // Assert + _mockSimManager.Verify(m => m.PublishEvent(It.Is(e => + e.TargetId == _tankId + )), Times.Once); + } + + [Fact] + public void Deactivate_UnsubscribesFromEvents() + { + // Act + _warner.Deactivate(); + + // Assert + _mockSimManager.Verify( + m => m.UnsubscribeFromEvent( + It.IsAny>()), + Times.Once); + } + } +} \ No newline at end of file