Skip to content

Commit

Permalink
Merge pull request #41 from WildernessLabs/feature/modbusdevice
Browse files Browse the repository at this point in the history
ModnusPolledDevice support
  • Loading branch information
adrianstevens committed Feb 6, 2024
2 parents dc52cbd + 98da92b commit bde6b5a
Show file tree
Hide file tree
Showing 4 changed files with 660 additions and 9 deletions.
104 changes: 102 additions & 2 deletions src/Meadow.Modbus.Unit.Tests/ModbusSerialTStatTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,108 @@

namespace Meadow.Modbus.Unit.Tests;

public class TStat8 : ModbusPolledDevice
{
private float _currentSetPoint;

Check warning on line 10 in src/Meadow.Modbus.Unit.Tests/ModbusSerialTStatTests.cs

View workflow job for this annotation

GitHub Actions / build

Field 'TStat8._currentSetPoint' is never assigned to, and will always have its default value 0

private const ushort SetPointRegister = 345;

public TStat8(ModbusRtuClient client, byte modbusAddress, TimeSpan? refreshPeriod = null)
: base(client, modbusAddress, refreshPeriod)
{
MapHoldingRegistersToProperty(
startRegister: 121,
registerCount: 1,
propertyName: nameof(Temperature),
scale: 0.10); // value is in 0.1 deg

// map to a field, not a property as the property setter needs to perform an action
MapHoldingRegistersToField(
startRegister: SetPointRegister,
registerCount: 1,
fieldName: nameof(_currentSetPoint),
scale: 0.10);

MapHoldingRegistersToProperty(
startRegister: 198,
registerCount: 1,
propertyName: nameof(Humidity));

MapHoldingRegistersToProperty(
startRegister: 364, // not scaled by 0.1
registerCount: 1,
propertyName: nameof(PowerUpSetPoint));

MapHoldingRegistersToProperty(
startRegister: 365,
registerCount: 1,
propertyName: nameof(MaxSetPoint));

MapHoldingRegistersToProperty(
startRegister: 366,
registerCount: 1,
propertyName: nameof(MinSetPoint));

MapHoldingRegistersToProperty(
startRegister: 410,
registerCount: 7,
propertyName: nameof(Clock),
conversionFunction: ConvertRegistersToClockTime);
}

private object ConvertRegistersToClockTime(ushort[] data)
{
// data[2] is week, so ignore
return new DateTime(data[0], data[1], data[3], data[4], data[5], data[6]);
}

public DateTime Clock { get; private set; }
public int Humidity { get; private set; }
public float Temperature { get; private set; }
public float MinSetPoint { get; private set; }
public float MaxSetPoint { get; private set; }
public float PowerUpSetPoint { get; private set; }

public float SetPoint
{
get => _currentSetPoint;
set
{
_ = WriteHoldingRegister(SetPointRegister, (ushort)(value * 10));
}
}
}

public class ModbusSerialTStatTests
{
// this class assumes a connected serial Temco Controls TSTAT7 or TSTAT8
[Fact]
public async void PolledDevicetest()
{
using (var port = new SerialPortShim("COM3", 19200, Hardware.Parity.None, 8, Hardware.StopBits.One))
{
port.ReadTimeout = TimeSpan.FromSeconds(15);
port.Open();

var client = new ModbusRtuClient(port);
var tstat = new TStat8(client, 201, TimeSpan.FromSeconds(1));
tstat.StartPolling();

var i = 0;

Check warning on line 95 in src/Meadow.Modbus.Unit.Tests/ModbusSerialTStatTests.cs

View workflow job for this annotation

GitHub Actions / build

The variable 'i' is assigned but its value is never used
while (true)
{
await Task.Delay(1000);
Debug.WriteLine($"Temp: {tstat.Temperature}");
Debug.WriteLine($"SetPoint: {tstat.SetPoint}");
Debug.WriteLine($"MinSetPoint: {tstat.MinSetPoint}");
Debug.WriteLine($"MaxSetPoint: {tstat.MaxSetPoint}");
Debug.WriteLine($"PowerUpSetPoint: {tstat.PowerUpSetPoint}");
Debug.WriteLine($"Humidity: {tstat.Humidity}");
Debug.WriteLine($"Clock: {tstat.Clock}");
}
}
}

// this class assumes a connected serial Temco Controls TSTAT7 or TSTAT8
[Fact(Skip = "Requires a connected TSTAT8 over RS485")]
public async void MultipleReadHoldingRegisterTest()
Expand All @@ -33,10 +133,10 @@ public async void MultipleReadHoldingRegisterTest()
}
}

