Skip to content

Commit

Permalink
Merge pull request #382 from pkuehnel/refactor/modbusCodeStructure
Browse files Browse the repository at this point in the history
feat(ModbusPlugin): use generic method for getting modbus value
  • Loading branch information
pkuehnel authored Oct 18, 2022
2 parents c0edb0e + 914ca8d commit 37cdccd
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 142 deletions.
8 changes: 2 additions & 6 deletions Plugins.Modbus/Contracts/IModbusClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@

public interface IModbusClient : IDisposable
{
Task<int> ReadInt32Value(byte unitIdentifier, ushort startingAddress, ushort quantity,
string ipAddressString,
int port, int connectDelay, int timeout, int? minimumResult);

bool DiconnectIfConnected();
Task<short> ReadInt16Value(byte unitIdentifier, ushort startingAddress, ushort quantity, string ipAddressString, int port, int connectDelay, int timeout, int? minimumResult);
Task<float> ReadFloatValue(byte unitIdentifier, ushort startingAddress, ushort quantity, string ipAddressString, int port, int connectDelay, int timeout, int? minimumResult);
Task<byte[]> GetByteArray(byte unitIdentifier, ushort startingAddress, ushort quantity, string ipAddressString,
int port, int connectDelay, int timeout);
}
9 changes: 3 additions & 6 deletions Plugins.Modbus/Contracts/IModbusService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@

public interface IModbusService
{
Task<int> ReadInt32Value(byte unitIdentifier, ushort startingAddress, ushort quantity, string ipAddressString,
int port, int connectDelay, int timeout, int? minimumResult);

Task<short> ReadInt16Value(byte unitIdentifier, ushort startingAddress, ushort quantity, string ipAddress, int port, int connectDelaySeconds, int timeoutSeconds, int? minimumResult);
Task<float> ReadFloatValue(byte unitIdentifier, ushort startingAddress, ushort quantity, string ipAddress, int port, int connectDelaySeconds, int timeoutSeconds, int? minimumResult);
}
Task<object> ReadValue<T>(byte unitIdentifier, ushort startingAddress, ushort quantity,
string ipAddressString, int port, int connectDelay, int timeout) where T : struct;
}
47 changes: 35 additions & 12 deletions Plugins.Modbus/Controllers/ModbusController.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Mvc;
using Plugins.Modbus.Contracts;
using TeslaSolarCharger.Shared.Enums;

namespace Plugins.Modbus.Controllers
{
Expand All @@ -14,6 +15,27 @@ public ModbusController(IModbusService modbusService)
_modbusService = modbusService;
}

public Task<object> GetTypedValue(byte unitIdentifier, ushort startingAddress, ushort quantity, string ipAddress,
int port, int connectDelaySeconds, int timeoutSeconds, ModbusValueType modbusValueType)
{
return modbusValueType switch
{
ModbusValueType.Int => _modbusService.ReadValue<int>(unitIdentifier, startingAddress, quantity, ipAddress, port,
connectDelaySeconds, timeoutSeconds),
ModbusValueType.Float => _modbusService.ReadValue<float>(unitIdentifier, startingAddress, quantity, ipAddress, port,
connectDelaySeconds, timeoutSeconds),
ModbusValueType.Short => _modbusService.ReadValue<short>(unitIdentifier, startingAddress, quantity, ipAddress, port,
connectDelaySeconds, timeoutSeconds),
ModbusValueType.UInt => _modbusService.ReadValue<uint>(unitIdentifier, startingAddress, quantity, ipAddress, port,
connectDelaySeconds, timeoutSeconds),
ModbusValueType.UShort => _modbusService.ReadValue<ushort>(unitIdentifier, startingAddress, quantity, ipAddress, port,
connectDelaySeconds, timeoutSeconds),
ModbusValueType.Ulong => _modbusService.ReadValue<ulong>(unitIdentifier, startingAddress, quantity, ipAddress, port,
connectDelaySeconds, timeoutSeconds),
_ => throw new ArgumentOutOfRangeException(nameof(modbusValueType), modbusValueType, null)
};
}

