新增视觉交互

master
CaesarBao 4 months ago
commit 06b6dc503c

@ -53,6 +53,11 @@ namespace SlnMesnac.Config
public List<RfidConfig> rfidConfig { get; set; }
/// <summary>
/// 视觉机械臂连接配置
/// </summary>
public List<VisionConfig> visionConfig { get; set; }
/// <summary>
/// Redis配置
/// </summary>

@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace SlnMesnac.Config
{
public class VisionConfig
{
/// <summary>
/// 视觉系统 ID
/// </summary>
public int VisionId { get; set; }
/// <summary>
/// 视觉系统 IP
/// </summary>
public string VisionIp { get; set; }
/// <summary>
/// 视觉系统 Port
/// </summary>
public int VisionPort { get; set; }
/// <summary>
/// AGV ID
/// </summary>
public string AGVID { get; set; }
/// <summary>
/// 视觉系统状态
/// </summary>
public string VisionState { get; set; }
/// <summary>
/// 是否启用(false未启用 true启用)
/// </summary>
public bool IsFlag { get; set; }
}
}

@ -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
{
/// <summary>
/// 拆包接收数据
/// </summary>
/// <param name="bytes">TCP接受的原始数据</param>
/// <returns></returns>
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)
/// <summary>
/// 装包发送数据
/// </summary>
/// <param name="SN">序列号</param>
/// <param name="Command">命令字</param>
/// <param name="data">数据体</param>
/// <returns></returns>
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;
return null;
//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 message;
}
public static byte[] GetCurrentUnixTimestampAsBytes()

