using SimpleLite.RCS; using SimpleLite.RCS.CarTypes; using SimpleCore; using SimpleCore.Library; using SimpleCore.PropType; using StandardScene.Charge; using StandardScene.TCP; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Sockets; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Timers; namespace StandardScene.ChargeStationType { public class MuXingChargeStation : AbstractChargeStation { public static TcpListener tcpListener; public static Thread listenerThread; public static NetworkStream stream; public static TcpClient client; //public Dictionary chargeClient = new Dictionary { }; public System.Timers.Timer _Timer; public bool reciveHeartBeat; public bool running; public byte[] IdBytes = new byte[] { 0x0f, 0x01 }; private static IPEndPoint _endPoint; /// /// 关闭当前TCP连接 /// public override void CloseCommunication() { try { running = false; // 停止定时器 if (_Timer != null) { _Timer.Stop(); _Timer.Dispose(); _Timer = null; } // 关闭流 if (stream != null) { stream.Close(); stream = null; } // 关闭客户端 if (client != null) { client.Close(); client = null; } // 停止监听器 if (tcpListener != null) { tcpListener.Stop(); tcpListener = null; } Diagnosis.Log($"MuXingChargeStation[{SiteId}] TCP connection closed"); } catch (Exception ex) { Diagnosis.Log($"ERR:MuXingChargeStation[{SiteId}] failed to close connection: {ex.Message}"); } } public override void CreateCommunication(IPAddress ip, int port) { // 先关闭旧连接 CloseCommunication(); _endPoint = new IPEndPoint(ip, port); reciveHeartBeat = false; running = true; int n = 0; DateTime offlineTime = DateTime.Now; try { tcpListener = new TcpListener(ip, port); tcpListener.Start(); Diagnosis.Post($"AGV与牧星充电站通信已建立,监听 IP: {ip}, 端口: {port}"); client = tcpListener.AcceptTcpClient(); client.SendTimeout = 5000; client.ReceiveTimeout = 5000; stream = client.GetStream(); } catch (Exception ex) { Console.WriteLine($"与充电站通信建立失败: {ExceptionFormatter.FormatEx(ex)}"); throw; } while (running) { try { if (client == null) { client = tcpListener.AcceptTcpClient(); client.SendTimeout = 5000; client.ReceiveTimeout = 5000; stream = client.GetStream(); } var type = ReceiveMesageType(); //item1:byte0 帧头 //item2:byte11 命令字 //item3:byte12 动作码/故障码/状态码 //item4:byte5 设备ID 低 //item5:byte6 设备ID 高 //0x10 充电桩登录回复 1次 if (type.Item1 == 0xAA && type.Item2 == 0x10) { var timestampBytes = GetTimeStamp(); byte[] dataByte = new byte[16] { 0, 0x0d, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x10, 0, 0, 0x00, 0x00 }; //长度必须大于10,与帧长度对应 dataByte = InitSendBytes(dataByte, type, timestampBytes); byte[] mesSendByte = CombineDataAndCRC(dataByte, CalculateCRC16(dataByte.Skip(1).ToArray())); //拼接crc(去掉包头) Diagnosis.Post($"login => {string.Join(" ", mesSendByte.Select(d => $"{d:X2}"))}", $"充电桩登录回复"); SendMessage(mesSendByte); } //0x12 充电桩对接完成回复 1次 else if (type.Item1 == 0xAA && type.Item2 == 0x12) { var timestampBytes = GetTimeStamp(); byte[] dataByte = new byte[12] { 0, 0x09, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x12 }; ; dataByte = InitSendBytes(dataByte, type, timestampBytes); byte[] sendByte = CombineDataAndCRC(dataByte, CalculateCRC16(dataByte.Skip(1).ToArray())); //拼接crc(去掉包头) Diagnosis.Post($"docking => {string.Join(" ", sendByte.Select(d => $"{d:X2}"))}", $"充电桩对接完成回复"); SendMessage(sendByte); } //0x13 故障上报回复 else if (type.Item1 == 0xAA && type.Item2 == 0x13) { var timestampBytes = GetTimeStamp(); byte[] dataByte = new byte[13] { 0, 0x0A, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x13, type.Item3 }; dataByte = InitSendBytes(dataByte, type, timestampBytes); byte[] sendByte = CombineDataAndCRC(dataByte, CalculateCRC16(dataByte.Skip(1).ToArray())); //拼接crc(去掉包头) Diagnosis.Post($"Error => {string.Join(" ", sendByte.Select(d => $"{d:X2}"))}", $"故障上报回复"); SendMessage(sendByte); } //收到至少1次充电桩心跳包上传 if (type.Item1 == 0xAA && type.Item2 == 0xF0 && !reciveHeartBeat) { Diagnosis.Post($"收到充电桩心跳包上传", $"HeartBeatRecive"); reciveHeartBeat = true; StartSendingHeartBeat(); } //异常情况处理 //故障上报 故障码不为0 16 17 1 if (type.Item1 == 0xAA && type.Item2 == 0x13 && (type.Item3 != 0 && type.Item3 != 16 && type.Item3 != 17 && type.Item3 != 1)) { //充电桩掉线 if ((type.Item3 & (1 << 3)) != 0 && reciveHeartBeat) { if (n == 0) offlineTime = DateTime.Now; //充电桩掉线时间大于60s 重启WiFi模块 if ((DateTime.Now - offlineTime).TotalSeconds > 60) { var timestampBytes = GetTimeStamp(); //下发 命令码指令 byte[] dataByte = new byte[17] { 0, 0x0E, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x15, 0x04, //重启WiFi模块 0x00, 0x00, 0x00, 0x00 }; dataByte = InitSendBytes(dataByte, type, timestampBytes); byte[] sendChargeByte = CombineDataAndCRC(dataByte, CalculateCRC16(dataByte.Skip(1).ToArray())); Diagnosis.Post( $"Restart => {string.Join(" ", sendChargeByte.Select(d => $"{d:X2}"))}", $"重启WiFi模块"); SendMessage(sendChargeByte); n = 0; offlineTime = DateTime.Now; } n++; } else { n = 0; offlineTime = DateTime.Now; } } //if (!client.Connected) //{ // Console.WriteLine("客户端断开连接,退出循环"); // running = false; //} } catch (Exception ex) { Console.WriteLine($"充电桩通信异常:{ExceptionFormatter.FormatEx(ex)}"); running = false; client.Close(); tcpListener.Stop(); stream.Close(); } } } public override void SendToChargeStation(int isCharge, Car car,Site site) { var messageService = CommunicationMessageService.Instance; float setVoltage = 55.0f; float setElectricCurrent = 50.0f; float voltage = 430; // Site site = null; if (car != null) { // site = SimpleLib.GetSite(car.status.holdingLocks.FirstOrDefault()); int carId = 0; if (car != null && car.GetType() != typeof(DummyCar)) { voltage = float.Parse(Commons.GetCarStatus(car, "Voltage")) * 10; voltage = voltage > 430 ? voltage : 430; carId = car.id; } } if (site != null && site.fields.ContainsKey("setVoltage") && site.fields.ContainsKey("setElectricCurrent")) { setVoltage = float.Parse(site.fields["setVoltage"]); setElectricCurrent = float.Parse(site.fields["setElectricCurrent"]); } var byte1 = BitConverter.GetBytes(voltage); var type = ReceiveMesageType(); var openChargePort = isCharge == 1 ? 2 : 3; //下发打开充电口指令 var timestampBytes = GetTimeStamp(); byte[] dataByte = new byte[18] { 0, 0x0F, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x11, (byte)openChargePort,//开始充电/结束充电 byte1[0], byte1[1], 0xc8, 0x00,//下发最大充电电流 0x02//电池种类 }; dataByte = InitSendBytes(dataByte, type, timestampBytes); byte[] openCharge = CombineDataAndCRC(dataByte, CalculateCRC16(dataByte.Skip(1).ToArray())); if (client.Connected) { stream.WriteAsync(openCharge, 0, openCharge.Length); stream.FlushAsync(); } messageService.AddSendMessage(_endPoint.Address.ToString(), _endPoint.Port, BitConverter.ToString(openCharge).Replace("-", " "), site?.name, "MuXing"); Diagnosis.Post($"openCharge => {string.Join(" ", openCharge.Select(d => $"{d:X2}"))}", $"下发开始充电"); } public void StartSendingHeartBeat() { if (client == null) { Console.WriteLine("客户端为null,无法启动心跳包定时器"); return; } _Timer = new System.Timers.Timer(2000); _Timer.Elapsed += SendHeartBeat; _Timer.AutoReset = true; // 反复执行 _Timer.Enabled = true; } private void SendHeartBeat(Object item, ElapsedEventArgs e) { var timestampBytes = GetTimeStamp(); var type = ReceiveMesageType(); //下发心跳包报文 var rcs = new byte[] { 0xBB, 0x09, 0x00, 0x03, 0x00, IdBytes[0], IdBytes[1], timestampBytes[0], timestampBytes[1], timestampBytes[2], timestampBytes[3], 0xF0 }; byte[] mesSendByte = CombineDataAndCRC(rcs, CalculateCRC16(rcs.Skip(1).ToArray())); // 日志记录 Diagnosis.Post($"heartBeat => {string.Join(" ", mesSendByte.Select(d => $"{d:X2}"))}", $"下发心跳包"); // 发送心跳包 SendMessage(mesSendByte); } private byte[] GetTimeStamp() { // 获取当前时间戳(秒级别) int timestamp = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(); //将时间戳转换为字节数组,低字节在前 byte[] timestampBytes = BitConverter.GetBytes(timestamp); return timestampBytes; } private static Tuple ReceiveMesageType() { byte[] message = new byte[1024]; int bytesRead = 0; try { if (client != null && client.Connected && stream != null && stream.CanRead && client.Available > 0) { bytesRead = stream.Read(message, 0, 1024); } } catch (Exception ex) { client = null; stream = null; Console.WriteLine($"Error reading message: {ex.Message}"); } //message长度判断:至少需要 13 字节才能安全访问 message[12] if (bytesRead >= 13) { string receivedMessage = BitConverter.ToString(message, 0, bytesRead); Diagnosis.Post($"Received <= {string.Join(" ", message.Take(bytesRead).ToArray().Select(d => $"{d:X2}"))}", "读取报文"); Diagnosis.Post($"item1:{message[0]:x2},item2:{message[11]:x2},item3:{message[12]:x2},item4:{message[5]:x2},item5:{message[6]:x2},", "Tuple.Item"); var messageService = CommunicationMessageService.Instance; messageService.AddReceiveMessage(_endPoint.Address.ToString(), _endPoint.Port, string.Join(",", message), "MuXing"); return Tuple.Create(message[0], message[11], message[12], message[5], message[6]); } return new Tuple(0, 0, 0, 0, 0); } public static byte[] CombineDataAndCRC(byte[] data, byte[] crc) { //byte[] crcBytes = BitConverter.GetBytes(crc); // 合并数据和CRC byte[] combined = new byte[data.Length + crc.Length]; Array.Copy(data, combined, data.Length); Array.Copy(crc, 0, combined, data.Length, crc.Length); return combined; } private static byte[] CalculateCRC16(byte[] data) { byte b = byte.MaxValue; byte b2 = byte.MaxValue; byte b3 = 1; byte b4 = 160; for (int i = 0; i < data.Length; i++) { b = (byte)(b ^ data[i]); for (int j = 0; j <= 7; j++) { byte b5 = b2; byte b6 = b; b2 = (byte)(b2 >> 1); b = (byte)(b >> 1); if ((b5 & 1) == 1) { b = (byte)(b | 0x80u); } if ((b6 & 1) == 1) { b2 = (byte)(b2 ^ b4); b = (byte)(b ^ b3); } } } return new byte[2] { b,b2 }; } private void SendMessage(byte[] message) { try { if (client != null && stream != null) { stream.Write(message, 0, message.Length); stream.Flush(); } } catch (Exception ex) { client = null; Console.WriteLine($"Error sending message: {ExceptionFormatter.FormatEx(ex)}"); } } private byte[] InitSendBytes(byte[] sendBytes, Tuple type, byte[] timestampBytes) { sendBytes[0] = 0xBB; //帧头 sendBytes[2] = 0x00; //帧长 高 sendBytes[3] = 0x03; //设备类型 低 sendBytes[4] = 0x00; //设备类型 高 sendBytes[5] = IdBytes[0]; //设备ID 低 sendBytes[6] = IdBytes[1]; //设备ID 高 sendBytes[7] = timestampBytes[0]; //时间戳 低 sendBytes[8] = timestampBytes[1]; sendBytes[9] = timestampBytes[2]; sendBytes[10] = timestampBytes[3]; //时间戳 高 return sendBytes; } } }