Files
2026-04-04 17:25:15 +08:00

188 lines
9.6 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Modbus.Net;
using Modbus.Net.Modbus;
using MultipleMachinesJobScheduler = Modbus.Net.MultipleMachinesJobScheduler<Modbus.Net.IMachineMethodDatas, string, double>;
namespace SampleModbusRtuServer.Service
{
/// <summary>
/// Modbus RTU 服务器后台工作服务
/// 监听串口,响应 Modbus RTU 读写请求
/// </summary>
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
// 线圈数据数组0X 区10000 点)
private bool[] zerox = new bool[10000];
// 保持寄存器数据数组4X 区20000 字节=10000 个寄存器)
private byte[] threex = new byte[20000];
// 数据更新标志
private bool _isUpdate = false;
// 最后更新时间
private DateTime _updateTime = DateTime.MinValue;
public Worker(ILogger<Worker> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
// 创建 Modbus RTU 协议接收器,监听 COM2 端口,从站地址 1
ModbusRtuProtocolReceiver receiver = new ModbusRtuProtocolReceiver("COM2", 1);
// 设置数据处理回调
receiver.DataProcess = receiveContent =>
{
byte[]? returnBytes = null;
// 读取内容缓冲区(每个寄存器 2 字节)
var readContent = new byte[receiveContent.Count * 2];
// 写入内容
var values = receiveContent.WriteContent;
// 值字典(未使用)
var valueDic = new Dictionary<string, double>();
// Redis 值字典(未使用)
var redisValues = new Dictionary<string, ReturnUnit<double>>();
// 处理写操作
if (values != null)
{
try
{
// 如果处于更新模式且距离上次更新超过 9.5 秒,记录日志
if (_isUpdate && DateTime.Now - _updateTime > TimeSpan.FromSeconds(9.5))
{
_logger.LogDebug($"receive content { String.Concat(receiveContent.WriteContent.Select(p => " " + p.ToString("X2")))}");
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error");
}
// 根据功能码处理不同的写操作
switch (receiveContent.FunctionCode)
{
case (byte)ModbusProtocolFunctionCode.WriteMultiRegister:
{
// 写多个寄存器(功能码 16
// 将写入的数据复制到保持寄存器数组
Array.Copy(receiveContent.WriteContent, 0, threex, receiveContent.StartAddress * 2, receiveContent.WriteContent.Length);
// 生成响应帧
returnBytes = new WriteDataModbusProtocol().Format(receiveContent.SlaveAddress, receiveContent.FunctionCode, receiveContent.StartAddress, receiveContent.Count);
_isUpdate = true;
break;
}
case (byte)ModbusProtocolFunctionCode.WriteSingleCoil:
{
// 写单个线圈(功能码 5
// 255=ON, 0=OFF
if (receiveContent.WriteContent[0] == 255)
{
zerox[receiveContent.StartAddress] = true;
}
else
{
zerox[receiveContent.StartAddress] = false;
}
// 生成响应帧
returnBytes = new WriteDataModbusProtocol().Format(receiveContent.SlaveAddress, receiveContent.FunctionCode, receiveContent.StartAddress, receiveContent.WriteContent);
_isUpdate = true;
break;
}
case (byte)ModbusProtocolFunctionCode.WriteMultiCoil:
{
// 写多个线圈(功能码 15
var pos = 0;
List<bool> bitList = new List<bool>();
// 将字节数组转换为位列表
for (int i = 0; i < receiveContent.WriteByteCount; i++)
{
var bitArray = BigEndianLsbValueHelper.Instance.GetBits(receiveContent.WriteContent, ref pos);
bitList.AddRange(bitArray.ToList());
}
// 将位列表复制到线圈数组
Array.Copy(bitList.ToArray(), 0, zerox, receiveContent.StartAddress, bitList.Count);
// 生成响应帧
returnBytes = new WriteDataModbusProtocol().Format(receiveContent.SlaveAddress, receiveContent.FunctionCode, receiveContent.StartAddress, receiveContent.Count);
_isUpdate = true;
break;
}
case (byte)ModbusProtocolFunctionCode.WriteSingleRegister:
{
// 写单个寄存器(功能码 6
// 将写入的数据复制到保持寄存器数组
Array.Copy(receiveContent.WriteContent, 0, threex, receiveContent.StartAddress * 2, receiveContent.Count * 2);
// 生成响应帧
returnBytes = new WriteDataModbusProtocol().Format(receiveContent.SlaveAddress, receiveContent.FunctionCode, receiveContent.StartAddress, receiveContent.Count);
_isUpdate = true;
break;
}
}
}
// 处理读操作
else
{
switch (receiveContent.FunctionCode)
{
case (byte)ModbusProtocolFunctionCode.ReadHoldRegister:
{
// 读保持寄存器(功能码 3
// 从保持寄存器数组复制数据到读取缓冲区
Array.Copy(threex, receiveContent.StartAddress, readContent, 0, readContent.Length);
// 生成响应帧
returnBytes = new ReadDataModbusProtocol().Format(receiveContent.SlaveAddress, receiveContent.FunctionCode, (byte)receiveContent.Count, readContent);
break;
}
case (byte)ModbusProtocolFunctionCode.ReadCoilStatus:
{
// 读线圈状态(功能码 1
// 计算需要读取的位数
var bitCount = receiveContent.WriteByteCount * 8;
var boolContent = new bool[bitCount];
// 从线圈数组复制数据
Array.Copy(zerox, receiveContent.StartAddress, boolContent, 0, bitCount);
// 将位数组打包为字节数组
var byteList = new List<byte>();
for (int i = 0; i < receiveContent.WriteByteCount; i++)
{
byte result = 0;
for (int j = i; j < i + 8; j++)
{
// 将布尔值转换为位1 或 0
byte bit = boolContent[j] ? (byte)1 : (byte)0;
// 使用位移运算将位合并为字节
result = (byte)((result << 1) | bit);
}
byteList.Add(result);
}
readContent = byteList.ToArray();
// 生成响应帧
returnBytes = new ReadDataModbusProtocol().Format(receiveContent.SlaveAddress, receiveContent.FunctionCode, receiveContent.WriteByteCount, readContent);
break;
}
}
}
// 返回响应帧,如果无法处理则返回 null
if (returnBytes != null) return returnBytes;
else return null;
};
// 连接接收器(打开串口)
await receiver.ConnectAsync();
}
public override Task StopAsync(CancellationToken cancellationToken)
{
// 停止所有作业调度
return Task.Run(() => MultipleMachinesJobScheduler.CancelJob());
}
}
}