diff --git a/SlnMesnac.Config/AppConfig.cs b/SlnMesnac.Config/AppConfig.cs index bd9fe6a..ecc275c 100644 --- a/SlnMesnac.Config/AppConfig.cs +++ b/SlnMesnac.Config/AppConfig.cs @@ -53,6 +53,11 @@ namespace SlnMesnac.Config public List rfidConfig { get; set; } + /// + /// 视觉机械臂连接配置 + /// + public List visionConfig { get; set; } + /// /// Redis配置 /// diff --git a/SlnMesnac.Config/VisionConfig.cs b/SlnMesnac.Config/VisionConfig.cs new file mode 100644 index 0000000..ff8a8d8 --- /dev/null +++ b/SlnMesnac.Config/VisionConfig.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SlnMesnac.Config +{ + public class VisionConfig + { + + /// + /// 视觉系统 ID + /// + public int VisionId { get; set; } + + /// + /// 视觉系统 IP + /// + public string VisionIp { get; set; } + + /// + /// 视觉系统 Port + /// + public int VisionPort { get; set; } + + /// + /// AGV ID + /// + public string AGVID { get; set; } + + /// + /// 视觉系统状态 + /// + public string VisionState { get; set; } + + /// + /// 是否启用(false未启用 true启用) + /// + public bool IsFlag { get; set; } + } +} diff --git a/SlnMesnac.TouchSocket/BufferDataAnalysis.cs b/SlnMesnac.TouchSocket/BufferDataAnalysis.cs index f86339b..8f18b32 100644 --- a/SlnMesnac.TouchSocket/BufferDataAnalysis.cs +++ b/SlnMesnac.TouchSocket/BufferDataAnalysis.cs @@ -8,6 +8,7 @@ using System.Buffers; using System.Collections.Generic; using System.Text; using System.Threading; +using System.Xml; using TouchSocket.Sockets; namespace SlnMesnac.TouchSocket @@ -15,14 +16,20 @@ namespace SlnMesnac.TouchSocket public class BufferDataAnalysis { + /// + /// 拆包接收数据 + /// + /// TCP接受的原始数据 + /// public static TcpVisionEntity BufferRootAnalysis(byte[] bytes) { TcpVisionEntity entity; - //数据校验,从起始符开始到数据位,按字节求和得出的结果对256求余 - if(bytes.Length <= 2) + //一帧正常的数据最少12位 + if (bytes.Length < 12) { return null; } + //数据校验,从起始符开始到数据位,按字节求和得出的结果对256求余 int checkDatalength = bytes.Length - 2; byte checksum = bytes[checkDatalength]; byte[] checkData = new byte[checkDatalength]; @@ -68,10 +75,60 @@ namespace SlnMesnac.TouchSocket return entity; } - public byte[] DataCombine(byte[] bytes) + /// + /// 装包发送数据 + /// + /// 序列号 + /// 命令字 + /// 数据体 + /// + public static byte[] DataCombine(byte[] SN, byte Command, byte[] data) { + byte[] message = new byte[12 + data.Length]; + int index = 0; + //head + message[index++] = 0x55; + message[index++] = 0xAA; + + //SN + message[index++] = SN[0]; + message[index++] = SN[1]; + + //timestamp + // 获取当前时间的Unix时间戳,表示自1970-01-01以来的秒数 + int unixTimestamp = (int)(DateTimeOffset.UtcNow.ToUnixTimeSeconds()); + // 将时间戳转换为字节数组 + byte[] timestampBytes = BitConverter.GetBytes(unixTimestamp); + if (BitConverter.IsLittleEndian) + { + Array.Reverse(timestampBytes); + } + message[index++] = timestampBytes[0]; + message[index++] = timestampBytes[1]; + message[index++] = timestampBytes[2]; + message[index++] = timestampBytes[3]; + + //command + message[index++] = Command; + + //datalength + message[index++] = (byte)data.Length; + + foreach (byte b in data) message[index++] = b; + + //CRC + int checkDatalength = message.Length - 2; + byte[] checkData = new byte[checkDatalength]; + Array.Copy(message, 0, checkData, 0, checkDatalength); + int sum = 0; + foreach (byte b in checkData) sum += b; + int count = sum % 256; + message[index++] = (byte)count; + + //end + message[index++] = 0xEE; - return null; + return message; } public static byte[] GetCurrentUnixTimestampAsBytes() diff --git a/SlnMesnac.TouchSocket/TcpServer.cs b/SlnMesnac.TouchSocket/TcpServer.cs index e98ff5e..6b97724 100644 --- a/SlnMesnac.TouchSocket/TcpServer.cs +++ b/SlnMesnac.TouchSocket/TcpServer.cs @@ -56,58 +56,67 @@ namespace SlnMesnac.TouchSocket public Action RefreshStateAction; - public delegate void GetVisionData(TcpVisionEntity entity); + public delegate void GetVisionData(TcpVisionEntity entity, string id); /// /// 视觉系统反馈给上位机调度系统状态 /// - public event GetVisionData? VisionSysStateEvent; + public event GetVisionData? ReceiveVisionSysStateEvent; /// /// 视觉系统回复给上位机调度系统开始工作状态 /// - public event GetVisionData? VisionStartWorkEvent; + public event GetVisionData? ReceiveVisionStartWorkEvent; /// /// 一次码垛完成,发送码垛结果 /// - public event GetVisionData? StackWorkDoneEvent; + public event GetVisionData? ReceiveStackWorkDoneEvent; /// /// 视觉系统发送给机器人码垛位置定位结果 /// - public event GetVisionData? StackRobotLocationEvent; + public event GetVisionData? ReceiveStackRobotLocationEvent; /// /// 人工异常处理完成并更新计数 /// - public event GetVisionData? ManualExceptionDealDoneEvent; + public event GetVisionData? ReceiveManualExceptionDealDoneEvent; - private string ClientID = ""; + private string TestClientID = ""; - public TcpServer(ILogger logger,TcpService tcpService, AppConfig appConfig) + public TcpServer(ILogger logger, TcpService tcpService, AppConfig appConfig) { _logger = logger; _service = tcpService; _appConfig = appConfig; + + ReceiveStackWorkDoneEvent += StackResultSend; + ReceiveManualExceptionDealDoneEvent += ManualExceptionDealDone; + + } - + public void Init(int serverPort) { try { - _service.Connecting = (client, e) => { + _service.Connecting = (client, e) => + { _logger.LogInformation($"客户端{client.IP}正在接入服务"); return EasyTask.CompletedTask; }; - _service.Connected = (client, e) => { + _service.Connected = (client, e) => + { _logger.LogInformation($"客户端{client.IP}接入服务成功"); RefreshClientInfoEvent?.Invoke(_service); - RefreshStateAction?.Invoke(client.IP,true); - ClientID = client.Id; + RefreshStateAction?.Invoke(client.IP, true); + client.ResetId(client.Port.ToString()); + TestClientID = client.Port.ToString(); return EasyTask.CompletedTask; }; - _service.Disconnected = (client, e) => { + _service.Disconnected = (client, e) => + { _logger.LogInformation($"客户端{client.IP}断开连接"); RefreshStateAction?.Invoke(client.IP, false); RefreshClientInfoEvent?.Invoke(_service); @@ -116,18 +125,19 @@ namespace SlnMesnac.TouchSocket _service.Received = (client, e) => { var mes = Encoding.UTF8.GetString(e.ByteBlock.Buffer, 0, e.ByteBlock.Len);//注意:数据长度是byteBlock.Len - _logger.LogInformation($"客户端{client.IP}:"+ mes); + _logger.LogInformation($"客户端{client.IP}:" + mes); //区分一下指令类型,委托传参 + if (mes == "heartbeat") { RefreshStateAction?.Invoke(client.IP, true); } - //var mes = Encoding.UTF8.GetString(e.ByteBlock.Buffer, 0, e.ByteBlock.Len);//注意:数据长度是byteBlock.Len + + byte[] receivedBuffer = new byte[e.ByteBlock.Len]; Array.Copy(e.ByteBlock.Buffer, 0, receivedBuffer, 0, e.ByteBlock.Len); - //ReceivedClientBufferEvent?.Invoke(receivedBuffer); - DataClassify(BufferDataAnalysis.BufferRootAnalysis(receivedBuffer)); + DataClassify(BufferDataAnalysis.BufferRootAnalysis(receivedBuffer), client.Id); return EasyTask.CompletedTask; }; @@ -185,37 +195,243 @@ namespace SlnMesnac.TouchSocket /// - /// 数据类型区分 + /// 接收数据类型区分 /// /// - public void DataClassify(TcpVisionEntity entity) + public void DataClassify(TcpVisionEntity entity, string id) { - if(entity == null) + if (entity == null) { _logger.LogError("数据格式校验错误!"); return; } - switch(entity.Command) - { - case 10: VisionSysStateEvent?.Invoke(entity); break; - case 11: VisionStartWorkEvent?.Invoke(entity); break; - case 12: StackWorkDoneEvent?.Invoke(entity); break; - case 13: StackRobotLocationEvent?.Invoke(entity); break; - case 14: ManualExceptionDealDoneEvent?.Invoke(entity); break; - default: _logger.LogInformation("未知命令字!"); return; + switch (entity.Command) + { + case 0x10: ReceiveVisionSysStateEvent?.Invoke(entity, id); break; + case 0x11: ReceiveVisionStartWorkEvent?.Invoke(entity, id); break; + case 0x12: ReceiveStackWorkDoneEvent?.Invoke(entity, id); break; + case 0x13: ReceiveStackRobotLocationEvent?.Invoke(entity, id); break; + case 0x14: ReceiveManualExceptionDealDoneEvent?.Invoke(entity, id); break; + default: _logger.LogInformation("未知命令字!"); return; } } - ///// - ///// 向所有客户端发送心跳 - ///// - //public void SendHeartBeat() - //{ - // var clients = _service.SocketClients.GetClients(); - // foreach (var item in clients) - // { - // _service.Send(item.Id,"heartbeat"); - // } - //} + #region 数据发送 + + /// + /// 上位机调度系统请求视觉系统状态 + /// + /// SN码 + /// 客户端ID + public void SendRequestVisionSysState(byte[] SN, string id) + { + try + { + _service.SendAsync(id, BufferDataAnalysis.DataCombine(SN, 0x00, new byte[0])); + } + catch (Exception e) + { + _logger.LogError("Error:" + e); + } + } + + /// + /// AMR就绪,请求视觉开始工作 + /// + /// SN码 + /// 数据体 + /// 客户端ID + public void SendAMRRequestVisionStartWork(byte[] SN, byte[] data, string id) + { + try + { + _service.SendAsync(id, BufferDataAnalysis.DataCombine(SN, 0x01, data)); + } + catch (Exception e) + { + _logger.LogError("Error:" + e); + } + } + + /// + /// 上位机调度系统回复收到码垛结果 + /// + /// SN码 + /// 客户端ID + public void SendReplayStackResult(byte[] SN, string id) + { + try + { + _service.SendAsync(id, BufferDataAnalysis.DataCombine(SN, 0x02, new byte[0])); + } + catch (Exception e) + { + _logger.LogError("Error:" + e); + } + } + + /// + /// 码垛结束,请求视觉系统复位 + /// + /// SN码 + /// 客户端ID + public void SendStackOverRequestVisionSysReplace(byte[] SN, string id) + { + try + { + _service.SendAsync(id, BufferDataAnalysis.DataCombine(SN, 0x03, new byte[0])); + } + catch (Exception e) + { + _logger.LogError("Error:" + e); + } + } + + /// + /// 上位机调度系统回复收到人工异常处理 + /// + /// SN码 + /// 客户端ID + public void SendReplyGetManualException(byte[] SN, string id) + { + try + { + _service.SendAsync(TestClientID, BufferDataAnalysis.DataCombine(SN, 0x04, new byte[0])); + } + catch (Exception e) + { + _logger.LogError("Error:" + e); + } + } + + /// + /// 上位机调度系统请求视觉系统状态(默认SN) + /// + /// 客户端ID + public void SendRequestVisionSysState(string id) + { + SendRequestVisionSysState(new byte[2] { 0x00, 0x00 }, id); + } + + /// + /// AMR就绪,请求视觉开始工作(默认SN) + /// + /// 数据体 + /// 客户端ID + public void SendAMRRequestVisionStartWork(byte[] data, string id) + { + SendAMRRequestVisionStartWork(new byte[2] { 0x00, 0x00 }, data, id); + } + + /// + /// 上位机调度系统回复收到码垛结果(默认SN) + /// + /// 客户端ID + public void SendReplayStackResult(string id) + { + SendReplayStackResult(new byte[2] { 0x00, 0x00 }, id); + } + + /// + /// 码垛结束,请求视觉系统复位(默认SN) + /// + /// 客户端ID + public void SendStackOverRequestVisionSysReplace(string id) + { + SendStackOverRequestVisionSysReplace(new byte[2] { 0x00, 0x00 }, id); + } + + /// + /// 上位机调度系统回复收到人工异常处理(默认SN) + /// + /// 客户端ID + public void SendReplyGetManualException(string id) + { + SendReplyGetManualException(new byte[2] { 0x00, 0x00 }, id); + } + + #endregion + + /// + /// 上位机调度系统每隔固定时间,请求视觉系统状态 + /// + public void VisionStateRequest(int port) + { + SendRequestVisionSysState(port.ToString()); + } + + /// + /// AMR就绪,请求视觉开始工作 + /// + public void RequestVisionStartWork(StackState state, int port) + { + SendAMRRequestVisionStartWork(new byte[1] { (byte)state }, port.ToString()); + } + + /// + /// 一次码垛完成,发送码垛结果 + /// + public void StackResultSend(TcpVisionEntity entity, string id) + { + SendReplayStackResult(id); + } + + /// + /// 码垛结束,请求视觉系统复位 + /// + public void RequestVisionReplace(int port) + { + SendStackOverRequestVisionSysReplace(port.ToString()); + } + + /// + /// 人工异常处理完成并更新计数 + /// + public void ManualExceptionDealDone(TcpVisionEntity entity, string id) + { + SendReplyGetManualException(id); + } + + public void InitGetVisionMessage() + { + ReceiveVisionSysStateEvent = (entity, id) => + { + _logger.LogInformation("视觉系统被动反馈给上位机调度系统状态,端口:" + id); + }; + + ReceiveVisionStartWorkEvent = (entity, id) => + { + _logger.LogInformation("视觉系统回复给上位机调度系统开始工作状态,端口:" + id); + }; + + ReceiveStackRobotLocationEvent = (entity, id) => + { + _logger.LogInformation("视觉系统回复机器人复位结果,端口:" + id); + }; + } } + + public enum StackState + { + /// + /// 复合机器人码垛无需定位 + /// + AMRNoPositioning = 0x00, + + /// + /// 复合机器人码垛需要定位 + /// + AMRNeedPositioning = 0x01, + + /// + /// 搬运机器人码垛无需定位 + /// + RGVNoPositioning = 0x10, + + /// + /// 搬运机器人码垛需要定位 + /// + RGVNeedPositioning = 0x11, + } + } diff --git a/SlnMesnac.WPF/ViewModel/IndexPage/IndexContentViewModel.cs b/SlnMesnac.WPF/ViewModel/IndexPage/IndexContentViewModel.cs index 0d9abab..e2fe7af 100644 --- a/SlnMesnac.WPF/ViewModel/IndexPage/IndexContentViewModel.cs +++ b/SlnMesnac.WPF/ViewModel/IndexPage/IndexContentViewModel.cs @@ -19,6 +19,8 @@ using SlnMesnac.WPF.Page.IndexPage; using System.Windows.Documents; using SlnMesnac.TouchSocket; using HslCommunication.Enthernet; +using SlnMesnac.Config; +using System.Threading; #region << 版 本 注 释 >> /*-------------------------------------------------------------------- @@ -56,6 +58,7 @@ namespace SlnMesnac.WPF.ViewModel.IndexPage private BaseStateRefreshBusiness _StateRefreshBusiness; private DispatcherTimer _timer; private TcpServer _tcpServer; + private AppConfig _appConfig; public IndexContentViewModel() { @@ -64,6 +67,7 @@ namespace SlnMesnac.WPF.ViewModel.IndexPage _taskservice = App.ServiceProvider.GetService(); _agvstateService = App.ServiceProvider.GetService(); _tcpServer = App.ServiceProvider.GetService(); + _appConfig = App.ServiceProvider.GetService(); _taskInfoBusiness = BaseTaskInfoBusiness.GetInstance(_taskBusinessLogger, _taskservice, _agvstateService, _tcpServer); _taskInfoBusiness._RefreshLogMessageAction += RefreshLogMessage; @@ -107,6 +111,12 @@ namespace SlnMesnac.WPF.ViewModel.IndexPage LoadTaskInfo(); + //Thread.Sleep(5000); + + //_tcpServer.SendReplyGetManualException( + // _appConfig.visionConfig.Where(x => x.VisionPort == 7001).First().VisionPort.ToString() + // ); + //_StateRefreshBusiness.UpdateManipulatorStateByResposne( // new Model.AirportApiEntity.ManipulatorStateRequestEntity() // { ManipulatorNo = "1", SignalSendTime = DateTime.Now.ToString()}); diff --git a/SlnMesnac.WPF/appsettings.json b/SlnMesnac.WPF/appsettings.json index 7980c0f..e0e208b 100644 --- a/SlnMesnac.WPF/appsettings.json +++ b/SlnMesnac.WPF/appsettings.json @@ -23,7 +23,7 @@ { "configId": "AGV", "dbType": 2, - "connStr": "Data Source=F:\\Mesnac\\2023部门项目\\机场AGV调度\\HightWay_AirPot_WCS\\SlnMesnac.WPF\\bin\\Debug\\net6.0-windows\\data\\Airport_db.sqlite" + "connStr": "Data Source=D:\\WorkCode\\AirPortWCS\\SlnMesnac.WPF\\bin\\Debug\\net6.0-windows\\data\\Airport_db.sqlite" } ], "PlcConfig": [ @@ -60,6 +60,24 @@ "isFlage": true } ], + "visionConfig": [ + { + "VisionId": 1, + "VisionIp": "127.0.0.1", + "VisionPort": 7001, + "AGVID": "1", + "VisionState": "1", + "IsFlag": true + }, + { + "VisionId": 2, + "VisionIp": "127.0.0.1", + "VisionPort": 7002, + "AGVID": "2", + "VisionState": "1", + "IsFlag": true + } + ], "AMRIP": "127.0.0.1",