Преглед на файлове

20250702001 modbus通讯模块添加,与PLC使用Modbus通讯进行计数通讯。

向羽 孟 преди 10 месеца
родител
ревизия
a242b662f1
променени са 6 файла, в които са добавени 328 реда и са изтрити 82 реда
  1. 4 0
      TestWork.DLL/CCDCount.DLL.csproj
  2. 142 71
      TestWork.DLL/MainThreadClass.cs
  3. 0 10
      TestWork.DLL/ShuLiClass.cs
  4. 175 0
      TestWork.DLL/Tools/ModbusClass.cs
  5. 1 0
      TestWork.DLL/packages.config
  6. 6 1
      TestWork/Forms/MainForm.cs

+ 4 - 0
TestWork.DLL/CCDCount.DLL.csproj

@@ -45,6 +45,9 @@
       <SpecificVersion>False</SpecificVersion>
       <HintPath>DLL\MvCameraControl.Net.dll</HintPath>
     </Reference>
+    <Reference Include="NModbus, Version=3.0.81.0, Culture=neutral, processorArchitecture=MSIL">
+      <HintPath>..\packages\NModbus.3.0.81\lib\net46\NModbus.dll</HintPath>
+    </Reference>
     <Reference Include="System" />
     <Reference Include="System.Buffers, Version=4.0.2.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
       <HintPath>..\packages\System.Buffers.4.4.0\lib\netstandard2.0\System.Buffers.dll</HintPath>
@@ -84,6 +87,7 @@
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Delta\EtherCAT_DLL.cs" />
     <Compile Include="Delta\EtherCAT_DLL_Err.cs" />
+    <Compile Include="Tools\ModbusClass.cs" />
     <Compile Include="Tools\XMLFileClass.cs" />
     <Compile Include="ZhengYunDong\Zmcaux.cs" />
     <Compile Include="ZhengYunDong\ZmcauxClass.cs" />

+ 142 - 71
TestWork.DLL/MainThreadClass.cs

@@ -1,14 +1,17 @@
-using CCDCount.MODEL.CameraClass;
+using CCDCount.DLL.Tools;
+using CCDCount.MODEL.CameraClass;
 using CCDCount.MODEL.ConfigModel;
 using CCDCount.MODEL.ShuLiClass;
 using LogClass;
 using MvCameraControl;
 using System;
+using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.Drawing;
 using System.IO;
 using System.Linq;
+using System.Runtime.InteropServices;
 using System.Runtime.Remoting.Messaging;
 using System.Threading;
 
@@ -31,6 +34,7 @@ namespace CCDCount.DLL
         public bool CameraStatic { get { return _CameraStatic; } }
         private bool _CameraStatic = false;
 
+        // 返回交换线程状态
         public bool CameraRunStatic { get { return IsSwitch; } }
 
         public bool IsOpenLoadThread { get  { return _IsOpenLoadThread; }}
@@ -40,9 +44,15 @@ namespace CCDCount.DLL
         public int OkHistoryNum { get { return shuLiClass.GetOkHistoryNum(); } }
         public int NgHistoryNum { get { return shuLiClass.GetNgHistoryNum(); } }
 
-        ZmcauxClass zmcauxClass = new ZmcauxClass();
-        //private BottingClass Botting = new BottingClass();
-        //private MechanicalControlClass  MechanicalControl = new MechanicalControlClass();
+        // 数粒信息发送线程
+        Thread SendBottLogicMessageThread = null;
+        bool IsSend = false;
+
+        // Modbus客户端实例
+        ModbusTcpClient modbusTcpClient = null;
+
+        // 颗粒结果待发送队列
+        ConcurrentQueue<ushort> SendQueue = new ConcurrentQueue<ushort>();
 
         /// <summary>
         /// 数粒状态计时器
@@ -56,6 +66,21 @@ namespace CCDCount.DLL
         #endregion
 
         #region 公共方法
