Skip to content

Commit

Permalink
Merge pull request #111 from OndrejCopak-eaton/multiple-coils
Browse files Browse the repository at this point in the history
support for WriteMultipleCoils
  • Loading branch information
Apollo3zehn authored Feb 20, 2024
2 parents 3049d03 + 0be3c58 commit 8be0a03
Show file tree
Hide file tree
Showing 6 changed files with 264 additions and 52 deletions.
43 changes: 36 additions & 7 deletions src/FluentModbus/Client/ModbusClient.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Runtime.InteropServices;
using System.Collections;
using System.Runtime.InteropServices;

namespace FluentModbus
{
Expand Down Expand Up @@ -199,13 +200,11 @@ public void WriteMultipleRegisters(byte unitIdentifier, ushort startingAddress,
{
writer.WriteReverse(startingAddress); // 08-09 Starting Address
writer.WriteReverse((ushort)quantity); // 10-11 Quantity of Registers

}
else
{
writer.Write(startingAddress); // 08-09 Starting Address
writer.Write((ushort)quantity); // 10-11 Quantity of Registers

}

writer.Write((byte)(quantity * 2)); // 12 Byte Count = Quantity of Registers * 2
Expand Down Expand Up @@ -425,12 +424,42 @@ public void WriteSingleRegister(byte unitIdentifier, ushort registerAddress, byt
// class 2

/// <summary>
/// This methdod is not implemented.
/// Writes the provided <paramref name="values"/> to the coil registers.
/// </summary>
[Obsolete("This method is not implemented.")]
public void WriteMultipleCoils()
/// <param name="unitIdentifier">The unit identifier is used to communicate via devices such as bridges, routers and gateways that use a single IP address to support multiple independent Modbus end units. Thus, the unit identifier is the address of a remote slave connected on a serial line or on other buses. Use the default values 0x00 or 0xFF when communicating to a Modbus server that is directly connected to a TCP/IP network.</param>
/// <param name="startingAddress">The coil register start address for the write operation.</param>
/// <param name="values">The values to write to the server.</param>
public void WriteMultipleCoils(int unitIdentifier, int startingAddress, bool[] values)
{
throw new NotImplementedException();
var unitIdentifier_converted = ConvertUnitIdentifier(unitIdentifier);
var startingAddress_converted = ConvertUshort(startingAddress);
var quantityOfOutputs = values.Length;
var byteCount = (quantityOfOutputs + 7) / 8;
var convertedData = new byte[byteCount];

new BitArray(values)
.CopyTo(convertedData, 0);

TransceiveFrame(unitIdentifier_converted, ModbusFunctionCode.WriteMultipleCoils, writer =>
{
writer.Write((byte)ModbusFunctionCode.WriteMultipleCoils); // 07 Function Code

if (BitConverter.IsLittleEndian)
{
writer.WriteReverse(startingAddress_converted); // 08-09 Starting Address
writer.WriteReverse((ushort)quantityOfOutputs); // 10-11 Quantity of Outputs
}

else
{
writer.Write(startingAddress_converted); // 08-09 Starting Address
writer.Write((ushort)quantityOfOutputs); // 10-11 Quantity of Outputs
}

writer.Write((byte)byteCount); // 12 Byte Count = Outputs

writer.Write(convertedData);
});
}

/// <summary>
Expand Down
40 changes: 36 additions & 4 deletions src/FluentModbus/Client/ModbusClientAsync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#pragma warning disable CS1998

using System.Collections;
using System.Runtime.InteropServices;

namespace FluentModbus
Expand Down Expand Up @@ -343,12 +344,43 @@ await TransceiveFrameAsync(unitIdentifier, ModbusFunctionCode.WriteSingleRegiste
}

/// <summary>
/// This methdod is not implemented.
/// Writes the provided <paramref name="values"/> to the coil registers.
/// </summary>
[Obsolete("This method is not implemented.")]
public async Task WriteMultipleCoilsAsync()
/// <param name="unitIdentifier">The unit identifier is used to communicate via devices such as bridges, routers and gateways that use a single IP address to support multiple independent Modbus end units. Thus, the unit identifier is the address of a remote slave connected on a serial line or on other buses. Use the default values 0x00 or 0xFF when communicating to a Modbus server that is directly connected to a TCP/IP network.</param>
/// <param name="startingAddress">The coil register start address for the write operation.</param>
/// <param name="values">The values to write to the server.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="CancellationToken.None"/>.</param>
public void WriteMultipleCoilsAsync(int unitIdentifier, int startingAddress, bool[] values, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
var unitIdentifier_converted = ConvertUnitIdentifier(unitIdentifier);
var startingAddress_converted = ConvertUshort(startingAddress);
var quantityOfOutputs = values.Length;
var byteCount = (quantityOfOutputs + 7) / 8;
var convertedData = new byte[byteCount];

new BitArray(values)
.CopyTo(convertedData, 0);

TransceiveFrameAsync(unitIdentifier_converted, ModbusFunctionCode.WriteMultipleCoils, writer =>
{
writer.Write((byte)ModbusFunctionCode.WriteMultipleCoils); // 07 Function Code

if (BitConverter.IsLittleEndian)
{
writer.WriteReverse(startingAddress_converted); // 08-09 Starting Address
writer.WriteReverse((ushort)quantityOfOutputs); // 10-11 Quantity of Outputs
}

else
{
writer.Write(startingAddress_converted); // 08-09 Starting Address
writer.Write((ushort)quantityOfOutputs); // 10-11 Quantity of Outputs
}

writer.Write((byte)byteCount); // 12 Byte Count = Outputs

writer.Write(convertedData);
}, cancellationToken);
}

/// <summary>
Expand Down
3 changes: 2 additions & 1 deletion src/FluentModbus/FluentModbus.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0" />
<PackageReference Include="System.IO.Ports" Version="5.0.0" />
<PackageReference Include="System.Memory" Version="4.5.4" />

<PackageReference Include="PolySharp" Version="1.13.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
Expand All @@ -22,7 +23,7 @@
<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.1'">
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0" />
<PackageReference Include="System.IO.Ports" Version="5.0.0" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.7.1" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="5.0.0" />
</ItemGroup>

<ItemGroup>
Expand Down
118 changes: 96 additions & 22 deletions src/FluentModbus/Server/ModbusRequestHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public void WriteResponse()
ModbusFunctionCode.WriteSingleCoil => ProcessWriteSingleCoil,
ModbusFunctionCode.WriteSingleRegister => ProcessWriteSingleRegister,
//ModbusFunctionCode.ReadExceptionStatus
//ModbusFunctionCode.WriteMultipleCoils
ModbusFunctionCode.WriteMultipleCoils => ProcessWriteMultipleCoils,
//ModbusFunctionCode.ReadFileRecord
//ModbusFunctionCode.WriteFileRecord
//ModbusFunctionCode.MaskWriteRegister
Expand Down Expand Up @@ -210,7 +210,7 @@ private void DetectChangedRegisters(int startingAddress, Span<short> oldValues,
}
}

ModbusServer.OnRegistersChanged(UnitIdentifier, changedRegisters.Slice(0, length).ToArray());
ModbusServer.OnRegistersChanged(UnitIdentifier, changedRegisters[..length].ToArray());
}

// class 0
Expand All @@ -236,10 +236,14 @@ private void ProcessWriteMultipleRegisters()
if (CheckRegisterBounds(ModbusFunctionCode.WriteMultipleRegisters, startingAddress, ModbusServer.MaxHoldingRegisterAddress, quantityOfRegisters, 0x7B))
{
var holdingRegisters = ModbusServer.GetHoldingRegisters(UnitIdentifier);
var oldValues = holdingRegisters.Slice(startingAddress).ToArray();

var oldValues = ModbusServer.EnableRaisingEvents
? holdingRegisters[startingAddress..].ToArray()
: Array.Empty<short>();

var newValues = MemoryMarshal.Cast<byte, short>(FrameBuffer.Reader.ReadBytes(byteCount).AsSpan());

newValues.CopyTo(holdingRegisters.Slice(startingAddress));
newValues.CopyTo(holdingRegisters[startingAddress..]);

if (ModbusServer.EnableRaisingEvents)
DetectChangedRegisters(startingAddress, oldValues, newValues);
Expand Down Expand Up @@ -337,6 +341,85 @@ private void ProcessReadInputRegisters()
}
}

