初始化上传

This commit is contained in:
2025-08-26 08:37:44 +08:00
commit 31d81b91b6
448 changed files with 80981 additions and 0 deletions

View File

@@ -0,0 +1,64 @@
using System;
namespace SharpModbus
{
public class ModbusF01ReadCoils : IModbusCommand
{
private readonly byte slave;
private readonly ushort address;
private readonly ushort count;
public byte Code { get { return 1; } }
public byte Slave { get { return slave; } }
public ushort Address { get { return address; } }
public ushort Count { get { return count; } }
public int RequestLength { get { return 6; } }
public int ResponseLength { get { return 3 + ModbusUtils.BytesForBools(count); } }
public ModbusF01ReadCoils(byte slave, ushort address, ushort count)
{
this.slave = slave;
this.address = address;
this.count = count;
}
public void FillRequest(byte[] request, int offset)
{
request[offset + 0] = slave;
request[offset + 1] = 1;
request[offset + 2] = ModbusUtils.High(address);
request[offset + 3] = ModbusUtils.Low(address);
request[offset + 4] = ModbusUtils.High(count);
request[offset + 5] = ModbusUtils.Low(count);
}
public object ParseResponse(byte[] response, int offset)
{
var bytes = ModbusUtils.BytesForBools(count);
Tools.AssertEqual(response[offset + 0], slave, "Slave mismatch got {0} expected {1}");
Tools.AssertEqual(response[offset + 1], 1, "Function mismatch got {0} expected {1}");
Tools.AssertEqual(response[offset + 2], bytes, "Bytes mismatch got {0} expected {1}");
return ModbusUtils.DecodeBools(response, offset + 3, count);
}
public object ApplyTo(IModbusModel model)
{
return model.getDOs(slave, address, count);
}
public void FillResponse(byte[] response, int offset, object value)
{
var bytes = ModbusUtils.BytesForBools(count);
response[offset + 0] = slave;
response[offset + 1] = 1;
response[offset + 2] = bytes;
var data = ModbusUtils.EncodeBools(value as bool[]);
ModbusUtils.Copy(data, 0, response, offset + 3, bytes);
}
public override string ToString()
{
return string.Format("[ModbusF01ReadCoils Slave={0}, Address={1}, Count={2}]", slave, address, count);
}
}
}

View File

@@ -0,0 +1,64 @@
using System;
namespace SharpModbus
{
public class ModbusF02ReadInputs : IModbusCommand
{
private readonly byte slave;
private readonly ushort address;
private readonly ushort count;
public byte Code { get { return 2; } }
public byte Slave { get { return slave; } }
public ushort Address { get { return address; } }
public ushort Count { get { return count; } }
public int RequestLength { get { return 6; } }
public int ResponseLength { get { return 3 + ModbusUtils.BytesForBools(count); } }
public ModbusF02ReadInputs(byte slave, ushort address, ushort count)
{
this.slave = slave;
this.address = address;
this.count = count;
}
public void FillRequest(byte[] request, int offset)
{
request[offset + 0] = slave;
request[offset + 1] = 2;
request[offset + 2] = ModbusUtils.High(address);
request[offset + 3] = ModbusUtils.Low(address);
request[offset + 4] = ModbusUtils.High(count);
request[offset + 5] = ModbusUtils.Low(count);
}
public object ParseResponse(byte[] response, int offset)
{
var bytes = ModbusUtils.BytesForBools(count);
Tools.AssertEqual(response[offset + 0], slave, "Slave mismatch got {0} expected {1}");
Tools.AssertEqual(response[offset + 1], 2, "Function mismatch got {0} expected {1}");
Tools.AssertEqual(response[offset + 2], bytes, "Bytes mismatch got {0} expected {1}");
return ModbusUtils.DecodeBools(response, offset + 3, count);
}
public object ApplyTo(IModbusModel model)
{
return model.getDIs(slave, address, count);
}
public void FillResponse(byte[] response, int offset, object value)
{
var bytes = ModbusUtils.BytesForBools(count);
response[offset + 0] = slave;
response[offset + 1] = 2;
response[offset + 2] = bytes;
var data = ModbusUtils.EncodeBools(value as bool[]);
ModbusUtils.Copy(data, 0, response, offset + 3, bytes);
}
public override string ToString()
{
return string.Format("[ModbusF02ReadInputs Slave={0}, Address={1}, Count={2}]", slave, address, count);
}
}
}

View File

@@ -0,0 +1,64 @@
using System;
namespace SharpModbus
{
public class ModbusF03ReadHoldingRegisters : IModbusCommand
{
private readonly byte slave;
private readonly ushort address;
private readonly ushort count;
public byte Code { get { return 3; } }
public byte Slave { get { return slave; } }
public ushort Address { get { return address; } }
public ushort Count { get { return count; } }
public int RequestLength { get { return 6; } }
public int ResponseLength { get { return 3 + ModbusUtils.BytesForWords(count); } }
public ModbusF03ReadHoldingRegisters(byte slave, ushort address, ushort count)
{
this.slave = slave;
this.address = address;
this.count = count;
}
public void FillRequest(byte[] request, int offset)
{
request[offset + 0] = slave;
request[offset + 1] = 3;
request[offset + 2] = ModbusUtils.High(address);
request[offset + 3] = ModbusUtils.Low(address);
request[offset + 4] = ModbusUtils.High(count);
request[offset + 5] = ModbusUtils.Low(count);
}
public object ParseResponse(byte[] response, int offset)
{
var bytes = ModbusUtils.BytesForWords(count);
Tools.AssertEqual(response[offset + 0], slave, "Slave mismatch got {0} expected {1}");
Tools.AssertEqual(response[offset + 1], 3, "Function mismatch got {0} expected {1}");
Tools.AssertEqual(response[offset + 2], bytes, "Bytes mismatch got {0} expected {1}");
return ModbusUtils.DecodeWords(response, offset + 3, count);
}
public object ApplyTo(IModbusModel model)
{
return model.getWOs(slave, address, count);
}
public void FillResponse(byte[] response, int offset, object value)
{
var bytes = ModbusUtils.BytesForWords(count);
response[offset + 0] = slave;
response[offset + 1] = 3;
response[offset + 2] = bytes;
var data = ModbusUtils.EncodeWords((ushort[])value);
ModbusUtils.Copy(data, 0, response, offset + 3, bytes);
}
public override string ToString()
{
return string.Format("[ModbusF03ReadHoldingRegisters Slave={0}, Address={1}, Count={2}]", slave, address, count);
}
}
}

