LB_SmartVision/VisionForm.cs
@@ -1,18 +1,28 @@
using HalconDotNet;
using LB_SmartVision.Forms;
using LB_SmartVision.Forms.Pages;
using LB_SmartVision.Forms.Pages.CameraPage;
using LB_SmartVision.Forms.Pages.CommunicatorPage;
using LB_SmartVision.Forms.Pages.HistoricalData;
using LB_SmartVision.Forms.Pages.MESPage;
using LB_SmartVision.Forms.Pages.MotionControlPage;
using LB_SmartVision.Forms.Pages.ProcessPage;
using LB_SmartVision.Forms.Pages.SettingPage;
using LB_SmartVision.Forms.Pages.UserManagementPage;
using LB_SmartVision.ProcessRun;
using LB_SmartVision.SQL;
using LB_SmartVision.Tool;
using LB_SmartVisionCommon;
using LB_SmartVisionLoginUI;
using LB_VisionProcesses;
using LB_VisionProcesses.Cameras;
using LB_VisionProcesses.Cameras.HRCameras;
using LB_VisionProcesses.Cameras.LBCameras;
using LB_VisionProcesses.Communicators;
using LB_VisionProcesses.Communicators.SiemensS7;
using LB_VisionProcesses.Communicators.TCom;
using LB_VisionProcesses.Forms;
using log4net.Config;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Sunny.UI;
@@ -34,8 +44,9 @@
{
    public partial class VisionForm : Form
    {
        AllProcessesPage AllProcessesPage = new AllProcessesPage();
        AllProcessesPage AllProcessesPages = new AllProcessesPage();
        CamerasEditPage CamerasEditPage = new CamerasEditPage();
        HistoricalDataEditPage HistoricalDataEditPage = new HistoricalDataEditPage();
        CommunicatorsEditPage CommunicatorsEditPage = new CommunicatorsEditPage();
        SettingEditPage SettingEditPage = new SettingEditPage();
        MESEditPage MESEditPage = new MESEditPage();
@@ -56,11 +67,16 @@
            Assembly_LB_VisionProcessesDll = Assembly.Load(Assembly_LB_VisionProcessesBytes);
            GlobalVar.dicCommunicators.DictionaryChanged += CommunicatorsChanged;
            GlobalVar.dicCameras.DictionaryChanged += CamerasChanged;
            GlobalVar.dicProcesses.DictionaryChanged += ProcessRunBllChanged;
            //最开始就清空所有Tab页
            materialTabControl.TabPages.Clear();
            materialTabControl.Controls.Add(AllProcessesPage);
            AllProcessesPages.controlsPanel.Dock = DockStyle.Fill;
            materialTabControl.Controls.Add(AllProcessesPages);
            HistoricalDataEditPage.LogInfo += LogInfo;
            materialTabControl.Controls.Add(new MyPage(HistoricalDataEditPage));
            CamerasEditPage.LogInfo += LogInfo;
            materialTabControl.Controls.Add(new MyPage(CamerasEditPage));
@@ -84,7 +100,6 @@
            {
                //materialTabControl.TabPages[i].Font= new Font("Microsoft YaHei UI", 18F, FontStyle.Regular, GraphicsUnit.Point, 0);
            }
            materialTabSelector.BaseTabControl = materialTabControl;
            //materialTabSelector.Font = new Font("Microsoft YaHei UI", 18F, FontStyle.Regular, GraphicsUnit.Point, 0);
        }
@@ -226,12 +241,126 @@
        private void CommunicatorsChanged(object? sender, DictionaryChangedEventArgs<string, BaseCommunicator> e)
        {
            try
            {
                switch (e.ChangeType)
                {
                    case DictionaryChangeType.Added:
                        e.NewValue.TriggerRunMessageReceived += TriggerRunMessageReceived;
                        LogInfo($"通讯口[{e.NewValue.CommunicatorName}]加载触发通讯", LogInfoType.INFO);
                        e.NewValue.CommunicatorName = e.NewKey;
                        break;
                    case DictionaryChangeType.Renamed:
                        string OldCommunicatorName = e.OldKey;
                        string NewCommunicatorName = e.NewKey;
                        LogInfo(string.Format("重命名通讯口名[{0}]修改为[{1}]", OldCommunicatorName, NewCommunicatorName), LogInfoType.INFO);
                        e.NewValue.CommunicatorName = NewCommunicatorName;
                        break;
                    case DictionaryChangeType.Removed:
                        if (e.OldValue != null && e.OldValue is BaseCommunicator)
                            e.OldValue.Disconnect();
                        e.OldValue.TriggerRunMessageReceived -= TriggerRunMessageReceived;
                        LogInfo($"通讯口[{e.OldValue.CommunicatorName}]移除触发通讯", LogInfoType.INFO);
                        break;
                }
            }
            catch { }
        }
        private void LogInfo(string arg1, LogInfoType type)
        private void LogInfo(string strLog, LogInfoType infoType)
        {
            if (string.IsNullOrEmpty(strLog))
            {
                return;
            }
            string strInfo = DateTime.Now.ToString("[yyyy:MM:dd:HH:mm:ss:fff] ");
            strInfo += strLog;
            if (infoType != LogInfoType.NOSHOW)
            {
                // 如果当前不是 UI 线程,则通过 Invoke 将操作调度到 UI 线程
                if (this.rich_Info.InvokeRequired)
                {
                    this.rich_Info.BeginInvoke(new Action<string>((msg) =>
                    {
                        if (this.rich_Info.Lines.Length > 1000)
                        {
                            this.rich_Info.Clear();
                        }
                        switch (infoType)
                        {
                            case LogInfoType.INFO:
                                {
                                    this.rich_Info.SelectionColor = Color.Wheat;
                                    AsyncLogHelper.Info(strLog);
                                    break;
                                }
                            case LogInfoType.WARN:
                                {
                                    this.rich_Info.SelectionColor = Color.Yellow;
                                    AsyncLogHelper.Warn(strLog);
                                    break;
                                }
                            case LogInfoType.PASS:
                                {
                                    this.rich_Info.SelectionColor = Color.Green;
                                    AsyncLogHelper.Info(strLog);
                                    break;
                                }
                            case LogInfoType.ERROR:
                                {
                                    this.rich_Info.SelectionColor = Color.Red;
                                    AsyncLogHelper.Error(strLog);
                                    break;
                                }
                        }
                        // 更新 UI 控件,比如显示接收到的消息
                        this.rich_Info.AppendText(strInfo);
                        this.rich_Info.AppendText("\r\n");
                        this.rich_Info.SelectionStart = this.rich_Info.Text.Length;
                        this.rich_Info.ScrollToCaret();
                    }), strInfo);
                }
                else
                {
                    if (this.rich_Info.Lines.Length > 1000)
                    {
                        this.rich_Info.Clear();
                    }
                    // 如果已经在 UI 线程上,直接更新 UI
                    switch (infoType)
                    {
                        case LogInfoType.INFO:
                            {
                                this.rich_Info.SelectionColor = Color.Wheat;
                                AsyncLogHelper.Info(strLog);
                                break;
                            }
                        case LogInfoType.WARN:
                            {
                                this.rich_Info.SelectionColor = Color.Yellow;
                                AsyncLogHelper.Warn(strLog);
                                break;
                            }
                        case LogInfoType.PASS:
                            {
                                this.rich_Info.SelectionColor = Color.Green;
                                AsyncLogHelper.Info(strLog);
                                break;
                            }
                        case LogInfoType.ERROR:
                            {
                                this.rich_Info.SelectionColor = Color.Red;
                                AsyncLogHelper.Error(strLog);
                                break;
                            }
                    }
                    this.rich_Info.AppendText(strInfo);
                    this.rich_Info.AppendText("\r\n");
                    this.rich_Info.SelectionStart = this.rich_Info.Text.Length;
                    this.rich_Info.ScrollToCaret();
                }
            }
        }
        public static bool SaveAllLayout()
@@ -406,9 +535,58 @@
            }
            catch { return false; }
        }
        private void EnsureDirectory(string path)
        {
            // 如果是相对路径,转换为绝对路径
            string fullPath = Path.IsPathRooted(path) ? path : Path.GetFullPath(path);
            if (!Directory.Exists(fullPath))
            {
                Directory.CreateDirectory(fullPath);
                LogInfo($"✅ 目录创建: {fullPath}", LogInfoType.INFO);
            }
            else
            {
                LogInfo($"ℹ️ 目录已存在: {fullPath}", LogInfoType.INFO);
            }
        }
        private void CamerasChanged(object sender, DictionaryChangedEventArgs<string, BaseCamera> e)
        {
            try
            {
                switch (e.ChangeType)
                {
                    case DictionaryChangeType.Added:
                        e.NewValue.TriggerRunMessageReceived += TriggerRunMessageReceived;
                        LogInfo($"相机[{e.NewValue.SN}]加载触发通讯", LogInfoType.INFO);
                        e.NewValue.SN = e.NewKey;
                        break;
                    case DictionaryChangeType.Removed:
                        if (e.OldValue != null && e.OldValue is BaseCommunicator)
                            e.OldValue.CloseDevice();
                        e.OldValue.TriggerRunMessageReceived -= TriggerRunMessageReceived;
                        LogInfo($"相机[{e.OldValue.SN}]移除触发通讯", LogInfoType.INFO);
                        break;
                }
            }
            catch { }
        }
        private void VisionForm_Load(object sender, EventArgs e)
        {
            XmlConfigurator.Configure(new System.IO.FileInfo("log4net.config"));
            string[] paths = {
            @"生产日志\Run",
            @"生产日志\Debug",
            @"生产日志\Error",
            @"生产日志\Fatal",
            @"生产日志\Warn",
            };
            foreach (string path in paths)
            {
                EnsureDirectory(path);
            }
            if (!LB_SmartVision.Tool.Tool.ReadStringConfig("数据库名称", out string DateBaseName))
            {
                DateBaseName = "产品0";
@@ -417,72 +595,108 @@
            }
            LB_SmartVision.Tool.Tool.ReadStringConfig("User ID", out string User_ID);
            LB_SmartVision.Tool.Tool.ReadStringConfig("Password", out string Password);
            GlobalVar.strProductName = DateBaseName;
            //加载通讯
            foreach (BaseCommunicator com in GlobalVar.dicCommunicators.Values)
            {
                com.Disconnect();
            }
            GlobalVar.dicCommunicators.Clear();
            if (LoadAllCommunicators(GlobalVar.allCommunicatorsConnectionStringPath))
            {
                LogInfo("通讯加载成功", LogInfoType.PASS);
            }
            else
            {
                LogInfo("通讯加载失败", LogInfoType.ERROR);
            }
            //加载相机
            foreach (BaseCamera camera in GlobalVar.dicCameras.Values)
            {
                camera.Dispose();
            }
            GlobalVar.dicCameras.Clear();
            if (LoadAllCameras(GlobalVar.allCamerasConnectionStringPath))
            {
                LogInfo("相机加载成功", LogInfoType.PASS);
            }
            else
            {
                LogInfo("相机加载失败", LogInfoType.ERROR);
            //必须先加载相机和通讯端口,因为流程加载过程中会用到相机和通讯口
            }
            //加载全局变量
            IProcess.dicGlobalVars.Clear();
            if (LoadAllProcessVars(GlobalVar.allProcessVarsPath))
            {
                LogInfo("全局变量加载成功", LogInfoType.PASS);
            }
            else
            {
                LogInfo("全局变量加载失败", LogInfoType.ERROR);
            }
            GlobalVar.dicMotionControlData.Clear();
            if (LoadMotionControlDatas(GlobalVar.allMotionControlDataPath))
            {
                LogInfo("运动控制参数加载成功", LogInfoType.PASS);
            }
            else
            {
                LogInfo("运动控制参数加载失败", LogInfoType.ERROR);
            }
            //加载流程
            GlobalVar.dicProcesses.Clear();
            if (LoadAllProcess(GlobalVar.allProcessConnectionStringPath))
            {
                LogInfo("流程加载成功", LogInfoType.PASS);
            }
            else
            {
                LogInfo("流程加载失败", LogInfoType.ERROR);
            }
            //加载触发设置
            if (LoadAllProcessSetting(GlobalVar.allProcessSettingStringPath))
            {
                LogInfo("流程设置加载成功", LogInfoType.PASS);
            }
            else
            {
                LogInfo("流程设置加载失败", LogInfoType.ERROR);
            }
            //加载流程布局
            GlobalVar.dicLayout.Clear();
            if (LoadAllLayout(GlobalVar.allLayoutPath))
            {
                LogInfo("流程布局加载成功", LogInfoType.PASS);
            }
            else
            {
                LogInfo("流程布局加载失败", LogInfoType.ERROR);
            }
            //加载流程表格
            GlobalVar.dicCsvSetting.Clear();
            if (LoadAllCsv(GlobalVar.allCsvPath))
            {
                LogInfo("流程表格加载成功", LogInfoType.PASS);
            }
            else
            {
                LogInfo("流程表格加载失败", LogInfoType.ERROR);
            }
            //显示所有产品
            com_ProductName.Items.Clear();
            LB_SmartVision.Tool.Tool.ReadStringConfig("产品列表", out string Products);
            List<string> lstProduct = (Products.Split(',')).ToList();
            foreach (string DatabaseName in lstProduct)
            {
                com_ProductName.Items.Add(DatabaseName);
            }
            com_ProductName.Items.Add("新增");
            com_ProductName.Text = GlobalVar.strProductName;
            this.WindowState = FormWindowState.Maximized;
            DatabaseRecordProductDataHelper.InitializeDatabase();
        }
        public void SaveAllSetting()
