using System.Collections.Concurrent;
using CommunityToolkit.Mvvm.ComponentModel;
using YZWater.Core.Services;
namespace YZWater.Core.Models;
///
/// PLC 数据模型 - 内存数据快照
///
public partial class PlcDataModel : ObservableObject
{
// ═══════════════════════════════════════════════
// 水箱液位
// ═══════════════════════════════════════════════
[ObservableProperty] private float _tank1Level;
[ObservableProperty] private float _tank2Level;
[ObservableProperty] private float _tank3Level;
[ObservableProperty] private float _tank4Level;
// ═══════════════════════════════════════════════
// 流量
// ═══════════════════════════════════════════════
[ObservableProperty] private float _inflowRate;
[ObservableProperty] private float _outflowRate;
[ObservableProperty] private float _flowDelta;
// ═══════════════════════════════════════════════
// 泵状态 (0-4)
// ═══════════════════════════════════════════════
[ObservableProperty] private bool _pump1Running;
[ObservableProperty] private bool _pump2Running;
[ObservableProperty] private bool _pump3Running;
[ObservableProperty] private bool _pump4Running;
[ObservableProperty] private bool _pump5Running;
// 泵故障
[ObservableProperty] private bool _pump1Fault;
[ObservableProperty] private bool _pump2Fault;
[ObservableProperty] private bool _pump3Fault;
[ObservableProperty] private bool _pump4Fault;
[ObservableProperty] private bool _pump5Fault;
// 泵频率
[ObservableProperty] private float _pump1Freq;
[ObservableProperty] private float _pump2Freq;
[ObservableProperty] private float _pump3Freq;
[ObservableProperty] private float _pump4Freq;
[ObservableProperty] private float _pump5Freq;
// 泵电流
[ObservableProperty] private float _pump1Current;
[ObservableProperty] private float _pump2Current;
[ObservableProperty] private float _pump3Current;
[ObservableProperty] private float _pump4Current;
[ObservableProperty] private float _pump5Current;
// ═══════════════════════════════════════════════
// 风机状态
// ═══════════════════════════════════════════════
[ObservableProperty] private bool _fan1Running;
[ObservableProperty] private bool _fan2Running;
[ObservableProperty] private bool _fan1Fault;
[ObservableProperty] private bool _fan2Fault;
// ═══════════════════════════════════════════════
// 阀门位置 (0-3, 0=100=全关, 100=全开)
// ═══════════════════════════════════════════════
[ObservableProperty] private int _valve1Position;
[ObservableProperty] private int _valve2Position;
[ObservableProperty] private int _valve3Position;
[ObservableProperty] private int _valve4Position;
// ═══════════════════════════════════════════════
// 系统状态
// ═══════════════════════════════════════════════
[ObservableProperty] private bool _isAutoMode;
// ═══════════════════════════════════════════════
// 数据质量
// ═══════════════════════════════════════════════
[ObservableProperty] private DateTime _lastReadTime;
[ObservableProperty] private bool _isDataValid;
[ObservableProperty] private bool _isPlcConnected;
// ═══════════════════════════════════════════════
// 动态寄存器(由 ProjectProfile 驱动)
// ═══════════════════════════════════════════════
/// 模拟量动态寄存器(名称 → 值),由项目档案驱动
public ConcurrentDictionary AnalogRegisters { get; } = new();
/// 数字量动态寄存器(名称 → 值),由项目档案驱动
public ConcurrentDictionary DigitalRegisters { get; } = new();
// ═══════════════════════════════════════════════
// 测点级数据质量(OPC 风格的 Good/Bad)
// ═══════════════════════════════════════════════
///
/// 测点质量(名称 → 最近一次读取是否成功)。
/// 读取失败的测点保留上一次有效值,只把质量位置为 Bad,
/// 这样单个坏地址不会把整轮数据打成零,也不会有零被当成真实值使用。
///
private readonly ConcurrentDictionary _tagQuality = new();
///
/// 测点质量是否为 Bad(读不到/从未读到)。质量表里没有的键视为非 Bad:
/// 那可能是旧版硬编码测点,或客户端模式(服务端只传寄存器值、不传质量表),
/// 这种情况下由调用方自己的默认值语义负责。
///
public bool IsTagBad(string key)
=> _tagQuality.TryGetValue(key, out var readable) && !readable;
///
/// 记录测点质量。
/// 返回本次是否发生跳变以及跳变前的质量,调用方据此决定是否需要告警
/// (Good→Bad 才需要通知操作员,Bad→Good 由通信层记恢复日志)。
///
public (bool Changed, bool WasReadable) SetTagQuality(string key, bool readable)
{
// 先取旧值再写:AddOrUpdate 返回的是"新值",拿它比较永远得不出跳变
var known = _tagQuality.TryGetValue(key, out var previous);
_tagQuality[key] = readable;
return (known && previous != readable, previous);
}
///
/// 初始化动态寄存器(根据项目档案创建空条目)
///
public void InitializeDynamicRegisters(ProjectProfile profile)
{
AnalogRegisters.Clear();
DigitalRegisters.Clear();
// 泵相关
foreach (var pump in profile.Pumps)
{
if (!string.IsNullOrEmpty(pump.CurrentAddress))
AnalogRegisters[$"{pump.Name}.Current"] = 0;
if (!string.IsNullOrEmpty(pump.RunAddress))
DigitalRegisters[$"{pump.Name}.Run"] = false;
if (!string.IsNullOrEmpty(pump.FaultAddress))
DigitalRegisters[$"{pump.Name}.Fault"] = false;
if (!string.IsNullOrEmpty(pump.AutoStateAddress))
DigitalRegisters[$"{pump.Name}.AutoMode"] = false;
if (!string.IsNullOrEmpty(pump.DailyHoursAddress))
AnalogRegisters[$"{pump.Name}.DailyHours"] = 0;
if (!string.IsNullOrEmpty(pump.DailyMinutesAddress))
AnalogRegisters[$"{pump.Name}.DailyMinutes"] = 0;
}
// 水箱/水池
foreach (var tank in profile.Tanks)
{
if (!string.IsNullOrEmpty(tank.LevelAddress))
AnalogRegisters[$"{tank.Name}.Level"] = 0;
// 动态启泵液位:数量跟随项目泵数(至少检查 3 个以兼容旧配置)
var pumpThresholdCount = Math.Max(3, profile.Pumps.Count);
for (int i = 1; i <= pumpThresholdCount; i++)
{
var addr = tank.GetStartPumpLevelAddress(i);
if (!string.IsNullOrEmpty(addr))
AnalogRegisters[$"{tank.Name}.StartPump{i}Level"] = 0;
}
if (!string.IsNullOrEmpty(tank.StopLevelAddress))
AnalogRegisters[$"{tank.Name}.StopLevel"] = 0;
if (!string.IsNullOrEmpty(tank.ProtectionLevelAddress))
AnalogRegisters[$"{tank.Name}.ProtectionLevel"] = 0;
}
// 仪表
foreach (var gauge in profile.Gauges)
{
if (!string.IsNullOrEmpty(gauge.ValueAddress))
AnalogRegisters[$"{gauge.Name}.Value"] = 0;
if (!string.IsNullOrEmpty(gauge.HighSetAddress))
AnalogRegisters[$"{gauge.Name}.HighSet"] = 0;
if (!string.IsNullOrEmpty(gauge.LowSetAddress))
AnalogRegisters[$"{gauge.Name}.LowSet"] = 0;
}
// 通用传感器
foreach (var sensor in profile.Sensors)
{
if (!string.IsNullOrEmpty(sensor.Address))
AnalogRegisters[$"{sensor.Name}.Value"] = 0;
}
// 通用数字量输入(如变频器故障)
foreach (var di in profile.DigitalInputs)
{
if (!string.IsNullOrEmpty(di.Address))
DigitalRegisters[$"DI.{di.Name}"] = false;
}
// 通用数字量输出(如复位按钮)
foreach (var do_ in profile.DigitalOutputs)
{
if (!string.IsNullOrEmpty(do_.Address))
DigitalRegisters[$"DO.{do_.Name}"] = false;
}
// 全局中控/屏控:所有泵共用一个地址,读键约定与 PlcPollingService 一致(挂在第一个泵名下)。
// 不登记这个键的话它永远"未登记",IsTagBad 判不出 Bad,首轮就会把默认 false 当成"中控"放行写操作。
if (!string.IsNullOrEmpty(profile.ModeSelectAddress) && profile.Pumps.Count > 0)
DigitalRegisters[$"{profile.Pumps[0].Name}.ModeSelect"] = false;
// 档案里的测点初始质量为 Bad:读到过一次成功才转 Good。
// 地址填错/被轮询跳过的测点因此一直是 Bad,消费者不会拿零值去做判断。
_tagQuality.Clear();
foreach (var key in AnalogRegisters.Keys) _tagQuality[key] = false;
foreach (var key in DigitalRegisters.Keys) _tagQuality[key] = false;
}
/// 获取动态模拟量寄存器值
public float GetAnalogRegister(string key, float defaultValue = 0)
=> AnalogRegisters.TryGetValue(key, out var value) ? value : defaultValue;
/// 获取动态数字量寄存器值
public bool GetDigitalRegister(string key, bool defaultValue = false)
=> DigitalRegisters.TryGetValue(key, out var value) ? value : defaultValue;
///
/// 复制另一份模型的动态寄存器与测点质量。
/// UI 线程用快照取值,不能直接引用轮询线程正在写入的字典,
/// 但档案驱动的数据都在这两个字典里,不复制就等于读到的全是默认零值。
///
public void CopyDynamicFrom(PlcDataModel source)
{
AnalogRegisters.Clear();
DigitalRegisters.Clear();
_tagQuality.Clear();
foreach (var (key, value) in source.AnalogRegisters) AnalogRegisters[key] = value;
foreach (var (key, value) in source.DigitalRegisters) DigitalRegisters[key] = value;
foreach (var (key, value) in source._tagQuality) _tagQuality[key] = value;
}
// ═══════════════════════════════════════════════
// 计算属性
// ═══════════════════════════════════════════════
public int RunningPumpCount => (Pump1Running ? 1 : 0) + (Pump2Running ? 1 : 0) +
(Pump3Running ? 1 : 0) + (Pump4Running ? 1 : 0) +
(Pump5Running ? 1 : 0);
public int RunningFanCount => (Fan1Running ? 1 : 0) + (Fan2Running ? 1 : 0);
public bool HasAnyFault => Pump1Fault || Pump2Fault || Pump3Fault || Pump4Fault || Pump5Fault ||
Fan1Fault || Fan2Fault;
/// 数据可用于记录/控制(Mock 模式视为始终有效;读取失败整轮作废时 IsDataValid=false)
public bool HasValidData => MockPlcService.IsEnabled || (IsPlcConnected && IsDataValid);
///
/// 获取泵运行状态
///
public bool GetPumpRunning(int index) => index switch
{
0 => Pump1Running,
1 => Pump2Running,
2 => Pump3Running,
3 => Pump4Running,
4 => Pump5Running,
_ => false
};
///
/// 获取泵故障状态
///
public bool GetPumpFault(int index) => index switch
{
0 => Pump1Fault,
1 => Pump2Fault,
2 => Pump3Fault,
3 => Pump4Fault,
4 => Pump5Fault,
_ => false
};
///
/// 获取泵频率
///
public float GetPumpFreq(int index) => index switch
{
0 => Pump1Freq,
1 => Pump2Freq,
2 => Pump3Freq,
3 => Pump4Freq,
4 => Pump5Freq,
_ => 0
};
///
/// 获取泵电流
///
public float GetPumpCurrent(int index) => index switch
{
0 => Pump1Current,
1 => Pump2Current,
2 => Pump3Current,
3 => Pump4Current,
4 => Pump5Current,
_ => 0
};
}