View File

@@ -0,0 +1,64 @@
using System;
namespace SharpModbus
{
public class ModbusF04ReadInputRegisters : IModbusCommand
{
private readonly byte slave;
private readonly ushort address;
private readonly ushort count;
public byte Code { get { return 4; } }
public byte Slave { get { return slave; } }
public ushort Address { get { return address; } }
public ushort Count { get { return count; } }
public int RequestLength { get { return 6; } }
public int ResponseLength { get { return 3 + ModbusUtils.BytesForWords(count); } }
public ModbusF04ReadInputRegisters(byte slave, ushort address, ushort count)
{
this.slave = slave;
this.address = address;
this.count = count;
}
public void FillRequest(byte[] request, int offset)
{
request[offset + 0] = slave;
request[offset + 1] = 4;
request[offset + 2] = ModbusUtils.High(address);
request[offset + 3] = ModbusUtils.Low(address);
request[offset + 4] = ModbusUtils.High(count);
request[offset + 5] = ModbusUtils.Low(count);
}
public object ParseResponse(byte[] response, int offset)
{
var bytes = ModbusUtils.BytesForWords(count);
Tools.AssertEqual(response[offset + 0], slave, "Slave mismatch got {0} expected {1}");
Tools.AssertEqual(response[offset + 1], 4, "Function mismatch got {0} expected {1}");
Tools.AssertEqual(response[offset + 2], bytes, "Bytes mismatch got {0} expected {1}");
return ModbusUtils.DecodeWords(response, offset + 3, count);
}
public object ApplyTo(IModbusModel model)
{
return model.getWIs(slave, address, count);
}
public void FillResponse(byte[] response, int offset, object value)
{
var bytes = ModbusUtils.BytesForWords(count);
response[offset + 0] = slave;
response[offset + 1] = 4;
response[offset + 2] = bytes;
var data = ModbusUtils.EncodeWords(value as ushort[]);
ModbusUtils.Copy(data, 0, response, offset + 3, bytes);
}
public override string ToString()
{
return string.Format("[ModbusF04ReadInputRegisters Slave={0}, Address={1}, Count={2}]", slave, address, count);
}
}
}

View File

@@ -0,0 +1,61 @@
using System;
namespace SharpModbus
{
public class ModbusF05WriteCoil : IModbusCommand
{
private readonly byte slave;
private readonly ushort address;
private readonly bool value;
public byte Code { get { return 5; } }
public byte Slave { get { return slave; } }
public ushort Address { get { return address; } }
public bool Value { get { return value; } }
public int RequestLength { get { return 6; } }
public int ResponseLength { get { return 6; } }
public ModbusF05WriteCoil(byte slave, ushort address, bool state)
{
this.slave = slave;
this.address = address;
this.value = state;
}
public void FillRequest(byte[] request, int offset)
{
request[offset + 0] = slave;
request[offset + 1] = 5;
request[offset + 2] = ModbusUtils.High(address);
request[offset + 3] = ModbusUtils.Low(address);
request[offset + 4] = ModbusUtils.EncodeBool(value);
request[offset + 5] = 0;
}
public object ParseResponse(byte[] response, int offset)
{
Tools.AssertEqual(response[offset + 0], slave, "Slave mismatch got {0} expected {1}");
Tools.AssertEqual(response[offset + 1], 5, "Function mismatch got {0} expected {1}");
Tools.AssertEqual(ModbusUtils.GetUShort(response, offset + 2), address, "Address mismatch got {0} expected {1}");
Tools.AssertEqual(response[offset + 4], ModbusUtils.EncodeBool(value), "Value mismatch got {0} expected {1}");
Tools.AssertEqual(response[offset + 5], 0, "Pad mismatch {0} expected:{1}");
return null;
}
public object ApplyTo(IModbusModel model)
{
model.setDO(slave, address, value);
return null;
}
public void FillResponse(byte[] response, int offset, object value)
{
FillRequest(response, offset);
}
public override string ToString()
{
return string.Format("[ModbusF05WriteCoil Slave={0}, Address={1}, Value={2}]", slave, address, value);
}
}
}

View File

@@ -0,0 +1,60 @@
using System;
namespace SharpModbus
{
public class ModbusF06WriteRegister : IModbusCommand
{
private readonly byte slave;
private readonly ushort address;
private readonly ushort value;
public byte Code { get { return 6; } }
public byte Slave { get { return slave; } }
public ushort Address { get { return address; } }
public ushort Value { get { return value; } }
public int RequestLength { get { return 6; } }
public int ResponseLength { get { return 6; } }
public ModbusF06WriteRegister(byte slave, ushort address, ushort value)
{
this.slave = slave;
this.address = address;
this.value = value;
}
public void FillRequest(byte[] request, int offset)
{
request[offset + 0] = slave;
request[offset + 1] = 6;
request[offset + 2] = ModbusUtils.High(address);
request[offset + 3] = ModbusUtils.Low(address);
request[offset + 4] = ModbusUtils.High(value);
request[offset + 5] = ModbusUtils.Low(value);
}
public object ParseResponse(byte[] response, int offset)
{
Tools.AssertEqual(response[offset + 0], slave, "Slave mismatch got {0} expected {1}");
Tools.AssertEqual(response[offset + 1], 6, "Function mismatch got {0} expected {1}");
Tools.AssertEqual(ModbusUtils.GetUShort(response, offset + 2), address, "Address mismatch got {0} expected {1}");
Tools.AssertEqual(ModbusUtils.GetUShort(response, offset + 4), value, "Value mismatch got {0} expected {1}");
return null;
}
public object ApplyTo(IModbusModel model)
{
model.setWO(slave, address, value);
return null;
}
public void FillResponse(byte[] response, int offset, object value)
{
FillRequest(response, offset);
}
public override string ToString()
{
return string.Format("[ModbusF06WriteRegister Slave={0}, Address={1}, Value={2}]", slave, address, value);
}
}
}