@@ -494,6 +708,54 @@
            SaveAllProcessSetting();
            SaveAllLayout();
            SaveAllCsv();
            SaveMotionControlDatas();
        }
        public bool LoadMotionControlDatas(string alMotionControlDataPath)
        {
            try
            {
                GlobalVar.dicMotionControlData = ConfigManager<ConcurrentDictionary<string, ConcurrentDictionary<string, RecordMotionControlData>>>.LoadConfig<ConcurrentDictionary<string, ConcurrentDictionary<string, RecordMotionControlData>>>(alMotionControlDataPath);
            }
            catch
            {
                return false;
            }
            return true;
        }
        public bool SaveMotionControlDatas()
        {
            try
            {
                ConcurrentDictionary<string, List<string>> removeCameraSN = new ConcurrentDictionary<string, List<string>>();
                foreach (var item in GlobalVar.dicMotionControlData.Keys)
                {
                    List<string> list = new List<string>();
                    foreach (var itemSN in GlobalVar.dicMotionControlData[item].Keys)
                    {
                        if (!GlobalVar.dicCameras.Keys.Contains(itemSN))
                        {
                            list.Add(itemSN);
                        }
                    }
                    if (list.Count > 0)
                    {
                        removeCameraSN.TryAdd(item, list);
                    }
                }
                foreach (var item in removeCameraSN.Keys)
                {
                    foreach (var itemSN in removeCameraSN[item])
                    {
                        GlobalVar.dicMotionControlData[item].Keys.Remove(itemSN);
                    }
                }
                ConfigManager<ConcurrentDictionary<string, ConcurrentDictionary<string, RecordMotionControlData>>>.SaveConfig<ConcurrentDictionary<string, ConcurrentDictionary<string, RecordMotionControlData>>>(GlobalVar.dicMotionControlData, GlobalVar.allMotionControlDataPath);
            }
            catch
            {
                return false;
            }
            return true;
        }
        public bool LoadAllCameras(string allCamerasConnectionStringPath)
