初版的功能

This commit is contained in:
2025-11-11 17:26:31 +08:00
parent a178c3550e
commit dde463b43b
21 changed files with 1038 additions and 197 deletions

View File

@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FATrace.WPLApp.Services
{
/// <summary>
/// CSV服务
///导出CSV文件
/// </summary>
public class CsvServices
{
public CsvServices()
{
}
}
}

View File

@@ -1,8 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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
{
@@ -10,8 +13,542 @@ namespace FATrace.WPLApp.Services
/// 数据服务
/// 产线PLC数据交互的服务
/// </summary>
public class DataServices
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();
}
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -12,9 +12,15 @@ namespace FATrace.WPLApp.Services
public interface ILogService
{
void Debug(string msg);
void Debug(string msg, Exception ex);
void Error(string msg);
void Error(string msg, Exception ex);
void Error(Exception ex);
void Fatal(string msg);
void Fatal(string msg, Exception ex);
void Info(string msg);
void Info(string msg, Exception ex);
void Warn(string msg);
void Warn(string msg, Exception ex);
}
}

View File

@@ -1,4 +1,4 @@
using NLog;
using NLog;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -12,16 +12,14 @@ namespace FATrace.WPLApp.Services
/// </summary>
public class LogService : ILogService
{
private static Logger Logger = LogManager.GetCurrentClassLogger(); //初始化日志类
private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); // 初始化日志类
/// <summary>
/// 调试日志
/// </summary>
/// <param name="msg">日志内容</param>
public void Debug(string msg)
{
Logger.Debug(msg);
}
public void Debug(string msg) => Logger.Debug(msg);
public void Debug(string msg, Exception ex) => Logger.Debug(ex, msg);
/// <summary>
/// 信息日志
@@ -31,10 +29,8 @@ namespace FATrace.WPLApp.Services
/// 适用大部分场景
/// 1.记录日志文件
/// </remarks>
public void Info(string msg)
{
Logger.Info(msg);
}
public void Info(string msg) => Logger.Info(msg);
public void Info(string msg, Exception ex) => Logger.Info(ex, msg);
/// <summary>
/// 错误日志
@@ -44,10 +40,9 @@ namespace FATrace.WPLApp.Services
/// 适用异常,错误日志记录
/// 1.记录日志文件
/// </remarks>
public void Error(string msg)
{
Logger.Error(msg);
}
public void Error(string msg) => Logger.Error(msg);
public void Error(string msg, Exception ex) => Logger.Error(ex, msg);
public void Error(Exception ex) => Logger.Error(ex);
/// <summary>
/// 严重致命错误日志
@@ -57,10 +52,8 @@ namespace FATrace.WPLApp.Services
/// 1.记录日志文件
/// 2.控制台输出
/// </remarks>
public void Fatal(string msg)
{
Logger.Fatal(msg);
}
public void Fatal(string msg) => Logger.Fatal(msg);
public void Fatal(string msg, Exception ex) => Logger.Fatal(ex, msg);
/// <summary>
/// 警告日志
@@ -70,13 +63,7 @@ namespace FATrace.WPLApp.Services
/// 1.记录日志文件
/// 2.发送日志邮件
/// </remarks>
public void Warn(string msg)
{
try
{
Logger.Warn(msg);
}
catch { }
}
public void Warn(string msg) => Logger.Warn(msg);
public void Warn(string msg, Exception ex) => Logger.Warn(ex, msg);
}
}

View File

@@ -24,34 +24,7 @@ namespace FATrace.WPLApp.Services
CmdPar = "Dashboard",
Icon = "\ued78",
IsParent = false,
},
// 工艺监控导航项(父级)
new NavItemDto()
{
Name = "工艺监控",
CmdPar = "",
Icon = "\uea12",
IsParent = true,
ChildrenNavItemDtos = new ObservableCollection<NavItemDto>()
{
// 基础数据的子项
new NavItemDto()
{
Name = "3D工艺流程",
CmdPar = "3D工艺流程",
Icon = "\uee12",
IsParent = false,
},
new NavItemDto()
{
Name = "管道线路",
CmdPar = "管道线路",
Icon = "\ue650",
IsParent = false,
},
}
},
},
// 生产管理导航项(父级) - 新增测试导航
new NavItemDto()

View File

@@ -23,6 +23,17 @@ namespace FATrace.WPLApp.Services
}
private bool _PlcLinkState = false;
/// <summary>
/// PLC连接状态
/// </summary>
public bool PlcLinkState
{
get { return _PlcLinkState; }
set { _PlcLinkState = value;RaisePropertyChanged(); }
}
private string? _CurUser;
/// <summary>
/// 当前的用户