View File

@@ -0,0 +1,63 @@
using System;
namespace SharpModbus
{
public class ModbusF15WriteCoils : IModbusCommand
{
private readonly byte slave;
private readonly ushort address;
private readonly bool[] values;
public byte Code { get { return 15; } }
public byte Slave { get { return slave; } }
public ushort Address { get { return address; } }
public bool[] Values { get { return ModbusUtils.Clone(values); } }
public int RequestLength { get { return 7 + ModbusUtils.BytesForBools(values.Length); } }
public int ResponseLength { get { return 6; } }
public ModbusF15WriteCoils(byte slave, ushort address, bool[] values)
{
this.slave = slave;
this.address = address;
this.values = values;
}
public void FillRequest(byte[] request, int offset)
{
FillResponse(request, offset, null);
var bytes = ModbusUtils.EncodeBools(values);
request[offset + 6] = (byte)bytes.Length;
ModbusUtils.Copy(bytes, 0, request, offset + 7, bytes.Length);
}
public object ParseResponse(byte[] response, int offset)
{
Tools.AssertEqual(response[offset + 0], slave, "Slave mismatch got {0} expected {1}");
Tools.AssertEqual(response[offset + 1], 15, "Function mismatch got {0} expected {1}");
Tools.AssertEqual(ModbusUtils.GetUShort(response, offset + 2), address, "Address mismatch got {0} expected {1}");
Tools.AssertEqual(ModbusUtils.GetUShort(response, offset + 4), values.Length, "Coil count mismatch got {0} expected {1}");
return null;
}
public object ApplyTo(IModbusModel model)
{
model.setDOs(slave, address, values);
return null;
}
public void FillResponse(byte[] response, int offset, object value)
{
response[offset + 0] = slave;
response[offset + 1] = 15;
response[offset + 2] = ModbusUtils.High(address);
response[offset + 3] = ModbusUtils.Low(address);
response[offset + 4] = ModbusUtils.High(values.Length);
response[offset + 5] = ModbusUtils.Low(values.Length);
}
public override string ToString()
{
return string.Format("[ModbusF15WriteCoils Slave={0}, Address={1}, Values={2}]", slave, address, values);
}
}
}

View File

@@ -0,0 +1,63 @@
using System;
namespace SharpModbus
{
public class ModbusF16WriteRegisters : IModbusCommand
{
private readonly byte slave;
private readonly ushort address;
private readonly ushort[] values;
public byte Code { get { return 16; } }
public byte Slave { get { return slave; } }
public ushort Address { get { return address; } }
public ushort[] Values { get { return ModbusUtils.Clone(values); } }
public int RequestLength { get { return 7 + ModbusUtils.BytesForWords(values.Length); } }
public int ResponseLength { get { return 6; } }
public ModbusF16WriteRegisters(byte slave, ushort address, ushort[] values)
{
this.slave = slave;
this.address = address;
this.values = values;
}
public void FillRequest(byte[] request, int offset)
{
FillResponse(request, offset, null);
var bytes = ModbusUtils.EncodeWords(values);
request[offset + 6] = (byte)bytes.Length;
ModbusUtils.Copy(bytes, 0, request, offset + 7, bytes.Length);
}
public object ParseResponse(byte[] response, int offset)
{
Tools.AssertEqual(response[offset + 0], slave, "Slave mismatch got {0} expected {1}");
Tools.AssertEqual(response[offset + 1], 16, "Function mismatch got {0} expected {1}");
Tools.AssertEqual(ModbusUtils.GetUShort(response, offset + 2), address, "Address mismatch got {0} expected {1}");
Tools.AssertEqual(ModbusUtils.GetUShort(response, offset + 4), values.Length, "Register count mismatch got {0} expected {1}");
return null;
}
public object ApplyTo(IModbusModel model)
{
model.setWOs(slave, address, values);
return null;
}
public void FillResponse(byte[] response, int offset, object value)
{
response[offset + 0] = slave;
response[offset + 1] = 16;
response[offset + 2] = ModbusUtils.High(address);
response[offset + 3] = ModbusUtils.Low(address);
response[offset + 4] = ModbusUtils.High(values.Length);
response[offset + 5] = ModbusUtils.Low(values.Length);
}
public override string ToString()
{
return string.Format("[ModbusF16WriteRegisters Slave={0}, Address={1}, Values={2}]", slave, address, values);
}
}
}

View File

@@ -0,0 +1,18 @@

using System;
namespace SharpModbus
{
public interface IModbusCommand
{
byte Code { get; }
byte Slave { get; }
ushort Address { get; }
int RequestLength { get; }
int ResponseLength { get; }
void FillRequest(byte[] request, int offset);
object ParseResponse(byte[] response, int offset);
object ApplyTo(IModbusModel model);
void FillResponse(byte[] response, int offset, object value);
}
}

View File

@@ -0,0 +1,39 @@
using System;
namespace SharpModbus
{
public interface IModbusModel
{
void setDI(byte slave, ushort address, bool value);
void setDIs(byte slave, ushort address, bool[] values);
bool getDI(byte slave, ushort address);
bool[] getDIs(byte slave, ushort address, int count);
void setDO(byte slave, ushort address, bool value);
void setDOs(byte slave, ushort address, bool[] values);
bool getDO(byte slave, ushort address);
bool[] getDOs(byte slave, ushort address, int count);
void setWI(byte slave, ushort address, ushort value);
void setWIs(byte slave, ushort address, ushort[] values);
ushort getWI(byte slave, ushort address);
ushort[] getWIs(byte slave, ushort address, int count);
void setWO(byte slave, ushort address, ushort value);
void setWOs(byte slave, ushort address, ushort[] values);
ushort getWO(byte slave, ushort address);
ushort[] getWOs(byte slave, ushort address, int count);
}
}

View File

@@ -0,0 +1,10 @@
using System;
namespace SharpModbus
{
public interface IModbusProtocol
{
IModbusWrapper Wrap(IModbusCommand wrapped);
IModbusWrapper Parse(byte[] request, int offset);
}
}

View File

@@ -0,0 +1,10 @@
using System;
namespace SharpModbus
{
public interface IModbusScanner
{
void Append(byte[] data, int offset, int count);
IModbusWrapper Scan();
}
}