@@ -512,7 +774,6 @@
                strJson = streamReader.ReadToEnd();
                streamReader.Close();
            }
            GlobalVar.allCamerasConnectionString = JsonConvert.DeserializeObject<ConcurrentDictionary<string, string>>(strJson);
            if (GlobalVar.allCamerasConnectionString == null)
            {
@@ -526,18 +787,23 @@
                switch (brand)
                {
                    case CameraBrand.HRCamera:
                        //camera = new HRCamera();
                        break;
                        {
                            camera = new HRCamera();
                            break;
                        }
                    case CameraBrand.LBCamera:
                        //camera = new LBCamera();
                        break;
                        {
                            camera = new LBCamera();
                            break;
                        }
                    default:
                        MessageBox.Show($"[{CameraConnectionString.Key}]品牌不支持!", "异常");
                        continue;
                        {
                            MessageBox.Show($"[{CameraConnectionString.Key}]品牌不支持!", "异常");
                            continue;
                        }
                }
                camera.SN = CameraConnectionString.Key;
                if (!camera.InitDevice(CameraConnectionString.Key, this.Handle))
                if (!camera.InitDevice(CameraConnectionString.Key, IntPtr.Zero))
                {
                    LogInfo($"初始化相机[{CameraConnectionString.Key}]失败", LogInfoType.ERROR);
                    if (camera != null)
@@ -563,8 +829,9 @@
                    string CameraBrand = item.Value.Brand.ToString();//"1111"
                    if (string.IsNullOrEmpty(CameraSN) || string.IsNullOrEmpty(CameraBrand))
                    {
                        break;
                    }
                    GlobalVar.allCamerasConnectionString.TryAdd(CameraSN, CameraBrand);
                }
                var settings = new JsonSerializerSettings
@@ -629,61 +896,121 @@
                {
                    string CommunicatorName = CommunicatorConnectionString.Key;
                    string CommunicatorAddress = CommunicatorConnectionString.Value;
                    // 定义正则表达式以提取协议、IP 地址和端口
                    //1.    \((.*?)\):\(和 \) 是用于匹配括号的转义字符。
                    //      (.*?) 是一个非贪婪的匹配,用来匹配类名(MyProcesses.Communicators.TCPServer 或 MyProcesses.Communicators.UARTPort)。
                    //2.    ([^:] +):匹配冒号之前的部分,即地址(127.0.0.1 或 COM5)。这里使用了[^:] 来匹配除了冒号之外的任意字符。
                    //3.    (\d +) :匹配端口号,确保它匹配一个或多个数字。
                    string pattern = @"^\((?<ClassName>[^)]+)\)\[(?<IP>[^]]+)\]\[(?<PORT>[^]]+)\]$";
                    Match match = Regex.Match(CommunicatorAddress, pattern);
                    if (match.Success)
                    if (!string.IsNullOrEmpty(CommunicatorAddress) && CommunicatorAddress.Contains("SiemensLBS7"))
                    {
                        string ClassName = match.Groups["ClassName"].Value;   // "TCP"
                        string IP = match.Groups["IP"].Value;          // "127.0.0.1"
                        string PORT = match.Groups["PORT"].Value;        // "1111"
                        // 定义正则表达式以提取协议、IP 地址和端口
                        // 更新正则以支持可选的数据类型字段
                        string pattern = @"^\((?<ClassName>[^)]+)\)\[(?<IP>[^]]+)\]\[(?<Slot>[^]]+)\]\[(?<CpuType>[^]]+)\]\[(?<PlcAddress>[^]]+)\](?:\[(?<DataType>[^]]+)\])?$";
                        Match match = Regex.Match(CommunicatorAddress, pattern);
                        if (string.IsNullOrEmpty(ClassName) || string.IsNullOrEmpty(IP) || string.IsNullOrEmpty(PORT))
                            break;
                        //利用反射创建实例
                        Type type = IProcess.GetExecutingAssembly().GetType(ClassName);
                        if (type == null)
                        if (match.Success)
                        {
                            Debug.WriteLine("Class not found.");
                            return false;
                        }
                        var Communicator = Activator.CreateInstance(type, CommunicatorName) as BaseCommunicator;
                            string ClassName = match.Groups["ClassName"].Value;   // "TCP"
                            string IP = match.Groups["IP"].Value;          // "127.0.0.1"
                            string Slot = match.Groups["Slot"].Value;        // "1111"
                            string CpuType = match.Groups["CpuType"].Value;
                            string PlcAddress = match.Groups["PlcAddress"].Value;
                            string DataType = match.Groups["DataType"].Success ? match.Groups["DataType"].Value : "String";
                        if (Communicator == null)
                        {
                            Debug.WriteLine("BaseCommunicator not found.");
                            return false;
                        }
                            if (string.IsNullOrEmpty(ClassName) || string.IsNullOrEmpty(IP) || string.IsNullOrEmpty(Slot) || string.IsNullOrEmpty(CpuType) || string.IsNullOrEmpty(PlcAddress))
                                break;
                        //TCP客户端最后再连接
                        if (Communicator is TCPClient)
                        {
                            clientsCommunicatorsConnectionString.TryAdd(CommunicatorConnectionString.Key, CommunicatorConnectionString.Value);
                            continue;
                        }
                            //利用反射创建实例
                            Type type = IProcess.GetExecutingAssembly().GetType(ClassName);
                            if (type == null)
                            {
                                Debug.WriteLine("Class not found.");
                                return false;
                            }
                            var Communicator = Activator.CreateInstance(type, CommunicatorName) as BaseCommunicator;
                        Communicator.CommunicatorConnections.Add("地址", IP);
                        Communicator.CommunicatorConnections.Add("端口", PORT);
                        Communicator.CommunicatorName = CommunicatorName;
                        if (!Communicator.Connect())
                            LogInfo($"初始化通讯口[{CommunicatorName}]失败,原因是{Communicator.Msg}", LogInfoType.ERROR);
                            if (Communicator == null)
                            {
                                Debug.WriteLine("BaseCommunicator not found.");
                                return false;
                            }
                            Communicator.CommunicatorConnections.Add("地址", IP);
                            Communicator.CommunicatorConnections.Add("端口", Slot);
                            Communicator.CommunicatorConnections.Add("型号", CpuType);
                            Communicator.CommunicatorConnections.Add("变量地址", PlcAddress);
                            Communicator.CommunicatorConnections.Add("数据类型", DataType);
                            Communicator.CommunicatorName = CommunicatorName;
                            if (!Communicator.Connect())
                            {
                                LogInfo($"初始化通讯口[{CommunicatorName}]失败,原因是{Communicator.Msg}", LogInfoType.ERROR);
                            }
                            else
                            {
                                LogInfo($"初始化通讯口[{CommunicatorName}]成功", LogInfoType.PASS);
                            }
                            GlobalVar.dicCommunicators.TryAdd(CommunicatorName, Communicator);
                        }
                        else
                            LogInfo($"初始化通讯口[{CommunicatorName}]成功", LogInfoType.PASS);
                        GlobalVar.dicCommunicators.TryAdd(CommunicatorName, Communicator);
                        {
                            Debug.WriteLine("No match found.");
                        }
                    }
                    else
                    {
                        Debug.WriteLine("No match found.");
                    }
                        // 定义正则表达式以提取协议、IP 地址和端口
                        //1.    \((.*?)\):\(和 \) 是用于匹配括号的转义字符。
                        //      (.*?) 是一个非贪婪的匹配,用来匹配类名(MyProcesses.Communicators.TCPServer 或 MyProcesses.Communicators.UARTPort)。
                        //2.    ([^:] +):匹配冒号之前的部分,即地址(127.0.0.1 或 COM5)。这里使用了[^:] 来匹配除了冒号之外的任意字符。
                        //3.    (\d +) :匹配端口号,确保它匹配一个或多个数字。
                        string pattern = @"^\((?<ClassName>[^)]+)\)\[(?<IP>[^]]+)\]\[(?<PORT>[^]]+)\]$";
                        Match match = Regex.Match(CommunicatorAddress, pattern);
                        if (match.Success)
                        {
                            string ClassName = match.Groups["ClassName"].Value;   // "TCP"
                            string IP = match.Groups["IP"].Value;          // "127.0.0.1"
                            string PORT = match.Groups["PORT"].Value;        // "1111"
                            if (string.IsNullOrEmpty(ClassName) || string.IsNullOrEmpty(IP) || string.IsNullOrEmpty(PORT))
                                break;
                            //利用反射创建实例
                            Type type = IProcess.GetExecutingAssembly().GetType(ClassName);
                            if (type == null)
                            {
                                Debug.WriteLine("Class not found.");
                                return false;
                            }
                            var Communicator = Activator.CreateInstance(type, CommunicatorName) as BaseCommunicator;
                            if (Communicator == null)
                            {
                                Debug.WriteLine("BaseCommunicator not found.");
                                return false;
                            }
                            //TCP客户端最后再连接
                            if (Communicator is TCPClient)
                            {
                                clientsCommunicatorsConnectionString.TryAdd(CommunicatorConnectionString.Key, CommunicatorConnectionString.Value);
                                continue;
                            }
                            Communicator.CommunicatorConnections.Add("地址", IP);
                            Communicator.CommunicatorConnections.Add("端口", PORT);
                            Communicator.CommunicatorName = CommunicatorName;
                            if (!Communicator.Connect())
                            {
                                LogInfo($"初始化通讯口[{CommunicatorName}]失败,原因是{Communicator.Msg}", LogInfoType.ERROR);
                            }
                            else
                            {
                                LogInfo($"初始化通讯口[{CommunicatorName}]成功", LogInfoType.PASS);
                            }
                            GlobalVar.dicCommunicators.TryAdd(CommunicatorName, Communicator);
                        }
                        else
                        {
                            Debug.WriteLine("No match found.");
                        }
                    }
                }
                //TCP客户端最后连接