/// <summary>
/// Gets a Modbus Integer value
/// </summary>
Expand All @@ -24,13 +46,12 @@ public ModbusController(IModbusService modbusService)
/// <param name="port">The modbus port of the modbus device</param>
/// <param name="connectDelaySeconds"></param>
/// <param name="timeoutSeconds"></param>
/// <param name="minimumResult">Sets a minimum return result. This ist important, if your inverter does not send 0 as power if it is off.</param>
/// <returns></returns>
[Obsolete]
[HttpGet]
public Task<int> GetValue(byte unitIdentifier, ushort startingAddress, ushort quantity, string ipAddress,
int port, int connectDelaySeconds, int timeoutSeconds, int? minimumResult = null)
=> GetInt32Value(unitIdentifier, startingAddress, quantity, ipAddress, port, connectDelaySeconds, timeoutSeconds, minimumResult);
public Task<object> GetValue(byte unitIdentifier, ushort startingAddress, ushort quantity, string ipAddress,
int port, int connectDelaySeconds, int timeoutSeconds)
=> GetTypedValue(unitIdentifier, startingAddress, quantity, ipAddress, port, connectDelaySeconds, timeoutSeconds, ModbusValueType.Int);

/// <summary>
/// Gets a Modbus Int32 value
Expand All @@ -42,12 +63,12 @@ public Task<int> GetValue(byte unitIdentifier, ushort startingAddress, ushort qu
/// <param name="port">The modbus port of the modbus device</param>
/// <param name="connectDelaySeconds"></param>
/// <param name="timeoutSeconds"></param>
/// <param name="minimumResult">Sets a minimum return result. This ist important, if your inverter does not send 0 as power if it is off.</param>
/// <returns>Modbus value converted to Int32</returns>
[Obsolete]
[HttpGet]
public Task<int> GetInt32Value(byte unitIdentifier, ushort startingAddress, ushort quantity, string ipAddress,
int port, int connectDelaySeconds, int timeoutSeconds, int? minimumResult = null)
=> _modbusService.ReadInt32Value(unitIdentifier, startingAddress, quantity, ipAddress, port, connectDelaySeconds, timeoutSeconds, minimumResult);
public Task<object> GetInt32Value(byte unitIdentifier, ushort startingAddress, ushort quantity, string ipAddress,
int port, int connectDelaySeconds, int timeoutSeconds)
=> GetTypedValue(unitIdentifier, startingAddress, quantity, ipAddress, port, connectDelaySeconds, timeoutSeconds, ModbusValueType.Int);

/// <summary>
/// Gets a Modbus Int16 value
Expand All @@ -61,10 +82,11 @@ public Task<int> GetInt32Value(byte unitIdentifier, ushort startingAddress, usho
/// <param name="timeoutSeconds"></param>
/// <param name="minimumResult">Sets a minimum return result. This ist important, if your inverter does not send 0 as power if it is off.</param>
/// <returns>Modbus value converted to Int16</returns>
[Obsolete]
[HttpGet]
public Task<short> GetInt16Value(byte unitIdentifier, ushort startingAddress, ushort quantity, string ipAddress,
public Task<object> GetInt16Value(byte unitIdentifier, ushort startingAddress, ushort quantity, string ipAddress,
int port, int connectDelaySeconds, int timeoutSeconds, int? minimumResult = null)
=> _modbusService.ReadInt16Value(unitIdentifier, startingAddress, quantity, ipAddress, port, connectDelaySeconds, timeoutSeconds, minimumResult);
=> GetTypedValue(unitIdentifier, startingAddress, quantity, ipAddress, port, connectDelaySeconds, timeoutSeconds, ModbusValueType.Short);

/// <summary>
/// Gets a Modbus Float value
Expand All @@ -78,10 +100,11 @@ public Task<short> GetInt16Value(byte unitIdentifier, ushort startingAddress, us
/// <param name="timeoutSeconds"></param>
/// <param name="minimumResult">Sets a minimum return result. This ist important, if your inverter does not send 0 as power if it is off.</param>
/// <returns>Modbus value converted to float</returns>
[Obsolete]
[HttpGet]
public Task<float> GetFloatValue(byte unitIdentifier, ushort startingAddress, ushort quantity, string ipAddress,
public Task<object> GetFloatValue(byte unitIdentifier, ushort startingAddress, ushort quantity, string ipAddress,
int port, int connectDelaySeconds, int timeoutSeconds, int? minimumResult = null)
=> _modbusService.ReadFloatValue(unitIdentifier, startingAddress, quantity, ipAddress, port, connectDelaySeconds, timeoutSeconds, minimumResult);
=> GetTypedValue(unitIdentifier, startingAddress, quantity, ipAddress, port, connectDelaySeconds, timeoutSeconds, ModbusValueType.Float);

}
}
4 changes: 4 additions & 0 deletions Plugins.Modbus/Plugins.Modbus.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\TeslaSolarCharger\Shared\TeslaSolarCharger.Shared.csproj" />
</ItemGroup>

<ItemGroup>
<None Update="EnvironmentVariables.env">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
Expand Down
74 changes: 3 additions & 71 deletions Plugins.Modbus/Services/ModbusClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,8 @@ public ModbusClient(ILogger<ModbusClient> logger)
_semaphoreSlim = new SemaphoreSlim(1);
}

public async Task<int> ReadInt32Value(byte unitIdentifier, ushort startingAddress, ushort quantity,
string ipAddressString, int port, int connectDelay, int timeout, int? minimumResult)
{
_logger.LogTrace("{method}({unitIdentifier}, {startingAddress}, {quantity}, {ipAddressString}, {port}, " +
"{connectDelay}, {timeout}, {minimumResult})",
nameof(ReadInt32Value), unitIdentifier, startingAddress, quantity, ipAddressString, port,
connectDelay, timeout, minimumResult);

var tmpArrayPowerComplete = await GetByteArray(unitIdentifier, startingAddress, quantity, ipAddressString, port, connectDelay, timeout).ConfigureAwait(false);
_logger.LogTrace("Converting {array} to Int value...", Convert.ToHexString(tmpArrayPowerComplete));
var intValue = BitConverter.ToInt32(tmpArrayPowerComplete, 0);
if (minimumResult == null)
{
return intValue;
}
return intValue < minimumResult ? (int)minimumResult : intValue;
}

private async Task<byte[]> GetByteArray(byte unitIdentifier, ushort startingAddress, ushort quantity, string ipAddressString,
int port, int connectDelay, int timeout)
public async Task<byte[]> GetByteArray(byte unitIdentifier, ushort startingAddress, ushort quantity, string ipAddressString,
int port, int connectDelay, int timeout)
{
var tmpArrayPowerComplete =
await GetRegisterValue(unitIdentifier, startingAddress, quantity, ipAddressString, port, connectDelay, timeout)
Expand All @@ -56,44 +38,8 @@ public bool DiconnectIfConnected()
return false;
}

public async Task<short> ReadInt16Value(byte unitIdentifier, ushort startingAddress, ushort quantity, string ipAddressString, int port,
int connectDelay, int timeout, int? minimumResult)
{
_logger.LogTrace("{method}({unitIdentifier}, {startingAddress}, {quantity}, {ipAddressString}, {port}, " +
"{connectDelay}, {timeout}, {minimumResult})",
nameof(ReadInt16Value), unitIdentifier, startingAddress, quantity, ipAddressString, port,
connectDelay, timeout, minimumResult);

var tmpArrayPowerComplete = await GetByteArray(unitIdentifier, startingAddress, quantity, ipAddressString, port, connectDelay, timeout).ConfigureAwait(false);
_logger.LogTrace("Converting {array} to Int value...", Convert.ToHexString(tmpArrayPowerComplete));
var intValue = BitConverter.ToInt16(tmpArrayPowerComplete, 0);
if (minimumResult == null)
{
return intValue;
}
return intValue < minimumResult ? (short)minimumResult : intValue;
}

public async Task<float> ReadFloatValue(byte unitIdentifier, ushort startingAddress, ushort quantity, string ipAddressString, int port,
int connectDelay, int timeout, int? minimumResult)
{
_logger.LogTrace("{method}({unitIdentifier}, {startingAddress}, {quantity}, {ipAddressString}, {port}, " +
"{connectDelay}, {timeout}, {minimumResult})",
nameof(ReadInt16Value), unitIdentifier, startingAddress, quantity, ipAddressString, port,
connectDelay, timeout, minimumResult);

var tmpArrayPowerComplete = await GetByteArray(unitIdentifier, startingAddress, quantity, ipAddressString, port, connectDelay, timeout).ConfigureAwait(false);
_logger.LogTrace("Converting {array} to Int value...", Convert.ToHexString(tmpArrayPowerComplete));
var intValue = BitConverter.ToSingle(tmpArrayPowerComplete, 0);
if (minimumResult == null)
{
return intValue;
}
return intValue < minimumResult ? (short)minimumResult : intValue;
}

private async Task<byte[]> GetRegisterValue(byte unitIdentifier, ushort startingAddress, ushort quantity, string ipAddressString,
int port, int connectDelay, int timeout)
int port, int connectDelay, int timeout)
{
ReadTimeout = (int)TimeSpan.FromSeconds(timeout).TotalMilliseconds;
WriteTimeout = (int)TimeSpan.FromSeconds(timeout).TotalMilliseconds;
Expand Down Expand Up @@ -136,18 +82,4 @@ private async Task<byte[]> GetRegisterValue(byte unitIdentifier, ushort starting
});
}
}