+
+        public void SetModbusClient(ModbusTcpClient modbusTcpClient)
+        {
+            this.modbusTcpClient = modbusTcpClient;
+            if (!this.modbusTcpClient.IsTcpClientConnected())
+            {
+                LOG.error("ModbusTcpClient Connect Failed!");
+            }
+            else
+            {
+                StartSendBottLogicMessageThread();
+                LOG.log("ModbusTcpClient Connect Success!", 6);
+            }
+        }
+
         public MainThreadClass(ShuLiConfigClass configClass,CameraConfig CameraConfig)
         {
             // 数粒配置文件地址
@@ -108,7 +133,6 @@ namespace CCDCount.DLL
             shuLiClass.WorkCompleted += Worker_OneGrainCompleted;
             // 开启识别线程
             shuLiClass.StartIdentifyFuntion(cameraClass.GetCamereImageSize().Width);
-            zmcauxClass.OpenZmcauxCard();
             result = true;
             return result;
         }
@@ -140,7 +164,6 @@ namespace CCDCount.DLL
             StartSwitchThread();
             // 开启识别线程
             shuLiClass.StartIdentifyFuntion(cameraClass.GetCamereImageSize().Width);
-            zmcauxClass.OpenZmcauxCard();
             result = true;
             return result;
         }
@@ -156,7 +179,7 @@ namespace CCDCount.DLL
             StopSwitchThread();
             // 数粒识别线程关闭
             shuLiClass.StopIdentifyFuntion();
-            zmcauxClass.CloseZmcauxCard();
+            StopSendBottLogicMessageThread();
         }
 
         public bool ReLoadCamera(string CameraSN)
