Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 26 additions & 18 deletions src/Longbow.Modbus/DefaultModbusTcpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ class DefaultModbusTcpClient(ITcpSocketClient client) : IModbusTcpClient

public ValueTask<ushort[]?> ReadInputRegistersAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints) => ReadAsync(slaveAddress, 0x04, startAddress, numberOfPoints, ReadUShort);

public ValueTask<bool> WriteCoilAsync(byte slaveAddress, ushort coilAddress, bool value) => WriteBoolAsync(slaveAddress, 0x05, coilAddress, value);

public ushort[] ReadWriteMultipleRegisters(byte slaveAddress, ushort startReadAddress, ushort numberOfPointsToRead, ushort startWriteAddress, ushort[] writeData)
{
throw new NotImplementedException();
Expand All @@ -38,11 +40,6 @@ public Task<ushort[]> ReadWriteMultipleRegistersAsync(byte slaveAddress, ushort
throw new NotImplementedException();
}

public void WriteFileRecord(byte slaveAdress, ushort fileNumber, ushort startingAddress, byte[] data)
{
throw new NotImplementedException();
}

public void WriteMultipleCoils(byte slaveAddress, ushort startAddress, bool[] data)
{
throw new NotImplementedException();
Expand All @@ -63,16 +60,6 @@ public Task WriteMultipleRegistersAsync(byte slaveAddress, ushort startAddress,
throw new NotImplementedException();
}

public void WriteSingleCoil(byte slaveAddress, ushort coilAddress, bool value)
{
throw new NotImplementedException();
}

public Task WriteSingleCoilAsync(byte slaveAddress, ushort coilAddress, bool value)
{
throw new NotImplementedException();
}

public void WriteSingleRegister(byte slaveAddress, ushort registerAddress, ushort value)
{
throw new NotImplementedException();
Expand All @@ -90,8 +77,8 @@ public Task WriteSingleRegisterAsync(byte slaveAddress, ushort registerAddress,
throw new InvalidOperationException("站点未连接请先使用 ConnectAsync 连接设备");
}

var data = _builder.Build(slaveAddress, functionCode, startAddress, numberOfPoints);
var result = await client.SendAsync(data);
var request = _builder.BuildReadRequest(slaveAddress, functionCode, startAddress, numberOfPoints);
var result = await client.SendAsync(request);
if (!result)
{
return default;
Expand All @@ -101,14 +88,35 @@ public Task WriteSingleRegisterAsync(byte slaveAddress, ushort registerAddress,
var received = await client.ReceiveAsync(_receiveCancellationTokenSource.Token);
var response = received.Span;

if (!ValidateResponse(response, data.Span[..2], functionCode))
if (!ValidateResponse(response, request.Span[..2], functionCode))
{
return default;
}

return parser(received, numberOfPoints);
}

private ValueTask<bool> WriteBoolAsync(byte slaveAddress, byte functionCode, ushort address, bool value)
{
if (!client.IsConnected)
{
throw new InvalidOperationException("站点未连接请先使用 ConnectAsync 连接设备");
}

var request = _builder.BuildWriteRequest(slaveAddress, functionCode, WriteBool(address, value));
return client.SendAsync(request);
Comment thread
ArgoZhang marked this conversation as resolved.
}

private static ReadOnlyMemory<byte> WriteBool(ushort address, bool value)
{
var data = new byte[4];
data[0] = (byte)(address >> 8);
data[1] = (byte)address;
data[2] = value ? (byte)0xFF : (byte)0x00;
data[3] = 0x00;
return data;
}

private static bool[] ReadBool(ReadOnlyMemory<byte> response, ushort numberOfPoints)
{
var values = new bool[numberOfPoints];
Expand Down
53 changes: 6 additions & 47 deletions src/Longbow.Modbus/IModbusTcpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,30 +69,23 @@ public interface IModbusTcpClient : IAsyncDisposable
/// <returns>A task that represents the asynchronous read operation.</returns>
ValueTask<ushort[]?> ReadInputRegistersAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints);

/// <summary>
/// Writes a single coil value.
/// </summary>
/// <param name="slaveAddress">Address of the device to write to.</param>
/// <param name="coilAddress">Address to write value to.</param>
/// <param name="value">Value to write.</param>
void WriteSingleCoil(byte slaveAddress, ushort coilAddress, bool value);

/// <summary>
/// Asynchronously writes a single coil value.
/// </summary>
/// <param name="slaveAddress">Address of the device to write to.</param>
/// <param name="coilAddress">Address to write value to.</param>
/// <param name="value">Value to write.</param>
/// <returns>A task that represents the asynchronous write operation.</returns>
Task WriteSingleCoilAsync(byte slaveAddress, ushort coilAddress, bool value);
ValueTask<bool> WriteCoilAsync(byte slaveAddress, ushort coilAddress, bool value);

/// <summary>
/// Writes a single holding register.
/// Asynchronously writes a sequence of coils.
/// </summary>
/// <param name="slaveAddress">Address of the device to write to.</param>
/// <param name="registerAddress">Address to write.</param>
/// <param name="value">Value to write.</param>
void WriteSingleRegister(byte slaveAddress, ushort registerAddress, ushort value);
/// <param name="startAddress">Address to begin writing values.</param>
/// <param name="data">Values to write.</param>
/// <returns>A task that represents the asynchronous write operation.</returns>
Task WriteMultipleCoilsAsync(byte slaveAddress, ushort startAddress, bool[] data);

/// <summary>
/// Asynchronously writes a single holding register.
Expand All @@ -103,14 +96,6 @@ public interface IModbusTcpClient : IAsyncDisposable
/// <returns>A task that represents the asynchronous write operation.</returns>
Task WriteSingleRegisterAsync(byte slaveAddress, ushort registerAddress, ushort value);

/// <summary>
/// Writes a block of 1 to 123 contiguous registers.
/// </summary>
/// <param name="slaveAddress">Address of the device to write to.</param>
/// <param name="startAddress">Address to begin writing values.</param>
/// <param name="data">Values to write.</param>
void WriteMultipleRegisters(byte slaveAddress, ushort startAddress, ushort[] data);

/// <summary>
/// Asynchronously writes a block of 1 to 123 contiguous registers.
/// </summary>
Expand All @@ -120,23 +105,6 @@ public interface IModbusTcpClient : IAsyncDisposable
/// <returns>A task that represents the asynchronous write operation.</returns>
Task WriteMultipleRegistersAsync(byte slaveAddress, ushort startAddress, ushort[] data);

/// <summary>
/// Writes a sequence of coils.
/// </summary>
/// <param name="slaveAddress">Address of the device to write to.</param>
/// <param name="startAddress">Address to begin writing values.</param>
/// <param name="data">Values to write.</param>
void WriteMultipleCoils(byte slaveAddress, ushort startAddress, bool[] data);

/// <summary>
/// Asynchronously writes a sequence of coils.
/// </summary>
/// <param name="slaveAddress">Address of the device to write to.</param>
/// <param name="startAddress">Address to begin writing values.</param>
/// <param name="data">Values to write.</param>
/// <returns>A task that represents the asynchronous write operation.</returns>
Task WriteMultipleCoilsAsync(byte slaveAddress, ushort startAddress, bool[] data);

/// <summary>
/// Performs a combination of one read operation and one write operation in a single Modbus transaction.
/// The write operation is performed before the read.
Expand Down Expand Up @@ -169,13 +137,4 @@ Task<ushort[]> ReadWriteMultipleRegistersAsync(
ushort numberOfPointsToRead,
ushort startWriteAddress,
ushort[] writeData);

/// <summary>
/// Write a file record to the device.
/// </summary>
/// <param name="slaveAdress">Address of device to write values to</param>
/// <param name="fileNumber">The Extended Memory file number</param>
/// <param name="startingAddress">The starting register address within the file</param>
/// <param name="data">The data to be written</param>
void WriteFileRecord(byte slaveAdress, ushort fileNumber, ushort startingAddress, byte[] data);
}
44 changes: 42 additions & 2 deletions src/Longbow.Modbus/ModbusMessageBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ public class ModbusTcpMessageBuilder
private ushort _transactionId = 0;

/// <summary>
/// 构建 Modbus TCP 请求消息方法
/// 构建 Modbus TCP 读取消息方法
/// </summary>
/// <param name="slaveAddress"></param>
/// <param name="functionCode"></param>
/// <param name="startAddress"></param>
/// <param name="numberOfPoints"></param>
/// <returns></returns>
public ReadOnlyMemory<byte> Build(byte slaveAddress, byte functionCode, ushort startAddress, ushort numberOfPoints)
public ReadOnlyMemory<byte> BuildReadRequest(byte slaveAddress, byte functionCode, ushort startAddress, ushort numberOfPoints)
{
byte[] request =
[
Expand Down Expand Up @@ -52,4 +52,44 @@ public ReadOnlyMemory<byte> Build(byte slaveAddress, byte functionCode, ushort s

return request;
}


/// <summary>
/// 构建 Modbus TCP 写入消息方法
/// </summary>
/// <param name="slaveAddress"></param>
/// <param name="functionCode"></param>
/// <param name="data"></param>
/// <returns></returns>
public ReadOnlyMemory<byte> BuildWriteRequest(byte slaveAddress, byte functionCode, ReadOnlyMemory<byte> data)
{
var request = new byte[8 + data.Length];

// MBAP头(7字节)
request[0] = (byte)(_transactionId >> 8); // 00 事务标识符高字节(可随机)
request[1] = (byte)(_transactionId & 0xFF); // 01 事务标识符低字节
request[2] = 0x00; // 02 协议标识符高字节(Modbus固定0)
request[3] = 0x00; // 03 协议标识符低字节
request[4] = 0x00; // 04 长度高字节(后续字节数)
request[5] = 0x06; // 05 长度低字节(6字节PDU)
Comment thread
ArgoZhang marked this conversation as resolved.

// PDU部分
request[6] = slaveAddress; // 06 从站地址
request[7] = functionCode; // 07 功能码

// 写入数据部分
data.CopyTo(request.AsMemory(8));

// 递增事务标识符以供下次使用
if (_transactionId == ushort.MaxValue)
{
_transactionId = 0;
}
else
{
_transactionId++;
}

return request;
}
}
19 changes: 19 additions & 0 deletions test/UnitTestModbus/TcpClientTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,23 @@ public async Task ReadInputRegistersAsync_Ok()
Assert.NotNull(response);
Assert.Equal(10, response.Length);
}

[Fact]
public async Task WriteCoilAsync_Ok()
{
var sc = new ServiceCollection();
sc.AddTcpSocketFactory();
sc.AddModbusFactory();

var provider = sc.BuildServiceProvider();
var factory = provider.GetRequiredService<IModbusFactory>();
await using var client = factory.GetOrCreateTcpMaster("test");

// 连接 Master
await client.ConnectAsync("127.0.0.1", 502);

// 读取 0x01 从站输入寄存器数据
Comment thread
ArgoZhang marked this conversation as resolved.
var response = await client.WriteCoilAsync(0x01, 0, true);
Assert.True(response);
}
}