public static byte[] StringToByteArray(string hex)
{
return Enumerable.Range(0, hex.Length)
.Where(x => x % 2 == 0)
.Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
.ToArray();
}

public void Dispose()
{
DiconnectIfConnected();
_semaphoreSlim.Dispose();
}
}
83 changes: 36 additions & 47 deletions Plugins.Modbus/Services/ModbusService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,77 +14,66 @@ public ModbusService(ILogger<ModbusService> logger, IServiceProvider serviceProv
_serviceProvider = serviceProvider;
}

public async Task<int> ReadInt32Value(byte unitIdentifier, ushort startingAddress, ushort quantity,
string ipAddressString, int port, int connectDelay, int timeout, int? minimumResult)
public async Task<object> ReadValue<T>(byte unitIdentifier, ushort startingAddress, ushort quantity,
string ipAddressString, int port, int connectDelay, int timeout) where T : struct
{
_logger.LogTrace("{method}({unitIdentifier}, {startingAddress}, {quantity}, {ipAddressString}, {port}, " +
"{connectDelay}, {timeout}, {minimumResult})",
nameof(ReadInt32Value), unitIdentifier, startingAddress, quantity, ipAddressString, port,
connectDelay, timeout,minimumResult);
"{connectDelay}, {timeout})",
nameof(ReadValue), unitIdentifier, startingAddress, quantity, ipAddressString, port,
connectDelay, timeout);

