using Cognex.VisionPro.ToolBlock; using MahApps.Metro.Controls; using NPOI.OpenXmlFormats.Vml; using Opc.Ua; using Org.BouncyCastle.Crypto; using Prism.Events; using Prism.Ioc; using Prism.Mvvm; using Prism.Regions; using SqlSugar; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Drawing; using System.IO; using System.IO.Compression; using System.Linq; using System.Net.Sockets; using System.Security.Claims; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Web; using System.Web.UI.WebControls; using System.Windows.Media; using Team.FFFeederService.Interfaces; using TeamAAS_VP.Core.PLCs; using TeamAAS_VP.Core.ScrewDriver; using TeamAAS_VP.Data; using TeamAAS_VP.Enums; using TeamAAS_VP.Events; using TeamAAS_VP.Interfaces; using TeamAAS_VP.Models; using TeamAAS_VP.Models.PLC; using TeamAAS_VP.Models.Torque; using TeamAAS_VP.Resources.Languages; using TeamAAS_VP.Views.Product; using TouchSocket.Core; using TouchSocket.SerialPorts; using TouchSocket.Sockets; using Timer = System.Threading.Timer; namespace TeamAAS_VP.Core { public class Management : BindableBase { #region 字段 IRegionManager _regionManager; IEventAggregator _eventAggregator; IContainerProvider _container; IConfigService _configService; IRobotService _robotService; ICameraService _cameraService; IFeederService _feederService; IPlcService _plcService; ILightManagerService _lightManagerService; IProductService _productService; ICalibrationService _calibrationService; IRemoteCommandService _remoteCommandService; ISystemDatabaseService _systemDatabaseService; IMesService _mesService; Timer yieldtimer; private DateTime StartTime = DateTime.Now; SerialPortClient client; // Pressure collection /// /// 压力采集信号控制CTS /// private CancellationTokenSource[] _pressureCts = new CancellationTokenSource[100]; /// /// 压力采集超时CTS /// private CancellationTokenSource[] _pressureTimeoutCts = new CancellationTokenSource[100]; /// /// 压力采集CTS /// private CancellationTokenSource[] _pressureLinkedCts = new CancellationTokenSource[100]; /// /// 压力数据集合 /// private List<(DateTime Timestamp, float Value)> _pressureSamples = new List<(DateTime, float)>(); /// /// 采样间隔毫秒 /// private int _pressureSampleIntervalMs = 50; //采样间隔毫秒 /// /// 超时自动停止秒数 /// private int _pressureMaxDurationSeconds = 30; //超时自动停止秒数 DateTime[] StartCtqTime = new DateTime[3];//开始采集Ctq数据的时间 /// /// 上次执行上传csv的日期 /// private DateTime _lastCompressDate = DateTime.MinValue; /// /// 当天是否上传过csv /// private bool _hasCompressedToday = false; List lockResultsAll = new List();//一个产品的总结果 string productSn = "";//产品sn DateTime StartScrewTime = DateTime.Now;//开始锁附的时间 #endregion #region 属性 /// /// 当前顶部显示的运行模式 /// private RunDisplayMode _CurrentDisplayMode = RunDisplayMode.Unknown; public RunDisplayMode CurrentDisplayMode { get { return _CurrentDisplayMode; } set { SetProperty(ref _CurrentDisplayMode, value); } } private ObservableCollection _Renders; /// /// 显示界面 /// public ObservableCollection Renders { get { return _Renders; } set { SetProperty(ref _Renders, value); } } public BgCommunicate BgCommunicate { get; private set; } public BgModbusTcpCommunicate BgModbusTcpCommunicate { get; private set; } private ObservableCollection _Status = new ObservableCollection(); /// /// 状态栏集合 /// public ObservableCollection Status { get { return _Status; } set { SetProperty(ref _Status, value); } } private Int64 _TotalQuantity; /// /// 总产量 /// public Int64 TotalQuantity { get { return _TotalQuantity; } set { SetProperty(ref _TotalQuantity, value); } } private Int64 _TotalQuantityToday; /// /// 当天的总产量 /// public Int64 TotalQuantityToday { get { return _TotalQuantityToday; } set { SetProperty(ref _TotalQuantityToday, value); } } private Int64 _CurrentUPH; /// /// 当前的UPH /// public Int64 CurrentUPH { get { return _CurrentUPH; } set { SetProperty(ref _CurrentUPH, value); } } private Int64 _ThrowNumber; /// /// 抛料数 /// public Int64 ThrowNumber { get { return _ThrowNumber; } set { SetProperty(ref _ThrowNumber, value); } } private Int64 _NgNumber; /// /// ng数量 /// public Int64 NgNumber { get { return _NgNumber; } set { SetProperty(ref _NgNumber, value); } } private double _Yield; /// /// 良率 /// public double Yield { get { return _Yield; } set { SetProperty(ref _Yield, value); } } private XYD_ScrewDriver _ScrewDriver; public XYD_ScrewDriver ScrewDriver { get { return _ScrewDriver; } set { SetProperty(ref _ScrewDriver, value); } } public Dictionary BackupObject { get; set; } = new Dictionary(); private string _SignalValue = "0"; /// /// 扭力测量仪获取的最大值 /// public string SignalValue { get { return _SignalValue; } set { SetProperty(ref _SignalValue, value); } } private NumberStatistics _NumStatistics; /// /// 数量(批头、吸嘴、供料器) /// public NumberStatistics NumStatistics { get { return _NumStatistics; } set { SetProperty(ref _NumStatistics, value); } } private double _CTtime; /// /// ct /// public double CTtime { get { return _CTtime; } set { SetProperty(ref _CTtime, value); } } private bool _TorqueStatus = false; public bool TorqueStatus { get { return _TorqueStatus; } set { _TorqueStatus = value; } } private ObservableCollection _dfc = new ObservableCollection(); /// /// CTQ数据集合 /// public ObservableCollection DFC { get { return _dfc; } set { SetProperty(ref _dfc, value); } } private ObservableCollection _ProductList; public ObservableCollection ProductList { get { return _ProductList; } set { SetProperty(ref _ProductList, value); } } #endregion #region 命令 #endregion #region 事件 #endregion public Management(IRegionManager regionManager, IEventAggregator ea, IContainerProvider container, IConfigService configService, IRobotService robotService, ICameraService cameraService, IFeederService feederService, IPlcService plcService, IProductService productService, ICalibrationService calibrationService, IRemoteCommandService remoteCommandService, ISystemDatabaseService systemDatabaseService, ILightManagerService lightManagerService, IMesService mesService) { _regionManager = regionManager; _eventAggregator = ea; _container = container; _configService = configService; _robotService = robotService; _cameraService = cameraService; _feederService = feederService; _plcService = plcService; _calibrationService = calibrationService; yieldtimer = new Timer(DoYieldTime, null, 10000, 1000); Renders = new ObservableCollection(); _productService = productService; _remoteCommandService = remoteCommandService; _systemDatabaseService = systemDatabaseService; _lightManagerService = lightManagerService; _mesService = mesService; _eventAggregator.GetEvent().Subscribe(CurrentRunStatus); } #region 读取配置参数 public async Task ReadConfig() { await ReadNumber(); } /// /// 读取数量配置 /// public async Task ReadNumber() { try { NumStatistics = new NumberStatistics(); var latestHeadChangeRecord = await _systemDatabaseService.GetLatestHeadChangeRecordAsync(); if (latestHeadChangeRecord != null) { NumStatistics.HeaderNum = latestHeadChangeRecord.CurrentLockCount; } var latestNozzleChangeRecord = await _systemDatabaseService.GetLatestNozzleChangeRecordAsync(); if (latestNozzleChangeRecord != null) { NumStatistics.NozzleNum = latestNozzleChangeRecord.CurrentLockCount; } NumStatistics.Feeder1Num = _configService.GetRemainingCountByFeederNumber(1); NumStatistics.Feeder2Num = _configService.GetRemainingCountByFeederNumber(2); } catch (Exception ex) { NumStatistics = new NumberStatistics(); LogHelper.WriteLogError("加载数量配置文件时出错!", ex); } } #endregion #region 初始化硬件模块 /// /// 初始化Feeder /// /// public async Task<(bool IsSucceed, string Msg)> InitFeeders() { var feeders = _configService.GetAllFeeders(); StringBuilder sb = new StringBuilder(); foreach (var item in feeders) { _feederService.CreateFeeder(item.Id, item.FeederNo, item.FeederName, item.IP, item.Port, item.FeederBrand); var feeder = _feederService.GetFeeder(item.Id); Status.Add(new StatusInfo(item.Id, item.FeederName + $"{Lang.未连接}", new SolidColorBrush(Colors.Red))); try { feeder.FeederConnectionStatusChanged += Management_FeederConnectionStatusChanged; await feeder.ConnectAsync(); //连接Feeder //SendTaskMessage($"{item.FeederName}已连接!", MessageLevel.Debug); } catch (Exception ex) { sb.AppendLine($"{item.FeederName}:{Lang.断开连接}![{ex.Message}]"); SendTaskMessage($"{item.FeederName}{Lang.断开连接}!", MessageLevel.Alarm); } } if (sb.Length == 0) { return (true, ""); } else { return (false, sb.ToString()); } } /// /// 初始化机器人 /// /// public async Task<(bool IsSucceed, string Msg)> InitRobots() { var robots = _configService.GetAllRobots(); StringBuilder sb = new StringBuilder(); foreach (var item in robots) { _robotService.CreateRobot(item.Id, item); try { Status.Add(new StatusInfo(item.Id, item.RobotName + $"{Lang.未连接}", new SolidColorBrush(Colors.Red))); var robot = _robotService.GetRobot(item.Id); robot.ReceivedEvent += Management_ReceivedEvent; robot.SendEvent += Management_SendEvent; robot.ConnectedEvent += Management_ConnectedEvent; robot.DisconnectedEvent += Management_DisconnectedEvent; await robot.ConnectAsync(); //连接Robot SendTaskMessage($"{item.RobotName}{Lang.已连接}!", MessageLevel.Debug); } catch (Exception ex) { sb.AppendLine($"{item.RobotName}:{Lang.断开连接}![{ex.Message}]"); SendTaskMessage($"{item.RobotName}{Lang.断开连接}!", MessageLevel.Alarm); } } if (sb.Length == 0) { return (true, ""); } else { return (false, sb.ToString()); } } /// /// 初始化PLC /// /// public async Task<(bool IsSucceed, string Msg)> InitPlc() { var plcs = _configService.GetAllPlcs(); StringBuilder sb = new StringBuilder(); foreach (var item in plcs) { try { _plcService.CreatePlc(item.Id, item); var plc = _plcService.GetPlc(item.Id); Status.Add(new StatusInfo(item.Id, item.Name + $"{Lang.未连接}", new SolidColorBrush(Colors.Red))); plc.ConnectChangedEvent += Management_PlcConnectChangedEvent; await plc.ConnectAsync(); //连接PLC //SendTaskMessage($"{item.Name}{Lang.已连接}!", MessageLevel.Debug); } catch (Exception ex) { sb.AppendLine($"{item.Name}:{Lang.断开连接}![{ex.Message}]"); SendTaskMessage($"{item.Name}{Lang.断开连接}!", MessageLevel.Alarm); } } try { await WriteSystemParameterToPlcAsync(_configService.GetSystemConfiguration()); RegisterPlcMonitorAddress(); } catch (Exception ex) { LogHelper.WriteLogError(Lang.为PLC写入系统参数和订阅变量时出错, ex); } if (sb.Length == 0) { return (true, ""); } else { return (false, sb.ToString()); } } /// /// 初始化相机 /// /// public async Task<(bool IsSucceed, string Msg)> InitCameras() { var cameras = _configService.GetAllCameras(); StringBuilder sb = new StringBuilder(); foreach (var item in cameras) { try { //Renders.Add(new ShowRender() { Id = item.Id, CameraName = item.CameraName }); _cameraService.CreateCamera(item.Id, item); var camera = _cameraService.GetCamera(item.Id); Status.Add(new StatusInfo(item.Id, item.CameraName + $"{Lang.未连接}", new SolidColorBrush(Colors.Red))); camera.CameraConnectChangedEvent += Management_CameraConnectChangedEvent; try { if (await camera.OpenDeviceAsync()) { SendTaskMessage($"{item.CameraName}{Lang.已连接}!", MessageLevel.Debug); } else { SendTaskMessage($"{item.CameraName}{Lang.连接失败}!", MessageLevel.Alarm); sb.AppendLine($"{item.CameraName}:{Lang.连接失败}!"); } } catch (Exception ex) { SendTaskMessage($"{item.CameraName}{ex.Message}!", MessageLevel.Alarm); sb.AppendLine($"{item.CameraName}:{ex.Message}!"); } } catch (Exception ex) { SendTaskMessage($"{item.CameraName}{Lang.连接失败}!", MessageLevel.Alarm); sb.AppendLine($"{item.CameraName}:{Lang.连接失败}!:{ex.Message}"); } } //_container.Resolve().Renders = Renders; if (sb.Length == 0) { return (true, ""); } else { return (false, sb.ToString()); } } /// /// 初始化光源控制器 /// /// public async Task<(bool IsSucceed, string Msg)> InitLightControllers() { var lightcontrollers = _configService.GetAllLightControllers(); StringBuilder sb = new StringBuilder(); foreach (var item in lightcontrollers) { try { var lishtCtr = await _lightManagerService.RegisterControllerAsync(item.Id, item); //var lightcontroller = _cameraService.GetLightController(item.Id); //await lightcontroller.ConnectAsync(); //连接光源控制器 //SendTaskMessage($"{item.LightControllerName}{Lang.已连接}!", MessageLevel.Debug); } catch (Exception ex) { //sb.AppendLine($"{item.LightControllerName}:{Lang.断开连接}![{ex.Message}]"); //SendTaskMessage($"{item.LightControllerName}{Lang.断开连接}!", MessageLevel.Alarm); } } if (lightcontrollers.Count > 0) { var result = await _lightManagerService.ConnectAllAsync(); if (result) { return (true, ""); } else { return (false, "Light Controller Connect Failed!"); } } return (true, ""); //if (sb.Length == 0) //{ // return (true, ""); //} //else //{ // return (false, sb.ToString()); //} } /// /// 初始化后台TCP服务器通讯 /// public void InitBgTcp() { var config = _configService.GetBgTcp(); try { BgCommunicate = new BgCommunicate(config); BgCommunicate.ConnectedEvent += BgCommunicate_ConnectedEvent; BgCommunicate.DisconnectedEvent += BgCommunicate_DisconnectedEvent; BgCommunicate.ReceivedEvent += BgCommunicate_ReceivedEvent; BgCommunicate.SendEvent += BgCommunicate_SendEvent; BgCommunicate.StartListening(); SendTaskMessage($"{Lang.已开启监听端口}:{config.Port}", MessageLevel.Debug); } catch (Exception ex) { SendTaskMessage($"{Lang.未开启监听端口}:{config.Port}", MessageLevel.Error); LogHelper.WriteLogError(Lang.开启后台服务器通讯时出错, ex); } } /// /// 初始化后台ModbusTCP从站 /// public void InitModbusTcp() { try { var config = _configService.GetBgModbusTcp(); BgModbusTcpCommunicate = new BgModbusTcpCommunicate(config); if (config.IsEnabled) { BgModbusTcpCommunicate.Listen(); SendTaskMessage($"Start Modbus Tcp Slave:{BgModbusTcpCommunicate.IP}:{BgModbusTcpCommunicate.Port}:{BgModbusTcpCommunicate.SlaveID}", MessageLevel.Debug); } } catch (Exception ex) { SendTaskMessage($"No Start Modbus Tcp Slave:{ex.Message}", MessageLevel.Alarm); LogHelper.WriteLogError(Lang.开启后台ModbusTCP通讯时出错, ex); } } /// /// 初始化后台脚本 /// public void InitBackgroundcript() { try { if (!Directory.Exists(FilePath.BackgroundScriptPath)) { Directory.CreateDirectory(FilePath.BackgroundScriptPath); } try { string[] files = Directory.GetFiles(FilePath.BackgroundScriptPath); List ScriptFilesCollection = new List(); foreach (string file in files) { string fileName = Path.GetFileName(file); string fileExtension = Path.GetExtension(file); if (fileExtension == ".cs") { ScriptFilesCollection.Add(file); } } foreach (var item in ScriptFilesCollection) { try { string script = FileHelper.ReadFile(item); SendTaskMessage($"{Lang.执行脚本}:{item}", MessageLevel.Info); Task.Factory.StartNew(() => { ScriptHelper.ExecuteScriptingAsync(script, _regionManager, _eventAggregator, _container, rst => { if (!rst.Item1) { foreach (var err in rst.Item2) { SendTaskMessage($"{Lang.后台脚本出错}:{err}", MessageLevel.Error); } } }); }); } catch (Exception ex) { LogHelper.WriteLogError(string.Format(Lang.开启脚本时出错0, item), ex); } } } catch (Exception ex) { LogHelper.WriteLogError(Lang.读取本地脚本文件列表时出错, ex); } } catch (Exception ex) { LogHelper.WriteLogError(Lang.开启后台脚本时出错, ex); } } /// /// 初始化电批 /// /// public async Task InitScrewDriver() { try { var screwDriverConfig = _configService.GetScrewDriverConfig(); ScrewDriver = new XYD_ScrewDriver(screwDriverConfig.IPAdress, screwDriverConfig.Port); Status.Add(new StatusInfo(ScrewDriver.Id, Lang.电批未连接, new SolidColorBrush(Colors.Red))); ScrewDriver.ConnectStateChangedEvent += ScrewDriver_ConnectStateChangedEvent; ScrewDriver.SendTimeout = screwDriverConfig.SendTimeout; screwDriverConfig.ReceiveTimeout = screwDriverConfig.ReceiveTimeout; var isConnected = await ScrewDriver.ConnectAsync(); if (isConnected) { SendTaskMessage(Lang.电批已连接, MessageLevel.Debug); } else { SendTaskMessage(Lang.电批连接失败, MessageLevel.Error); } } catch (Exception ex) { SendTaskMessage(string.Format(Lang.电批连接失败0, ex.Message), MessageLevel.Alarm); LogHelper.WriteLogError(Lang.连接电批时出错, ex); } } /// /// 初始化扭力测量仪 /// /// public async Task InitSignal() { var torqueConfig = _configService.GetTorqueReaderConfig(); if (torqueConfig != null) { try { var port = FileHelper.ReadJsonFile(FilePath.TorqueReaderConfigurationPath); if (string.IsNullOrEmpty(port.TorqueMeasureSerialPort)) { client = new TouchSocket.SerialPorts.SerialPortClient(); client.Connecting = (client1, e) => { return EasyTask.CompletedTask; };//即将连接到端口 client.Connected = (client1, e) => { return EasyTask.CompletedTask; };//成功连接到端口 client.Closing = (client1, e) => { return EasyTask.CompletedTask; };//即将从端口断开连接。此处仅主动断开才有效。 client.Closed = (client1, e) => { return EasyTask.CompletedTask; };//从端口断开连接,当连接不成功时不会触发。 client.Received = async (c, e) => { //await Console.Out.WriteLineAsync(Encoding.UTF8.GetString(e.ByteBlock.Buffer)); await Console.Out.WriteLineAsync(e.ByteBlock.Span.ToString(Encoding.ASCII)); }; client.Received = Sp_DataReceived; await client.SetupAsync(new TouchSocketConfig() .SetSerialPortOption(new SerialPortOption() { BaudRate = torqueConfig.BaudRate,//波特率 DataBits = torqueConfig.DataBits,//数据位 Parity = torqueConfig.Parity,//校验位 PortName = torqueConfig.PortName,//COM StopBits = torqueConfig.StopBits//停止位 }) .SetSerialDataHandlingAdapter(() => new PeriodPackageAdapter() { CacheTimeout = TimeSpan.FromMilliseconds(100) }) ); await client.ConnectAsync(); } else { SendTaskMessage("请设置扭力测量仪串口!", MessageLevel.Error); } } catch (Exception ex) { LogHelper.WriteLogError("初始化扭力测量仪时出错!", ex); SendTaskMessage("初始化扭力测量仪时出错!" + ex.Message, MessageLevel.Error); } } else { TorqueSerialPortConfig config = new TorqueSerialPortConfig(); FileHelper.WriteJsonFile(config, FilePath.TorqueReaderConfigurationPath); } } #endregion #region 机器人指令执行 public Dictionary RobotTCPCollection = new Dictionary(); /// /// 机器人TCP接收 /// /// /// /// private async void Management_ReceivedEvent(Guid robotid, object arg2, TouchSocket.Sockets.ReceivedDataEventArgs e) { try { if (!RobotTCPCollection.ContainsKey(robotid)) RobotTCPCollection.Add(robotid, new CancellationTokenSource()); else { if (RobotTCPCollection[robotid].IsCancellationRequested) { RobotTCPCollection[robotid] = new CancellationTokenSource(); } } var robot = await _robotService.GetRobotAsync(robotid); //从服务器收到信息。但是一般byteBlock和requestInfo会根据适配器呈现不同的值。 var buffer = e.ByteBlock.Span.ToString(robot.GetEncoding()); SendTaskMessage($"{robot.Name}Received:{buffer}", MessageLevel.Info); var _ = _remoteCommandService.ExecuteAsync(buffer, RobotTCPCollection[robotid].Token, async msg => { try { if (robot.ConnectType == TCPConnectType.Client) { if (robot.IsConnected) { await robot.SendAsync($"{msg}"); } } else { var cient = (ITcpSessionClient)arg2; if (cient.Online) { await robot.SendAsync(cient, $"{msg}"); } } } catch (Exception ex) { LogHelper.WriteLogError(Lang.TCP发送数据时出错, ex); SendTaskMessage(ex.Message, MessageLevel.Alarm); } }); } catch (Exception ex) { LogHelper.WriteLogError(Lang.处理机器人接收的命令时出错, ex); SendTaskMessage(ex.Message, MessageLevel.Alarm); } } #endregion #region 机器人&Feeder&相机连接状态事件 /// /// 机器人发送事件 /// /// /// /// /// private void Management_SendEvent(Guid robotid, object arg2, string arg3) { var r = _robotService.GetRobot(robotid); SendTaskMessage($"{r.Name}Send:{arg3}", MessageLevel.Info); } /// /// 机器人断开连接时 /// /// /// /// /// private void Management_DisconnectedEvent(Guid robotid, object arg2, ClosedEventArgs arg3) { if (RobotTCPCollection.ContainsKey(robotid)) { RobotTCPCollection[robotid].Cancel(); } var r = _configService.GetRobot(robotid); if (r.ConnectType == TCPConnectType.Server) { ITcpSessionClient socketClient = arg2 as ITcpSessionClient; SendTaskMessage($"{r.RobotName}[{socketClient.Id}:{socketClient.IP}]{Lang.断开连接}!", MessageLevel.Error); var robot = _robotService.GetRobot(robotid); if (robot.TcpService.Count < 1) { var sta = Status.FirstOrDefault(s => s.ID == robotid); App.Current.Dispatcher.Invoke(new Action(() => { sta.Message = $"{r.RobotName}{Lang.未连接}"; sta.Background = new SolidColorBrush(Colors.Red); })); } } else { SendTaskMessage($"{r.RobotName}{Lang.断开连接}!", MessageLevel.Error); var sta = Status.FirstOrDefault(s => s.ID == robotid); App.Current.Dispatcher.Invoke(new Action(() => { sta.Message = $"{r.RobotName}{Lang.断开连接}"; sta.Background = new SolidColorBrush(Colors.Red); })); } } /// /// 机器人连接成功时 /// /// /// /// /// private void Management_ConnectedEvent(Guid robotid, object arg2, ConnectedEventArgs arg3) { var r = _configService.GetRobot(robotid); if (r.ConnectType == TCPConnectType.Server) { ITcpSessionClient socketClient = arg2 as ITcpSessionClient; SendTaskMessage($"{r.RobotName}[{socketClient.Id}:{socketClient.IP}]{Lang.已连接}!", MessageLevel.Debug); var robot = _robotService.GetRobot(robotid); if (robot.TcpService.Count > 0) { var sta = Status.FirstOrDefault(s => s.ID == robotid); App.Current.Dispatcher.Invoke(new Action(() => { sta.Message = $"{r.RobotName}{Lang.已连接}"; sta.Background = new SolidColorBrush(Colors.Green); })); } } else { SendTaskMessage($"{r.RobotName}{Lang.已连接}!", MessageLevel.Debug); var sta = Status.FirstOrDefault(s => s.ID == robotid); App.Current.Dispatcher.Invoke(new Action(() => { sta.Message = $"{r.RobotName}{Lang.已连接}"; sta.Background = new SolidColorBrush(Colors.Green); })); } } /// /// Feeder连接状态改变时 /// /// /// private void Management_FeederConnectionStatusChanged(object sender, bool state) { IFeeder feeder = sender as IFeeder; var sta = Status.FirstOrDefault(s => s.ID == feeder.Id); if (state) { App.Current.Dispatcher.Invoke(new Action(() => { sta.Message = $"{feeder.Name}{Lang.已连接}"; sta.Background = new SolidColorBrush(Colors.Green); })); SendTaskMessage($"{feeder.Name}{Lang.已连接}!", MessageLevel.Debug); } else { App.Current.Dispatcher.Invoke(new Action(() => { sta.Message = $"{feeder.Name}{Lang.未连接}"; sta.Background = new SolidColorBrush(Colors.Red); })); SendTaskMessage($"{feeder.Name}{Lang.断开连接}!", MessageLevel.Error); } } /// /// 相机连接或者断开时 /// /// /// private void Management_CameraConnectChangedEvent(Guid id, bool state) { var sta = Status.FirstOrDefault(s => s.ID == id); var camera = _configService.GetCamera(id); if (sta != null && camera != null) { if (state) { App.Current.Dispatcher.Invoke(new Action(() => { sta.Message = $"{camera.CameraName}{Lang.已连接}"; sta.Background = new SolidColorBrush(Colors.Green); })); } else { App.Current.Dispatcher.Invoke(new Action(() => { sta.Message = $"{camera.CameraName}{Lang.断开连接}"; sta.Background = new SolidColorBrush(Colors.Red); })); } } } /// /// PLC连接状态改变事件 /// /// /// private async void Management_PlcConnectChangedEvent(object sender, bool isconnected) { IPlc plc = sender as IPlc; var sta = Status.FirstOrDefault(s => s.ID == plc.Id); if (plc.IsConnected) { SendTaskMessage($"{plc.Name}{Lang.已连接}!", MessageLevel.Debug); if (sta == null) return; App.Current.Dispatcher.Invoke(new Action(() => { sta.Message = $"{plc.Name}{Lang.已连接}"; sta.Background = new SolidColorBrush(Colors.Green); })); try { await WriteSystemParameterToPlcAsync(_configService.GetSystemConfiguration()); await WriteParameterToPlcExAsync(); } catch (Exception ex) { LogHelper.WriteLogError("PLC连接成功时,写入系统参数和点位信息时出错!", ex); } } else { SendTaskMessage($"{plc.Name}{Lang.断开连接}!", MessageLevel.Error); if (sta == null) return; App.Current.Dispatcher.Invoke(new Action(() => { sta.Message = $"{plc.Name}{Lang.未连接}"; sta.Background = new SolidColorBrush(Colors.Red); })); } } /// /// 电批连接状态改变时 /// /// /// private void ScrewDriver_ConnectStateChangedEvent(object screwDevice, bool state) { try { XYD_ScrewDriver screwDriver = screwDevice as XYD_ScrewDriver; if (state) { SendTaskMessage(Lang.电批已连接, MessageLevel.Debug); var sta = Status.FirstOrDefault(s => s.ID == screwDriver.Id); App.Current.Dispatcher.Invoke(new Action(() => { sta.Message = Lang.电批已连接; sta.Background = new SolidColorBrush(Colors.Green); })); } else { SendTaskMessage(Lang.电批未连接, MessageLevel.Error); var sta = Status.FirstOrDefault(s => s.ID == screwDriver.Id); App.Current.Dispatcher.Invoke(new Action(() => { sta.Message = Lang.电批未连接; sta.Background = new SolidColorBrush(Colors.Red); })); } } catch (Exception ex) { LogHelper.WriteLogError(Lang.电批连接状态改变时出错了, ex); } } #endregion #region 后台服务器通讯事件 private void BgCommunicate_SendEvent(ITcpSessionClient client, string msg) { SendTaskMessage($"{Lang.服务器}[{client.ServicePort}] Send To {Lang.客户端}[{client.Id}:{client.IP}]:{msg}", MessageLevel.Info); } public Dictionary BgCommunicateollection = new Dictionary(); private async void BgCommunicate_ReceivedEvent(ITcpSessionClient client, string msg) { try { if (!BgCommunicateollection.ContainsKey(client.Id)) { BgCommunicateollection.Add(client.Id, new CancellationTokenSource()); } SendTaskMessage($"{Lang.服务器}[{client.ServicePort}]ReceiveFor{Lang.客户端}[{client.Id}:{client.IP}]:{msg}", MessageLevel.Info); var _ = _remoteCommandService.ExecuteAsync(msg, BgCommunicateollection[client.Id].Token, async response => { try { if (!client.Online) { return; } await BgCommunicate.SendAsync(client, $"{response}"); } catch (Exception ex) { LogHelper.WriteLogError(Lang.后台TCP发送数据时出错, ex); SendTaskMessage(ex.Message, MessageLevel.Alarm); } }); } catch (Exception ex) { LogHelper.WriteLogError(Lang.处理后台服务器接收的命令时出错, ex); SendTaskMessage(ex.Message, MessageLevel.Alarm); } } private void BgCommunicate_DisconnectedEvent(ITcpSessionClient client, ClosedEventArgs e) { SendTaskMessage($"{Lang.服务器}[{client.ServicePort}]{Lang.客户端}[{client.Id}:{client.IP}]{Lang.断开连接}!", MessageLevel.Alarm); if (BgCommunicateollection.ContainsKey(client.Id)) { BgCommunicateollection[client.Id].Cancel(); } } private void BgCommunicate_ConnectedEvent(ITcpSessionClient client, ConnectedEventArgs e) { SendTaskMessage($"{Lang.服务器}[{client.ServicePort}]{Lang.客户端}[{client.Id}:{client.IP}]{Lang.连接成功}!", MessageLevel.Debug); } /// /// 扭力测量仪接收信息 /// /// /// /// private async Task Sp_DataReceived(ISerialPortClient client, ReceivedDataEventArgs e) { try { string poname = client.MainSerialPort.PortName; string data = e.ByteBlock.Span.ToString(Encoding.ASCII); await Console.Out.WriteLineAsync(data); string response = data.Split('\r')[0]; SendTaskMessage($"{poname}Receive:{response}", MessageLevel.Info); SignalValue = ""; SignalValue = response; } catch (Exception ex) { LogHelper.WriteLogError("扭力测量仪接收信息时出错!", ex); SendTaskMessage("扭力测量仪接收信息时出错!" + ex.Message, MessageLevel.Error); Thread.Sleep(50); } } #endregion #region PLC /// /// 将系统参数写入PLC /// /// /// public async Task WriteSystemParameterToPlcAsync(SystemConfiguration systemConfiguration) { var addressConfig = _configService.GetPlcAddresses(); var plc = _plcService.GetPlcByNumber(addressConfig.PlcNo) as OpcUaClientPLC; if (plc == null || !plc.IsConnected) return; Dictionary nodeValues = new Dictionary(); nodeValues.Add(addressConfig.Out_WorkMode.Address, (Int16)systemConfiguration.WorkMode); nodeValues.Add(addressConfig.Out_PickPointNum.Address, (Int16)systemConfiguration.PickPointNum); nodeValues.Add(addressConfig.Out_DowmCameraNum.Address, (Int16)systemConfiguration.DowmCameraNum); nodeValues.Add(addressConfig.Out_UpCameraPickNum.Address, (Int16)systemConfiguration.UpCameraPickNum); nodeValues.Add(addressConfig.Out_UpCameraPutNum.Address, (Int16)systemConfiguration.UpCameraPutNum); nodeValues.Add(addressConfig.Out_UPCameracheckNum.Address, (Int16)systemConfiguration.UPCameracheckNum); nodeValues.Add(addressConfig.Out_FixedCameraPickNum.Address, (Int16)systemConfiguration.FixedCameraPickNum); nodeValues.Add(addressConfig.Out_FixedCameraPutNum.Address, (Int16)systemConfiguration.FixedCameraPutNum); nodeValues.Add(addressConfig.Out_FixedCameracheckNum.Address, (Int16)systemConfiguration.FixedCameracheckNum); nodeValues.Add(addressConfig.Out_ScrewTeachNum.Address, (Int16)systemConfiguration.ScrewTeachNum); var res = await plc.WriteNodesAsync(nodeValues); } /// /// 注册需要监控的PLC地址 /// public void RegisterPlcMonitorAddress() { var addressConfig = _configService.GetPlcAddresses(); var plc = _plcService.GetPlcByNumber(addressConfig.PlcNo) as OpcUaClientPLC; if (plc == null || !plc.IsConnected) return; //PLC地址数组 List monitorNodeIds = new List() { addressConfig.In_DowmCameraExe.Address, addressConfig.In_UpCameraPickExe.Address, addressConfig.In_UpCameraPutExe.Address, addressConfig.In_UPCameracheckExe.Address, addressConfig.In_FixedCameraPickExe.Address, addressConfig.In_FixedCameraPutExe.Address, addressConfig.In_FixedCameracheckExe.Address, addressConfig.In_WorkStart.Address, addressConfig.In_WorkDone.Address, //addressConfig.In_MesCheck.Address, addressConfig.In_RequestPosition.Address, addressConfig.In_QrCodePhotoRequest.Address, addressConfig.In_ScrewTeachExe.Address, addressConfig.In_Screw_checkExe.Address, addressConfig.In_PickINC.Address, addressConfig.In_Throw.Address, addressConfig.RunMode.Address, addressConfig.AutoRunning.Address, addressConfig.ManualMode.Address, addressConfig.ClearMode.Address, addressConfig.Fault.Address, addressConfig.In_FzCameraPixExe.Address, }; var systemconfig = _configService.GetSystemConfiguration(); for (int i = 0; i < 10; i++) { if (systemconfig.DoubleSoftMode && i == 1) { continue; } monitorNodeIds.Add(string.Format(addressConfig.In_MesCheck.Address, i)); } //订阅PLC地址变化 //plc.SubscribeNodes("AutoSensor", monitorNodeIds, PlcCommandTrigger, OpcUaSubscriptionMode.CustomPolling); plc.SubscribeNodes("AutoSensor", monitorNodeIds, PlcCommandTrigger, OpcUaSubscriptionMode.CustomPolling); // 2. 监听重连事件,重连成功后自动重新订阅 plc.ConnectChangedEvent -= Plc_ConnectChanged; // 防止重复注册 plc.ConnectChangedEvent += Plc_ConnectChanged; // 定义重连处理方法(写在同一个类里即可) void Plc_ConnectChanged(object sender, bool isConnected) { if (isConnected) { // 重连成功后,重新订阅节点 plc.SubscribeNodes("AutoSensor", monitorNodeIds, PlcCommandTrigger, OpcUaSubscriptionMode.CustomPolling); } } } /// /// 将产品的参数统一写入PLC /// /// public async Task WriteParameterToPlcExAsync() { await WritePositionToPlcAsync();// 写入点位信息给PLC //await WriteParameterToPlcAsync();// 写入组装上下限给PLC await WriteProductLoadModeToPLC();//把当前产品型号,写入PLC } /// /// 写入点位信息给PLC /// /// public async Task WritePositionToPlcAsync() { var addressConfig = _configService.GetPlcAddresses(); var plc = _plcService.GetPlcByNumber(addressConfig.PlcNo) as OpcUaClientPLC; if (plc == null || !plc.IsConnected) return; var current = _productService.GetCurrentProduct(); if (current == null) return; _LastAutoRunning = plc.ReadNode(addressConfig.AutoRunning.Address); if (current.PickCameraPoints != null && current.PickCameraPoints.Count > 0) { foreach (var item in current.PickCameraPoints) { await item.WriteToPlcAddress(addressConfig.Out_PickCamera, plc); } } if (current.PickPoints != null && current.PickPoints.Count > 0) { foreach (var item in current.PickPoints) { await item.WriteToPlcAddress(addressConfig.Out_Pick, plc, item.Feeder); //await item.WriteToPlcAddress(addressConfig.Out_Pick, plc); } } if (current.SecPosCameraPoints != null && current.SecPosCameraPoints.Count > 0) { foreach (var item in current.SecPosCameraPoints) { await item.WriteToPlcAddress(addressConfig.Out_SecPosCamera, plc); } } if (current.ScrewCameraPoints != null && current.ScrewCameraPoints.Count > 0) { int index = 0; foreach (var item in current.ScrewCameraPoints) { //根据使用的点位进行排序 if (item.IsUse) { index++; await item.WriteToPlcAddress(addressConfig.Out_ScrewCamera, plc, index); } } } if (current.ScrewPoints != null && current.ScrewPoints.Count > 0) { int index = 0; foreach (var item in current.ScrewPoints) { if (item.IsUse) { index++; await item.WriteToPlcAddressEx(addressConfig.Out_Screw, plc, index); } } await plc.WriteNodeAsync(addressConfig.Out_WorkNum.Address, (Int16)index); } if (current.CheckCameraPoints != null && current.CheckCameraPoints.Count > 0) { int index = 0; foreach (var item in current.CheckCameraPoints) { if (item.IsUse) { index++; await item.WriteToPlcAddress(addressConfig.Out_CheckCamera, plc, index); } } } if (current.QrCodePoints != null && current.QrCodePoints.Count > 0) { foreach (var item in current.QrCodePoints) { await item.WriteToPlcAddress(addressConfig.Out_QrCodeCamera, plc); } } } /// /// 将当前产品类型写入PLC /// /// public async Task WriteProductLoadModeToPLC(int modeIndex) { var addressConfig = _configService.GetPlcAddresses(); var plc = _plcService.GetPlcByNumber(addressConfig.PlcNo) as OpcUaClientPLC; if (plc == null || !plc.IsConnected) return; await plc.WriteNodeAsync(addressConfig.Out_ProductLoadMode.Address, (Int16)modeIndex); } /// /// 将当前产品类型写入PLC /// /// public async Task WriteProductLoadModeToPLC() { var addressConfig = _configService.GetPlcAddresses(); var plc = _plcService.GetPlcByNumber(addressConfig.PlcNo) as OpcUaClientPLC; if (plc == null || !plc.IsConnected) return; var current = _productService.GetCurrentProduct(); if (current == null) return; var qMes = await _systemDatabaseService.QueryProductFromMesAsync(current.Name); if (qMes == null) return; await plc.WriteNodeAsync(addressConfig.Out_ProductLoadMode.Address, (Int16)qMes.ProductLoadMode); } PointF[] DowmCameraResults = new PointF[10]; PointF[] UpCameraPutResults = new PointF[10]; bool _LastDowmCameraExe = false; bool _LastScrewTeachExe = false; bool _LastUpCameraPickExe = false; bool _LastScrewFuJianExe = false; bool _LastUpCameraPutExe = false; bool _LastUPCameracheckExe = false; bool _LastFixedCameraPickExe = false; bool _LastFixedCameraPutExe = false; bool _LastFixedCameracheckExe = false; bool _LastFzCameracheckExe = false; bool _LastThrowExe = false; bool _LastRunModel = false; public bool _LastAutoRunning = false; bool _LastManualMode = false; bool _LastClearMode = false; bool _LastFault = false; Int16 _LastWorkStart = 0; Int16 _LastWorkDone = 0; Int16 _LastRequestPosition = 0; Int16 _LastQrCodePhotoRequest = 0; Int16[] _LastMesCheck = new Int16[10]; Int16 _LastPickINC = 0; DateTime[] _LastMesCheckTime = new DateTime[10]; Dictionary mes_cc = new Dictionary(); Dictionary Point = new Dictionary(); string[] ProductMainSN { get; set; } = new string[10];//产品主sn string[] model_num { get; set; } = new string[10];//产品model_num string[] wo { get; set; } = new string[10];//产品wo string[] color { get; set; } = new string[10];//产品color string[] ppid { get; set; } = new string[10];//产品ppid string[] revision { get; set; } = new string[10];//产品revision Dictionary dataToMes = new Dictionary();//上传mes的数据 List resultToMes { get; set; } = new List();//上传mes的数据 /// /// PLC命令触发时 /// /// private async void PlcCommandTrigger((string key, string nodeId, object value) tuple) { try { if (tuple.key != "AutoSensor") { SendTaskMessage("PLC指令错误", MessageLevel.Debug); return; } var addressConfig = _configService.GetPlcAddresses(); var plc = _plcService.GetPlcByNumber(addressConfig.PlcNo) as OpcUaClientPLC; if (plc == null || !plc.IsConnected) { SendTaskMessage("plc为空", MessageLevel.Debug); return; } // 获取当前产品 var currentProduct = _productService.GetCurrentProduct(); if (currentProduct == null) { return; } ////获取产品编码 //_productService.CurrentProductCode = plc.ReadNode(string.Format(addressConfig.In_Code.Address, 0)); ////如果产品编码为空,则以当前的时间戳作为编码 //if (string.IsNullOrEmpty(_productService.CurrentProductCode)) //{ // _productService.CurrentProductCode = DateTime.Now.ToString("yyyyMMddHHmmssfff"); //} // 触发下相机拍照 if (addressConfig.In_DowmCameraExe.Address.Contains(tuple.nodeId)) { if (tuple.value is bool state) { if (_LastDowmCameraExe != state) { _LastDowmCameraExe = state; } else { return; } if (state) { SendTaskMessage(Lang.触发下相机拍照, MessageLevel.Debug); //相机的编号 Int16 cameraIndex = plc.ReadNode(addressConfig.In_DowmCameraNum.Address); SendTaskMessage(string.Format(Lang.编号0, cameraIndex), MessageLevel.Info); // 获取流程 var process = _productService.GetCurrentProductProcedureModelById(currentProduct.FixedUpCamera1ProcedureId); if (process == null) { SendTaskMessage(string.Format(Lang.固定向上相机1视觉流程流程ID0, currentProduct.FixedUpCamera1ProcedureId), MessageLevel.Alarm); await plc.WriteNodeAsync(addressConfig.Out_DownCameraStatus.Address, (Int16)2); return; } // 获取机器人当前坐标 var robot = _robotService.GetAllRobots().FirstOrDefault(); if (robot == null) { SendTaskMessage(Lang.未找到机器人无法执行上相机锁附组装拍照视觉任务, MessageLevel.Alarm); await plc.WriteNodeAsync(addressConfig.Out_DownCameraStatus.Address, (Int16)2); return; } var currentPosition = robot.GetRobotPos(); // 执行视觉任务 var _cts = new CancellationTokenSource(); ImageParameters imgPara = new ImageParameters(); imgPara.SNCode = productSn; var visionResult = await _remoteCommandService.ExecutePhotoGetSinglePoint(process, null, null, _cts.Token, cameraIndex, cameraIndex, null, imgPara); if (visionResult.IsSucceed) { //披头偏差 float offsetX = (float)(visionResult.X - currentPosition.X); float offsetY = (float)(visionResult.Y - currentPosition.Y); DowmCameraResults[cameraIndex] = new PointF(offsetX, offsetY); await plc.WriteNodeAsync(addressConfig.Out_DownCameraStatus.Address, (Int16)1); return; } else { await plc.WriteNodeAsync(addressConfig.Out_DownCameraStatus.Address, (Int16)2); return; } } else { SendTaskMessage(Lang.触发下相机拍照信号已关闭, MessageLevel.Alarm); await plc.WriteNodeAsync(addressConfig.Out_DownCameraStatus.Address, (Int16)0); } } } // 触发下相机拍照披头 if (addressConfig.In_ScrewTeachExe.Address.Contains(tuple.nodeId)) { if (tuple.value is bool state) { if (_LastScrewTeachExe != state) { _LastScrewTeachExe = state; } else { return; } if (state) { SendTaskMessage(Lang.触发下相机拍照, MessageLevel.Debug); //相机的编号 Int16 cameraIndex = plc.ReadNode(addressConfig.In_DowmCameraNum.Address); SendTaskMessage(string.Format(Lang.编号0, cameraIndex), MessageLevel.Info); // 获取流程 var process = _productService.GetCurrentProductProcedureModelById(currentProduct.FixedUpCamera1ProcedureId); if (process == null) { SendTaskMessage(string.Format(Lang.固定向上相机1视觉流程流程ID0, currentProduct.FixedUpCamera1ProcedureId), MessageLevel.Alarm); await plc.WriteNodeAsync(addressConfig.Out_ScrewTeachOk.Address, (Int16)2); return; } // 获取机器人当前坐标 var robot = _robotService.GetAllRobots().FirstOrDefault(); if (robot == null) { SendTaskMessage(Lang.未找到机器人无法执行上相机锁附组装拍照视觉任务, MessageLevel.Alarm); await plc.WriteNodeAsync(addressConfig.Out_ScrewTeachOk.Address, (Int16)2); return; } var currentPosition = robot.GetRobotPos(); // 执行视觉任务 var _cts = new CancellationTokenSource(); ImageParameters imgPara = new ImageParameters(); imgPara.SNCode = productSn; var visionResult = await _remoteCommandService.ExecutePhotoGetSinglePoint(process, null, null, _cts.Token, cameraIndex, cameraIndex, null, imgPara); if (visionResult.IsSucceed) { //披头偏差 float offsetX = (float)(visionResult.X - currentPosition.X); float offsetY = (float)(visionResult.Y - currentPosition.Y); DowmCameraResults[cameraIndex] = new PointF(offsetX, offsetY); await plc.WriteNodeAsync(addressConfig.Out_ScrewTeachOk.Address, (Int16)1); return; } else { await plc.WriteNodeAsync(addressConfig.Out_ScrewTeachOk.Address, (Int16)2); return; } } else { SendTaskMessage(Lang.触发下相机拍照信号已关闭, MessageLevel.Alarm); await plc.WriteNodeAsync(addressConfig.Out_ScrewTeachOk.Address, (Int16)0); } } } // 触发上相机取料拍照 else if (addressConfig.In_UpCameraPickExe.Address.Contains(tuple.nodeId)) { if (tuple.value is bool state) { if (_LastUpCameraPickExe != state) { _LastUpCameraPickExe = state; } else { return; } if (state) { SendTaskMessage(Lang.触发上相机取料拍照, MessageLevel.Debug); //相机的编号 //Int16 cameraIndex = plc.ReadNode(addressConfig.In_UpCameraPickNum.Address); await plc.WriteNodeAsync(addressConfig.Out_UpCameraPickStatus.Address, (Int16)1); } else { SendTaskMessage(Lang.触发上相机取料拍照信号已关闭, MessageLevel.Alarm); await plc.WriteNodeAsync(addressConfig.Out_UpCameraPickStatus.Address, (Int16)0); } } } // 上相机锁附/组装拍照 else if (addressConfig.In_UpCameraPutExe.Address.Contains(tuple.nodeId)) { if (tuple.value is bool state) { if (_LastUpCameraPutExe != state) { _LastUpCameraPutExe = state; } else { return; } if (state) { SendTaskMessage(Lang.触发上相机锁附组装拍照, MessageLevel.Debug); //相机的编号 Int16 cameraIndex = plc.ReadNode(addressConfig.In_UpCameraPutNum.Address); SendTaskMessage(string.Format(Lang.编号0, cameraIndex), MessageLevel.Info); //视觉流程ID Guid procedureId = currentProduct.MoveDownCameraLockPhotoProcedureId; List plcPoints = new List(); foreach (var item in currentProduct.ScrewCameraPoints) { if (item.IsUse) { plcPoints.Add(item); } } int num = plcPoints.Count; //获取锁付拍照的点位 var pos = plcPoints[cameraIndex - 1]; if (pos != null) { procedureId = pos.VisionProcessID; } // 获取流程 var process = _productService.GetCurrentProductProcedureModelById(procedureId); if (process == null) { SendTaskMessage(string.Format(Lang.未找到上相机锁附组装拍照对应的视觉流程流程ID0, procedureId), MessageLevel.Alarm); await plc.WriteNodeAsync(addressConfig.Out_UpCameraPutStatus.Address, (Int16)2); return; } // 获取机器人当前坐标 var robot = _robotService.GetAllRobots().FirstOrDefault(); if (robot == null) { SendTaskMessage(Lang.未找到机器人无法执行上相机锁附组装拍照视觉任务, MessageLevel.Alarm); await plc.WriteNodeAsync(addressConfig.Out_UpCameraPutStatus.Address, (Int16)2); return; } var currentPosition = robot.GetRobotPos(); var systemConfig = _configService.GetSystemConfiguration(); // 执行视觉任务 var _cts = new CancellationTokenSource(); ImageParameters imgPara = new ImageParameters(); imgPara.SNCode = productSn; var visionResult = await _remoteCommandService.ExecutePhotoGetSinglePoint(process, null, new double[] { currentPosition.X, currentPosition.Y, currentPosition.U }, _cts.Token, cameraIndex, num, null, imgPara); if (visionResult.IsSucceed) { //是拍一个锁一个还是拍两个点锁所有 if (systemConfig.IsBasePartOpenLightKB) { if (mes_cc.ContainsKey(visionResult.distance.Split(',')[0])) { mes_cc[visionResult.distance.Split(',')[0]] = visionResult.distance.Split(',')[1]; } else { mes_cc.Add(visionResult.distance.Split(',')[0], visionResult.distance.Split(',')[1]); } } //拍1打1 if (systemConfig.WorkMode == 1) { UpCameraPutResults[1] = new PointF((float)visionResult.X, (float)visionResult.Y); } else if(systemConfig.WorkMode==2) { UpCameraPutResults[cameraIndex] = new PointF((float)visionResult.X, (float)visionResult.Y); } else if(systemConfig.WorkMode==3) { if (cameraIndex <= 3) { UpCameraPutResults[1] = new PointF((float)visionResult.X, (float)visionResult.Y); } else { UpCameraPutResults[cameraIndex] = new PointF((float)visionResult.X, (float)visionResult.Y); } } if (resultToMes.Contains($"Screw{cameraIndex}_Fail")) { resultToMes.Remove($"Screw{cameraIndex}_Fail"); SendTaskMessage($"Screw{cameraIndex}:重复拍照成功", MessageLevel.Debug); } await plc.WriteNodeAsync(addressConfig.Out_UpCameraPutStatus.Address, (Int16)1); } else { if (systemConfig.IsBasePartOpenLightKB) { if (mes_cc.ContainsKey(visionResult.distance.Split(',')[0])) { mes_cc[visionResult.distance.Split(',')[0]] = visionResult.distance.Split(',')[1]; } else { if (mes_cc.ContainsKey(cameraIndex.ToString())) { mes_cc[cameraIndex.ToString()] = "0"; } else { mes_cc.Add(cameraIndex.ToString(), "0"); } } } if (!resultToMes.Contains($"Screw{cameraIndex}_Fail")) { resultToMes.Add($"Screw{cameraIndex}_Fail"); SendTaskMessage($"Screw{cameraIndex}:拍照失败", MessageLevel.Error); } await plc.WriteNodeAsync(addressConfig.Out_UpCameraPutStatus.Address, (Int16)2); } } else { SendTaskMessage(Lang.触发上相机锁附组装拍照信号已关闭, MessageLevel.Alarm); await plc.WriteNodeAsync(addressConfig.Out_UpCameraPutStatus.Address, (Int16)0); } } } // 上相机检测拍照 else if (addressConfig.In_UPCameracheckExe.Address.Contains(tuple.nodeId)) { if (tuple.value is bool state) { if (_LastUPCameracheckExe != state) { _LastUPCameracheckExe = state; } else { return; } if (state) { SendTaskMessage(Lang.触发上相机检测拍照, MessageLevel.Debug); //相机的编号 Int16 cameraIndex = plc.ReadNode(addressConfig.In_UPCameracheckNum.Address); SendTaskMessage(string.Format(Lang.编号0, cameraIndex), MessageLevel.Info); //获取锁付拍照的点位 List plcPoints = new List(); foreach (var item in currentProduct.CheckCameraPoints) { if (item.IsUse) { plcPoints.Add(item); } } int num = plcPoints.Count; //视觉流程ID Guid procedureId = Guid.Empty; //获取锁付拍照的点位 var pos = plcPoints[cameraIndex - 1]; if (pos != null) { procedureId = pos.VisionProcessID; } // 获取流程 var process = _productService.GetCurrentProductProcedureModelById(procedureId); if (process == null) { SendTaskMessage(string.Format("未找到点位:{0}的视觉流程:{1}", cameraIndex, procedureId), MessageLevel.Alarm); await plc.WriteNodeAsync(addressConfig.Out_UPCameracheckStatus.Address, (Int16)2); return; } // 获取机器人当前坐标 var robot = _robotService.GetAllRobots().FirstOrDefault(); if (robot == null) { SendTaskMessage("未找到机器人无法执行 上相机检测拍照视觉任务", MessageLevel.Alarm); await plc.WriteNodeAsync(addressConfig.Out_UpCameraPutStatus.Address, (Int16)2); return; } var currentPosition = robot.GetRobotPos(); // 执行视觉任务 var _cts = new CancellationTokenSource(); var visionResult = await _remoteCommandService.ExecutePhotoGetSinglePoint(process, null, new double[] { currentPosition.X, currentPosition.Y, currentPosition.U }, _cts.Token, cameraIndex, num, null, null); if (visionResult.IsSucceed) { await plc.WriteNodeAsync(addressConfig.Out_UPCameracheckStatus.Address, (Int16)1); SendTaskMessage($"点位{cameraIndex}检测OK", MessageLevel.Debug); } else { await plc.WriteNodeAsync(addressConfig.Out_UPCameracheckStatus.Address, (Int16)2); SendTaskMessage($"点位{cameraIndex}检测NG", MessageLevel.Error); } } else { SendTaskMessage(Lang.触发上相机检测拍照信号已关闭, MessageLevel.Alarm); await plc.WriteNodeAsync(addressConfig.Out_UPCameracheckStatus.Address, (Int16)0); } } } //下相机拍照螺丝复检 else if (addressConfig.In_Screw_checkExe.Address.Contains(tuple.nodeId)) { Point.Clear(); if (tuple.value is bool state) { if (_LastScrewFuJianExe != state) { _LastScrewFuJianExe = state; } else { return; } if (state) { SendTaskMessage(Lang.触发相机拍照螺丝复检, MessageLevel.Debug); //相机的编号 Int16 cameraIndex = plc.ReadNode(addressConfig.In_DowmCameraNum.Address); SendTaskMessage(string.Format(Lang.编号0, cameraIndex), MessageLevel.Info); // 获取流程 var process = _productService.GetCurrentProductProcedureModelById(currentProduct.FixedUpCameraProcedureId); if (process == null) { SendTaskMessage(string.Format(Lang.固定向上相机视觉流程流程ID, currentProduct.FixedUpCameraProcedureId), MessageLevel.Alarm); await plc.WriteNodeAsync(addressConfig.Out_Screw_check_status.Address, (Int16)2); return; } // 获取机器人当前坐标 var robot = _robotService.GetAllRobots().FirstOrDefault(); if (robot == null) { SendTaskMessage(Lang.未找到机器人无法执行上相机锁附组装拍照视觉任务, MessageLevel.Alarm); await plc.WriteNodeAsync(addressConfig.Out_Screw_check_status.Address, (Int16)2); return; } var currentPosition = robot.GetRobotPos(); // 执行视觉任务 var _cts = new CancellationTokenSource(); ImageParameters imgPara = new ImageParameters(); imgPara.SNCode = productSn; var visionResult = await _remoteCommandService.ExecutePhotoGetSinglePoint1(process, null, null, _cts.Token, cameraIndex, cameraIndex, null, imgPara); //string X, Y, U; if (visionResult.IsSucceed) { //披头偏差 //float offsetX = (float)(visionResult.X - currentPosition.X); //float offsetY = (float)(visionResult.Y - currentPosition.Y); string[] keys = { "X", "Y", "U" }; double[] values = { visionResult.X, visionResult.Y, visionResult.U }; for (int i = 0; i < keys.Length; i++) { Point.Add(keys[i], values[i]); } // 2. 生成CSV文件(替换成你的保存路径) string csvPath = @"D:\视觉检测数据\XYU结果.csv"; // 调用生成方法(appendMode=true 每次拍照新增一行,不会覆盖历史数据) ExportDictionaryToCsv(Point, csvPath, appendMode: true); //DowmCameraResults[cameraIndex] = new PointF(offsetX, offsetY); //ExportDictionaryToCsv1(ProductMainSN[1], visionResult.X, visionResult.Y, visionResult.U); await plc.WriteNodeAsync(addressConfig.Out_Screw_check_status.Address, (Int16)1); return; } else { await plc.WriteNodeAsync(addressConfig.Out_Screw_check_status.Address, (Int16)2); return; } } else { SendTaskMessage(Lang.触发下相机拍照信号已关闭, MessageLevel.Alarm); await plc.WriteNodeAsync(addressConfig.Out_Screw_check_status.Address, (Int16)0); } } } // 固定相机取料拍照 else if (addressConfig.In_FixedCameraPickExe.Address.Contains(tuple.nodeId)) { if (tuple.value is bool state) { if (_LastFixedCameraPickExe != state) { _LastFixedCameraPickExe = state; } else { return; } if (state) { SendTaskMessage(Lang.触发固定相机取料拍照, MessageLevel.Debug); //相机的编号 //Int16 cameraIndex = plc.ReadNode(addressConfig.In_FixedCameraPickNum.Address); await plc.WriteNodeAsync(addressConfig.Out_FixedCameraPickStatus.Address, (Int16)1); } else { SendTaskMessage(Lang.触发固定相机取料拍照信号已关闭, MessageLevel.Alarm); await plc.WriteNodeAsync(addressConfig.Out_FixedCameraPickStatus.Address, (Int16)0); } } } // 固定相机锁附/组装拍照 else if (addressConfig.In_FixedCameraPutExe.Address.Contains(tuple.nodeId)) { if (tuple.value is bool state) { if (_LastFixedCameraPutExe != state) { _LastFixedCameraPutExe = state; } else { return; } if (state) { SendTaskMessage(Lang.触发固定相机锁附组装拍照, MessageLevel.Debug); //相机的编号 //Int16 cameraIndex = plc.ReadNode(addressConfig.In_FixedCameraPutNum.Address); await plc.WriteNodeAsync(addressConfig.Out_FixedCameraPutStatus.Address, (Int16)1); } else { SendTaskMessage(Lang.触发固定相机锁附组装拍照信号已关闭, MessageLevel.Alarm); await plc.WriteNodeAsync(addressConfig.Out_FixedCameraPutStatus.Address, (Int16)0); } } } // 固定相机检测拍照 else if (addressConfig.In_FixedCameracheckExe.Address.Contains(tuple.nodeId)) { if (tuple.value is bool state) { if (_LastFixedCameracheckExe != state) { _LastFixedCameracheckExe = state; } else { return; } if (state) { SendTaskMessage(Lang.触发固定相机检测拍照, MessageLevel.Debug); //相机的编号 //Int16 cameraIndex = plc.ReadNode(addressConfig.In_FixedCameracheckNum.Address); await plc.WriteNodeAsync(addressConfig.Out_FixedCameracheckStatus.Address, (Int16)1); } else { SendTaskMessage(Lang.触发固定相机检测拍照信号已关闭, MessageLevel.Alarm); await plc.WriteNodeAsync(addressConfig.Out_FixedCameracheckStatus.Address, (Int16)0); } } } // 翻转相机检测拍照 else if (addressConfig.In_FzCameraPixExe.Address.Contains(tuple.nodeId)) { if (tuple.value is bool state) { if (_LastFzCameracheckExe != state) { _LastFzCameracheckExe = state; } else { return; } if (state) { int i = 5; SendTaskMessage("触发翻转相机拍照", MessageLevel.Debug); var code = plc.ReadNode(string.Format(addressConfig.In_Code.Address, 1)); string resultphoto = ""; //这里缺少从MES获取产品配方的内容,需要补充 _LastMesCheckTime[5] = (await _mesService.GetServerTimeAsync(5, code, 2)).Now; var stationConfig2 = _configService.GetDeviceInfo(0); LogHelper.WriteLogMes($"【mes开始QUERY】"); SendTaskMessage($"mes开始QUERY", MessageLevel.Info); //if (stationConfig2.EnableMES) //{ // (bool isSuccess, string response) = await _mesService.StationGetExAsync(code, "", 2); // if (isSuccess) // { // string ProductMainSN_Temp = ""; // LogHelper.WriteLogMes($"询问mes成功"); // SendTaskMessage($"询问mes成功:{response}", MessageLevel.Debug); // string[] item = response.Split('\n'); // //获取过度件sn // string snItem = item.FirstOrDefault(f => f.Contains("sn=")); // if (snItem != null) // { // ProductMainSN_Temp = snItem.Replace("sn=", "").Trim(); // SendTaskMessage($"过度件sn:{ProductMainSN_Temp}", MessageLevel.Info); // } // LogHelper.WriteLogMes($"询问mes成功"); // SendTaskMessage($"询问mes成功:{response}", MessageLevel.Debug); // //20260214,螺丝机特别版 // (bool isSuccess2, string response2) = await _mesService.StationGetAsync(ProductMainSN_Temp, "", 2);//询问mes // if (isSuccess2) // { // LogHelper.WriteLogMes($"询问mes成功"); // SendTaskMessage($"询问mes成功:{response2}", MessageLevel.Debug); // string[] items = response2.Split('\n'); // //获取产品主sn和wo用于后续上传数据使用 // string snItem2 = items.FirstOrDefault(f => f.Contains("sn=")); // if (snItem2 != null) // { // var codesn = snItem2.Replace("sn=", "").Trim(); // //_productService.CurrentProductCode = codesn; // SendTaskMessage($"产品主sn:{codesn}", MessageLevel.Info); // } // string woItem = item.FirstOrDefault(f => f.Contains("wo=")); // if (woItem != null) // { // wo[i] = woItem.Replace("wo=", "").Trim(); // SendTaskMessage($"工单信息:{wo[i]}", MessageLevel.Info); // } // string modelItem = item.FirstOrDefault(f => f.Contains("model_num=")); // if (modelItem != null) // { // model_num[i] = modelItem.Replace("model_num=", "").Trim(); // SendTaskMessage($"model_num:{model_num[i]}", MessageLevel.Info); // } // string colorItem = item.FirstOrDefault(f => f.Contains("color=")); // if (colorItem != null) // { // color[i] = colorItem.Replace("color=", "").Trim(); // SendTaskMessage($"color:{color[i]}", MessageLevel.Info); // } // string ppidItem = item.FirstOrDefault(f => f.Contains("ppid=")); // if (ppidItem != null) // { // ppid[5] = ppidItem.Replace("ppid=", "").Trim(); // SendTaskMessage($"ppid:{ppid[5]}", MessageLevel.Info); // } // string revisionItem = item.FirstOrDefault(f => f.Contains("stage=")); // if (revisionItem != null) // { // revision[5] = revisionItem.Replace("stage=", "").Trim(); // SendTaskMessage($"stage:{revision[5]}", MessageLevel.Info); // } // LogHelper.WriteLogMes($"第二次询问mes成功"); // SendTaskMessage($"第二次询问mes成功:{response2}", MessageLevel.Debug); // } // else // { // LogHelper.WriteLogMes($"第二次询问mes失败"); // SendTaskMessage($"第二次询问mes失败:{response2}", MessageLevel.Error); // await plc.WriteNodeAsync(addressConfig.Out_FzCameraPickStatus.Address, (Int16)2); // return; // } // } // else // { // LogHelper.WriteLogMes($"询问mes失败"); // SendTaskMessage($"询问mes失败:{response}", MessageLevel.Error); // await plc.WriteNodeAsync(addressConfig.Out_FzCameraPickStatus.Address, (Int16)2); // return; // } //} //else //{ // SendTaskMessage($"mes未启用", MessageLevel.Info); //} Guid FzCameraProcedureId = currentProduct.FzCameraProcedureId; var FzCameraProcess = _productService.GetCurrentProductProcedureModelById(FzCameraProcedureId); if (FzCameraProcess == null) { SendTaskMessage($"未找到翻转相机视觉流程,流程ID: {FzCameraProcedureId}", MessageLevel.Alarm); await plc.WriteNodeAsync(addressConfig.Out_FzCameraPickStatus.Address, (Int16)2); return; } SendTaskMessage($"开始执行翻转相机视觉流程", MessageLevel.Debug); var _cts = new CancellationTokenSource(); await _remoteCommandService.SetLightBeforePhoto(FzCameraProcess); var FzCameraResult = _remoteCommandService.ExecutePhotoGetDistance(FzCameraProcess, null); if (FzCameraResult) { SendTaskMessage("翻转相机拍照成功", MessageLevel.Debug); resultphoto = "Pass"; await plc.WriteNodeAsync(addressConfig.Out_FzCameraPickStatus.Address, (Int16)1); } else { SendTaskMessage("翻转相机拍照失败", MessageLevel.Alarm); resultphoto = "Fail"; await plc.WriteNodeAsync(addressConfig.Out_FzCameraPickStatus.Address, (Int16)2); } if (stationConfig2.SaveCsv) { List DFC = new List(); string saveppid = "NA"; string names = currentProduct.Name; if (stationConfig2.SaveCsvPPID) saveppid = ppid[i]; DateTime servertime = DateTime.Now; // Dictionary adjacentDiffDict1 = GetAdjacentDiffDict(keyData1); //判断锁付路径字符串是否为空,如果不为空则保存,否则不保存 if (!string.IsNullOrEmpty(stationConfig2.SaveCsvPath)) { DFC.Add(new DataFormCtq { PPID = saveppid, PROCESS_NAME = stationConfig2.DataInfo.PROCESS_NAME, PROCESS_START_TIME = _LastMesCheckTime[5].ToString("yyyy-MM-ddTHH:mm:ss.fffffff") + "+08:00", CTQ_NAME = "FzCameraPhoto", CTQ_VALUE = 0, CTQ_LSL = 0, CTQ_USL = 0, PROCESS_END_TIME = servertime.ToString("yyyy-MM-ddTHH:mm:ss.fffffff") + "+08:00", SUPPLIER_NAME = stationConfig2.DataInfo.SUPPLIER_NAME, COMMODITY_TYPE = stationConfig2.DataInfo.COMMODITY_TYPE, REVISION = stationConfig2.DataInfo.REVISION + "_" + revision[i], WO = "wo",// MFG_LOT = stationConfig2.DataInfo.MFG_LOT,// MFG_ASSY_LINE = stationConfig2.DataInfo.MFG_ASSY_LINE, PROCESS_OUTCOME = resultphoto, PROCESS_MACHINE_ID = stationConfig2.DataInfo.PROCESS_MACHINE_ID, CTQ_UNIT_OF_MEASURE = "mm", ERROR_MESSAGE = "", PD_ATTR_01 = _productService.CurrentProductCode,// PD_ATTR_02 = "", PD_ATTR_03 = "", PD_ATTR_04 = code, PD_ATTR_05 = "", }); bool recordResult = RecordStationResult(0, DFC, _productService.CurrentProductCode, servertime.Subtract(_LastMesCheckTime[5]).TotalSeconds, _LastMesCheckTime[5], stationConfig2.SaveCsvPath); if (recordResult) { SendTaskMessage($"翻转相机拍照产品记录保存成功", MessageLevel.Debug); } else { SendTaskMessage($"{stationConfig2.DeviceName}产品记录保存失败", MessageLevel.Error); } } else { SendTaskMessage($"{stationConfig2.DeviceName}工位未配置保存路径,无法保存产品记录", MessageLevel.Error); } } } else { SendTaskMessage($"触发翻转相机检测拍照信号已关闭", MessageLevel.Alarm); await plc.WriteNodeAsync(addressConfig.Out_FzCameraPickStatus.Address, (Int16)0); } } } // 开始锁付 else if (addressConfig.In_WorkStart.Address.Contains(tuple.nodeId)) { if (tuple.value is Int16 state) { if (_LastWorkStart != state) { _LastWorkStart = state; } else { return; } var code = plc.ReadNode(string.Format(addressConfig.In_Code.Address, 0)); if (state == 1) { SendTaskMessage(Lang.收到开始锁付信号, MessageLevel.Debug); StartScrewTime = DateTime.Now; //位置编号 Int16 positionIndex = plc.ReadNode(addressConfig.In_ProductNum.Address); SendTaskMessage(string.Format(Lang.编号0, positionIndex), MessageLevel.Info); // 开始采集压力值,ProductMainSN[i] //StartPressureCollection(currentProduct.Name, _productService.CurrentProductCode, 0); StartPressureCollection(currentProduct.Name, _productService.CurrentProductCode, 0); } else { SendTaskMessage(Lang.开始锁付信号已关闭, MessageLevel.Alarm); // 关闭时的处理(如需)可放在这里 } } } // 停止锁付 else if (addressConfig.In_WorkDone.Address.Contains(tuple.nodeId)) { if (tuple.value is Int16 state) { if (_LastWorkDone != state) { _LastWorkDone = state; } else { return; } if (state == 1 || state == 2) { if (TorqueStatus) { } SendTaskMessage(Lang.收到停止锁付信号, MessageLevel.Debug); //位置编号 Int16 positionIndex = plc.ReadNode(addressConfig.In_ProductNum.Address); SendTaskMessage(string.Format(Lang.编号0, positionIndex), MessageLevel.Info); //获取当前锁付点 var screwPoint = currentProduct.ScrewPoints.FirstOrDefault(p => p.Number == positionIndex); //获取当前的机器人点位 var robot = _robotService.GetAllRobots().FirstOrDefault(); var currentPosition = robot.GetRobotPos(); //读取压力值 float pressure = _maxPressure; //当前用户名 string currentUserName = _systemDatabaseService.GetCurrentUser().UserName; //吸嘴最后一次的记录 var nozzleRecord = await _systemDatabaseService.GetLatestNozzleChangeRecordAsync(); //最后一次的吸嘴编号 string nozzleNumber = nozzleRecord != null ? nozzleRecord.NozzleBatchNumber : "N/A"; //披头最后一次的记录 var headRecord = await _systemDatabaseService.GetLatestHeadChangeRecordAsync(); //最后一次的披头编号 string headNumber = headRecord != null ? headRecord.HeadBatchNumber : "N/A"; await Task.Delay(60); //获取电批结果 bool isSucced = ScrewDriver.GetData(); LockResult lockResult = new LockResult(); lockResult.ScrewdriverGetDataSucceed = isSucced; lockResult.Number = positionIndex; lockResult.LockPassed = ScrewDriver.Success; lockResult.ProductName = currentProduct.Name; lockResult.ProductNumber = _productService.CurrentProductCode; lockResult.LockTurns = (float)ScrewDriver.Truns; lockResult.LockTorque = (float)ScrewDriver.TorqueValue; lockResult.LockDuration = (float)ScrewDriver.FinalTime; lockResult.ScrewdriverProgramNumber = ScrewDriver.ProgramNumber; lockResult.ResultDetails = ScrewDriver.ErrorInfo; lockResult.Pressure = pressure; lockResult.TargetTorqueUpperLimit = ScrewDriver.TargetTorqueUpperLimit; lockResult.TargetTorqueLowerLimit = ScrewDriver.TargetTorqueLowerLimit; lockResult.TargetTurnsUpperLimit = ScrewDriver.TargetTurnsUpperLimit / 360; lockResult.TargetTurnsLowerLimit = ScrewDriver.TargetTurnsLowerLimit / 360; lockResult.TargetLockSpeed = ScrewDriver.TargetLockSpeed; lockResult.PositionX = currentPosition.X; lockResult.PositionY = currentPosition.Y; lockResult.StartPositionZ = screwPoint.Z_Position_Start; lockResult.EndPositionZ = currentPosition.Z; lockResult.LockDescentSpeed = screwPoint.Z_Velocity_Stop; lockResult.FeederNumber = screwPoint != null ? screwPoint.Feeder : 0; lockResult.NozzleNumber = nozzleNumber; lockResult.HeadNumber = headNumber; lockResult.OperatorName = currentUserName; lockResult.ProductImagePath = ""; lockResult.WaveDatas = ScrewDriver.WaveDatas; //pressure = 0; //获取系统参数 var systemConfig = _configService.GetSystemConfiguration(); //判断是否保存锁付曲线 if (systemConfig.IsSaveScrewCurve) { //锁付曲线的保存路径为:systemConfig.ScrewCurvePath//年月//年月日//配方名//产品SN//产品SN+螺丝索引 string datePath = DateTime.Now.ToString("yyyy-MM") + "\\" + DateTime.Now.ToString("yyyy-MM-dd") + "\\"; string dirPath = System.IO.Path.Combine(systemConfig.ScrewCurvePath, datePath, currentProduct.Name, lockResult.ProductNumber); if (!Directory.Exists(dirPath)) { Directory.CreateDirectory(dirPath); } string curveFileName = $"{lockResult.ProductNumber}-{lockResult.Number}.png"; //锁付CSV保存路径 string curveFileFullPath = System.IO.Path.Combine(dirPath, $"{lockResult.ProductNumber}-{lockResult.Number}.csv"); //锁付曲线保存路径 lockResult.LockCurveImagePath = System.IO.Path.Combine(dirPath, curveFileName); var _ = lockResult.SaveToCsvAsync(curveFileFullPath); } if (lockResult.Number == 1) { dataToMes.Clear(); resultToMes.Clear(); lockResultsAll.Clear(); } //基于当前的锁付结果和产品的规格参数判断锁付结果是否合格 var stationConfig = _configService.GetDeviceInfo(0); bool torqueRes = false, turnRes = false, pressureRes = false; bool lastRes = false;//默认不合格,只有当扭力、圈数、压力都在设定的上下限范围内时,才判定为锁付合格 if (lockResult.LockTorque >= lockResult.TargetTorqueLowerLimit && lockResult.LockTorque <= lockResult.TargetTorqueUpperLimit) { torqueRes = true; } if (lockResult.Pressure >= stationConfig.DataInfo.CTQ_LSL_Pressure && lockResult.Pressure <= stationConfig.DataInfo.CTQ_USL_Pressure) { pressureRes = true; } if (lockResult.LockTurns >= lockResult.TargetTurnsLowerLimit && lockResult.LockTurns <= lockResult.TargetTurnsUpperLimit) { turnRes = true; } if (torqueRes == true && turnRes == true && pressureRes == true)//当扭力、圈数、压力都在设定的上下限范围内时,才判定为锁付合格 { lastRes = true; } if (dataToMes.Keys.Contains($"Torque{lockResult.Number}")) { dataToMes.Remove($"Torque{lockResult.Number}"); } if (dataToMes.Keys.Contains($"Turn{lockResult.Number}")) { dataToMes.Remove($"Turn{lockResult.Number}"); } if (dataToMes.Keys.Contains($"Pressure{lockResult.Number}")) { dataToMes.Remove($"Pressure{lockResult.Number}"); } lockResult.LockPassed = lastRes; dataToMes.Add($"Torque{lockResult.Number}", $"{lockResult.LockTorque:F2}"); dataToMes.Add($"Turn{lockResult.Number}", $"{lockResult.LockTurns:F2}"); dataToMes.Add($"Pressure{lockResult.Number}", $"{lockResult.Pressure:F2}"); //dataToMes.Add($"LastResult{lockResult.Number}", $"{lastRes}"); if (!lastRes)//如果不合格,则在MES结果中添加"Screw{lockResult.Number}_Fail" { if (!resultToMes.Contains($"Screw{lockResult.Number}_Fail")) { resultToMes.Add($"Screw{lockResult.Number}_Fail"); SendTaskMessage($"Screw{lockResult.Number}:锁附不合格", MessageLevel.Error); } } SendTaskMessage($"螺丝序号:{lockResult.Number},扭力:{lockResult.LockTorque},压力:{lockResult.Pressure},圈数:{lockResult.LockTurns}", MessageLevel.Info); _maxPressure = 0; lockResultsAll.Add(lockResult); App.Current.Dispatcher.Invoke(() => { _eventAggregator.GetEvent().Publish(lockResult); }); //记录锁付结果至数据库 await _systemDatabaseService.RecordLockResultAsync(lockResult); //披头数量+1 NumStatistics.HeaderNum = await _systemDatabaseService.IncrementLatestHeadLockCountAsync(currentUserName); //吸嘴数量+1 NumStatistics.NozzleNum = await _systemDatabaseService.IncrementLatestNozzleLockCountAsync(currentUserName); await plc.WriteNodeAsync(addressConfig.In_WorkDone.Address, (Int16)0); // 停止采集压力并保存 await StopAndSavePressureCollectionAsync(currentProduct.Name, _productService.CurrentProductCode, positionIndex); pressure = 0; } else { SendTaskMessage(Lang.停止锁付信号已关闭, MessageLevel.Alarm); } } } // 多相机坐标合并 else if (addressConfig.In_RequestPosition.Address.Contains(tuple.nodeId)) { if (tuple.value is Int16 state) { if (_LastRequestPosition != state) { _LastRequestPosition = state; } else { return; } if (state == 1) { SendTaskMessage(Lang.收到多相机坐标合并请求开始合并, MessageLevel.Debug); //位置编号 Int16 positionIndex = plc.ReadNode(addressConfig.In_ProductNum.Address); //是拍一个锁一个还是拍两个点锁所有 var systemConfig = _configService.GetSystemConfiguration(); //拍1打1 if (systemConfig.WorkMode == 1) { List plcPoints = new List(); foreach (var item in currentProduct.ScrewPoints) { if (item.IsUse) { plcPoints.Add(item); } } var TempPoint = plcPoints[positionIndex - 1]; if (TempPoint == null) { SendTaskMessage($"点:{positionIndex}不存在", MessageLevel.Info); await plc.WriteNodeAsync(addressConfig.Out_CameraDone.Address, (Int16)2); return; } var PositionPoint = TempPoint.Clone(); PositionPoint.X_Position = UpCameraPutResults[1].X + DowmCameraResults[1].X; PositionPoint.Y_Position = UpCameraPutResults[1].Y + DowmCameraResults[1].Y; SendTaskMessage($"点:{positionIndex},{PositionPoint.X_Position:F3},{PositionPoint.Y_Position:F4}", MessageLevel.Info); if (PositionPoint.X_Position == 0 || PositionPoint.Y_Position == 0) { SendTaskMessage("拟合位置为0,请检查", MessageLevel.Error); await plc.WriteNodeAsync(addressConfig.Out_CameraDone.Address, (Int16)2); return; } if (_configService.GetDeviceInfo().EnableXY) { //验证当前锁附点位与设置锁附点位的差值是否超限,超限则报警并停止流程 //目前验证已OK 缺少切换按钮 try { var ScrewLimit = currentProduct.GlobalParamListCollection.FirstOrDefault((p) => p.Number == positionIndex - 1); //判断实际偏差与设置的点位差数是否偏差过大 float XOffsetPoint = Math.Abs(PositionPoint.X_Position - (TempPoint.X_Position + TempPoint.X_Offset)); float YOffsetPoint = Math.Abs(PositionPoint.Y_Position - (TempPoint.Y_Position + TempPoint.Y_Offset)); if (!(XOffsetPoint >= float.Parse(ScrewLimit.Param1) && XOffsetPoint <= float.Parse(ScrewLimit.Param2)) || !(YOffsetPoint >= float.Parse(ScrewLimit.Param3) && YOffsetPoint <= float.Parse(ScrewLimit.Param4))) { SendTaskMessage($"当前锁附点位{positionIndex}与设置锁附点位差值超限,X偏差:{XOffsetPoint},Y偏差{YOffsetPoint}", MessageLevel.Error); await plc.WriteNodeAsync(addressConfig.Out_CameraDone.Address, (Int16)2); return; } } catch (Exception ex) { SendTaskMessage($"当前锁附点位与设置锁附点位差值未设置:" + ex.Message, MessageLevel.Error); await plc.WriteNodeAsync(addressConfig.Out_CameraDone.Address, (Int16)2); return; } } await PositionPoint.WriteToPlcAddress(addressConfig.Out_Screw, plc, positionIndex); } //拍2打所有 else if (systemConfig.WorkMode == 2) { //根据UpCameraPutResults[]中的索引1、2,和拍照点对应的Local下的坐标,从创建Local坐标系 PointF worldP1 = new PointF(UpCameraPutResults[1].X, UpCameraPutResults[1].Y); PointF worldP2 = new PointF(UpCameraPutResults[2].X, UpCameraPutResults[2].Y); PointF localP1 = new PointF(currentProduct.ScrewCameraLocalPos1.X_Position, currentProduct.ScrewCameraLocalPos1.Y_Position); PointF localP2 = new PointF(currentProduct.ScrewCameraLocalPos2.X_Position, currentProduct.ScrewCameraLocalPos2.Y_Position); CoordinateTransformer local = new CoordinateTransformer(worldP1, worldP2, localP1, localP2); for (int i = 0; i < currentProduct.ScrewPoints.Count; i++) { var localPoint = new PointF(currentProduct.ScrewPoints[i].X_Position + currentProduct.ScrewPoints[i].X_Offset, currentProduct.ScrewPoints[i].Y_Position + currentProduct.ScrewPoints[i].Y_Offset); var worldPoint = local.ToOldCoord(localPoint.X, localPoint.Y); var point = currentProduct.ScrewPoints[i].Clone(); //锁付点位在Local中已经加了偏移,这里写回PLC时需要将偏移清零,否则写入PLC端时会重复计算偏移 point.X_Offset = 0; point.Y_Offset = 0; point.X_Position = (float)worldPoint[0]; point.Y_Position = (float)worldPoint[1]; SendTaskMessage($"点:{i},{point.X_Position:F3},{point.Y_Position:F4}", MessageLevel.Info); if (_configService.GetDeviceInfo().ScrewOverrun) { try { var ScrewLimit = currentProduct.GlobalParamListCollection.FirstOrDefault((p) => p.Number == positionIndex - 1); //判断实际偏差与设置的点位差数是否偏差过大 float XOffsetPoint = Math.Abs(localPoint.X - (float)worldPoint[0]); float YOffsetPoint = Math.Abs(localPoint.Y - (float)worldPoint[1]); if ((XOffsetPoint >= float.Parse(ScrewLimit.Param1) && XOffsetPoint <= float.Parse(ScrewLimit.Param2)) || (YOffsetPoint >= float.Parse(ScrewLimit.Param3) && YOffsetPoint <= float.Parse(ScrewLimit.Param4))) { SendTaskMessage($"当前锁附点位{i}与设置锁附点位差值超限,X偏差:{XOffsetPoint},Y偏差{YOffsetPoint}", MessageLevel.Error); await plc.WriteNodeAsync(addressConfig.Out_CameraDone.Address, (Int16)2); return; } } catch (Exception ex) { SendTaskMessage($"当前锁附点位与设置锁附点位差值超限:" + ex.Message, MessageLevel.Error); await plc.WriteNodeAsync(addressConfig.Out_CameraDone.Address, (Int16)2); } } await point.WriteToPlcAddress(addressConfig.Out_Screw, plc); } await plc.WriteNodeAsync(addressConfig.Out_WorkNum.Address, (Int16)currentProduct.ScrewPoints.Count); } //拍2打所有 else if (systemConfig.WorkMode == 3) { if (positionIndex <= 3) { List plcPoints = new List(); foreach (var item in currentProduct.ScrewPoints) { if (item.IsUse) { plcPoints.Add(item); } } var point = plcPoints[positionIndex - 1]; if (point == null) { SendTaskMessage($"点:{positionIndex}不存在", MessageLevel.Info); await plc.WriteNodeAsync(addressConfig.Out_CameraDone.Address, (Int16)2); return; } point = point.Clone(); point.X_Position = UpCameraPutResults[1].X + DowmCameraResults[1].X; point.Y_Position = UpCameraPutResults[1].Y + DowmCameraResults[1].Y; SendTaskMessage($"点:{positionIndex},{point.X_Position:F3},{point.Y_Position:F4}", MessageLevel.Info); await point.WriteToPlcAddress(addressConfig.Out_Screw, plc, positionIndex); } else { //根据UpCameraPutResults[]中的索引1、2,和拍照点对应的Local下的坐标,从创建Local坐标系 PointF worldP1 = new PointF(UpCameraPutResults[4].X, UpCameraPutResults[4].Y); PointF worldP2 = new PointF(UpCameraPutResults[5].X, UpCameraPutResults[5].Y); PointF localP1 = new PointF(currentProduct.ScrewCameraLocalPos1.X_Position, currentProduct.ScrewCameraLocalPos1.Y_Position); PointF localP2 = new PointF(currentProduct.ScrewCameraLocalPos2.X_Position, currentProduct.ScrewCameraLocalPos2.Y_Position); CoordinateTransformer local = new CoordinateTransformer(worldP1, worldP2, localP1, localP2); for (int i = 0; i < currentProduct.ScrewPoints.Count; i++) { var localPoint = new PointF(currentProduct.ScrewPoints[i].X_Position + currentProduct.ScrewPoints[i].X_Offset, currentProduct.ScrewPoints[i].Y_Position + currentProduct.ScrewPoints[i].Y_Offset); var worldPoint = local.ToOldCoord(localPoint.X, localPoint.Y); var point = currentProduct.ScrewPoints[i].Clone(); //锁付点位在Local中已经加了偏移,这里写回PLC时需要将偏移清零,否则写入PLC端时会重复计算偏移 point.X_Offset = 0; point.Y_Offset = 0; point.X_Position = (float)worldPoint[0]; point.Y_Position = (float)worldPoint[1]; SendTaskMessage($"点:{i},{point.X_Position:F3},{point.Y_Position:F4}", MessageLevel.Info); await point.WriteToPlcAddress(addressConfig.Out_Screw, plc); } await plc.WriteNodeAsync(addressConfig.Out_WorkNum.Address, (Int16)currentProduct.ScrewPoints.Count); } } await plc.WriteNodeAsync(addressConfig.Out_CameraDone.Address, (Int16)1); } else { SendTaskMessage(string.Format(Lang.多相机坐标合并请求已结束或被取消state0, state), MessageLevel.Alarm); await plc.WriteNodeAsync(addressConfig.Out_CameraDone.Address, (Int16)0); } } } // PLC请求二维码拍照位置 else if (addressConfig.In_QrCodePhotoRequest.Address.Contains(tuple.nodeId)) { if (tuple.value is Int16 state) { if (_LastQrCodePhotoRequest != state) { _LastQrCodePhotoRequest = state; } else { return; } if (state == 1) { SendTaskMessage("收到PLC二维码拍照位置请求", MessageLevel.Debug); if (currentProduct.QrCodePoints == null || currentProduct.QrCodePoints.Count == 0) { SendTaskMessage("未找到示教后的二维码拍照点位", MessageLevel.Alarm); await plc.WriteNodeAsync(addressConfig.Out_QrCodePhotoResault.Address, (Int16)2); return; } var qrpoint = currentProduct.QrCodePoints[0]; //发送点位 bool SendSuccess = await qrpoint.WriteToPlcAddress(addressConfig.Out_QrCodeCamera, plc); if (SendSuccess) { SendTaskMessage("二维码拍照点位发送成功", MessageLevel.Alarm); await plc.WriteNodeAsync(addressConfig.Out_QrCodePhotoResault.Address, (Int16)1); Guid qrCodeProcedureId = currentProduct.QrCodeProcedureId; if (qrCodeProcedureId == Guid.Empty) { // 如果没有配置二维码视觉流程,直接返回成功 SendTaskMessage("未配置二维码视觉流程,跳过扫码", MessageLevel.Info); return; } } else { SendTaskMessage("二维码拍照点位发送失败", MessageLevel.Alarm); await plc.WriteNodeAsync(addressConfig.Out_QrCodePhotoResault.Address, (Int16)2); } } else { SendTaskMessage("二维码拍照点位发送信号已关闭", MessageLevel.Alarm); await plc.WriteNodeAsync(addressConfig.Out_QrCodePhotoResault.Address, (Int16)0); } } } // 螺丝供料器螺丝递减 else if (addressConfig.In_PickINC.Address.Contains(tuple.nodeId)) { if (tuple.value is Int16 state) { if (_LastPickINC != state) { _LastPickINC = state; } else { return; } if (state != 0) { SendTaskMessage($"螺丝供料器...", MessageLevel.Info); await plc.WriteNodeAsync(addressConfig.In_PickINC.Address, (Int16)0); var feeders = _configService.GetRemainingCountByFeederNumber(state); if (state == 1) { NumStatistics.Feeder1Num = _configService.DecrementScrewCountByFeederNumber(feeders); } else if (state == 2) { NumStatistics.Feeder2Num = _configService.DecrementScrewCountByFeederNumber(feeders); } } } } // 抛料计数 else if (addressConfig.In_Throw.Address.Contains(tuple.nodeId)) { if (tuple.value is bool state) { if (_LastThrowExe != state) { _LastThrowExe = state; } else { return; } if (state) { SendTaskMessage("收到抛料信号", MessageLevel.Info); await _systemDatabaseService.RecordThrowNumberAsync(new ThrowRecord() { DayTime = DateTime.Now, ThrowCount = 1 }); } } } // 切换成自动运行模式 else if (addressConfig.AutoRunning.Address.Contains(tuple.nodeId)) { if (tuple.value is bool state) { if (_LastAutoRunning != state) { _LastAutoRunning = state; } else { return; } if (state) { SendTaskMessage("收到切换成自动模式", MessageLevel.Info); } else { SendTaskMessage("收到关闭自动模式", MessageLevel.Info); } CurrentRunStatus(); } } // 切换成维修模式 else if (addressConfig.ManualMode.Address.Contains(tuple.nodeId)) { if (tuple.value is bool state) { if (_LastManualMode != state) { _LastManualMode = state; } else { return; } if (state) { SendTaskMessage("收到切换成维修模式", MessageLevel.Info); } else { SendTaskMessage("收到退出维修模式", MessageLevel.Info); } CurrentRunStatus(); } } // 切换成流线模式 else if (addressConfig.ClearMode.Address.Contains(tuple.nodeId)) { if (tuple.value is bool state) { if (_LastClearMode != state) { _LastClearMode = state; } else { return; } if (state) { SendTaskMessage("收到切换成流线模式", MessageLevel.Info); } else { SendTaskMessage("收到退出流线模式", MessageLevel.Info); } CurrentRunStatus(); } } //plc报警 else if (addressConfig.Fault.Address.Contains(tuple.nodeId)) { if (tuple.value is bool state) { if (_LastFault != state) { _LastFault = state; } else { return; } if (state) { SendAlarmToUi(plc); SendTaskMessage("收到PLC报警", MessageLevel.Error); } else { SendTaskMessage("收到PLC消除报警", MessageLevel.Debug); } } } // 过站检查 else { for (int i = 0; i < 10; i++) { if (string.Format(addressConfig.In_MesCheck.Address, i).Contains(tuple.nodeId)) { if (tuple.value is Int16 state) { //var oldState = _LastMesCheck[i]; //_LastMesCheck[i] = state; //SendTaskMessage($"站点{i},收到PLC状态为:{state},旧状态为:{oldState}",MessageLevel.Info); //if (oldState == state) //{ // return; //} if (_LastMesCheck[i] != state) { _LastMesCheck[i] = state; } else { return; } var systemConfig = _configService.GetSystemConfiguration(); string code = ""; //plc启用的模式 bool models = plc.ReadNode(addressConfig.Cam_code_mode.Address); if (models && state == 1) { //拍照读码 SendTaskMessage(Lang.触发上相机锁附组装拍照, MessageLevel.Debug); //相机的编号 Int16 cameraIndex = plc.ReadNode(addressConfig.In_UpCameraPutNum.Address); SendTaskMessage(string.Format(Lang.编号0, cameraIndex), MessageLevel.Info); Guid qrCodeProcedureId = currentProduct.QrCodeProcedureId; //视觉流程ID var qrCodeProcess = _productService.GetCurrentProductProcedureModelById(qrCodeProcedureId); if (qrCodeProcess == null) { SendTaskMessage($"未找到二维码视觉流程,流程ID: {qrCodeProcedureId}", MessageLevel.Alarm); await plc.WriteNodeAsync(addressConfig.Out_QrCodePhotoResault.Address, (Int16)2); return; } SendTaskMessage($"开始执行二维码扫码视觉流程", MessageLevel.Debug); var _cts = new CancellationTokenSource(); await _remoteCommandService.SetLightBeforePhoto(qrCodeProcess); var qrCodeResult = _remoteCommandService.ExecutePhoto(i + 1, qrCodeProcess, out CogToolBlockTerminalCollection outputCollection); if (qrCodeResult && outputCollection.Contains("QR")) { code = outputCollection["QR"].Value as string; code = code.ToUpper(); if (!string.IsNullOrEmpty(code) && code.Length > 5) { if (systemConfig.IsBasePartCodeChooseScrew)//是否基于零件码,判断是不是当站要打的螺丝 { var val = CheckCode(code, systemConfig.ChooseScrewModel); if (val) { SendTaskMessage($"是当站螺丝:{code}", MessageLevel.Info); await plc.WriteNodeAsync(addressConfig.Out_QrCodePhotoResault.Address, (Int16)1); } else { SendTaskMessage($"不是当站螺丝:{code}", MessageLevel.Info); await plc.WriteNodeAsync(addressConfig.Out_QrCodePhotoResault.Address, (Int16)2); await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, i), (Int16)3); return; } } bool _isSuccess = plc.WriteNode(string.Format(addressConfig.In_Code.Address, 0), code); if (_isSuccess) { SendTaskMessage($"二维码扫码成功:{code}", MessageLevel.Info); await plc.WriteNodeAsync(addressConfig.Out_QrCodePhotoResault.Address, (Int16)1); } else { SendTaskMessage($"二维码扫码成功:{code}", MessageLevel.Info); SendTaskMessage($"发送plc二维码失败:{code}", MessageLevel.Error); await plc.WriteNodeAsync(addressConfig.Out_QrCodePhotoResault.Address, (Int16)2); } } else { SendTaskMessage("二维码扫码失败", MessageLevel.Alarm); await plc.WriteNodeAsync(addressConfig.Out_QrCodePhotoResault.Address, (Int16)2); await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, i), (Int16)2); return; } } else { code = ""; SendTaskMessage("二维码扫码失败", MessageLevel.Alarm); await plc.WriteNodeAsync(addressConfig.Out_QrCodePhotoResault.Address, (Int16)2); await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, i), (Int16)2); return; } } else { code = plc.ReadNode(string.Format(addressConfig.In_Code.Address, i)); } //if (!(i == 0 || i == 7)) //{ // return; //} productSn = code; SendTaskMessage($"收到plc信号{state}", MessageLevel.Debug); //询问mes if (state == 1) { SendTaskMessage($"收到{i}过站检查请求(当站是否生产)...", MessageLevel.Debug); //获取过站时间 _LastMesCheckTime[i] = (await _mesService.GetServerTimeAsync(i, code, 2)).Now; if (_configService.GetDeviceInfo().EnableMES) { LogHelper.WriteLogMes($"【mes开始QUERY】"); SendTaskMessage($"mes开始QUERY", MessageLevel.Info); //dataToMes.Clear(); if (systemConfig.IsBasePartCodeGetSN)//是否基于零件码,判断是否过站 { SendTaskMessage($"基于零件码,判断是否过站", MessageLevel.Debug); (bool isSuccess, string response) = await _mesService.StationGetExAsync(code, "", 2);//基于电池的SN获取产品SN,询问mes if (isSuccess) { string ProductMainSN_Temp = ""; LogHelper.WriteLogMes($"询问mes成功"); SendTaskMessage($"询问mes成功:{response}", MessageLevel.Debug); string[] item = response.Split('\n'); //获取过度件sn string snItem = item.FirstOrDefault(f => f.Contains("sn=")); if (snItem != null) { ProductMainSN_Temp = snItem.Replace("sn=", "").Trim(); SendTaskMessage($"过度件sn:{ProductMainSN_Temp}", MessageLevel.Info); } LogHelper.WriteLogMes($"询问mes成功"); SendTaskMessage($"询问mes成功:{response}", MessageLevel.Debug); //20260214,螺丝机特别版 (bool isSuccess2, string response2) = await _mesService.StationGetAsync(ProductMainSN_Temp, "", 2);//询问mes if (isSuccess2) { LogHelper.WriteLogMes($"询问mes成功"); SendTaskMessage($"询问mes成功:{response2}", MessageLevel.Debug); item = response2.Split('\n'); snItem = item.FirstOrDefault(f => f.Contains("sn=")); if (snItem != null) { ProductMainSN[i] = snItem.Replace("sn=", "").Trim(); if (i == 0) { _productService.CurrentProductCode = ProductMainSN[i]; } //_eventAggregator.GetEvent().Publish(true);//通知界面延迟保存图片,等待MES查询结果返回,避免图片和数据不同步 SendTaskMessage($"产品主sn:{ProductMainSN[i]}", MessageLevel.Info); } string woItem = item.FirstOrDefault(f => f.Contains("wo=")); if (woItem != null) { wo[i] = woItem.Replace("wo=", "").Trim(); SendTaskMessage($"工单信息:{wo[i]}", MessageLevel.Info); } string modelItem = item.FirstOrDefault(f => f.Contains("model_num=")); if (modelItem != null) { model_num[i] = modelItem.Replace("model_num=", "").Trim(); SendTaskMessage($"model_num:{model_num[i]}", MessageLevel.Info); } string colorItem = item.FirstOrDefault(f => f.Contains("color=")); if (colorItem != null) { color[i] = colorItem.Replace("color=", "").Trim(); SendTaskMessage($"color:{color[i]}", MessageLevel.Info); } string ppidItem = item.FirstOrDefault(f => f.Contains("ppid=")); if (ppidItem != null) { ppid[i] = ppidItem.Replace("ppid=", "").Trim(); SendTaskMessage($"ppid:{ppid[i]}", MessageLevel.Info); } string revisionItem = item.FirstOrDefault(f => f.Contains("stage=")); if (revisionItem != null) { revision[i] = revisionItem.Replace("stage=", "").Trim(); SendTaskMessage($"stage:{revision[i]}", MessageLevel.Info); } LogHelper.WriteLogMes($"第二次询问mes成功"); SendTaskMessage($"第二次询问mes成功:{response2}", MessageLevel.Debug); //切换产品 if (i == 0)//i=0时才会切换产品 { var q = await _systemDatabaseService.QueryProductFromMesAsync(model_num[i], color[i]); if (q != null && !string.IsNullOrEmpty(q.ProductName)) { ProductList = new ObservableCollection(_productService.GetAllProducts()); Guid productId = Guid.Empty; foreach (var itt in ProductList) { if (itt.Name == q.ProductName) { productId = itt.ID; break; } } if (currentProduct.ID != productId) { //切换产品 _productService.LoadProduct(productId); _productService.SetCurrentProduct(productId); await WriteParameterToPlcExAsync(); SendTaskMessage($"切换产品成功", MessageLevel.Debug); } } } //产品发生改变并且供料器发生了变化时,需要通知PLC进行取料更改 await plc.WriteNodeAsync(addressConfig.Out_ScrewFeederIsChanged.Address, (Int16)1); //不发生改变 await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, i), (Int16)1); } else { LogHelper.WriteLogMes($"第二次询问mes失败"); SendTaskMessage($"第二次询问mes失败:{response2}", MessageLevel.Error); //产品发生改变并且供料器发生了变化时,需要通知PLC进行取料更改 await plc.WriteNodeAsync(addressConfig.Out_ScrewFeederIsChanged.Address, (Int16)1); //不发生改变 await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, i), (Int16)3); } } else { LogHelper.WriteLogMes($"询问mes失败"); SendTaskMessage($"询问mes失败:{response}", MessageLevel.Error); //产品发生改变并且供料器发生了变化时,需要通知PLC进行取料更改 await plc.WriteNodeAsync(addressConfig.Out_ScrewFeederIsChanged.Address, (Int16)1); //不发生改变 await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, i), (Int16)3); } } else //直接基于RFID询问是否过站 { SendTaskMessage($"基于RFID询问是否过站", MessageLevel.Debug); (bool isSuccess, string response) = await _mesService.StationGetExAsync(code, "", 2); if (isSuccess) { string ProductMainSN_Temp = ""; LogHelper.WriteLogMes($"询问mes成功"); SendTaskMessage($"询问mes成功:{response}", MessageLevel.Debug); string[] item = response.Split('\n'); //获取过度件sn string snItem = item.FirstOrDefault(f => f.Contains("sn=")); if (snItem != null) { ProductMainSN_Temp = snItem.Replace("sn=", "").Trim(); SendTaskMessage($"过度件sn:{ProductMainSN_Temp}", MessageLevel.Info); } LogHelper.WriteLogMes($"询问mes成功"); SendTaskMessage($"询问mes成功:{response}", MessageLevel.Debug); (bool isSuccess2, string response2) = await _mesService.StationGetAsync(ProductMainSN_Temp, "", 2);//询问mes if (isSuccess2) { item = response2.Split('\n'); snItem = item.FirstOrDefault(f => f.Contains("sn=")); if (snItem != null) { ProductMainSN[i] = snItem.Replace("sn=", "").Trim(); if (i == 0) { _productService.CurrentProductCode = ProductMainSN[i]; } SendTaskMessage($"产品主sn:{ProductMainSN[i]}", MessageLevel.Info); } string woItem = item.FirstOrDefault(f => f.Contains("wo=")); if (woItem != null) { wo[i] = woItem.Replace("wo=", "").Trim(); SendTaskMessage($"工单信息:{wo[i]}", MessageLevel.Info); } string modelItem = item.FirstOrDefault(f => f.Contains("model_num=")); if (modelItem != null) { model_num[i] = modelItem.Replace("model_num=", "").Trim(); SendTaskMessage($"model_num:{model_num[i]}", MessageLevel.Info); } string colorItem = item.FirstOrDefault(f => f.Contains("color=")); if (colorItem != null) { color[i] = colorItem.Replace("color=", "").Trim(); SendTaskMessage($"color:{color[i]}", MessageLevel.Info); } string ppidItem = item.FirstOrDefault(f => f.Contains("ppid=")); if (ppidItem != null) { ppid[i] = ppidItem.Replace("ppid=", "").Trim(); SendTaskMessage($"ppid:{ppid[i]}", MessageLevel.Info); } string revisionItem = item.FirstOrDefault(f => f.Contains("stage=")); if (revisionItem != null) { revision[i] = revisionItem.Replace("stage=", "").Trim(); SendTaskMessage($"stage:{revision[i]}", MessageLevel.Info); } LogHelper.WriteLogMes($"第二次询问mes成功"); SendTaskMessage($"第二次询问mes成功:{response2}", MessageLevel.Debug); //切换产品 if (i == 0)//i=0时才会切换产品 { var q = await _systemDatabaseService.QueryProductFromMesAsync(model_num[i], color[i]); if (q != null && !string.IsNullOrEmpty(q.ProductName)) { ProductList = new ObservableCollection(_productService.GetAllProducts()); Guid productId = Guid.Empty; foreach (var itt in ProductList) { if (itt.Name == q.ProductName) { productId = itt.ID; break; } } if (currentProduct.ID != productId) { //切换产品 _productService.LoadProduct(productId); _productService.SetCurrentProduct(productId); await WriteParameterToPlcExAsync(); SendTaskMessage($"切换产品成功", MessageLevel.Debug); } } } //产品发生改变并且供料器发生了变化时,需要通知PLC进行取料更改 await plc.WriteNodeAsync(addressConfig.Out_ScrewFeederIsChanged.Address, (Int16)1); //不发生改变 await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, i), (Int16)1); return; } else { LogHelper.WriteLogMes($"第二次询问mes失败"); SendTaskMessage($"第二次询问mes失败:{response2}", MessageLevel.Error); //产品发生改变并且供料器发生了变化时,需要通知PLC进行取料更改 await plc.WriteNodeAsync(addressConfig.Out_ScrewFeederIsChanged.Address, (Int16)1); //不发生改变 if (models) { await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, i), (Int16)3); SendTaskMessage($"发送plc信号3", MessageLevel.Debug); return; } else { await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, i), (Int16)2); SendTaskMessage($"发送plc信号2", MessageLevel.Debug); return; } } } else { LogHelper.WriteLogMes($"询问mes失败"); SendTaskMessage($"询问mes失败:{response}", MessageLevel.Error); //产品发生改变并且供料器发生了变化时,需要通知PLC进行取料更改 await plc.WriteNodeAsync(addressConfig.Out_ScrewFeederIsChanged.Address, (Int16)1); //不发生改变 if (models) { await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, i), (Int16)3); SendTaskMessage($"发送plc信号3", MessageLevel.Debug); return; } else { await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, i), (Int16)2); SendTaskMessage($"发送plc信号2", MessageLevel.Debug); return; } } } LogHelper.WriteLogMes($"【mes结束QUERY】"); SendTaskMessage($"mes结束QUERY", MessageLevel.Info); } else { ProductMainSN[i] = DateTime.Now.ToString("yyyyMMddHHmmssfff"); if (i == 0) { _productService.CurrentProductCode = ProductMainSN[i]; } LogHelper.WriteLogMes($"【未启用mes】"); SendTaskMessage($"未启用mes", MessageLevel.Info); await plc.WriteNodeAsync(addressConfig.Out_ScrewFeederIsChanged.Address, (Int16)1); //不发生改变 await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, i), (Int16)1); SendTaskMessage($"发送plc信号2", MessageLevel.Debug); } } // 上传本站数据 else if (state == 2) { DFC.Clear();//CTQ数据集合 // string productccode = ProductMainSN[i]; //获取当前工位的配置 var stationConfig = _configService.GetDeviceInfo(i); var sysConfig = _configService.GetSystemConfiguration(); SendTaskMessage($"收到{i}:{stationConfig.DeviceName}上传本站数据请求...", MessageLevel.Debug); //从服务器中获取当前时间 LogHelper.WriteLogMes($"【从服务器中获取当前时间】"); SendTaskMessage($"从服务器中获取当前时间", MessageLevel.Info); var serverTimeResult = await _mesService.GetServerTimeAsync(i, ProductMainSN[i], 2); //这里缺少上传MES的本站数据结果及开始和结束时间,需要补充 if (i == 0) { CTtime = serverTimeResult.Now.Subtract(_LastMesCheckTime[i]).TotalSeconds; _eventAggregator.GetEvent().Publish(true);//通知界面延迟保存图片,等待MES查询结果返回,避免图片和数据不同步 } //读取产品结果 Int16 resultIndex = plc.ReadNode(string.Format(addressConfig.In_Result.Address, i)); if (stationConfig.EnableMES) { //目前只记录当前站 if (i == 0) await _systemDatabaseService.RecordProductionAsync(new ProductionRecord() { ProductName = currentProduct.Name, ProductCode = ProductMainSN[i], DeviceId = i, DeviceName = "", Category = resultIndex == 1 ? "OK" : "NG", Quantity = 1, Remark = $"TotalSeconds:{serverTimeResult.Now.Subtract(_LastMesCheckTime[i]).TotalSeconds.ToString("F2")}", }); } LogHelper.WriteLogMes($"【mes开始ADD】"); SendTaskMessage($"mes开始ADD", MessageLevel.Info); if (i == 0) { foreach (var lockResult in lockResultsAll) { bool torqueRes, turnRes, pressureRes; if (lockResult.LockTorque >= lockResult.TargetTorqueLowerLimit && lockResult.LockTorque <= lockResult.TargetTorqueUpperLimit) { torqueRes = true; } else torqueRes = false; if (lockResult.Pressure >= stationConfig.DataInfo.CTQ_LSL_Pressure && lockResult.Pressure <= stationConfig.DataInfo.CTQ_USL_Pressure) { pressureRes = true; } else pressureRes = false; if (lockResult.LockTurns >= lockResult.TargetTurnsLowerLimit && lockResult.LockTurns <= lockResult.TargetTurnsUpperLimit) { turnRes = true; } else turnRes = false; string midValue = "NA", saveppid = "NA"; if (stationConfig.SaveCsvPPID) saveppid = ppid[i]; midValue = ((lockResult.TargetTorqueLowerLimit + lockResult.TargetTorqueUpperLimit) / 2).ToString("F3"); DFC.Add(new DataFormCtq { PPID = saveppid, ScrewNum = lockResult.Number, PROCESS_NAME = stationConfig.DataInfo.PROCESS_NAME, PROCESS_START_TIME = _LastMesCheckTime[i].ToString("yyyy-MM-ddTHH:mm:ss") + "+08:00", CTQ_NAME = "Torque", CTQ_VALUE = lockResult.LockTorque, CTQ_LSL = lockResult.TargetTorqueLowerLimit, CTQ_USL = lockResult.TargetTorqueUpperLimit, PROCESS_END_TIME = serverTimeResult.Now.ToString("yyyy-MM-ddTHH:mm:ss") + "+08:00", SUPPLIER_NAME = stationConfig.DataInfo.SUPPLIER_NAME, COMMODITY_TYPE = stationConfig.DataInfo.COMMODITY_TYPE, REVISION = stationConfig.DataInfo.REVISION + "_" + revision[i], WO = wo[i], MFG_LOT = stationConfig.DataInfo.MFG_LOT, MFG_ASSY_LINE = stationConfig.DataInfo.MFG_ASSY_LINE, PROCESS_OUTCOME = torqueRes ? "PASS" : "FAIL", PROCESS_MACHINE_ID = stationConfig.DataInfo.PROCESS_MACHINE_ID, CTQ_UNIT_OF_MEASURE = "kgf.cm", ERROR_MESSAGE = torqueRes ? "NA" : "Torque_Spec OOS", PD_ATTR_01 = ProductMainSN[i], PD_ATTR_02 = midValue, PD_ATTR_03 = "NA", PD_ATTR_04 = productSn, PD_ATTR_05 = "NA", }); midValue = ((lockResult.TargetTurnsLowerLimit + lockResult.TargetTurnsUpperLimit) / 2).ToString("F3"); DFC.Add(new DataFormCtq { PPID = saveppid, ScrewNum = lockResult.Number, PROCESS_NAME = stationConfig.DataInfo.PROCESS_NAME, PROCESS_START_TIME = _LastMesCheckTime[i].ToString("yyyy-MM-ddTHH:mm:ss") + "+08:00", CTQ_NAME = "Turn", CTQ_VALUE = lockResult.LockTurns, CTQ_LSL = lockResult.TargetTurnsLowerLimit, CTQ_USL = lockResult.TargetTurnsUpperLimit, PROCESS_END_TIME = serverTimeResult.Now.ToString("yyyy-MM-ddTHH:mm:ss") + "+08:00", SUPPLIER_NAME = stationConfig.DataInfo.SUPPLIER_NAME, COMMODITY_TYPE = stationConfig.DataInfo.COMMODITY_TYPE, REVISION = stationConfig.DataInfo.REVISION + "_" + revision[i], WO = wo[i], MFG_LOT = stationConfig.DataInfo.MFG_LOT, MFG_ASSY_LINE = stationConfig.DataInfo.MFG_ASSY_LINE, PROCESS_OUTCOME = turnRes ? "PASS" : "FAIL", PROCESS_MACHINE_ID = stationConfig.DataInfo.PROCESS_MACHINE_ID, CTQ_UNIT_OF_MEASURE = "Turn", ERROR_MESSAGE = turnRes ? "NA" : "Turn_Spec OOS", PD_ATTR_01 = ProductMainSN[i], PD_ATTR_02 = midValue, PD_ATTR_03 = "NA", PD_ATTR_04 = productSn, PD_ATTR_05 = "NA", }); midValue = ((stationConfig.DataInfo.CTQ_LSL_Pressure + stationConfig.DataInfo.CTQ_USL_Pressure) / 2).ToString("F3"); DFC.Add(new DataFormCtq { PPID = saveppid, ScrewNum = lockResult.Number, PROCESS_NAME = stationConfig.DataInfo.PROCESS_NAME, PROCESS_START_TIME = _LastMesCheckTime[i].ToString("yyyy-MM-ddTHH:mm:ss") + "+08:00", CTQ_NAME = "Pressure", CTQ_VALUE = lockResult.Pressure, CTQ_LSL = stationConfig.DataInfo.CTQ_LSL_Pressure, CTQ_USL = stationConfig.DataInfo.CTQ_USL_Pressure, PROCESS_END_TIME = serverTimeResult.Now.ToString("yyyy-MM-ddTHH:mm:ss") + "+08:00", SUPPLIER_NAME = stationConfig.DataInfo.SUPPLIER_NAME, COMMODITY_TYPE = stationConfig.DataInfo.COMMODITY_TYPE, REVISION = stationConfig.DataInfo.REVISION + "_" + revision[i], WO = wo[i], MFG_LOT = stationConfig.DataInfo.MFG_LOT, MFG_ASSY_LINE = stationConfig.DataInfo.MFG_ASSY_LINE, PROCESS_OUTCOME = pressureRes ? "PASS" : "FAIL", PROCESS_MACHINE_ID = stationConfig.DataInfo.PROCESS_MACHINE_ID, CTQ_UNIT_OF_MEASURE = "kg", ERROR_MESSAGE = pressureRes ? "NA" : "Pressure_Spec OOS", PD_ATTR_01 = ProductMainSN[i], PD_ATTR_02 = midValue, PD_ATTR_03 = "NA", PD_ATTR_04 = productSn, PD_ATTR_05 = "NA", }); } if (systemConfig.IsBasePartOpenLightKB) { string midValue = "999", saveppid = "NA"; double upvlalue = 0, doenvlaue = 0; if (stationConfig.SaveCsvPPID) saveppid = ppid[i]; bool pressureRes = true; foreach (var item in mes_cc) { switch (item.Key) { case "1": upvlalue = Convert.ToDouble(currentProduct.GlobalParamEx.CC1DownLimit.ToString("F3")); doenvlaue = Convert.ToDouble(currentProduct.GlobalParamEx.CC1UpLimit.ToString("F3")); midValue = ((currentProduct.GlobalParamEx.CC1DownLimit + currentProduct.GlobalParamEx.CC1UpLimit) / 2).ToString("F3"); if (Convert.ToDouble(item.Value) < upvlalue || Convert.ToDouble(item.Value) > doenvlaue) { resultToMes.Add($"CC{item.Key}_Fail"); SendTaskMessage($"CC{item.Key}:超限", MessageLevel.Error); } break; case "2": upvlalue = Convert.ToDouble(currentProduct.GlobalParamEx.CC2DownLimit.ToString("F3")); doenvlaue = Convert.ToDouble(currentProduct.GlobalParamEx.CC2UpLimit.ToString("F3")); midValue = ((currentProduct.GlobalParamEx.CC2DownLimit + currentProduct.GlobalParamEx.CC2UpLimit) / 2).ToString("F3"); if (Convert.ToDouble(item.Value) < upvlalue || Convert.ToDouble(item.Value) > doenvlaue) { resultToMes.Add($"CC{item.Key}_Fail"); SendTaskMessage($"CC{item.Key}:超限", MessageLevel.Error); } break; } dataToMes.Add($"CC{item.Key}", $"{item.Value}"); DFC.Add(new DataFormCtq { PPID = saveppid, ScrewNum = 1, PROCESS_NAME = stationConfig.DataInfo.PROCESS_NAME, PROCESS_START_TIME = _LastMesCheckTime[i].ToString("yyyy-MM-ddTHH:mm:ss") + "+08:00", CTQ_NAME = $"CC{item.Key}", CTQ_VALUE = Convert.ToDouble(item.Value.ToString()), CTQ_LSL = doenvlaue, CTQ_USL = upvlalue, PROCESS_END_TIME = serverTimeResult.Now.ToString("yyyy-MM-ddTHH:mm:ss") + "+08:00", SUPPLIER_NAME = stationConfig.DataInfo.SUPPLIER_NAME, COMMODITY_TYPE = stationConfig.DataInfo.COMMODITY_TYPE, REVISION = stationConfig.DataInfo.REVISION + "_" + revision[i], WO = wo[i], MFG_LOT = stationConfig.DataInfo.MFG_LOT, MFG_ASSY_LINE = stationConfig.DataInfo.MFG_ASSY_LINE, PROCESS_OUTCOME = pressureRes ? "PASS" : "FAIL", PROCESS_MACHINE_ID = stationConfig.DataInfo.PROCESS_MACHINE_ID, CTQ_UNIT_OF_MEASURE = "mm", ERROR_MESSAGE = pressureRes ? "NA" : $"CC{item.Key} OOS", PD_ATTR_01 = ProductMainSN[i], PD_ATTR_02 = midValue, PD_ATTR_03 = "NA", PD_ATTR_04 = productSn, PD_ATTR_05 = "NA", }); } } } //上传mes if (stationConfig.EnableMES) { //测试使用,强制上传mes为pass if (sysConfig.IsMesResult) { resultToMes.Clear(); } (bool isSuccess, string response) = await _mesService.SubmitGetExAsync(ProductMainSN[i], "", dataToMes, resultToMes, _LastMesCheckTime[i], serverTimeResult.Now); if (isSuccess) { LogHelper.WriteLogMes($"上传mes成功"); SendTaskMessage($"上传mes成功", MessageLevel.Debug); } else { LogHelper.WriteLogMes($"上传mes失败"); SendTaskMessage($"上传mes失败", MessageLevel.Error); } } else { LogHelper.WriteLogMes($"上传mes成功"); SendTaskMessage($"上传mes成功", MessageLevel.Debug); } await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, i), (Int16)3); if (stationConfig.SaveCsv) { if (!string.IsNullOrEmpty(stationConfig.SaveCsvPath)) { bool recordResult = false; recordResult = RecordStationResult(i, DFC, ProductMainSN[i], serverTimeResult.Now.Subtract(_LastMesCheckTime[i]).TotalSeconds, _LastMesCheckTime[i], stationConfig.SaveCsvPath); if (recordResult) { SendTaskMessage($"{i}:{stationConfig.DeviceName}产品记录保存成功", MessageLevel.Debug); //上传数据 try { if (stationConfig.EnableMES) { DateTime dt = DateTime.Now; string filename = $"{ProductMainSN[i]}_{dt.ToString("yyyyMMddHHmmss")}"; var device = _configService.GetDeviceInfo(); var config = _configService.GetSystemConfiguration(); (bool isSuc, string resp) = await _mesService.SendFtpFileAsync(ProductMainSN[i], filename); if (isSuc) { LogHelper.WriteLogMes($"上传数据成功"); SendTaskMessage($"上传数据成功", MessageLevel.Debug); string filePath = $"D:\\image\\Recored\\{dt.ToString("yyyy-MM")}\\{dt.ToString("MM-dd")}\\{currentProduct.Name}\\{ProductMainSN[i]}"; //string source_csv2 = $"{stationConfig.SaveCsvPath}\\{dt.ToString("yyyy-MM")}\\{i}_{stationConfig.DeviceName}_{dt.ToString("yyyy-MM-dd")}"; if (!Directory.Exists(filePath)) { Directory.CreateDirectory(filePath); } string csvfilename = $"{i}_{stationConfig.DeviceName}_{dt.ToString("yyyy-MM-dd")}"; //复制csv string source_csv = $"{stationConfig.SaveCsvPath}\\{dt.ToString("yyyy-MM")}\\{i}_{stationConfig.DeviceName}_{dt.ToString("yyyy-MM-dd")}.csv"; string target_csv = Path.Combine(filePath, Path.GetFileName(source_csv)); File.Copy(source_csv, target_csv, true); //复制log string source_log = $"..\\Log\\LogInfo\\{dt.ToString("yyyy-MM")}\\{dt.ToString("yyyy-MM-dd")}.txt"; string target_log = Path.Combine(filePath, Path.GetFileName(source_log)); File.Copy(source_log, target_log, true); //判断是否保存锁付曲线 if (systemConfig.IsSaveScrewCurve) { string target_Directory = filePath + "\\扭力曲线\\"; string source_Torque_Image = config.ScrewCurvePath + $"\\{dt.ToString("yyyy-MM")}\\{dt.ToString("yyyy-MM-dd")}\\{currentProduct.Name}\\{ProductMainSN[i]}"; try { var col = Directory.GetFiles(source_Torque_Image); Directory.CreateDirectory(target_Directory); foreach (var item in col) { string target_Torque = target_Directory + item.Replace(source_Torque_Image, ""); File.Copy(item, target_Torque, true); } } catch (Exception) { SendTaskMessage($"扭力曲线图读取失败", MessageLevel.Error); } } CompressionImage(filePath, filename, device.Station, device.FixtureId); //上传csv bool isAfter20 = dt.Hour >= 20; //判断是否是新的一天 bool isNewDay = dt.Date != _lastCompressDate.Date; //当天还没执行过压缩 if (isAfter20 && isNewDay && !_hasCompressedToday) { // 执行压缩 CompressionSingleCsv(source_csv, csvfilename, device.Station, device.FixtureId); _lastCompressDate = dt; _hasCompressedToday = true; LogHelper.WriteLogMes("csv上传数据成功"); SendTaskMessage($"csv上传数据成功", MessageLevel.Debug); } // 跨天后重置当天执行标记 if (!isNewDay && _hasCompressedToday) { _hasCompressedToday = false; } } else { LogHelper.WriteLogMes($"上传数据失败"); SendTaskMessage($"上传数据失败", MessageLevel.Error); } } else { LogHelper.WriteLogMes($"未启用mes"); SendTaskMessage($"未启用mes", MessageLevel.Error); } } catch (Exception ex) { SendTaskMessage($"上传数据时出错:{ex.Message}", MessageLevel.Error); LogHelper.WriteLogError($"上传数据时出错", ex); } } else { SendTaskMessage($"{i}:{stationConfig.DeviceName}产品记录保存失败", MessageLevel.Error); } } else { SendTaskMessage($"{i}:{stationConfig.DeviceName}工位未配置保存路径,无法保存产品记录", MessageLevel.Error); } } else { SendTaskMessage($"{i}:{stationConfig.DeviceName}工位未配置保存产品记录", MessageLevel.Debug); } WriteProductLog(lockResultsAll, ProductMainSN[i], CTtime); mes_cc.Clear(); LogHelper.WriteLogMes($"【mes结束ADD】"); SendTaskMessage($"mes结束ADD", MessageLevel.Info); } //kb 螺丝站位询问mes 具体需求跟plc对接 else if (state == 5) { SendTaskMessage($"收到KB Screw站点{i}过站检查请求(当站是否生产)...", MessageLevel.Debug); //获取过站时间 _LastMesCheckTime[i] = (await _mesService.GetServerTimeAsync(i, code, 2)).Now; if (_configService.GetDeviceInfo().EnableMES) { LogHelper.WriteLogMes($"【mes开始QUERY】"); SendTaskMessage($"mes开始QUERY", MessageLevel.Info); SendTaskMessage($"基于RFID询问是否过站", MessageLevel.Debug); (bool isSuccess, string response) = await _mesService.StationGetExAsync(code, "", 2); //如果成功的话就代表跟正常mes询问一样,当前站需工作 if (isSuccess) { string ProductMainSN_Temp = ""; LogHelper.WriteLogMes($"询问mes成功"); SendTaskMessage($"询问mes成功:{response}", MessageLevel.Debug); string[] item = response.Split('\n'); //获取过度件sn string snItem = item.FirstOrDefault(f => f.Contains("sn=")); if (snItem != null) { ProductMainSN_Temp = snItem.Replace("sn=", "").Trim(); SendTaskMessage($"过度件sn:{ProductMainSN_Temp}", MessageLevel.Info); } LogHelper.WriteLogMes($"询问mes成功"); SendTaskMessage($"询问mes成功:{response}", MessageLevel.Debug); (bool isSuccess2, string response2) = await _mesService.StationGetAsync(ProductMainSN_Temp, "", 2);//询问mes if (isSuccess2) { item = response2.Split('\n'); snItem = item.FirstOrDefault(f => f.Contains("sn=")); if (snItem != null) { ProductMainSN[i] = snItem.Replace("sn=", "").Trim(); if (i == 0) { _productService.CurrentProductCode = ProductMainSN[i]; } SendTaskMessage($"产品主sn:{ProductMainSN[i]}", MessageLevel.Info); } string woItem = item.FirstOrDefault(f => f.Contains("wo=")); if (woItem != null) { wo[i] = woItem.Replace("wo=", "").Trim(); SendTaskMessage($"工单信息:{wo[i]}", MessageLevel.Info); } string modelItem = item.FirstOrDefault(f => f.Contains("model_num=")); if (modelItem != null) { model_num[i] = modelItem.Replace("model_num=", "").Trim(); SendTaskMessage($"model_num:{model_num[i]}", MessageLevel.Info); } string colorItem = item.FirstOrDefault(f => f.Contains("color=")); if (colorItem != null) { color[i] = colorItem.Replace("color=", "").Trim(); SendTaskMessage($"color:{color[i]}", MessageLevel.Info); } string ppidItem = item.FirstOrDefault(f => f.Contains("ppid=")); if (ppidItem != null) { ppid[i] = ppidItem.Replace("ppid=", "").Trim(); SendTaskMessage($"ppid:{ppid[i]}", MessageLevel.Info); } string revisionItem = item.FirstOrDefault(f => f.Contains("stage=")); if (revisionItem != null) { revision[i] = revisionItem.Replace("stage=", "").Trim(); SendTaskMessage($"stage:{revision[i]}", MessageLevel.Info); } LogHelper.WriteLogMes($"第二次询问mes成功"); SendTaskMessage($"第二次询问mes成功:{response2}", MessageLevel.Debug); //切换产品 if (i == 0)//i=0时才会切换产品 { var q = await _systemDatabaseService.QueryProductFromMesAsync(model_num[i], color[i]); if (q != null && !string.IsNullOrEmpty(q.ProductName)) { ProductList = new ObservableCollection(_productService.GetAllProducts()); Guid productId = Guid.Empty; foreach (var itt in ProductList) { if (itt.Name == q.ProductName) { productId = itt.ID; break; } } if (currentProduct.ID != productId) { //切换产品 await _productService.LoadProductAsync(productId); _productService.SetCurrentProduct(productId); await WriteParameterToPlcExAsync(); SendTaskMessage($"切换产品成功", MessageLevel.Debug); } } } //产品发生改变并且供料器发生了变化时,需要通知PLC进行取料更改 await plc.WriteNodeAsync(addressConfig.Out_ScrewFeederIsChanged.Address, (Int16)1); //不发生改变 await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, i), (Int16)1); } else { LogHelper.WriteLogMes($"第二次询问mes失败"); SendTaskMessage($"第二次询问mes失败:{response2}", MessageLevel.Error); //产品发生改变并且供料器发生了变化时,需要通知PLC进行取料更改 await plc.WriteNodeAsync(addressConfig.Out_ScrewFeederIsChanged.Address, (Int16)1); //不发生改变 if (models) { await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, i), (Int16)3); } else { await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, i), (Int16)2); } } } else { LogHelper.WriteLogMes($"询问mes失败"); SendTaskMessage($"询问mes失败:{response}", MessageLevel.Error); //产品发生改变并且供料器发生了变化时,需要通知PLC进行取料更改 await plc.WriteNodeAsync(addressConfig.Out_ScrewFeederIsChanged.Address, (Int16)1); //不发生改变 //表示前面的kb screw站点已经打完螺丝并且过站成功到3d aoi,根据plc的需求直接放行 if (response.Contains("3D AOI")) { await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, i), (Int16)2); return; } //表示kb组装 站点ng 具体写入型号跟plc对接 else if (response.Contains("C_KB_Link")) { await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, i), (Int16)5); return; } // 后面还有一些判断条件也需跟当前AE对接 await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, i), (Int16)2); } LogHelper.WriteLogMes($"【mes结束QUERY】"); SendTaskMessage($"mes结束QUERY", MessageLevel.Info); } else { ProductMainSN[i] = DateTime.Now.ToString("yyyyMMddHHmmssfff"); if (i == 0) { _productService.CurrentProductCode = ProductMainSN[i]; } LogHelper.WriteLogMes($"【未启用mes】"); SendTaskMessage($"未启用mes", MessageLevel.Info); await plc.WriteNodeAsync(addressConfig.Out_ScrewFeederIsChanged.Address, (Int16)1); //不发生改变 await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, i), (Int16)1); } } else { SendTaskMessage($"{i}过站检查信号:state={state},处理为默认状态", MessageLevel.Alarm); await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, i), (Int16)0); return; } // _LastMesCheck[i] = 0; } } } } } catch (Exception ex) { SendTaskMessage(ex.Message, MessageLevel.Error); } } #endregion #region 方法 public void SendTaskMessage(string msg, MessageLevel level) { App.Current.Dispatcher.Invoke(() => { _eventAggregator.GetEvent().Publish(new Models.MessageStruct() { Message = msg, level = level }); }); } #region 界面状态显示 // 新增字段 private readonly object _runStatusLock = new object(); private RunDisplayMode _lastRunDisplayMode = RunDisplayMode.Unknown; /// /// 刷新当前运行状态 /// public void CurrentRunStatus() { lock (_runStatusLock) { RunDisplayMode mode; try { mode = EvaluateRunDisplayMode(); } catch (Exception ex) { LogHelper.WriteLogError("刷新运行状态时出错", ex); mode = RunDisplayMode.Manual; } ApplyRunDisplayMode(mode); } } /// /// 计算当前应该显示的模式 /// private RunDisplayMode EvaluateRunDisplayMode() { // 1. 流线模式优先 if (_LastClearMode) { return RunDisplayMode.Clear; } // 2. 调机模式次优先 if (_LastManualMode) { return RunDisplayMode.Manual; } // 3. 没进自动运行,默认按调机显示 if (!_LastAutoRunning) { return RunDisplayMode.Manual; } // 4. 自动运行时,再检查前置条件 bool connectStatus = IsAllDeviceConnected(); bool mesStatus = IsMesReady(); bool paraStatus = IsParameterReady(); var curUser = _systemDatabaseService.GetCurrentUser(); if (connectStatus && mesStatus && paraStatus && curUser.userPart == UserPart.Operator) { return RunDisplayMode.Auto; } return RunDisplayMode.Manual; } /// /// 所有设备是否连接正常 /// private bool IsAllDeviceConnected() { List statusSnapshot = null; App.Current.Dispatcher.Invoke(() => { statusSnapshot = Status?.ToList() ?? new List(); }); if (statusSnapshot.Count == 0) { return false; } foreach (var item in statusSnapshot) { // 注意:这里不要直接 item.Background == Brushes.Red if (item?.Background is SolidColorBrush brush) { bool res = true; App.Current.Dispatcher.Invoke(() => { if (brush.Color == Colors.Red) { SendTaskMessage("当前设备存在连接失败,请检查..." + item.Message, MessageLevel.Error); res = false; } }); if (!res) { return false; } } } return true; } /// /// MES相关配置是否满足 /// private bool IsMesReady() { var allDevices = _configService.GetAllDeviceInfos(); for (int i = 0; i < ((allDevices.Count >= 8) ? 2 : 1); i++) { var stationConfig = allDevices.FirstOrDefault(d => d.DeviceNo == (i == 0 ? 0 : 7)); if (stationConfig == null) { SendTaskMessage("当前MES未配置,请检查...", MessageLevel.Error); return false; } if (string.IsNullOrEmpty(stationConfig.SaveCsvPath) && i == 0) { SendTaskMessage("当前CSV路径为空,请检查...", MessageLevel.Error); return false; } if (!stationConfig.EnableMES || !stationConfig.SaveCsv) { SendTaskMessage("当前未启用MES或未启用CSV保存,请检查...", MessageLevel.Error); return false; } } return true; } /// /// 参数相关配置是否满足 /// private bool IsParameterReady() { var systemConfig = _configService.GetSystemConfiguration(); if (systemConfig == null) { SendTaskMessage("当前无系统文件,请检查...", MessageLevel.Error); return false; } if (!systemConfig.IsSaveScrewCurve) { SendTaskMessage("当前保存扭力曲线未勾选,请检查...", MessageLevel.Error); return false; } if (string.IsNullOrEmpty(systemConfig.ScrewCurvePath)) { SendTaskMessage("当前保存扭力曲线为空,请检查...", MessageLevel.Error); return false; } return true; } /// /// 应用显示模式到UI /// private void ApplyRunDisplayMode(RunDisplayMode mode) { // 模式没变化就不重复刷UI,也不重复打日志 if (_lastRunDisplayMode == mode) { return; } _lastRunDisplayMode = mode; App.Current.Dispatcher.Invoke(() => { switch (mode) { case RunDisplayMode.Auto: CurrentDisplayMode = mode; SendTaskMessage("当前为运行模式", MessageLevel.Debug); break; case RunDisplayMode.Clear: CurrentDisplayMode = mode; SendTaskMessage("当前为流线模式", MessageLevel.Info); break; case RunDisplayMode.Manual: default: CurrentDisplayMode = mode; SendTaskMessage("当前为调机模式", MessageLevel.Info); break; } }); } #endregion #region 压力采集 private float _maxPressure = float.MinValue; /// /// 开始采集压力值 /// /// 配方名称 /// 产品编号 /// 锁付点编号 private void StartPressureCollection(string productName, string productCode, int positionIndex) { try { // cancel existing if any //_pressureCts?.Cancel(); //_pressureTimeoutCts?.Cancel(); //_pressureLinkedCts?.Dispose(); //_pressureSamples?.Clear(); //_pressureTimeoutCts[positionIndex]?.Cancel(); _pressureSamples?.Clear(); _pressureCts[positionIndex] = new CancellationTokenSource(); _pressureTimeoutCts[positionIndex] = new CancellationTokenSource(); //_pressureCts = new CancellationTokenSource(); //_pressureTimeoutCts = new CancellationTokenSource(); // timeout after configured seconds _pressureTimeoutCts[positionIndex].CancelAfter(TimeSpan.FromSeconds(_pressureMaxDurationSeconds)); // link tokens so either stop or timeout cancels _pressureLinkedCts[positionIndex] = CancellationTokenSource.CreateLinkedTokenSource(_pressureCts[positionIndex].Token, _pressureTimeoutCts[positionIndex].Token); var token = _pressureLinkedCts[positionIndex].Token; var addressConfig = _configService.GetPlcAddresses(); var plc = _plcService.GetPlcByNumber(addressConfig.PlcNo) as OpcUaClientPLC; if (plc == null || !plc.IsConnected) return; Task.Run(async () => { SendTaskMessage($"Pressure collection started for position {positionIndex}", MessageLevel.Debug); while (!token.IsCancellationRequested) { try { float pressure = plc.ReadNode(addressConfig.In_PressureValue.Address); lock (_pressureSamples) { //SendTaskMessage($"压力采集时出错:{pressure}", MessageLevel.Info); _pressureSamples.Add((DateTime.Now, pressure)); if (pressure > _maxPressure) { _maxPressure = pressure; } } } catch (Exception ex) { LogHelper.WriteLogError("采集压力值时出错", ex); } await Task.Delay(_pressureSampleIntervalMs); } SendTaskMessage($"Pressure {_pressureSamples}", MessageLevel.Debug); // if cancelled due to timeout //if (_pressureTimeoutCts[positionIndex].IsCancellationRequested && !_pressureCts[positionIndex].IsCancellationRequested) //{ // SendTaskMessage("Pressure collection timed out, saving collected data.", MessageLevel.Alarm); // await StopAndSavePressureCollectionAsync(productName, productCode, positionIndex); //} }, token); } catch (Exception ex) { LogHelper.WriteLogError("StartPressureCollection error", ex); SendTaskMessage($"压力采集时出错:{ex.Message}", MessageLevel.Error); } } /// /// 停止采集并保存压力数据 /// /// /// /// /// private async Task StopAndSavePressureCollectionAsync(string productName, string productCode, int positionIndex) { try { // 获取当前产品 var currentProduct = _productService.GetCurrentProduct(); if (currentProduct == null) { return; } // cancel sampling //_pressureCts?.Cancel(); // allow worker to observe cancel //await Task.Delay(50); List<(DateTime Timestamp, float Value)> snapshot; lock (_pressureSamples) { snapshot = new List<(DateTime, float)>(_pressureSamples); } if (snapshot.Count == 0) { SendTaskMessage("No pressure samples collected.", MessageLevel.Info); return; } // save to file under configured path try { //获取系统参数 var systemConfig = _configService.GetSystemConfiguration(); string datePath = DateTime.Now.ToString("yyyy-MM") + "\\" + DateTime.Now.ToString("yyyy-MM-dd") + "\\"; string dirPath = System.IO.Path.Combine(systemConfig.ScrewCurvePath, datePath, currentProduct.Name, positionIndex.ToString()); if (!Directory.Exists(dirPath)) { Directory.CreateDirectory(dirPath); } string fileName = $"pressure-{productCode}-{positionIndex}-{DateTime.Now.ToString("HHmmss")}.csv"; string fullPath = Path.Combine(dirPath, fileName); string pngfileName = $"pressure-{productCode}-{positionIndex}-{DateTime.Now.ToString("HHmmss")}.png"; string pngPath = Path.Combine(dirPath, pngfileName); if (!systemConfig.IsSaveScrewCurve) { fullPath = string.Empty; pngPath = string.Empty; } App.Current.Dispatcher.Invoke(() => { _eventAggregator.GetEvent().Publish((true, snapshot.ToArray(), pngPath)); }); //判断是否保存锁付曲线 if (systemConfig.IsSaveScrewCurve) { if (!string.IsNullOrEmpty(fullPath)) { var sb = new StringBuilder(); sb.AppendLine("Timestamp,Value"); foreach (var s in snapshot) { sb.AppendLine($"{s.Timestamp:O},{s.Value}"); } File.WriteAllText(fullPath, sb.ToString()); SendTaskMessage($"Pressure samples saved to {fullPath}", MessageLevel.Debug); } } //var sb = new StringBuilder(); //sb.AppendLine("Timestamp,Value"); //foreach (var s in snapshot) //{ // sb.AppendLine($"{s.Timestamp:O},{s.Value}"); //} //File.WriteAllText(fullPath, sb.ToString()); // SendTaskMessage($"Pressure samples saved to {fullPath}", MessageLevel.Debug); } catch (Exception ex) { LogHelper.WriteLogError("保存压力采样文件时出错", ex); SendTaskMessage("保存压力采样文件时出错: " + ex.Message, MessageLevel.Error); } } catch (Exception ex) { LogHelper.WriteLogError("StopAndSavePressureCollectionAsync error", ex); } finally { try { _pressureLinkedCts[positionIndex]?.Cancel(); _pressureLinkedCts[positionIndex]?.Dispose(); _pressureTimeoutCts[positionIndex]?.Dispose(); _pressureCts[positionIndex]?.Dispose(); _pressureSamples?.Clear(); //_maxPressure = 0; } catch { } } } #endregion #region 产品记录 private async void DoYieldTime(object state) { try { //获取当前产品 var currentproduct = _productService.GetCurrentProduct(); if (currentproduct == null) return; TotalQuantity = await _systemDatabaseService.GetOverallProductionAsync(currentproduct.Name); //TotalQuantityToday = await _systemDatabaseService.GetTodayProductionAsync(currentproduct.Name); //CurrentUPH = await _systemDatabaseService.GetLastHourProductionAsync(currentproduct.Name); //// ThrowNumber = await _systemDatabaseService.GetThrowNumberAsync(); //NgNumber = await _systemDatabaseService.GetNGProductionAsync(currentproduct.Name); //Yield = ((double)TotalQuantityToday - (double)NgNumber) / (double)TotalQuantityToday * 100; TotalQuantityToday = 0; CurrentUPH = 0; NgNumber = 0; Yield = 0; ObservableCollection ProductList = new ObservableCollection(_productService.GetAllProducts()); foreach (var product in ProductList) { //TotalQuantity += await _systemDatabaseService.GetOverallProductionAsync(product.Name); TotalQuantityToday += await _systemDatabaseService.GetTodayProductionAsync(product.Name); CurrentUPH += await _systemDatabaseService.GetLastHourProductionAsync(product.Name); NgNumber += await _systemDatabaseService.GetNGProductionAsync(product.Name); Yield += ((double)TotalQuantityToday - (double)NgNumber) / (double)TotalQuantityToday * 100; } } catch (Exception) { } } #endregion #region 字符判定 /// /// 判断是否符合当站 /// /// /// /// private bool CheckCode(string data, string checkData) { try { if (data.Length != checkData.Length) { var len = checkData.Length - data.Length; LogHelper.WriteLogMes($"判断是否符合当站失败,字符长度差{len}"); SendTaskMessage($"判断是否符合当站失败,字符长度差{len}", MessageLevel.Error); return false; } int count = checkData.Length; for (int i = 0; i < count; i++) { if (checkData[i] == '*')//不需要检测的部分 { continue; } if (data[i] != checkData[i])//有值不一致 { LogHelper.WriteLogMes($"判断是否符合当站失败,第{i}个字符,不一致"); SendTaskMessage($"判断是否符合当站失败,第{i}个字符,不一致", MessageLevel.Error); return false; } } return true; } catch (Exception ex) { LogHelper.WriteLogMes($"判断是否符合当站失败,{ex}"); SendTaskMessage($"判断是否符合当站失败,{ex}", MessageLevel.Error); return false; } } #endregion #region vm启动检测 /// /// 杀死所有VM有关进程 /// public void KillVMProcess() { KillProcess("VisionMasterServerApp"); KillProcess("VisionMaster"); KillProcess("VmModuleProxy.exe"); KillProcess("vServerApp.exe"); } /// /// 杀死指定进程名的进程 /// /// public void KillProcess(string strKillName) { foreach (System.Diagnostics.Process p in System.Diagnostics.Process.GetProcesses()) { if (p.ProcessName.Contains(strKillName)) { try { p.Kill(); p.WaitForExit(); } catch (Exception ex) { LogHelper.WriteLogError(string.Format(Lang.杀死进程0时失败, strKillName), ex); } } } } #endregion #region 拍螺丝数据保存csv /// /// 通用方法:将存储XYU的字典导出为规范CSV文件 /// /// 存储XYU的字典 /// CSV文件保存路径 /// 是否追加模式(true=每次运行新增一行,false=覆盖原有文件) /// 数值保留的小数位数(默认4位,适配工业视觉精度) static void ExportDictionaryToCsv(Dictionary dataDict, string savePath, bool appendMode = false, int decimalPlaces = 4) { // 自动创建目录(避免路径不存在报错) string directory = Path.GetDirectoryName(savePath); if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } // 构建CSV内容 StringBuilder csvContent = new StringBuilder(); // 处理表头:仅当文件不存在/非追加模式时,才写入表头 if (!appendMode || !File.Exists(savePath)) { csvContent.AppendLine(string.Join(",", dataDict.Keys)); } // 处理数据行:按指定小数位数格式化数值 List formattedValues = new List(); foreach (var value in dataDict.Values) { formattedValues.Add(Math.Round(value, decimalPlaces).ToString()); } csvContent.AppendLine(string.Join(",", formattedValues)); // 写入文件:UTF-8带BOM编码,Excel打开无乱码 if (appendMode) { // 追加模式:在文件末尾新增内容 File.AppendAllText(savePath, csvContent.ToString(), Encoding.UTF8); } else { // 覆盖模式:新建/替换文件 File.WriteAllText(savePath, csvContent.ToString(), Encoding.UTF8); } } public bool ExportDictionaryToCsv1(string rfid, double x, double y, double u) { try { DateTime dt = DateTime.Now; if (rfid == null || rfid.Count() == 0) { rfid = dt.ToString("MM-dd-mm"); } string path = $"D:\\视觉检测数据XYU数据\\{dt.ToString("yyyy-MM")}\\{dt.ToString("yyyy-MM-dd")}\\{rfid}.csv"; StringBuilder sb = new StringBuilder(); sb.AppendLine($"SN,X,Y,U"); sb.AppendLine(); sb.AppendLine($"{rfid},{x},{y},{u}"); sb.AppendLine(); if (!Directory.Exists(Path.GetDirectoryName(path))) //判断文件夹是否 { Directory.CreateDirectory(Path.GetDirectoryName(path)); //创建文件夹 } if (File.Exists(path)) { // 使用 StreamWriter 以追加模式打开文件 StreamWriter sw = new StreamWriter(path, true, Encoding.UTF8); sw.WriteLine(sb.ToString().TrimEnd(',')); //sw.WriteLine(sb1.ToString().TrimEnd(',')); sw.Close(); sw.Dispose(); } else { StreamWriter sw = new StreamWriter(path, true, Encoding.UTF8); sw.WriteLine(sb.ToString().TrimEnd(',')); sw.Close(); sw.Dispose(); } return true; } catch (Exception ex) { LogHelper.WriteLogError("写入产品记录时出错!", ex); return false; } } #endregion #region 产品锁付记录进行存储 /// /// 产品锁付记录进行存储 /// /// 结果 public void WriteProductLog(List lockResults, string rfid, double ct) { try { string result = "";//产品的总结果 foreach (var item in lockResults) { result = "OK"; if (!item.LockPassed) { result = "NG"; break; } } DateTime dt = DateTime.Now; int number = lockResults.Count;//螺丝数量 string path = $"D:\\Data\\生产数据\\{dt.ToString("yyyy-MM")}\\{dt.ToString("yyyy-MM-dd")}.csv"; StringBuilder sb = new StringBuilder(); //具体的ct需要优化 sb.Append($"{dt.ToString("yyyy-MM-dd HH:mm:ss")},{lockResults[0].ProductName},{Math.Round(ct, 3)},{rfid},{result},{lockResults[number - 1].NozzleNumber},{lockResults[number - 1].HeadNumber},"); foreach (var item in lockResults) { try { string res = ""; if (item.LockPassed) { res = "Pass"; } else res = "Fail"; sb.Append($"{res},{item.ResultDetails},{item.LockTorque},{item.TargetTorqueUpperLimit},{item.TargetTorqueLowerLimit},{item.LockTurns},{item.TargetTurnsUpperLimit},{item.TargetTurnsLowerLimit},{item.Pressure},{item.PositionX},{item.PositionY},{item.StartPositionZ},{item.EndPositionZ},{item.LockDescentSpeed},"); } catch (Exception) { sb.Append($",,,,,,,,,,,,,,"); } } //======================== if (!Directory.Exists(Path.GetDirectoryName(path))) //判断文件夹是否 { Directory.CreateDirectory(Path.GetDirectoryName(path)); //创建文件夹 } if (File.Exists(path)) { // 使用 StreamWriter 以追加模式打开文件 StreamWriter sw = new StreamWriter(path, true, Encoding.UTF8); sw.WriteLine(sb.ToString().TrimEnd(',')); sw.Close(); sw.Dispose(); } else { StreamWriter sw = new StreamWriter(path, true, Encoding.UTF8); StringBuilder sb1 = new StringBuilder(); StringBuilder sb_up = new StringBuilder(); StringBuilder sb_down = new StringBuilder(); sb1.Append("时间,产品名称,CT,RFID,产品结果,吸嘴数量,披头数量,"); for (int i = 0; i < number; i++) { sb1.Append($"螺丝{i + 1}结果,螺丝{i + 1}结果详情,螺丝{i + 1}扭矩,螺丝{i + 1}扭矩上限,螺丝{i + 1}扭矩下限,螺丝{i + 1}圈数,螺丝{i + 1}圈数上限,螺丝{i + 1}圈数下限,螺丝{i + 1}压力值,螺丝{i + 1}锁附X,螺丝{i + 1}锁附Y,螺丝{i + 1}开始位置Z,螺丝{i + 1}结束位置Z,螺丝{i + 1}锁附下降速度,"); } sw.WriteLine(sb1.ToString().TrimEnd(',')); sw.WriteLine(sb.ToString().TrimEnd(',')); sw.Close(); sw.Dispose(); } } catch (Exception ex) { LogHelper.WriteLogError("写入产品记录时出错!", ex); } lockResultsAll.Clear(); } /// /// 记录当前工位的锁付结果 /// /// /// /// /// /// /// /// 是否将所有螺丝的锁付结果一行显示,还是每颗螺丝单独一行显示,默认所有颗螺丝单独一行显示 /// private bool RecordLockResult(int stationIndex, List lockResults, string productMainSN, double ct, DateTime timestamp, string savePath, bool isSingleLine = true, bool isFirstEnter = true) { try { //获取当前工位的配置 var stationConfig = _configService.GetDeviceInfo(stationIndex); if (stationConfig == null) return false; //如果保存路径为空,则不保存 if (string.IsNullOrEmpty(savePath)) return false; //如果锁付结果列表为空,则不保存 if (lockResults == null || lockResults.Count == 0) return false; string fullPath = savePath; //如果保存路径是一个文件路径,则直接保存,否则按照工位和日期进行保存 if (File.Exists(savePath) || Path.HasExtension(savePath)) { fullPath = savePath; //如果路径不存在,则创建 // 创建目录 string directory = Path.GetDirectoryName(fullPath); if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) { Directory.CreateDirectory(directory); } } else { //锁付结果保存至CSV文件,文件名格式:savePath\yyyy-MM\工位编号_工位名称_yyyy-MM-dd.csv string datePath = timestamp.ToString("yyyy-MM") + "\\"; string dirPath = Path.Combine(savePath, datePath); if (!Directory.Exists(dirPath)) Directory.CreateDirectory(dirPath); string fileName = $"{stationIndex}_{stationConfig.DeviceName}_{timestamp.ToString("yyyy-MM-dd")}.csv"; fullPath = Path.Combine(dirPath, fileName); } StringBuilder sb = new StringBuilder(); if (!File.Exists(fullPath)) { if (isSingleLine) { //将所有螺丝的锁付结果一行显示,列名格式:时间,产品主SN,CT,工位编号,工位名称,配方名称,配方编号,螺丝编号1,锁付结果1,锁付圈数1,锁付扭矩1,锁付时长1,电批程序编号1,结果详情1,压力值1,扭矩上限1,扭矩下限1,圈数上限1,圈数下限1,电批锁付速度1,锁附X1,锁附Y1,开始位置Z1,结束位置Z1,锁附下降速度1,螺丝供料器编号,...(依次类推直到螺丝编号n) sb.Append("时间,产品主SN,CT,工位编号,工位名称,配方名称,配方编号,"); for (int i = 0; i < lockResults.Count; i++) { sb.Append($"螺丝编号{i + 1},锁付结果{i + 1},锁付圈数{i + 1},锁付扭矩{i + 1},锁付时长{i + 1},电批程序编号{i + 1},结果详情{i + 1},压力值{i + 1}," + $"扭矩上限{i + 1},扭矩下限{i + 1},圈数上限{i + 1},圈数下限{i + 1},电批锁付速度{i + 1},锁附X{i + 1},锁附Y{i + 1},开始位置Z{i + 1},结束位置Z{i + 1},锁附下降速度{i + 1},螺丝供料器编号{i + 1},"); } sb.Append("吸嘴编号,披头编号,操作员姓名"); } else { sb.Append("时间,产品主SN,CT,工位编号,工位名称,配方名称,配方编号,螺丝编号,锁付结果,锁付圈数,锁付扭矩,锁付时长,电批程序编号,结果详情,压力值," + "扭矩上限,扭矩下限,圈数上限,圈数下限,电批锁付速度,锁附X,锁附Y,开始位置Z,结束位置Z,锁附下降速度,螺丝供料器编号,吸嘴编号,披头编号,操作员姓名"); } sb.AppendLine(); } if (isSingleLine) { //将所有螺丝的锁付结果一行显示,列名格式:时间,产品主SN,CT,工位编号,工位名称,配方名称,配方编号,螺丝编号1,锁付结果1,锁付圈数1,锁付扭矩1,锁付时长1,电批程序编号1,结果详情1,压力值1,扭矩上限1,扭矩下限1,圈数上限1,圈数下限1,电批锁付速度1,锁附X1,锁附Y1,开始位置Z1,结束位置Z1,锁附下降速度1,螺丝供料器编号,...(依次类推直到螺丝编号n) sb.Append($"{timestamp:yyyy-MM-dd HH:mm:ss},{productMainSN},{Math.Round(ct, 3)},{stationIndex},{stationConfig.DeviceName},{lockResults[0].ProductName},{lockResults[0].ProductNumber},"); for (int i = 0; i < lockResults.Count; i++) { var item = lockResults[i]; sb.Append($"螺丝{i + 1},{item.LockPassed},{item.LockTurns},{item.LockTorque},{item.LockDuration},{item.ScrewdriverProgramNumber}," + $"{item.ResultDetails},{item.Pressure},{item.TargetTorqueUpperLimit},{item.TargetTorqueLowerLimit},{item.TargetTurnsUpperLimit},{item.TargetTurnsLowerLimit},{item.TargetLockSpeed}," + $"{item.PositionX},{item.PositionY},{item.StartPositionZ},{item.EndPositionZ},{item.LockDescentSpeed},{item.FeederNumber},"); } sb.Append($"{lockResults[lockResults.Count - 1].NozzleNumber},{lockResults[lockResults.Count - 1].HeadNumber},{lockResults[lockResults.Count - 1].OperatorName}"); sb.AppendLine(); } else { foreach (var item in lockResults) { sb.Append($"{timestamp:yyyy-MM-dd HH:mm:ss},{productMainSN},{Math.Round(ct, 3)},{stationIndex},{stationConfig.DeviceName},{item.ProductName},{item.ProductNumber},{item.Number},{item.LockPassed},{item.LockTurns},{item.LockTorque},{item.LockDuration},{item.ScrewdriverProgramNumber}," + $"{item.ResultDetails},{item.Pressure},{item.TargetTorqueUpperLimit},{item.TargetTorqueLowerLimit},{item.TargetTurnsUpperLimit},{item.TargetTurnsLowerLimit},{item.TargetLockSpeed}," + $"{item.PositionX},{item.PositionY},{item.StartPositionZ},{item.EndPositionZ},{item.LockDescentSpeed},{item.FeederNumber},{item.NozzleNumber},{item.HeadNumber},"); sb.AppendLine(); } } // 使用 StreamWriter 以追加模式打开文件 StreamWriter sw = new StreamWriter(fullPath, true, Encoding.UTF8); sw.Write(sb.ToString()); sw.Close(); sw.Dispose(); return true; } catch (Exception ex) { if (!isFirstEnter) return false; LogHelper.WriteLogError("记录锁付结果时出错!", ex); //我希望在这里生成一个临时文件来保存这次的记录,因为如果本地文件被打开时可能会导致无法写入,如果不保存的话可能会丢失这次的记录,所以我想在这里生成一个临时文件来保存这次的记录 //生成临时文件的路径,格式为savePath\yyyy-MM\工位编号_yyyy-MM-dd_temp\工位编号_yyyy-MM-dd_HH-mm-ss-fff_temp.csv string datePath = timestamp.ToString("yyyy-MM") + "\\"; string tempDirPath = Path.Combine(savePath, datePath, $"{stationIndex}_{timestamp.ToString("yyyy-MM-dd")}_temp"); string tempFileName = $"{stationIndex}_{timestamp.ToString("yyyy-MM-dd_HH-mm-ss-fff")}_temp.csv"; RecordLockResult(stationIndex, lockResults, productMainSN, ct, timestamp, tempFileName, isSingleLine, false); return false; } } /// /// 记录其他工位的结果,其他工位要存储的数据项按照键值对集合的形式传入,键为数据项名称,值为数据项的值 /// /// /// /// /// /// /// /// /// private bool RecordOtherStationResult(int stationIndex, Dictionary dataItems, string productMainSN, double ct, DateTime timestamp, string savePath, bool isFirstEnter = true) { try { //获取当前工位的配置 var stationConfig = _configService.GetDeviceInfo(stationIndex); if (stationConfig == null) return false; // 获取当前产品 var currentProduct = _productService.GetCurrentProduct(); if (currentProduct == null) { return false; } //如果保存路径为空,则不保存 if (string.IsNullOrEmpty(savePath)) return false; string fullPath = savePath; //如果保存路径是一个文件路径,则直接保存,否则按照工位和日期进行保存 if (File.Exists(savePath) || Path.HasExtension(savePath)) { fullPath = savePath; //如果路径不存在,则创建 // 创建目录 string directory = Path.GetDirectoryName(fullPath); if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) { Directory.CreateDirectory(directory); } } else { //结果保存至CSV文件,文件名格式:savePath\yyyy-MM\工位编号_工位名称_yyyy-MM-dd.csv string datePath = timestamp.ToString("yyyy-MM") + "\\"; string dirPath = Path.Combine(savePath, datePath); if (!Directory.Exists(dirPath)) Directory.CreateDirectory(dirPath); string fileName = $"{stationIndex}_{stationConfig.DeviceName}_{timestamp.ToString("yyyy-MM-dd")}.csv"; fullPath = Path.Combine(dirPath, fileName); } StringBuilder sb = new StringBuilder(); if (!File.Exists(fullPath)) { sb.Append("时间,产品主SN,CT,工位编号,工位名称,配方名称,配方编号,"); foreach (var item in dataItems) { sb.Append($"{item.Key},"); } sb.AppendLine(); } sb.Append($"{timestamp:yyyy-MM-dd HH:mm:ss},{productMainSN},{Math.Round(ct, 3)},{stationIndex},{stationConfig.DeviceName},{currentProduct.Name},{currentProduct.NumberCode},"); foreach (var item in dataItems) { sb.Append($"{item.Value},"); } sb.AppendLine(); // 使用 StreamWriter 以追加模式打开文件 StreamWriter sw = new StreamWriter(fullPath, true, Encoding.UTF8); sw.Write(sb.ToString()); sw.Close(); sw.Dispose(); return true; } catch (Exception ex) { if (!isFirstEnter) { return false; } LogHelper.WriteLogError("记录其他工位结果时出错!", ex); //我希望在这里生成一个临时文件来保存这次的记录,因为如果本地文件被打开时可能会导致无法写入,如果不保存的话可能会丢失这次的记录,所以我想在这里生成一个临时文件来保存这次的记录 //生成临时文件的路径,格式为savePath\yyyy-MM\工位编号_yyyy-MM-dd_temp\工位编号_yyyy-MM-dd_HH-mm-ss-fff_temp.csv string datePath = timestamp.ToString("yyyy-MM") + "\\"; string tempDirPath = Path.Combine(savePath, datePath, $"{stationIndex}_{timestamp.ToString("yyyy-MM-dd")}_temp"); string tempFileName = $"{stationIndex}_{timestamp.ToString("yyyy-MM-dd_HH-mm-ss-fff")}_temp.csv"; RecordOtherStationResult(stationIndex, dataItems, productMainSN, ct, timestamp, tempFileName, false); return false; } } /// /// 记录工位的结果,其他工位要存储的数据项按照键值对集合的形式传入,键为数据项名称,值为数据项的值 /// /// /// /// /// /// /// /// /// private bool RecordStationResult(int stationIndex, IEnumerable dfcs, string productMainSN, double ct, DateTime timestamp, string savePath, bool isFirstEnter = true) { try { //获取当前工位的配置 var stationConfig = _configService.GetDeviceInfo(stationIndex); if (stationConfig == null) return false; //如果保存路径为空,则不保存 if (string.IsNullOrEmpty(savePath)) return false; string fullPath = savePath; //如果保存路径是一个文件路径,则直接保存,否则按照工位和日期进行保存 if (!File.Exists(savePath) || !Path.HasExtension(savePath)) { fullPath = savePath; //如果路径不存在,则创建 // 创建目录 string directory = Path.GetDirectoryName(fullPath); if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) { Directory.CreateDirectory(directory); } } //结果保存至CSV文件,文件名格式:savePath\yyyy-MM\工位编号_工位名称_yyyy-MM-dd.csv string datePath = timestamp.ToString("yyyy-MM") + "\\"; string dirPath = Path.Combine(savePath, datePath); if (!Directory.Exists(dirPath)) Directory.CreateDirectory(dirPath); string fileName = $"{stationIndex}_{stationConfig.DeviceName}_{timestamp.ToString("yyyy-MM-dd")}.csv"; fullPath = Path.Combine(dirPath, fileName); StringBuilder sb = new StringBuilder(); if (!File.Exists(fullPath)) { sb.Append("PPID,Screw_Number,PROCESS_NAME,PROCESS_START_TIME,CTQ_NAME,CTQ_VALUE,CTQ_LSL,CTQ_USL,PROCESS_END_TIME,SUPPLIER_NAME,COMMODITY_TYPE,REVISION,WO#,MFG_LOT#," + "MFG_ASSY_LINE,PROCESS_OUTCOME,PROCESS_MACHINE_ID,CTQ_UNIT_OF_MEASURE,ERROR_MESSAGE,PD_ATTR_01,PD_ATTR_02,PD_ATTR_03,PD_ATTR_04,PD_ATTR_05"); sb.AppendLine(); } if (dfcs == null || dfcs.Count() == 0) { return false; } foreach (var dfc in dfcs) { sb.Append($"{dfc.PPID},{dfc.ScrewNum},{dfc.PROCESS_NAME},{dfc.PROCESS_START_TIME},{dfc.CTQ_NAME},{dfc.CTQ_VALUE:F3},{dfc.CTQ_LSL:F3},{dfc.CTQ_USL:F3},{dfc.PROCESS_END_TIME},{dfc.SUPPLIER_NAME},{dfc.COMMODITY_TYPE},{dfc.REVISION},{dfc.WO},{dfc.MFG_LOT},{dfc.MFG_ASSY_LINE},{dfc.PROCESS_OUTCOME},{dfc.PROCESS_MACHINE_ID},{dfc.CTQ_UNIT_OF_MEASURE},{dfc.ERROR_MESSAGE},{dfc.PD_ATTR_01},{dfc.PD_ATTR_02},{dfc.PD_ATTR_03},{dfc.PD_ATTR_04},{dfc.PD_ATTR_05}"); sb.AppendLine(); } // 使用 StreamWriter 以追加模式打开文件 StreamWriter sw = new StreamWriter(fullPath, true, Encoding.UTF8); sw.Write(sb.ToString()); sw.Close(); sw.Dispose(); return true; } catch (Exception ex) { if (!isFirstEnter) { return false; } LogHelper.WriteLogError("记录其他工位结果时出错!", ex); //我希望在这里生成一个临时文件来保存这次的记录,因为如果本地文件被打开时可能会导致无法写入,如果不保存的话可能会丢失这次的记录,所以我想在这里生成一个临时文件来保存这次的记录 //生成临时文件的路径,格式为savePath\yyyy-MM\工位编号_yyyy-MM-dd_temp\工位编号_yyyy-MM-dd_HH-mm-ss-fff_temp.csv string datePath = timestamp.ToString("yyyy-MM") + "\\"; string tempDirPath = Path.Combine(savePath, datePath, $"{stationIndex}_{timestamp.ToString("yyyy-MM-dd")}_temp"); string tempFileName = $"{stationIndex}_{timestamp.ToString("yyyy-MM-dd_HH-mm-ss-fff")}_temp.csv"; RecordStationResult(stationIndex, dfcs, productMainSN, ct, timestamp, tempFileName, false); return false; } } #endregion #region 写log TxtFile mesLog = new TxtFile($"D:\\Data\\Mes记录", ""); public void MesMessage(string msg) { mesLog.WriteLine("== " + msg + "\r\n"); } TxtFile infoLog = new TxtFile($"D:\\Data\\运行日志", ""); public void InfoMessage(string msg) { infoLog.WriteLine("== " + msg + "\r\n"); } #endregion #region 压缩图片文件夹 /// /// 压缩文件 /// /// 原路径 /// 压缩后文件的名称 /// mes中的station /// mes中的fixtureID public async void CompressionImage(string sourcePath, string zipname, string station, string fixid) { string zipPath = ""; try { SendTaskMessage($"开始压缩文件", MessageLevel.Info); await Task.Run(() => { zipPath = $"Z:\\{station}\\{fixid}\\{DateTime.Now.ToString("yyyyMMdd")}\\{zipname}.zip";//生成的zip文件路径 string path = $"Z:\\{station}\\{fixid}\\{DateTime.Now.ToString("yyyyMMdd")}"; if (!Directory.Exists(path)) { Directory.CreateDirectory(path); } ZipFile.CreateFromDirectory(sourcePath, zipPath); }); } catch (Exception ex) { LogHelper.WriteLogError($"压缩文件时出错", ex); } } public async void CompressionSingleCsv(string csvFilePath, string zipname, string station, string fixid) { string zipPath = ""; try { SendTaskMessage("开始压缩CSV文件", MessageLevel.Info); await Task.Run(() => { if (!File.Exists(csvFilePath)) { LogHelper.WriteLogMes($"CSV文件不存在,不压缩:{csvFilePath}"); return; } zipPath = $"Z:\\{station}\\{fixid}\\{DateTime.Now.ToString("yyyyMMdd")}\\{zipname}.zip"; string path = $"Z:\\{station}\\{fixid}\\{DateTime.Now.ToString("yyyyMMdd")}"; if (!Directory.Exists(path)) { Directory.CreateDirectory(path); } using (FileStream fs = new FileStream(zipPath, FileMode.Create)) { using (ZipArchive archive = new ZipArchive(fs, ZipArchiveMode.Create)) { archive.CreateEntryFromFile(csvFilePath, Path.GetFileName(csvFilePath)); } } }); SendTaskMessage("CSV文件压缩完成", MessageLevel.Debug); } catch (Exception ex) { LogHelper.WriteLogError("压缩CSV文件失败", ex); } } #endregion /// /// 后台报警刷新显示(适配PLC结构化变量名Bool点位 OPC版) /// 完全沿用你原有循环结构、防抖逻辑、UI报警推送规则 /// public void SendAlarmToUi(OpcUaClientPLC plc) { PlcExcelLoad loadData = new PlcExcelLoad(); loadData.LoadData(6); // 增加报警状态缓存,防止循环重复刷屏报警 Dictionary alarmLastState = new Dictionary(); foreach (var alarm in loadData.AlaemList) { alarmLastState.Add(alarm.OpcAddress, true); } Task.Run(() => { while (true) { try { Thread.Sleep(50); foreach (var alarm in loadData.AlaemList) { // 调用你封装好的OPC读取Bool方法 bool plcState = plc.ReadBoolNode(alarm.OpcAddress); // 上升沿防抖:只有状态从正常→异常(false)变化时,才推送一次报警 if (!plcState && alarmLastState[alarm.OpcAddress]) { SendTaskMessage($"PLC报警信息:{alarm.Name}", MessageLevel.Error); LogHelper.WriteLogPlcWarn($"PLC报警信息:{alarm.Name}"); alarmLastState[alarm.OpcAddress] = false; } // 报警复位:状态恢复正常,重置缓存 else if (plcState && !alarmLastState[alarm.OpcAddress]) { alarmLastState[alarm.OpcAddress] = true; } } } catch { Thread.Sleep(200); } } }); } #endregion } }