@@ -708,7 +1035,9 @@
                        string PORT = match.Groups[3].Value;        // "1111"
                        if (string.IsNullOrEmpty(ClassName) || string.IsNullOrEmpty(IP) || string.IsNullOrEmpty(PORT))
                        {
                            break;
                        }
                        //利用反射创建实例
                        Type type = IProcess.GetExecutingAssembly().GetType(ClassName);
@@ -729,10 +1058,13 @@
                        Communicator.CommunicatorConnections.Add("端口", PORT);
                        Communicator.CommunicatorName = CommunicatorName;
                        if (!Communicator.Connect())
                        {
                            LogInfo($"初始化通讯口[{CommunicatorName}]失败,原因是{Communicator.Msg}", LogInfoType.ERROR);
                        }
                        else
                        {
                            LogInfo($"初始化通讯口[{CommunicatorName}]成功", LogInfoType.PASS);
                        }
                        GlobalVar.dicCommunicators.TryAdd(CommunicatorName, Communicator);
                    }
                    else
@@ -753,20 +1085,36 @@
            {
                string strJson = string.Empty;
                GlobalVar.allCommunicatorsConnectionString = new ConcurrentDictionary<string, string>();
                foreach (var item in GlobalVar.dicCommunicators)
                {
                    string ClassName = item.Value.GetType().FullName;// "TCP"
                    string IP = item.Value.CommunicatorConnections["地址"].ToString();//"127.0.0.1"
                    string PORT = item.Value.CommunicatorConnections["端口"].ToString();//"1111"
                    if (!string.IsNullOrEmpty(ClassName) && ClassName.Contains("SiemensLBS7"))
                    {
                        string CpuType = item.Value.CommunicatorConnections["型号"].ToString();
                        string PlcAddress = item.Value.CommunicatorConnections["变量地址"].ToString();
                        string DataType = item.Value.CommunicatorConnections.Contains("数据类型") ?
                                          item.Value.CommunicatorConnections["数据类型"].ToString() : "String";
                    if (string.IsNullOrEmpty(ClassName) || string.IsNullOrEmpty(IP) || string.IsNullOrEmpty(PORT))
                        break;
                    string CommunicatorConnectionString = $"({ClassName})[{IP}][{PORT}]";
                    GlobalVar.allCommunicatorsConnectionString.TryAdd(item.Key, CommunicatorConnectionString);
                        if (string.IsNullOrEmpty(ClassName) || string.IsNullOrEmpty(IP) || string.IsNullOrEmpty(PORT) || string.IsNullOrEmpty(CpuType) || string.IsNullOrEmpty(PlcAddress))
                        {
                            break;
                        }
                        string CommunicatorConnectionString = $"({ClassName})[{IP}][{PORT}][{CpuType}][{PlcAddress}][{DataType}]";
                        GlobalVar.allCommunicatorsConnectionString.TryAdd(item.Key, CommunicatorConnectionString);
                    }
                    else
                    {
                        if (string.IsNullOrEmpty(ClassName) || string.IsNullOrEmpty(IP) || string.IsNullOrEmpty(PORT))
                        {
                            break;
                        }
                        string CommunicatorConnectionString = $"({ClassName})[{IP}][{PORT}]";
                        GlobalVar.allCommunicatorsConnectionString.TryAdd(item.Key, CommunicatorConnectionString);
                    }
                    GlobalVar.dicCommunicators[item.Key].ClassName = ClassName;
                }
                var settings = new JsonSerializerSettings
                {
                    Formatting = Formatting.Indented,
@@ -790,6 +1138,11 @@
                    { }
                }
                File.WriteAllText(GlobalVar.allCommunicatorsConnectionStringPath, strJson, Encoding.UTF8);
                foreach (var item in GlobalVar.dicCommunicators)
                {
                    string ClassName = item.Value.GetType().FullName;// "TCP"
                }
                ConfigManager<ObservableConcurrentDictionary<string, BaseCommunicator>>.SaveConfig<ObservableConcurrentDictionary<string, BaseCommunicator>>(GlobalVar.dicCommunicators, GlobalVar.strApplicationPath + "\\所有产品\\" + GlobalVar.strProductName + "\\dicCommunicators.json");
                return true;
            }
            catch { return false; }
