初始化上传

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,211 @@
/*
Copyright (c) 2018-2020 Rossmann-Engineering
Permission is hereby granted, free of charge,
to any person obtaining a copy of this software
and associated documentation files (the "Software"),
to deal in the Software without restriction,
including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit
persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission
notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System;
using System.Runtime.Serialization;
namespace EasyModbus.Exceptions
{
/// <summary>
/// Exception to be thrown if serial port is not opened
/// </summary>
public class SerialPortNotOpenedException : ModbusException
{
public SerialPortNotOpenedException()
: base()
{
}
public SerialPortNotOpenedException(string message)
: base(message)
{
}
public SerialPortNotOpenedException(string message, Exception innerException)
: base(message, innerException)
{
}
protected SerialPortNotOpenedException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
}
/// <summary>
/// Exception to be thrown if Connection to Modbus device failed
/// </summary>
public class ConnectionException : ModbusException
{
public ConnectionException()
: base()
{
}
public ConnectionException(string message)
: base(message)
{
}
public ConnectionException(string message, Exception innerException)
: base(message, innerException)
{
}
protected ConnectionException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
}
/// <summary>
/// Exception to be thrown if Modbus Server returns error code "Function code not supported"
/// </summary>
public class FunctionCodeNotSupportedException : ModbusException
{
public FunctionCodeNotSupportedException()
: base()
{
}
public FunctionCodeNotSupportedException(string message)
: base(message)
{
}
public FunctionCodeNotSupportedException(string message, Exception innerException)
: base(message, innerException)
{
}
protected FunctionCodeNotSupportedException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
}
/// <summary>
/// Exception to be thrown if Modbus Server returns error code "quantity invalid"
/// </summary>
public class QuantityInvalidException : ModbusException
{
public QuantityInvalidException()
: base()
{
}
public QuantityInvalidException(string message)
: base(message)
{
}
public QuantityInvalidException(string message, Exception innerException)
: base(message, innerException)
{
}
protected QuantityInvalidException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
}
/// <summary>
/// Exception to be thrown if Modbus Server returns error code "starting adddress and quantity invalid"
/// </summary>
public class StartingAddressInvalidException : ModbusException
{
public StartingAddressInvalidException()
: base()
{
}
public StartingAddressInvalidException(string message)
: base(message)
{
}
public StartingAddressInvalidException(string message, Exception innerException)
: base(message, innerException)
{
}
protected StartingAddressInvalidException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
}
/// <summary>
/// Exception to be thrown if Modbus Server returns error code "Function Code not executed (0x04)"
/// </summary>
public class ModbusException : Exception
{
public ModbusException()
: base()
{
}
public ModbusException(string message)
: base(message)
{
}
public ModbusException(string message, Exception innerException)
: base(message, innerException)
{
}
protected ModbusException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
}
/// <summary>
/// Exception to be thrown if CRC Check failed
/// </summary>
public class CRCCheckFailedException : ModbusException
{
public CRCCheckFailedException()
: base()
{
}
public CRCCheckFailedException(string message)
: base(message)
{
}
public CRCCheckFailedException(string message, Exception innerException)
: base(message, innerException)
{
}
protected CRCCheckFailedException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,120 @@
/*
Copyright (c) 2018-2020 Rossmann-Engineering
Permission is hereby granted, free of charge,
to any person obtaining a copy of this software
and associated documentation files (the "Software"),
to deal in the Software without restriction,
including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit
persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission
notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System;
using System.Collections.Generic;
using System.Text;
namespace EasyModbus
{
/// <summary>
/// Store Log-Data in a File
/// </summary>
public sealed class StoreLogData
{
private String filename = null;
private static volatile StoreLogData instance;
private static object syncObject = new Object();
/// <summary>
/// Private constructor; Ensures the access of the class only via "instance"
/// </summary>
private StoreLogData()
{
}
/// <summary>
/// Returns the instance of the class (singleton)
/// </summary>
/// <returns>instance (Singleton)</returns>
public static StoreLogData Instance
{
get
{
if (instance == null)
{
lock (syncObject)
{
if (instance == null)
instance = new StoreLogData();
}
}
return instance;
}
}
/// <summary>
/// Store message in Log-File
/// </summary>
/// <param name="message">Message to append to the Log-File</param>
public void Store(String message)
{
if (this.filename == null)
return;
using (System.IO.StreamWriter file =
new System.IO.StreamWriter(Filename, true))
{
file.WriteLine(message);
}
}
/// <summary>
/// Store message in Log-File including Timestamp
/// </summary>
/// <param name="message">Message to append to the Log-File</param>
/// <param name="timestamp">Timestamp to add to the same Row</param>
public void Store(String message, DateTime timestamp)
{
try
{
using (System.IO.StreamWriter file =
new System.IO.StreamWriter(Filename, true))
{
file.WriteLine(timestamp.ToString("dd.MM.yyyy H:mm:ss.ff ") + message);
}
}
catch (Exception e)
{
}
}
/// <summary>
/// Gets or Sets the Filename to Store Strings in a File
/// </summary>
public string Filename
{
get
{
return filename;
}
set
{
filename = value;
}
}
}
}

File diff suppressed because it is too large Load Diff

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;
}
}
}

View File

@@ -0,0 +1,47 @@
using System;
using System.Diagnostics;
namespace SharpSerial
{
public static class Stdio
{
private static bool traceTimed = false;
private static bool traceEnabled = false;
private static readonly object locker = new object();
public static string ReadLine() => Console.ReadLine();
public static void WriteLine(string format, params object[] args)
{
lock (locker)
{
Console.Out.WriteLine(format, args);
Console.Out.Flush();
}
}
[Conditional("DEBUG")]
public static void Trace(string format, params object[] args)
{
if (traceEnabled)
{
lock (locker)
{
if (traceTimed)
{
Console.Error.Write(DateTime.Now.ToString("HH:mm:ss.fff"));
Console.Error.Write(" ");
}
Console.Error.WriteLine(format, args);
Console.Error.Flush();
}
}
}
public static void EnableTrace(bool enable, bool timed)
{
traceTimed = timed;
traceEnabled = enable;
}
}
}

View File

@@ -0,0 +1,99 @@
using System;
using System.Text;
namespace SharpSerial
{
public static class Tools
{
public static void SetupGlobalCatcher()
{
AppDomain.CurrentDomain.UnhandledException += ExceptionHandler;
}
public static void ExceptionHandler(object sender, UnhandledExceptionEventArgs args)
{
ExceptionHandler(args.ExceptionObject as Exception);
}
public static void ExceptionHandler(Exception ex)
{
Try(() => Stdio.Trace("!{0}", ex.ToString()));
Try(() => Stdio.WriteLine("!{0}", ex.ToString()));
Environment.Exit(1);
}
public static string StringHex(byte[] data)
{
var sb = new StringBuilder();
sb.Append("<");
foreach (var b in data) sb.Append(b.ToString("X2"));
return sb.ToString();
}
public static byte[] ParseHex(string text)
{
Assert(text.Length % 2 == 1, "Odd length expected for {0}:{1}", text.Length, text);
var bytes = new byte[text.Length / 2];
for (var i = 0; i < bytes.Length; i++)
{
var b2 = text.Substring(1 + i * 2, 2);
bytes[i] = Convert.ToByte(b2, 16);
}
return bytes;
}
public static void SetProperty(object target, string line)
{
var parts = line.Split(new char[] { '=' });
if (parts.Length != 2) throw Make("Expected 2 parts in {0}", Readable(line));
var propertyName = parts[0];
var propertyValue = parts[1];
var property = target.GetType().GetProperty(propertyName);
if (property == null) throw Make("Property not found {0}", Readable(propertyName));
var value = FromString(property.PropertyType, propertyValue);
property.SetValue(target, value, null);
}
public static object FromString(Type type, string text)
{
if (type.IsEnum) return Enum.Parse(type, text);
return Convert.ChangeType(text, type);
}
public static void Try(Action action)
{
try
{
action.Invoke();
}
catch (Exception)
{
//no clear use case for cleanup exception
}
}
public static Exception Make(string format, params object[] args)
{
var line = format;
if (args.Length > 0) line = string.Format(format, args);
return new Exception(line);
}
public static string Readable(string text)
{
var sb = new StringBuilder();
foreach (var c in text)
{
if (Char.IsControl(c)) sb.Append(((int)c).ToString("X2"));
else if (Char.IsWhiteSpace(c)) sb.Append(((int)c).ToString("X2"));
else sb.Append(c);
}
return sb.ToString();
}
public static void Assert(bool condition, string format, params object[] args)
{
if (!condition) throw Make(format, args);
}
}
}

View File

@@ -0,0 +1,97 @@
using System;
using System.IO;
using System.Threading;
using System.Collections.Generic;
using System.IO.Ports;
namespace SharpSerial
{
// com0com BytesToRead is UNRELIABLE
// Solution based on BaseStream and influenced by
// https://www.sparxeng.com/blog/software/must-use-net-system-io-ports-serialport
// https://www.vgies.com/a-reliable-serial-port-in-c/
public class SerialDevice : ISerialStream, IDisposable
{
private readonly SerialPort serial;
private readonly List<byte> list;
private readonly Queue<byte> queue;
private readonly byte[] buffer;
public SerialDevice(object settings)
{
this.buffer = new byte[256];
this.list = new List<byte>(256);
this.queue = new Queue<byte>(256);
this.serial = new SerialPort();
//init serial port and launch async reader
SerialSettings.CopyProperties(settings, serial);
serial.Open();
//DiscardInBuffer not needed by FTDI and ignored by com0com
var stream = serial.BaseStream;
//unavailable after closed so pass it
stream.BeginRead(buffer, 0, buffer.Length, ReadCallback, stream);
}
public void Dispose()
{
Tools.Try(serial.Close);
Tools.Try(serial.Dispose);
}
public void Write(byte[] data)
{
var stream = serial.BaseStream;
stream.Write(data, 0, data.Length);
//always flush to allow sync by following read available
stream.Flush();
}
public byte[] Read(int size, int eop, int toms)
{
list.Clear();
var dl = DateTime.Now.AddMilliseconds(toms);
while (true)
{
int b = ReadByte();
if (b == -1)
{
//toms=0 should return immediately with available
if (DateTime.Now >= dl) break;
Thread.Sleep(1);
continue;
}
list.Add((byte)b);
if (eop >= 0 && b == eop) break;
if (size >= 0 && list.Count >= size) break;
dl = DateTime.Now.AddMilliseconds(toms);
}
return list.ToArray();
}
private int ReadByte()
{
lock (queue)
{
if (queue.Count == 0) return -1;
return queue.Dequeue();
}
}
private void ReadCallback(IAsyncResult ar)
{
Tools.Try(() =>
{
//try needed to avoid triggering the domain unhandled
//exception handler when used as standalone stream
var stream = ar.AsyncState as Stream;
int count = stream.EndRead(ar);
if (count > 0) //0 for closed stream
{
lock (queue) for (var i = 0; i < count; i++) queue.Enqueue(buffer[i]);
stream.BeginRead(buffer, 0, buffer.Length, ReadCallback, stream);
}
});
}
}
}

View File

@@ -0,0 +1,16 @@
using System;
namespace SharpSerial
{
public class SerialException : Exception
{
private readonly string trace;
public SerialException(string message, string trace) : base(message)
{
this.trace = trace;
}
public string Trace { get { return trace; } }
}
}

View File

@@ -0,0 +1,126 @@
using System;
using System.IO;
using System.Text;
using System.Diagnostics;
using System.Threading.Tasks;
namespace SharpSerial
{
//this class should not swallow exceptions outside dispose
public class SerialProcess : ISerialStream, IDisposable
{
private readonly Process process;
private readonly int pid;
public int Pid { get { return pid; } }
public SerialProcess(object settings)
{
var ss = new SerialSettings(settings);
var args = new StringBuilder();
foreach (var p in ss.GetType().GetProperties())
{
if (args.Length > 0) args.Append(" ");
args.AppendFormat("{0}={1}", p.Name, p.GetValue(ss, null).ToString());
}
process = new Process();
process.StartInfo = new ProcessStartInfo()
{
FileName = typeof(SerialProcess).Assembly.Location,
Arguments = args.ToString(),
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = false,
};
EnableStandardError(process.StartInfo);
process.Start();
pid = process.Id;
ForwardStandardError(process.StandardError);
}
public void Dispose()
{
Tools.Try(() =>
{
process.StandardInput.Close();
process.WaitForExit(200);
});
Tools.Try(process.Kill);
Tools.Try(process.Dispose);
}
public void Write(byte[] data)
{
WriteHex(data);
var line = ReadLine();
Tools.Assert(line == "<ok", "Unexpected write response {0}", line);
}
public byte[] Read(int size, int eop, int toms)
{
WriteLine("$r,{0},{1},{2}", size, eop, toms);
return ParseHex(ReadLine());
}
private string ReadLine()
{
var line = process.StandardOutput.ReadLine();
if (line == null) throw new EndOfStreamException("Serial process EOF");
if (line.StartsWith("!"))
{
var trace = process.StandardOutput.ReadToEnd();
throw new SerialException(line.Substring(1), trace);
}
return line;
}
private void WriteLine(string format, params object[] args)
{
process.StandardInput.WriteLine(format, args);
process.StandardInput.Flush();
}
private void WriteHex(byte[] data)
{
var sb = new StringBuilder();
sb.Append(">");
foreach (var b in data) sb.Append(b.ToString("X2"));
WriteLine(sb.ToString());
}
private byte[] ParseHex(string text)
{
Tools.Assert(text.StartsWith("<"), "First char < expected for {0}:{1}", text.Length, text);
Tools.Assert(text.Length % 2 == 1, "Odd length expected for {0}:{1}", text.Length, text);
var bytes = new byte[text.Length / 2];
for (var i = 0; i < bytes.Length; i++)
{
var b2 = text.Substring(1 + i * 2, 2);
bytes[i] = Convert.ToByte(b2, 16);
}
return bytes;
}
[Conditional("DEBUG")]
private void EnableStandardError(ProcessStartInfo psi)
{
psi.RedirectStandardError = true;
}
[Conditional("DEBUG")]
private void ForwardStandardError(StreamReader reader)
{
Task.Factory.StartNew(() =>
{
var line = reader.ReadLine();
while (line != null)
{
Stdio.Trace(line);
line = reader.ReadLine();
}
});
}
}
}

View File

@@ -0,0 +1,141 @@
using System;
using System.IO.Ports;
using System.Globalization;
using System.ComponentModel;
using System.Text.RegularExpressions;
namespace SharpSerial
{
public class SerialSettings
{
public SerialSettings() => CopyProperties(new SerialPort(), this);
public SerialSettings(string portName) => CopyProperties(new SerialPort(portName), this);
public SerialSettings(object source) => CopyProperties(source, this);
public void CopyFrom(object source) => CopyProperties(source, this);
public void CopyTo(object target) => CopyProperties(this, target);
[TypeConverter(typeof(PortNameConverter))]
public string PortName { get; set; }
[TypeConverter(typeof(BaudRateConverter))]
public int BaudRate { get; set; }
public int DataBits { get; set; }
public Parity Parity { get; set; }
public StopBits StopBits { get; set; }
public Handshake Handshake { get; set; }
public static void CopyProperties(Object source, Object target)
{
CopyProperty(source, target, "PortName");
CopyProperty(source, target, "BaudRate");
CopyProperty(source, target, "DataBits");
CopyProperty(source, target, "Parity");
CopyProperty(source, target, "StopBits");
CopyProperty(source, target, "Handshake");
}
static void CopyProperty(Object source, Object target, string name)
{
var propertySource = source.GetType().GetProperty(name);
var propertyTarget = target.GetType().GetProperty(name);
propertyTarget.SetValue(target, propertySource.GetValue(source, null), null);
}
}
public class PortNameConverter : TypeConverter
{
//windows only?
private readonly Regex re = new Regex(@"[^a-zA-Z0-9_]");
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{
return true;
}
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
return new StandardValuesCollection(SerialPort.GetPortNames());
}
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
{
return false;
}
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return (sourceType == typeof(string));
}
public override object ConvertFrom(ITypeDescriptorContext context,
CultureInfo culture, object value)
{
if (re.IsMatch(value.ToString())) throw Tools.Make("Invalid chars");
return value;
}
public override object ConvertTo(ITypeDescriptorContext context,
CultureInfo culture, object value, Type destinationType)
{
return value;
}
}
public class BaudRateConverter : TypeConverter
{
public readonly static int[] BaudRates = new int[] {
110,
300,
600,
1200,
2400,
4800,
9600,
14400,
19200,
28800,
38400,
56000,
57600,
115200,
128000,
153600,
230400,
256000,
460800,
921600
};
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{
return true;
}
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
return new StandardValuesCollection(BaudRates);
}
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
{
return false;
}
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return (sourceType == typeof(string));
}
public override object ConvertFrom(ITypeDescriptorContext context,
CultureInfo culture, object value)
{
return int.Parse(value.ToString());
}
public override object ConvertTo(ITypeDescriptorContext context,
CultureInfo culture, object value, Type destinationType)
{
return value.ToString();
}
}
}

View File

@@ -0,0 +1,10 @@
using System;
namespace SharpSerial
{
public interface ISerialStream : IDisposable
{
void Write(byte[] data);
byte[] Read(int size, int eop, int toms);
}
}