View File

@@ -0,0 +1,10 @@
using System;
namespace SharpModbus
{
public interface IModbusStream : IDisposable
{
void Write(byte[] data);
int Read(byte[] data);
}
}

View File

@@ -0,0 +1,11 @@
using System;
namespace SharpModbus
{
public interface IModbusWrapper : IModbusCommand
{
IModbusCommand Wrapped { get; }
byte[] GetException(byte code);
void CheckException(byte[] respose, int count);
}
}

View File

@@ -0,0 +1,17 @@
using System;
namespace SharpModbus
{
public class ModbusException : Exception
{
private readonly byte code;
public byte Code { get { return code; } }
public ModbusException(byte code) :
base(string.Format("Modbus exception {0}", code))
{
this.code = code;
}
}
}

View File

@@ -0,0 +1,39 @@
using System;
using SharpSerial;
namespace SharpModbus
{
public class ModbusIsolatedStream : IModbusStream
{
private readonly Action<char, byte[], int> monitor;
private readonly SerialProcess serialProcess;
private readonly int timeout;
public ModbusIsolatedStream(object settings, int timeout, Action<char, byte[], int> monitor = null)
{
this.serialProcess = new SerialProcess(settings);
this.timeout = timeout;
this.monitor = monitor;
}
public void Dispose()
{
Tools.Dispose(serialProcess);
}
public void Write(byte[] data)
{
if (monitor != null) monitor('>', data, data.Length);
serialProcess.Write(data);
}
public int Read(byte[] data)
{
var response = serialProcess.Read(data.Length, -1, timeout);
var count = response.Length;
for (var i = 0; i < count; i++) data[i] = response[i];
if (monitor != null) monitor('<', data, count);
return count;
}
}
}

View File

@@ -0,0 +1,132 @@
using MES.Utility.Core;
using System;
using System.Linq;
namespace SharpModbus
{
public class ModbusMaster : IDisposable
{
public delegate void OnSendedData(byte[] bytes, string hexString);
public event OnSendedData OnSended;
public delegate void OnRecivedData(byte[] bytes, string hexString);
public event OnRecivedData OnRecived;
public static ModbusMaster RTU(SerialSettings settings, int timeout = 400)
{
var stream = new ModbusSerialStream(settings, timeout);
var protocol = new ModbusRTUProtocol();
return new ModbusMaster(stream, protocol);
}
public static ModbusMaster IsolatedRTU(SerialSettings settings, int timeout = 400)
{
var stream = new ModbusIsolatedStream(settings, timeout);
var protocol = new ModbusRTUProtocol();
return new ModbusMaster(stream, protocol);
}
public static ModbusMaster TCP(string ip, int port, int timeout = 400)
{
var socket = Tools.ConnectWithTimeout(ip, port, timeout);
var stream = new ModbusSocketStream(socket, timeout);
var protocol = new ModbusTCPProtocol();
return new ModbusMaster(stream, protocol);
}
private readonly IModbusProtocol protocol;
private readonly IModbusStream stream;
public ModbusMaster(IModbusStream stream, IModbusProtocol protocol)
{
this.stream = stream;
this.protocol = protocol;
}
public void Dispose()
{
Tools.Dispose(stream);
}
public bool ReadCoil(byte slave, ushort address)
{
return ReadCoils(slave, address, 1)[0]; //there is no code for single read
}
public bool ReadInput(byte slave, ushort address)
{
return ReadInputs(slave, address, 1)[0]; //there is no code for single read
}
public ushort ReadInputRegister(byte slave, ushort address)
{
return ReadInputRegisters(slave, address, 1)[0]; //there is no code for single read
}
public ushort ReadHoldingRegister(byte slave, ushort address)
{
return ReadHoldingRegisters(slave, address, 1)[0]; //there is no code for single read
}
public bool[] ReadCoils(byte slave, ushort address, ushort count)
{
return Execute(new ModbusF01ReadCoils(slave, address, count)) as bool[];
}
public bool[] ReadInputs(byte slave, ushort address, ushort count)
{
return Execute(new ModbusF02ReadInputs(slave, address, count)) as bool[];
}
public ushort[] ReadInputRegisters(byte slave, ushort address, ushort count)
{
return Execute(new ModbusF04ReadInputRegisters(slave, address, count)) as ushort[];
}
public ushort[] ReadHoldingRegisters(byte slave, ushort address, ushort count)
{
return Execute(new ModbusF03ReadHoldingRegisters(slave, address, count)) as ushort[];
}
public void WriteCoil(byte slave, ushort address, bool value)
{
Execute(new ModbusF05WriteCoil(slave, address, value));
}
public void WriteRegister(byte slave, ushort address, ushort value)
{
Execute(new ModbusF06WriteRegister(slave, address, value));
}
public void WriteCoils(byte slave, ushort address, params bool[] values)
{
Execute(new ModbusF15WriteCoils(slave, address, values));
}
public void WriteRegisters(byte slave, ushort address, params ushort[] values)
{
Execute(new ModbusF16WriteRegisters(slave, address, values));
}
private object Execute(IModbusCommand cmd)
{
var wrapper = protocol.Wrap(cmd);
var request = new byte[wrapper.RequestLength];
var response = new byte[wrapper.ResponseLength];
wrapper.FillRequest(request, 0);
OnSended?.Invoke(request, DataHexString(request));
stream.Write(request);
var count = stream.Read(response);
OnRecived?.Invoke(response, DataHexString(response));
if (count < response.Length) wrapper.CheckException(response, count);
return wrapper.ParseResponse(response, 0);
}
private string DataHexString(byte[] bytes)
{
if (bytes == null)
return string.Empty;
return bytes.Select(it => Convert.ToString(it, 16).PadLeft(2, '0').ToUpper()).ToList().GetStrArray(" ");
}
}
}

View File