@@ -817,17 +1170,19 @@
                List<string> lstProcessName = JsonConvert.DeserializeObject<List<string>>(strJson);
                if (lstProcessName == null)
                {
                    return false;
                }
                // 使用方式
                var sortedKeys = lstProcessName
                    .OrderBy(k => k, new NaturalStringComparer())
                    .ToList();
                GlobalVar.dicProcesses.Clear();
                foreach (var ProcessName in sortedKeys)
                {
                    GlobalVar.dicProcesses.TryAdd(ProcessName
                        , new ProcessRunBll(ProcessName, GlobalVar.dicCameras, GlobalVar.dicCommunicators));
                }
                return true;
            }
            catch { return false; }
@@ -864,7 +1219,9 @@
                foreach (var process in GlobalVar.dicProcesses.Values)
                {
                    if (!process.Save(out string msg))
                    {
                        LogInfo($"流程[{process.Name}]保存失败,原因:{msg}", LogInfoType.NOSHOW);
                    }
                }
                try
@@ -957,7 +1314,6 @@
                File.WriteAllText(GlobalVar.allLayoutPath, strJson, Encoding.UTF8);
                LogInfo($"全局布局保存成功", LogInfoType.INFO);
                return true;
            }
            catch { return false; }
@@ -970,6 +1326,7 @@
                if (!File.Exists(allLayoutPath))
                {
                    Debug.WriteLine("文件不存在创建空文件");
                    AsyncLogHelper.Info("文件不存在创建空文件");
                    // 获取不带文件名的目录路径
                    string directoryPath = Path.GetDirectoryName(allLayoutPath);
                    SaveAllLayout();
@@ -993,5 +1350,373 @@
            }
            catch { return false; }
        }
        private void btn_GlobalVar_Click(object sender, EventArgs e)
        {
            GlobalVarForm globalVarForm = new GlobalVarForm(GlobalVar.allProcessVarsPath);
            globalVarForm.ShowDialog();
        }
        private void btn_Login_Click(object sender, EventArgs e)
        {
            //this.Hide();
            //MainWindow.InstanceLoginandConfirmation().ShowDialog();
            //if (!MainWindow.InstanceLoginandConfirmation().isQuit && MainWindow.InstanceLoginandConfirmation().correctUser)
            //{
            //    MainWindow.InstanceLoginandConfirmation().closeLoginFrm();
            //    if (UserManager.Instance.CurrentUser.EmployeePermission == UserPermission.Operator)
            //    {
            //        //操作员权限界面
            //    }
            //    else if (UserManager.Instance.CurrentUser.EmployeePermission == UserPermission.Engineer)
            //    {
            //        //技术员权限界面
            //    }
            //    else if (UserManager.Instance.CurrentUser.EmployeePermission == UserPermission.Administrator)
            //    {
            //        //管理员权限界面
            //    }
            //    this.Show();
            //}
        }
        private void com_ProductName_SelectedValueChanged(object sender, EventArgs e)
        {
            if (com_ProductName.SelectedItem == null || com_ProductName.SelectedItem?.ToString() == GlobalVar.strProductName)
            {
                return;
            }
            if (com_ProductName.SelectedItem?.ToString() == "新增")
            {
                using (CreateProductForm createDatabaseForm = new CreateProductForm())
                {
                    createDatabaseForm.ShowDialog();
                }
            }
            else
            {
                //变更前保存现有配置
                SaveAllSetting();
                LogInfo($"产品从{GlobalVar.strProductName}切换{com_ProductName.SelectedItem?.ToString()}", LogInfoType.WARN);
                //Tool.WriteConfig("数据库名称", com_ProductName.SelectedItem?.ToString());
                GlobalVar.strProductName = com_ProductName.SelectedItem?.ToString();
                foreach (BaseCamera camera in GlobalVar.dicCameras.Values)
                {
                    camera.TriggerRunMessageReceived -= TriggerRunMessageReceived;
                    camera.Dispose();
                }
                GlobalVar.dicCameras.Clear();
                foreach (BaseCommunicator communicator in GlobalVar.dicCommunicators.Values)
                {
                    communicator.TriggerRunMessageReceived -= TriggerRunMessageReceived;
                    communicator.Disconnect();
                }
                GlobalVar.dicCommunicators.Clear();
                //保存完现有配置后断开所有事件
                foreach (var Process in GlobalVar.dicProcesses.Values)
                {
                    Process.LogInfo -= LogInfo;
                }
                GlobalVar.dicProcesses.Clear();
                //重新加载配置
                this.VisionForm_Load(sender, e);
            }
        }
        private void TriggerRunMessageReceived(string name, string msg)
        {
            if (msg == null || msg.Trim('\0', '\r', '\n', ' ', '\uFEFF') == "" || string.IsNullOrEmpty(msg))
            {
                return;
            }
            LogInfo(string.Format("通讯[{0}]接收到的消息\"{1}\"", name, msg), LogInfoType.INFO);
            var matchedItems = GlobalVar.dicProcessSetting
                .Where(item =>
                {
                    var value = item.Value;
                    var triggerComm = value["触发通讯"];
                    var triggerChar = value["触发字符"];
                    return triggerComm != null && triggerComm.Equals(name) &&
                           (string.IsNullOrEmpty(triggerChar?.ToString()) ||
                            msg.StartsWith(triggerChar.ToString()));
                })
                .ToList(); // 避免重复字典访问和装箱操作
            if (matchedItems.Count <= 0)
            {
                return;
            }
            if (!ckb_AllowRun.Checked)
            {
                LogInfo(string.Format($"检查到可被触发的流程,当前不为运行模式!"), LogInfoType.ERROR);
                return;
            }
            GlobalVar.dicProcesses.Values.AsParallel().ForAll(v => v.bCompleted = false);
            LogInfo(string.Format($"检查到可被触发的流程,清空所有流程运行完成标记位!"), LogInfoType.INFO);
            Parallel.ForEach(matchedItems, item =>
            {
                string ProcessName = item.Value["流程名"];
                LogInfo($"流程[{ProcessName}]开始运行", LogInfoType.INFO);
                if (!GlobalVar.dicProcesses.ContainsKey(ProcessName))
                {
                    LogInfo(string.Format("流程[{0}]不存在,请检查流程设置", ProcessName), LogInfoType.ERROR);
                    return;
                }
                ProcessRunBll RunBll = GlobalVar.dicProcesses[ProcessName];
                if (RunBll == null || RunBll.bRuning)
                {
                    LogInfo(string.Format("流程[{0}]上次未运行完成,触发失败", ProcessName)
                        , LogInfoType.ERROR);
                    return;
                }
                try
                {
                    bool result = false;
                    string msg = string.Empty;
                    int times = Convert.ToInt32(item.Value["重测次数"].ToString());
                    string ConnecResult = item.Value["关联结果"];
                    if (times < 0)
                    {
                        result = GlobalVar.dicProcesses[ProcessName].Run();
                        msg = GlobalVar.dicProcesses[ProcessName].Msg;
                        if (!(string.IsNullOrEmpty(ConnecResult) || ConnecResult.Trim() == "未关联"))
                        {
                            GlobalVar.dicProcesses[ProcessName].GetBooleanOutput(ConnecResult, out result);
                            GlobalVar.dicProcesses[ProcessName].Result = result;
                        }
                        if (!result)
                        {
                            LogInfo($"流程[{ProcessName}]被强制运行成功", LogInfoType.WARN);
                            GlobalVar.dicProcesses[ProcessName].Result = true;
                            result = true;
                        }
                    }
                    else
                    {
                        while (times >= 0)
                        {
                            result = RunBll.Run();
                            msg = RunBll.Msg;
                            if (!(string.IsNullOrEmpty(ConnecResult) || ConnecResult.Trim() == "未关联"))
                            {
                                RunBll.GetBooleanOutput(ConnecResult, out result);
                                RunBll.Result = result;
                            }
                            if (result)
                            {
                                break;
                            }
                            else if (!result && times > 0)
                            {
                                LogInfo(string.Format("流程[{0}]运行失败重新测试,剩余次数[{1}]", ProcessName, times), LogInfoType.WARN);
                            }
                            times--;
                        }
                    }
                    string ConnectProcess = item.Value["关联流程"];
                    if (!(ConnectProcess == null || string.IsNullOrEmpty(ConnectProcess) || ConnectProcess.Trim() == ""))
                    {
                        //用逗号或者分号去间隔关联流程
                        string[] arrConnectProcess;
                        if (ConnectProcess.Split(';').Length >= ConnectProcess.Split(',').Length)
                        {
                            arrConnectProcess = ConnectProcess.Split(';');
                        }
                        else
                        {
                            arrConnectProcess = ConnectProcess.Split(',');
                        }
                        foreach (string strConnectProcess in arrConnectProcess)
                        {
                            if (GlobalVar.dicProcesses.ContainsKey(strConnectProcess))
                            {
                                ProcessRunBll ConnectRunBll = GlobalVar.dicProcesses[strConnectProcess];
                                int waitTime = 10;
                                DateTime startTime = DateTime.Now;
                                while ((DateTime.Now - startTime).TotalSeconds < waitTime
                                && (ConnectRunBll.bRuning || !ConnectRunBll.bCompleted))
                                {
                                    LogInfo(string.Format("关联流程[{0}]未运行完成,剩余等待[{1}]s", strConnectProcess, (waitTime - ((DateTime.Now - startTime).TotalSeconds)))
                                        , LogInfoType.NOSHOW);
                                    Thread.Sleep(1000);
                                    continue;
                                }
                                if (ConnectRunBll.bRuning || !ConnectRunBll.bCompleted)
                                {
                                    GlobalVar.dicProcesses[ProcessName].Msg = string.Format("流程[{0}]未运行完成", ProcessName);
                                    LogInfo(string.Format("关联流程[{0}]未运行完成", strConnectProcess), LogInfoType.ERROR);
                                    result = false;
                                    break;
                                }
                                else if (!ConnectRunBll.bRuning && ConnectRunBll.bCompleted)
                                {
                                    LogInfo(string.Format("关联流程[{0}]运行完成", strConnectProcess), LogInfoType.INFO);
                                }
                                result &= ConnectRunBll.Result;
                                if (!ConnectRunBll.Result)
                                {
                                    LogInfo($"流程[{ProcessName}]的关联流程[{strConnectProcess}]运行失败", LogInfoType.ERROR);
                                    msg = $"关联流程[{strConnectProcess}]运行失败";
                                }
                            }
                        }
                    }
                    LogInfo(result ? $"流程[{ProcessName}]运行成功" : $"流程[{ProcessName}]运行失败,原因是{msg}", result ? LogInfoType.PASS : LogInfoType.ERROR);
                    string SendComName = result ? item.Value["成功通讯"] : item.Value["失败通讯"];
                    string SendMsg = result ? item.Value["成功字符"] : item.Value["失败字符"];
                    if (GlobalVar.dicCommunicators.ContainsKey(SendComName) && (!string.IsNullOrEmpty(SendMsg) || SendMsg.Trim() != ""))
                    {
                        GlobalVar.dicCommunicators[SendComName].SendMessage(SendMsg);
                        LogInfo(string.Format("发送给[{0}]了消息\"{1}\"", SendComName, SendMsg), LogInfoType.INFO);
                    }
                }
                catch (Exception ex)
                {
                    LogInfo(string.Format("流程[{0}]运行发生了意外,原因是:{1}", ProcessName, ex.Message + $"【{ex.StackTrace}】"), LogInfoType.ERROR);
                    RunBll.Result = false;
                    RunBll.Msg = $"[意外]{ex.Message}";
                }
                finally
                {
                    #region 从RunSettingPage和Layout中获取是否保存图片
                    string strImageType = "jpeg";
                    bool bSaveRunImage = false;
                    bool bSaveResultImage = false;
                    long lImageQuality = 100L;
                    //ckbSaveRunImage
                    if (GlobalVar.ControlStates.TryGetValue("ckbSaveRunImage_CheckBox", out object oSaveRunImage))
                    {
                        if (oSaveRunImage != null && oSaveRunImage is bool)
                        {
                            bSaveRunImage = (bool)oSaveRunImage;
                        }
                    }
                    //ckbSaveResultImage
                    if (GlobalVar.ControlStates.TryGetValue("ckbSaveResultImage_CheckBox", out object oSaveResultImage))
                    {
                        if (oSaveResultImage != null && oSaveResultImage is bool)
                        {
                            bSaveResultImage = (bool)oSaveResultImage;
                        }
                    }
                    //txtImageQuality
                    if (GlobalVar.ControlStates.TryGetValue("txtImageQuality_TextBox", out object oImageQuality))
                    {
                        if (oImageQuality != null && oImageQuality is string)
                        {
                            lImageQuality = Convert.ToInt64((string)oImageQuality);
                        }
                    }
                    //cmbImageType
                    if (GlobalVar.ControlStates.TryGetValue("cmbImageType_ComboBox", out object oImageType))
                    {
                        try
                        {
                            // 动态解析ComboBox数据
                            var json = JsonConvert.SerializeObject(oImageType);
                            var comboData = JsonConvert.DeserializeAnonymousType(json, new
                            {
                                Items = new List<object>(),
                                SelectedIndex = 0
                            });
                            if (comboData != null && comboData.Items.Count > 0)
                            {
                                strImageType = comboData.Items[comboData.SelectedIndex].ToString();
                            }
                        }
                        catch { }
                    }
                    // 生成图片并显示到控件中
                    HObject InputImage = null;
                    HObject RecordImage = null;
                    foreach (var layout in GlobalVar.dicLayout.Values
                                .Where(layout => layout.ProcessName == ProcessName)
                                .ToList())
                    {
                        string title = layout.Title;
                        string strImagePath = layout.SaveImageDir;
                        if (!AllProcessesPages.dicProcessControls.ContainsKey(title))
                        {
                            continue;
                        }
                        RunBll.GetImage(layout, out InputImage, out RecordImage);
                        AllProcessesPages.dicProcessControls[title].ShowHoImage(RecordImage);
                        if (!string.IsNullOrEmpty(layout.SaveImageDir))
                        {
                            string fileNameHead = layout.SaveImageHead;
                            string result = Regex.Replace(fileNameHead, @"\{[^}]+\}", match =>
                            {
                                // 去除{}只保留括号内的内容
                                string content = match.Value;
                                content = content.Trim('{', '}'); // 去除首尾的{}
                                RunBll.GetStringOutput(content, out string str);
                                return str;
                            });
                            string fileName = $"{result}-[{DateTime.Now.ToString("HH.mm.ss.ffff")}]";
                            // 使用正则表达式替换所有非法字符
                            string invalidChars = Regex.Escape(new string(Path.GetInvalidFileNameChars()));
                            string pattern = $"[{invalidChars}]";
                            fileName = Regex.Replace(fileName, pattern, "-");
                            strImagePath = Regex.Replace(strImagePath, @"\{[^}]+\}", match =>
                            {
                                // 去除{}只保留括号内的内容
                                string content = match.Value;
                                content = content.Trim('{', '}'); // 去除首尾的{}
                                RunBll.GetStringOutput(content, out string str);
                                return str;
                            });
                            if (bSaveRunImage)
                            {
                                // 最后一级目录必须为年月日,会根据时间来删除旧图片
                                string directoryPath = Path.Combine(strImagePath, $"{ProcessName}\\原图\\{RunBll.Result}\\{DateTime.Now.ToString("yyyyMMdd")}\\");
                                LB_SmartVision.Tool.Tool.AddRealImage(InputImage, directoryPath, fileName, strImageType, lImageQuality);
                            }
                            if (bSaveResultImage)
                            {
                                // 最后一级目录必须为年月日,会根据时间来删除旧图片
                                string directoryPath = Path.Combine(strImagePath, $"{ProcessName}\\截图\\{RunBll.Result}\\{DateTime.Now.ToString("yyyyMMdd")}\\");
                                LB_SmartVision.Tool.Tool.AddRealImage(RecordImage, directoryPath, fileName, "jpg", 50L);
                            }
                        }
                    }
                    foreach (var csv in GlobalVar.dicCsvSetting.Values
                                .Where(csv => csv.ProcessName == ProcessName)
                                .ToList())
                    {
                        if (RunBll.GetCsv(csv
                            , out List<string> DataTitle, out Dictionary<string, object> ResultData))
                        {
                            string filePath = Path.Combine(GlobalVar.strPathCsv, $"{ProcessName}.csv");
                            LB_SmartVision.Tool.Tool.SaveData(filePath, DataTitle, ResultData);
                        }
                    }
                    #endregion
                }
            });
        }
        private void VisionForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            SaveAllSetting();
            if (MessageBox.Show("是否关闭软件?", "提示", MessageBoxButtons.OKCancel, MessageBoxIcon.Warning) != DialogResult.OK)//
            {
                e.Cancel = true;
                return;
            }
            //关闭窗体释放资源
            AsyncLogHelper.Dispose();
            foreach (BaseCamera camera in GlobalVar.dicCameras.Values)
            {
                camera.Dispose();
            }
            foreach (BaseCommunicator communicator in GlobalVar.dicCommunicators.Values)
            {
                communicator.Disconnect();
            }
            FormClosing -= VisionForm_FormClosing;
        }
    }
}