using System.IO.Ports; namespace AmrControl.Common { public delegate void DltReceiveCarStateCmd(CarStateCmd carState); public class RS485 { SerialPort serial; string portName = "COM4"; int baudRate = 38400; int dataBits = 8; StopBits stopBits = StopBits.One; Parity parity = Parity.None; byte[] buf; int len; int start = 0, end = 0; bool IsStart = false; ushort head; //两字节的帧头、帧尾 //车的定时更新状态事件 public event DltReceiveCarStateCmd ReceiveCarStateCmdEvent; //车的到达目标地址的事件 public event DltReceiveCarStateCmd CarArrivedEvent; //车上线的事件 public event DltReceiveCarStateCmd CarConnectedEvent; //车离线的事件 public event DltReceiveCarStateCmd CarDisconnectedEvent; public int CarCount { get { if(cars == null) { return 0; } else { return cars.Count; } } } private object lockobj = new object(); //所有的车 Dictionary cars; public RS485() { buf = new byte[10240]; len = 0; start = -1; end = 0; cars = new Dictionary(); portName = AppHelper.ReadAppSettings("zigbee", "Com"); baudRate = Int32.Parse(AppHelper.ReadAppSettings("zigbee", "baudRate")); } private void Serial_DataReceived(object sender, SerialDataReceivedEventArgs e) { //报文的首尾都是0x7e,判断思路是当发现有两个7e时候,结合长度位判断是否是一条报文 //如果是则提取报文并把剩余数据前移到索引0,如果不是则把第2个7e之前的数据抛弃掉 int size = serial.BytesToRead; //正常情况下,不应该出现超出缓冲的报文,如果出现则可能是出错了,直接把索引置0 if (len + size > buf.Length) { //以缓存大小为主 size = buf.Length - len; } serial.Read(buf, len, size); int olen = len; len += size; for (int i = olen; i < len; i++) { head = (ushort)(((head << 8) & 0xff00) + buf[i]); //这里要求不能连续出现三个0x7e if(head == 0x7e7e) { start = i - 1; } else if (head == 0x0d0a) { end = i; if((start >=0) &&((end - start + 1 )== buf[start + 2])) { //帧长度正确 byte[] datas = new byte[end - start + 1]; Array.Copy(buf, start, datas, 0, datas.Length); HandleReceive(datas); //移动剩余数据到0; for (int j = end + 1; j < len; j++) { buf[j - end - 1] = buf[j]; } len = len - end - 1; //重新调整长度 i = -1;//下一个循环从0开始 start = -1;//第2个7前移动到0 end = 0; } else { start = -1; end = 0; } } } //如果处理完了缓存依然是满的,则抛弃数据,重新归零 if (len >= buf.Length) { //相当于初始化缓冲 len = 0; start = -1; end = 0; } } //处理接收到的报文,先转义再校验 bool HandleReceive(byte[] datas) { //长度不是正常报文的长度 if (datas == null || datas.Length < 8 || IsStart == false || AmrManager.ProcessExit) return false; ushort carid = (ushort)(datas[4] + ((datas[5] << 8) & 0xff00)); //命令码 switch (datas[3]) { case 0x01: //不存在则创建 CarStateCmd cmd = null; bool ok = false; //保留最新的 lock (lockobj) { if (cars.ContainsKey((ushort)carid)) { cmd = (CarStateCmd)cars[carid]; cmd.sendTime = DateTime.Now; ok = true; } else { cmd = new CarStateCmd(); cmd.iCarID = carid; cmd.carId = "0x" + carid.ToString("X2"); cmd.sendTime = DateTime.Now; cars.Add(carid, cmd); } } //这里的写和心跳里面的读不能同时执行 HandleCarStateMsg(cmd, datas); if (ok == false) { //车辆上线是一个事件,需要对外抛出事件 if (CarConnectedEvent != null) { Task.Run(() => { cmd.msgType = MessageType.MSG_TYPE_CAR_Connected; CarConnectedEvent(cmd); }); } } break; case 0x04: CarStateCmd cmd2 = new CarStateCmd(); cmd2.carId = "0x" + carid.ToString("X2"); cmd2.iCarID = carid; HandleCarArrivedMsg(cmd2, datas); //车辆到位是一个事件,需要对外抛出事件 if(CarArrivedEvent != null) { Task.Run(() => { CarArrivedEvent(cmd2); }); } break; default: break; } //获取锁,移除旧信息 //如果长度值匹配得上,就是正常的包 return true; } //处理车的心跳消息,更新到车的对象中 void HandleCarStateMsg(CarStateCmd cmd , byte[] datas) { cmd.msgType = MessageType.MSG_TYPE_CAR_REPORT; cmd.powerLevel = datas[8].ToString(); cmd.speed = (datas[21] * 0.1).ToString("0.00"); cmd.state = (datas[22] > 0) ? CarState.Moving : CarState.Stoped; //根据要求,未完整,还有是否故障,是否中断,是否到达等 cmd.loading = datas[23] > 0 ? true : false; cmd.isEnable = datas[24] > 0? true : false; int x, y, z, yaw; x = (int)(datas[9] + ((datas[10] << 8) & 0xff00)); y = (int)(datas[11] + ((datas[12] << 8) & 0xff00)); z = (int)(datas[13] + ((datas[14] << 8) & 0xff00)); cmd.curPos = new Position(x,y,z, (z * 0.01).ToString("0.00")); x = (int)(datas[15] + ((datas[16] << 8) & 0xff00)); y = (int)(datas[17] + ((datas[18] << 8) & 0xff00)); z = (int)(datas[19] + ((datas[20] << 8) & 0xff00)); cmd.destPos = new Position(x,y,z, (z * 0.01).ToString("0.00")); if(datas[27] == 0) { cmd.type = CarType.AMR; } else { cmd.type = CarType.AGV; } //几下更新数据的时间,更新数据与发送数据到mqtt总线上的时间不一样 cmd.sendTime = DateTime.Now; //此次z和yaw赋值一样 //需要z和yaw分开,要和老蔡商量 } //心跳和车到达消息都存在车的属性缓存消息结构中,只是中间产生了车到达的事件 void HandleCarArrivedMsg(CarStateCmd cmd , byte[] datas) { cmd.state = CarState.Arrived; //小车到达的消息,需要和老马确认 cmd.msgType = MessageType.MSG_TYPE_CAR_Arrived; int x, y, z, yaw; x = (int)(datas[8] + ((datas[9] << 8) & 0xff00)); y = (int)(datas[10] + ((datas[11] << 8) & 0xff00)); z = (int)(datas[12] + ((datas[13] << 8) & 0xff00)); cmd.curPos = new Position(x, y, z, (z*0.01).ToString("0.00")); x = (int)(datas[14] + ((datas[15] << 8) & 0xff00)); y = (int)(datas[16] + ((datas[17] << 8) & 0xff00)); z = (int)(datas[18] + ((datas[19] << 8) & 0xff00)); cmd.destPos = new Position(x, y, z, (z * 0.01).ToString("0.00")); //此次z和yaw赋值一样 if (datas[27] == 0) { cmd.type = CarType.AMR; } else { cmd.type = CarType.AGV; } //几下更新数据的时间,更新数据与发送数据到mqtt总线上的时间不一样 cmd.sendTime = DateTime.Now; } //串口对外发送 public bool Send(byte[] buffer) { if (buffer == null) return false; if (serial == null || serial.IsOpen == false) Start(); if (serial != null && serial.IsOpen) { lock (this) { serial.Write(buffer, 0, buffer.Length); } } return true; } //串口启动后的定期执行事件,如果有内容,则对外发送定时消息 private void CreateHeartBeat() { // return; List offlinecars = new List(); List onlinecars = new List(); Task.Run(() => { while (true) { if (IsStart == false || AmrManager.ProcessExit) break; Thread.Sleep(350); //保留最新的,lock内的内容要尽快结束,并且不要出错,另一个线程会把最新的车状态替换到cars里面。 lock (lockobj) { offlinecars.Clear(); onlinecars.Clear(); List carstates = cars.Values.ToList(); foreach (var item in carstates) { //首先移除超时的消息,正常是每辆车100ms都有数据上来 if ((DateTime.Now - item.sendTime).TotalMilliseconds > 2000) { //超时,认为掉线 offlinecars.Add(item); //移除 cars.Remove((ushort)item.iCarID); } else { onlinecars.Add(item); } } } //对offlinecars,产生离线消息 //车辆上线是一个事件,需要对外抛出事件 if (CarDisconnectedEvent != null) { foreach (var item in offlinecars) { item.msgType = MessageType.MSG_TYPE_CAR_Disconnected; CarDisconnectedEvent(item); } } //对onlinecars,产生状态变更消息 if (ReceiveCarStateCmdEvent != null) { foreach (var item in onlinecars) { ReceiveCarStateCmdEvent(item); } } } }); } public bool Start() { if (serial != null) serial.Close(); serial = new SerialPort(); serial.PortName = portName; serial.BaudRate = baudRate; serial.DataBits = dataBits; serial.StopBits = stopBits; serial.Parity = parity; serial.ReceivedBytesThreshold = 1; len = 0; start = 0; end = 0; serial.DataReceived += Serial_DataReceived; try { serial.Open(); IsStart = true; //创建定时程序 CreateHeartBeat(); } catch (Exception) { //串口线被拔掉时会有异常 } return serial.IsOpen; } public bool Stop() { if (serial != null) { serial.Close(); serial = null; IsStart = false; len = 0; start = -1; end = 0; } return true; } public void Dispose() { try { Stop(); } catch (Exception) { } } //对车发送命令 public bool SendCarCmd(CallCarCmd cmd) { byte[] datas = new byte[36]; datas[0] = 0xfd; //zigbee帧头 datas[1] = 32; //数据长度 ushort id = Convert.ToUInt16(cmd.carId, 16); datas[2] = (byte)(id & 0xff); datas[3] = (byte)((id >> 8) & 0xff); datas[4] = 0x7e; datas[4 + 1] = 0x7e; datas[4 + 2] = 32; datas[4 + 3] = 0x02; //命令 datas[4 + 6] = (byte)(id & 0xff); datas[4 + 7] = (byte)((id >> 8) & 0xff); datas[4 + 8] = (byte)(cmd.destPos.x & 0xff); datas[4 + 9] = (byte)((cmd.destPos.x >> 8) & 0xff); datas[4 + 10] = (byte)(cmd.destPos.y & 0xff); datas[4 + 11] = (byte)((cmd.destPos.y >> 8) & 0xff); datas[4 + 12] = (byte)(cmd.destPos.z & 0xff); datas[4 + 13] = (byte)((cmd.destPos.z >> 8) & 0xff); if(id != 0xfe04) { datas[4 + 27] = (byte)CarType.AMR; } else { datas[4 + 27] = (byte)CarType.AGV; } datas[4 + 30] = 0x0d; datas[4 + 31] = 0x0a; return Send(datas); } //让车停 public bool SendCarStop(StopCarCmd cmd) { byte[] datas = new byte[36]; datas[0] = 0xfd; //zigbee帧头 datas[1] = 32; //数据长度 ushort id = Convert.ToUInt16(cmd.carId, 16); datas[2] = (byte)(id & 0xff); datas[3] = (byte)((id >> 8) & 0xff); datas[4] = 0x7e; datas[4 + 1] = 0x7e; datas[4 + 2] = 32; datas[4 + 3] = 0x03; //命令 datas[4 + 6] = (byte)(id & 0xff); //目的ID datas[4 + 7] = (byte)((id >> 8) & 0xff); if (id != 0xfe04) { datas[4 + 27] = (byte)CarType.AMR; } else { datas[4 + 27] = (byte)CarType.AGV; } datas[4 + 30] = 0x0d; datas[4 + 31] = 0x0a; return Send(datas); } //对车使能 public bool SendCarEnable(EnableCarCmd cmd) { byte[] datas = new byte[36]; datas[0] = 0xfd; //zigbee帧头 datas[1] = 32; //数据长度 ushort id = Convert.ToUInt16(cmd.carId, 16); datas[2] = (byte)(id & 0xff); datas[3] = (byte)((id >> 8) & 0xff); datas[4] = 0x7e; datas[4 + 1] = 0x7e; datas[4 + 2] = 32; datas[4 + 3] = 0x05; //命令 datas[4 + 6] = (byte)(id & 0xff); //目的ID datas[4 + 7] = (byte)((id >> 8) & 0xff); if (id != 0xfe04) { datas[4 + 27] = (byte)CarType.AMR; } else { datas[4 + 27] = (byte)CarType.AGV; } datas[4 + 30] = 0x0d; datas[4 + 31] = 0x0a; return Send(datas); } //停止车使能 public bool SendCarDisable(DisableCarCmd cmd) { byte[] datas = new byte[36]; datas[0] = 0xfd; //zigbee帧头 datas[1] = 32; //数据长度 ushort id = Convert.ToUInt16(cmd.carId, 16); datas[2] = (byte)(id & 0xff); datas[3] = (byte)((id >> 8) & 0xff); datas[4] = 0x7e; datas[4 + 1] = 0x7e; datas[4 + 2] = 32; datas[4 + 3] = 0x06; //命令 datas[4 + 6] = (byte)(id & 0xff); //目的ID datas[4 + 7] = (byte)((id >> 8) & 0xff); if (id != 0xfe04) { datas[4 + 27] = (byte)CarType.AMR; } else { datas[4 + 27] = (byte)CarType.AGV; } datas[4 + 30] = 0x0d; datas[4 + 31] = 0x0a; return Send(datas); } public bool SendCarShowImage(CarShowCmd cmd) { byte[] datas = new byte[36]; datas[0] = 0xfd; //zigbee帧头 datas[1] = 32; //数据长度 ushort id = Convert.ToUInt16(cmd.carId, 16); datas[2] = (byte)(id & 0xff); datas[3] = (byte)((id >> 8) & 0xff); datas[4] = 0x7e; datas[4 + 1] = 0x7e; datas[4 + 2] = 32; datas[4 + 3] = 0x07; //命令 datas[4 + 6] = (byte)(id & 0xff); //目的ID datas[4 + 7] = (byte)((id >> 8) & 0xff); //为了解析简单,数组必须是2 if(cmd.channelIds != null && cmd.picCodes != null && cmd.picCodes.Length > 1 && cmd.channelIds.Length > 1) { datas[8] = cmd.picCodes[0]; datas[9] = cmd.picCodes[1]; } //else //{ // //否则索引号为0,默认不显示 //} if (id != 0xfe04) { datas[4 + 27] = (byte)CarType.AMR; } else { datas[4 + 27] = (byte)CarType.AGV; } datas[4 + 30] = 0x0d; datas[4 + 31] = 0x0a; return Send(datas); } } }