Skip to content

Commit

Permalink
Merge pull request #7 from WildernessLabs/develop
Browse files Browse the repository at this point in the history
Update main for RC2-1
  • Loading branch information
jorgedevs authored Feb 2, 2023
2 parents aa519e0 + 4909bc9 commit 175950a
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 40 deletions.
14 changes: 0 additions & 14 deletions src/Meadow.Modbus.sln
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,11 @@ VisualStudioVersion = 17.1.31911.260
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Meadow.Modbus", "Meadow.Modbus\Meadow.Modbus.csproj", "{934C99DF-77F7-432C-AA81-B5FAE23B20DF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Meadow.Modbus.Unit.Tests", "Meadow.Modbus.Unit.Tests\Meadow.Modbus.Unit.Tests.csproj", "{A3F6B117-3E3C-497D-9525-7A8A0FFA2119}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7C7A8928-0C6D-4781-BE71-7BEF6F479E6D}"
ProjectSection(SolutionItems) = preProject
readme.md = readme.md
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FeatherV2RtuSample", "samples\F7v2RtuSample\FeatherV2RtuSample.csproj", "{A79616E3-8399-4A12-BDC7-1FD52CE286DC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -24,16 +20,6 @@ Global
{934C99DF-77F7-432C-AA81-B5FAE23B20DF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{934C99DF-77F7-432C-AA81-B5FAE23B20DF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{934C99DF-77F7-432C-AA81-B5FAE23B20DF}.Release|Any CPU.Build.0 = Release|Any CPU
{A3F6B117-3E3C-497D-9525-7A8A0FFA2119}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A3F6B117-3E3C-497D-9525-7A8A0FFA2119}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A3F6B117-3E3C-497D-9525-7A8A0FFA2119}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A3F6B117-3E3C-497D-9525-7A8A0FFA2119}.Release|Any CPU.Build.0 = Release|Any CPU
{A79616E3-8399-4A12-BDC7-1FD52CE286DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A79616E3-8399-4A12-BDC7-1FD52CE286DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A79616E3-8399-4A12-BDC7-1FD52CE286DC}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{A79616E3-8399-4A12-BDC7-1FD52CE286DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A79616E3-8399-4A12-BDC7-1FD52CE286DC}.Release|Any CPU.Build.0 = Release|Any CPU
{A79616E3-8399-4A12-BDC7-1FD52CE286DC}.Release|Any CPU.Deploy.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
20 changes: 20 additions & 0 deletions src/Meadow.Modbus/Clients/IModbusBusClient.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Meadow.Modbus
Expand All @@ -21,6 +22,15 @@ public interface IModbusBusClient
/// <param name="value"></param>
Task WriteHoldingRegister(byte modbusAddress, ushort register, ushort value);

/// <summary>
/// Writes multiple values to holding registers (modbus function 16)
/// </summary>
/// <param name="modbusAddress">The target device modbus address</param>
/// <param name="startRegister">The first register to begin writing</param>
/// <param name="values">The registers (16-bit values) to write</param>
/// <returns></returns>
Task WriteHoldingRegisters(byte modbusAddress, ushort startRegister, IEnumerable<ushort> values);

/// <summary>
/// Reads the requested number of holding registers from a device
/// </summary>
Expand All @@ -30,6 +40,16 @@ public interface IModbusBusClient
/// <returns></returns>
Task<ushort[]> ReadHoldingRegisters(byte modbusAddress, ushort startRegister, int registerCount);

/// <summary>
/// Reads the requested number of floats from the holding registers
/// Each float is two sequential registers
/// </summary>
/// <param name="modbusAddress"></param>
/// <param name="startRegister"></param>
/// <param name="floatCount"></param>
/// <returns></returns>
Task<float[]> ReadHoldingRegistersFloat(byte modbusAddress, ushort startRegister, int floatCount);

Task WriteCoil(byte modbusAddress, ushort register, bool value);
Task<bool[]> ReadCoils(byte modbusAddress, ushort startCoil, int coilCount);
}
Expand Down
63 changes: 63 additions & 0 deletions src/Meadow.Modbus/Clients/ModbusClientBase.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -115,6 +117,45 @@ public async Task WriteHoldingRegister(byte modbusAddress, ushort register, usho
}
}

public async Task WriteHoldingRegisters(byte modbusAddress, ushort startRegister, IEnumerable<ushort> values)
{
if (startRegister > 40000)
{
// holding registers are defined as starting at 40001, but the actual bus read doesn't use the address, but instead the offset
// we'll support th user passing in the definition either way
startRegister -= 40001;
}

// swap endianness, because Modbus
var data = values.SelectMany(i => BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)i))).ToArray();

var message = GenerateWriteMessage(modbusAddress, ModbusFunction.WriteMultipleRegisters, startRegister, data);
await _syncRoot.WaitAsync();

try
{
await DeliverMessage(message);
await ReadResult(ModbusFunction.WriteMultipleRegisters);
}
finally
{
_syncRoot.Release();
}
}

public async Task<float[]> ReadHoldingRegistersFloat(byte modbusAddress, ushort startRegister, int floatCount)
{
var data = await ReadHoldingRegisters(modbusAddress, startRegister, floatCount * 2);

var values = new float[data.Length / 2];

for (int i = 0; i < values.Length; i++)
{
values[i] = ConvertUShortsToFloat(data[i * 2 + 1], data[i * 2]);
}
return values;
}