@@ -280,7 +303,9 @@ namespace CCDCount.DLL
         /// <param name="e"></param>
         private void Worker_OneGrainCompleted(object sender, ActiveObjectEventArgsClass e)
         {
-            LOG.log("有活跃物体转换为了历史物体,回调事件被触发!",6);
+            LOG.log("有活跃物体转换为了历史物体,回调事件被触发!", 6);
+            LOG.log(string.Format("图像处理实例中的待识别图像缓存队列长度{0}", shuLiClass.ImageNum), 6);
+
             if (e.Actives.Where(o => o.StateCode == 7).Count() > 0)
             {
                 stopwatch.Restart();
@@ -291,82 +316,34 @@ namespace CCDCount.DLL
                 stopwatch.Stop();
                 _ShuLiState = true;
             }
+            //往数组中计数
+            ushort result = new ushort();
+            // 事件处理逻辑
             foreach (ActiveObjectClass oneActive in e.Actives)
             {
-                LOG.log(string.Format("输出当前颗粒信息,开始行:{0},结束行:{1},开始时间:{2}结束时间:{3},颗粒状态:{4}",
-                    oneActive.StartLine, oneActive.LastSeenLine, oneActive.StartCheckTime.ToString("O"), oneActive.EndCheckTime.ToString("O"), oneActive.StateCode), 6);
+                Console.WriteLine(string.Format("输出当前颗粒信息,开始行:{0},结束行:{1},开始时间:{2}结束时间:{3},颗粒状态:{4},通道数:{5}",
+                    oneActive.StartLine, oneActive.LastSeenLine, oneActive.StartCheckTime.ToString("O"), oneActive.EndCheckTime.ToString("O"), oneActive.StateCode, oneActive.ChannelNO));
+                LOG.log(string.Format("输出当前颗粒信息,开始行:{0},结束行:{1},开始时间:{2}结束时间:{3},颗粒状态:{4},通道数:{5}",
+                    oneActive.StartLine, oneActive.LastSeenLine, oneActive.StartCheckTime.ToString("O"), oneActive.EndCheckTime.ToString("O"), oneActive.StateCode, oneActive.ChannelNO), 6);
 
-            }
-           // 事件处理逻辑
-            float step = -1;
-            float[] floats  = new float[9];
-            zmcauxClass.GetMessageFromVar("countStep", ref step);
-            LOG.log(string.Format("{0}:countStep = {1}", "MainThreadClass-Worker_OneGrainCompleted", step),6);
-            if (step < 1 || step>3)
-            {
-                zmcauxClass.GetMessageFromArray("CCD_DATA4", ref floats, 0, 9);
-            }
-            else
-            {
-                switch (step)
+                if (oneActive.StateCode == 0 && oneActive.ChannelNO != -1)
                 {
-                    case 1:
-                        zmcauxClass.GetMessageFromArray("CCD_DATA1", ref floats, 0, 9);
-                        break;
-                    case 2:
-                        zmcauxClass.GetMessageFromArray("CCD_DATA2", ref floats, 0, 9);
-                        break;
-                    case 3:
-                        zmcauxClass.GetMessageFromArray("CCD_DATA3", ref floats, 0, 9);
-                        break;
-                }
-            }
-            LOG.log("读取轴卡结果数据:" + floats[0] + " " + floats[1] + " " + floats[2] + " " + floats[3] + " " + floats[4] + " " + floats[5] + " " + floats[6] + " " + floats[7] + " " + floats[8],6);
-            foreach (ActiveObjectClass oneActive in e.Actives)
-            {
-                if (oneActive.StateCode != 0)
-                {
-                    floats[8] += 1;
+                    //单通道合格计数
+                    result |= (ushort)(1 << (oneActive.ChannelNO));
                 }
                 else
                 {
-                    if (oneActive.ChannelNO != -1)
-                        floats[oneActive.ChannelNO] += 1;
-                    else
-                        floats[8] += 1;
+                    //单通道不合格计数
+                    result |= (ushort)(1 << 8);
                 }
-
             }
-            LOG.log("写入轴卡结果数据:" + floats[0] + " " + floats[1] + " " + floats[2] + " " + floats[3] + " " + floats[4] + " " + floats[5] + " " + floats[6] + " " + floats[7] + " " + floats[8],6);
-            if  (step < 1 || step>3)
+            LOG.log("当前待发送队列数量:" + SendQueue.Count, 6);
+            if(IsSend)
             {
-                zmcauxClass.SetMessageToArray("CCD_DATA4", floats, 0, 9);
-            }
-            else
-            {
-                switch (step)
-                {
-                    case 1:
-                        zmcauxClass.SetMessageToArray("CCD_DATA1", floats, 0, 9);
-                        break;
-                    case 2:
-                        zmcauxClass.SetMessageToArray("CCD_DATA2", floats, 0, 9);
-                        break;
-                    case 3:
-                        zmcauxClass.SetMessageToArray("CCD_DATA3", floats, 0, 9);
-                        break;
-                }
+                SendQueue.Enqueue(result);
             }
-            //OnOneGrain(e.Actives);
         }
 
-        /// <summary>
-        /// 装瓶逻辑
-        /// </summary>
-        private void BottLogicFunction()
-        { 
-
-        }
 
         /// <summary>
         /// 对外通知事件
@@ -441,6 +418,100 @@ namespace CCDCount.DLL
             }
             stopwatch.Stop();
         }
