Files
FATrace/FATrace.WPLApp/Services/DataServices.cs
2025-11-11 17:26:31 +08:00

555 lines
20 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using FATrace.Model;
using FATrace.WPLApp.Models;
using HslCommunication;
using HslCommunication.Profinet.Keyence;
using Prism.Mvvm;
using System.Collections.Concurrent;
using System.Windows;
using ComConfigHelper = FATrace.Com.ConfigHelper;
namespace FATrace.WPLApp.Services
{
/// <summary>
/// 数据服务
/// 产线PLC数据交互的服务
/// </summary>
public class DataServices : BindableBase, IDisposable
{
public DataServices(ILogService logService, IFreeSql freeSql)
{
LogService = logService;
FreeSql = freeSql;
LineSglModel = new LineSglModel();
LineSglModel.WeightScanCodeHandle += LineSglModel_WeightScanCodeHandle;
LineSglModel.BoxSprayCodeReqHandle += LineSglModel_BoxSprayCodeReqHandle;
LineSglModel.BoxScanCodeReqHandle += LineSglModel_BoxScanCodeReqHandle;
PLCLinkInitial();
StartPlcScan();
//var DD= RevData("DYG05030013,20250923,802,3,01,0001,");
}
/// <summary>
///外箱喷码请求
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void LineSglModel_BoxSprayCodeReqHandle(object? sender, string e)
{
//首先复位PLC信号
KeyencePlcMcNet!.Write("D1100", (Int16)0);
//获取箱子喷码的数据+A
var ReqData = FreeSql.Select<LineTempCode>()
.OrderBy(a => a.Id)
.Limit(1)
.ToList();
if (ReqData.Count > 0)
{
var CodeItem = ReqData.FirstOrDefault();
KeyencePlcMcNet.Write("D1150", RevData(CodeItem!.Code + ",A"));
Console.WriteLine($"外箱喷码:{CodeItem.Code}-发送OK");
//这里需要删除数据吗?还是等扫码后删除,会不会过来新的箱子
}
else
{
//不存在请求的数据,报错
KeyencePlcMcNet.Write("D1102", (Int16)1);
Console.WriteLine($"外箱喷码:不存在请求的数据");
}
Task.Run(async () =>
{
await Task.Delay(1000).ConfigureAwait(false);
KeyencePlcMcNet!.Write("D1102", (Int16)0);
});
}
/// <summary>
/// 箱子扫描码请求
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <exception cref="NotImplementedException"></exception>
private void LineSglModel_BoxScanCodeReqHandle(object? sender, string e)
{
//首先复位PLC信号
KeyencePlcMcNet!.Write("D1200", (Int16)0);
if (!string.IsNullOrEmpty(BoxScanCode))
{
//var IsExist = FreeSql.Select<RawProUse>().Where(a => a.BoxCode == BoxScanCode);
var IsExist = FreeSql.Select<RawProUse>().Where(a => a.BoxCode!.Contains(BoxScanCode));// 内包和外包一样的条码测试用 ???????????????
if (IsExist.Count() > 0)
{
//存在条码数据 OK返回PLC结果
KeyencePlcMcNet!.Write("D1210", (Int16)1);
Console.WriteLine($"外箱扫描码:{BoxScanCode}-存在找到");
//// 加入队列
//EnqueueMessage(WeightScanCode);
LogService.Info($"外箱扫描码:{BoxScanCode}");
//删除临时的队列条码数据
var DeleteResult = FreeSql.Delete<LineTempCode>()
.Where(p => BoxScanCode.Contains(p.Code!))
.ExecuteAffrows();
if (DeleteResult > 0)
{
Console.WriteLine($"外箱扫描码:{WeightScanCode}-删除临时队列数据成功");
LogService.Info($"外箱扫描码:{WeightScanCode}-删除临时队列数据成功");
}
else
{
Console.WriteLine($"外箱扫描码:{WeightScanCode}-删除临时队列数据失败");
LogService.Info($"外箱扫描码:{WeightScanCode}-删除临时队列数据失败");
}
var Result = FreeSql.Update<RawProUse>()
.Set(p => p.OutTime, DateTime.Now)
.Where(p => p.BoxCode == BoxScanCode+",A")//外箱二维码就是外箱扫描码
.ExecuteAffrows();
if (Result > 0)
{
Console.WriteLine($"外箱扫描码:{WeightScanCode}更新时间成功");
LogService.Info($"外箱扫描码:{WeightScanCode}更新时间成功");
}
else
{
Console.WriteLine($"外箱扫描码:{WeightScanCode}更新时间失败");
LogService.Warn($"外箱扫描码:{WeightScanCode}更新时间失败");
}
}
else
{
//不存在条码数据 NG返回PLC结果报警
KeyencePlcMcNet!.Write("D1210", (Int16)2);
Console.WriteLine($"外箱扫描码:{WeightScanCode}-未发现外箱扫码数据在数据库中");
LogService.Warn($"外箱扫描码:{WeightScanCode}-未发现外箱扫码数据在数据库中");
}
}
else
{
Console.WriteLine($"外箱扫描码为空");
LogService.Warn("外箱扫描码为空");
}
Task.Run(async () =>
{
await Task.Delay(1000).ConfigureAwait(false);
KeyencePlcMcNet!.Write("D1210", (Int16)0);
KeyencePlcMcNet.Write("D1250", new Int16[30]);
});
}
/// <summary>
/// 称重扫描码请求数据
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <exception cref="NotImplementedException"></exception>
private void LineSglModel_WeightScanCodeHandle(object? sender, string e)
{
//首先复位PLC信号
KeyencePlcMcNet!.Write("D1000", (Int16)0);
if (!string.IsNullOrEmpty(WeightScanCode))
{
var IsExist = FreeSql.Select<RawProUse>().Where(a => a.InBagCode == WeightScanCode);
if (IsExist.Count() > 0)
{
//存在条码数据 OK返回PLC结果
KeyencePlcMcNet!.Write("D1010", (Int16)1);
Console.WriteLine($"称重扫描码:{WeightScanCode}");
//// 加入队列
//EnqueueMessage(WeightScanCode);
LogService.Info($"称重扫描码:{WeightScanCode}");
var TempData = FreeSql.Insert<LineTempCode>(new LineTempCode()
{
Code = WeightScanCode
}).ExecuteAffrows();
var Result = FreeSql.Update<RawProUse>()
.Set(p => p.WeightScanTime, DateTime.Now)
.Where(p => p.InBagCode == WeightScanCode)//内箱二维码就是称重扫描码
.ExecuteAffrows();
if (Result > 0)
{
Console.WriteLine($"称重扫描码:{WeightScanCode}更新时间成功");
LogService.Info($"称重扫描码:{WeightScanCode}更新时间成功");
}
else
{
Console.WriteLine($"称重扫描码:{WeightScanCode}更新时间失败");
LogService.Warn($"称重扫描码:{WeightScanCode}更新时间失败");
}
}
else
{
//不存在条码数据 NG返回PLC结果报警
KeyencePlcMcNet!.Write("D1010", (Int16)2);
Console.WriteLine($"称重扫描码:{WeightScanCode}-未发现内包扫码数据在数据库中");
LogService.Warn($"称重扫描码:{WeightScanCode}-未发现内包扫码数据在数据库中");
}
Task.Run(async () =>
{
await Task.Delay(1000).ConfigureAwait(false);
KeyencePlcMcNet!.Write("D1010", (Int16)0);
KeyencePlcMcNet.Write("D1050", new Int16[30]);
});
}
else
{
Console.WriteLine($"称重扫描码为空");
LogService.Warn("称重扫描码为空");
}
}
public ILogService LogService { get; }
public IFreeSql FreeSql { get; }
//通信组件
private KeyenceMcNet KeyencePlcMcNet { get; set; } = null;
private string _WeightScanCode;
/// <summary>
/// 称重扫描码
/// </summary>
public string WeightScanCode
{
get { return _WeightScanCode; }
set { _WeightScanCode = value; RaisePropertyChanged(); }
}
private string _BoxScanCode;
/// <summary>
/// 外箱喷码扫码数据
/// </summary>
public string BoxScanCode
{
get { return _BoxScanCode; }
set { _BoxScanCode = value; RaisePropertyChanged(); }
}
// 扫描控制
private CancellationTokenSource? _plcScanCts;
private Task? _plcScanTask;
private volatile bool _isScanning = false;
public bool IsScanning => _isScanning;
private bool _disposed = false;
/// <summary>
/// 简单字符串队列FIFO
/// </summary>
private readonly ConcurrentQueue<string> _messageQueue = new();
/// <summary>
/// 入队:将字符串加入 FIFO 队列(先进先出)。允许空字符串,不允许 null。
/// </summary>
/// <param name="message">要加入队列的字符串</param>
/// <exception cref="ArgumentNullException">当 message 为 null 时抛出</exception>
public void EnqueueMessage(string message)
{
if (message is null) throw new ArgumentNullException(nameof(message));
_messageQueue.Enqueue(message);
}
/// <summary>
/// 出队:尝试从 FIFO 队列取出一个字符串。
/// </summary>
/// <param name="message">输出参数:若成功则为取出的字符串;若失败(队列为空)则为 default</param>
/// <returns>是否成功取出</returns>
public bool TryDequeueMessage(out string message)
{
return _messageQueue.TryDequeue(out message);
}
/// <summary>
/// 产线信号模型
/// </summary>
public LineSglModel LineSglModel { get; set; }
/// <summary>
/// PLC通信初始化
/// </summary>
/// <param name="PLCIp"></param>
/// <param name="Port"></param>
private void PLCLinkInitial()
{
try
{
// 读取 PLC 配置(大小写敏感,采用公共 ConfigHelper 并提供默认值)
string PLCIP = ComConfigHelper.GetStringOrDefault("PLCIP", "192.0.1.10");
int Port = ComConfigHelper.GetIntOrDefault("PLCPort", 5000);
//PLC通信的连接
KeyencePlcMcNet = new KeyenceMcNet();
KeyencePlcMcNet.IpAddress = PLCIP;
KeyencePlcMcNet.Port = Port;
KeyencePlcMcNet.ConnectClose();
KeyencePlcMcNet.ConnectTimeOut = 3000; // 连接3秒超时
OperateResult connect = KeyencePlcMcNet.ConnectServer();
if (connect.IsSuccess)//初始连接状态的显示判断
{
Console.WriteLine($"PLC-连接 OK");
}
else
{
MessageBox.Show(connect.Message + Environment.NewLine + "ErrorCode: " + connect.ErrorCode);
}
}
catch (Exception ex)
{
LogService.Error(String.Format("ErrSource : {0} ErrMsg : {1}", ex.StackTrace.ToString(), ex.Message.ToString()));
//insertLogToDBDelegate.BeginInvoke(1, "UpdateUIMethod异常", ex.Message.ToString() + ex.StackTrace.Substring(ex.StackTrace.Length - 40, 40), null, null);
}
}
/// <summary>
/// 启动PLC扫描。若已在扫描则忽略重复启动。
/// </summary>
/// <param name="scanPeriod">扫描周期,若为 null 则读取 App.config 的 PLCScan毫秒默认600ms</param>
/// <param name="externalToken">可选:外部取消令牌,用于联动取消</param>
public void StartPlcScan(TimeSpan? scanPeriod = null, CancellationToken externalToken = default)
{
if (_isScanning) return;
var period = scanPeriod ?? ComConfigHelper.GetTimeSpanOrDefault("PLCScan", TimeSpan.FromMilliseconds(600));
_plcScanCts = CancellationTokenSource.CreateLinkedTokenSource(externalToken);
var token = _plcScanCts.Token;
_isScanning = true;
_plcScanTask = Task.Run(() => PlcScanLoopAsync(period, token), token);
}
/// <summary>
/// 停止PLC扫描等待后台任务结束。
/// </summary>
public async Task StopPlcScanAsync()
{
try
{
if (_plcScanCts != null)
{
_plcScanCts.Cancel();
}
if (_plcScanTask != null)
{
try { await _plcScanTask.ConfigureAwait(false); } catch { /* ignore */ }
}
}
finally
{
_isScanning = false;
_plcScanTask = null;
_plcScanCts?.Dispose();
_plcScanCts = null;
}
}
/// <summary>
/// 称重扫描条码OperateResult结果
/// </summary>
private OperateResult<string> OperateResultWeightScanCode { get; set; }
/// <summary>
/// 称重扫描条码OK信号
/// </summary>
private OperateResult<Int16> OperateResultWeightScanSgl { get; set; }
/// <summary>
/// 外箱喷码请求信号
/// </summary>
private OperateResult<Int16> OperateResultBoxSprayCodeReqSgl { get; set; }
/// <summary>
/// 外箱扫码条码OperateResult结果
/// </summary>
private OperateResult<string> OperateResultBoxScanCode { get; set; }
/// <summary>
/// 外箱扫码请求信号
/// </summary>
private OperateResult<Int16> OperateResultBoxScanSgl { get; set; }
/// <summary>
/// 扫描循环按周期轮询PLC寄存器并更新模型。
/// - 读取配置项 Addr_WeightPhotoEnable默认 M100为布尔量
/// - 读失败时尝试重连一次
/// </summary>
private async Task PlcScanLoopAsync(TimeSpan period, CancellationToken token)
{
string addrWeight = ComConfigHelper.GetStringOrDefault("Addr_WeightPhotoEnable", "M100");
bool? lastWeight = null;
while (!token.IsCancellationRequested)
{
try
{
if (KeyencePlcMcNet == null)
{
PLCLinkInitial();
}
//内包扫码
OperateResultWeightScanCode = KeyencePlcMcNet!.ReadString("D1050", 20);
if (OperateResultWeightScanCode.IsSuccess)
{
WeightScanCode = RevData(OperateResultWeightScanCode.Content).Replace("\r", "").Replace("\n", "");
}
OperateResultWeightScanSgl = KeyencePlcMcNet!.ReadInt16("D1000");
if (OperateResultWeightScanSgl.IsSuccess)
{
LineSglModel.WeightScanCodeEnable = OperateResultWeightScanSgl.Content;
}
//外箱喷码
OperateResultBoxSprayCodeReqSgl = KeyencePlcMcNet!.ReadInt16("D1100");
if (OperateResultBoxSprayCodeReqSgl.IsSuccess)
{
LineSglModel.BoxSprayCodeReqEnable = OperateResultBoxSprayCodeReqSgl.Content;
}
//外箱扫码
OperateResultBoxScanCode = KeyencePlcMcNet!.ReadString("D1250", 20);
if (OperateResultBoxScanCode.IsSuccess)
{
BoxScanCode = RevData(OperateResultBoxScanCode.Content).Replace("\r", "").Replace("\n", "");
}
OperateResultBoxScanSgl = KeyencePlcMcNet!.ReadInt16("D1200");
if (OperateResultBoxScanSgl.IsSuccess)
{
LineSglModel.BoxScanCodeEnable = OperateResultBoxScanSgl.Content;
}
}
catch (OperationCanceledException)
{
break;
}
catch (Exception ex)
{
LogService.Error($"PLC 扫描异常: {ex}");
}
finally
{
try
{
await Task.Delay(period, token).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
// 不允许从 finally 中 break/return
// 这里吞掉异常,循环将在下一次 while 判断时依据 token 自动退出
}
}
}
_isScanning = false;
}
/// <summary>
/// 尝试重连 PLC快速一次
/// </summary>
private async Task TryReconnectAsync(CancellationToken token)
{
try
{
if (KeyencePlcMcNet == null) { PLCLinkInitial(); return; }
KeyencePlcMcNet.ConnectClose();
await Task.Delay(100, token).ConfigureAwait(false);
KeyencePlcMcNet.ConnectTimeOut = 3000;
var ret = KeyencePlcMcNet.ConnectServer();
if (!ret.IsSuccess)
{
LogService.Warn($"PLC重连失败: {ret.Message} (Code: {ret.ErrorCode})");
}
}
catch (Exception ex)
{
LogService.Error($"PLC重连异常: {ex.Message}");
}
}
/// <summary>
/// 释放资源确保扫描停止并断开PLC连接。
/// </summary>
public void Dispose()
{
if (_disposed) return;
_disposed = true;
try
{
// 停止扫描
try { StopPlcScanAsync().GetAwaiter().GetResult(); } catch { }
// 解除事件订阅(当前未订阅自定义事件,保留占位以便后续扩展)
// try { /* no event to unsubscribe */ } catch { }
// 关闭PLC连接
try { KeyencePlcMcNet?.ConnectClose(); } catch { }
}
finally
{
KeyencePlcMcNet = null;
}
}
/// <summary>
/// 把字符串对调
/// </summary>
private string RevData(string Code)
{
// 需求PLC 字符串按“字”(2字节)进行高低字节对调,导致 ABCDEF -> BADCFE
// 纠正方式:按两个字符一组交换顺序,奇数长度保留最后一个字符
if (string.IsNullOrEmpty(Code)) return string.Empty;
// 去掉读取中可能包含的 '\0' 空字符
var src = Code.Replace("\0", string.Empty);
var sb = new System.Text.StringBuilder(src.Length);
for (int i = 0; i < src.Length; i += 2)
{
if (i + 1 < src.Length)
{
sb.Append(src[i + 1]);
sb.Append(src[i]);
}
else
{
// 奇数长度,最后一个字符原样保留
sb.Append(src[i]);
}
}
return sb.ToString();
}
}
}