public async Task<ushort[]> ReadHoldingRegisters(byte modbusAddress, ushort startRegister, int registerCount)
{
if (startRegister > 40000)
Expand Down Expand Up @@ -236,5 +277,27 @@ public async Task<bool[]> ReadCoils(byte modbusAddress, ushort startCoil, int co

return values;
}

float ConvertUShortsToFloat(ushort high, ushort low)
{
// Combine the high and low values into a single uint
uint input = (uint)(((high & 0x00FF) << 24) |
((high & 0xFF00) << 8) |
(low & 0x00FF) << 8 |
low >> 8);

// Get the sign bit
uint signBit = (input >> 31) & 1;
int sign = 1 - (int)(2 * signBit);
// Get the exponent bits
var exponentBits = ((input >> 23) & 0xFF);
var exponent = exponentBits - 127;
// Get the fraction
var fractionBits = (input & 0x7FFFFF);
var fraction = 1.0 + fractionBits / Math.Pow(2, 23);

// get the value
return (float)(sign * fraction * Math.Pow(2, exponent));
}
}
}
83 changes: 59 additions & 24 deletions src/Meadow.Modbus/Clients/ModbusRtuClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,41 +74,47 @@ protected override async Task<byte[]> ReadResult(ModbusFunction function)
if (_port.ReadTimeout.TotalMilliseconds > 0 && t > _port.ReadTimeout.TotalMilliseconds) throw new TimeoutException();
}

var header = new byte[3];

// read 5 bytes so we can get the length
_port.Read(header, 0, header.Length);
int headerLen = function switch
{
ModbusFunction.WriteMultipleRegisters => 6,
_ => 3
};

// TODO: verify these?
// header[0] == modbus address
// header[1] == called function
// header[2] == data length
var header = new byte[headerLen];

// if (function != (ModbusFunction)header[1])
// {
// TODO: should we care?
// }
// read header to determine result length
_port.Read(header, 0, header.Length);

int dataLength;
int bufferLen;
int resultLen;

switch (function)
{
case ModbusFunction.WriteMultipleRegisters:
case ModbusFunction.WriteMultipleCoils:
bufferLen = 8; //fixed length
resultLen = 0; //no result data
break;
case ModbusFunction.WriteRegister:
dataLength = 7;
bufferLen = 7 + header[headerLen - 1];
resultLen = header[2];
break;
case ModbusFunction.ReadHoldingRegister:
dataLength = 5;
bufferLen = 5 + header[headerLen - 1];
resultLen = header[2];
break;
default:
dataLength = 5;
bufferLen = 5 + header[headerLen - 1];
resultLen = header[2];
break;
}
var buffer = new byte[header[2] + dataLength]; // header + length + CRC

var buffer = new byte[bufferLen]; // header + length + CRC

// the CRC includes the header, so we need those in the buffer
Array.Copy(header, buffer, 3);
Array.Copy(header, buffer, headerLen);

var read = 3;
var read = headerLen;
while (read < buffer.Length)
{
read += _port.Read(buffer, read, buffer.Length - read);
Expand All @@ -117,10 +123,15 @@ protected override async Task<byte[]> ReadResult(ModbusFunction function)
// do a CRC on all but the last 2 bytes, then see if that matches the last 2
var expectedCrc = Crc(buffer, 0, buffer.Length - 2);
var actualCrc = buffer[buffer.Length - 2] | buffer[buffer.Length - 1] << 8;
if (expectedCrc != actualCrc) throw new CrcException();
if (expectedCrc != actualCrc) { throw new CrcException(); }

if(resultLen == 0)
{ //happens on write multiples
return new byte[0];
}

var result = new byte[buffer[2]];
Array.Copy(buffer, 3, result, 0, result.Length);
var result = new byte[resultLen];
Array.Copy(buffer, headerLen, result, 0, result.Length);

return await Task.FromResult(result);
}
Expand Down Expand Up @@ -157,14 +168,38 @@ protected override byte[] GenerateReadMessage(byte modbusAddress, ModbusFunction

protected override byte[] GenerateWriteMessage(byte modbusAddress, ModbusFunction function, ushort register, byte[] data)
{
var message = new byte[4 + data.Length + 2]; // header + data + crc
byte[] message;
int offset = HEADER_DATA_OFFSET;

switch (function)
{
case ModbusFunction.WriteMultipleCoils:
case ModbusFunction.WriteMultipleRegisters:
message = new byte[4 + data.Length + 5]; // header + length + data + crc
break;
default:
message = new byte[4 + data.Length + 2]; // header + data + crc
break;
}

message[0] = modbusAddress;
message[1] = (byte)function;
message[2] = (byte)(register >> 8);
message[3] = (byte)(register & 0xff);

Array.Copy(data, 0, message, HEADER_DATA_OFFSET, data.Length);
switch (function)
{
case ModbusFunction.WriteMultipleCoils:
case ModbusFunction.WriteMultipleRegisters:
var registers = (ushort)(data.Length / 2);
message[4] = (byte)(registers >> 8);
message[5] = (byte)(registers & 0xff);
message[6] = (byte)data.Length;
offset += 3;
break;
}

Array.Copy(data, 0, message, offset, data.Length);

FillCRC(message);

Expand Down
4 changes: 2 additions & 2 deletions src/Meadow.Modbus/Meadow.Modbus.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@

<ItemGroup>
<None Include="icon.png" Pack="true" PackagePath="" />
<PackageReference Include="Meadow.Contracts" Version="0.94.1" />
<PackageReference Include="Meadow.Logging" Version="0.92.5" />
<PackageReference Include="System.IO.Ports" Version="6.0.0" />
<PackageReference Include="Meadow.Logging" Version="0.*" />
<PackageReference Include="Meadow.Contracts" Version="0.*" />
</ItemGroup>


Expand Down

0 comments on commit 175950a

Please sign in to comment.