+
+        /// <summary>
+        /// 启动发送消息线程
+        /// </summary>
+        private void StartSendBottLogicMessageThread()
+        {
+            try
+            {
+                //zmcauxClass.OpenZmcauxCard();
+
+                timeBeginPeriod(1); // 设置为1ms精度
+                IsSend = true;
+                SendBottLogicMessageThread = new Thread(SendBottLogicMessageProcess);
+                SendBottLogicMessageThread.Start();
+            }
+            catch (Exception ex)
+            {
+                LOG.error("Start thread failed!, " + ex.Message);
+                throw;
+            }
+        }
+        /// <summary>
+        /// 停止发送消息线程
+        /// </summary>
+        private void StopSendBottLogicMessageThread()
+        {
+            try
+            {
+                // 标志位设为false
+                IsSend = false;
+                if (SendBottLogicMessageThread != null && SendBottLogicMessageThread.IsAlive)
+                    SendBottLogicMessageThread.Join();
+                if (modbusTcpClient != null) modbusTcpClient.Disconnect();
+            }
+            catch (Exception ex)
+            {
+                LOG.error("Start thread failed!, " + ex.Message);
+                throw;
+            }
+        }
+
+        /// <summary>
+        /// 信息发送线程
+        /// </summary>
+        private void SendBottLogicMessageProcess()
+        {
+            //获取数据
+            ushort sendMessage = 0;
+            bool AllowTransfer = false;
+            bool TransferDone = false;
+            Stopwatch sw = Stopwatch.StartNew();
+            while (IsSend)
+            {
+                LOG.log("进入线程", 6);
+                sw.Restart();
+                sendMessage = new ushort();
+                //读取装瓶状态
+                AllowTransfer = false;
+                TransferDone = false;
+                bool[] ReadResult = modbusTcpClient.ReadCoilsRegister(slaveId: 1, startAddress: 11, numRegisters: 2);
+                if (ReadResult == null)
+                {
+                    continue;
+                }
+                AllowTransfer = ReadResult[1];
+                TransferDone = ReadResult[0];
+                //LOG.log(string.Format("读取值:AllowTransfer[0]:{0},TransferDone[1]:{1}", AllowTransfer, TransferDone), 6);
+                //当允许写入且处于未写入的状态时
+                if (AllowTransfer && !TransferDone)
+                {
+                    if (SendQueue.Count() > 0)
+                    {
+                        if (!SendQueue.TryDequeue(out sendMessage))
+                        {
+                            LOG.error("MainThreadClass-SendBottLogicMessageProcess-SendQueue.TryDequeue failed!");
+                        }
+                    }
+                    if (sendMessage != 0)
+                    {
+                        //写入数据
+                        modbusTcpClient.WriteSingleRegister(slaveId: 1, registerAddress: 100, value: sendMessage);
+                        modbusTcpClient.WriteCoilsRegister(slaveId: 1, CoilsAddress: 11, values: true);
+                    }
+                }
+                sw.Stop();
+                LOG.log(string.Format("离开线程写入值:sendMessage[1]:{0},此次写值耗时:{1}", sendMessage, sw.Elapsed), 6);
+                Thread.Sleep(1);
+            }
+        }
+        #endregion
+
+        #region 外部函数
+        [DllImport("winmm.dll")]
+        static extern uint timeBeginPeriod(uint period);
         #endregion
     }
 }

+ 0 - 10
TestWork.DLL/ShuLiClass.cs

@@ -21,7 +21,6 @@ namespace CCDCount.DLL
         public event EventHandler<ActiveObjectEventArgsClass> WorkCompleted;
         private List<ActiveObjectClass> activeObjects = new List<ActiveObjectClass>(); // 当前跟踪中的物体
         private List<ActiveObjectClass> historyActiveObjects = new List<ActiveObjectClass>(); // 历史物体
-        private ConcurrentQueue<byte[]> ImageBytes = new ConcurrentQueue<byte[]>(); //图像数据队列
         private ConcurrentQueue<IImage> IFrameDatas = new ConcurrentQueue<IImage>(); //图像数据队列
         private Thread IdentifyImageProcessThread = null; // 识别线程
         private bool IsIdentify = false; //线程是否开始识别的标志
@@ -189,15 +188,6 @@ namespace CCDCount.DLL
             }
         }
 
-        /// <summary>
-        /// 向识别队列添加一个数据
-        /// </summary>
-        /// <param name="items"></param>
-        public void SetOnceIdentifyImageData(byte[] items)
-        {
-            ImageBytes.Enqueue(items.Clone() as byte[]);
-        }
-
         /// <summary>
         /// 向识别队列添加一个数据
         /// </summary>

+ 175 - 0
TestWork.DLL/Tools/ModbusClass.cs