var modbusClient = GetModbusClient(ipAddressString, port);

byte[] byteArray;
if (timeout < 1)
{
_logger.LogDebug("Timeout is reduced to minimum value of 1 second");
timeout = 1;
}
try
{
var value = await modbusClient.ReadInt32Value(unitIdentifier, startingAddress, quantity, ipAddressString, port,
connectDelay, timeout, minimumResult).ConfigureAwait(false);
return value;
byteArray = await modbusClient.GetByteArray(unitIdentifier, startingAddress, quantity, ipAddressString, port, connectDelay, timeout)
.ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.LogError(ex, "Could not get value. Dispose modbus client");
_logger.LogError(ex, "Could not get byte array. Dispose modbus client");
modbusClient.Dispose();
_modbusClients.Remove(GetKeyString(ipAddressString, port));
throw;
}

}

public async Task<short> ReadInt16Value(byte unitIdentifier, ushort startingAddress, ushort quantity, string ipAddressString, int port,
int connectDelay, int timeout, int? minimumResult)
{
_logger.LogTrace("{method}({unitIdentifier}, {startingAddress}, {quantity}, {ipAddressString}, {port}, " +
"{connectDelay}, {timeout}, {minimumResult})",
nameof(ReadInt16Value), unitIdentifier, startingAddress, quantity, ipAddressString, port,
connectDelay, timeout, minimumResult);
var modbusClient = GetModbusClient(ipAddressString, port);
if (typeof(T) == typeof(int))
{
return (T) Convert.ChangeType(BitConverter.ToInt32(byteArray, 0), typeof(T));
}

try
if (typeof(T) == typeof(float))
{
var value = await modbusClient.ReadInt16Value(unitIdentifier, startingAddress, quantity, ipAddressString, port,
connectDelay, timeout, minimumResult).ConfigureAwait(false);
return value;
return (T)Convert.ChangeType(BitConverter.ToSingle(byteArray, 0), typeof(T));
}
catch (Exception ex)

if (typeof(T) == typeof(short))
{
_logger.LogError(ex, "Could not get value. Dispose modbus client");
modbusClient.Dispose();
_modbusClients.Remove(GetKeyString(ipAddressString, port));
throw;
return (T)Convert.ChangeType(BitConverter.ToInt16(byteArray, 0), typeof(T));
}
}

