Commit d946520f authored by Andreas Müller's avatar Andreas Müller

Modbus.Serial now has the ability to change the serial driver settings to use the RS485 specs.

parent 6c94d02d
Pipeline #25 passed with stage
in 2 minutes and 56 seconds
......@@ -62,9 +62,6 @@ namespace ConsoleDemo
Console.Write("Baud: ");
var baud = Convert.ToInt32(Console.ReadLine().Trim());
Console.Write("Data-Bits [7|8]: ");
var dataBits = Convert.ToInt32(Console.ReadLine().Trim());
Console.Write("Stop-Bits [0|1|2|3=1.5]: ");
var stopBits = Convert.ToInt32(Console.ReadLine().Trim());
......@@ -74,14 +71,27 @@ namespace ConsoleDemo
Console.Write("Handshake [0] None [1] X-On/Off [2] RTS [3] RTS+X-On/Off: ");
var handshake = Convert.ToInt32(Console.ReadLine().Trim());
Console.Write("Timeout: ");
var timeout = Convert.ToInt32(Console.ReadLine().Trim());
Console.Write("Set Driver to RS485 [0] No [1] Yes: ");
var setDriver = Convert.ToInt32(Console.ReadLine().Trim());
client = new SerialClient(port)
{
BaudRate = (BaudRate)baud,
DataBits = dataBits,
DataBits = 8,
StopBits = (StopBits)stopBits,
Parity = (Parity)parity,
Handshake = (Handshake)handshake
Handshake = (Handshake)handshake,
SendTimeout = timeout,
ReceiveTimeout = timeout
};
if (setDriver == 1)
{
((SerialClient)client).DriverEnableRS485 = true;
}
}
break;
default:
......
......@@ -3,7 +3,6 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Version>1.0.0</Version>
<Authors>Andreas Müller</Authors>
<Company />
<Product>Modbus.Common</Product>
......
......@@ -3,12 +3,14 @@ using AMWD.Modbus.Common.Interfaces;
using AMWD.Modbus.Common.Structures;
using AMWD.Modbus.Common.Util;
using AMWD.Modbus.Serial.Protocol;
using AMWD.Modbus.Serial.Util;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Ports;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
......@@ -41,6 +43,9 @@ namespace AMWD.Modbus.Serial.Client
private int sendTimeout = 1000;
private int receiveTimeout = 1000;
private bool driverModified;
private RS485Flags serialDriverFlags;
#endregion Fields
#region Events
......@@ -253,10 +258,28 @@ namespace AMWD.Modbus.Serial.Client
/// </summary>
public TimeSpan ReconnectTimeSpan { get; set; } = TimeSpan.MaxValue;
/// <summary>
/// Gets or sets a value indicating whether to indicate the driver to switch to RS485 mode.
/// </summary>
public bool DriverEnableRS485 { get; set; }
#endregion Properties
#region Public methods
#region Static
/// <summary>
/// Returns a list of available serial ports.
/// </summary>
/// <returns></returns>
public static string[] AvailablePorts()
{
return SerialPort.GetPortNames();
}
#endregion Static
#region Control
/// <summary>
......@@ -275,6 +298,25 @@ namespace AMWD.Modbus.Serial.Client
{
logger?.LogInformation("ModbusClient starting");
isStarted = true;
if (DriverEnableRS485 && !RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
try
{
var rs485 = GetDriverState();
serialDriverFlags = rs485.Flags;
rs485.Flags |= RS485Flags.SerRS485Enabled;
rs485.Flags &= ~RS485Flags.SerRS485RxDuringTx;
SetDriverState(rs485);
driverModified = true;
}
catch (Exception ex)
{
logger.LogError(ex, "ModbusClient.Connect faild to set RS485 serial driver state.");
throw;
}
}
ConnectingTask = Task.Run((Action)Reconnect);
}
......@@ -298,9 +340,27 @@ namespace AMWD.Modbus.Serial.Client
var connected = IsConnected;
logger?.LogInformation("ModbusClient stopping");
serialPort?.Dispose();
serialPort = null;
if (driverModified)
{
try
{
var rs485 = GetDriverState();
rs485.Flags = serialDriverFlags;
SetDriverState(rs485);
driverModified = false;
}
catch (Exception ex)
{
logger?.LogError(ex, "ModbusClient.Disconnect failed to reset the serial driver state.");
}
}
isStarted = false;
if (connected)
{
logger?.LogTrace("ModbusClient.Disconnect fire disconnected event.");
......@@ -1165,6 +1225,38 @@ namespace AMWD.Modbus.Serial.Client
return bytes.ToArray();
}
private SerialRS485 GetDriverState()
{
var rs485 = new SerialRS485();
SafeUnixHandle handle = null;
try
{
handle = UnsafeNativeMethods.Open(PortName, UnsafeNativeMethods.O_RDWR | UnsafeNativeMethods.O_NOCTTY);
if (UnsafeNativeMethods.IoCtl(handle, UnsafeNativeMethods.TIOCGRS485, ref rs485) == -1)
throw new UnixIOException();
}
finally
{
handle?.Close();
}
return rs485;
}
private void SetDriverState(SerialRS485 rs485)
{
SafeUnixHandle handle = null;
try
{
handle = UnsafeNativeMethods.Open(PortName, UnsafeNativeMethods.O_RDWR | UnsafeNativeMethods.O_NOCTTY);
if (UnsafeNativeMethods.IoCtl(handle, UnsafeNativeMethods.TIOCSRS485, ref rs485) == -1)
throw new UnixIOException();
}
finally
{
handle?.Close();
}
}
#endregion Private methods
......
......@@ -3,7 +3,6 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Version>1.0.0</Version>
<Authors>Andreas Müller</Authors>
<Company />
<Product>Modbus.Serial</Product>
......
using System;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security.Permissions;
namespace AMWD.Modbus.Serial.Util
{
[SecurityPermission(SecurityAction.InheritanceDemand, UnmanagedCode = true)]
[SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
internal sealed class SafeUnixHandle : SafeHandle
{
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
private SafeUnixHandle()
: base(new IntPtr(-1), true)
{ }
public override bool IsInvalid
{
get { return handle == new IntPtr(-1); }
}
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
protected override bool ReleaseHandle()
{
return UnsafeNativeMethods.Close(handle) != -1;
}
}
}
using System;
using System.Runtime.InteropServices;
namespace AMWD.Modbus.Serial.Util
{
[StructLayout(LayoutKind.Sequential, Size = 32)]
internal struct SerialRS485
{
public RS485Flags Flags;
public uint RtsDelayBeforeSend;
public uint RtsDelayAfterSend;
}
[Flags]
internal enum RS485Flags : uint
{
SerRS485Enabled = 1,
SerRS485RtsOnSend = 2,
SerRS485RtsAfterSend = 4,
SerRS485RxDuringTx = 16
}
}
using System;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Security.Permissions;
using System.Text;
namespace AMWD.Modbus.Serial.Util
{
/// <summary>
/// Represents a unix specific IO exception.
/// </summary>
[Serializable]
public class UnixIOException : ExternalException
{
/// <summary>
/// Initializes a new instance of a <see cref="UnixIOException"/> class.
/// </summary>
[SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
public UnixIOException()
: this(Marshal.GetLastWin32Error())
{ }
/// <summary>
/// Initializes a new instance of a <see cref="UnixIOException"/> class.
/// </summary>
/// <param name="error">The error number.</param>
[SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
public UnixIOException(int error)
: this(error, GetErrorMessage(error))
{ }
/// <summary>
/// Initializes a new instance of a <see cref="UnixIOException"/> class.
/// </summary>
/// <param name="message">The error message.</param>
[SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
public UnixIOException(string message)
: this(Marshal.GetLastWin32Error(), message)
{ }
/// <summary>
/// Initializes a new instance of a <see cref="UnixIOException"/> class.
/// </summary>
/// <param name="error">The error number.</param>
/// <param name="message">The error message.</param>
public UnixIOException(int error, string message)
: base(message)
{
NativeErrorCode = error;
}
/// <summary>
/// Initializes a new instance of a <see cref="UnixIOException"/> class.
/// </summary>
/// <param name="message">The error message.</param>
/// <param name="innerException">An inner exception.</param>
public UnixIOException(string message, Exception innerException)
: base(message, innerException)
{ }
/// <summary>
/// Initializes a new instance of a <see cref="UnixIOException"/> class.
/// </summary>
/// <param name="info">The serialization information.</param>
/// <param name="context">The stream context.</param>
protected UnixIOException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
NativeErrorCode = info.GetInt32("NativeErrorCode");
}
/// <summary>
/// Gets the native error code set by the unix system.
/// </summary>
public int NativeErrorCode { get; }
/// <summary>
/// Tries to get the object data.
/// </summary>
/// <param name="info">The serialization information.</param>
/// <param name="context">The stream context.</param>
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
if (info == null)
{
throw new ArgumentNullException("info");
}
info.AddValue("NativeErrorCode", NativeErrorCode);
base.GetObjectData(info, context);
}
private static string GetErrorMessage(int error)
{
try
{
var buffer = new StringBuilder(256);
var res = UnsafeNativeMethods.StrError(error, buffer, (ulong)buffer.Capacity);
return res == -1 ? $"Unknown error (0x{error:x})" : buffer.ToString();
}
catch (EntryPointNotFoundException)
{
return $"Unknown error (0x{error:x})";
}
}
}
}
using System;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Text;
namespace AMWD.Modbus.Serial.Util
{
internal static class UnsafeNativeMethods
{
internal const int O_RDWR = 2;
internal const int O_NOCTTY = 256;
internal const uint TIOCGRS485 = 0x542E;
internal const uint TIOCSRS485 = 0x542F;
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
[DllImport("libc", EntryPoint = "close", SetLastError = true)]
internal static extern int Close(IntPtr handle);
[DllImport("libc", EntryPoint = "ioctl", SetLastError = true)]
internal static extern int IoCtl(SafeUnixHandle handle, uint request, ref SerialRS485 serialRs485);
[DllImport("libc", EntryPoint = "open", SetLastError = true)]
internal static extern SafeUnixHandle Open(string path, uint flag);
[DllImport("libc", EntryPoint = "strerr", SetLastError = true)]
internal static extern int StrError(int error, [Out] StringBuilder buffer, ulong bufferLength);
}
}
......@@ -3,7 +3,6 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Version>1.0.0</Version>
<Authors>Andreas Müller</Authors>
<Company />
<Product>Modbus.TCP</Product>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment