using JinYuan.Models; using Org.BouncyCastle.Ocsp; using System; using System.Buffers; using System.Collections.Generic; using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; namespace JinYuan.Helper { public class EnergyMeterHelper { public const int BufferSize = 1024;//1kb private readonly object _lockObj = new object(); // 线程安全锁 private int _timeout = 2000; private Ping _ping = null; private IPEndPoint _endPoint = null; private Socket _energyMeterSocket = null; private PingReply _pingReply = null; private string _lastError = ""; public enum ModBusExceptionCode //错误代码 { IllegalFunction = 01, IllegalDataAddress, IllegalDataValue, SlaveDeviceFailure, Acknowledge, SlaveDeviceBusy, GatewayPathUnavailable, GatewayTargetDeviceFailed2Respond, } /// /// 构造函数 /// public EnergyMeterHelper() { // used to ping the PLC // this._ping = new Ping(); // EndPoint parametres // this._endPoint = new IPEndPoint(0, 0); } /// /// set ip and port /// public void SetTCPParams(IPAddress ip, int port) { if (ip == null) throw new ArgumentNullException(nameof(ip), "IP地址不能为空"); if (port < 1 || port > 65535) throw new ArgumentOutOfRangeException(nameof(port), "端口号必须在1-65535之间"); // 禁止本地回环地址 if (ip == IPAddress.Loopback || ip == IPAddress.IPv6Loopback) { throw new ArgumentException("禁止使用本地回环地址(127.0.0.1),请配置智能电表的真实IP地址", nameof(ip)); } lock (_lockObj) { _endPoint = new IPEndPoint(ip, port); // 重新创建实例,避免修改原有对象 } } /// /// returns the connection status /// public bool IsConnected { get { lock (_lockObj) { if (_energyMeterSocket == null) return false; try { bool part1 = _energyMeterSocket.Poll(100, SelectMode.SelectRead); bool part2 = (_energyMeterSocket.Available == 0); if (part1 && part2) { // 连接已断开,释放Socket Close(); return false; } return _energyMeterSocket.Connected; } catch (SocketException) { Close(); return false; } catch (Exception) { return false; } } } } /// /// close the socket /// /// public void Close() { lock (_lockObj) { if (_energyMeterSocket == null) return; try { // 优雅关闭:先关闭发送/接收,再释放 if (_energyMeterSocket.Connected) { _energyMeterSocket.Shutdown(SocketShutdown.Both); } } catch { } finally { _energyMeterSocket.Close(); _energyMeterSocket.Dispose(); _energyMeterSocket = null; // 关键:置空,避免后续操作无效套接字 } } } public bool Connect() { lock (_lockObj) { // 已连接则直接返回 if (IsConnected) return true; // 先释放旧连接 Close(); try { return TCPConnect(); } catch (Exception ex) { TxtHelper.WriteTxt($@"D:\APILog\Logs\智能电表信息\{DateTime.Now:yyyy-MM-dd}.txt", $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} 智能电表:连接异常 {ex.Message}"); Close(); return false; } } } private bool TCPConnect() { // 若目标是本地回环地址,直接提示错误(关键!) if (_endPoint.Address == IPAddress.Loopback || _endPoint.Address == IPAddress.IPv6Loopback) { TxtHelper.WriteTxt($@"D:\APILog\Logs\智能电表信息\{DateTime.Now:yyyy-MM-dd}.txt", $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} 智能电表:连接地址为本地回环(127.0.0.1),请配置电表真实IP!"); return false; } try { if (_endPoint.Address == IPAddress.None) { throw new InvalidOperationException("未设置有效的IP和端口"); } // 重新创建Socket实例 _energyMeterSocket = new Socket(_endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp) { SendTimeout = _timeout, ReceiveTimeout = _timeout }; // 异步连接(避免阻塞,可选) IAsyncResult result = _energyMeterSocket.BeginConnect(_endPoint, null, null); bool connectSuccess = result.AsyncWaitHandle.WaitOne(_timeout); if (connectSuccess && _energyMeterSocket.Connected) { TxtHelper.WriteTxt($@"D:\APILog\Logs\智能电表信息\{DateTime.Now:yyyy-MM-dd}.txt", $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} 智能电表:连接成功"); return true; } // 连接超时,释放当前Socket _energyMeterSocket.Close(); _energyMeterSocket.Dispose(); _energyMeterSocket = null; return true; } catch (SocketException ex) { TxtHelper.WriteTxt($@"D:\APILog\Logs\智能电表信息\{DateTime.Now:yyyy-MM-dd}.txt", $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} 智能电表:网口连接失败 {ex.SocketErrorCode} - {ex.Message}"); Close(); return false; } catch (Exception ex) { TxtHelper.WriteTxt($@"D:\APILog\Logs\智能电表信息\{DateTime.Now:yyyy-MM-dd}.txt", $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} 智能电表:连接失败 {ex.Message}"); Close(); return false; } } public bool Ping() { if (_endPoint.Address == IPAddress.None) return false; try { var reply = _ping.Send(_endPoint.Address, _timeout); return reply.Status == IPStatus.Success; } catch (Exception) { return false; } } public bool SendAndGetRes(byte[] bytes, ref List listM)//, ref JetReturnResult jet { // 参数校验 if (bytes == null || bytes.Length == 0) { _lastError = "发送指令为空"; return false; } if (listM == null) listM = new List(); var arrayPool = ArrayPool.Shared; byte[] buffer = arrayPool.Rent(BufferSize);//new byte[1024]; byte[] recvData = new byte[88 * 4 + 9];//0x58 int size = recvData.Length; //0x58 try { if (!Ping()) { _lastError = "Ping设备失败,网络不可达"; TxtHelper.WriteTxt($@"D:\APILog\Logs\智能电表信息\{DateTime.Now:yyyy-MM-dd}.txt", $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} 智能电表:{_lastError}"); return false; } if (!IsConnected && !Connect()) { _lastError = "重连设备失败"; return false; } // 发送 Send(bytes); // 接收 int recvLen = Receive(ref recvData, size); if (recvLen <= 0) { _lastError = "未接收到设备响应"; TxtHelper.WriteTxt($@"D:\APILog\Logs\智能电表信息\{DateTime.Now:yyyy-MM-dd}.txt", $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} 智能电表:{_lastError}"); return false; } // 解析 if ((recvData[0] == Convert.ToByte(01 >> 8) && recvData[1] == Convert.ToByte(01)))//pTask.id { if (recvData[7] == 0x04)//读线圈寄存器功能码 { //数据赋值 Array.Copy(recvData, 0, buffer, 0, recvData.Length); TxtHelper.WriteTxt($@"D:\APILog\Logs\智能电表信息\{DateTime.Now.ToString("yyyy-MM-dd")}.txt", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + $"电能表读取原始数据:{string.Join(" ", recvData)}"); decimal PhaseVoltageA = Convert.ToDecimal(HextoFloat(buffer[9], buffer[10], buffer[11], buffer[12])); decimal PhaseVoltageB = Convert.ToDecimal(HextoFloat(buffer[13], buffer[14], buffer[15], buffer[16])); decimal PhaseVoltageC = Convert.ToDecimal(HextoFloat(buffer[17], buffer[18], buffer[19], buffer[20])); decimal PhaseCurrentA = Convert.ToDecimal(HextoFloat(buffer[21], buffer[22], buffer[23], buffer[24])); decimal PhaseCurrentB = Convert.ToDecimal(HextoFloat(buffer[25], buffer[26], buffer[27], buffer[28])); decimal PhaseCurrentC = Convert.ToDecimal(HextoFloat(buffer[29], buffer[30], buffer[31], buffer[32])); decimal PhasePowerA = Convert.ToDecimal(HextoFloat(buffer[33], buffer[34], buffer[35], buffer[36])); decimal PhasePowerB = Convert.ToDecimal(HextoFloat(buffer[37], buffer[38], buffer[39], buffer[40])); decimal PhasePowerC = Convert.ToDecimal(HextoFloat(buffer[41], buffer[42], buffer[43], buffer[44])); decimal ActivePower = Convert.ToDecimal(HextoFloat(buffer[121], buffer[122], buffer[123], buffer[124])); decimal PowerFactor = Convert.ToDecimal(HextoFloat(buffer[133], buffer[134], buffer[135], buffer[136])); decimal pt = 1; decimal ct = 150 / 5; decimal AccumulatedElectricity = Convert.ToDecimal(HextoFloat(buffer[181], buffer[182], buffer[183], buffer[184])); ElectricEnergy m = new ElectricEnergy(); m.PhaseVoltageA = PhaseVoltageA; m.PhaseVoltageB = PhaseVoltageB; m.PhaseVoltageC = PhaseVoltageC; m.PhaseCurrentA = PhaseCurrentA * ct; m.PhaseCurrentB = PhaseCurrentB * ct; m.PhaseCurrentC = PhaseCurrentC * ct; m.PhasePowerA = PhaseVoltageA * PhaseCurrentA * ct; m.PhasePowerB = PhaseVoltageB * PhaseCurrentB * ct; m.PhasePowerC = PhaseVoltageC * PhaseCurrentC * ct; m.ActivePower = ActivePower * ct; m.PowerFactor = PowerFactor * ct; m.pt = pt;//Convert.ToDecimal(HextoFloat(buffer[0], buffer[0], buffer[0], buffer[0])); m.ct = ct;//Convert.ToDecimal(HextoFloat(buffer[0], buffer[0], buffer[0], buffer[0])); m.AccumulatedElectricity = AccumulatedElectricity; listM.Add(m); } else { switch (recvData[8]) { case (byte)ModBusExceptionCode.IllegalDataAddress: _lastError = string.Format("SendAndGetRes():ReadInputRegister [IllegalDataAddress]"); break; case (byte)ModBusExceptionCode.IllegalDataValue: _lastError = string.Format("SendAndGetRes():ReadInputRegister [IllegalDataValue]"); break; case (byte)ModBusExceptionCode.IllegalFunction: _lastError = string.Format("SendAndGetRes():ReadInputRegister [IllegalFunction]"); break; default: _lastError = string.Format("SendAndGetRes():ReadInputRegister [FunctionException]"); break; } TxtHelper.WriteTxt($@"D:\APILog\Logs\智能电表信息\{DateTime.Now.ToString("yyyy-MM-dd")}.txt", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + $"电能表读取数据异常,原因:{_lastError}"); } } } catch (Exception ex) { TxtHelper.WriteTxt($@"D:\APILog\Logs\智能电表信息\{DateTime.Now.ToString("yyyy-MM-dd")}.txt", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + $"电能表读取原始数据:{string.Join(" ", recvData)}"); TxtHelper.WriteTxt($@"D:\APILog\Logs\智能电表信息\{DateTime.Now.ToString("yyyy-MM-dd")}.txt", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + $"电能表读取数据异常,原因:{ex}"); Close(); return false; } finally { arrayPool.Return(buffer); } return true; } /// /// send a command to energy meter device /// /// /// private int Send(Byte[] command) { lock (_lockObj) { // 核心校验:Socket不为null且连接有效 if (_energyMeterSocket == null || !IsConnected) { throw new SocketException((int)SocketError.NotConnected); } int bytesSent = _energyMeterSocket.Send(command, 0, command.Length, SocketFlags.None); return bytesSent; } } /// /// receives a response from the plc /// /// /// /// private int Receive(ref Byte[] response, int expectLen) { lock (_lockObj) { if (_energyMeterSocket == null || !IsConnected) { throw new SocketException((int)SocketError.NotConnected); } int totalRecv = 0; while (totalRecv < expectLen) { // 分段接收,避免单次接收不足 int recvLen = _energyMeterSocket.Receive(response, totalRecv, expectLen - totalRecv, SocketFlags.None); if (recvLen == 0) { throw new SocketException((int)SocketError.ConnectionReset); } totalRecv += recvLen; } return totalRecv; } } /// /// HexToFloat /// /// 高字高字节 /// 高字低字节 /// 低字高字节 /// 低字低字节 /// float public static float HextoFloat(byte H1, byte H2, byte D1, byte D2) { try { //byte-->short int s1, s2; s1 = Convert.ToInt32(H1 * 256) + Convert.ToInt32(H2); s2 = Convert.ToInt32(D1 * 256) + Convert.ToInt32(D2); //将输入数值short转化为无符号unsigned short int us1 = s1, us2 = s2; if (s1 < 0) us1 += 65536; if (s2 < 0) us2 += 65536; //sign: 符号位, exponent: 阶码, mantissa:尾数 int sign, exponent; float mantissa; //计算符号位 sign = us1 / 32768; //去掉符号位 int emCode = us1 % 32768; //计算阶码 exponent = emCode / 128; //计算尾数 mantissa = (float)(emCode % 128 * 65536 + us2) / 8388608; //代入公式 fValue = (-1) ^ S x 2 ^ (E - 127) x (1 + M) return (float)Math.Pow(-1, sign) * (float)Math.Pow(2, exponent - 127) * (1 + mantissa); } catch (Exception ex) { return 0; } } } }