private void ProcessWriteMultipleCoils()
{
const int maxQuantityOfOutputs = 0x07B0;

var startingAddress = FrameBuffer.Reader.ReadUInt16Reverse();
var quantityOfOutputs = FrameBuffer.Reader.ReadUInt16Reverse();
var byteCount = FrameBuffer.Reader.ReadByte();
var byteCountFromQuantity = (quantityOfOutputs + 7) / 8;

if (byteCountFromQuantity != byteCount)
{
WriteExceptionResponse(ModbusFunctionCode.WriteMultipleCoils, ModbusExceptionCode.IllegalDataValue);
return;
}

if (CheckRegisterBounds(ModbusFunctionCode.WriteMultipleCoils, startingAddress, ModbusServer.MaxCoilAddress, quantityOfOutputs, maxQuantityOfOutputs))
{
var newValues = FrameBuffer.Reader.ReadBytes(byteCount);

Span<int> changedOutputs = stackalloc int[0];

if (ModbusServer.EnableRaisingEvents)
changedOutputs = stackalloc int[quantityOfOutputs];

var changedOutputsLength = 0;

for (var i = 0; i < quantityOfOutputs; i++)
{
byte b = newValues[i / 8];
int bit = i % 8;
bool value = (b & (1 << bit)) != 0;

var hasChanged = WriteCoil(value, (ushort)(startingAddress + i));

if (ModbusServer.EnableRaisingEvents && (hasChanged || ModbusServer.AlwaysRaiseChangedEvent))
{
changedOutputs[changedOutputsLength] = startingAddress + i;
changedOutputsLength++;
}
}

if (ModbusServer.EnableRaisingEvents)
ModbusServer.OnCoilsChanged(UnitIdentifier, changedOutputs[..changedOutputsLength].ToArray());
}

FrameBuffer.Writer.Write((byte)ModbusFunctionCode.WriteMultipleCoils);

if (BitConverter.IsLittleEndian)
{
FrameBuffer.Writer.WriteReverse(startingAddress);
FrameBuffer.Writer.WriteReverse(quantityOfOutputs);
}
else
{
FrameBuffer.Writer.Write(startingAddress);
FrameBuffer.Writer.Write(quantityOfOutputs);
}
}