@ -56,58 +56,67 @@ namespace SlnMesnac.TouchSocket
public Action<String, bool> RefreshStateAction;
public delegate void GetVisionData(TcpVisionEntity entity);
public delegate void GetVisionData(TcpVisionEntity entity, string id);
/// <summary>
/// 视觉系统反馈给上位机调度系统状态
/// </summary>
public event GetVisionData? VisionSysStateEvent;
public event GetVisionData? ReceiveVisionSysStateEvent;
/// <summary>
/// 视觉系统回复给上位机调度系统开始工作状态
/// </summary>
public event GetVisionData? VisionStartWorkEvent;
public event GetVisionData? ReceiveVisionStartWorkEvent;
/// <summary>
/// 一次码垛完成,发送码垛结果
/// </summary>
public event GetVisionData? StackWorkDoneEvent;
public event GetVisionData? ReceiveStackWorkDoneEvent;
/// <summary>
/// 视觉系统发送给机器人码垛位置定位结果
/// </summary>
public event GetVisionData? StackRobotLocationEvent;
public event GetVisionData? ReceiveStackRobotLocationEvent;
/// <summary>
/// 人工异常处理完成并更新计数
/// </summary>
public event GetVisionData? ManualExceptionDealDoneEvent;
public event GetVisionData? ReceiveManualExceptionDealDoneEvent;
private string ClientID = "";
private string TestClientID = "";
public TcpServer(ILogger<TcpServer> logger,TcpService tcpService, AppConfig appConfig)
public TcpServer(ILogger<TcpServer> 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
/// <summary>
/// 数据类型区分
/// 接收数据类型区分
/// </summary>
/// <param name="entity"></param>
public void DataClassify(TcpVisionEntity entity)
public void DataClassify(TcpVisionEntity entity, string id)
{
if(entity == null)
if (entity == null)
{
_logger.LogError("数据格式校验错误!");
return;
}
switch(entity.Command)
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;
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;
}
}
///// <summary>
///// 向所有客户端发送心跳
///// </summary>
//public void SendHeartBeat()
//{
// var clients = _service.SocketClients.GetClients();
// foreach (var item in clients)
// {
// _service.Send(item.Id,"heartbeat");
// }
//}
#region 数据发送
/// <summary>
/// 上位机调度系统请求视觉系统状态
/// </summary>
/// <param name="SN">SN码</param>
/// <param name="id">客户端ID</param>
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);
}
}
/// <summary>
/// AMR就绪请求视觉开始工作
/// </summary>
/// <param name="SN">SN码</param>
/// <param name="data">数据体</param>
/// <param name="id">客户端ID</param>
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);
}
}
/// <summary>
/// 上位机调度系统回复收到码垛结果
/// </summary>
/// <param name="SN">SN码</param>
/// <param name="id">客户端ID</param>
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);
}
}
/// <summary>
/// 码垛结束,请求视觉系统复位
/// </summary>
/// <param name="SN">SN码</param>
/// <param name="id">客户端ID</param>
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);
}
}
/// <summary>
/// 上位机调度系统回复收到人工异常处理
/// </summary>
/// <param name="SN">SN码</param>
/// <param name="id">客户端ID</param>
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);
}
}
/// <summary>
/// 上位机调度系统请求视觉系统状态默认SN
/// </summary>
/// <param name="id">客户端ID</param>
public void SendRequestVisionSysState(string id)
{
SendRequestVisionSysState(new byte[2] { 0x00, 0x00 }, id);
}
/// <summary>
/// AMR就绪请求视觉开始工作默认SN
/// </summary>
/// <param name="data">数据体</param>
/// <param name="id">客户端ID</param>
public void SendAMRRequestVisionStartWork(byte[] data, string id)
{
SendAMRRequestVisionStartWork(new byte[2] { 0x00, 0x00 }, data, id);
}
/// <summary>
/// 上位机调度系统回复收到码垛结果默认SN
/// </summary>
/// <param name="id">客户端ID</param>
public void SendReplayStackResult(string id)
{
SendReplayStackResult(new byte[2] { 0x00, 0x00 }, id);
}
/// <summary>
/// 码垛结束请求视觉系统复位默认SN
/// </summary>
/// <param name="id">客户端ID</param>
public void SendStackOverRequestVisionSysReplace(string id)
{
SendStackOverRequestVisionSysReplace(new byte[2] { 0x00, 0x00 }, id);
}
/// <summary>
/// 上位机调度系统回复收到人工异常处理默认SN
/// </summary>
/// <param name="id">客户端ID</param>
public void SendReplyGetManualException(string id)
{
SendReplyGetManualException(new byte[2] { 0x00, 0x00 }, id);
}
#endregion
/// <summary>
/// 上位机调度系统每隔固定时间,请求视觉系统状态
/// </summary>
public void VisionStateRequest(int port)
{
SendRequestVisionSysState(port.ToString());
}
/// <summary>
/// AMR就绪请求视觉开始工作
/// </summary>
public void RequestVisionStartWork(StackState state, int port)
{
SendAMRRequestVisionStartWork(new byte[1] { (byte)state }, port.ToString());
}
/// <summary>
/// 一次码垛完成,发送码垛结果
/// </summary>
public void StackResultSend(TcpVisionEntity entity, string id)
{
SendReplayStackResult(id);
}
/// <summary>
/// 码垛结束,请求视觉系统复位
/// </summary>
public void RequestVisionReplace(int port)
{
SendStackOverRequestVisionSysReplace(port.ToString());
}
/// <summary>
/// 人工异常处理完成并更新计数
/// </summary>
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
{
/// <summary>
/// 复合机器人码垛无需定位
/// </summary>
AMRNoPositioning = 0x00,
/// <summary>
/// 复合机器人码垛需要定位
/// </summary>
AMRNeedPositioning = 0x01,
/// <summary>
/// 搬运机器人码垛无需定位
/// </summary>
RGVNoPositioning = 0x10,
/// <summary>
/// 搬运机器人码垛需要定位
/// </summary>
RGVNeedPositioning = 0x11,
}
}

@ -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<IAirportTaskService>();
_agvstateService = App.ServiceProvider.GetService<IAGVStateService>();
_tcpServer = App.ServiceProvider.GetService<TcpServer>();
_appConfig = App.ServiceProvider.GetService<AppConfig>();
_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()});

@ -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",

Loading…
Cancel
Save