@@ -0,0 +1,175 @@
+using LogClass;
+using NModbus;
+using System;
+using System.Net.Sockets;
+
+namespace CCDCount.DLL.Tools
+{
+    public class ModbusTcpClient
+    {
+        private TcpClient _tcpClient;
+        private IModbusMaster _modbusMaster;
+
+        public bool IsTcpClientConnected()
+        {
+            if (_tcpClient == null || _tcpClient.Client == null)
+                return false;
+
+            try
+            {
+                Socket socket = _tcpClient.Client;
+                bool isReadable = socket.Poll(0, SelectMode.SelectRead);
+                bool hasNoData = (socket.Available == 0);
+
+                // 可读且无数据 = 连接已断开
+                return !(isReadable && hasNoData);
+            }
+            catch (SocketException) { return false; }
+            catch (ObjectDisposedException) { return false; }
+        }
+
+        /// <summary>
+        /// 连接 Modbus TCP 服务器
+        /// </summary>
+        /// <param name="ipAddress"></param>
+        /// <param name="port"></param>
+        /// <returns></returns>
+        public bool Connect(string ipAddress, int port = 502)
+        {
+            try
+            {
+                _tcpClient = new TcpClient(ipAddress, port)
+                {
+                    SendTimeout = 2000,
+                    ReceiveTimeout = 2000
+                };
+                // 使用 ModbusFactory 创建 Master(新版本 API)
+                var factory = new ModbusFactory();
+                _modbusMaster = factory.CreateMaster(_tcpClient);
+                return true;
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine($"连接失败: {ex.Message}");
+                return false;
+            }
+        }
+
+        /// <summary>
+        /// 断开连接
+        /// </summary>
+        public void Disconnect()
+        {
+            _modbusMaster?.Dispose();
+            _tcpClient?.Close();
+        }
+        /// <summary>
+        /// 读取线圈(功能码01)
+        /// </summary>
+        /// <param name="slaveId"></param>
+        /// <param name="startAddress"></param>
+        /// <param name="numRegisters"></param>
+        /// <returns></returns>
+        /// <exception cref="InvalidOperationException"></exception>
+        public bool[] ReadCoilsRegister(byte slaveId, ushort startAddress, ushort numRegisters)
+        {
+            if (_modbusMaster == null) throw new InvalidOperationException("未连接到服务器");
+            try
+            {
+                return _modbusMaster.ReadCoils(slaveId, startAddress, numRegisters);
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine($"读取线圈失败: {ex.Message}");
+                LOG.error($"MainThreadClass-ReadCoilsRegister-ReadCoilsRegister failed:{ex.Message}");
+                return null;
+            }
+        }
+
+        /// <summary>
+        /// 读取保持寄存器(功能码03)
+        /// </summary>
+        /// <param name="slaveId"></param>
+        /// <param name="startAddress"></param>
+        /// <param name="numRegisters"></param>
+        /// <returns></returns>
+        /// <exception cref="InvalidOperationException"></exception>
+        public ushort[] ReadHoldingRegisters(byte slaveId, ushort startAddress, ushort numRegisters)
+        {
+            if (_modbusMaster == null) throw new InvalidOperationException("未连接到服务器");
+            try
+            {
+                return _modbusMaster.ReadHoldingRegisters(slaveId, startAddress, numRegisters);
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine($"读取保持寄存器失败: {ex.Message}");
+                LOG.error($"MainThreadClass-ReadHoldingRegisters-ReadHoldingRegisters failed:{ex.Message}");
+                return null;
+            }
+        }
+        /// <summary>
+        /// 写入线圈(功能码05)
+        /// </summary>
+        /// <param name="slaveId"></param>
+        /// <param name="CoilsAddress"></param>
+        /// <param name="values"></param>
+        /// <exception cref="InvalidOperationException"></exception>
+        public void WriteCoilsRegister(byte slaveId, ushort CoilsAddress, bool values)
+        {
+            if (_modbusMaster == null) throw new InvalidOperationException("未连接到服务器");
+            try
+            {
+                _modbusMaster.WriteSingleCoil(slaveId, CoilsAddress, values);
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine($"写入线圈失败: {ex.Message}");
+                LOG.error($"MainThreadClass-WriteCoilsRegister-WriteCoilsRegister failed:{ex.Message}");
+            }
+        }
+
+        /// <summary>
+        /// 写入单个寄存器(功能码06)
+        /// </summary>
+        /// <param name="slaveId"></param>
+        /// <param name="registerAddress"></param>
+        /// <param name="value"></param>
+        /// <exception cref="InvalidOperationException"></exception>
+        public void WriteSingleRegister(byte slaveId, ushort registerAddress, ushort value)
+        {
+
+            if (_modbusMaster == null) throw new InvalidOperationException("未连接到服务器");
+            try
+            {
+                _modbusMaster.WriteSingleRegister(slaveId, registerAddress, value);
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine($"写入单个寄存器失败: {ex.Message}");
+                LOG.error($"MainThreadClass-WriteSingleRegister-WriteSingleRegister failed:{ex.Message}");
+            }
+        }
+
+        /// <summary>
+        /// 写入多个寄存器(功能码16)
+        /// </summary>
+        /// <param name="slaveId"></param>
+        /// <param name="startAddress"></param>
+        /// <param name="values"></param>
+        /// <exception cref="InvalidOperationException"></exception>
+        public void WriteMultipleRegisters(byte slaveId, ushort startAddress, ushort[] values)
+        {
+            if (_modbusMaster == null) throw new InvalidOperationException("未连接到服务器");
+            try
+            {
+                _modbusMaster.WriteMultipleRegisters(slaveId, startAddress, values);
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine($"写入多个寄存器失败: {ex.Message}");
+                LOG.error($"MainThreadClass-WriteMultipleRegisters-WriteMultipleRegisters failed:{ex.Message}");
+            }
+        }
+    }
+}

