diff --git a/Modbus.Net/Modbus.Net.HJ212/HJ212Protocol.cs b/Modbus.Net/Modbus.Net.HJ212/HJ212Protocol.cs index e704324..bc01a23 100644 --- a/Modbus.Net/Modbus.Net.HJ212/HJ212Protocol.cs +++ b/Modbus.Net/Modbus.Net.HJ212/HJ212Protocol.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Text; using System.Threading.Tasks; @@ -27,7 +26,7 @@ namespace Modbus.Net.HJ212 else { ProtocolLinker = new HJ212ProtocolLinker(ip, int.Parse(ConfigurationReader.GetValueDirect("TCP:" + ip, "HJ212Port") ?? ConfigurationReader.GetValueDirect("TCP:Modbus", "HJ212Port") ?? "443")); - } + } } /// diff --git a/Modbus.Net/Modbus.Net.HJ212/HJ212ProtocolLinkerBytesExtend.cs b/Modbus.Net/Modbus.Net.HJ212/HJ212ProtocolLinkerBytesExtend.cs index c006b2a..26a09c0 100644 --- a/Modbus.Net/Modbus.Net.HJ212/HJ212ProtocolLinkerBytesExtend.cs +++ b/Modbus.Net/Modbus.Net.HJ212/HJ212ProtocolLinkerBytesExtend.cs @@ -25,7 +25,7 @@ namespace Modbus.Net.HJ212 Array.Copy(lengthCalc, 0, newFormat, 0, 6); //Modbus/Rtu协议扩张,增加CRC校验 var crc = new byte[2]; - Crc16.GetInstance().GetCRC(content, ref crc); + Crc16.GetInstance().GetCRC(content, ref crc); string crcString = BitConverter.ToString(crc).Replace("-", string.Empty); crcString = "&&" + crcString; var crcCalc = Encoding.ASCII.GetBytes(crcString); diff --git a/Modbus.Net/Modbus.Net.Modbus/ModbusProtocolLinkerBytesExtend.cs b/Modbus.Net/Modbus.Net.Modbus/ModbusProtocolLinkerBytesExtend.cs index f8334e6..3b7f7cc 100644 --- a/Modbus.Net/Modbus.Net.Modbus/ModbusProtocolLinkerBytesExtend.cs +++ b/Modbus.Net/Modbus.Net.Modbus/ModbusProtocolLinkerBytesExtend.cs @@ -169,4 +169,9 @@ namespace Modbus.Net.Modbus return newContent.ToArray(); } } + + public class ModbusRtuProtocolReceiverBytesExtend : ModbusRtuProtocolLinkerBytesExtend + { + + } } \ No newline at end of file diff --git a/Modbus.Net/Modbus.Net.Modbus/ModbusRtuProtocolReceiver.cs b/Modbus.Net/Modbus.Net.Modbus/ModbusRtuProtocolReceiver.cs new file mode 100644 index 0000000..40ae8d8 --- /dev/null +++ b/Modbus.Net/Modbus.Net.Modbus/ModbusRtuProtocolReceiver.cs @@ -0,0 +1,34 @@ +using System; + +namespace Modbus.Net.Modbus +{ + public class ModbusRtuProtocolReceiver : ProtocolReceiver + { + public ModbusRtuProtocolReceiver(string com, int slaveAddress) + : base(com, slaveAddress) + { + + } + + protected override Func DataExplain + { + get + { + return receiveBytes => + { + var writeContent = receiveBytes.Length > 6 ? new byte[receiveBytes.Length - 7] : null; + if (receiveBytes.Length > 6) Array.Copy(receiveBytes, 7, writeContent, 0, receiveBytes.Length - 7); + return new ReceiveDataDef() + { + SlaveAddress = receiveBytes[0], + FunctionCode = receiveBytes[1], + StartAddress = (ushort)(receiveBytes[2] * 256 + receiveBytes[3]), + Count = (ushort)(receiveBytes[4] * 256 + receiveBytes[5]), + WriteByteCount = (byte)(receiveBytes.Length > 6 ? receiveBytes[6] : 0), + WriteContent = writeContent + }; + }; + } + } + } +} \ No newline at end of file diff --git a/Modbus.Net/Modbus.Net.Modbus/ModbusUtility.cs b/Modbus.Net/Modbus.Net.Modbus/ModbusUtility.cs index b7293ce..d13f8b3 100644 --- a/Modbus.Net/Modbus.Net.Modbus/ModbusUtility.cs +++ b/Modbus.Net/Modbus.Net.Modbus/ModbusUtility.cs @@ -292,12 +292,13 @@ namespace Modbus.Net.Modbus var outputStruct = await Wrapper.SendReceiveAsync(Wrapper[typeof(WriteDataModbusProtocol)], inputStruct); + var ans = outputStruct?.WriteCount * 2 == BigEndianLsbValueHelper.Instance.ObjectArrayToByteArray(setContents).Length; return new ReturnStruct() { - Datas = outputStruct?.WriteCount == setContents.Length, - IsSuccess = outputStruct?.WriteCount == setContents.Length, - ErrorCode = outputStruct?.WriteCount == setContents.Length ? 0 : -2, - ErrorMsg = outputStruct?.WriteCount == setContents.Length ? "" : "Data length mismatch" + Datas = ans, + IsSuccess = ans, + ErrorCode = ans ? 0 : -2, + ErrorMsg = ans ? "" : "Data length mismatch" }; } catch (ModbusProtocolErrorException e) diff --git a/Modbus.Net/Modbus.Net.Modbus/ModbusUtilityServer.cs b/Modbus.Net/Modbus.Net.Modbus/ModbusUtilityServer.cs new file mode 100644 index 0000000..5c4c258 --- /dev/null +++ b/Modbus.Net/Modbus.Net.Modbus/ModbusUtilityServer.cs @@ -0,0 +1,7 @@ +namespace Modbus.Net.Modbus +{ + public class ModbusUtilityServer + { + + } +} diff --git a/Modbus.Net/Modbus.Net.sln b/Modbus.Net/Modbus.Net.sln index 5b176af..f3312da 100644 --- a/Modbus.Net/Modbus.Net.sln +++ b/Modbus.Net/Modbus.Net.sln @@ -49,7 +49,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Modbus.Net.HJ212", "Modbus. EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Modbus.Net.CodeGenerator", "Modbus.Net.CodeGenerator\Modbus.Net.CodeGenerator.csproj", "{D3210531-BA79-49B2-9F99-09FD0E1627B6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MachineJob.CodeGenerator", "..\Samples\MachineJob.CodeGenerator\MachineJob.CodeGenerator.csproj", "{7337BC9A-ED07-463D-8FCD-A82896CEC6BE}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MachineJob.CodeGenerator", "..\Samples\MachineJob.CodeGenerator\MachineJob.CodeGenerator.csproj", "{7337BC9A-ED07-463D-8FCD-A82896CEC6BE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ModbusTcpToRtu", "..\Samples\ModbusTcpToRtu\ModbusTcpToRtu.csproj", "{9CA7E35C-B5BC-4743-8732-1D8912AA12D8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleModbusRtuServer", "..\Samples\SampleModbusRtuServer\SampleModbusRtuServer.csproj", "{CB85D4B3-EEA2-431F-A47F-1DE7FB0A4802}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -211,6 +215,22 @@ Global {7337BC9A-ED07-463D-8FCD-A82896CEC6BE}.Release|Any CPU.Build.0 = Release|Any CPU {7337BC9A-ED07-463D-8FCD-A82896CEC6BE}.Release|x64.ActiveCfg = Release|Any CPU {7337BC9A-ED07-463D-8FCD-A82896CEC6BE}.Release|x64.Build.0 = Release|Any CPU + {9CA7E35C-B5BC-4743-8732-1D8912AA12D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9CA7E35C-B5BC-4743-8732-1D8912AA12D8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9CA7E35C-B5BC-4743-8732-1D8912AA12D8}.Debug|x64.ActiveCfg = Debug|Any CPU + {9CA7E35C-B5BC-4743-8732-1D8912AA12D8}.Debug|x64.Build.0 = Debug|Any CPU + {9CA7E35C-B5BC-4743-8732-1D8912AA12D8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9CA7E35C-B5BC-4743-8732-1D8912AA12D8}.Release|Any CPU.Build.0 = Release|Any CPU + {9CA7E35C-B5BC-4743-8732-1D8912AA12D8}.Release|x64.ActiveCfg = Release|Any CPU + {9CA7E35C-B5BC-4743-8732-1D8912AA12D8}.Release|x64.Build.0 = Release|Any CPU + {CB85D4B3-EEA2-431F-A47F-1DE7FB0A4802}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CB85D4B3-EEA2-431F-A47F-1DE7FB0A4802}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CB85D4B3-EEA2-431F-A47F-1DE7FB0A4802}.Debug|x64.ActiveCfg = Debug|Any CPU + {CB85D4B3-EEA2-431F-A47F-1DE7FB0A4802}.Debug|x64.Build.0 = Debug|Any CPU + {CB85D4B3-EEA2-431F-A47F-1DE7FB0A4802}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CB85D4B3-EEA2-431F-A47F-1DE7FB0A4802}.Release|Any CPU.Build.0 = Release|Any CPU + {CB85D4B3-EEA2-431F-A47F-1DE7FB0A4802}.Release|x64.ActiveCfg = Release|Any CPU + {CB85D4B3-EEA2-431F-A47F-1DE7FB0A4802}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -222,6 +242,8 @@ Global {AA3A42D2-0502-41D3-929A-BAB729DF07D6} = {3597B5C5-45B9-4ECB-92A3-D0FFBE47920A} {414956B8-DBD4-414C-ABD3-565580739646} = {3597B5C5-45B9-4ECB-92A3-D0FFBE47920A} {7337BC9A-ED07-463D-8FCD-A82896CEC6BE} = {3597B5C5-45B9-4ECB-92A3-D0FFBE47920A} + {9CA7E35C-B5BC-4743-8732-1D8912AA12D8} = {3597B5C5-45B9-4ECB-92A3-D0FFBE47920A} + {CB85D4B3-EEA2-431F-A47F-1DE7FB0A4802} = {3597B5C5-45B9-4ECB-92A3-D0FFBE47920A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {AF00D64E-3C70-474A-8A81-E9E48017C4B5} diff --git a/Modbus.Net/Modbus.Net/Configuration/ConfigurationReader.cs b/Modbus.Net/Modbus.Net/Configuration/ConfigurationReader.cs index 6829f81..fd670a4 100644 --- a/Modbus.Net/Modbus.Net/Configuration/ConfigurationReader.cs +++ b/Modbus.Net/Modbus.Net/Configuration/ConfigurationReader.cs @@ -41,6 +41,20 @@ namespace Modbus.Net return ans; } + public static ContentType? GetContent(string path, string key) where ContentType : class + { + var root = configuration.GetSection("Modbus.Net"); + var firstColon = path.IndexOf(":"); + while (firstColon != -1) + { + root = root?.GetSection(path.Substring(0, firstColon)); + path = path.Substring(firstColon + 1); + firstColon = path.IndexOf(":"); + } + root = root?.GetSection(path); + return root?.GetSection(key).Get(); + } + /// /// 根据路径,直接查找路径上是否有该元素 /// diff --git a/Modbus.Net/Modbus.Net/Connector/BaseConnector.cs b/Modbus.Net/Modbus.Net/Connector/BaseConnector.cs index 5f83e7a..3936851 100644 --- a/Modbus.Net/Modbus.Net/Connector/BaseConnector.cs +++ b/Modbus.Net/Modbus.Net/Connector/BaseConnector.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.Logging; +using System; using System.Threading.Tasks; namespace Modbus.Net @@ -14,18 +15,10 @@ namespace Modbus.Net /// public abstract class BaseConnector : IConnectorWithController where TParamIn : class { - /// - /// 数据返回代理参数 - /// - /// - /// - /// - public delegate MessageReturnCallbackArgs MessageReturnDelegate(object sender, MessageReturnArgs args); - /// /// 数据返回代理 /// - public event MessageReturnDelegate MessageReturn; + public Func, MessageReturnCallbackArgs> MessageReturn { get; set; } /// public void AddController(IController controller) @@ -76,7 +69,7 @@ namespace Modbus.Net /// protected TParamIn InvokeReturnMessage(TParamOut receiveMessage) { - return MessageReturn?.Invoke(this, new MessageReturnArgs { ReturnMessage = receiveMessage })?.SendMessage; + return MessageReturn?.Invoke(new MessageReturnArgs { ReturnMessage = receiveMessage })?.SendMessage; } } } \ No newline at end of file diff --git a/Modbus.Net/Modbus.Net/Connector/ComConnector.cs b/Modbus.Net/Modbus.Net/Connector/ComConnector.cs index 5a149f7..1ed5696 100644 --- a/Modbus.Net/Modbus.Net/Connector/ComConnector.cs +++ b/Modbus.Net/Modbus.Net/Connector/ComConnector.cs @@ -278,7 +278,7 @@ namespace Modbus.Net } // Release unmanaged resources Controller?.SendStop(); - ReceiveMsgThreadStop(); + ReceiveMsgThreadStop(); Linkers?.Remove((_slave, _com)); logger.LogInformation("Com connector {ConnectionToken} Removed", _com); if (Linkers?.Count(p => p.Item2 == _com) == 0) @@ -288,12 +288,12 @@ namespace Modbus.Net SerialPort?.Close(); } SerialPort?.Dispose(); - logger.LogInformation("Com interface {Com} Disposed", _com); + logger.LogInformation("Com interface {Com} Disposed", _com); if (Connectors.ContainsKey(_com)) { Connectors[_com] = null; Connectors.Remove(_com); - } + } } } @@ -500,7 +500,7 @@ namespace Modbus.Net if (_receiveThread == null) { _receiveThreadCancel = new CancellationTokenSource(); - _receiveThread = Task.Run(async ()=>await ReceiveMessage(_receiveThreadCancel.Token), _receiveThreadCancel.Token); + _receiveThread = Task.Run(async () => await ReceiveMessage(_receiveThreadCancel.Token), _receiveThreadCancel.Token); try { await _receiveThread; diff --git a/Modbus.Net/Modbus.Net/Connector/EventHandlerConnector.cs b/Modbus.Net/Modbus.Net/Connector/EventHandlerConnector.cs index 5447680..9eea657 100644 --- a/Modbus.Net/Modbus.Net/Connector/EventHandlerConnector.cs +++ b/Modbus.Net/Modbus.Net/Connector/EventHandlerConnector.cs @@ -32,18 +32,10 @@ namespace Modbus.Net /// public abstract class EventHandlerConnector : ChannelHandlerAdapter, IConnectorWithController where TParamIn : class { - /// - /// 数据返回代理参数 - /// - /// - /// - /// - public delegate MessageReturnCallbackArgs MessageReturnDelegate(object sender, MessageReturnArgs args); - /// /// 数据返回代理 /// - public event MessageReturnDelegate MessageReturn; + public Func, MessageReturnCallbackArgs> MessageReturn { get; set; } /// public void AddController(IController controller) @@ -84,7 +76,7 @@ namespace Modbus.Net /// protected TParamIn InvokeReturnMessage(TParamOut receiveMessage) { - return MessageReturn?.Invoke(this, new MessageReturnArgs { ReturnMessage = receiveMessage })?.SendMessage; + return MessageReturn?.Invoke(new MessageReturnArgs { ReturnMessage = receiveMessage })?.SendMessage; } } } \ No newline at end of file diff --git a/Modbus.Net/Modbus.Net/Controller/BaseController.cs b/Modbus.Net/Modbus.Net/Controller/BaseController.cs index bf784f6..a96a3de 100644 --- a/Modbus.Net/Modbus.Net/Controller/BaseController.cs +++ b/Modbus.Net/Modbus.Net/Controller/BaseController.cs @@ -1,4 +1,3 @@ -using Quartz.Logging; using System; using System.Collections.Generic; using System.Linq; @@ -106,8 +105,8 @@ namespace Modbus.Net finally { _sendingThreadCancel.Dispose(); - _sendingThreadCancel = null; - } + _sendingThreadCancel = null; + } } } diff --git a/Modbus.Net/Modbus.Net/Controller/NoResponseController.cs b/Modbus.Net/Modbus.Net/Controller/NoResponseController.cs index 5b686ef..ef90627 100644 --- a/Modbus.Net/Modbus.Net/Controller/NoResponseController.cs +++ b/Modbus.Net/Modbus.Net/Controller/NoResponseController.cs @@ -1,5 +1,4 @@ using Microsoft.Extensions.Logging; -using Quartz.Logging; using System; using System.Collections.Generic; using System.Linq; diff --git a/Modbus.Net/Modbus.Net/Helper/ControllerHelper.cs b/Modbus.Net/Modbus.Net/Helper/ControllerHelper.cs index d9942bf..79428f6 100644 --- a/Modbus.Net/Modbus.Net/Helper/ControllerHelper.cs +++ b/Modbus.Net/Modbus.Net/Helper/ControllerHelper.cs @@ -36,5 +36,34 @@ namespace Modbus.Net } throw new NotImplementedException(controllerName + " not found exception"); } + + /// + /// 添加一个Controller + /// + /// ProtocolLinker实例 + /// 参数 + /// Connector实例 + /// 如果没有发现控制器,报错 + public static void AddController(this IProtocolReceiver protocolReceiver, object[] constructorParams, IConnector connector) + { + IController controller = null; + var assemblies = AssemblyHelper.GetAllLibraryAssemblies(); + string controllerName = protocolReceiver.GetType().Name.Substring(0, protocolReceiver.GetType().Name.Length - 16) + "ResponseController"; + foreach (var assembly in assemblies) + { + var controllerType = assembly.GetType(assembly.GetName().Name + "." + controllerName); + if (controllerType != null) + { + controller = assembly.CreateInstance(controllerType.FullName, true, BindingFlags.Default, null, constructorParams, null, null) as IController; + break; + } + } + if (controller != null) + { + ((IConnectorWithController)connector).AddController(controller); + return; + } + throw new NotImplementedException(controllerName + " not found exception"); + } } } diff --git a/Modbus.Net/Modbus.Net/Interface/IConnector.cs b/Modbus.Net/Modbus.Net/Interface/IConnector.cs index d570a3a..48cf3dc 100644 --- a/Modbus.Net/Modbus.Net/Interface/IConnector.cs +++ b/Modbus.Net/Modbus.Net/Interface/IConnector.cs @@ -1,3 +1,4 @@ +using System; using System.Threading.Tasks; namespace Modbus.Net @@ -5,8 +6,13 @@ namespace Modbus.Net /// /// Эӽӿ /// - public interface IConnector + public interface IConnector { + /// + /// ݷش + /// + Func, MessageReturnCallbackArgs> MessageReturn { get; set; } + /// /// ʶConnectorӹؼ /// diff --git a/Modbus.Net/Modbus.Net/Interface/IConnectorWithController.cs b/Modbus.Net/Modbus.Net/Interface/IConnectorWithController.cs index 4fab028..2575da5 100644 --- a/Modbus.Net/Modbus.Net/Interface/IConnectorWithController.cs +++ b/Modbus.Net/Modbus.Net/Interface/IConnectorWithController.cs @@ -3,7 +3,7 @@ /// /// 基础的协议连接接口 /// - public interface IConnectorWithController : IConnector + public interface IConnectorWithController : IConnector { /// /// 增加传输控制器 diff --git a/Modbus.Net/Modbus.Net/Interface/IMachineServerMethod.cs b/Modbus.Net/Modbus.Net/Interface/IMachineServerMethod.cs new file mode 100644 index 0000000..db400a6 --- /dev/null +++ b/Modbus.Net/Modbus.Net/Interface/IMachineServerMethod.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Modbus.Net.Interface +{ + /// + /// Machine的数据读写接口 + /// + public interface IMachineServerMethodDatas : IMachineMethod + { + /// + /// 从站发送事件 + /// + event EventHandler ServerMessageEvent; + + /// + /// 读取数据 + /// + /// 从设备读取的数据 + Task>>> ServerReadDatasAsync(MachineDataType getDataType); + + /// + /// 写入数据 + /// + /// 写入类型 + /// 需要写入的数据字典,当写入类型为Address时,键为需要写入的地址,当写入类型为CommunicationTag时,键为需要写入的单元的描述 + /// 是否写入成功 + Task> ServerUploadDatasAsync(MachineDataType setDataType, Dictionary values); + } +} diff --git a/Modbus.Net/Modbus.Net/Interface/IProtocolReceiver.cs b/Modbus.Net/Modbus.Net/Interface/IProtocolReceiver.cs new file mode 100644 index 0000000..9c00d97 --- /dev/null +++ b/Modbus.Net/Modbus.Net/Interface/IProtocolReceiver.cs @@ -0,0 +1,61 @@ +using System; +using System.Threading.Tasks; + +namespace Modbus.Net +{ + /// + /// 协议接收器接口 + /// + /// 从Receiver传出的数据类型 + /// 向Receiver传入的数据类型 + public interface IProtocolReceiver + { + /// + /// 转发事件 + /// + Func DispatchEvent { get; } + + /// + /// 通讯字符串 + /// + string ConnectionToken { get; } + + /// + /// 设备是否连接 + /// + bool IsConnected { get; } + + /// + /// 连接设备 + /// + /// 设备是否连接成功 + Task ConnectAsync(); + + /// + /// 断开设备 + /// + /// 设备是否断开成功 + bool Disconnect(); + + /// + /// 接收并发送数据 + /// + /// 接收协议的内容 + /// 发送协议的内容 + TParamIn ReceiveSend(TParamOut content); + + /// + /// 接收并发送数据,不进行协议扩展和收缩,用于特殊协议 + /// + /// 发送协议的内容 + /// 接收协议的内容 + TParamIn ReceiveSendWithoutExtAndDec(TParamOut content); + + /// + /// 检查接收的数据是否正确 + /// + /// 接收协议的内容 + /// 协议是否是正确的 + bool? CheckRight(TParamOut content); + } +} \ No newline at end of file diff --git a/Modbus.Net/Modbus.Net/Interface/IUtilityServer.cs b/Modbus.Net/Modbus.Net/Interface/IUtilityServer.cs new file mode 100644 index 0000000..5b48f40 --- /dev/null +++ b/Modbus.Net/Modbus.Net/Interface/IUtilityServer.cs @@ -0,0 +1,9 @@ +namespace Modbus.Net +{ + /// + /// Api入口的抽象 + /// + public interface IUtilityServer : IUtilityProperty, IUtilityServerMethodDatas + { + } +} \ No newline at end of file diff --git a/Modbus.Net/Modbus.Net/Interface/IUtilityServerMethod.cs b/Modbus.Net/Modbus.Net/Interface/IUtilityServerMethod.cs new file mode 100644 index 0000000..0befef8 --- /dev/null +++ b/Modbus.Net/Modbus.Net/Interface/IUtilityServerMethod.cs @@ -0,0 +1,33 @@ +using System.Threading.Tasks; + +namespace Modbus.Net +{ + /// + /// Utility方法读写接口 + /// + public interface IUtilityServerMethod + { + } + + /// + /// Utility的数据读写接口 + /// + public interface IUtilityServerMethodDatas : IUtilityServerMethod + { + /// + /// 获取数据 + /// + /// 开始地址 + /// 获取字节数个数 + /// 接收到的byte数据 + Task> GetServerDatasAsync(string startAddress, int getByteCount); + + /// + /// 设置数据 + /// + /// 开始地址 + /// 设置数据 + /// 是否设置成功 + Task> SetServerDatasAsync(string startAddress, object[] setContents); + } +} diff --git a/Modbus.Net/Modbus.Net/Linker/ProtocolReceiver.cs b/Modbus.Net/Modbus.Net/Linker/ProtocolReceiver.cs new file mode 100644 index 0000000..009aa0f --- /dev/null +++ b/Modbus.Net/Modbus.Net/Linker/ProtocolReceiver.cs @@ -0,0 +1,216 @@ +using System; +using System.IO.Ports; +using System.Reflection; +using System.Threading.Tasks; + +namespace Modbus.Net +{ + /// + /// 基本的协议连接器 + /// + public abstract class ProtocolReceiver : ProtocolReceiver + { + protected ProtocolReceiver(string com, int slaveAddress, BaudRate? baudRate = null, Parity? parity = null, StopBits? stopBits = null, DataBits? dataBits = null, Handshake? handshake = null, + int? connectionTimeout = null, bool? isFullDuplex = null) + { + baudRate = Enum.Parse(baudRate != null ? baudRate.ToString() : null ?? ConfigurationReader.GetValue("COM:" + com, "BaudRate")); + parity = Enum.Parse(parity != null ? parity.ToString() : null ?? ConfigurationReader.GetValue("COM:" + com, "Parity")); + stopBits = Enum.Parse(stopBits != null ? stopBits.ToString() : null ?? ConfigurationReader.GetValue("COM:" + com, "StopBits")); + dataBits = Enum.Parse(dataBits != null ? dataBits.ToString() : null ?? ConfigurationReader.GetValue("COM:" + com, "DataBits")); + handshake = Enum.Parse(handshake != null ? handshake.ToString() : null ?? ConfigurationReader.GetValue("COM:" + com, "Handshake")); + connectionTimeout = int.Parse(connectionTimeout != null ? connectionTimeout.ToString() : null ?? ConfigurationReader.GetValue("COM:" + com, "ConnectionTimeout")); + isFullDuplex = bool.Parse(isFullDuplex != null ? isFullDuplex.ToString() : null ?? ConfigurationReader.GetValue("COM:" + com, "FullDuplex")); + BaseConnector = new ComConnector(com + ":" + slaveAddress, baudRate.Value, parity.Value, stopBits.Value, dataBits.Value, handshake.Value, connectionTimeout.Value, isFullDuplex.Value); + var noResponse = bool.Parse(ConfigurationReader.GetValue("COM:" + com, "NoResponse") ?? ConfigurationReader.GetValue("Controller", "NoResponse")); + if (noResponse) + { + ((IConnectorWithController)BaseConnector).AddController(new NoResponseController(int.Parse(ConfigurationReader.GetValue("COM:" + com + ":" + slaveAddress, "FetchSleepTime")))); + } + else + { + this.AddController(new object[2] { com, slaveAddress }, BaseConnector); + } + BaseConnector.MessageReturn = receiveMessage => new MessageReturnCallbackArgs() { SendMessage = ReceiveSend(receiveMessage.ReturnMessage) }; + } + + /// + /// 发送并接收数据 + /// + /// 发送协议的内容 + /// 接收协议的内容 + public override byte[] ReceiveSend(byte[] content) + { + var checkRight = CheckRight(content); + if (checkRight == true) + { + var decBytes = BytesDecact(content); + var explainContent = DataExplain(decBytes); + var returnBytes = DataProcess(explainContent); + var extBytes = BytesExtend(returnBytes); + return extBytes; + } + else + { + return null; + } + } + + /// + /// 协议内容扩展,发送时根据需要扩展 + /// + /// 扩展前的基本协议内容 + /// 扩展后的协议内容 + public virtual byte[] BytesExtend(byte[] content) + { + //自动查找相应的协议放缩类,命令规则为——当前的实际类名(注意是继承后的)+"BytesExtend"。 + var bytesExtend = + Activator.CreateInstance(GetType().GetTypeInfo().Assembly.GetType(GetType().FullName + "BytesExtend")) + as + IProtocolLinkerBytesExtend; + return bytesExtend?.BytesExtend(content); + } + + /// + /// 协议内容缩减,接收时根据需要缩减 + /// + /// 缩减前的完整协议内容 + /// 缩减后的协议内容 + public virtual byte[] BytesDecact(byte[] content) + { + //自动查找相应的协议放缩类,命令规则为——当前的实际类名(注意是继承后的)+"BytesExtend"。 + var bytesExtend = + Activator.CreateInstance(GetType().GetTypeInfo().Assembly.GetType(GetType().FullName + "BytesExtend")) + as + IProtocolLinkerBytesExtend; + return bytesExtend?.BytesDecact(content); + } + + /// + /// 检查接收的数据是否正确 + /// + /// 接收协议的内容 + /// 协议是否是正确的 + public override bool? CheckRight(byte[] content) + { + if (content == null) + { + Disconnect(); + return false; + } + if (content.Length == 0) return null; + return true; + } + + protected abstract Func DataExplain { get; } + + public Func DataProcess { get; set; } = null; + } + + public abstract class ProtocolReceiver : IProtocolReceiver + where TParamIn : class + { + /// + /// 传输连接器 + /// + protected IConnector BaseConnector; + + /// + /// 连接设备 + /// + /// 设备是否连接成功 + public async Task ConnectAsync() + { + return await BaseConnector.ConnectAsync(); + } + + /// + /// 断开设备 + /// + /// 设备是否断开成功 + public bool Disconnect() + { + return BaseConnector.Disconnect(); + } + + /// + /// 通讯字符串 + /// + public string ConnectionToken => BaseConnector.ConnectionToken; + + /// + /// 设备是否连接 + /// + public bool IsConnected => BaseConnector != null && BaseConnector.IsConnected; + + public virtual Func DispatchEvent + { + get + { + return receiveContent => ReceiveSend(receiveContent); + } + } + + /// + /// 发送并接收数据 + /// + /// 发送协议的内容 + /// 接收协议的内容 + public virtual TParamIn ReceiveSend(TParamOut content) + { + return ReceiveSendWithoutExtAndDec(content); + } + + /// + /// 发送并接收数据,不进行协议扩展和收缩,用于特殊协议 + /// + /// 发送协议的内容 + /// 接收协议的内容 + public virtual TParamIn ReceiveSendWithoutExtAndDec(TParamOut content) + { + var checkRight = CheckRight(content); + if (checkRight == true) + { + if (DispatchEvent != null) + { + var returnContent = DispatchEvent(content); + return returnContent; + } + else + { + return null; + } + } + else + { + return null; + } + } + + /// + /// 检查接收的数据是否正确 + /// + /// 接收协议的内容 + /// 协议是否是正确的 + public virtual bool? CheckRight(TParamOut content) + { + if (content != null) return true; + Disconnect(); + return false; + } + } + + public class ReceiveDataDef + { + public byte SlaveAddress { get; set; } + + public byte FunctionCode { get; set; } + + public ushort StartAddress { get; set; } + + public ushort Count { get; set; } + + public byte WriteByteCount { get; set; } + + public byte[] WriteContent { get; set; } + } +} diff --git a/Modbus.Net/Modbus.Net/Machine/BaseServer.cs b/Modbus.Net/Modbus.Net/Machine/BaseServer.cs new file mode 100644 index 0000000..f4f04a2 --- /dev/null +++ b/Modbus.Net/Modbus.Net/Machine/BaseServer.cs @@ -0,0 +1,6 @@ +namespace Modbus.Net.Machine +{ + public class BaseServer + { + } +} diff --git a/Modbus.Net/Modbus.Net/Modbus.Net.csproj b/Modbus.Net/Modbus.Net/Modbus.Net.csproj index 0be88e2..6e1eca0 100644 --- a/Modbus.Net/Modbus.Net/Modbus.Net.csproj +++ b/Modbus.Net/Modbus.Net/Modbus.Net.csproj @@ -39,6 +39,7 @@ + diff --git a/Modbus.Net/Modbus.Net/Utility/BaseUtilityServer.cs b/Modbus.Net/Modbus.Net/Utility/BaseUtilityServer.cs new file mode 100644 index 0000000..396dc41 --- /dev/null +++ b/Modbus.Net/Modbus.Net/Utility/BaseUtilityServer.cs @@ -0,0 +1,120 @@ +using Microsoft.Extensions.Logging; +using System.Threading.Tasks; + +namespace Modbus.Net +{ + /// + /// 基础Api入口 + /// + public abstract class BaseUtilityServer : IUtilityServer + where TProtocolUnit : class, IProtocolFormatting where TParamOut : class + where TPipeUnit : PipeUnit, TProtocolUnit> + { + private static readonly ILogger> logger = LogProvider.CreateLogger>(); + + /// + /// 协议收发主体 + /// + protected IProtocol Wrapper; + + /// + /// 构造器 + /// + protected BaseUtilityServer(byte slaveAddress, byte masterAddress) + { + SlaveAddress = slaveAddress; + MasterAddress = masterAddress; + AddressTranslator = new AddressTranslatorBase(); + } + + /// + /// 连接字符串 + /// + protected string ConnectionString { get; set; } + + /// + /// 从站号 + /// + public byte SlaveAddress { get; set; } + + /// + /// 主站号 + /// + public byte MasterAddress { get; set; } + + /// + /// 获取数据 + /// + /// 开始地址 + /// 获取字节数个数 + /// 接收到的byte数据 + public abstract Task> GetServerDatasAsync(string startAddress, int getByteCount); + + /// + /// 设置数据 + /// + /// 开始地址 + /// 设置数据 + /// 是否设置成功 + public abstract Task> SetServerDatasAsync(string startAddress, object[] setContents); + + /// + /// 协议是否遵循小端格式 + /// + public abstract Endian Endian { get; } + + /// + /// 设备是否已经连接 + /// + public bool IsConnected => Wrapper?.ProtocolLinker != null && Wrapper.ProtocolLinker.IsConnected; + + /// + /// 标识设备的连接关键字 + /// + public string ConnectionToken + => Wrapper?.ProtocolLinker == null ? ConnectionString : Wrapper.ProtocolLinker.ConnectionToken; + + /// + /// 地址翻译器 + /// + public AddressTranslator AddressTranslator { get; set; } + + /// + /// 连接设备 + /// + /// 设备是否连接成功 + public async Task ConnectAsync() + { + return await Wrapper.ConnectAsync(); + } + + /// + /// 断开设备 + /// + /// 设备是否断开成功 + public bool Disconnect() + { + return Wrapper.Disconnect(); + } + + /// + /// 返回Utility的方法集合 + /// + /// Utility方法集合类型 + /// Utility方法集合 + public TUtilityMethod GetUtilityMethods() where TUtilityMethod : class, IUtilityMethod + { + if (this is TUtilityMethod) + { + return this as TUtilityMethod; + } + return null; + } + + /// + /// 设置连接类型 + /// + /// 连接类型 + public abstract void SetConnectionType(int connectionType); + } +} \ No newline at end of file diff --git a/Samples/MachineJob/Worker.cs b/Samples/MachineJob/Worker.cs index 3eca9c2..1b6cb03 100644 --- a/Samples/MachineJob/Worker.cs +++ b/Samples/MachineJob/Worker.cs @@ -135,15 +135,15 @@ namespace MachineJob.Service return values.MapGetValuesToSetValues(); } - else if(dataReturnDef.ReturnValues.IsSuccess == null) + else if (dataReturnDef.ReturnValues.IsSuccess == null) { Random r = new Random(); Dictionary ans = new Dictionary(); - for (int i = 0; i< 10; i++) + for (int i = 0; i < 10; i++) { - ans["Test" + (i+1)] = r.Next(65536) - 32768; + ans["Test" + (i + 1)] = r.Next(65536) - 32768; } return ans; @@ -153,7 +153,7 @@ namespace MachineJob.Service _logger.LogError(dataReturnDef.MachineId + " Return Error."); return null; } - + } } } \ No newline at end of file diff --git a/Samples/ModbusTcpToRtu/ConsoleLogProvider.cs b/Samples/ModbusTcpToRtu/ConsoleLogProvider.cs new file mode 100644 index 0000000..f2507e0 --- /dev/null +++ b/Samples/ModbusTcpToRtu/ConsoleLogProvider.cs @@ -0,0 +1,36 @@ +using Quartz.Logging; + +namespace ModbusTcpToRtu +{ + // simple log provider to get something to the console + public class ConsoleLogProvider : ILogProvider + { + private readonly IConfigurationRoot configuration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json") + .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? "Production"}.json", true) + .Build(); + + public Logger GetLogger(string name) + { + return (level, func, exception, parameters) => + { + if (level >= configuration.GetSection("Quartz").GetValue("LogLevel") && func != null) + { + Console.WriteLine("[" + DateTime.Now.ToLongTimeString() + "] [" + level + "] " + func(), parameters); + } + return true; + }; + } + + public IDisposable OpenNestedContext(string message) + { + throw new NotImplementedException(); + } + + public IDisposable OpenMappedContext(string key, object value, bool destructure = false) + { + throw new NotImplementedException(); + } + } +} diff --git a/Samples/ModbusTcpToRtu/ModbusTcpToRtu.csproj b/Samples/ModbusTcpToRtu/ModbusTcpToRtu.csproj new file mode 100644 index 0000000..573e877 --- /dev/null +++ b/Samples/ModbusTcpToRtu/ModbusTcpToRtu.csproj @@ -0,0 +1,23 @@ + + + + net6.0 + enable + enable + dotnet-ModbusTcpToRtu-b7b7d9ed-80ce-4790-86de-5c3cf21e0a2e + + + + + + + + + + + + + + + + diff --git a/Samples/ModbusTcpToRtu/Program.cs b/Samples/ModbusTcpToRtu/Program.cs new file mode 100644 index 0000000..9861b2a --- /dev/null +++ b/Samples/ModbusTcpToRtu/Program.cs @@ -0,0 +1,38 @@ +using ModbusTcpToRtu; +using Serilog; + +IHost host = Host.CreateDefaultBuilder(args).UseWindowsService() + .ConfigureAppConfiguration((hostingContext, config) => + { + var configuration = config + .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) + .AddJsonFile("appsettings.json") + .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? "Production"}.json", true) + .AddEnvironmentVariables() + .Build(); + + Directory.SetCurrentDirectory(hostingContext.HostingEnvironment.ContentRootPath); + + Log.Logger = new LoggerConfiguration() + .ReadFrom.Configuration(configuration) + .Enrich.FromLogContext() + .WriteTo.Console() + .WriteTo.File("Log\\log..txt", Serilog.Events.LogEventLevel.Error, shared: true, rollingInterval: RollingInterval.Day) + .CreateLogger(); + + var loggerFactory = new LoggerFactory().AddSerilog(Log.Logger); + + Quartz.Logging.LogProvider.SetCurrentLogProvider(new ConsoleLogProvider()); + Modbus.Net.LogProvider.SetLogProvider(loggerFactory); + } + ) + .ConfigureServices(services => + { + services.AddHostedService(); + + services.AddLogging(loggingBuilder => loggingBuilder.AddSerilog(Log.Logger, true)); + }) + .Build(); + +await host.RunAsync(); + diff --git a/Samples/ModbusTcpToRtu/Worker.cs b/Samples/ModbusTcpToRtu/Worker.cs new file mode 100644 index 0000000..29895a6 --- /dev/null +++ b/Samples/ModbusTcpToRtu/Worker.cs @@ -0,0 +1,163 @@ +using Modbus.Net; +using Modbus.Net.Modbus; +using Quartz; +using Quartz.Impl; +using Quartz.Impl.Matchers; +using BaseUtility = Modbus.Net.BaseUtility, Modbus.Net.PipeUnit>; +using MultipleMachinesJobScheduler = Modbus.Net.MultipleMachinesJobScheduler; + +namespace ModbusTcpToRtu +{ + public class Worker : BackgroundService + { + private readonly ILogger _logger; + + private BaseUtility readUtility; + + private BaseUtility writeUtility; + + public Worker(ILogger logger) + { + _logger = logger; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + var triggerKey = "Modbus.Net.Job.Utility.SchedulerTrigger"; + var jobKey = "Modbus.Net.Job.Utility.JobKey"; + + var intervalMilliSecond = int.Parse(ConfigurationReader.GetValue("Utility", "interval")) * 1000; + var count = int.Parse(ConfigurationReader.GetValue("Utility", "count")); + + var readWriteGroup = ConfigurationReader.GetContent>("Utility", "readwrite"); + + var readType = Enum.Parse(ConfigurationReader.GetValue("Utility:read", "type")); + var readAddress = ConfigurationReader.GetValue("Utility:read", "address"); + var readSlaveAddress = byte.Parse(ConfigurationReader.GetValue("Utility:read", "slaveAddress")); + var readMasterAddress = byte.Parse(ConfigurationReader.GetValue("Utility:read", "masterAddress")); + var writeType = Enum.Parse(ConfigurationReader.GetValue("Utility:write", "type")); + var writeAddress = ConfigurationReader.GetValue("Utility:write", "address"); + var writeSlaveAddress = byte.Parse(ConfigurationReader.GetValue("Utility:write", "slaveAddress")); + var writeMasterAddress = byte.Parse(ConfigurationReader.GetValue("Utility:write", "masterAddress")); + + readUtility = new ModbusUtility(readType, readAddress, readSlaveAddress, readMasterAddress, Endian.BigEndianLsb); + writeUtility = new ModbusUtility(writeType, writeAddress, writeSlaveAddress, writeMasterAddress, Endian.BigEndianLsb); + + IScheduler scheduler = await StdSchedulerFactory.GetDefaultScheduler(); + + await scheduler.Start(); + + ITrigger trigger; + if (intervalMilliSecond <= 0) + { + trigger = TriggerBuilder.Create() + .WithIdentity(triggerKey, "Modbus.Net.DataQuery.Group." + triggerKey) + .StartNow() + .Build(); + } + else if (count >= 0) + trigger = TriggerBuilder.Create() + .WithIdentity(triggerKey, "Modbus.Net.DataQuery.Group." + triggerKey) + .StartNow() + .WithSimpleSchedule(b => b.WithInterval(TimeSpan.FromMilliseconds(intervalMilliSecond)).WithRepeatCount(count)) + .Build(); + else + trigger = TriggerBuilder.Create() + .WithIdentity(triggerKey, "Modbus.Net.DataQuery.Group." + triggerKey) + .StartNow() + .WithSimpleSchedule(b => b.WithInterval(TimeSpan.FromMilliseconds(intervalMilliSecond)).RepeatForever()) + .Build(); + + IJobListener listener; + if (intervalMilliSecond <= 0) + { + listener = new JobChainingJobLIstenerWithDataMapRepeated("Modbus.Net.DataQuery.Chain." + triggerKey, new string[2] { "Value", "SetValue" }, count); + } + else + { + listener = new JobChainingJobListenerWithDataMap("Modbus.Net.DataQuery.Chain." + triggerKey, new string[2] { "Value", "SetValue" }); + } + scheduler.ListenerManager.AddJobListener(listener, GroupMatcher.GroupEquals("Modbus.Net.DataQuery.Group." + triggerKey)); + + if (await scheduler.GetTrigger(new TriggerKey(triggerKey)) != null) + { + await scheduler.UnscheduleJob(new TriggerKey(triggerKey, "Modbus.Net.DataQuery.Group." + triggerKey)); + } + var jobKeys = await scheduler.GetJobKeys(GroupMatcher.GroupEquals("Modbus.Net.DataQuery.Group." + triggerKey)); + await scheduler.DeleteJobs(jobKeys); + + var job = JobBuilder.Create() + .WithIdentity(jobKey) + .StoreDurably(true) + .Build(); + + job.JobDataMap.Put("UtilityRead", readUtility); + job.JobDataMap.Put("UtilityReadWriteGroup", readWriteGroup); + job.JobDataMap.Put("UtilityWrite", writeUtility); + + await scheduler.ScheduleJob(job, trigger); + + } + + public override Task StopAsync(CancellationToken cancellationToken) + { + return Task.Run(() => MultipleMachinesJobScheduler.CancelJob()); + } + } + + public class UtilityPassDataJob : IJob + { + public async Task Execute(IJobExecutionContext context) + { + object utilityReadObject; + object utilityWriteObject; + object utilityReadWriteGroupObject; + + context.JobDetail.JobDataMap.TryGetValue("UtilityRead", out utilityReadObject); + context.JobDetail.JobDataMap.TryGetValue("UtilityWrite", out utilityWriteObject); + context.JobDetail.JobDataMap.TryGetValue("UtilityReadWriteGroup", out utilityReadWriteGroupObject); + + var readUtility = (BaseUtility)utilityReadObject; + var writeUtility = (BaseUtility)utilityWriteObject; + var utilityReadWriteGroup = (List)utilityReadWriteGroupObject; + + if (readUtility.IsConnected != true) + await readUtility.ConnectAsync(); + if (writeUtility.IsConnected != true) + await writeUtility.ConnectAsync(); + foreach (var rwGroup in utilityReadWriteGroup) + { + var datas = await readUtility.GetDatasAsync(rwGroup.ReadStart / 10000 + "X " + rwGroup.ReadStart % 10000, rwGroup.ReadCount * 2); + if (datas.IsSuccess == true) + { + var ans = await writeUtility.SetDatasAsync(rwGroup.WriteStart / 10000 + "X " + rwGroup.WriteStart % 10000, ByteArrayToObjectArray(datas.Datas)); + if (ans.Datas) + { + Console.WriteLine("success"); + } + } + else + { + Console.WriteLine("failed"); + } + } + } + + public static object[] ByteArrayToObjectArray(byte[] arrBytes) + { + List objArray = new List(); + foreach (byte b in arrBytes) + { + objArray.Add(b); + } + return objArray.ToArray(); + } + } + + public class ReadWriteGroup + { + public int ReadStart { get; set; } + public int ReadCount { get; set; } + public int WriteStart { get; set; } + } +} \ No newline at end of file diff --git a/Samples/ModbusTcpToRtu/appsettings.Development.json b/Samples/ModbusTcpToRtu/appsettings.Development.json new file mode 100644 index 0000000..b2dcdb6 --- /dev/null +++ b/Samples/ModbusTcpToRtu/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/Samples/ModbusTcpToRtu/appsettings.default.json b/Samples/ModbusTcpToRtu/appsettings.default.json new file mode 100644 index 0000000..edc739c --- /dev/null +++ b/Samples/ModbusTcpToRtu/appsettings.default.json @@ -0,0 +1,53 @@ +{ + "Modbus.Net": { + "TCP": { + "ConnectionTimeout": "5000", + "FetchSleepTime": "100", + "FullDuplex": "True", + "Modbus": { + "ModbusPort": "502", + "IP": "192.168.1.1" + }, + "Siemens": { + "SiemensPort": "102", + "IP": "192.168.1.1" + } + }, + "UDP": { + "ConnectionTimeout": "5000", + "FetchSleepTime": "100", + "FullDuplex": "True", + "Modbus": { + "ModbusPort": "502", + "IP": "192.168.1.1" + } + }, + "COM": { + "FetchSleepTime": "100", + "ConnectionTimeout": "5000", + "BaudRate": "BaudRate9600", + "Parity": "None", + "StopBits": "One", + "DataBits": "Eight", + "Handshake": "None", + "FullDuplex": "False", + "Modbus": { + "COM": "COM1" + }, + "Siemens": { + "COM": "COM2", + "Parity": "Even" + } + }, + "OpcDa": { + "Host": "opcda://localhost/test" + }, + "OpcUa": { + "Host": "opc.tcp://localhost/test" + }, + "Controller": { + "WaitingListCount": "100", + "NoResponse": false + } + } +} \ No newline at end of file diff --git a/Samples/ModbusTcpToRtu/appsettings.json b/Samples/ModbusTcpToRtu/appsettings.json new file mode 100644 index 0000000..950161f --- /dev/null +++ b/Samples/ModbusTcpToRtu/appsettings.json @@ -0,0 +1,55 @@ +{ + "Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Information", + "Microsoft.Hosting.Lifetime": "Information" + } + } + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Information", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "Quartz": { + "LogLevel": "Info" + }, + "ConnectionStrings": { + "DatabaseWriteConnectionString": "Server=127.0.0.1; User ID=root; Password=123456; Database=modbusnettest;" + }, + + "Modbus.Net": { + "Utility": { + "interval": 10, //间隔时常(秒) + "count": -1, //不要动 + "readwrite": [ + { + "readStart": 40001, //读取开始地址 + "readCount": 16, //读取字的个数 + "writeStart": 40001 //写入开始地址 + }, //可以写多个 + { + "readStart": 40016, //读取开始地址 + "readCount": 16, //读取字的个数 + "writeStart": 40016 //写入开始地址 + } //可以写多个 + ], + "read": { + "type": "Tcp", + "address": "127.0.0.1:502", //读取的设备地址 + "slaveAddress": 2, //从站地址 + "masterAddress": 1 //主站地址 + }, + "write": { + "type": "Rtu", + "address": "COM2", //写入的设备地址 + "slaveAddress": 3, //从站地址 + "masterAddress": 1 //主站地址 + } + } + } +} diff --git a/Samples/SampleModbusRtuServer/ConsoleLogProvider.cs b/Samples/SampleModbusRtuServer/ConsoleLogProvider.cs new file mode 100644 index 0000000..52bb796 --- /dev/null +++ b/Samples/SampleModbusRtuServer/ConsoleLogProvider.cs @@ -0,0 +1,36 @@ +using Quartz.Logging; + +namespace SampleModbusRtuServer +{ + // simple log provider to get something to the console + public class ConsoleLogProvider : ILogProvider + { + private readonly IConfigurationRoot configuration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json") + .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? "Production"}.json", true) + .Build(); + + public Logger GetLogger(string name) + { + return (level, func, exception, parameters) => + { + if (level >= configuration.GetSection("Quartz").GetValue("LogLevel") && func != null) + { + Console.WriteLine("[" + DateTime.Now.ToLongTimeString() + "] [" + level + "] " + func(), parameters); + } + return true; + }; + } + + public IDisposable OpenNestedContext(string message) + { + throw new NotImplementedException(); + } + + public IDisposable OpenMappedContext(string key, object value, bool destructure = false) + { + throw new NotImplementedException(); + } + } +} diff --git a/Samples/SampleModbusRtuServer/DatabaseWrite.cs b/Samples/SampleModbusRtuServer/DatabaseWrite.cs new file mode 100644 index 0000000..2faebfe --- /dev/null +++ b/Samples/SampleModbusRtuServer/DatabaseWrite.cs @@ -0,0 +1,34 @@ +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace MachineJob +{ + public class DatabaseWriteContext : DbContext + { + private static readonly IConfigurationRoot configuration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.default.json", optional: false, reloadOnChange: true) + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true, reloadOnChange: true) + .Build(); + + private static readonly string connectionString = configuration.GetConnectionString("DatabaseWriteConnectionString")!; + + public DbSet? DatabaseWrites { get; set; } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)); + } + } + + [Table(name: "databasewrites")] + public partial class DatabaseWriteEntity + { + [Key] + public int Id { get; set; } + + public DateTime UpdateTime { get; set; } + } +} diff --git a/Samples/SampleModbusRtuServer/Program.cs b/Samples/SampleModbusRtuServer/Program.cs new file mode 100644 index 0000000..b698c95 --- /dev/null +++ b/Samples/SampleModbusRtuServer/Program.cs @@ -0,0 +1,39 @@ +using SampleModbusRtuServer; +using SampleModbusRtuServer.Service; +using Serilog; + +IHost host = Host.CreateDefaultBuilder(args).UseWindowsService() + .ConfigureAppConfiguration((hostingContext, config) => + { + var configuration = config + .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) + .AddJsonFile("appsettings.json") + .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? "Production"}.json", true) + .AddEnvironmentVariables() + .Build(); + + Directory.SetCurrentDirectory(hostingContext.HostingEnvironment.ContentRootPath); + + Log.Logger = new LoggerConfiguration() + .ReadFrom.Configuration(configuration) + .Enrich.FromLogContext() + .WriteTo.Console() + .WriteTo.File("Log\\log..txt", Serilog.Events.LogEventLevel.Error, shared: true, rollingInterval: RollingInterval.Day) + .CreateLogger(); + + var loggerFactory = new LoggerFactory().AddSerilog(Log.Logger); + + Quartz.Logging.LogProvider.SetCurrentLogProvider(new ConsoleLogProvider()); + Modbus.Net.LogProvider.SetLogProvider(loggerFactory); + } + ) + .ConfigureServices(services => + { + services.AddHostedService(); + + services.AddLogging(loggingBuilder => loggingBuilder.AddSerilog(Log.Logger, true)); + }) + .Build(); + +await host.RunAsync(); + diff --git a/Samples/SampleModbusRtuServer/SampleModbusRtuServer.csproj b/Samples/SampleModbusRtuServer/SampleModbusRtuServer.csproj new file mode 100644 index 0000000..ba01091 --- /dev/null +++ b/Samples/SampleModbusRtuServer/SampleModbusRtuServer.csproj @@ -0,0 +1,59 @@ + + + + net6.0 + enable + enable + dotnet-SampleModbusRtuServer-b9a42287-9797-4b7f-81e6-0796c51ff8e0 + + + + <_ContentIncludedByDefault Remove="appsettings.default.json" /> + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + true + + + + + + + + + + + + + + + + + + + + + PreserveNewest + true + PreserveNewest + + + PreserveNewest + true + PreserveNewest + + + PreserveNewest + true + PreserveNewest + + + PreserveNewest + true + PreserveNewest + + + diff --git a/Samples/SampleModbusRtuServer/Worker.cs b/Samples/SampleModbusRtuServer/Worker.cs new file mode 100644 index 0000000..9d49cfd --- /dev/null +++ b/Samples/SampleModbusRtuServer/Worker.cs @@ -0,0 +1,85 @@ +using Modbus.Net.Modbus; +using MultipleMachinesJobScheduler = Modbus.Net.MultipleMachinesJobScheduler; + +namespace SampleModbusRtuServer.Service +{ + public class Worker : BackgroundService + { + private readonly ILogger _logger; + + private byte[] threex = new byte[19998]; + + public Worker(ILogger logger) + { + _logger = logger; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + ModbusRtuProtocolReceiver receiver = new ModbusRtuProtocolReceiver("COM2", 1); + receiver.DataProcess = receiveContent => + { + byte[] returnBytes = null; + var readContent = new byte[receiveContent.Count * 2]; + var values = receiveContent.WriteContent; + if (values != null) + { + try + { + /*using (var context = new DatabaseWriteContext()) + { + context.DatabaseWrites?.Add(new DatabaseWriteEntity + { + Value1 = values["Test1"].DeviceValue, + Value2 = values["Test2"].DeviceValue, + Value3 = values["Test3"].DeviceValue, + Value4 = values["Test4"].DeviceValue, + Value5 = values["Test5"].DeviceValue, + Value6 = values["Test6"].DeviceValue, + Value7 = values["Test7"].DeviceValue, + Value8 = values["Test8"].DeviceValue, + Value9 = values["Test9"].DeviceValue, + Value10 = values["Test10"].DeviceValue, + UpdateTime = DateTime.Now, + }); + context.SaveChanges(); + }*/ + switch (receiveContent.FunctionCode) + { + case (byte)ModbusProtocolFunctionCode.WriteMultiRegister: + { + Array.Copy(receiveContent.WriteContent, 0, threex, receiveContent.StartAddress * 2, receiveContent.Count); + returnBytes = new WriteDataModbusProtocol().Format(receiveContent.SlaveAddress, receiveContent.FunctionCode, receiveContent.StartAddress, receiveContent.Count); + break; + } + } + } + catch + { + //ignore + } + } + else + { + switch (receiveContent.FunctionCode) + { + case (byte)ModbusProtocolFunctionCode.ReadHoldRegister: + { + Array.Copy(threex, receiveContent.StartAddress, readContent, 0, readContent.Length); + returnBytes = new ReadDataModbusProtocol().Format(receiveContent.SlaveAddress, receiveContent.FunctionCode, (byte)receiveContent.Count, readContent); + break; + } + } + } + if (returnBytes != null) return returnBytes; + else return null; + }; + await receiver.ConnectAsync(); + } + + public override Task StopAsync(CancellationToken cancellationToken) + { + return Task.Run(() => MultipleMachinesJobScheduler.CancelJob()); + } + } +} \ No newline at end of file diff --git a/Samples/SampleModbusRtuServer/appsettings.Development.json b/Samples/SampleModbusRtuServer/appsettings.Development.json new file mode 100644 index 0000000..84216e9 --- /dev/null +++ b/Samples/SampleModbusRtuServer/appsettings.Development.json @@ -0,0 +1,21 @@ +{ + "Serilog": { + "MinimumLevel": { + "Default": "Debug", + "Override": { + "Microsoft": "Debug", + "Microsoft.Hosting.Lifetime": "Debug" + } + } + }, + "Logging": { + "LogLevel": { + "Default": "Debug", + "Microsoft": "Debug", + "Microsoft.Hosting.Lifetime": "Debug" + } + }, + "Quartz": { + "LogLevel": "Debug" + } +} diff --git a/Samples/SampleModbusRtuServer/appsettings.Production.json b/Samples/SampleModbusRtuServer/appsettings.Production.json new file mode 100644 index 0000000..8e0045d --- /dev/null +++ b/Samples/SampleModbusRtuServer/appsettings.Production.json @@ -0,0 +1,21 @@ +{ + "Serilog": { + "MinimumLevel": { + "Default": "Error", + "Override": { + "Microsoft": "Error", + "Microsoft.Hosting.Lifetime": "Error" + } + } + }, + "Logging": { + "LogLevel": { + "Default": "Error", + "Microsoft": "Error", + "Microsoft.Hosting.Lifetime": "Error" + } + }, + "Quartz": { + "LogLevel": "Error" + } +} diff --git a/Samples/SampleModbusRtuServer/appsettings.default.json b/Samples/SampleModbusRtuServer/appsettings.default.json new file mode 100644 index 0000000..edc739c --- /dev/null +++ b/Samples/SampleModbusRtuServer/appsettings.default.json @@ -0,0 +1,53 @@ +{ + "Modbus.Net": { + "TCP": { + "ConnectionTimeout": "5000", + "FetchSleepTime": "100", + "FullDuplex": "True", + "Modbus": { + "ModbusPort": "502", + "IP": "192.168.1.1" + }, + "Siemens": { + "SiemensPort": "102", + "IP": "192.168.1.1" + } + }, + "UDP": { + "ConnectionTimeout": "5000", + "FetchSleepTime": "100", + "FullDuplex": "True", + "Modbus": { + "ModbusPort": "502", + "IP": "192.168.1.1" + } + }, + "COM": { + "FetchSleepTime": "100", + "ConnectionTimeout": "5000", + "BaudRate": "BaudRate9600", + "Parity": "None", + "StopBits": "One", + "DataBits": "Eight", + "Handshake": "None", + "FullDuplex": "False", + "Modbus": { + "COM": "COM1" + }, + "Siemens": { + "COM": "COM2", + "Parity": "Even" + } + }, + "OpcDa": { + "Host": "opcda://localhost/test" + }, + "OpcUa": { + "Host": "opc.tcp://localhost/test" + }, + "Controller": { + "WaitingListCount": "100", + "NoResponse": false + } + } +} \ No newline at end of file diff --git a/Samples/SampleModbusRtuServer/appsettings.json b/Samples/SampleModbusRtuServer/appsettings.json new file mode 100644 index 0000000..ca147e3 --- /dev/null +++ b/Samples/SampleModbusRtuServer/appsettings.json @@ -0,0 +1,249 @@ +{ + "Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Information", + "Microsoft.Hosting.Lifetime": "Information" + } + } + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Information", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "Quartz": { + "LogLevel": "Info" + }, + "ConnectionStrings": { + "DatabaseWriteConnectionString": "Server=127.0.0.1; User ID=root; Password=123456; Database=modbusnettest;" + }, + + "Modbus.Net": { + "Machine": [ + { + "a:id": "ModbusMachine1", + "b:protocol": "Modbus", + "c:type": "Tcp", + "d:connectionString": "127.0.0.1", + "e:addressMap": "AddressMapModbus", + "f:keepConnect": true, + "g:slaveAddress": 1, + "h:masterAddress": 2, + "i:endian": "BigEndianLsb" + }, + { + "a:id": "SiemensMachine1", + "b:protocol": "Siemens", + "c:type": "Tcp", + "d:connectionString": "127.0.0.1", + "e:model": "S7_1200", + "f:addressMap": "AddressMapSiemens", + "g:keepConnect": true, + "h:slaveAddress": 1, + "i:masterAddress": 2, + "j:src": 1, + "k:dst": 0 + }, + { + "a:id": "ModbusMachine2", + "b:protocol": "Modbus", + "c:type": "Rtu", + "d:connectionString": "COM1", + "e:addressMap": "AddressMapModbus", + "f:keepConnect": true, + "g:slaveAddress": 1, + "h:masterAddress": 2, + "i:endian": "BigEndianLsb" + }, + { + "a:id": "SiemensMachine2", + "b:protocol": "Siemens", + "c:type": "Ppi", + "d:connectionString": "COM2", + "e:model": "S7_200", + "f:addressMap": "AddressMapSiemens", + "g:keepConnect": true, + "h:slaveAddress": 2, + "i:masterAddress": 0, + "j:src": 1, + "k:dst": 0 + }, + { + "a:id": "OpcMachine1", + "b:protocol": "Opc", + "c:type": "Da", + "d:connectionString": "opcda://localhost/Matrikon.OPC.Simulation.1", + "e:addressMap": "AddressMapOpc", + "f:tagSpliter": "." + } + ], + "addressMap": { + "AddressMapModbus": [ + { + "Area": "4X", + "Address": 1, + "DataType": "Int16", + "Id": "1", + "Name": "Test1" + }, + { + "Area": "4X", + "Address": 2, + "DataType": "Int16", + "Id": "2", + "Name": "Test2" + }, + { + "Area": "4X", + "Address": 3, + "DataType": "Int16", + "Id": "3", + "Name": "Test3" + }, + { + "Area": "4X", + "Address": 4, + "DataType": "Int16", + "Id": "4", + "Name": "Test4" + }, + { + "Area": "4X", + "Address": 5, + "DataType": "Int16", + "Id": "5", + "Name": "Test5" + }, + { + "Area": "4X", + "Address": 6, + "DataType": "Int16", + "Id": "6", + "Name": "Test6" + }, + { + "Area": "4X", + "Address": 7, + "DataType": "Int16", + "Id": "7", + "Name": "Test7" + }, + { + "Area": "4X", + "Address": 8, + "DataType": "Int16", + "Id": "8", + "Name": "Test8" + }, + { + "Area": "4X", + "Address": 9, + "DataType": "Int16", + "Id": "9", + "Name": "Test9" + }, + { + "Area": "4X", + "Address": 10, + "DataType": "Int16", + "Id": "10", + "Name": "Test10" + } + ], + "AddressMapSiemens": [ + { + "Area": "DB1", + "Address": 0, + "DataType": "Int16", + "Id": "1", + "Name": "Test1" + }, + { + "Area": "DB1", + "Address": 2, + "DataType": "Int16", + "Id": "2", + "Name": "Test2" + }, + { + "Area": "DB1", + "Address": 4, + "DataType": "Int16", + "Id": "3", + "Name": "Test3" + }, + { + "Area": "DB1", + "Address": 6, + "DataType": "Int16", + "Id": "4", + "Name": "Test4" + }, + { + "Area": "DB1", + "Address": 8, + "DataType": "Int16", + "Id": "5", + "Name": "Test5" + }, + { + "Area": "DB1", + "Address": 10, + "DataType": "Int16", + "Id": "6", + "Name": "Test6" + }, + { + "Area": "DB1", + "Address": 12, + "DataType": "Int16", + "Id": "7", + "Name": "Test7" + }, + { + "Area": "DB1", + "Address": 14, + "DataType": "Int16", + "Id": "8", + "Name": "Test8" + }, + { + "Area": "DB1", + "Address": 16, + "DataType": "Int16", + "Id": "9", + "Name": "Test9" + }, + { + "Area": "DB1", + "Address": 18, + "DataType": "Int16", + "Id": "10", + "Name": "Test10" + } + ], + "AddressMapOpc": [ + { + "Area": "Random", + "Address": "Real4", + "DataType": "Single", + "Id": "1", + "Name": "Test1", + "DecimalPos": 2 + }, + { + "Area": "Random", + "Address": "Real8", + "DataType": "Double", + "Id": "2", + "Name": "Test2", + "DecimalPos": 4 + } + ] + } + } +}