| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273 |
- using Serilog;
- using YZWater.Core.Models;
- namespace YZWater.Core.Services;
- /// <summary>
- /// Mock PLC 服务 - 无真实 PLC 时生成模拟数据用于测试
- /// </summary>
- public static class MockPlcService
- {
- private static readonly Random Rng = new();
- private static bool _enabled;
- // 模拟状态
- private static float _tank1 = 50, _tank2 = 60, _tank3 = 45, _tank4 = 55;
- private static float _inflow = 30, _outflow = 28;
- private static bool _pump1 = true, _pump2 = false, _pump3 = true, _pump4 = false, _pump5 = false;
- private static bool _fan1 = true, _fan2 = false;
- private static float _pump1Freq = 45, _pump2Freq = 0, _pump3Freq = 40, _pump4Freq = 0, _pump5Freq = 0;
- private static bool _autoMode = true;
- // 项目化模拟用的故障状态(1# 泵永远不故障,2#/3# 偶尔故障演示跳过逻辑)
- private static bool _pump2Fault, _pump3Fault;
- /// <summary>
- /// 是否启用 Mock 模式
- /// </summary>
- public static bool IsEnabled => _enabled;
- /// <summary>
- /// 启用 Mock 模式(替代真实 PLC 连接)
- /// </summary>
- public static void Enable()
- {
- _enabled = true;
- Log.Information("Mock PLC 数据源已启用");
- }
- /// <summary>
- /// 生成一帧模拟数据并更新 PlcDataModel
- /// </summary>
- public static void UpdateData(PlcDataModel data)
- {
- if (!_enabled) return;
- // 模拟液位波动
- _tank1 = Clamp(_tank1 + RandomDelta(2f), 0, 100);
- _tank2 = Clamp(_tank2 + RandomDelta(1.5f), 0, 100);
- _tank3 = Clamp(_tank3 + RandomDelta(1f), 0, 100);
- _tank4 = Clamp(_tank4 + RandomDelta(1.5f), 0, 100);
- // 模拟流量波动
- _inflow = Clamp(_inflow + RandomDelta(3f), 0, 200);
- _outflow = Clamp(_outflow + RandomDelta(2.5f), 0, 200);
- // 模拟泵频率波动
- if (_pump1) _pump1Freq = Clamp(_pump1Freq + RandomDelta(1f), 0, 50);
- if (_pump3) _pump3Freq = Clamp(_pump3Freq + RandomDelta(1f), 0, 50);
- // 按液位逻辑自动控制泵启停(按编号顺序:1# → 2# → 3#)
- // 阈值对应模拟归一化后的液位 0~10m
- var stopLevel = 3.5f;
- var startPump1 = 5.5f;
- var startPump2 = 7.0f;
- var startPump3 = 8.5f;
- var level = _tank1 / 10f; // 归一化 0~10m
- if (level < stopLevel)
- {
- _pump1 = _pump2 = _pump3 = false;
- }
- else if (level >= startPump3)
- {
- _pump1 = _pump2 = _pump3 = true;
- }
- else if (level >= startPump2)
- {
- _pump1 = _pump2 = true;
- _pump3 = false;
- }
- else if (level >= startPump1)
- {
- _pump1 = true;
- _pump2 = _pump3 = false;
- }
- // 偶尔随机故障(演示故障跳过逻辑)
- if (Rng.NextDouble() < 0.0005) _pump2Fault = !_pump2Fault;
- if (Rng.NextDouble() < 0.0003) _pump3Fault = !_pump3Fault;
- // 更新数据模型
- data.Tank1Level = _tank1;
- data.Tank2Level = _tank2;
- data.Tank3Level = _tank3;
- data.Tank4Level = _tank4;
- data.InflowRate = _inflow;
- data.OutflowRate = _outflow;
- data.FlowDelta = _inflow - _outflow;
- data.Pump1Running = _pump1;
- data.Pump2Running = _pump2;
- data.Pump3Running = _pump3;
- data.Pump4Running = _pump4;
- data.Pump5Running = _pump5;
- data.Pump1Fault = false;
- data.Pump2Fault = _pump2Fault;
- data.Pump3Fault = _pump3Fault;
- data.Pump4Fault = false;
- data.Pump5Fault = false;
- data.Pump1Freq = _pump1Freq;
- data.Pump2Freq = _pump2Freq;
- data.Pump3Freq = _pump3Freq;
- data.Pump4Freq = _pump4Freq;
- data.Pump5Freq = _pump5Freq;
- data.Fan1Running = _fan1;
- data.Fan2Running = _fan2;
- data.Fan1Fault = false;
- data.Fan2Fault = false;
- data.IsAutoMode = _autoMode;
- data.LastReadTime = DateTime.Now;
- data.IsDataValid = true;
- data.IsPlcConnected = true;
- // 同步到项目化动态寄存器(ProjectViewModel 通过这些键读数据)
- SyncDynamicRegisters(data);
- }
- /// <summary>
- /// 把模拟数据同步到项目化动态寄存器(AnalogRegisters / DigitalRegisters)。
- /// 按键名后缀模式匹配:.Level / .Current / .Value / .DailyHours 等。
- /// 阈值类寄存器(.StartPumpNLevel / .StopLevel / .ProtectionLevel)保留合理默认值,
- /// 保证项目化视图(如泵站首页 sparkline、负荷条等)在 Mock 模式下也能看到变化。
- /// </summary>
- private static void SyncDynamicRegisters(PlcDataModel data)
- {
- if (data.AnalogRegisters.Count == 0 && data.DigitalRegisters.Count == 0) return;
- // 模拟量寄存器:按 key 后缀匹配,填充模拟值
- foreach (var key in data.AnalogRegisters.Keys)
- {
- data.AnalogRegisters[key] = SimulateAnalog(key);
- }
- // 数字量寄存器:按 key 后缀匹配
- foreach (var key in data.DigitalRegisters.Keys)
- {
- data.DigitalRegisters[key] = SimulateDigital(key);
- }
- }
- private static float SimulateAnalog(string key)
- {
- // ── 液位(所有 tank 的 .Level 都统一用 Tank1 模拟,
- // 对于单水箱项目如扬杰二厂刚好一一对应)──
- if (key.EndsWith(".Level")) return _tank1 / 10f; // 归一到 0~10m
- // ── 阈值寄存器:使用合理默认值(不覆盖配置)──
- if (key.EndsWith(".StartPump1Level")) return 5.5f;
- if (key.EndsWith(".StartPump2Level")) return 7.0f;
- if (key.EndsWith(".StartPump3Level")) return 8.5f;
- if (key.EndsWith(".StopLevel")) return 3.5f;
- if (key.EndsWith(".ProtectionLevel")) return 9.5f;
- // ── 泵电流:运行时 ~22A(接近额定 25A),停止时 0 ──
- if (key.EndsWith(".Current"))
- {
- var n = ExtractPumpIndex(key);
- return n switch
- {
- 1 => _pump1 ? 22f + RandomDelta(0.5f) : 0f,
- 2 => _pump2 ? 23f + RandomDelta(0.5f) : 0f,
- 3 => _pump3 ? 21f + RandomDelta(0.5f) : 0f,
- 4 => _pump4 ? 20f + RandomDelta(0.5f) : 0f,
- 5 => _pump5 ? 19f + RandomDelta(0.5f) : 0f,
- _ => 0f
- };
- }
- // ── 日台时(静态演示值,避免每帧变化让 UI 抖动)──
- if (key.EndsWith(".DailyHours")) return 4;
- if (key.EndsWith(".DailyMinutes")) return 30;
- // ── 仪表(按单位匹配)──
- if (key.EndsWith(".Value"))
- {
- if (key.StartsWith("压力")) return Clamp(_outflow * 0.003f + 0.25f + RandomDelta(0.02f), 0f, 1.6f);
- if (key.StartsWith("流量")) return Clamp(_inflow, 0f, 200f);
- return 0;
- }
- return 0;
- }
- private static bool SimulateDigital(string key)
- {
- if (key.EndsWith(".Run"))
- {
- var n = ExtractPumpIndex(key);
- return n switch
- {
- 1 => _pump1,
- 2 => _pump2,
- 3 => _pump3,
- 4 => _pump4,
- 5 => _pump5,
- _ => false
- };
- }
- if (key.EndsWith(".Fault"))
- {
- var n = ExtractPumpIndex(key);
- return n switch
- {
- 1 => false, // 1# 泵永远不故障(主泵)
- 2 => _pump2Fault, // 2# 偶尔故障
- 3 => _pump3Fault, // 3# 偶尔故障
- _ => false
- };
- }
- if (key.EndsWith(".AutoMode")) return _autoMode;
- if (key.EndsWith(".ModeSelect")) return true; // 1=屏控(默认屏控,上位机不可操作)
- if (key.StartsWith("DI.")) return false; // 默认 DI 正常
- return false;
- }
- /// <summary>从 "1#泵.Run" 之类的 key 中抽出泵编号(1..5)</summary>
- private static int ExtractPumpIndex(string key)
- {
- // 匹配 "1#..." / "2#..." 等前缀
- if (key.Length >= 2 && key[0] >= '1' && key[0] <= '5' && key[1] == '#')
- return key[0] - '0';
- return 0;
- }
- /// <summary>
- /// Mock 写入操作(总是成功)
- /// </summary>
- public static Task<bool> WriteBoolAsync(string address, bool value)
- {
- Log.Debug("Mock 写入: {Address} = {Value}", address, value);
- // 模拟泵启停
- if (address == PlcConfig.Pump1Run) _pump1 = value;
- else if (address == PlcConfig.Pump2Run) _pump2 = value;
- else if (address == PlcConfig.Pump3Run) _pump3 = value;
- else if (address == PlcConfig.Pump4Run) _pump4 = value;
- else if (address == PlcConfig.Pump5Run) _pump5 = value;
- else if (address == PlcConfig.Fan1Run) _fan1 = value;
- else if (address == PlcConfig.Fan2Run) _fan2 = value;
- return Task.FromResult(true);
- }
- public static Task<bool> WriteFloatAsync(string address, float value)
- {
- Log.Debug("Mock 写入: {Address} = {Value}", address, value);
- return Task.FromResult(true);
- }
- private static float RandomDelta(float maxDelta)
- {
- return (float)(Rng.NextDouble() * 2 - 1) * maxDelta;
- }
- private static float Clamp(float value, float min, float max)
- {
- return Math.Max(min, Math.Min(max, value));
- }
- }
|