提交最新项目改动

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

@@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Threading.Tasks;
namespace MesWork
@@ -431,17 +430,17 @@ namespace MesWork
// ====================================================================
/// <summary>
/// 创建曲线记录(工件到位时调用)
/// 创建紧凑曲线记录,点位值在保存时一次性写入。
/// </summary>
/// <returns>段ID失败返回-1</returns>
public static long Curve_StartSegment(string opName, string engineNo, string modelNo)
public static long CurveRecord_Start(string opName, string productNo, string modelNo, int sampleIntervalMs)
{
var sqlParameter = new SqlParameter[] {
new SqlParameter("@工位号", opName),
new SqlParameter("@发动机号", engineNo),
new SqlParameter("@机型号", modelNo ?? (object)DBNull.Value)
new SqlParameter("@产品编号", productNo),
new SqlParameter("@机型号", modelNo ?? (object)DBNull.Value),
new SqlParameter("@采样间隔ms", sampleIntervalMs)
};
SqlOperation.ExecuteStoredProcedure("称重曲线_开始", sqlParameter, out DataTable dt, out string err);
SqlOperation.ExecuteStoredProcedure("称重曲线记录_开始", sqlParameter, out DataTable dt, out string err);
if (dt != null && dt.Rows.Count > 0 && dt.Rows[0]["result"].ToString() == "1")
{
return Convert.ToInt64(dt.Rows[0]["ID"]);
@@ -450,46 +449,43 @@ namespace MesWork
}
/// <summary>
/// 批量写入曲线采样点采集线程每攒够N条调用一次
/// 保存紧凑曲线点位字符串,并关联最终称重记录。
/// </summary>
/// <param name="segmentId">段ID</param>
/// <param name="points">采样点列表</param>
/// <returns>写入条数失败返回0</returns>
public static int Curve_WritePoints(long segmentId, List<CurveSamplePoint> points)
public static bool CurveRecord_Save(long curveId, long weighingRecordId, int sampleCount, string pointValues,
decimal? finalWeight, int? finalStartSeq, int? finalEndSeq)
{
if (points == null || points.Count == 0) return 0;
// 用 JSON 序列化避免小数区域格式和字符串拼接转义问题。
var json = JsonConvert.SerializeObject(points.Select(p => new
{
seq = p.SeqNo,
time = p.SampleTime.ToString("yyyy-MM-ddTHH:mm:ss.fff"),
weight = p.Weight
}));
var sqlParameter = new SqlParameter[] {
new SqlParameter("@ID", segmentId),
new SqlParameter("@采样数据", json)
new SqlParameter("@ID", curveId),
new SqlParameter("@称重记录ID", weighingRecordId > 0 ? (object)weighingRecordId : DBNull.Value),
new SqlParameter("@采样点数", sampleCount),
new SqlParameter("@点位值", string.IsNullOrEmpty(pointValues) ? (object)DBNull.Value : pointValues),
new SqlParameter("@最终重量", finalWeight.HasValue ? (object)finalWeight.Value : DBNull.Value),
new SqlParameter("@最终取点开始序号", finalStartSeq.HasValue ? (object)finalStartSeq.Value : DBNull.Value),
new SqlParameter("@最终取点结束序号", finalEndSeq.HasValue ? (object)finalEndSeq.Value : DBNull.Value)
};
SqlOperation.ExecuteStoredProcedure("称重曲线_批量写入", sqlParameter, out DataTable dt, out string err);
if (dt != null && dt.Rows.Count > 0 && dt.Rows[0]["result"].ToString() == "1")
{
return Convert.ToInt32(dt.Rows[0]["count"]);
}
return 0;
SqlOperation.ExecuteStoredProcedure("称重曲线记录_保存", sqlParameter, out DataTable dt, out string err);
return dt != null && dt.Rows.Count > 0 && dt.Rows[0]["result"].ToString() == "1";
}
/// <summary>
/// 结束曲线段请求保存时调用关联称重记录ID
/// 查询指定产品最新紧凑曲线记录。
/// </summary>
public static bool Curve_EndSegment(long segmentId, long weighingRecordId)
public static bool CurveRecord_QueryLatest(string opName, string productNo, out string pointValues, out decimal finalWeight)
{
pointValues = "";
finalWeight = 0;
var sqlParameter = new SqlParameter[] {
new SqlParameter("@段ID", segmentId),
new SqlParameter("@称重记录ID", weighingRecordId > 0 ? (object)weighingRecordId : DBNull.Value)
new SqlParameter("@工位号", opName),
new SqlParameter("@产品编号", productNo)
};
SqlOperation.ExecuteStoredProcedure("称重曲线_结束段", sqlParameter, out DataTable dt, out string err);
return dt != null && dt.Rows.Count > 0 && dt.Rows[0]["result"].ToString() == "1";
SqlOperation.ExecuteStoredProcedure("称重曲线记录_查询最新", sqlParameter, out DataTable dt, out string err);
if (dt != null && dt.Rows.Count > 0 && dt.Rows[0]["result"].ToString() == "1")
{
pointValues = dt.Rows[0]["点位值"]?.ToString() ?? "";
decimal.TryParse(dt.Rows[0]["最终重量"]?.ToString(), out finalWeight);
return true;
}
return false;
}
}

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;
}
}
}

View File