@@ -0,0 +1,152 @@
using System;
using System.Collections.Generic;
namespace SharpModbus
{
public enum ModbusIoType
{
DI,
DO,
WO,
WI
}
public class ModbusModel : IModbusModel
{
private readonly IDictionary<string, bool> digitals = new Dictionary<string, bool>();
private readonly IDictionary<string, ushort> words = new Dictionary<string, ushort>();
public void setDI(byte slave, ushort address, bool value)
{
var key = Key(ModbusIoType.DI, slave, address);
digitals[key] = value;
}
public void setDIs(byte slave, ushort address, bool[] values)
{
for (var i = 0; i < values.Length; i++)
{
var key = Key(ModbusIoType.DI, slave, address + i);
digitals[key] = values[i];
}
}
public bool getDI(byte slave, ushort address)
{
var key = Key(ModbusIoType.DI, slave, address);
return digitals[key];
}
public bool[] getDIs(byte slave, ushort address, int count)
{
var values = new bool[count];
for (var i = 0; i < values.Length; i++)
{
var key = Key(ModbusIoType.DI, slave, address + i);
values[i] = digitals[key];
}
return values;
}
public void setDO(byte slave, ushort address, bool value)
{
var key = Key(ModbusIoType.DO, slave, address);
digitals[key] = value;
}
public void setDOs(byte slave, ushort address, bool[] values)
{
for (var i = 0; i < values.Length; i++)
{
var key = Key(ModbusIoType.DO, slave, address + i);
digitals[key] = values[i];
}
}
public bool getDO(byte slave, ushort address)
{
var key = Key(ModbusIoType.DO, slave, address);
return digitals[key];
}
public bool[] getDOs(byte slave, ushort address, int count)
{
var values = new bool[count];
for (var i = 0; i < values.Length; i++)
{
var key = Key(ModbusIoType.DO, slave, address + i);
values[i] = digitals[key];
}
return values;
}
public void setWI(byte slave, ushort address, ushort value)
{
var key = Key(ModbusIoType.WI, slave, address);
words[key] = value;
}
public void setWIs(byte slave, ushort address, ushort[] values)
{
for (var i = 0; i < values.Length; i++)
{
var key = Key(ModbusIoType.WI, slave, address + i);
words[key] = values[i];
}
}
public ushort getWI(byte slave, ushort address)
{
var key = Key(ModbusIoType.WI, slave, address);
return words[key];
}
public ushort[] getWIs(byte slave, ushort address, int count)
{
var values = new ushort[count];
for (var i = 0; i < values.Length; i++)
{
var key = Key(ModbusIoType.WI, slave, address + i);
values[i] = words[key];
}
return values;
}
public void setWO(byte slave, ushort address, ushort value)
{
var key = Key(ModbusIoType.WO, slave, address);
words[key] = value;
}
public void setWOs(byte slave, ushort address, ushort[] values)
{
for (var i = 0; i < values.Length; i++)
{
var key = Key(ModbusIoType.WO, slave, address + i);
words[key] = values[i];
}
}
public ushort getWO(byte slave, ushort address)
{
var key = Key(ModbusIoType.WO, slave, address);
return words[key];
}
public ushort[] getWOs(byte slave, ushort address, int count)
{
var values = new ushort[count];
for (var i = 0; i < values.Length; i++)
{
var key = Key(ModbusIoType.WO, slave, address + i);
values[i] = words[key];
}
return values;
}
private string Key(ModbusIoType type, byte slave, int address)
{
return string.Format("{0},{1},{2}", slave, type, address);
}
}
}

View File

@@ -0,0 +1,90 @@
using System;
namespace SharpModbus
{
public static class ModbusParser
{
public static IModbusCommand Parse(byte[] request, int offset)
{
var slave = request[offset + 0];
var code = request[offset + 1];
var address = ModbusUtils.GetUShort(request, offset + 2);
switch (code)
{
case 1:
return Parse01(slave, code, address, request, offset);
case 2:
return Parse02(slave, code, address, request, offset);
case 3:
return Parse03(slave, code, address, request, offset);
case 4:
return Parse04(slave, code, address, request, offset);
case 5:
return Parse05(slave, code, address, request, offset);
case 6:
return Parse06(slave, code, address, request, offset);
case 15:
return Parse15(slave, code, address, request, offset);
case 16:
return Parse16(slave, code, address, request, offset);
}
throw Tools.Make("Unsupported function code {0}", code);
}
private static IModbusCommand Parse01(byte slave, byte code, ushort address, byte[] request, int offset)
{
var count = ModbusUtils.GetUShort(request, offset + 4);
return new ModbusF01ReadCoils(slave, address, count);
}
private static IModbusCommand Parse02(byte slave, byte code, ushort address, byte[] request, int offset)
{
var count = ModbusUtils.GetUShort(request, offset + 4);
return new ModbusF02ReadInputs(slave, address, count);
}
private static IModbusCommand Parse03(byte slave, byte code, ushort address, byte[] request, int offset)
{
var count = ModbusUtils.GetUShort(request, offset + 4);
return new ModbusF03ReadHoldingRegisters(slave, address, count);
}
private static IModbusCommand Parse04(byte slave, byte code, ushort address, byte[] request, int offset)
{
var count = ModbusUtils.GetUShort(request, offset + 4);
return new ModbusF04ReadInputRegisters(slave, address, count);
}
private static IModbusCommand Parse05(byte slave, byte code, ushort address, byte[] request, int offset)
{
var value = ModbusUtils.DecodeBool(request[offset + 4]);
var zero = request[offset + 5];
Tools.AssertEqual(zero, 0, "Zero mismatch got {0} expected {1}");
return new ModbusF05WriteCoil(slave, address, value);
}
private static IModbusCommand Parse06(byte slave, byte code, ushort address, byte[] request, int offset)
{
var value = ModbusUtils.GetUShort(request, offset + 4);
return new ModbusF06WriteRegister(slave, address, value);
}
private static IModbusCommand Parse15(byte slave, byte code, ushort address, byte[] request, int offset)
{
var count = ModbusUtils.GetUShort(request, offset + 4);
var values = ModbusUtils.DecodeBools(request, offset + 7, count);
var bytes = request[offset + 6];
Tools.AssertEqual(ModbusUtils.BytesForBools(count), bytes, "Byte count mismatch got {0} expected {1}");
return new ModbusF15WriteCoils(slave, address, values);
}
private static IModbusCommand Parse16(byte slave, byte code, ushort address, byte[] request, int offset)
{
var count = ModbusUtils.GetUShort(request, offset + 4);
var values = ModbusUtils.DecodeWords(request, offset + 7, count);
var bytes = request[offset + 6];
Tools.AssertEqual(ModbusUtils.BytesForWords(count), bytes, "Byte count mismatch got {0} expected {1}");
return new ModbusF16WriteRegisters(slave, address, values);
}
}
}