[Fact(Skip = "Requires a connected TSTAT8 over RS485")]
[Fact]
public async void MultipleWriteHoldingRegisterTest()
{
using (var port = new SerialPortShim("COM4", 19200, Hardware.Parity.None, 8, Hardware.StopBits.One))
using (var port = new SerialPortShim("COM3", 19200, Hardware.Parity.None, 8, Hardware.StopBits.One))
{
port.ReadTimeout = TimeSpan.FromSeconds(15);
port.Open();
Expand Down
37 changes: 30 additions & 7 deletions src/Meadow.Modbus/Clients/ModbusClientBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public abstract class ModbusClientBase : IModbusBusClient, IDisposable
private const int MaxCoilReadCount = 0x7d0;
private const int MaxRegisterWriteCount = 0x7b;
private const int MaxCoilWriteCount = 0x7b0;
private const int LockTimeoutMs = 2000;

/// <summary>
/// Event triggered when the client is disconnected.
Expand Down Expand Up @@ -143,7 +144,10 @@ public async Task WriteHoldingRegister(byte modbusAddress, ushort register, usho
// swap endianness, because Modbus
var data = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)value));
var message = GenerateWriteMessage(modbusAddress, ModbusFunction.WriteRegister, register, data);
await _syncRoot.WaitAsync();
if (!await _syncRoot.WaitAsync(LockTimeoutMs))
{
return;
}

try
{
Expand Down Expand Up @@ -185,7 +189,10 @@ public async Task WriteHoldingRegisters(byte modbusAddress, ushort startRegister
var data = values.SelectMany(i => BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)i))).ToArray();

var message = GenerateWriteMessage(modbusAddress, ModbusFunction.WriteMultipleRegisters, startRegister, data);
await _syncRoot.WaitAsync();
if (!await _syncRoot.WaitAsync(LockTimeoutMs))
{
return;
}

try
{
Expand Down Expand Up @@ -237,7 +244,10 @@ public async Task<ushort[]> ReadHoldingRegisters(byte modbusAddress, ushort star
}

var message = GenerateReadMessage(modbusAddress, ModbusFunction.ReadHoldingRegister, startRegister, registerCount);
await _syncRoot.WaitAsync();
if (!await _syncRoot.WaitAsync(LockTimeoutMs))
{
return Array.Empty<ushort>();
}

byte[] result;

Expand Down Expand Up @@ -279,7 +289,10 @@ public async Task<ushort[]> ReadInputRegisters(byte modbusAddress, ushort startR
if (registerCount > MaxRegisterReadCount) throw new ArgumentException($"A maximum of {MaxRegisterReadCount} registers can be retrieved at one time");

var message = GenerateReadMessage(modbusAddress, ModbusFunction.ReadInputRegister, startRegister, registerCount);
await _syncRoot.WaitAsync();
if (!await _syncRoot.WaitAsync(LockTimeoutMs))
{
return Array.Empty<ushort>();
}

byte[] result;

Expand Down Expand Up @@ -308,7 +321,11 @@ public async Task WriteCoil(byte modbusAddress, ushort register, bool value)

var message = GenerateWriteMessage(modbusAddress, ModbusFunction.WriteCoil, register, data);

await _syncRoot.WaitAsync();
if (!await _syncRoot.WaitAsync(LockTimeoutMs))
{
return;
}

try
{
await DeliverMessage(message);
Expand Down Expand Up @@ -339,7 +356,10 @@ public async Task WriteMultipleCoils(byte modbusAddress, ushort startRegister, I
new BitArray(values.ToArray()).CopyTo(msgSegment, 3); // Concatinate bool binary values list as converted bytes

var message = GenerateWriteMessage(modbusAddress, ModbusFunction.WriteMultipleCoils, startRegister, msgSegment);
await _syncRoot.WaitAsync();
if (!await _syncRoot.WaitAsync(LockTimeoutMs))
{
return;
}

try
{
Expand All @@ -362,7 +382,10 @@ public async Task<bool[]> ReadCoils(byte modbusAddress, ushort startCoil, int co
if (coilCount > MaxCoilReadCount) throw new ArgumentException($"A maximum of {MaxCoilReadCount} coils can be retrieved at one time");

var message = GenerateReadMessage(modbusAddress, ModbusFunction.ReadCoil, startCoil, coilCount);
await _syncRoot.WaitAsync();
if (!await _syncRoot.WaitAsync(LockTimeoutMs))
{
return Array.Empty<bool>();
}

byte[] result;

Expand Down
1 change: 1 addition & 0 deletions src/Meadow.Modbus/Clients/ModbusRtuClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public class ModbusRtuClient : ModbusClientBase
public ModbusRtuClient(ISerialPort port, IDigitalOutputPort? enablePort = null)
{
_port = port;
_port.WriteTimeout = _port.ReadTimeout = TimeSpan.FromSeconds(5);
_enable = enablePort;
}

Expand Down

0 comments on commit bde6b5a

Please sign in to comment.