From 9c71e1a286654333c3cab7e15fd1307bd42c86b1 Mon Sep 17 00:00:00 2001 From: iioter <535915157@qq.com> Date: Sat, 6 Aug 2022 18:21:00 +0800 Subject: [PATCH] =?UTF-8?q?1.=E5=90=88=E5=B9=B6@sugerlcc=E7=9A=84Modbus?= =?UTF-8?q?=E9=A9=B1=E5=8A=A8=E5=86=99=E5=85=A5PR=202.=E5=BC=95=E5=85=A5NL?= =?UTF-8?q?og=EF=BC=8C=E9=BB=98=E8=AE=A4=E5=86=99=E5=85=A5=E6=9C=AC?= =?UTF-8?q?=E5=9C=B0=203.Driver=E6=9E=84=E9=80=A0=E5=87=BD=E6=95=B0?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0Logger=E5=92=8CDevice=E5=8F=82=E6=95=B0?= =?UTF-8?q?=EF=BC=8C=E7=94=A8=E4=BA=8E=E8=AE=B0=E5=BD=95=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IoTGateway/IoTGateway.csproj | 2 + IoTGateway/Program.cs | 5 +- IoTGateway/nlog.config | 35 +++ .../DriverAllenBradley/AllenBradley.cs | 12 +- .../DriverAllenBradley.csproj | 1 + .../Drivers/DriverFanuc/DriverFanuc.csproj | 1 + Plugins/Drivers/DriverFanuc/Fanuc.cs | 14 +- .../DriverFanucHsl/DriverFanucHsl.csproj | 1 + Plugins/Drivers/DriverFanucHsl/FanucHsl.cs | 14 +- .../DriverMTConnect/DriverMTConnect.csproj | 1 + .../DriverMTConnect/MTConnectClient.cs | 13 +- .../DriverMitsubishi/DriverMitsubishi.csproj | 1 + .../Drivers/DriverMitsubishi/Mitsubishi.cs | 12 +- .../DriverModbusMaster.csproj | 1 + .../DriverModbusMaster/ModbusDataConver.cs | 211 +++++++++++++++++ .../DriverModbusMaster/ModbusMaster.cs | 224 ++++++++++-------- .../DriverOPCDaClient.csproj | 1 + .../Drivers/DriverOPCDaClient/OPCDaClient.cs | 11 +- .../DriverOPCUaClient.csproj | 1 + .../Drivers/DriverOPCUaClient/OPCUaClient.cs | 11 +- .../DriverOmronFins/DriverOmronFins.csproj | 1 + Plugins/Drivers/DriverOmronFins/OmronFins.cs | 12 +- .../DriverSiemensS7/DriverSiemensS7.csproj | 1 + Plugins/Drivers/DriverSiemensS7/SiemensS7.cs | 12 +- .../DriverSimTcpClient.csproj | 1 + .../DriverSimTcpClient/SimTcpClient.cs | 12 +- Plugins/Plugin/DeviceService.cs | 6 +- Plugins/PluginInterface/IDriver.cs | 5 + .../PluginInterface/PluginInterface.csproj | 1 + README.md | 2 +- images/qq.png | Bin 2166 -> 31550 bytes 31 files changed, 503 insertions(+), 122 deletions(-) create mode 100644 IoTGateway/nlog.config create mode 100644 Plugins/Drivers/DriverModbusMaster/ModbusDataConver.cs diff --git a/IoTGateway/IoTGateway.csproj b/IoTGateway/IoTGateway.csproj index 6a24c64..94e53f9 100644 --- a/IoTGateway/IoTGateway.csproj +++ b/IoTGateway/IoTGateway.csproj @@ -23,6 +23,8 @@ + + diff --git a/IoTGateway/Program.cs b/IoTGateway/Program.cs index 21d3105..f770fc8 100644 --- a/IoTGateway/Program.cs +++ b/IoTGateway/Program.cs @@ -8,6 +8,8 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using MQTTnet.AspNetCore.Extensions; using WalkingTec.Mvvm.Core; +using NLog; +using NLog.Web; namespace IoTGateway { @@ -48,7 +50,8 @@ namespace IoTGateway option.ListenAnyIP(1888, l => l.UseMqtt()); option.ListenAnyIP(518); }); - }); + }) + .UseNLog(); } } } diff --git a/IoTGateway/nlog.config b/IoTGateway/nlog.config new file mode 100644 index 0000000..0783d6e --- /dev/null +++ b/IoTGateway/nlog.config @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Plugins/Drivers/DriverAllenBradley/AllenBradley.cs b/Plugins/Drivers/DriverAllenBradley/AllenBradley.cs index 0357910..94e322e 100644 --- a/Plugins/Drivers/DriverAllenBradley/AllenBradley.cs +++ b/Plugins/Drivers/DriverAllenBradley/AllenBradley.cs @@ -3,6 +3,8 @@ using IoTClient.Enums; using PluginInterface; using System; using System.Text; +using IoTGateway.Model; +using Microsoft.Extensions.Logging; namespace DriverAllenBradley { @@ -11,6 +13,9 @@ namespace DriverAllenBradley public class AllenBradley : IDriver { private AllenBradleyClient plc = null; + + public ILogger _logger { get; set; } + private readonly Device _device; #region 配置参数 [ConfigParameter("设备Id")] @@ -30,9 +35,12 @@ namespace DriverAllenBradley #endregion - public AllenBradley(Guid deviceId) + public AllenBradley(Device device, ILogger logger) { - DeviceId = deviceId; + _device = device; + _logger = logger; + + _logger.LogInformation($"Device:[{_device.DeviceName}],Create()"); } diff --git a/Plugins/Drivers/DriverAllenBradley/DriverAllenBradley.csproj b/Plugins/Drivers/DriverAllenBradley/DriverAllenBradley.csproj index d2e828e..b460b4d 100644 --- a/Plugins/Drivers/DriverAllenBradley/DriverAllenBradley.csproj +++ b/Plugins/Drivers/DriverAllenBradley/DriverAllenBradley.csproj @@ -14,5 +14,6 @@ + diff --git a/Plugins/Drivers/DriverFanuc/DriverFanuc.csproj b/Plugins/Drivers/DriverFanuc/DriverFanuc.csproj index 41dcdda..579346d 100644 --- a/Plugins/Drivers/DriverFanuc/DriverFanuc.csproj +++ b/Plugins/Drivers/DriverFanuc/DriverFanuc.csproj @@ -9,5 +9,6 @@ + diff --git a/Plugins/Drivers/DriverFanuc/Fanuc.cs b/Plugins/Drivers/DriverFanuc/Fanuc.cs index bbe5125..d1c7d8d 100644 --- a/Plugins/Drivers/DriverFanuc/Fanuc.cs +++ b/Plugins/Drivers/DriverFanuc/Fanuc.cs @@ -1,4 +1,6 @@ -using PluginInterface; +using IoTGateway.Model; +using Microsoft.Extensions.Logging; +using PluginInterface; namespace DriverFaunc { @@ -20,6 +22,9 @@ namespace DriverFaunc private ushort _hndl; private int _result = -1; + public ILogger _logger { get; set; } + private readonly Device _device; + public bool IsConnected { get @@ -39,9 +44,12 @@ namespace DriverFaunc } } - public Fanuc(Guid deviceId) + public Fanuc(Device device, ILogger logger) { - DeviceId = deviceId; + _device = device; + _logger = logger; + + _logger.LogInformation($"Device:[{_device.DeviceName}],Create()"); } public bool Close() diff --git a/Plugins/Drivers/DriverFanucHsl/DriverFanucHsl.csproj b/Plugins/Drivers/DriverFanucHsl/DriverFanucHsl.csproj index cb96cdd..4f81f30 100644 --- a/Plugins/Drivers/DriverFanucHsl/DriverFanucHsl.csproj +++ b/Plugins/Drivers/DriverFanucHsl/DriverFanucHsl.csproj @@ -15,5 +15,6 @@ + diff --git a/Plugins/Drivers/DriverFanucHsl/FanucHsl.cs b/Plugins/Drivers/DriverFanucHsl/FanucHsl.cs index 1d0ea86..1acab67 100644 --- a/Plugins/Drivers/DriverFanucHsl/FanucHsl.cs +++ b/Plugins/Drivers/DriverFanucHsl/FanucHsl.cs @@ -4,6 +4,8 @@ using System; using System.Text; using HslCommunication.CNC.Fanuc; using HslCommunication; +using IoTGateway.Model; +using Microsoft.Extensions.Logging; namespace DriverFanucHsl { @@ -12,6 +14,9 @@ namespace DriverFanucHsl public class FanucHsl : IDriver { private FanucSeries0i fanuc; + + public ILogger _logger { get; set; } + private readonly Device _device; #region 配置参数 [ConfigParameter("设备Id")] @@ -31,7 +36,7 @@ namespace DriverFanucHsl #endregion - public FanucHsl(Guid deviceId) + public FanucHsl(Device device, ILogger logger) { // 授权示例 Authorization example if (!Authorization.SetAuthorizationCode("输入你的授权号")) @@ -39,10 +44,13 @@ namespace DriverFanucHsl //return; // 激活失败应该退出系统 } - DeviceId = deviceId; - } + _device = device; + _logger = logger; + _logger.LogInformation($"Device:[{_device.DeviceName}],Create()"); + } + public bool IsConnected { get diff --git a/Plugins/Drivers/DriverMTConnect/DriverMTConnect.csproj b/Plugins/Drivers/DriverMTConnect/DriverMTConnect.csproj index 82c2e3e..17cf3d3 100644 --- a/Plugins/Drivers/DriverMTConnect/DriverMTConnect.csproj +++ b/Plugins/Drivers/DriverMTConnect/DriverMTConnect.csproj @@ -14,5 +14,6 @@ + diff --git a/Plugins/Drivers/DriverMTConnect/MTConnectClient.cs b/Plugins/Drivers/DriverMTConnect/MTConnectClient.cs index 5f51ab0..04516a3 100644 --- a/Plugins/Drivers/DriverMTConnect/MTConnectClient.cs +++ b/Plugins/Drivers/DriverMTConnect/MTConnectClient.cs @@ -1,6 +1,9 @@ using PluginInterface; using System; +using Microsoft.Extensions.Logging; using OpenNETCF.MTConnect; +using Device = IoTGateway.Model.Device; + namespace DriverMTConnect { internal class MTConnectClient : IDriver @@ -26,9 +29,15 @@ namespace DriverMTConnect EntityClient m_client = null; - public MTConnectClient(Guid deviceId) + public ILogger _logger { get; set; } + private readonly Device _device; + + public MTConnectClient(Device device, ILogger logger) { - DeviceId = deviceId; + _device = device; + _logger = logger; + + _logger.LogInformation($"Device:[{_device.DeviceName}],Create()"); } public bool Close() diff --git a/Plugins/Drivers/DriverMitsubishi/DriverMitsubishi.csproj b/Plugins/Drivers/DriverMitsubishi/DriverMitsubishi.csproj index 42df9f2..e88f3f4 100644 --- a/Plugins/Drivers/DriverMitsubishi/DriverMitsubishi.csproj +++ b/Plugins/Drivers/DriverMitsubishi/DriverMitsubishi.csproj @@ -14,5 +14,6 @@ + diff --git a/Plugins/Drivers/DriverMitsubishi/Mitsubishi.cs b/Plugins/Drivers/DriverMitsubishi/Mitsubishi.cs index 0bb5842..47b0725 100644 --- a/Plugins/Drivers/DriverMitsubishi/Mitsubishi.cs +++ b/Plugins/Drivers/DriverMitsubishi/Mitsubishi.cs @@ -3,6 +3,8 @@ using IoTClient.Enums; using PluginInterface; using System; using System.Text; +using IoTGateway.Model; +using Microsoft.Extensions.Logging; namespace DriverMitsubishi { @@ -12,6 +14,9 @@ namespace DriverMitsubishi public class Mitsubishi : IDriver { private MitsubishiClient plc = null; + + public ILogger _logger { get; set; } + private readonly Device _device; #region 配置参数 [ConfigParameter("设备Id")] @@ -34,9 +39,12 @@ namespace DriverMitsubishi #endregion - public Mitsubishi(Guid deviceId) + public Mitsubishi(Device device, ILogger logger) { - DeviceId = deviceId; + _device = device; + _logger = logger; + + _logger.LogInformation($"Device:[{_device.DeviceName}],Create()"); } diff --git a/Plugins/Drivers/DriverModbusMaster/DriverModbusMaster.csproj b/Plugins/Drivers/DriverModbusMaster/DriverModbusMaster.csproj index bca6eb3..68b50e8 100644 --- a/Plugins/Drivers/DriverModbusMaster/DriverModbusMaster.csproj +++ b/Plugins/Drivers/DriverModbusMaster/DriverModbusMaster.csproj @@ -12,6 +12,7 @@ + diff --git a/Plugins/Drivers/DriverModbusMaster/ModbusDataConver.cs b/Plugins/Drivers/DriverModbusMaster/ModbusDataConver.cs new file mode 100644 index 0000000..c6a6fe4 --- /dev/null +++ b/Plugins/Drivers/DriverModbusMaster/ModbusDataConver.cs @@ -0,0 +1,211 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DriverModbusMaster +{ + public class ModbusDataConvert + { + /// + /// 赋值string + /// + /// + /// + /// + /// + public static void SetString(ushort[] src, int start, string value) + { + byte[] bytesTemp = Encoding.UTF8.GetBytes(value); + ushort[] dest = Bytes2Ushorts(bytesTemp); + dest.CopyTo(src, start); + } + + /// + /// 获取string + /// + /// + /// + /// + /// + public static string GetString(ushort[] src, int start, int len) + { + ushort[] temp = new ushort[len]; + for (int i = 0; i < len; i++) + { + temp[i] = src[i + start]; + } + byte[] bytesTemp = Ushorts2Bytes(temp); + string res = Encoding.UTF8.GetString(bytesTemp).Trim(new char[] { '\0' }); + return res; + } + + /// + /// 赋值Real类型数据 + /// + /// + /// + /// + public static void SetReal(ushort[] src, int start, float value) + { + byte[] bytes = BitConverter.GetBytes(value); + + ushort[] dest = Bytes2Ushorts(bytes); + + dest.CopyTo(src, start); + } + + /// + /// 获取float类型数据 + /// + /// + /// + /// + public static float GetReal(ushort[] src, int start) + { + ushort[] temp = new ushort[2]; + for (int i = 0; i < 2; i++) + { + temp[i] = src[i + start]; + } + byte[] bytesTemp = Ushorts2Bytes(temp); + float res = BitConverter.ToSingle(bytesTemp, 0); + return res; + } + + /// + /// 赋值Short类型数据 + /// + /// + /// + /// + public static void SetShort(ushort[] src, int start, short value) + { + byte[] bytes = BitConverter.GetBytes(value); + + ushort[] dest = Bytes2Ushorts(bytes); + + dest.CopyTo(src, start); + } + + /// + /// 获取short类型数据 + /// + /// + /// + /// + public static short GetShort(ushort[] src, int start) + { + ushort[] temp = new ushort[1]; + temp[0] = src[start]; + byte[] bytesTemp = Ushorts2Bytes(temp); + short res = BitConverter.ToInt16(bytesTemp, 0); + return res; + } + + + public static bool[] GetBools(ushort[] src, int start, int num) + { + ushort[] temp = new ushort[num]; + for (int i = start; i < start + num; i++) + { + temp[i] = src[i + start]; + } + byte[] bytes = Ushorts2Bytes(temp); + + bool[] res = Bytes2Bools(bytes); + + return res; + } + + private static bool[] Bytes2Bools(byte[] b) + { + bool[] array = new bool[8 * b.Length]; + + for (int i = 0; i < b.Length; i++) + { + for (int j = 0; j < 8; j++) + { + array[i * 8 + j] = (b[i] & 1) == 1;//判定byte的最后一位是否为1,若为1,则是true;否则是false + b[i] = (byte)(b[i] >> 1);//将byte右移一位 + } + } + return array; + } + + private static byte Bools2Byte(bool[] array) + { + if (array != null && array.Length > 0) + { + byte b = 0; + for (int i = 0; i < 8; i++) + { + if (array[i]) + { + byte nn = (byte)(1 << i);//左移一位,相当于×2 + b += nn; + } + } + return b; + } + return 0; + } + + private static ushort[] Bytes2Ushorts(byte[] src, bool reverse = false) + { + int len = src.Length; + + byte[] srcPlus = new byte[len + 1]; + src.CopyTo(srcPlus, 0); + int count = len >> 1; + + if (len % 2 != 0) + { + count += 1; + } + + ushort[] dest = new ushort[count]; + if (reverse) + { + for (int i = 0; i < count; i++) + { + dest[i] = (ushort)(srcPlus[i * 2] << 8 | srcPlus[2 * i + 1] & 0xff); + } + } + else + { + for (int i = 0; i < count; i++) + { + dest[i] = (ushort)(srcPlus[i * 2] & 0xff | srcPlus[2 * i + 1] << 8); + } + } + + return dest; + } + + private static byte[] Ushorts2Bytes(ushort[] src, bool reverse = false) + { + + int count = src.Length; + byte[] dest = new byte[count << 1]; + if (reverse) + { + for (int i = 0; i < count; i++) + { + dest[i * 2] = (byte)(src[i] >> 8); + dest[i * 2 + 1] = (byte)(src[i] >> 0); + } + } + else + { + for (int i = 0; i < count; i++) + { + dest[i * 2] = (byte)(src[i] >> 0); + dest[i * 2 + 1] = (byte)(src[i] >> 8); + } + } + return dest; + } + } +} diff --git a/Plugins/Drivers/DriverModbusMaster/ModbusMaster.cs b/Plugins/Drivers/DriverModbusMaster/ModbusMaster.cs index 8867ce6..60ae502 100644 --- a/Plugins/Drivers/DriverModbusMaster/ModbusMaster.cs +++ b/Plugins/Drivers/DriverModbusMaster/ModbusMaster.cs @@ -1,10 +1,9 @@ -using Modbus.Device; +using IoTGateway.Model; +using Microsoft.Extensions.Logging; +using Modbus.Device; using Modbus.Serial; using PluginInterface; -using System; -using System.Collections.Generic; using System.IO.Ports; -using System.Net; using System.Net.Sockets; using System.Text; @@ -14,21 +13,24 @@ namespace DriverModbusMaster [DriverSupported("ModbusUDP")] [DriverSupported("ModbusRtu")] [DriverSupported("ModbusAscii")] - [DriverInfoAttribute("ModbusMaster", "V1.0.0", "Copyright IoTGateway© 2021-12-19")] + [DriverInfoAttribute("ModbusMaster", "V1.1.0", "Copyright IoTGateway© 2022-8-6")] public class ModbusMaster : IDriver { - private TcpClient clientTcp = null; - private UdpClient clientUdp = null; - private SerialPort port = null; - private Modbus.Device.ModbusMaster master = null; - private SerialPortAdapter adapter = null; + private TcpClient? _tcpClient; + private UdpClient? _udpClient; + private SerialPort? _serialPort; + private Modbus.Device.ModbusMaster? _master; + private SerialPortAdapter? _adapter; + + public ILogger _logger { get; set; } + private readonly Device _device; #region 配置参数 [ConfigParameter("设备Id")] public Guid DeviceId { get; set; } [ConfigParameter("PLC类型")] - public PLC_TYPE PLCType { get; set; } = PLC_TYPE.S71200; + public PLC_TYPE PlcType { get; set; } = PLC_TYPE.S71200; [ConfigParameter("主站类型")] public Master_TYPE Master_TYPE { get; set; } = Master_TYPE.Tcp; @@ -65,11 +67,13 @@ namespace DriverModbusMaster #endregion - public ModbusMaster(Guid deviceId) + public ModbusMaster(Device device, ILogger logger) { - DeviceId = deviceId; - } + _device = device; + _logger = logger; + _logger.LogInformation($"Device:[{_device.DeviceName}],Create()"); + } public bool IsConnected { @@ -80,14 +84,14 @@ namespace DriverModbusMaster case Master_TYPE.Tcp: case Master_TYPE.RtuOnTcp: case Master_TYPE.AsciiOnTcp: - return clientTcp != null && master != null && clientTcp.Connected; + return _tcpClient != null && _master != null && _tcpClient.Connected; case Master_TYPE.Udp: case Master_TYPE.RtuOnUdp: case Master_TYPE.AsciiOnUdp: - return clientUdp != null && master != null && clientUdp.Client.Connected; + return _udpClient != null && _master != null && _udpClient.Client.Connected; case Master_TYPE.Rtu: case Master_TYPE.Ascii: - return port != null && master != null && port.IsOpen; + return _serialPort != null && _master != null && _serialPort.IsOpen; default: return false; } @@ -98,68 +102,68 @@ namespace DriverModbusMaster { try { + _logger.LogInformation($"Device:[{_device.DeviceName}],Connect()"); switch (Master_TYPE) { case Master_TYPE.Tcp: - clientTcp = new TcpClient(IpAddress.ToString(), Port); - clientTcp.ReceiveTimeout = Timeout; - clientTcp.SendTimeout = Timeout; - master = ModbusIpMaster.CreateIp(clientTcp); + _tcpClient = new TcpClient(IpAddress, Port); + _tcpClient.ReceiveTimeout = Timeout; + _tcpClient.SendTimeout = Timeout; + _master = ModbusIpMaster.CreateIp(_tcpClient); break; case Master_TYPE.Udp: - clientUdp = new UdpClient(IpAddress.ToString(), Port); - clientUdp.Client.ReceiveTimeout = Timeout; - clientUdp.Client.SendTimeout = Timeout; - master = ModbusIpMaster.CreateIp(clientUdp); + _udpClient = new UdpClient(IpAddress, Port); + _udpClient.Client.ReceiveTimeout = Timeout; + _udpClient.Client.SendTimeout = Timeout; + _master = ModbusIpMaster.CreateIp(_udpClient); break; case Master_TYPE.Rtu: - port = new SerialPort(PortName, BaudRate, Parity, DataBits, StopBits); - port.ReadTimeout = Timeout; - port.WriteTimeout = Timeout; - port.Open(); - adapter = new SerialPortAdapter(port); - master = ModbusSerialMaster.CreateRtu(adapter); + _serialPort = new SerialPort(PortName, BaudRate, Parity, DataBits, StopBits); + _serialPort.ReadTimeout = Timeout; + _serialPort.WriteTimeout = Timeout; + _serialPort.Open(); + _adapter = new SerialPortAdapter(_serialPort); + _master = ModbusSerialMaster.CreateRtu(_adapter); break; case Master_TYPE.RtuOnTcp: - clientTcp = new TcpClient(IpAddress.ToString(), Port); - clientTcp.ReceiveTimeout = Timeout; - clientTcp.SendTimeout = Timeout; - master = ModbusSerialMaster.CreateRtu(clientTcp); + _tcpClient = new TcpClient(IpAddress, Port); + _tcpClient.ReceiveTimeout = Timeout; + _tcpClient.SendTimeout = Timeout; + _master = ModbusSerialMaster.CreateRtu(_tcpClient); break; case Master_TYPE.RtuOnUdp: - clientUdp = new UdpClient(IpAddress.ToString(), Port); - clientUdp.Client.ReceiveTimeout = Timeout; - clientUdp.Client.SendTimeout = Timeout; - master = ModbusSerialMaster.CreateRtu(clientUdp); + _udpClient = new UdpClient(IpAddress, Port); + _udpClient.Client.ReceiveTimeout = Timeout; + _udpClient.Client.SendTimeout = Timeout; + _master = ModbusSerialMaster.CreateRtu(_udpClient); break; case Master_TYPE.Ascii: - port = new SerialPort(PortName, BaudRate, Parity, DataBits, StopBits); - port.ReadTimeout = Timeout; - port.WriteTimeout = Timeout; - port.Open(); - adapter = new SerialPortAdapter(port); - master = ModbusSerialMaster.CreateAscii(adapter); + _serialPort = new SerialPort(PortName, BaudRate, Parity, DataBits, StopBits); + _serialPort.ReadTimeout = Timeout; + _serialPort.WriteTimeout = Timeout; + _serialPort.Open(); + _adapter = new SerialPortAdapter(_serialPort); + _master = ModbusSerialMaster.CreateAscii(_adapter); break; case Master_TYPE.AsciiOnTcp: - clientTcp = new TcpClient(IpAddress.ToString(), Port); - clientTcp.ReceiveTimeout = Timeout; - clientTcp.SendTimeout = Timeout; - master = ModbusSerialMaster.CreateAscii(clientTcp); + _tcpClient = new TcpClient(IpAddress, Port); + _tcpClient.ReceiveTimeout = Timeout; + _tcpClient.SendTimeout = Timeout; + _master = ModbusSerialMaster.CreateAscii(_tcpClient); break; case Master_TYPE.AsciiOnUdp: - clientUdp = new UdpClient(IpAddress.ToString(), Port); - clientUdp.Client.ReceiveTimeout = Timeout; - clientUdp.Client.SendTimeout = Timeout; - master = ModbusSerialMaster.CreateAscii(clientUdp); - break; - default: + _udpClient = new UdpClient(IpAddress, Port); + _udpClient.Client.ReceiveTimeout = Timeout; + _udpClient.Client.SendTimeout = Timeout; + _master = ModbusSerialMaster.CreateAscii(_udpClient); break; } - master.Transport.ReadTimeout = Timeout; - master.Transport.WriteTimeout = Timeout; + _master.Transport.ReadTimeout = Timeout; + _master.Transport.WriteTimeout = Timeout; } catch (Exception ex) { + _logger.LogError($"Device:[{_device.DeviceName}],Connect(),Error", ex); return false; } return IsConnected; @@ -169,14 +173,15 @@ namespace DriverModbusMaster { try { - clientTcp?.Close(); - clientUdp?.Close(); - port?.Close(); + _logger.LogInformation($"Device:[{_device.DeviceName}],Close()"); + _tcpClient?.Close(); + _udpClient?.Close(); + _serialPort?.Close(); return !IsConnected; } - catch (Exception) + catch (Exception ex) { - + _logger.LogError($"Device:[{_device.DeviceName}],Close(),Error", ex); return false; } } @@ -185,14 +190,15 @@ namespace DriverModbusMaster { try { - clientTcp?.Dispose(); - clientUdp?.Dispose(); - port?.Dispose(); - master?.Dispose(); + _tcpClient?.Dispose(); + _udpClient?.Dispose(); + _serialPort?.Dispose(); + _master?.Dispose(); + _logger.LogInformation($"Device:[{_device.DeviceName}],Dispose()"); } - catch (Exception) + catch (Exception ex) { - + _logger.LogError($"Device:[{_device.DeviceName}],Dispose(),Error", ex); } } @@ -214,6 +220,7 @@ namespace DriverModbusMaster { ret.StatusType = VaribaleStatusTypeEnum.UnKnow; ret.Message = ex.Message; + _logger.LogInformation($"Device:[{_device.DeviceName}],ReadHoldingRegisters(),Error", ex); } return ret; @@ -238,6 +245,7 @@ namespace DriverModbusMaster { ret.StatusType = VaribaleStatusTypeEnum.UnKnow; ret.Message = ex.Message; + _logger.LogInformation($"Device:[{_device.DeviceName}],ReadInputRegisters(),Error", ex); } return ret; @@ -252,7 +260,7 @@ namespace DriverModbusMaster { if (IsConnected) { - var retBool = master.ReadCoils(SlaveAddress, ushort.Parse(ioarg.Address), 1)[0]; + var retBool = _master.ReadCoils(SlaveAddress, ushort.Parse(ioarg.Address), 1)[0]; if (ioarg.ValueType == DataTypeEnum.Bit) { if (retBool) @@ -274,6 +282,7 @@ namespace DriverModbusMaster { ret.StatusType = VaribaleStatusTypeEnum.UnKnow; ret.Message = ex.Message; + _logger.LogInformation($"Device:[{_device.DeviceName}],ReadCoil(),Error", ex); } return ret; @@ -287,7 +296,7 @@ namespace DriverModbusMaster { if (IsConnected) { - var retBool = master.ReadInputs(SlaveAddress, ushort.Parse(ioarg.Address), 1)[0]; + var retBool = _master.ReadInputs(SlaveAddress, ushort.Parse(ioarg.Address), 1)[0]; if (ioarg.ValueType == DataTypeEnum.Bit) { if (retBool) @@ -309,6 +318,7 @@ namespace DriverModbusMaster { ret.StatusType = VaribaleStatusTypeEnum.UnKnow; ret.Message = ex.Message; + _logger.LogInformation($"Device:[{_device.DeviceName}],ReadInput(),Error", ex); } return ret; @@ -336,16 +346,16 @@ namespace DriverModbusMaster else { ushort startAddress, count; - ret = AnalyseAddress(ioarg, out startAddress, out count); - if(ret.StatusType!= VaribaleStatusTypeEnum.Good) + ret = AnalyzeAddress(ioarg, out startAddress, out count); + if (ret.StatusType != VaribaleStatusTypeEnum.Good) return ret; try { var rawBuffers = new ushort[] { }; if (FunCode == 3) - rawBuffers = master.ReadHoldingRegisters(SlaveAddress, startAddress, count); + rawBuffers = _master.ReadHoldingRegisters(SlaveAddress, startAddress, count); else if (FunCode == 4) - rawBuffers = master.ReadInputRegisters(SlaveAddress, startAddress, count); + rawBuffers = _master.ReadInputRegisters(SlaveAddress, startAddress, count); var retBuffers = ChangeBuffersOrder(rawBuffers, ioarg.ValueType); if (ioarg.ValueType == DataTypeEnum.AsciiString) @@ -356,9 +366,9 @@ namespace DriverModbusMaster else if (ioarg.ValueType.ToString().Contains("Int16")) ret.Value = (short)retBuffers[0]; else if (ioarg.ValueType.ToString().Contains("Uint32")) - ret.Value = (UInt32)(retBuffers[0] << 16) + retBuffers[1]; + ret.Value = (uint)(retBuffers[0] << 16) + retBuffers[1]; else if (ioarg.ValueType.ToString().Contains("Int32")) - ret.Value = (Int32)(retBuffers[0] << 16) + retBuffers[1]; + ret.Value = (retBuffers[0] << 16) + retBuffers[1]; else if (ioarg.ValueType.ToString().Contains("Float")) { var bytes = new byte[] { (byte)(retBuffers[1] & 0xff), (byte)((retBuffers[1] >> 8) & 0xff), (byte)(retBuffers[0] & 0xff), (byte)((retBuffers[0] >> 8) & 0xff) }; @@ -366,7 +376,7 @@ namespace DriverModbusMaster } else if (ioarg.ValueType.ToString().Contains("AsciiString")) { - var str= Encoding.ASCII.GetString(GetBytes(retBuffers).ToArray()); + var str = Encoding.ASCII.GetString(GetBytes(retBuffers).ToArray()); if (str.Contains('\0')) str = str.Split('\0')[0]; ret.Value = str; @@ -377,6 +387,7 @@ namespace DriverModbusMaster { ret.StatusType = VaribaleStatusTypeEnum.Bad; ret.Message = ex.Message; + _logger.LogInformation($"Device:[{_device.DeviceName}],ReadRegistersBuffers(),Error", ex); } } @@ -387,10 +398,9 @@ namespace DriverModbusMaster { if (dataType.ToString().Contains("32") || dataType.ToString().Contains("Float")) return 2; - else if (dataType.ToString().Contains("64") || dataType.ToString().Contains("Double")) + if (dataType.ToString().Contains("64") || dataType.ToString().Contains("Double")) return 4; - else - return 1; + return 1; } //预留了大小端转换的 @@ -454,18 +464,16 @@ namespace DriverModbusMaster private List GetBytes(ushort[] retBuffers) { - List vs = new(); - for (int i = 0; i < retBuffers.Length; i++) + foreach (var retBuffer in retBuffers) { - vs.Add((byte)(retBuffers[i] & 0xFF)); - vs.Add((byte)((retBuffers[i] & 0xFF00) >> 8)); + vs.Add((byte)(retBuffer & 0xFF)); + vs.Add((byte)((retBuffer & 0xFF00) >> 8)); } - return vs; } - private DriverReturnValueModel AnalyseAddress(DriverAddressIoArgModel ioarg, out ushort StartAddress, out ushort ReadCount) + private DriverReturnValueModel AnalyzeAddress(DriverAddressIoArgModel ioarg, out ushort StartAddress, out ushort ReadCount) { DriverReturnValueModel ret = new() { StatusType = VaribaleStatusTypeEnum.Good }; try @@ -489,46 +497,70 @@ namespace DriverModbusMaster ret.Message = ex.Message; StartAddress = 0; ReadCount = 0; + _logger.LogInformation($"Device:[{_device.DeviceName}],AnalyzeAddress(),Error", ex); return ret; } } - public async Task WriteAsync(string RequestId, string Method, DriverAddressIoArgModel Ioarg) + public async Task WriteAsync(string requestId, string method, DriverAddressIoArgModel ioarg) { RpcResponse rpcResponse = new() { IsSuccess = false }; try { - ushort address = ushort.Parse(Ioarg.Address); if (!IsConnected) rpcResponse.Description = "设备连接已断开"; else { + DriverReturnValueModel ret = new() { StatusType = VaribaleStatusTypeEnum.Good }; + ushort address, count; + ret = AnalyzeAddress(ioarg, out address, out count); + //功能码01 - if (Method == nameof(ReadCoil)) + if (method == nameof(ReadCoil)) { - bool value = Ioarg.Value.ToString() == "1" || Ioarg.Value.ToString().ToLower() == "true"; - master.WriteSingleCoilAsync(SlaveAddress, address, value); + var value = ioarg.Value.ToString() == "1" || ioarg.Value.ToString().ToLower() == "true"; + await _master.WriteSingleCoilAsync(SlaveAddress, address, value); rpcResponse.IsSuccess = true; return rpcResponse; } //功能码03 - else if (Method == nameof(ReadHoldingRegisters)) + if (method == nameof(ReadHoldingRegisters)) { - master.WriteSingleRegisterAsync(SlaveAddress, address, ushort.Parse(Ioarg.Value.ToString())); + ushort[] shortArray = new ushort[count]; + + switch (ioarg.ValueType) + { + case DataTypeEnum.AsciiString: + ModbusDataConvert.SetString(shortArray, 0, ioarg.Value.ToString()); + await _master.WriteMultipleRegistersAsync(SlaveAddress, address, shortArray); + + break; + case DataTypeEnum.Float: + float f = 0; + float.TryParse(ioarg.Value.ToString(), out f); + ModbusDataConvert.SetReal(shortArray, 0, f); + await _master.WriteMultipleRegistersAsync(SlaveAddress, address, shortArray); + + break; + default: + await _master.WriteSingleRegisterAsync(SlaveAddress, address, ushort.Parse(ioarg.Value.ToString())); + break; + } rpcResponse.IsSuccess = true; return rpcResponse; } - else - rpcResponse.Description = $"不支持写入:{Method}"; + rpcResponse.Description = $"不支持写入:{method}"; } } catch (Exception ex) { - rpcResponse.Description = $"写入失败,[Method]:{Method},[Ioarg]:{Ioarg},[ex]:{ex}"; + rpcResponse.Description = $"写入失败,[Method]:{method},[Ioarg]:{ioarg},[ex]:{ex}"; + _logger.LogInformation($"Device:[{_device.DeviceName}],WriteAsync(),Error", ex); } return rpcResponse; } } + public enum PLC_TYPE { S7200 = 0, diff --git a/Plugins/Drivers/DriverOPCDaClient/DriverOPCDaClient.csproj b/Plugins/Drivers/DriverOPCDaClient/DriverOPCDaClient.csproj index ed5f48e..dd205ed 100644 --- a/Plugins/Drivers/DriverOPCDaClient/DriverOPCDaClient.csproj +++ b/Plugins/Drivers/DriverOPCDaClient/DriverOPCDaClient.csproj @@ -10,6 +10,7 @@ + diff --git a/Plugins/Drivers/DriverOPCDaClient/OPCDaClient.cs b/Plugins/Drivers/DriverOPCDaClient/OPCDaClient.cs index 43622b7..3cd6b1c 100644 --- a/Plugins/Drivers/DriverOPCDaClient/OPCDaClient.cs +++ b/Plugins/Drivers/DriverOPCDaClient/OPCDaClient.cs @@ -5,6 +5,8 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using Automation.OPCClient; +using IoTGateway.Model; +using Microsoft.Extensions.Logging; namespace DriverOPCDaClient { @@ -12,6 +14,9 @@ namespace DriverOPCDaClient { OPCClientWrapper opcDaClient = null; + public ILogger _logger { get; set; } + private readonly Device _device; + #region 配置参数 [ConfigParameter("设备Id")] public Guid DeviceId { get; set; } @@ -28,10 +33,12 @@ namespace DriverOPCDaClient #endregion - public OPCDaClient(Guid deviceId) + public OPCDaClient(Device device, ILogger logger) { - DeviceId = deviceId; + _device = device; + _logger = logger; + _logger.LogInformation($"Device:[{_device.DeviceName}],Create()"); } diff --git a/Plugins/Drivers/DriverOPCUaClient/DriverOPCUaClient.csproj b/Plugins/Drivers/DriverOPCUaClient/DriverOPCUaClient.csproj index 037b055..af13809 100644 --- a/Plugins/Drivers/DriverOPCUaClient/DriverOPCUaClient.csproj +++ b/Plugins/Drivers/DriverOPCUaClient/DriverOPCUaClient.csproj @@ -14,5 +14,6 @@ + diff --git a/Plugins/Drivers/DriverOPCUaClient/OPCUaClient.cs b/Plugins/Drivers/DriverOPCUaClient/OPCUaClient.cs index ef88dd4..43ac181 100644 --- a/Plugins/Drivers/DriverOPCUaClient/OPCUaClient.cs +++ b/Plugins/Drivers/DriverOPCUaClient/OPCUaClient.cs @@ -6,6 +6,8 @@ using Opc.Ua; using Opc.Ua.Client; using System.Collections.Generic; using System.Threading.Tasks; +using IoTGateway.Model; +using Microsoft.Extensions.Logging; using Opc.Ua.Configuration; using OpcUaHelper; @@ -16,6 +18,9 @@ namespace DriverOPCUaClient public class OPCUaClient : IDriver { OpcUaClientHelper opcUaClient = null; + + public ILogger _logger { get; set; } + private readonly Device _device; #region 配置参数 [ConfigParameter("设备Id")] @@ -32,10 +37,12 @@ namespace DriverOPCUaClient #endregion - public OPCUaClient(Guid deviceId) + public OPCUaClient(Device device, ILogger logger) { - DeviceId = deviceId; + _device = device; + _logger = logger; + _logger.LogInformation($"Device:[{_device.DeviceName}],Create()"); } diff --git a/Plugins/Drivers/DriverOmronFins/DriverOmronFins.csproj b/Plugins/Drivers/DriverOmronFins/DriverOmronFins.csproj index 42df9f2..e88f3f4 100644 --- a/Plugins/Drivers/DriverOmronFins/DriverOmronFins.csproj +++ b/Plugins/Drivers/DriverOmronFins/DriverOmronFins.csproj @@ -14,5 +14,6 @@ + diff --git a/Plugins/Drivers/DriverOmronFins/OmronFins.cs b/Plugins/Drivers/DriverOmronFins/OmronFins.cs index 0b97f7d..ea78abc 100644 --- a/Plugins/Drivers/DriverOmronFins/OmronFins.cs +++ b/Plugins/Drivers/DriverOmronFins/OmronFins.cs @@ -3,6 +3,8 @@ using IoTClient.Enums; using PluginInterface; using System; using System.Text; +using IoTGateway.Model; +using Microsoft.Extensions.Logging; namespace DriverOmronFins { @@ -11,6 +13,9 @@ namespace DriverOmronFins public class OmronFins : IDriver { private OmronFinsClient plc = null; + + public ILogger _logger { get; set; } + private readonly Device _device; #region 配置参数 [ConfigParameter("设备Id")] @@ -30,9 +35,12 @@ namespace DriverOmronFins #endregion - public OmronFins(Guid deviceId) + public OmronFins(Device device, ILogger logger) { - DeviceId = deviceId; + _device = device; + _logger = logger; + + _logger.LogInformation($"Device:[{_device.DeviceName}],Create()"); } diff --git a/Plugins/Drivers/DriverSiemensS7/DriverSiemensS7.csproj b/Plugins/Drivers/DriverSiemensS7/DriverSiemensS7.csproj index 2617eb5..80504e5 100644 --- a/Plugins/Drivers/DriverSiemensS7/DriverSiemensS7.csproj +++ b/Plugins/Drivers/DriverSiemensS7/DriverSiemensS7.csproj @@ -9,5 +9,6 @@ + diff --git a/Plugins/Drivers/DriverSiemensS7/SiemensS7.cs b/Plugins/Drivers/DriverSiemensS7/SiemensS7.cs index 288cf8b..085f471 100644 --- a/Plugins/Drivers/DriverSiemensS7/SiemensS7.cs +++ b/Plugins/Drivers/DriverSiemensS7/SiemensS7.cs @@ -2,6 +2,8 @@ using S7.Net; using System; using System.Text; +using IoTGateway.Model; +using Microsoft.Extensions.Logging; namespace DriverSiemensS7 { @@ -15,6 +17,9 @@ namespace DriverSiemensS7 public class SiemensS7 : IDriver { private Plc plc = null; + + public ILogger _logger { get; set; } + private readonly Device _device; #region 配置参数 [ConfigParameter("设备Id")] @@ -43,9 +48,12 @@ namespace DriverSiemensS7 #endregion - public SiemensS7(Guid deviceId) + public SiemensS7(Device device, ILogger logger) { - DeviceId = deviceId; + _device = device; + _logger = logger; + + _logger.LogInformation($"Device:[{_device.DeviceName}],Create()"); } diff --git a/Plugins/Drivers/DriverSimTcpClient/DriverSimTcpClient.csproj b/Plugins/Drivers/DriverSimTcpClient/DriverSimTcpClient.csproj index c517682..89ee96d 100644 --- a/Plugins/Drivers/DriverSimTcpClient/DriverSimTcpClient.csproj +++ b/Plugins/Drivers/DriverSimTcpClient/DriverSimTcpClient.csproj @@ -11,5 +11,6 @@ + \ No newline at end of file diff --git a/Plugins/Drivers/DriverSimTcpClient/SimTcpClient.cs b/Plugins/Drivers/DriverSimTcpClient/SimTcpClient.cs index 892ed7d..6d67fcc 100644 --- a/Plugins/Drivers/DriverSimTcpClient/SimTcpClient.cs +++ b/Plugins/Drivers/DriverSimTcpClient/SimTcpClient.cs @@ -2,6 +2,8 @@ using SimpleTCP; using System; using System.Text; +using IoTGateway.Model; +using Microsoft.Extensions.Logging; namespace DriverSimTcpClient { @@ -17,6 +19,9 @@ namespace DriverSimTcpClient /// 缓存最新的服务器返回的原始数据 /// private byte[] latestRcvData; + + public ILogger _logger { get; set; } + private readonly Device _device; #region 配置参数 [ConfigParameter("设备Id")] @@ -42,9 +47,12 @@ namespace DriverSimTcpClient #endregion - public SimTcpClient(Guid deviceId) + public SimTcpClient(Device device, ILogger logger) { - DeviceId = deviceId; + _device = device; + _logger = logger; + + _logger.LogInformation($"Device:[{_device.DeviceName}],Create()"); } diff --git a/Plugins/Plugin/DeviceService.cs b/Plugins/Plugin/DeviceService.cs index c9ea792..3df256b 100644 --- a/Plugins/Plugin/DeviceService.cs +++ b/Plugins/Plugin/DeviceService.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.Configuration; using PluginInterface; using System.Net; using System.Reflection; +using System.Runtime.CompilerServices; using WalkingTec.Mvvm.Core; using IoTGateway.DataAccess; using IoTGateway.Model; @@ -83,8 +84,9 @@ namespace Plugin else { var settings = DC.Set().Where(x => x.DeviceId == Device.ID).AsNoTracking().ToList(); - Type[] types = new Type[1] { typeof(Guid) }; - object[] param = new object[1] { Device.ID }; + + Type[] types = new Type[2] { typeof(Device) ,typeof(ILogger) }; + object[] param = new object[2] { Device , _logger }; ConstructorInfo constructor = driver.Type.GetConstructor(types); var DeviceObj = constructor.Invoke(param) as IDriver; diff --git a/Plugins/PluginInterface/IDriver.cs b/Plugins/PluginInterface/IDriver.cs index 8a5baad..5a8beac 100644 --- a/Plugins/PluginInterface/IDriver.cs +++ b/Plugins/PluginInterface/IDriver.cs @@ -1,4 +1,5 @@ using System; +using Microsoft.Extensions.Logging; namespace PluginInterface { @@ -8,6 +9,10 @@ namespace PluginInterface public bool IsConnected { get; } public int Timeout { get; } public uint MinPeriod { get; } + + + public ILogger _logger { get; set; } + public bool Connect(); public bool Close(); //标准数据读取 diff --git a/Plugins/PluginInterface/PluginInterface.csproj b/Plugins/PluginInterface/PluginInterface.csproj index 4079917..c042b3a 100644 --- a/Plugins/PluginInterface/PluginInterface.csproj +++ b/Plugins/PluginInterface/PluginInterface.csproj @@ -8,6 +8,7 @@ + diff --git a/README.md b/README.md index 18da284..415c13d 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ > ## 交流 -| 公众号:工业物联网网关 | [QQ群:712105424](https://qm.qq.com/cgi-bin/qm/qr?k=e3Y8biyVdhDxx3LPbjvNY3TSNOEAmjp7&jump_from=webapi) | +| 公众号:工业物联网网关 | 微信群 | | ------ | ---- | | ![wx](./images/wx.jpg) | ![qq](./images/qq.png) | diff --git a/images/qq.png b/images/qq.png index 964ae65c36d65d7d862d29b56779968f62d5f93b..fad45980927685ed478f7ac10c8d040ca8a72615 100644 GIT binary patch literal 31550 zcmX6^cRZWl+pbc36s4p^2~t$49eXua#fVLfTD1vk6}8pgEn?M(y|!~roa)V-U1;@fU=Jn9)?e^Pk&T>PD4c$`A-`>KVz*+96C=QvsBc40>S?t|KEW!1d z)Xi9(EP^BHiTWsPej@PrT67`OfKsg#8M=5cxGVn}-9(>;{h!oUkN znbG8ftrnk$Nq^oXT%&oDl=@yQ?;AVPOx$T_Ba!{wn9t+6jP7UoCjauSv#y%5e9pYG z&b=lh!X!^}tYbf8fE1e#zTVZDT-DVK-CYk!{ak^}++JgU-FtG^4!I#|KOnKrItFLC zI@b_%_@d+(;b(^Jr-or7UoY+sDDDn+%qV=OhSd&s7jJj1@BiYww_b7guVSd}7CU8i zKbK>P#bb$eFM|Z8yF(@^9snJRv+~V7B zX4@rvx`2!>Du~Gdp6d=C@vHg8n|V2dZx~v*?Z(j2t0DLqp4tK4WDL@$87X$PnRUB) z{1Cm-zRcCOywjU?-8(cdj(ys9(?hpALkk(pH9^nl%a(TjG<@!FFxtq+-twlx`u=~C zZX|bmi#IJb!rr;UqFQi_hu^x#pz)fe@mgTZ)#Bal{aj=J6~;FIx1Mjjl|SgWx9+!h z@Y-+jhg*Wqk71qM!`#F|Op`ALT6_9E$OwxU&7fam={K4T|xyAj(a z4YfS{Idt{&>73D1w;lW-!KNzAcw7ofUttiI$Ui3aof&D|ns9vXw|`EaYdrTtkM8y$j1)Le-!vNMkNR-@6%#=9hmpbk}Oj5f{3Ok^l3`Y>U zW?jv#&kgmr?esGiEKp(uPJIiky$f80)vjvQe9rV@PxaQ92A3n-HX_L}|85rVZgAU{ zp0zFUPB1n-lJ^>z{5;UvEaK;fQSig;_?@>bGBYTRiM6eW8GjT(e*vgP_ty$-613jl zufMVLaktZT_fyoRNbIpln8?Ha_?h@<)FsmYAJUrQ^MANJU3CPaWj%g7J(988TD)V! zuWgM)^Es0^{LifYKjqkqzKS(oqA^~Qo#MxQt_Jy3lelCNs~tX3b3qv{gNeXpE|MET zk{jwp{s#p9&U`QFBHcsc8~@eP-rnxt-KtL=DkufjE>NlgzeOA^7_8h6lROyOSfl=B z=pt&!6D1{n3X?hwdB}hxv&?9Wd25V$-0r5_;KtE{346Js>r`a0S~<0{6rM&zYOwQ-g#tl@Q9~zQ(7E@Q8OPsC=J-}#f&qyUH7+N@9A~J zlQDTCf`+h6nxgjBMp`%(K$V_1LmZ!vKK?&?xPeJmyNE*Jm{IjNMC?^#tj~91{RXdp z?JXJsF8qO;CluGeD2x=d1g$u~>ha%&-5oHIFX1s=#xr>_XAv&+POtcFe)HROg$IV7 zvpXHJhl#2*isEe7q-DN1R5GDCwKzffDAI!{3Wh5#A}Tx;6_$&)ms-EGf!GYAeXMSL zc2Gn!-rQmO!y$P;|C@n8Y0SrNVUnw`K}qcHa3`L#B8CLlK%nX(qsR9Na((IZ4AOT7 zeAF&zoZN~L*!sDD)wVxfn4>D)Z{qN4DexZt@zDO;3sJ`JYSzKye*lrz^ybsqMp4(*}a7 zz$cca{6i?h|5VrC{p^p)^`9`l#|=j{p^ID3trVcE-~UlfzH!J9TXk5IqZ+J>PozDx z$y~D`A1lyTFRe`aN@yL4@ZRRRs^$^1pNZ+fiw-!e>NAGVJVC<(yKg(Ct~;Oh$p-Mf z8|La(4U16$s{X?~%Sl9jnFUOS`A&tsM?D>{{jH0=dsYvFf2PV3OJdzNwbUW?BGsoy zqYA-heYRIWnlEGT4y72kfR0^b0j!X!%sq2nIVek863&abc(*53mDU4Ak{obbeR~

d$yOU&b$G3k{d2Vfcbrz4Ewz(R^5 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 D^$KYu literal 2166 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 s*wwNB;S