View File

@@ -0,0 +1,21 @@
using System;
namespace SharpModbus
{
public class ModbusRTUProtocol : IModbusProtocol
{
public IModbusWrapper Wrap(IModbusCommand wrapped)
{
return new ModbusRTUWrapper(wrapped);
}
public IModbusWrapper Parse(byte[] request, int offset)
{
var wrapped = ModbusParser.Parse(request, offset);
var crc = ModbusUtils.CRC16(request, offset, wrapped.RequestLength);
Tools.AssertEqual(crc, ModbusUtils.GetUShortLittleEndian(request, offset + wrapped.RequestLength),
"CRC mismatch {0:X4} expected {1:X4}");
return new ModbusRTUWrapper(wrapped);
}
}
}

View File

@@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
namespace SharpModbus
{
public class ModbusRTUScanner : IModbusScanner
{
private readonly ModbusRTUProtocol protocol = new ModbusRTUProtocol();
private readonly List<byte> buffer = new List<byte>();
public void Append(byte[] data, int offset, int count)
{
for (var i = 0; i < count; i++) buffer.Add(data[offset + i]);
}
public IModbusWrapper Scan()
{
//01,02,03,04,05,06 have 6 + 2(CRC)
//15,16 have 6 + 1(len) + len + 2(CRC)
if (buffer.Count >= 8)
{
var code = buffer[1];
CheckCode(code);
var length = 8;
if (HasBytesAt6(code)) length += 1 + buffer[6];
if (buffer.Count >= length)
{
var request = buffer.GetRange(0, length).ToArray();
buffer.RemoveRange(0, length);
return protocol.Parse(request, 0);
}
}
return null; //not enough data to parse
}
bool HasBytesAt6(byte code)
{
return "15,16".Contains(code.ToString("00"));
}
void CheckCode(byte code)
{
var valid = "01,02,03,04,05,06,15,16".Contains(code.ToString("00"));
if (!valid) Tools.Throw("Unsupported code {0}", code);
}
}
}

View File

@@ -0,0 +1,85 @@
using System;
namespace SharpModbus
{
public class ModbusRTUWrapper : IModbusWrapper
{
private readonly IModbusCommand wrapped;
public byte Code { get { return wrapped.Code; } }
public byte Slave { get { return wrapped.Slave; } }
public ushort Address { get { return wrapped.Address; } }
public IModbusCommand Wrapped { get { return wrapped; } }
public int RequestLength { get { return wrapped.RequestLength + 2; } }
public int ResponseLength { get { return wrapped.ResponseLength + 2; } }
public ModbusRTUWrapper(IModbusCommand wrapped)
{
this.wrapped = wrapped;
}
public void FillRequest(byte[] request, int offset)
{
wrapped.FillRequest(request, offset);
var crc = ModbusUtils.CRC16(request, offset, wrapped.RequestLength);
request[offset + wrapped.RequestLength + 0] = ModbusUtils.Low(crc);
request[offset + wrapped.RequestLength + 1] = ModbusUtils.High(crc);
}
public object ParseResponse(byte[] response, int offset)
{
var crc1 = ModbusUtils.CRC16(response, offset, wrapped.ResponseLength);
//crc is little endian page 13 http://modbus.org/docs/Modbus_over_serial_line_V1_02.pdf
var crc2 = ModbusUtils.GetUShortLittleEndian(response, offset + wrapped.ResponseLength);
Tools.AssertEqual(crc2, crc1, "CRC mismatch got {0:X4} expected {1:X4}");
return wrapped.ParseResponse(response, offset);
}
public object ApplyTo(IModbusModel model)
{
return wrapped.ApplyTo(model);
}
public void FillResponse(byte[] response, int offset, object value)
{
wrapped.FillResponse(response, offset, value);
var crc = ModbusUtils.CRC16(response, offset, wrapped.ResponseLength);
response[offset + wrapped.ResponseLength + 0] = ModbusUtils.Low(crc);
response[offset + wrapped.ResponseLength + 1] = ModbusUtils.High(crc);
}
public byte[] GetException(byte code)
{
var exception = new byte[5];
exception[0] = wrapped.Slave;
exception[1] = (byte)(wrapped.Code | 0x80);
exception[2] = code;
var crc = ModbusUtils.CRC16(exception, 0, 3);
exception[3] = ModbusUtils.Low(crc);
exception[4] = ModbusUtils.High(crc);
return exception;
}
public void CheckException(byte[] response, int count)
{
if (count < 5) Tools.Throw("Partial packet exception got {0} expected >= {1}", count, 5);
var offset = 0;
var code = response[offset + 1];
if ((code & 0x80) != 0)
{
Tools.AssertEqual(response[offset + 0], wrapped.Slave, "Slave mismatch got {0} expected {1}");
Tools.AssertEqual(code & 0x7F, wrapped.Code, "Code mismatch got {0} expected {1}");
var crc1 = ModbusUtils.CRC16(response, offset, 3);
//crc is little endian page 13 http://modbus.org/docs/Modbus_over_serial_line_V1_02.pdf
var crc2 = ModbusUtils.GetUShortLittleEndian(response, offset + 3);
Tools.AssertEqual(crc2, crc1, "CRC mismatch got {0:X4} expected {1:X4}");
throw new ModbusException(response[offset + 2]);
}
}
public override string ToString()
{
return string.Format("[ModbusRTUWrapper Wrapped={0}]", wrapped);
}
}
}

View File

@@ -0,0 +1,39 @@
using System;
using SharpSerial;
namespace SharpModbus
{
public class ModbusSerialStream : IModbusStream
{
private readonly Action<char, byte[], int> monitor;
private readonly SerialDevice serialDevice;
private readonly int timeout;
public ModbusSerialStream(SerialSettings settings, int timeout, Action<char, byte[], int> monitor = null)
{
this.serialDevice = new SerialDevice(settings);
this.timeout = timeout;
this.monitor = monitor;
}
public void Dispose()
{
Tools.Dispose(serialDevice);
}
public void Write(byte[] data)
{
if (monitor != null) monitor('>', data, data.Length);
serialDevice.Write(data);
}
public int Read(byte[] data)
{
var response = serialDevice.Read(data.Length, -1, timeout);
var count = response.Length;
for (var i = 0; i < count; i++) data[i] = response[i];
if (monitor != null) monitor('<', data, count);
return count;
}
}
}

