using Modbus.Device;
using Prism.Events;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using TeamAAS_VP.Events;
using TeamAAS_VP.Models;
namespace TeamAAS_VP.Core
{
///
/// Modbus TCP 主站:轮询 MDC V2 电批,锁付过程中累积实时曲线,完成后推送结果
/// 地址默认值参考 MDC V2 Appendix A(Input Register 功能码 0x04)
///
public class ModbusTcpMasterCommunicate : IDisposable
{
// MDC V2 事件数据区起始地址(3201~3213 一次读完,保证结果一致性)
private const ushort MdcEventDataStartAddress = 3201;
private const int MdcEventDataRegisterCount = 13;
private readonly ModbusTcpMasterConfig _config;
private readonly object _masterLock = new object();
private readonly IEventAggregator _eventAggregator;
private TcpClient _tcpClient;
private IModbusMaster _master;
private CancellationTokenSource _pollCts;
// 轮询状态
private bool _lastFinishComplete;
private bool _lastLockInProgress;
private int _lastWaveCount;
/// 本次锁付周期内是否检测到 Motor RUN,防止启动时误触发完成事件
private bool _finishArmed;
// 实时累积波形(MDC 无波形缓冲区,靠轮询 3300/3301 累积)
private readonly List _accumulatedWave = new List();
private DateTime _lockStartTime = DateTime.Now;
private double _accumulatedTurns;
public ModbusTcpMasterConfig Config { get { return _config; } }
public bool IsEnabled { get { return _config.IsEnabled; } }
public bool IsConnected { get; private set; }
public ModbusTcpMasterCommunicate(ModbusTcpMasterConfig config, IEventAggregator eventAggregator)
{
_config = config;
_eventAggregator = eventAggregator;
}
public void Start()
{
if (!_config.IsEnabled)
{
return;
}
Connect();
SyncInitialPollState();
_pollCts = new CancellationTokenSource();
Task.Run(() => PollLoop(_pollCts.Token));
}
public void Stop()
{
if (_pollCts != null)
{
_pollCts.Cancel();
_pollCts.Dispose();
_pollCts = null;
}
Disconnect();
}
public void Dispose()
{
Stop();
}
#region 连接
private void Connect()
{
Disconnect();
_tcpClient = new TcpClient();
_tcpClient.Connect(_config.IP, _config.Port);
_master = ModbusIpMaster.CreateIp(_tcpClient);
_master.Transport.ReadTimeout = 3000;
_master.Transport.WriteTimeout = 3000;
IsConnected = true;
}
private void Disconnect()
{
IsConnected = false;
if (_master != null)
{
_master.Dispose();
_master = null;
}
if (_tcpClient != null)
{
_tcpClient.Close();
_tcpClient = null;
}
}
private bool TryReconnect()
{
try
{
Connect();
SyncInitialPollState();
return true;
}
catch (Exception ex)
{
IsConnected = false;
LogHelper.WriteLogError("Modbus TCP 主站重连失败", ex);
return false;
}
}
///
/// 连接/启动时同步电批当前状态,避免 3212 仍为「完成」时误触发锁付完成
///
private void SyncInitialPollState()
{
_lastFinishComplete = ReadFinishComplete();
_lastLockInProgress = ReadLockInProgress();
_finishArmed = false;
_lastWaveCount = 0;
ClearAccumulatedWave();
}
#endregion
#region 轮询
private void PollLoop(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
try
{
PollOnce();
}
catch (Exception ex)
{
LogHelper.WriteLogError("Modbus TCP 主站轮询出错", ex);
Disconnect();
Thread.Sleep(2000);
TryReconnect();
}
Thread.Sleep(Math.Max(10, _config.PollIntervalMs));
}
}
private void PollOnce()
{
if (!IsConnected)
{
TryReconnect();
return;
}
bool isLocking = ReadLockInProgress();
bool finishComplete = ReadFinishComplete();
HandleLockStartEdge(isLocking);
if (isLocking)
{
AppendRealtimeWavePoint();
}
bool needCurve = isLocking || finishComplete;
if (!needCurve)
{
ResetPollStateIfIdle();
return;
}
List waveDatas = GetCurrentWaveDatas();
PublishCurveUpdateIfChanged(waveDatas, finishComplete);
// 必须:先检测到 Motor RUN(_finishArmed),再检测 3212 上升沿,才推送完成
if (finishComplete && !_lastFinishComplete && _finishArmed)
{
PublishLockFinish(waveDatas);
ClearAccumulatedWave();
_finishArmed = false;
}
_lastFinishComplete = finishComplete;
_lastLockInProgress = isLocking;
}
/// 检测到锁付开始时清空累积曲线并允许后续完成判定
private void HandleLockStartEdge(bool isLocking)
{
if (!isLocking || _lastLockInProgress)
{
return;
}
ClearAccumulatedWave();
_lockStartTime = DateTime.Now;
_finishArmed = true;
_lastWaveCount = 0;
}
private void ResetPollStateIfIdle()
{
_lastFinishComplete = false;
_lastLockInProgress = false;
_finishArmed = false;
_lastWaveCount = 0;
ClearAccumulatedWave();
}
private void ClearAccumulatedWave()
{
_accumulatedWave.Clear();
_accumulatedTurns = 0;
}
private List GetCurrentWaveDatas()
{
if (_config.WaveDataMode == WaveDataSourceMode.RegisterBlock)
{
return ReadWaveDatasFromRegisters();
}
return new List(_accumulatedWave);
}
private void PublishCurveUpdateIfChanged(List waveDatas, bool finishComplete)
{
// 完成态已处理过则不再刷曲线,避免 3212 保持为 1 时反复推送空曲线
if (finishComplete && _lastFinishComplete)
{
return;
}
int waveCount = waveDatas.Count;
if (waveCount == _lastWaveCount && !finishComplete)
{
return;
}
_lastWaveCount = waveCount;
_eventAggregator.GetEvent().Publish(waveDatas);
}
private void PublishLockFinish(List waveDatas)
{
LockResult result = BuildLockResult(waveDatas);
result.LockCurveImagePath = BuildLockCurveImagePath(result.Timestamp);
result.CurveCsvPath = BuildLockCurveCsvPath(result.Timestamp);
_eventAggregator.GetEvent().Publish(result);
}
#endregion
#region MDC 实时波形累积
///
/// 锁付进行中:读取 3300 扭矩、3301 转速,累积为曲线点
///
private void AppendRealtimeWavePoint()
{
float torque = ReadScaledTorque(_config.RealtimeTorqueAddress);
float speed = ReadRegister(_config.RealtimeSpeedAddress);
double intervalSec = _config.PollIntervalMs / 1000.0;
_accumulatedTurns += (speed / 60.0) * intervalSec;
_accumulatedWave.Add(new WaveData
{
Torque = torque,
Speed = speed,
Turns = _accumulatedTurns,
Time = (DateTime.Now - _lockStartTime).TotalSeconds
});
}
#endregion
#region 寄存器读取
private bool ReadLockInProgress()
{
if (_config.LockInProgressAddress == 0)
{
return false;
}
// MDC:3307 Motor RUN = 1 表示正在锁付
return ReadRegister(_config.LockInProgressAddress) == _config.LockInProgressActiveValue;
}
/// MDC 3212:1=完成,2=NG,均视为锁付结束
private bool ReadFinishComplete()
{
ushort status = ReadRegister(_config.FinishFlagAddress);
return status == _config.FinishFlagCompleteValue
|| status == _config.FinishFlagNgValue;
}
///
/// 组装锁付结果。优先批量读取 MDC 3201~3213 事件区,保证各字段同一时刻快照。
///
private LockResult BuildLockResult(List waveDatas)
{
if (TryReadMdcEventBlock(out ushort durationMs, out ushort presetNo, out ushort torqueRaw,
out ushort status, out ushort snugAngle))
{
return CreateLockResult(waveDatas, durationMs, presetNo, torqueRaw, status, snugAngle);
}
// 非标准地址布局:逐寄存器读取
ushort fallbackStatus = ReadRegister(_config.FinishFlagAddress);
return CreateLockResult(
waveDatas,
ReadRegister(_config.LockDurationAddress),
ReadRegister(_config.ProgramNumberAddress),
ReadRegister(_config.LockTorqueAddress),
fallbackStatus,
ReadRegister(_config.LockTurnsAddress));
}
private LockResult CreateLockResult(
List waveDatas,
ushort durationRaw,
ushort presetNo,
ushort torqueRaw,
ushort status,
ushort turnsRaw)
{
// MDC 3212:1=OK,2=NG(以事件 Status 为准,比 3305 实时位更可靠)
bool lockPassed = status == _config.FinishFlagCompleteValue;
float durationSec = _config.LockDurationIsMilliseconds
? durationRaw / 1000f
: durationRaw;
float lockTurns = _config.TurnsScaleFactor > 0
? turnsRaw / _config.TurnsScaleFactor
: turnsRaw;
float lockTorque = _config.TorqueScaleFactor > 0
? torqueRaw / _config.TorqueScaleFactor
: torqueRaw;
return new LockResult
{
Timestamp = DateTime.Now,
LockTurns = lockTurns,
LockTorque = lockTorque,
LockDuration = durationSec,
LockPassed = lockPassed,
ScrewdriverProgramNumber = presetNo,
WaveDatas = waveDatas
};
}
///
/// 一次读取 MDC 事件数据块 3201~3213(说明书 Appendix A,功能码 0x04)
///
private bool TryReadMdcEventBlock(
out ushort durationMs,
out ushort presetNo,
out ushort torqueRaw,
out ushort status,
out ushort snugAngle)
{
durationMs = presetNo = torqueRaw = status = snugAngle = 0;
if (!_config.UseInputRegisters || !IsStandardMdcEventLayout())
{
return false;
}
ushort[] block = ReadRegisters(MdcEventDataStartAddress, MdcEventDataRegisterCount);
if (block == null || block.Length < MdcEventDataRegisterCount)
{
return false;
}
// 相对 3201 的偏移:3201=0, 3202=1, 3204=3, 3212=11, 3213=12
durationMs = block[0];
presetNo = block[1];
torqueRaw = block[3];
status = block[11];
snugAngle = block[12];
return true;
}
/// 判断配置是否为 MDC V2 标准事件区地址布局
private bool IsStandardMdcEventLayout()
{
return _config.LockDurationAddress == 3201
&& _config.ProgramNumberAddress == 3202
&& _config.LockTorqueAddress == 3204
&& _config.FinishFlagAddress == 3212
&& _config.LockTurnsAddress == 3213;
}
/// RegisterBlock 模式:从连续寄存器块批量读波形
private List ReadWaveDatasFromRegisters()
{
int pointCount = ReadRegister(_config.WaveDataCountAddress);
if (pointCount <= 0)
{
return new List();
}
pointCount = Math.Min(pointCount, _config.MaxWavePoints);
int totalRegisters = pointCount * _config.WavePointRegisterCount;
ushort[] registers = ReadRegisters(_config.WaveDataStartAddress, totalRegisters);
List waveDatas = new List(pointCount);
for (int i = 0; i < pointCount; i++)
{
waveDatas.Add(ParseWavePointFromBlock(registers, i * _config.WavePointRegisterCount));
}
return waveDatas;
}
private WaveData ParseWavePointFromBlock(ushort[] registers, int baseOffset)
{
return new WaveData
{
Turns = ReadFloatFromRegisters(registers, baseOffset + 0),
Torque = ReadFloatFromRegisters(registers, baseOffset + 2),
LockAngle = ReadFloatFromRegisters(registers, baseOffset + 4),
Speed = ReadFloatFromRegisters(registers, baseOffset + 6),
Slopes = ReadFloatFromRegisters(registers, baseOffset + 8),
Time = ReadFloatFromRegisters(registers, baseOffset + 10)
};
}
private float ReadScaledTorque(ushort address)
{
return ScaleTorque(ReadRegister(address));
}
private float ScaleTorque(ushort raw)
{
if (_config.TorqueScaleFactor <= 0)
{
return raw;
}
return raw / _config.TorqueScaleFactor;
}
private ushort ReadRegister(ushort address)
{
lock (_masterLock)
{
if (_config.UseInputRegisters)
{
return _master.ReadInputRegisters(_config.SlaveID, address, 1)[0];
}
return _master.ReadHoldingRegisters(_config.SlaveID, address, 1)[0];
}
}
private ushort[] ReadRegisters(ushort startAddress, int count)
{
lock (_masterLock)
{
if (_config.UseInputRegisters)
{
return _master.ReadInputRegisters(_config.SlaveID, startAddress, (ushort)count);
}
return _master.ReadHoldingRegisters(_config.SlaveID, startAddress, (ushort)count);
}
}
private static float ReadFloatFromRegisters(ushort[] registers, int index)
{
if (registers == null || index + 1 >= registers.Length)
{
return 0f;
}
byte[] bytes = new byte[4];
BitConverter.GetBytes(registers[index + 1]).CopyTo(bytes, 0);
BitConverter.GetBytes(registers[index + 0]).CopyTo(bytes, 2);
return BitConverter.ToSingle(bytes, 0);
}
private string BuildLockCurveImagePath(DateTime timestamp)
{
string folder = _config.LockCurveImageFolder;
if (!Directory.Exists(folder))
{
Directory.CreateDirectory(folder);
}
return Path.Combine(folder, $"LockCurve_{timestamp:yyyyMMdd_HHmmss_fff}.png");
}
private string BuildLockCurveCsvPath(DateTime timestamp)
{
string folder = Path.Combine(_config.LockCurveImageFolder, "Csv");
if (!Directory.Exists(folder))
{
Directory.CreateDirectory(folder);
}
return Path.Combine(folder, $"LockCurve_{timestamp:yyyyMMdd_HHmmss_fff}.csv");
}
#endregion
}
}