private bool WriteCoil(bool value, ushort outputAddress)
{
var bufferByteIndex = outputAddress / 8;
var bufferBitIndex = outputAddress % 8;

var coils = ModbusServer.GetCoils(UnitIdentifier);
var oldValue = coils[bufferByteIndex];
var newValue = oldValue;

if (value)
newValue |= (byte)(1 << bufferBitIndex);

else
newValue &= (byte)~(1 << bufferBitIndex);

coils[bufferByteIndex] = newValue;

return newValue != oldValue;
}

private void ProcessWriteSingleCoil()
{
var outputAddress = FrameBuffer.Reader.ReadUInt16Reverse();
Expand All @@ -350,22 +433,10 @@ private void ProcessWriteSingleCoil()
}
else
{
var bufferByteIndex = outputAddress / 8;
var bufferBitIndex = outputAddress % 8;

var coils = ModbusServer.GetCoils(UnitIdentifier);
var oldValue = coils[bufferByteIndex];
var newValue = oldValue;

if (outputValue == 0x0000)
newValue &= (byte)~(1 << bufferBitIndex);
else
newValue |= (byte)(1 << bufferBitIndex);

coils[bufferByteIndex] = newValue;
var hasChanged = WriteCoil(outputValue == 0x00FF, outputAddress);

if (ModbusServer.EnableRaisingEvents && (newValue != oldValue || ModbusServer.AlwaysRaiseChangedEvent))
ModbusServer.OnCoilsChanged(UnitIdentifier, new int[] { outputAddress });
if (ModbusServer.EnableRaisingEvents && (hasChanged || ModbusServer.AlwaysRaiseChangedEvent))
ModbusServer.OnCoilsChanged(UnitIdentifier, [outputAddress]);

FrameBuffer.Writer.Write((byte)ModbusFunctionCode.WriteSingleCoil);

Expand All @@ -392,7 +463,7 @@ private void ProcessWriteSingleRegister()
holdingRegisters[registerAddress] = newValue;

if (ModbusServer.EnableRaisingEvents && (newValue != oldValue || ModbusServer.AlwaysRaiseChangedEvent))
ModbusServer.OnRegistersChanged(UnitIdentifier, new int[] { registerAddress });
ModbusServer.OnRegistersChanged(UnitIdentifier, [registerAddress]);

FrameBuffer.Writer.Write((byte)ModbusFunctionCode.WriteSingleRegister);

Expand Down Expand Up @@ -423,10 +494,13 @@ private void ProcessReadWriteMultipleRegisters()
// write data (write is performed before read according to spec)
var writeData = MemoryMarshal.Cast<byte, short>(FrameBuffer.Reader.ReadBytes(writeByteCount).AsSpan());

var oldValues = holdingRegisters.Slice(writeStartingAddress).ToArray();
var oldValues = ModbusServer.EnableRaisingEvents
? holdingRegisters[writeStartingAddress..].ToArray()
: Array.Empty<short>();

var newValues = writeData;

newValues.CopyTo(holdingRegisters.Slice(writeStartingAddress));
newValues.CopyTo(holdingRegisters[writeStartingAddress..]);

if (ModbusServer.EnableRaisingEvents)
DetectChangedRegisters(writeStartingAddress, oldValues, newValues);
Expand Down
Loading

0 comments on commit 8be0a03

Please sign in to comment.