提交最新项目改动

This commit is contained in:
XingCheng3
2026-05-12 11:14:28 +08:00
parent 79c0f9cd43
commit 50e849bbef
53 changed files with 1320 additions and 1216 deletions

View File

@@ -0,0 +1,281 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
namespace MesWork
{
public partial class MesWorkForm
{
private class CurveCollectorState
{
public string OpName;
public long CurveId;
public string ProductNo;
public Thread WorkerThread;
public volatile bool IsRunning;
public bool IsSegmentEnded;
public bool IsPointValuesSaved;
public int SeqNo;
public readonly object BufferLock = new object();
public readonly List<CurveSamplePoint> Buffer = new List<CurveSamplePoint>();
public decimal? StableWeight;
public int? StableStartSeq;
public int? StableEndSeq;
}
private static readonly ConcurrentDictionary<string, CurveCollectorState> _curveCollectors
= new ConcurrentDictionary<string, CurveCollectorState>(StringComparer.OrdinalIgnoreCase);
private const int CurveCollectorSampleIntervalMs = 200;
private const int CurveStableWindowSize = 20;
/// <summary>
/// 允许工作后启动曲线采集,每个工位同一时间只保留一个采集段。
/// </summary>
private void CurveCollector_Start(string opName, string productNo, string modelNo)
{
try
{
if (_curveCollectors.TryGetValue(opName, out CurveCollectorState oldState) && !oldState.IsSegmentEnded)
{
if (oldState.IsRunning) return;
CurveCollector_EndSegment(opName, 0); // 清理上一次未正常结束的曲线
}
long curveId = B_DB_Opera.CurveRecord_Start(opName, productNo, modelNo, CurveCollectorSampleIntervalMs); // 先建曲线主记录
if (curveId <= 0)
{
WeighingPage?.AppendLog($"[{opName}] 曲线记录创建失败,未启动采集");
return;
}
var state = new CurveCollectorState
{
OpName = opName,
CurveId = curveId,
ProductNo = productNo,
IsRunning = true
};
state.WorkerThread = new Thread(() => CurveCollector_Work(state))
{
IsBackground = true,
Name = $"CurveCollector_{opName}"
};
_curveCollectors[opName] = state;
state.WorkerThread.Start();
WeighingPage?.AppendLog($"[{opName}] 实时重量曲线采集启动记录ID={curveId}");
}
catch (Exception err)
{
WeighingPage?.AppendLog($"[{opName}] 曲线采集启动异常:{err.Message}");
}
}
/// <summary>
/// 采集线程200ms读取一次实时重量只缓存到内存保存时一次性写库。
/// </summary>
private void CurveCollector_Work(CurveCollectorState state)
{
while (state.IsRunning)
{
try
{
decimal weight = Convert.ToDecimal(ReadPLC(100220, state.OpName)); // 实时重量点位
lock (state.BufferLock)
{
state.Buffer.Add(new CurveSamplePoint
{
SeqNo = ++state.SeqNo, // 序号用于追溯最终取点区间
SampleTime = DateTime.Now,
Weight = weight
});
}
}
catch (Exception err)
{
WeighingPage?.AppendLog($"[{state.OpName}] 曲线采样异常:{err.Message}");
}
Thread.Sleep(CurveCollectorSampleIntervalMs);
}
}
/// <summary>
/// 停止采样线程,但暂不关闭曲线记录,便于保存流程先计算最终重量。
/// </summary>
private void CurveCollector_StopSampling(string opName, bool waitingSave = false)
{
if (!_curveCollectors.TryGetValue(opName, out CurveCollectorState state)) return;
state.IsRunning = false;
if (state.WorkerThread != null && state.WorkerThread.IsAlive)
{
state.WorkerThread.Join(2000);
}
if (waitingSave)
{
CurveCollector_SavePointValues(state, 0, null); // 保存前先把点位字符串落库,供算法读取
}
}
/// <summary>
/// 保存完成后关闭曲线记录并关联称重记录ID。
/// </summary>
private void CurveCollector_EndSegment(string opName, long weighingRecordId)
{
if (!_curveCollectors.TryGetValue(opName, out CurveCollectorState state)) return;
CurveCollector_StopSampling(opName);
if (!state.IsSegmentEnded)
{
CurveCollector_SavePointValues(state, weighingRecordId, state.StableWeight, state.StableStartSeq, state.StableEndSeq); // 补写称重记录ID和算法依据
state.IsSegmentEnded = true;
WeighingPage?.AppendLog($"[{opName}] 实时重量曲线采集结束记录ID={state.CurveId},点数={state.SeqNo}");
}
_curveCollectors.TryRemove(opName, out _);
}
/// <summary>
/// 优先用曲线点计算稳定重量失败时调用方继续使用PLC锁定重量。
/// </summary>
private bool CurveCollector_TryGetStableWeight(string opName, out decimal weight, out int pointCount, out string msg)
{
weight = 0;
pointCount = 0;
msg = "未启动曲线采集";
if (!_curveCollectors.TryGetValue(opName, out CurveCollectorState state))
{
return false;
}
CurveCollector_SavePointValues(state, 0, null); // 确保数据库已有当前点位字符串
if (!B_DB_Opera.CurveRecord_QueryLatest(opName, state.ProductNo, out string pointValues, out _))
{
msg = "未查询到当前产品曲线记录";
return false;
}
List<decimal> weights = CurveCollector_ParsePointValues(pointValues); // 从数据库点位字符串还原曲线
pointCount = weights.Count;
if (CurveCollector_CalculateStableWeight(weights, out weight, out int startSeq, out int endSeq))
{
state.StableWeight = weight;
state.StableStartSeq = startSeq; // 记录最终采用窗口起点
state.StableEndSeq = endSeq; // 记录最终采用窗口终点
msg = "";
return true;
}
msg = $"曲线点数不足或无法计算,当前点数={pointCount}";
return false;
}
private List<CurveSamplePoint> CurveCollector_GetPoints(CurveCollectorState state)
{
lock (state.BufferLock)
{
return state.Buffer.OrderBy(p => p.SeqNo).ToList();
}
}
private string CurveCollector_BuildPointValues(List<CurveSamplePoint> points)
{
return string.Join("|", points.Select(p => p.Weight.ToString("F3", CultureInfo.InvariantCulture)));
}
private void CurveCollector_SavePointValues(CurveCollectorState state, long weighingRecordId, decimal? finalWeight)
{
CurveCollector_SavePointValues(state, weighingRecordId, finalWeight, null, null);
}
private void CurveCollector_SavePointValues(CurveCollectorState state, long weighingRecordId,
decimal? finalWeight, int? finalStartSeq, int? finalEndSeq)
{
if (state.IsPointValuesSaved && weighingRecordId <= 0 && !finalWeight.HasValue) return;
List<CurveSamplePoint> points = CurveCollector_GetPoints(state);
string pointValues = CurveCollector_BuildPointValues(points); // 例50.120|50.115|50.118
B_DB_Opera.CurveRecord_Save(state.CurveId, weighingRecordId, points.Count, pointValues, finalWeight, finalStartSeq, finalEndSeq);
state.IsPointValuesSaved = true;
}
private List<decimal> CurveCollector_ParsePointValues(string pointValues)
{
var result = new List<decimal>();
if (string.IsNullOrWhiteSpace(pointValues)) return result;
foreach (string item in pointValues.Split('|'))
{
if (decimal.TryParse(item, NumberStyles.Number, CultureInfo.InvariantCulture, out decimal value))
{
result.Add(value);
}
}
return result;
}
/// <summary>
/// 找连续20点中极差最小的一段去掉一个最大值和一个最小值后取平均。
/// </summary>
private bool CurveCollector_CalculateStableWeight(List<decimal> weights, out decimal stableWeight, out int startSeq, out int endSeq)
{
stableWeight = 0;
startSeq = 0;
endSeq = 0;
if (weights == null || weights.Count < CurveStableWindowSize) return false;
int bestStart = 0; // 0-based窗口起点保存时转成1-based序号
decimal bestRange = decimal.MaxValue;
for (int i = 0; i <= weights.Count - CurveStableWindowSize; i++)
{
decimal min = weights[i];
decimal max = weights[i];
for (int j = i + 1; j < i + CurveStableWindowSize; j++)
{
if (weights[j] < min) min = weights[j];
if (weights[j] > max) max = weights[j];
}
decimal range = max - min;
if (range <= bestRange)
{
bestRange = range;
bestStart = i; // 极差相同时取靠后窗口,更接近保存时刻
}
}
List<decimal> window = weights.Skip(bestStart).Take(CurveStableWindowSize).ToList();
decimal windowMin = window.Min();
decimal windowMax = window.Max();
bool removedMin = false;
bool removedMax = false;
decimal sum = 0;
int count = 0;
foreach (decimal value in window)
{
if (!removedMin && value == windowMin)
{
removedMin = true;
continue;
}
if (!removedMax && value == windowMax)
{
removedMax = true; // 只剔除一个最大值,避免过度过滤
continue;
}
sum += value;
count++;
}
if (count <= 0) return false;
stableWeight = Math.Round(sum / count, 3, MidpointRounding.AwayFromZero); // 剩余18点平均值
startSeq = bestStart + 1; // 转为数据库可读的1-based序号
endSeq = bestStart + CurveStableWindowSize;
return true;
}
}
}