View File

@@ -0,0 +1,56 @@
using System;
using System.Threading;
using System.Net.Sockets;
namespace SharpModbus
{
public class ModbusSocketStream : IModbusStream
{
private readonly TcpClient socket;
private readonly Action<char, byte[], int> monitor;
public ModbusSocketStream(TcpClient socket, int timeout, Action<char, byte[], int> monitor = null)
{
socket.ReceiveTimeout = timeout;
socket.SendTimeout = timeout;
this.monitor = monitor;
this.socket = socket;
}
public void Dispose()
{
Tools.Dispose(socket);
}
public void Write(byte[] data)
{
//implicit discard to allow retry after timeout as per #5
while (socket.Available > 0) socket.GetStream().ReadByte();
if (monitor != null) monitor('>', data, data.Length);
socket.GetStream().Write(data, 0, data.Length);
}
public int Read(byte[] data)
{
var count = 0;
var dl = DateTime.Now.AddMilliseconds(socket.ReceiveTimeout);
while (count < data.Length)
{
var available = socket.Available;
if (available == 0)
{
if (DateTime.Now > dl) break;
Thread.Sleep(1);
}
else
{
var size = (int)Math.Min(available, data.Length - count);
count += socket.GetStream().Read(data, count, size);
dl = DateTime.Now; //should come in single packet
}
}
if (monitor != null) monitor('<', data, count);
return count;
}
}
}

View File

@@ -0,0 +1,29 @@
using System;
namespace SharpModbus
{
public class ModbusTCPProtocol : IModbusProtocol
{
private ushort transactionId = 0;
public ushort TransactionId
{
get { return transactionId; }
set { transactionId = value; }
}
public IModbusWrapper Wrap(IModbusCommand wrapped)
{
return new ModbusTCPWrapper(wrapped, transactionId++);
}
public IModbusWrapper Parse(byte[] request, int offset)
{
var wrapped = ModbusParser.Parse(request, offset + 6);
Tools.AssertEqual(wrapped.RequestLength, ModbusUtils.GetUShort(request, offset + 4),
"RequestLength mismatch got {0} expected {1}");
var transaction = ModbusUtils.GetUShort(request, offset);
return new ModbusTCPWrapper(wrapped, transaction);
}
}
}

View File

@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
namespace SharpModbus
{
public class ModbusTCPScanner : IModbusScanner
{
private readonly ModbusTCPProtocol protocol = new ModbusTCPProtocol();
private readonly List<byte> buffer = new List<byte>();
public void Append(byte[] data, int offset, int count)
{
for (var i = 0; i < count; i++) buffer.Add(data[offset + i]);
}
public IModbusWrapper Scan()
{
if (buffer.Count >= 6)
{
var length = ModbusUtils.GetUShort(buffer[4], buffer[5]);
if (buffer.Count >= 6 + length)
{
var request = buffer.GetRange(0, 6 + length).ToArray();
buffer.RemoveRange(0, 6 + length);
return protocol.Parse(request, 0);
}
}
return null; //not enough data to parse
}
}
}

View File

@@ -0,0 +1,92 @@
using System;
namespace SharpModbus
{
public class ModbusTCPWrapper : IModbusWrapper
{
private readonly IModbusCommand wrapped;
private readonly ushort transactionId;
public byte Code { get { return wrapped.Code; } }
public byte Slave { get { return wrapped.Slave; } }
public ushort Address { get { return wrapped.Address; } }
public IModbusCommand Wrapped { get { return wrapped; } }
public ushort TransactionId { get { return transactionId; } }
public int RequestLength { get { return wrapped.RequestLength + 6; } }
public int ResponseLength { get { return wrapped.ResponseLength + 6; } }
public ModbusTCPWrapper(IModbusCommand wrapped, ushort transactionId)
{
this.wrapped = wrapped;
this.transactionId = transactionId;
}
public void FillRequest(byte[] request, int offset)
{
request[offset + 0] = ModbusUtils.High(transactionId);
request[offset + 1] = ModbusUtils.Low(transactionId);
request[offset + 2] = 0;
request[offset + 3] = 0;
request[offset + 4] = ModbusUtils.High(wrapped.RequestLength);
request[offset + 5] = ModbusUtils.Low(wrapped.RequestLength);
wrapped.FillRequest(request, offset + 6);
}
public object ParseResponse(byte[] response, int offset)
{
Tools.AssertEqual(ModbusUtils.GetUShort(response, offset + 0), transactionId, "TransactionId mismatch got {0} expected {1}");
Tools.AssertEqual(ModbusUtils.GetUShort(response, offset + 2), 0, "Zero mismatch got {0} expected {1}");
Tools.AssertEqual(ModbusUtils.GetUShort(response, offset + 4), wrapped.ResponseLength, "Length mismatch got {0} expected {1}");
return wrapped.ParseResponse(response, offset + 6);
}
public object ApplyTo(IModbusModel model)
{
return wrapped.ApplyTo(model);
}
public void FillResponse(byte[] response, int offset, object value)
{
response[offset + 0] = ModbusUtils.High(transactionId);
response[offset + 1] = ModbusUtils.Low(transactionId);
response[offset + 2] = 0;
response[offset + 3] = 0;
response[offset + 4] = ModbusUtils.High(wrapped.ResponseLength);
response[offset + 5] = ModbusUtils.Low(wrapped.ResponseLength);
wrapped.FillResponse(response, offset + 6, value);
}
public byte[] GetException(byte code)
{
var exception = new byte[9];
exception[0] = ModbusUtils.High(transactionId);
exception[1] = ModbusUtils.Low(transactionId);
exception[2] = 0;
exception[3] = 0;
exception[4] = ModbusUtils.High(3);
exception[5] = ModbusUtils.Low(3);
exception[6 + 0] = wrapped.Slave;
exception[6 + 1] = (byte)(wrapped.Code | 0x80);
exception[6 + 2] = code;
return exception;
}
public void CheckException(byte[] response, int count)
{
if (count < 9) Tools.Throw("Partial packet exception got {0} expected >= {1}", count, 9);
var offset = 6;
var code = response[offset + 1];
if ((code & 0x80) != 0)
{
Tools.AssertEqual(response[offset + 0], wrapped.Slave, "Slave mismatch got {0} expected {1}");
Tools.AssertEqual(code & 0x7F, wrapped.Code, "Code mismatch got {0} expected {1}");
throw new ModbusException(response[offset + 2]);
}
}
public override string ToString()
{
return string.Format("[ModbusTCPWrapper Wrapped={0}, TransactionId={1}]", wrapped, transactionId);
}
}
}

