using Autofac; using Bro.Common.Base; using Bro.Common.Factory; using Bro.Common.Helper; using Bro.Common.Interface; using Bro.Common.Model; using Bro.Common.Model.Authority; using HalconDotNet; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Diagnostics; using System.Drawing; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using static Bro.Common.Helper.EnumHelper; namespace Bro.Process { [Process("", DeviceAttributeType.Device)] public partial class ProcessControl : IProcess { #region AutoFac private void AutoFacRegister() { #region AutoFac注册 GlobalVar.Builder.RegisterInstance(this).As().ExternallyOwned(); GlobalVar.Builder.RegisterInstance(IConfig).As().ExternallyOwned(); GlobalVar.Builder.RegisterInstance(DeviceCollection).ExternallyOwned(); GlobalVar.Builder.RegisterInstance(ProcessMethodCollection).ExternallyOwned(); //if (isBuild) //{ // GlobalVar.Container = GlobalVar.Builder.Build(); //} #endregion } #endregion #region Event public event Action OnBitmapOutput; //public event Action OnObjectOutput; public Action OnExceptionOccured { get; set; } public event Action OnProcessStateChanged; public event Action OnLog; public event Action OnAlarmUpdate; #endregion #region Property & Field private IProcessConfig iConfig; public IProcessConfig IConfig { get => iConfig; set { iConfig = value; LoggerHelper.LogPath = iConfig.LogPath; } } private DeviceState processState = DeviceState.DSUninit; public DeviceState ProcessState { get => processState; set { if (processState != value) { processState = value; Task.Run(() => { OnProcessStateChanged?.Invoke(value); }); } } } public List DeviceCollection { get; set; } = new List(); public bool IsServiceMode { get; set; } #endregion #region 构造函数 public ProcessControl() { } public ProcessControl(string productionCode) { if (productionCode.ToLower() == "default") { productionCode = ""; } ProductionCode = productionCode; } #endregion #region Initial public void InitialProcess() { InitialProcess(""); } public void InitialProcess(string configPath) { //ProcessException.OnExceptionNotice = LogAsync; IConfig = LoadStationConfig(configPath); #region 个别配置的特别处理 #endregion _warningRemains.CollectionChanged -= WarningRemains_CollectionChanged; _warningRemains.CollectionChanged += WarningRemains_CollectionChanged; DeviceCollection.Clear(); InitialDevices(); InitialProcessMethods(); AutoFacRegister(); LogAsync(DateTime.Now, "Process Initialized", ""); } private void InitialDevices() { IConfig.GetAllDeviceInitialConfigs().ForEach(iConfig => { if (string.IsNullOrWhiteSpace(iConfig.DriverType)) { //throw new ProcessException($"{iConfig.Name}未配置驱动类型"); LogAsync(DateTime.Now, $"{iConfig.Name}未配置驱动类型", ""); return; } IDevice device = DeviceFactory.GetDeviceInstanceByTypeName(iConfig.DriverType); device.InitialConfig = iConfig; RegisterDeviceEvent(device); if (!DeviceCollection.Any(u => u.Id == device.Id)) { DeviceCollection.Add(device); } }); } /// /// 注册设备事件 /// /// protected virtual void RegisterDeviceEvent(IDevice device) { //if (device is CameraBase) //{ // CameraBase camera = device as CameraBase; // camera.OnImageUpdated -= CameraUpdateImage; // camera.OnImageUpdated += CameraUpdateImage; //} if (device is IMonitor) { IMonitor monitor = device as IMonitor; monitor.OnMonitorAlarm -= OnMonitorAlarm; monitor.OnMonitorInvoke -= OnMonitorInvoke; monitor.OnMonitorAlarm += OnMonitorAlarm; monitor.OnMonitorInvoke += OnMonitorInvoke; } } #endregion #region Open public virtual void Open() { Open(false); } [ProcessExceptionAspect] public virtual void Open(bool _isServiceMode = false) { IsServiceMode = _isServiceMode; if (ProcessState == DeviceState.DSOpen) return; OpenDevices(DeviceCollection); ProcessState = DeviceState.DSOpen; LogAsync(DateTime.Now, "Process Opened", ""); } private void OpenDevices(List devices) where T : IDevice { devices.ForEach(d => { if (d.InitialConfig?.IsEnabled ?? false) { d.OnLog -= OnDeviceLog; d.OnLog += OnDeviceLog; d.StateChange(DeviceState.DSInit); d.StateChange(DeviceState.DSOpen); } }); } #endregion #region Close [ProcessExceptionAspect] public virtual void Close() { if (ProcessState == DeviceState.DSClose) return; CloseDevice(DeviceCollection); ProcessState = DeviceState.DSClose; LogAsync(DateTime.Now, "Process Closed", ""); } private void CloseDevice(List devices) where T : IDevice { devices.ForEach(d => { if (d.CurrentState != DeviceState.DSClose) { d.StateChange(DeviceState.DSClose); } }); } #endregion #region 配置操作和产品代码 string _configBackupStr = ""; string _configPath = ""; private string productionCode = null; public string ProductionCode { get => productionCode; set { if (productionCode != value && value != null) { productionCode = value; string baseDir = SettingHelper.GetConfigFilePath(); if (string.IsNullOrWhiteSpace(baseDir)) { baseDir = AppDomain.CurrentDomain.BaseDirectory; } if (!string.IsNullOrWhiteSpace(productionCode)) { _configPath = Path.Combine(baseDir, $"Config_{productionCode}.json"); } else { _configPath = Path.Combine(baseDir, "Config.json"); } } } } private IProcessConfig LoadStationConfig(string configPath = "") { IProcessConfig config = null; if (string.IsNullOrWhiteSpace(configPath)) { configPath = _configPath; } if (File.Exists(configPath)) { using (StreamReader reader = new StreamReader(configPath, System.Text.Encoding.UTF8)) { _configBackupStr = reader.ReadToEnd(); try { config = JsonConvert.DeserializeObject(_configBackupStr, new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.All }); } catch (Exception ex) { LogAsync(DateTime.Now, $"配置反序列化异常", ex.GetExceptionMessage()); config = null; } } } if (config == null) { var attr = GetType().GetCustomAttribute(); if (attr != null) { config = ProcessFactory.CreateStationConfig(attr.ProcessCode, out string msg); if (!string.IsNullOrWhiteSpace(msg)) { throw new ProcessException($"流程配置创建失败。{msg}"); } } else { throw new ProcessException($"未能获取流程代码特性"); } } return config; } public void SaveProcessConfig(IProcessConfig config) { if (config == null) throw new ProcessException("保存的配置信息不能为空"); string newConfig = JsonConvert.SerializeObject(config, new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.All }); using (StreamWriter writer = new StreamWriter(_configPath, false, System.Text.Encoding.UTF8)) { writer.Write(newConfig); writer.Flush(); writer.Close(); } if (_configBackupStr != newConfig) { SaveBackupConfig(); } } public void CreateNewConfig(IProcessConfig config, string newProductionCode) { try { if (config == null) throw new ProcessException("保存的配置信息不能为空"); ProductionCode = newProductionCode; //生成config.json string newConfig = JsonConvert.SerializeObject(config, new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.All }); using (StreamWriter writer = new StreamWriter(_configPath, false, System.Text.Encoding.UTF8)) { writer.Write(newConfig); writer.Flush(); writer.Close(); } //添加到Setting.json SettingHelper.AddNewProductionCode(ProductionCode); } catch (Exception ex) { throw new ProcessException(ex.Message, null); } } private void SaveBackupConfig() { string backPath = Path.GetDirectoryName(_configPath); backPath = Path.Combine(backPath, ProductionCode + "_bk"); if (!Directory.Exists(backPath)) { Directory.CreateDirectory(backPath); } backPath = Path.Combine(backPath, $"Config_{ProductionCode}_{DateTime.Now.ToString("yyyyMMddHHmmss")}.json"); using (StreamWriter writer = new StreamWriter(backPath, false, System.Text.Encoding.UTF8)) { writer.Write(_configBackupStr); writer.Flush(); writer.Close(); } } #endregion #region MethodInfo解析 public List ProcessMethodCollection { get; set; } = new List(); /// /// 调用方法的字典集合 /// Key:MethodCode,Value:MethodInfo /// public Dictionary ProcessMethodDict { get; set; } = new Dictionary(); /// /// Halcon算法工具字典,在初始化时统一一次性载入 /// Key:MethodCode,Value:HalconTool /// protected Dictionary _halconToolDict = new Dictionary(); public virtual void InitialProcessMethods() { ProcessMethodDict = new Dictionary(); var methods = GetType().GetMethods().ToList(); methods.ForEach(m => { var attr = m.GetCustomAttribute(); if (attr != null) { ProcessMethodDict[attr.MethodCode] = m; ProcessMethodCollection.Add(attr); } }); #region 初始化HalconTool 根据配置的接口类型来配置 InitialHalconTool(); #endregion } #endregion #region Halcon算子设置 protected virtual void InitialHalconTool() { foreach (HDevEngineTool tool in _halconToolDict.Values) { tool?.Dispose(); } _halconToolDict = new Dictionary(); IConfig.GetAllMonitorSet().ForEach(monitorSet => { if (monitorSet.OpConfig is IHalconToolPath toolPath) { toolPath.GetHalconToolPathList().ForEach(path => { LoadHalconTool(path, monitorSet.Id); }); } }); } protected void LoadHalconTool(string algorithemPath, string prefix) { if (!string.IsNullOrWhiteSpace(algorithemPath)) { string directoryPath = Path.GetDirectoryName(algorithemPath); string fileName = Path.GetFileNameWithoutExtension(algorithemPath); HDevEngineTool tool = new HDevEngineTool(directoryPath); tool.LoadProcedure(fileName); //使用“|”作为间隔符 _halconToolDict[prefix + "|" + algorithemPath] = tool; } } /// /// 获取预先载入的Halcon算法 /// /// 操作配置,用来确认监听来源和算法路径 /// 算法路径,默认是配置中的第一个算法路径 /// Halcon算法 protected HDevEngineTool GetHalconTool(IOperationConfig config, string algorithemPath = "") { if (string.IsNullOrWhiteSpace(algorithemPath)) { algorithemPath = (config as IHalconToolPath)?.GetHalconToolPathList()[0]; } string key = config.MonitorSetId + "|" + algorithemPath; if (_halconToolDict.ContainsKey(key)) { return _halconToolDict[key]; } else { throw new ProcessException($"未能获取路径:{algorithemPath} 的算法"); } } /// /// 重置或者重新载入配置中的Halcon算法 /// /// /// internal void ResetHalconTool(IOperationConfig config) { string algorithemPath = (config as IHalconToolPath)?.GetHalconToolPathList()[0]; string key = config.MonitorSetId + "|" + algorithemPath; if (_halconToolDict.ContainsKey(key)) { _halconToolDict[key].Dispose(); } string directoryPath = Path.GetDirectoryName(algorithemPath); string fileName = Path.GetFileNameWithoutExtension(algorithemPath); HDevEngineTool tool = new HDevEngineTool(directoryPath); tool.LoadProcedure(fileName); _halconToolDict[key] = tool; } #endregion #region IMonitor监听 protected virtual void OnMonitorInvoke(DateTime dt, string deviceId, IDevice sourceDevice, IMonitorSet monitorSet) { string methodCode = monitorSet.MethodCode; if (!AuthorityCheck.IsAuthorityOK) { LogAsync(DateTime.Now, "权限检测失败", $"权限检测失败,方法{methodCode}无法执行,请联系相关人员"); monitorSet.Response = new ProcessResponse((int)ReturnValue.UNAUTHORIZATION); return; } IOperationConfig config = monitorSet.OpConfig; object res = null; int reTryTimes = config.ReTryTimes; IDevice device = DeviceCollection.FirstOrDefault(u => u.Id == deviceId); LogAsync(DateTime.Now, $"{device.Name}调用{methodCode}开始", ""); Stopwatch sw = new Stopwatch(); sw.Start(); do { try { //有IOperationConfig参数的调用 res = ProcessMethodDict[methodCode].Invoke(this, new object[] { config, device, sourceDevice }); reTryTimes = -1; } catch (Exception invokeEx) //流程动作异常失败 { Exception ex = invokeEx.InnerException ?? invokeEx; reTryTimes--; if (reTryTimes <= 0) //如果没有重试次数了就通知PLC { if (config == null || config.ExceptionValue == 0) { if (!(ex is ProcessException)) { //如果是算法异常,返回NG值;否则返回异常值 if (ex is HDevEngineException) { res = new ProcessResponse((int)ReturnValue.NGVALUE); } else { res = new ProcessResponse((int)ReturnValue.EXCEPTIONVALUE); } var newEx = new ProcessException("函数" + methodCode + "执行异常", ex, ExceptionLevel.Warning); } else { if ((ex as ProcessException).InnerException != null) { res = new ProcessResponse((int)ReturnValue.EXCEPTIONVALUE); } else { res = new ProcessResponse((int)ReturnValue.NGVALUE); } } } else { res = new ProcessResponse(config.ExceptionValue); } LogAsync(DateTime.Now, methodCode + "异常信息", ex.GetExceptionMessage()); } } if (reTryTimes > 0) { LogAsync(DateTime.Now, methodCode + " reTryTimes", reTryTimes.ToString()); } } while (reTryTimes > 0); #region 设置返回值 monitorSet.Response = res as ProcessResponse; //测试模式下始终反馈OK信号 if (IConfig.IsDemoMode && monitorSet.Response.ResultValue <= 0) { monitorSet.Response.ResultValue = (int)ReturnValue.OKVALUE; } #endregion sw.Stop(); LogAsync(DateTime.Now, $"{device.Name}调用{methodCode}完成,耗时{sw.ElapsedMilliseconds}ms", ""); TimeRecordCSV(DateTime.Now, device.Name, $"{methodCode}调用完成", (int)sw.ElapsedMilliseconds); } #endregion #region 图像处理 protected HImage CollectHImage(CameraBase camera, IOperationConfig opConfig, out string imgSetId, [CallerMemberName]string methodCode = "") { IImageSet set = null; if (IConfig.IsImageOffline) { using (OfflineImageFrm oiF = new OfflineImageFrm()) { if (oiF.ShowDialog() == System.Windows.Forms.DialogResult.OK) { set = new ImageSet { HImage = oiF.HImg, ImageSaveOption = (opConfig as CameraOprerationConfigBase).ImageSaveOption }; camera.NewImageSet(set); camera.SaveOriginImage(oiF.Image, oiF.Image, set.Id); } else { throw new ProcessException("未能获取离线图片!"); } } } else { Stopwatch sw = new Stopwatch(); sw.Start(); var cameraConifg = opConfig as CameraOprerationConfigBase; if (cameraConifg.DelayBefore > 0) { Thread.Sleep(cameraConifg.DelayBefore); } camera.UploadOperationConfig(opConfig); set = camera.Snapshot(opConfig); if (cameraConifg.DelayAfter > 0) { Thread.Sleep(cameraConifg.DelayAfter); } sw.Stop(); LogAsync(DateTime.Now, $"{methodCode}采图耗时:{sw.ElapsedMilliseconds}ms", ""); TimeRecordCSV(DateTime.Now, camera.Name, methodCode + "采图", (int)sw.ElapsedMilliseconds); } imgSetId = set?.Id; return set.HImage; } protected async void CameraUpdateImage(CameraBase camera, Bitmap image, string setId) { await Task.Run(() => { OnBitmapOutput?.Invoke(camera.InitialConfig.Id, image, setId); }); } #endregion #region 报警和DownTime ObservableCollection _warningRemains = new ObservableCollection(); protected virtual void OnMonitorAlarm(DateTime dt, IDevice device, IWarningSet warning) { if (warning.CurrentStatus) { if (!_warningRemains.Any(u => u.WarningCode == warning.WarningCode)) { _warningRemains.Add(warning); } } else { var ws = _warningRemains.FirstOrDefault(u => u.WarningCode == warning.WarningCode); if (ws != null) { _warningRemains.Remove(ws); } } } private void WarningRemains_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { string alarmMsg = ""; if (_warningRemains.Count > 0) { alarmMsg = string.Join("; ", _warningRemains.Select(u => $"{u.TriggerTime.ToString("HH:mm:ss")} {u.Source} 报警:{u.WarningDescription}")); } OnAlarmUpdate?.BeginInvoke(alarmMsg, null, null); } #endregion #region ILogger public LoggerHelper LoggerHelper { get; set; } = new LoggerHelper(); public virtual void LogAsync(DateTime dt, string prefix, string msg) { OnLog?.BeginInvoke(dt, prefix, msg, null, null); if (IConfig.IsLogEnabled) { LoggerHelper.LogAsync(dt, prefix, msg); } } protected void OnDeviceLog(DateTime dt, string prefix, string msg) { OnLog?.BeginInvoke(dt, prefix, msg, null, null); } static readonly object _lockTimeCSV = new object(); private async void TimeRecordCSV(DateTime dt, string deviceName, string prefix, int ms) { await Task.Run(() => { lock (_lockTimeCSV) { string filePath = Path.Combine(IConfig.LogPath, $"TimeRecords_{DateTime.Now.ToString("yyyyMMdd")}.csv"); bool isFileExisted = File.Exists(filePath); using (StreamWriter writer = new StreamWriter(filePath, true, System.Text.Encoding.UTF8)) { if (!isFileExisted) { writer.WriteLine("Time,Device,Prefix,Consumed"); } writer.WriteLine($"{dt.ToString("HH:mm:ss.fff")},{deviceName},{prefix},{ms}"); writer.Flush(); writer.Close(); } } }); } #endregion #region 临时数据保存和读取 protected static Dictionary _tempDataLock = new Dictionary(); protected string _tempFileDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "TempData"); protected virtual T ReadTempDataFromHistory(T t, string propertyName) where T : class { if (!_tempDataLock.ContainsKey(propertyName)) { _tempDataLock[propertyName] = new object(); } if (t != null) { return t; } else { if (!Directory.Exists(_tempFileDirectory)) { Directory.CreateDirectory(_tempFileDirectory); } string filePath = Path.Combine(_tempFileDirectory, propertyName); if (!File.Exists(filePath)) { return null; } lock (_tempDataLock[propertyName]) { using (StreamReader reader = new StreamReader(filePath, System.Text.Encoding.UTF8)) { string content = reader.ReadToEnd(); var result = JsonConvert.DeserializeObject(content); return result; } } } } protected virtual void SaveTempData(T t, string propertyName, string defaultStr = "") { //await Task.Run(() => { if (!_tempDataLock.ContainsKey(propertyName)) { _tempDataLock[propertyName] = new object(); } lock (_tempDataLock[propertyName]) { try { if (!Directory.Exists(_tempFileDirectory)) { Directory.CreateDirectory(_tempFileDirectory); } string serialStr = JsonConvert.SerializeObject(t); if (!string.IsNullOrWhiteSpace(defaultStr)) { if (t == null) { serialStr = defaultStr; } } else { if (t == null) return; } string filePath = Path.Combine(_tempFileDirectory, propertyName); using (StreamWriter writer = new StreamWriter(filePath, false, System.Text.Encoding.UTF8)) { writer.Write(serialStr); writer.Flush(); writer.Close(); } } catch (Exception) { } } } //); } #endregion #region 结果上传 public Action OnUpdateResult { get; set; } public Action OnUpdateCT { get; set; } #endregion } public enum ReturnValue { OKVALUE = 1, NGVALUE = -1, EXCEPTIONVALUE = -2, UNAUTHORIZATION = -10, } }