public async Task<float> ReadFloatValue(byte unitIdentifier, ushort startingAddress, ushort quantity, string ipAddressString, int port,
int connectDelay, int timeout, int? minimumResult)
{
_logger.LogTrace("{method}({unitIdentifier}, {startingAddress}, {quantity}, {ipAddressString}, {port}, " +
"{connectDelay}, {timeout}, {minimumResult})",
nameof(ReadInt16Value), unitIdentifier, startingAddress, quantity, ipAddressString, port,
connectDelay, timeout, minimumResult);
var modbusClient = GetModbusClient(ipAddressString, port);
try
if (typeof(T) == typeof(uint))
{
var value = await modbusClient.ReadFloatValue(unitIdentifier, startingAddress, quantity, ipAddressString, port,
connectDelay, timeout, minimumResult).ConfigureAwait(false);
return value;
return (T)Convert.ChangeType(BitConverter.ToUInt32(byteArray, 0), typeof(T));
}
catch (Exception ex)

if (typeof(T) == typeof(ushort))
{
_logger.LogError(ex, "Could not get value. Dispose modbus client");
modbusClient.Dispose();
_modbusClients.Remove(GetKeyString(ipAddressString, port));
throw;
return (T)Convert.ChangeType(BitConverter.ToUInt16(byteArray, 0), typeof(T));
}

if (typeof(T) == typeof(ulong))
{
return (T)Convert.ChangeType(BitConverter.ToUInt64(byteArray, 0), typeof(T));
}

throw new NotImplementedException($"Can not convert value of type: {typeof(T)}");

}

private IModbusClient GetModbusClient(string ipAddressString, int port)
Expand Down
11 changes: 11 additions & 0 deletions TeslaSolarCharger/Shared/Enums/ModbusValueType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace TeslaSolarCharger.Shared.Enums;

public enum ModbusValueType
{
Int,
Float,
Short,
UInt,
UShort,
Ulong,
}

0 comments on commit 37cdccd

Please sign in to comment.