+ 1 - 0
TestWork.DLL/packages.config

@@ -4,6 +4,7 @@
   <package id="Microsoft.Bcl.AsyncInterfaces" version="8.0.0" targetFramework="net48" />
   <package id="Microsoft.Bcl.HashCode" version="1.1.1" targetFramework="net48" />
   <package id="Microsoft.CSharp" version="4.7.0" targetFramework="net48" />
+  <package id="NModbus" version="3.0.81" targetFramework="net48" />
   <package id="System.Buffers" version="4.4.0" targetFramework="net48" />
   <package id="System.Memory" version="4.5.0" targetFramework="net48" />
   <package id="System.Numerics.Vectors" version="4.4.0" targetFramework="net48" />

+ 6 - 1
TestWork/Forms/MainForm.cs

@@ -1,4 +1,5 @@
 using CCDCount.DLL;
+using CCDCount.DLL.Tools;
 using CCDCount.MODEL.ConfigModel;
 using CCDCount.MODEL.ShuLiClass;
 using LogClass;
@@ -27,6 +28,7 @@ namespace CCDCount.Forms
         #region 实例
         //主线程实例队列
         public List<MainThreadClass> LsMainThread = new List<MainThreadClass>();
+        public ModbusTcpClient modbusTcpClient = new ModbusTcpClient();
 
         #endregion
 
@@ -37,7 +39,9 @@ namespace CCDCount.Forms
         public MainForm()
         {
             InitializeComponent();
-            if(File.Exists(".\\Config\\CCDCountConfig.xml"))
+            //modbusTcpClient.Connect("192.168.1.88");
+            modbusTcpClient.Connect("127.0.0.1");
+            if (File.Exists(".\\Config\\CCDCountConfig.xml"))
             {
                 Configs = XmlStorage.DeserializeFromXml<List<(CameraConfig CameraConfigValue, ShuLiConfigClass ShuLiConfigValue)>>(".\\Config\\CCDCountConfig.xml");
             }
@@ -209,6 +213,7 @@ namespace CCDCount.Forms
                 //判断是否添加线程
                 if (!LsMainThread[i].IsOpenLoadThread)
                     return;
+                LsMainThread[i].SetModbusClient(modbusTcpClient);
                 //启动单相机实例的全部线程
                 if(!LsMainThread[i].StartMianThread(Configs.Select(o => o.CameraConfigValue).ToList().Where(o=>o.CameraSNNum == LsMainThread[i].ThisCameraSN).First()))
                 {