View File

@@ -0,0 +1,60 @@
using System;
using System.Net.Sockets;
namespace SharpModbus
{
public class SerialSettings : SharpSerial.SerialSettings { }
public static class Tools
{
public static void AssertEqual(int a, int b, string format)
{
if (a != b) Tools.Throw(format, a, b);
}
public static TcpClient ConnectWithTimeout(string host, int port, int timeout)
{
var socket = new TcpClient();
try
{
var result = socket.BeginConnect(host, port, null, null);
var connected = result.AsyncWaitHandle.WaitOne(timeout, true);
if (!connected) Tools.Throw("Timeout connecting to {0}:{1}", host, port);
socket.EndConnect(result);
return socket;
}
catch (Exception ex)
{
Tools.Dispose(socket);
throw ex;
}
}
public static void Dispose(IDisposable disposable)
{
try { if (disposable != null) disposable.Dispose(); }
catch (Exception) { }
}
public static Exception Make(string format, params object[] args)
{
var message = format;
if (args.Length > 0) message = string.Format(format, args);
return new Exception(message);
}
public static void Throw(string format, params object[] args)
{
var message = format;
if (args.Length > 0) message = string.Format(format, args);
throw new Exception(message);
}
public static void Throw(Exception inner, string format, params object[] args)
{
var message = format;
if (args.Length > 0) message = string.Format(format, args);
throw new Exception(message, inner);
}
}
}

View File

@@ -0,0 +1,169 @@
using System;
namespace SharpModbus
{
public static class ModbusUtils
{
public static ushort CRC16(byte[] bytes, int offset, int count)
{
ushort crc = 0xFFFF;
for (int pos = 0; pos < count; pos++)
{
crc ^= (ushort)bytes[pos + offset];
for (int i = 8; i > 0; i--)
{
if ((crc & 0x0001) != 0)
{
crc >>= 1;
crc ^= 0xA001;
}
else
crc >>= 1;
}
}
return crc;
}
public static byte EncodeBool(bool value)
{
return (byte)(value ? 0xFF : 0x00);
}
public static bool DecodeBool(byte value)
{
return (value != 0x00);
}
public static byte[] EncodeBools(bool[] bools)
{
var count = BytesForBools(bools.Length);
var bytes = new byte[count];
for (var i = 0; i < count; i++)
{
bytes[i] = 0;
}
for (var i = 0; i < bools.Length; i++)
{
var v = bools[i];
if (v)
{
var bi = i / 8;
bytes[bi] |= (byte)(1 << (i % 8));
}
}
return bytes;
}
public static byte[] EncodeWords(ushort[] words)
{
var count = 2 * words.Length;
var bytes = new byte[count];
for (var i = 0; i < count; i++)
{
bytes[i] = 0;
}
for (var i = 0; i < words.Length; i++)
{
bytes[2 * i + 0] = (byte)((words[i] >> 8) & 0xff);
bytes[2 * i + 1] = (byte)((words[i] >> 0) & 0xff);
}
return bytes;
}
public static bool[] DecodeBools(byte[] packet, int offset, ushort count)
{
var bools = new bool[count];
var bytes = BytesForBools(count);
for (var i = 0; i < bytes; i++)
{
var bits = count >= 8 ? 8 : count % 8;
var b = packet[offset + i];
ByteToBools(b, bools, bools.Length - count, bits);
count -= (ushort)bits;
}
return bools;
}
public static ushort[] DecodeWords(byte[] packet, int offset, ushort count)
{
var results = new ushort[count];
for (int i = 0; i < count; i++)
{
results[i] = (ushort)(packet[offset + 2 * i] << 8 | packet[offset + 2 * i + 1]);
}
return results;
}
private static void ByteToBools(byte b, bool[] bools, int offset, int count)
{
for (int i = 0; i < count; i++)
bools[offset + i] = ((b >> i) & 0x01) == 1;
}
public static byte BytesForWords(int count)
{
return (byte)(2 * count);
}
public static byte BytesForBools(int count)
{
return (byte)(count == 0 ? 0 : (count - 1) / 8 + 1);
}
public static byte High(int value)
{
return (byte)((value >> 8) & 0xff);
}
public static byte Low(int value)
{
return (byte)((value >> 0) & 0xff);
}
public static ushort GetUShort(byte bh, byte bl)
{
return (ushort)(
((bh << 8) & 0xFF00)
| (bl & 0xff)
);
}
public static ushort GetUShort(byte[] bytes, int offset)
{
return (ushort)(
((bytes[offset + 0] << 8) & 0xFF00)
| (bytes[offset + 1] & 0xff)
);
}
public static ushort GetUShortLittleEndian(byte[] bytes, int offset)
{
return (ushort)(
((bytes[offset + 1] << 8) & 0xFF00)
| (bytes[offset + 0] & 0xff)
);
}
public static void Copy(byte[] src, int srcOffset, byte[] dst, int dstOffset, int count)
{
for (var i = 0; i < count; i++)
dst[dstOffset + i] = src[srcOffset + i];
}
public static bool[] Clone(bool[] values)
{
var clone = new bool[values.Length];
for (var i = 0; i < values.Length; i++)
clone[i] = values[i];
return clone;
}
public static ushort[] Clone(ushort[] values)
{
var clone = new ushort[values.Length];
for (var i = 0; i < values.Length; i++)
clone[i] = values[i];
return clone;
}
}
}