init commit
This commit is contained in:
@@ -0,0 +1,456 @@
|
||||
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;
|
||||
using System.Windows.Forms;
|
||||
|
||||
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<IPAddress, (TcpClient, NetworkStream)> chargeClient = new Dictionary<IPAddress, (TcpClient, NetworkStream)> { };
|
||||
public System.Timers.Timer _Timer;
|
||||
public bool reciveHeartBeat;
|
||||
public bool running;
|
||||
public byte[] IdBytes = new byte[] { 0x0f, 0x01 };
|
||||
private static IPEndPoint _endPoint;
|
||||
|
||||
/// <summary>
|
||||
/// 关闭当前TCP连接
|
||||
/// </summary>
|
||||
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<byte, byte, byte, byte, byte> 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<byte, byte, byte, byte, byte>(message[0], message[11], message[12], message[5], message[6]);
|
||||
}
|
||||
return new Tuple<byte, byte, byte, byte, byte>(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<byte, byte, byte, byte, byte> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user