@@ -1,509 +1,383 @@
using ExternalDataSync;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Threading;
using DC_A95;
/// <summary>
///
/// </summary>
namespace MesWork
{
/// <summary>
/// 处理MIS逻辑
/// </summary>
public partial class MesWorkForm
{
/// <summary>
/// PLC信号变化处理入口
/// 当Bool型信号值变化时由框架自动触发
/// </summary>
/// <param name="e"></param>
private void MIS_Funtion(object e)
{
try
{
var msgEvent = (DeviceDriver_BasicData.CustomeEvetnArgs)e;
if (msgEvent.TagValue == null) return;
var tagID = msgEvent.TagID.ToString();
var opName = msgEvent.OpName.ToString();
var tagTypeCodeID = (int)msgEvent.TagTypeCodeID;
var tagTypeID = (EnumTagTypeID)msgEvent.TagTypeID;
var isFirstValue = msgEvent.IsFirstValue.ToString().ToLower();
var alarmLevel = msgEvent.ShaftID.ToString();
var alarmMsg = msgEvent.ItemName.ToString();
string tagValue;
switch (tagTypeID)
{
case EnumTagTypeID.BOOL:
tagValue = (Convert.ToInt32(msgEvent.TagValue)).ToString();
break;
default:
tagValue = msgEvent.TagValue.ToString();
break;
}
// ── 报警处理编码900~2000──
if (tagTypeCodeID >= 900 & tagTypeCodeID < 2000)
{
if (ValueT(isFirstValue))
{
//不处理第一次变化的值
return;
}
var opNameNew = opName.Replace("_Alarm", "");
if (tagValue == "1")
{
B_DB_Opera.Event_Alarm_Start(opNameNew, tagTypeCodeID, alarmMsg, alarmLevel, tagID);
}
else
{
B_DB_Opera.Event_Alarm_End(opNameNew, tagTypeCodeID, alarmMsg, alarmLevel, tagID);
}
}
// ── 信号分发 ──
switch (tagTypeCodeID)
{
case 2: // 工件到位 → 启动/停止实时重量曲线采集
if (tagValue == "1")
{
WeighingPage?.AppendLog($"[{opName}] 工件到位信号触发");
CurveCollector_Start(opName);
}
else
{
CurveCollector_Stop(opName);
}
break;
case 14: // PLC心跳 → 回写PC心跳
WritePLC_IF(15, opName, tagValue);
break;
case 44: // 请求工作
if (ValueT(isFirstValue)) return;
if (tagValue == "1")
{
WeighingPage?.AppendLog($"[{opName}] PLC请求工作开始处理...");
Weighing_HandleRequestWork(opName, tagID);
}
else
{
WritePLC_IF(66, opName, false); //允许工作
}
break;
case 19: // 请求保存
if (ValueT(isFirstValue)) return;
if (tagValue == "1")
{
CurveCollector_StopSampling(opName, true); // 先停止采样保存完成后再关联称重记录ID
WeighingPage?.AppendLog($"[{opName}] PLC请求保存数据...");
Weighing_HandleRequestSave(opName, tagID);
}
else
{
WritePLC_IF(20, opName, false); //保存完成
WritePLC_IF(21, opName, false); //合格标志
}
break;
case 203: // 设备状态
B_DB_Opera.Event_DeviceStatus_Change(opName, tagValue);
break;
default:
break;
}
}
catch (Exception err)
{ }
}
// ====================================================================
// 称重交互核心方法
// ====================================================================
/// <summary>
/// 获取称重类型:地面/放油前/放油后
/// Ground模式统一返回"地面"
/// Hanging模式OP10=放油前OP20=放油后
/// </summary>
private string GetWeighingType(string opName)
{
if (WeighingType == "Ground")
return "地面";
// Hanging模式
return opName == "OP10" ? "放油前" : "放油后";
}
/// <summary>
/// 处理请求工作信号 (tagTypeCodeID=44)
/// 流程:读取工件信息 → 查询参数 → 写过站记录 → 按类型处理称重记录 → 回写允许工作
/// </summary>
private void Weighing_HandleRequestWork(string opName, string tagID)
{
B_DB_Opera.SaveLog_Request(opName, Log_Type.ZK_MesHandler, "MIS_Function",
Log_FromAndTo.ZK, Log_FromAndTo.ZK, $"触发信号【44-请求工作】", out long AID);
try
{
// 1. 读取PLC工件信息
string engineNo = PLC_R.GetString_CleanGarbled(ReadPLC(10, opName)); // 总成编号
string modelNo = PLC_R.GetString_CleanGarbled(ReadPLC(11, opName)); // 机型号
string orderNo = PLC_R.GetString_CleanGarbled(ReadPLC(39, opName)); // 工单号
string palletNo = PLC_R.GetString_CleanGarbled(ReadPLC(80, opName)); // 托盘号
B_DB_Opera.SaveLog_Response($"【{opName}】读取工件:发动机={engineNo},机型={modelNo},工单={orderNo},托盘={palletNo}", AID);
// 推送工件信息到称重UI
int stIdx = WeighingPage?.GetStationIndex(opName) ?? 1;
WeighingPage?.UpdateStationInfo(stIdx, engineNo, modelNo, orderNo);
WeighingPage?.AppendLog($"[{opName}] 读取工件:{engineNo},机型={modelNo}");
// 2. 查询工件管理参数(加油量/抽油量/密度/残油量上下限)
if (!B_DB_Opera.QueryWorkpieceByModel(modelNo, out decimal addOilQty, out decimal extractOilQty, out decimal density,
out decimal residualOilUpperLimit, out decimal residualOilLowerLimit))
{
WritePLC_IF(112, opName, 3); // 报警代码=3机型错误
B_DB_Opera.SaveLog_Response($"【{opName}】工件管理中未找到机型[{modelNo}]的参数,发动机={engineNo}", AID);
WeighingPage?.AppendLog($"[{opName}] 机型[{modelNo}]未找到配置参数");
return;
}
// 3. 推送工件参数到UI
WeighingPage?.UpdateOilInfo(stIdx, addOilQty, extractOilQty, density, 0, 0);
// 4. 确定称重类型和工位名称
string weighType = GetWeighingType(opName); // 地面/放油前/放油后
string stationName = weighType; // 工位名称直接用称重类型名
// 4. 写过站记录所有类型统一INSERT一条到达时间=NOW
B_DB_Opera.InsertStationRecord(engineNo, modelNo, orderNo, palletNo,
opName, stationName, addOilQty, extractOilQty, density, Curr_UserName);
// 5. 按类型处理称重记录
if (weighType == "放油后")
{
// 空中放油后:查找最新放油前记录(无论是否完成,支持多次测量)
if (!B_DB_Opera.QueryWeighingBeforeOil(engineNo, out _, out decimal preWeight, out _, out _, out _))
{
// 写报警代码=3发动机号错误阻断流程
WritePLC_IF(112, opName, 3);
B_DB_Opera.SaveLog_Response($"【{opName}】未找到发动机[{engineNo}]的空中称重记录", AID);
WeighingPage?.AppendLog($"[{opName}] 未找到[{engineNo}]放油前记录,进站失败");
return;
}
else
{
B_DB_Opera.SaveLog_Response($"【{opName}】空中放油后准备完成,发动机={engineNo},机型={modelNo},放油前重量={preWeight}kg", AID);
}
}
else
{
// 地面 或 空中放油前:每次都新建称重记录(支持同一产品多次测量取最新)
string recordType = weighType == "地面" ? "地面" : "空中";
B_DB_Opera.InsertWeighingRecord(opName, recordType, engineNo, modelNo, orderNo, palletNo,
0, 0, addOilQty, extractOilQty, density, residualOilUpperLimit, residualOilLowerLimit, 0, 0, 0, 0,
opName, Curr_UserName, isComplete: 0);
B_DB_Opera.SaveLog_Response($"【{opName}】{weighType}准备完成,发动机={engineNo},机型={modelNo},残油量范围={residualOilLowerLimit:F4}-{residualOilUpperLimit:F4}L", AID);
}
// 6. 回写允许工作
WritePLC_IF(66, opName, true);
}
catch (Exception err)
{
WritePLC_IF(112, opName, 1); // 报警代码=1获取数据失败
B_DB_Opera.SaveLog_Response($"{opName}】请求工作处理失败:{err.Message}", AID);
}
}
/// <summary>
/// 处理请求保存信号 (tagTypeCodeID=19)
/// 流程:读取称重数据 → 更新过站记录 → 按类型处理称重记录 → 回写保存完成
/// </summary>
private void Weighing_HandleRequestSave(string opName, string tagID)
{
B_DB_Opera.SaveLog_Request(opName, Log_Type.ZK_MesHandler, "MIS_Function",
Log_FromAndTo.ZK, Log_FromAndTo.ZK, $"触发信号【19-请求保存】", out long AID);
try
{
// 1. 读取PLC数据
decimal weight = Convert.ToDecimal(ReadPLC(100140, opName)); // 重量
decimal waterContent = Convert.ToDecimal(ReadPLC(100180, opName)); // 水含量
string engineNo = PLC_R.GetString_CleanGarbled(ReadPLC(10, opName)); // 产品编号
long curveWeighingRecordId = 0;
// 2. 更新过站记录(离开时间+称重重量)
B_DB_Opera.UpdateStationComplete(engineNo, opName, weight);
// 3. 确定类型并处理
string weighType = GetWeighingType(opName);
decimal oilReleaseQty = 0;
decimal residualOilQty = 0;
int finalQualityFlag = 1;
string qualityMsg = "";
if (weighType == "地面")
{
// 地面:查最新记录 → 放油量=重量/密度 → 标记完成
B_DB_Opera.QueryLatestRecord(opName, engineNo,
out long recordId, out _, out decimal addOil, out decimal extractOil, out decimal dens);
curveWeighingRecordId = recordId;
oilReleaseQty = dens > 0 ? weight / dens : 0;
residualOilQty = addOil - extractOil - oilReleaseQty;
B_DB_Opera.UpdateWeighingComplete(recordId, "地面", opName,
weight, oilReleaseQty, residualOilQty, waterContent, out finalQualityFlag, out qualityMsg);
B_DB_Opera.SaveLog_Response(
$"【{opName}】地面保存完成:发动机={engineNo},重量={weight}kg放油量={oilReleaseQty:F4}L残油量={residualOilQty:F4}L合格标志={finalQualityFlag}{qualityMsg}", AID);
WeighingPage?.UpdateOilResult(WeighingPage?.GetStationIndex(opName) ?? 1, oilReleaseQty, residualOilQty);
WeighingPage?.AppendLog($"[{opName}] 保存完成:{engineNo},重量={weight:F2}kg放油量={oilReleaseQty:F4}L");
}
else if (weighType == "放油前")
{
// 空中放油前:查最新记录 → 仅写入进站重量,不标记完成
B_DB_Opera.QueryLatestRecord(opName, engineNo,
out long recordId, out _, out _, out _, out _);
curveWeighingRecordId = recordId;
B_DB_Opera.UpdateWeighingComplete(recordId, "空中放油前", opName,
weight, 0, 0, waterContent, out finalQualityFlag, out qualityMsg);
B_DB_Opera.SaveLog_Response(
$"【{opName}】空中放油前保存完成:发动机={engineNo},进站重量={weight}kg合格标志={finalQualityFlag}{qualityMsg}", AID);
WeighingPage?.AppendLog($"[{opName}] 放油前保存:{engineNo},重量={weight:F2}kg");
}
else // 放油后
{
// 空中放油后:查最新放油前记录(无论是否完成),计算放油量更新到该记录
if (B_DB_Opera.QueryWeighingBeforeOil(engineNo, out long recordId, out decimal preWeight,
out decimal addOil, out decimal extractOil, out decimal dens))
{
curveWeighingRecordId = recordId;
oilReleaseQty = dens > 0 ? (preWeight - weight) / dens : 0;
residualOilQty = addOil - extractOil - oilReleaseQty;
B_DB_Opera.UpdateWeighingComplete(recordId, "空中放油后", opName,
weight, oilReleaseQty, residualOilQty, waterContent, out finalQualityFlag, out qualityMsg);
B_DB_Opera.SaveLog_Response(
$"【{opName}】空中放油后保存完成:发动机={engineNo},放油前={preWeight}kg放油后={weight}kg放油量={oilReleaseQty:F4}L残油量={residualOilQty:F4}L合格标志={finalQualityFlag}{qualityMsg}", AID);
WeighingPage?.UpdateOilResult(WeighingPage?.GetStationIndex(opName) ?? 1, oilReleaseQty, residualOilQty);
WeighingPage?.AppendLog($"[{opName}] 放油后保存:{engineNo},放油量={oilReleaseQty:F4}L残油量={residualOilQty:F4}L");
}
else
{
// 找不到放油前记录可能直接进OP20创建一条放油后记录并标记完成
curveWeighingRecordId = B_DB_Opera.InsertWeighingRecord(opName, "空中放油后", engineNo, "", "", "",
0, weight, 0, 0, 0, 0, 0, 0, 0, 2, waterContent,
opName, Curr_UserName, isComplete: 1);
finalQualityFlag = 2;
qualityMsg = "未找到放油前记录";
B_DB_Opera.SaveLog_Response(
$"【{opName}】放油后保存(无放油前记录):发动机={engineNo},放油后重量={weight}kg", AID);
WeighingPage?.AppendLog($"[{opName}] ⚠️ 放油后保存:{engineNo}(无放油前记录,已新建)");
}
}
CurveCollector_EndSegment(opName, curveWeighingRecordId);
// TODO: MES接口数据上传接口对接后实现
if (finalQualityFlag == 2 && !string.IsNullOrWhiteSpace(qualityMsg))
{
WeighingPage?.AppendLog($"[{opName}] 判定不合格:{qualityMsg}");
}
// 4. 先回写上位机判定的合格标志,再回写保存完成
WritePLC_IF(21, opName, finalQualityFlag);
WritePLC_IF(20, opName, true);
}
catch (Exception err)
{
CurveCollector_EndSegment(opName, 0);
WritePLC_IF(112, opName, 2); // 报警代码=2保存失败
B_DB_Opera.SaveLog_Response($"【{opName}】保存处理失败:{err.Message}", AID);
WeighingPage?.AppendLog($"[{opName}] ❌ 保存异常:{err.Message}");
}
}
// ====================================================================
// 扫码枪业务处理
// ====================================================================
/// <summary>
/// 扫码枪扫码完成后的业务处理
/// 将码值写入PLC地址102500触发完成信号100016=11秒后复位
/// </summary>
/// <param name="opName">工位号OP10/OP20由COM口绑定决定</param>
/// <param name="barcode">扫到的条码值</param>
public void Barcode_HandleScan(string opName, string barcode)
{
B_DB_Opera.SaveLog_Request(opName, Log_Type.ZK_MesHandler, "MIS_Function",
Log_FromAndTo.ZK, Log_FromAndTo.ZK, $"扫码枪触发【{opName}】码值={barcode}", out long AID);
try
{
// 1. 写入码值到 PLC 地址 102500PLC_PC_扫码枪值
WritePLC_IF(102500, opName, barcode);
// 2. 写入扫码完成信号 100016 = 1PC_PLC_扫码枪扫码完成
WritePLC_IF(100016, opName, true);
// 3. 日志
B_DB_Opera.SaveLog_Response($"【{opName}】扫码完成:码值={barcode}", AID);
WeighingPage?.AppendLog($"[{opName}] 触发扫码 码值:{barcode}");
// 4. 1秒后复位 100016 = 0
System.Threading.Tasks.Task.Delay(1000).ContinueWith(_ =>
{
try { WritePLC_IF(100016, opName, false); }
catch { }
});
}
catch (Exception err)
{
B_DB_Opera.SaveLog_Response($"【{opName}】扫码处理失败:{err.Message}", AID);
WeighingPage?.AppendLog($"[{opName}] ❌ 扫码处理异常:{err.Message}");
}
}
private class CurveCollectorState
{
public string OpName;
public long SegmentId;
public Thread WorkerThread;
public volatile bool IsRunning;
public bool IsSegmentEnded;
public bool IsWaitingSave;
public int SeqNo;
public readonly object BufferLock = new object();
public readonly List<CurveSamplePoint> Buffer = new List<CurveSamplePoint>();
}
private static readonly ConcurrentDictionary<string, CurveCollectorState> _curveCollectors
= new ConcurrentDictionary<string, CurveCollectorState>(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// 工件到位后启动实时重量曲线采集。
/// </summary>
private void CurveCollector_Start(string opName)
{
try
{
if (_curveCollectors.TryGetValue(opName, out CurveCollectorState oldState) && !oldState.IsSegmentEnded)
{
if (oldState.IsRunning) return;
CurveCollector_EndSegment(opName, 0);
}
string engineNo = PLC_R.GetString_CleanGarbled(ReadPLC(10, opName));
string modelNo = PLC_R.GetString_CleanGarbled(ReadPLC(11, opName));
long segmentId = B_DB_Opera.Curve_StartSegment(opName, engineNo, modelNo);
if (segmentId <= 0)
{
WeighingPage?.AppendLog($"[{opName}] 曲线段创建失败,未启动采集");
return;
}
var state = new CurveCollectorState
{
OpName = opName,
SegmentId = segmentId,
IsRunning = true
};
state.WorkerThread = new Thread(() => CurveCollector_Work(state))
{
IsBackground = true,
Name = $"CurveCollector_{opName}"
};
_curveCollectors[opName] = state;
state.WorkerThread.Start();
WeighingPage?.AppendLog($"[{opName}] 实时重量曲线采集启动段ID={segmentId}");
}
catch (Exception err)
{
WeighingPage?.AppendLog($"[{opName}] 曲线采集启动异常:{err.Message}");
}
}
/// <summary>
/// 采集线程200ms读取一次实时重量满10条批量写库。
/// </summary>
private void CurveCollector_Work(CurveCollectorState state)
{
while (state.IsRunning)
{
try
{
decimal weight = Convert.ToDecimal(ReadPLC(100220, state.OpName));
List<CurveSamplePoint> writePoints = null;
lock (state.BufferLock)
{
state.Buffer.Add(new CurveSamplePoint
{
SeqNo = ++state.SeqNo,
SampleTime = DateTime.Now,
Weight = weight
});
if (state.Buffer.Count >= 10)
{
writePoints = new List<CurveSamplePoint>(state.Buffer);
state.Buffer.Clear();
}
}
if (writePoints != null)
{
B_DB_Opera.Curve_WritePoints(state.SegmentId, writePoints);
}
}
catch (Exception err)
{
WeighingPage?.AppendLog($"[{state.OpName}] 曲线采样异常:{err.Message}");
}
Thread.Sleep(200);
}
CurveCollector_Flush(state);
}
/// <summary>
/// 停止采样线程并刷出剩余点,但暂不关闭曲线段。
/// </summary>
private void CurveCollector_StopSampling(string opName, bool waitingSave = false)
{
if (!_curveCollectors.TryGetValue(opName, out CurveCollectorState state)) return;
if (waitingSave)
{
state.IsWaitingSave = true;
}
state.IsRunning = false;
if (state.WorkerThread != null && state.WorkerThread.IsAlive)
{
state.WorkerThread.Join(2000);
}
CurveCollector_Flush(state);
}
/// <summary>
/// 工件离开时停止采集并关闭曲线段。
/// </summary>
private void CurveCollector_Stop(string opName)
{
if (_curveCollectors.TryGetValue(opName, out CurveCollectorState state) && state.IsWaitingSave)
{
return;
}
CurveCollector_EndSegment(opName, 0);
}
/// <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)
{
B_DB_Opera.Curve_EndSegment(state.SegmentId, weighingRecordId);
state.IsSegmentEnded = true;
WeighingPage?.AppendLog($"[{opName}] 实时重量曲线采集结束段ID={state.SegmentId}");
}
_curveCollectors.TryRemove(opName, out _);
}
/// <summary>
/// 将采集缓冲区剩余点写入数据库。
/// </summary>
private void CurveCollector_Flush(CurveCollectorState state)
{
List<CurveSamplePoint> writePoints = null;
lock (state.BufferLock)
{
if (state.Buffer.Count > 0)
{
writePoints = new List<CurveSamplePoint>(state.Buffer);
state.Buffer.Clear();
}
}
if (writePoints != null)
{
B_DB_Opera.Curve_WritePoints(state.SegmentId, writePoints);
}
}
}
}
using ExternalDataSync;
using System;
using DC_A95;
/// <summary>
///
/// </summary>
namespace MesWork
{
/// <summary>
/// 处理MIS逻辑
/// </summary>
public partial class MesWorkForm
{
/// <summary>
/// PLC信号变化处理入口
/// 当Bool型信号值变化时由框架自动触发
/// </summary>
/// <param name="e"></param>
private void MIS_Funtion(object e)
{
try
{
var msgEvent = (DeviceDriver_BasicData.CustomeEvetnArgs)e;
if (msgEvent.TagValue == null) return;
var tagID = msgEvent.TagID.ToString();
var opName = msgEvent.OpName.ToString();
var tagTypeCodeID = (int)msgEvent.TagTypeCodeID;
var tagTypeID = (EnumTagTypeID)msgEvent.TagTypeID;
var isFirstValue = msgEvent.IsFirstValue.ToString().ToLower();
var alarmLevel = msgEvent.ShaftID.ToString();
var alarmMsg = msgEvent.ItemName.ToString();
string tagValue;
switch (tagTypeID)
{
case EnumTagTypeID.BOOL:
tagValue = (Convert.ToInt32(msgEvent.TagValue)).ToString();
break;
default:
tagValue = msgEvent.TagValue.ToString();
break;
}
// ── 报警处理编码900~2000──
if (tagTypeCodeID >= 900 & tagTypeCodeID < 2000)
{
if (ValueT(isFirstValue))
{
//不处理第一次变化的值
return;
}
var opNameNew = opName.Replace("_Alarm", "");
if (tagValue == "1")
{
B_DB_Opera.Event_Alarm_Start(opNameNew, tagTypeCodeID, alarmMsg, alarmLevel, tagID);
}
else
{
B_DB_Opera.Event_Alarm_End(opNameNew, tagTypeCodeID, alarmMsg, alarmLevel, tagID);
}
}
// ── 信号分发 ──
switch (tagTypeCodeID)
{
case 2: // 工件到位/离开
WritePLC_IF(112, opName, 0); // 报警代码清空
break;
case 14: // PLC心跳 → 回写PC心跳
WritePLC_IF(15, opName, tagValue);
break;
case 44: // 请求工作
if (ValueT(isFirstValue)) return;
if (tagValue == "1")
{
WeighingPage?.AppendLog($"[{opName}] PLC请求工作开始处理...");
Weighing_HandleRequestWork(opName, tagID);
}
else
{
WritePLC_IF(66, opName, false); //允许工作
}
break;
case 19: // 请求保存
if (ValueT(isFirstValue)) return;
if (tagValue == "1")
{
CurveCollector_StopSampling(opName, true); // 先停止采样保存完成后再关联称重记录ID
WeighingPage?.AppendLog($"[{opName}] PLC请求保存数据...");
Weighing_HandleRequestSave(opName, tagID);
}
else
{
WritePLC_IF(20, opName, false); //保存完成
WritePLC_IF(21, opName, false); //合格标志
}
break;
case 203: // 设备状态
B_DB_Opera.Event_DeviceStatus_Change(opName, tagValue);
break;
default:
break;
}
}
catch (Exception err)
{
string errMsg = $"PLC信号处理异常{err.Message}";
B_DB_Opera.SaveLog_Request("", Log_Type.ZK_MesHandler, "MIS_Function",
Log_FromAndTo.ZK, Log_FromAndTo.ZK, errMsg, out long AID);
B_DB_Opera.SaveLog_Response(errMsg, AID);
WeighingPage?.AppendLog(errMsg);
}
}
// ====================================================================
// 称重交互核心方法
// ====================================================================
/// <summary>
/// 获取称重类型:地面/放油前/放油后
/// Ground模式统一返回"地面"
/// Hanging模式OP10=放油前OP20=放油后
/// </summary>
private string GetWeighingType(string opName)
{
if (WeighingType == "Ground")
return "地面";
// Hanging模式
return opName == "OP10" ? "放油前" : "放油后";
}
/// <summary>
/// 码块数据转换:将产品编号统一转大写,并拆分出订货号与产品编号
/// 支持格式:
/// 0/DHN2.5Q0219-36/D426E016767
/// DHH02K0014-400/B726E00584555
/// </summary>
private bool TryConvertCodeBlockData(string codeBlock, out string orderNo, out string productNo)
{
orderNo = "";
productNo = "";
if (string.IsNullOrWhiteSpace(codeBlock))
{
return false;
}
string normalizedCode = codeBlock.Trim().ToUpperInvariant();
string[] parts = normalizedCode.Split('/');
if (parts.Length >= 2)
{
orderNo = parts[parts.Length - 2].Trim();
productNo = parts[parts.Length - 1].Trim();
}
else
{
productNo = normalizedCode;
}
return !string.IsNullOrWhiteSpace(productNo);
}
/// <summary>
/// 处理请求工作信号 (tagTypeCodeID=44)
/// 流程:读取工件信息 → 查询参数 → 写过站记录 → 按类型处理称重记录 → 回写允许工作
/// </summary>
private void Weighing_HandleRequestWork(string opName, string tagID)
{
B_DB_Opera.SaveLog_Request(opName, Log_Type.ZK_MesHandler, "MIS_Function",
Log_FromAndTo.ZK, Log_FromAndTo.ZK, $"触发信号【44-请求工作】", out long AID);
try
{
string engineNo = PLC_R.GetString_CleanGarbled(ReadPLC(10, opName)); // 原始代码串 0/dhn2.5q0219-36/d426e016767
string modelNo = PLC_R.GetString_CleanGarbled(ReadPLC(11, opName)); // 机型号
string orderNo = PLC_R.GetString_CleanGarbled(ReadPLC(39, opName)); // 工单号
string palletNo = PLC_R.GetString_CleanGarbled(ReadPLC(80, opName)); // 托盘号
modelNo = "WP7";
// 解析代码串格式: 0/DHN2.5Q0219-36/D426E016767 或 DHH02K0014-400/B726E00584555
if (TryConvertCodeBlockData(engineNo, out string parsedOrderNo, out string parsedProductNo))
{
if (!string.IsNullOrWhiteSpace(parsedOrderNo))
{
orderNo = parsedOrderNo;
}
engineNo = parsedProductNo;
B_DB_Opera.SaveLog_Response($"【{opName}】读取工件:发动机={engineNo},机型={modelNo},工单={orderNo},托盘={palletNo}", AID);
int stIdx = WeighingPage?.GetStationIndex(opName) ?? 1;
WeighingPage?.UpdateStationInfo(stIdx, engineNo, modelNo, orderNo);
WeighingPage?.AppendLog($"[{opName}] 读取工件:{engineNo},机型={modelNo}");
if (!B_DB_Opera.QueryWorkpieceByModel(modelNo, out decimal addOilQty, out decimal extractOilQty, out decimal density,
out decimal residualOilUpperLimit, out decimal residualOilLowerLimit))
{
WritePLC_IF(112, opName, 3); // 报警代码=3机型错误
B_DB_Opera.SaveLog_Response($"【{opName}】工件管理中未找到机型[{modelNo}]的参数,发动机={engineNo}", AID);
WeighingPage?.AppendLog($"[{opName}] 机型[{modelNo}]未找到配置参数");
return;
}
WeighingPage?.UpdateOilInfo(stIdx, addOilQty, extractOilQty, density, 0, 0);
string weighType = GetWeighingType(opName); // 地面/放油前/放油后
string stationName = weighType; // 工位名称直接用称重类型名
B_DB_Opera.InsertStationRecord(engineNo, modelNo, orderNo, palletNo,
opName, stationName, addOilQty, extractOilQty, density, Curr_UserName); // 请求工作成功先写到站记录
if (weighType == "放油后")
{
// 空中放油后:查找最新放油前记录(无论是否完成,支持多次测量)
if (!B_DB_Opera.QueryWeighingBeforeOil(engineNo, out _, out decimal preWeight, out _, out _, out _))
{
WritePLC_IF(112, opName, 3);
B_DB_Opera.SaveLog_Response($"【{opName}】未找到发动机[{engineNo}]的空中称重记录", AID);
WeighingPage?.AppendLog($"[{opName}] 未找到[{engineNo}]放油前记录,进站失败");
return;
}
else
{
B_DB_Opera.SaveLog_Response($"【{opName}】空中放油后准备完成,发动机={engineNo},机型={modelNo},放油前重量={preWeight}kg", AID);
}
}
else
{
// 地面 或 空中放油前:每次都新建称重记录(支持同一产品多次测量取最新)
string recordType = weighType == "地面" ? "地面" : "空中";
B_DB_Opera.InsertWeighingRecord(opName, recordType, engineNo, modelNo, orderNo, palletNo,
0, 0, addOilQty, extractOilQty, density, residualOilUpperLimit, residualOilLowerLimit, 0, 0, 0, 0,
opName, Curr_UserName, isComplete: 0); // 放油前/地面先占位,保存时回填重量结果
B_DB_Opera.SaveLog_Response($"【{opName}】{weighType}准备完成,发动机={engineNo},机型={modelNo},残油量范围={residualOilLowerLimit:F4}-{residualOilUpperLimit:F4}L", AID);
}
WritePLC_IF(66, opName, true);
CurveCollector_Start(opName, engineNo, modelNo); // 允许工作后开始记录本次曲线
}
else
{
WritePLC_IF(112, opName, 1); // 报警代码=1获取数据失败
B_DB_Opera.SaveLog_Response($"【{opName}】码块数据转换失败:原始码值=[{engineNo}]", AID);
WeighingPage?.AppendLog($"[{opName}] 码块数据转换失败:{engineNo}");
}
}
catch (Exception err)
{
WritePLC_IF(112, opName, 1); // 报警代码=1获取数据失败
B_DB_Opera.SaveLog_Response($"【{opName}】请求工作处理失败:{err.Message}", AID);
}
}
/// <summary>
/// 处理请求保存信号 (tagTypeCodeID=19)
/// 流程:读取称重数据 → 更新过站记录 → 按类型处理称重记录 → 回写保存完成
/// </summary>
private void Weighing_HandleRequestSave(string opName, string tagID)
{
B_DB_Opera.SaveLog_Request(opName, Log_Type.ZK_MesHandler, "MIS_Function",
Log_FromAndTo.ZK, Log_FromAndTo.ZK, $"触发信号【19-请求保存】", out long AID);
try
{
decimal plcWeight = Convert.ToDecimal(ReadPLC(100140, opName)); // PLC锁定重量曲线无效时兜底使用
decimal weight = plcWeight; // 默认使用PLC锁定重量曲线算法成功后覆盖
decimal waterContent = Convert.ToDecimal(ReadPLC(100180, opName)); // 水含量
string engineNo = PLC_R.GetString_CleanGarbled(ReadPLC(10, opName)); // 产品编号
if (TryConvertCodeBlockData(engineNo, out _, out string parsedProductNo))
{
engineNo = parsedProductNo;
}
if (CurveCollector_TryGetStableWeight(opName, out decimal curveWeight, out int curvePointCount, out string curveMsg))
{
weight = curveWeight; // 优先使用曲线稳定段计算重量
B_DB_Opera.SaveLog_Response($"【{opName}】曲线重量={curveWeight:F3}kgPLC重量={plcWeight:F3}kg差值={(curveWeight - plcWeight):F3}kg点数={curvePointCount}", AID);
}
else
{
B_DB_Opera.SaveLog_Response($"【{opName}】曲线重量不可用使用PLC锁定重量={plcWeight:F3}kg原因={curveMsg}", AID);
}
long curveWeighingRecordId = 0;
B_DB_Opera.UpdateStationComplete(engineNo, opName, weight); // 过站记录保存最终采用重量
string weighType = GetWeighingType(opName);
decimal oilReleaseQty = 0;
decimal residualOilQty = 0;
int finalQualityFlag = 1;
string qualityMsg = "";
if (weighType == "地面")
{
// 地面:查最新记录 → 放油量=重量/密度 → 标记完成
B_DB_Opera.QueryLatestRecord(opName, engineNo,
out long recordId, out _, out decimal addOil, out decimal extractOil, out decimal dens);
curveWeighingRecordId = recordId; // 曲线记录最终关联这条称重记录
oilReleaseQty = dens > 0 ? weight / dens : 0; // 地面:油桶重量/密度=放油量
residualOilQty = addOil - extractOil - oilReleaseQty; // 残油=加油-抽油-放油
B_DB_Opera.UpdateWeighingComplete(recordId, "地面", opName,
weight, oilReleaseQty, residualOilQty, waterContent, out finalQualityFlag, out qualityMsg);
B_DB_Opera.SaveLog_Response(
$"【{opName}】地面保存完成:发动机={engineNo},重量={weight}kg放油量={oilReleaseQty:F4}L残油量={residualOilQty:F4}L合格标志={finalQualityFlag}{qualityMsg}", AID);
WeighingPage?.UpdateOilResult(WeighingPage?.GetStationIndex(opName) ?? 1, oilReleaseQty, residualOilQty);
WeighingPage?.AppendLog($"[{opName}] 保存完成:{engineNo},重量={weight:F2}kg放油量={oilReleaseQty:F4}L");
}
else if (weighType == "放油前")
{
// 空中放油前:查最新记录 → 仅写入进站重量,不标记完成
B_DB_Opera.QueryLatestRecord(opName, engineNo,
out long recordId, out _, out _, out _, out _);
curveWeighingRecordId = recordId; // 放油前曲线也关联同一条称重记录
B_DB_Opera.UpdateWeighingComplete(recordId, "空中放油前", opName,
weight, 0, 0, waterContent, out finalQualityFlag, out qualityMsg);
B_DB_Opera.SaveLog_Response(
$"【{opName}】空中放油前保存完成:发动机={engineNo},进站重量={weight}kg合格标志={finalQualityFlag}{qualityMsg}", AID);
WeighingPage?.AppendLog($"[{opName}] 放油前保存:{engineNo},重量={weight:F2}kg");
}
else // 放油后
{
// 空中放油后:查最新放油前记录(无论是否完成),计算放油量更新到该记录
if (B_DB_Opera.QueryWeighingBeforeOil(engineNo, out long recordId, out decimal preWeight,
out decimal addOil, out decimal extractOil, out decimal dens))
{
curveWeighingRecordId = recordId; // 放油后回填放油前创建的称重记录
oilReleaseQty = dens > 0 ? (preWeight - weight) / dens : 0; // 空中:前后重量差/密度=放油量
residualOilQty = addOil - extractOil - oilReleaseQty; // 残油=加油-抽油-放油
B_DB_Opera.UpdateWeighingComplete(recordId, "空中放油后", opName,
weight, oilReleaseQty, residualOilQty, waterContent, out finalQualityFlag, out qualityMsg);
B_DB_Opera.SaveLog_Response(
$"【{opName}】空中放油后保存完成:发动机={engineNo},放油前={preWeight}kg放油后={weight}kg放油量={oilReleaseQty:F4}L残油量={residualOilQty:F4}L合格标志={finalQualityFlag}{qualityMsg}", AID);
WeighingPage?.UpdateOilResult(WeighingPage?.GetStationIndex(opName) ?? 1, oilReleaseQty, residualOilQty);
WeighingPage?.AppendLog($"[{opName}] 放油后保存:{engineNo},放油量={oilReleaseQty:F4}L残油量={residualOilQty:F4}L");
}
else
{
// 找不到放油前记录可能直接进OP20创建一条放油后记录并标记完成
curveWeighingRecordId = B_DB_Opera.InsertWeighingRecord(opName, "空中放油后", engineNo, "", "", "",
0, weight, 0, 0, 0, 0, 0, 0, 0, 2, waterContent,
opName, Curr_UserName, isComplete: 1); // 异常兜底记录,质量直接判不合格
finalQualityFlag = 2;
qualityMsg = "未找到放油前记录";
B_DB_Opera.SaveLog_Response(
$"{opName}】放油后保存(无放油前记录):发动机={engineNo},放油后重量={weight}kg", AID);
WeighingPage?.AppendLog($"[{opName}] ⚠️ 放油后保存:{engineNo}(无放油前记录,已新建)");
}
}
CurveCollector_EndSegment(opName, curveWeighingRecordId); // 保存点位、最终重量和取点区间
// TODO: MES接口数据上传接口对接后实现
if (finalQualityFlag == 2 && !string.IsNullOrWhiteSpace(qualityMsg))
{
WeighingPage?.AppendLog($"[{opName}] 判定不合格:{qualityMsg}");
}
// 4. 先回写上位机判定的合格标志,再回写保存完成
WritePLC_IF(21, opName, finalQualityFlag); // 合格标志
WritePLC_IF(20, opName, true); // 保存完成
}
catch (Exception err)
{
CurveCollector_EndSegment(opName, 0);
WritePLC_IF(112, opName, 2); // 报警代码=2保存失败
B_DB_Opera.SaveLog_Response($"【{opName}】保存处理失败:{err.Message}", AID);
WeighingPage?.AppendLog($"[{opName}] ❌ 保存异常:{err.Message}");
}
}
// ====================================================================
// 扫码枪业务处理
// ====================================================================
/// <summary>
/// 扫码枪扫码完成后的业务处理
/// 将码值写入PLC地址102500触发完成信号100016=11秒后复位
/// </summary>
/// <param name="opName">工位号OP10/OP20由COM口绑定决定</param>
/// <param name="barcode">扫到的条码值</param>
public void Barcode_HandleScan(string opName, string barcode)
{
B_DB_Opera.SaveLog_Request(opName, Log_Type.ZK_MesHandler, "MIS_Function",
Log_FromAndTo.ZK, Log_FromAndTo.ZK, $"扫码枪触发【{opName}】码值={barcode}", out long AID);
try
{
// 1. 写入码值到 PLC 地址 102500PLC_PC_扫码枪值
WritePLC_IF(102500, opName, barcode);
// 2. 写入扫码完成信号 100016 = 1PC_PLC_扫码枪扫码完成
WritePLC_IF(100016, opName, true);
// 3. 日志
B_DB_Opera.SaveLog_Response($"【{opName}】扫码完成:码值={barcode}", AID);
WeighingPage?.AppendLog($"[{opName}] 触发扫码 码值:{barcode}");
// 4. 1秒后复位 100016 = 0
System.Threading.Tasks.Task.Delay(1000).ContinueWith(_ =>
{
try { WritePLC_IF(100016, opName, false); }
catch { }
});
}
catch (Exception err)
{
B_DB_Opera.SaveLog_Response($"【{opName}】扫码处理失败:{err.Message}", AID);
WeighingPage?.AppendLog($"[{opName}] ❌ 扫码处理异常:{err.Message}");
}
}
}
}