diff --git a/Modbus.Net/Modbus.Net.Modbus/README.md b/Modbus.Net/Modbus.Net.Modbus/README.md index 84fd28e..4822119 100644 --- a/Modbus.Net/Modbus.Net.Modbus/README.md +++ b/Modbus.Net/Modbus.Net.Modbus/README.md @@ -4,77 +4,4 @@ Modbus.Net.Modbus Modbus Implementation of Modbus.Net -Table of Content: -* [Basic Concept](#basic) -* [Address Mapping](#address) -* [Addres Coding](#coding) -* [SubAddress Rules](#subpos) - -## Basic Concept - -Modbus is a serial communications protocol originally published by Modicon (now Schneider Electric) in 1979 for use with its programmable logic controllers (PLCs). Simple and robust, it has since become a de facto standard communication protocol, and it is now a commonly available means of connecting industrial electronic devices.(From Wekipedia) - -## Address Mapping - -Modbus has four types of address: Coil, Discrete Input, Input Register and Holding Register. - -Modbus has two address description method: standard and extend. - -The following table could show the full address discription in Modbus. - -Type | Standard | Extend | ----------------- | ----------- | ------------- | -Coil | 00001-09999 | 000001-065536 | -Discrete Input | 10001-19999 | 100001-165536 | -Input Register | 30001-39999 | 300001-365536 | -Holding Register | 40001-49999 | 400001-465536 | - -Standard and Extend address description are all supported in Modbus.Net.Modbus. The only difference is don't write too large number in address. - -The following table shows how to write address in Modbus.Net.Modbus - -Standard Modbus Address | Modbus.Net.Modbus String Address | ------------------------ | -------------------------------- | -00001 | 0X 1 | -00002 | 0X 2 | -09999 | 0X 9999 | -065536 | 0X 65536 | -10001 | 1X 1 | -30001 | 3X 1 | -40001 | 4X 1 | - -## Address Coding - -In Modbus.Net, you can write "0X"(Coil), "1X"(Discrete Input), "3X"(Input Register), "4X"(Holding Register) in Area. - -Don't forget to type a space between Area and Address. - -If you want to use subpos, type string address like this: - -4X 1.12 (Area Address.Subpos) - -## SubAddress Rules - -For 0X and 1X, the scalation is 1/8. This means each address is bool. - -Noticing that you can only read 1X. - -SubAddress System is not activated in 0X and 1X. - -For 3X and 4X, the scalation is 2. This meas each address is short. - -The number of SubAddress is from 0 to 15. - -Caution: Modbus.Net.Modbus SubAddress has a giant difference towards standard Modbus. - -Bit position from Modbus.Net is one less than standard modbus. - -Standard Modbus - -1 0 1 1 1 0 0 0 1 0 0 0 0 1 1 0
-16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 - -Modbus.Net.Modbus - -1 0 1 1 1 0 0 0 1 0 0 0 0 1 1 0
-15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +Doc has been moved to wiki. \ No newline at end of file diff --git a/Modbus.Net/Modbus.Net.OPC/README.md b/Modbus.Net/Modbus.Net.OPC/README.md index b2870ad..7e52ec9 100644 --- a/Modbus.Net/Modbus.Net.OPC/README.md +++ b/Modbus.Net/Modbus.Net.OPC/README.md @@ -4,46 +4,4 @@ Modbus Implementation of Modbus.Net -Table of Content: -* [Basic Concept](#basic) -* [Address Mapping](#address) -* [Regex System](#regex) -* [Link](#link) - -## Basic Concept - -OPC Protocal implements OPC DA and OPC UA protocal. - -## Address Mapping - -Modbus.Net.OPC has a simple address formatting tool. You can find it from AddressFormaterOPC. - -You need to use messages in BaseMachine and AddressUnit to create an OPC tag. - -Here is a Sample. - -If your tag is "1/15/Value_Opening", 1 is MachineId, 15 is StationId and Value_Opening is point name. - -Your tagGeter code should be. - -```C# -(baseMachine, addressUnit) => return new string[]{baseMachine.Id, ((XXUnitExtend)addressUnit.unitExtend).stationId, addressUnit.Name}; -``` - -If you want change your tag to "1.15.Value_Opening", just set the seperator to '.' . - -## Regex System - -Every tag could be a regex expression, Modbus.Net.OPC will always use regex comparison mode between each tags. - -Like if you want to use a wildcard for first tag(or first directory), input your tagGeter code like this: - -```C# -(baseMachine, addressUnit) => return new string[]{"(.*)", baseMachine.Id, ((XXUnitExtend)addressUnit.unitExtend).stationId, addressUnit.Name}; -``` - -## Link - -The link of OPC DA should like "opcda://PC-Name/OPC-Software-Name". - -The link of OPC UA should like "opc.tcp://PC-Name/Opc-Software-Name". +Doc has been moved to wiki. \ No newline at end of file diff --git a/Modbus.Net/Modbus.Net.Siemens/README.md b/Modbus.Net/Modbus.Net.Siemens/README.md index 1b775cf..7c2974c 100644 --- a/Modbus.Net/Modbus.Net.Siemens/README.md +++ b/Modbus.Net/Modbus.Net.Siemens/README.md @@ -4,39 +4,4 @@ Modbus Implementation of Modbus.Net -Table of Content: -* [Basic Concept](#basic) -* [Address Mapping](#address) -* [Addres Coding](#coding) -* [SubAddress Rules](#subpos) - -## Basic Concept - -Siemens Protocal is derived by Profibus and Profinet. - -## Address Mapping - -Modbus.Net.Siemens has two types of AddressMapping -- Modbus.Net way or Siemens way. - -But Modbus.Net already decleared the type in AddressUnit, so all of the formatting ways will ignore the address type. - -The following table shows the differences between them. - -Standard Siemens Address | Modbus.Net Address Format | Siemens Address Format | ------------------------- | ------------------------- | ---------------------- | -I0.0 | I 0.0 | I0.0 | -IB0 | I 0 | I0 | -V10.5 | V 10.5 | V10.5 | -VB19 | V 19 | V19 | -DB1.DBD22 | DB1 22 | DB1.DB22 | -DB2.DB35.1 | DB2 35.1 | DB2.DB35.1 | - -## Address Coding - -The Coding of Modbus.Net.Siemens is the same as standard siemens protocal. - -## SubAddress Rules - -SubAddress Rules is the same as standard siemens protocal. - -Area length will always be 1. +Doc has been moved to wiki. \ No newline at end of file diff --git a/Modbus.Net/Modbus.Net/README.md b/Modbus.Net/Modbus.Net/README.md index b0b2ddd..772a1df 100644 --- a/Modbus.Net/Modbus.Net/README.md +++ b/Modbus.Net/Modbus.Net/README.md @@ -97,23 +97,37 @@ List addressUnits = new List new AddressUnit() {Id = "0", Area = "V", Address = 1, CommunicationTag = "D1", DataType = typeof (ushort), Zoom = 1}, new AddressUnit() {Id = "1", Area = "V", Address = 3, CommunicationTag = "D2", DataType = typeof (float), Zoom = 1} }; -TaskManager task = new TaskManager(10, 300, true); +TaskManager task = new TaskManager(10, true); task.AddMachine(new SiemensMachine(SiemensType.Tcp, "192.168.3.11", SiemensMachineModel.S7_300, addressUnits, true, 2, 0)); -task.ReturnValues += (returnValues) => +task.InvokeTimerAll(new TaskItemGetData(returnValues => { - value = new List(); + //唯一的参数包含返回值,是一个唯一标识符(machine的第二个参数),返回值(类型ReturnUnit)的键值对。 if (returnValues.ReturnValues != null) { - value = from val in returnValues.ReturnValues select val.Key + " " + val.Value.PlcValue; - siemensItems.Dispatcher.Invoke(() => siemensItems.ItemsSource = value); + lock (values) + { + var unitValues = from val in returnValues.ReturnValues + select + new Tuple( + addressUnits.FirstOrDefault(p => p.CommunicationTag == val.Key), val.Value.PlcValue); + values = from unitValue in unitValues + select + new TaskViewModel() + { + Id = unitValue.Item1.Id, + Name = unitValue.Item1.Name, + Address = unitValue.Item1.Address.ToString(), + Value = unitValue.Item2 ?? 0, + Type = unitValue.Item1.DataType.Name + }; + } } else { Console.WriteLine($"ip {returnValues.MachineId} not return value"); } -}; -task.TaskStart(); +}, MachineGetDataType.CommunicationTag, 5000, 60000)); ``` @@ -129,26 +143,19 @@ More important, you can extend and implement your own field in UnitExtend in eve 3. Add a machine to TaskManager. Add a machine like siemens machine to the task manager. -4. Implement ReturnValues event. -The argument return values is a key value pair. The architechture is: - -* Key : the link address of machine (in sample is the second parameter). -* Value : Dictionary. -* Key : CommunicationTag/Address(string) in AddressUnit. -* Value : ReturnUnit. -* PlcValue : The return data, all in double type. -* UnitExtend : UnitExtend in AddressUnit. You should cast this class to your own class extends by UnitExtend. +4. Add a TaskItem for one machine or all Machines. + Modbus.Net implement TaskItemGetDatas and TaskItemSetDatas as the default. ## Tutorial This platform has three level APIs that you could use: Low level API called "BaseUtility"; Middle level API called "BaseMachine"; High level API called "TaskManager". -###BaseUtility -BaseUtility is a low level api, in this level you can get or set data only by byte array or object array. Here is an example. +### Utility +IUtilityProperty is a low level api, in this level you can get or set data only by byte array or object array. Here is an example. ```C# string ip = "192.168.0.10"; -BaseUtility utility = new ModbusUtility(ModbusType.Tcp, ip, 0x02, 0x00); -object[] getNum = utility.GetDatas("4X 1", new KeyValuePair(typeof(ushort), 4)); +IUtilityProperty utility = new ModbusUtility(ModbusType.Tcp, ip, 0x02, 0x00); +object[] getNum = utility.InvokeUtilityMethod?.GetDatas("4X 1", new KeyValuePair(typeof(ushort), 4)); ``` BaseUtility is an abstract class. You can check all apis in BaseUtility.cs in Modbus.Net project. @@ -157,24 +164,25 @@ To use BaseUtility, follow these steps. 1.New a BaseUtility instance, but remember BaseUtility is an abstract class, you should new class inherit from it. ```C# -BaseUtility utility = new ModbusUtility(ModbusType.Tcp, ip, 0x02, 0x00); +IUtilityPropety utility = new ModbusUtility(ModbusType.Tcp, ip, 0x02, 0x00); ``` -2.Use GetData and SetData Api in BaseUtility, like +2.Use GetData and SetData Api in IUtilityMethodData, like ```C# -object[] getNum = utility.GetDatas("4X 1", new KeyValuePair(typeof(ushort), 4)); +object[] getNum = utility.InvokeUtilityMethod?.GetDatas("4X 1", new KeyValuePair(typeof(ushort), 4)); -utility.SetDatas("4X 1", new object[] { (ushort)1, (ushort)2, (ushort)3 }); +utility.InvokeUtilityMethod?.SetDatas("4X 1", new object[] { (ushort)1, (ushort)2, (ushort)3 }); ``` Remember force set type of numbers because GetData and SetData Apis are type sensitive. You can also use async functions like ```C# -object[] getNum = await utility.GetDatasAsync("4X 1", new KeyValuePair(typeof(ushort), 4)); +object[] getNum = await utility.InvokeUtilityMethod?.GetDatasAsync("4X 1", new KeyValuePair(typeof(ushort), 4)); ``` -###BaseMachine -BaseMachine is a middle level api, in this level you could get and set datas in a managable data structure for a single machine. +### Machine + +IMachineProperty is a middle level api, in this level you could get and set datas in a managable data structure for a single machine. To understand this class, you have to see the AddressUnit first. ```C# public class AddressUnit @@ -205,9 +213,9 @@ For some reasons, AddressUnit has two keys: Id and CommunicationTag, one is inte * Unit : Unit of the Address. For example "¡æ". * UnitExtend : If you want to get something else when value returns, extend the class and give it to here. -Then using BaseMachine like this. +Then using IMachineProperty like this. ```C# -BaseMachine machine = new ModbusMachine(ModbusType.Tcp, "192.168.3.12", new List() +IMachineProperty machine = new ModbusMachine(ModbusType.Tcp, "192.168.3.12", new List() { machine = new ModbusMachine(ModbusType.Rtu, "COM3", new List() { @@ -219,18 +227,18 @@ machine = new ModbusMachine(ModbusType.Rtu, "COM3", new List() machine.AddressCombiner = new AddressCombinerContinus(machine.AddressTranslator); machine.AddressCombinerSet = new AddressCombinerContinus(machine.AddressTranslator); machine.AddressCombiner = new AddressCombinerPercentageJump(20.0); -var result = machine.GetDatas(MachineGetDataType.CommunicationTag); +var result = machine.InvokeMachineMethods?.GetDatas(MachineGetDataType.CommunicationTag); var add1 = result["Add1"].PlcValue; var resultFormat = result.MapGetValuesToSetValues(); -machine.SetDatas(MachineSetDataType.CommunicationTag, resultFormat); +machine.InvokeMachineMethods?.SetDatas(MachineSetDataType.CommunicationTag, resultFormat); ``` To use BaseMachine, follow these steps. -1.New a BaseMachine instance. Remeber BaseMachine is an abstract class. +1.New a IMachineProperty instance. ```C# -machine = new ModbusMachine(ModbusType.Rtu, "COM3", new List() +IMachineProperty machine = new ModbusMachine(ModbusType.Rtu, "COM3", new List() { new AddressUnit() {Id = "1", Area = "4X", Address = 1, CommunicationTag = "Add1", DataType = typeof(ushort), Zoom = 1, DecimalPos = 0}, new AddressUnit() {Id = "2", Area = "4X", Address = 2, CommunicationTag = "Add2", DataType = typeof(ushort), Zoom = 1, DecimalPos = 0}, @@ -259,8 +267,8 @@ There are 4 AddressCombiners implemented in the platform. 3.Use GetDatas Api. ```C# -var result = machine.GetDatas(MachineGetDataType.CommunicationTag); -//var result = await machine.GetDatasAsync(MachineGetDataType.CommunicationTag); +var result = machine.InvokeMachineMethods?.GetDatas(MachineGetDataType.CommunicationTag); +//var result = await machine.InvokeMachineMethods?.GetDatasAsync(MachineGetDataType.CommunicationTag); ``` 4.Retrive data from result. @@ -275,13 +283,15 @@ var resultFormat = result.MapGetValuesToSetValues(); 6.SetData to machine or another machine. ```C# -machine.SetDatas(MachineSetDataType.CommunicationTag, resultFormat); +machine.InvokeMachineMethods?.SetDatas(MachineSetDataType.CommunicationTag, resultFormat); ``` There is also a SetDatasAsync Api. -machine.SetDatas has two types. It is referenced as the first parameter. +machine.SetDatas has four types. It is referenced as the first parameter. 1. MachineSetDataType.Address: the key of the dictionary of the second parameter is address. 2. MachineSetDataType.CommunicationTag: the key of the dictionary of the second parameter is communication tag. +3. MachineSetDataType.Id: the key of the dictionary of the second paramenter is ID. +4. MachineSetDataType.Name: the key of the dictionary of the second paramenter is name. ### TaskManager TaskManager is a high level api that you can manage and control many machines together. Remenber if you want to use this class, all communications must be asyncronized. @@ -294,20 +304,33 @@ List addressUnits = new List new AddressUnit() {Id = 1, Area = "V", Address = 3, CommunicationTag = "D2", DataType = typeof (float), Zoom = 1} }; task.AddMachine(new SiemensMachine(SiemensType.Tcp, "192.168.3.11",SiemensMachineModel.S7_300, addressUnits, true)); -task.ReturnValues += (returnValues) => +task.InvokeTimerAll(new TaskItemGetData(returnValues => { - value = new List(); - if (returnValues.Value != null) - { - value = from val in returnValues.Value select val.Key + " " + val.Value.PlcValue; - siemensItems.Dispatcher.Invoke(() => siemensItems.ItemsSource = value); - } - else - { - Console.WriteLine($"ip {returnValues.Key} not return value"); - } -}; -task.TaskStart(); + if (returnValues.ReturnValues != null) + { + lock (values) + { + var unitValues = from val in returnValues.ReturnValues + select + new Tuple( + addressUnits.FirstOrDefault(p => p.CommunicationTag == val.Key), val.Value.PlcValue); + values = from unitValue in unitValues + select + new TaskViewModel() + { + Id = unitValue.Item1.Id, + Name = unitValue.Item1.Name, + Address = unitValue.Item1.Address.ToString(), + Value = unitValue.Item2 ?? 0, + Type = unitValue.Item1.DataType.Name + }; + } + } + else + { + Console.WriteLine($"ip {returnValues.MachineId} not return value"); + } +}, MachineGetDataType.CommunicationTag, 5000, 60000)); ``` To use the TaskManager, use following steps. @@ -327,28 +350,35 @@ List addressUnits = new List task.AddMachine(new SiemensMachine(SiemensType.Tcp, "192.168.3.11", SiemensMachineModel.S7_300, addressUnits, true)); ``` -3.Register the ReturnValue Event. +3.Add a get value cycling task. You can handle return values with your own code (ReturnValue event before 1.3.2). ```C# -task.ReturnValues += (returnValues) => +task.InvokeTimerAll(new TaskItemGetData(returnValues => { - value = new List(); - if (returnValues.Value != null) - { - value = from val in returnValues.Value select val.Key + " " + val.Value.PlcValue; - siemensItems.Dispatcher.Invoke(() => siemensItems.ItemsSource = value); - } - else - { - Console.WriteLine($"ip {returnValues.Key} not return value"); - } -}; -``` -The ReturnValues' key is the machineToken, this sample is "192.168.3.11". -And the value is the same as the machine.GetDatas returns. - -4.Start the TaskManager. -```C# -task.TaskStart(); + if (returnValues.ReturnValues != null) + { + lock (values) + { + var unitValues = from val in returnValues.ReturnValues + select + new Tuple( + addressUnits.FirstOrDefault(p => p.CommunicationTag == val.Key), val.Value.PlcValue); + values = from unitValue in unitValues + select + new TaskViewModel() + { + Id = unitValue.Item1.Id, + Name = unitValue.Item1.Name, + Address = unitValue.Item1.Address.ToString(), + Value = unitValue.Item2 ?? 0, + Type = unitValue.Item1.DataType.Name + }; + } + } + else + { + Console.WriteLine($"ip {returnValues.MachineId} not return value"); + } +}, MachineGetDataType.CommunicationTag, 5000, 60000)); ``` 5.And don't forget that there is also a SetDatasAsync Api in the TaskManager. @@ -462,8 +492,6 @@ You need to implement three functions. public override void SetConnectionType(int connectionType) protected override async Task GetDatasAsync(byte slaveAddress, byte masterAddress, string startAddress, int getByteCount) public override async Task SetDatasAsync(byte slaveAddress, byte masterAddress, string startAddress, object[] setContents) -public override bool GetLittleEndian -public override bool SetLittleEndian ``` And don't remember set default AddressTranslator, slaveAddress, masterAddress and Protocal. ```C# @@ -509,8 +537,8 @@ public class AddressFormaterModbus : AddressFormater ## Addition -### For Subpos System -Subpos system is implemented for reading and writing of bits. +### For Subaddress System +Subaddress system is implemented for reading and writing of bits. ```C# public class AddressUnit {