using Modbus.Net; using Modbus.Net.Modbus; using Quartz; using Quartz.Impl; using Quartz.Impl.Matchers; using BaseUtility = Modbus.Net.BaseUtility, Modbus.Net.PipeUnit>; using MultipleMachinesJobScheduler = Modbus.Net.MultipleMachinesJobScheduler; namespace ModbusTcpToRtu { /// /// Modbus TCP 转 RTU 后台工作服务 /// 定期从 Modbus TCP 设备读取数据并写入到 Modbus RTU 设备 /// public class Worker : BackgroundService { private readonly ILogger _logger; // Modbus 读取工具(TCP) private BaseUtility readUtility; // Modbus 写入工具(RTU) private BaseUtility writeUtility; public Worker(ILogger logger) { _logger = logger; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { // 配置 Quartz 调度器的触发器和作业键 var triggerKey = "Modbus.Net.Job.Utility.SchedulerTrigger"; var jobKey = "Modbus.Net.Job.Utility.JobKey"; // 从配置读取调度间隔(秒转毫秒) var intervalMilliSecond = int.Parse(ConfigurationReader.GetValue("Utility", "interval")) * 1000; // 读取重复次数(-1 表示无限重复) var count = int.Parse(ConfigurationReader.GetValue("Utility", "count")); // 读取读写配置组 var readWriteGroup = ConfigurationReader.GetContent>("Utility", "readwrite"); // 读取 Modbus TCP 配置 var readType = Enum.Parse(ConfigurationReader.GetValue("Utility:read", "type")); var readAddress = ConfigurationReader.GetValue("Utility:read", "address"); var readSlaveAddress = byte.Parse(ConfigurationReader.GetValue("Utility:read", "slaveAddress")); var readMasterAddress = byte.Parse(ConfigurationReader.GetValue("Utility:read", "masterAddress")); // 读取 Modbus RTU 配置 var writeType = Enum.Parse(ConfigurationReader.GetValue("Utility:write", "type")); var writeAddress = ConfigurationReader.GetValue("Utility:write", "address"); var writeSlaveAddress = byte.Parse(ConfigurationReader.GetValue("Utility:write", "slaveAddress")); var writeMasterAddress = byte.Parse(ConfigurationReader.GetValue("Utility:write", "masterAddress")); // 创建 Modbus 工具实例 readUtility = new ModbusUtility(readType, readAddress, readSlaveAddress, readMasterAddress, Endian.BigEndianLsb); writeUtility = new ModbusUtility(writeType, writeAddress, writeSlaveAddress, writeMasterAddress, Endian.BigEndianLsb); // 获取 Quartz 调度器 IScheduler scheduler = await StdSchedulerFactory.GetDefaultScheduler(); // 启动调度器 await scheduler.Start(); // 创建触发器 ITrigger trigger; if (intervalMilliSecond <= 0) { // 间隔<=0,立即执行一次 trigger = TriggerBuilder.Create() .WithIdentity(triggerKey, "Modbus.Net.DataQuery.Group." + triggerKey) .StartNow() .Build(); } else if (count >= 0) // 指定重复次数 trigger = TriggerBuilder.Create() .WithIdentity(triggerKey, "Modbus.Net.DataQuery.Group." + triggerKey) .StartNow() .WithSimpleSchedule(b => b.WithInterval(TimeSpan.FromMilliseconds(intervalMilliSecond)).WithRepeatCount(count)) .Build(); else // 无限重复 trigger = TriggerBuilder.Create() .WithIdentity(triggerKey, "Modbus.Net.DataQuery.Group." + triggerKey) .StartNow() .WithSimpleSchedule(b => b.WithInterval(TimeSpan.FromMilliseconds(intervalMilliSecond)).RepeatForever()) .Build(); // 创建作业监听器 IJobListener listener; if (intervalMilliSecond <= 0) { // 重复指定次数的监听器 listener = new JobChainingJobLIstenerWithDataMapRepeated("Modbus.Net.DataQuery.Chain." + triggerKey, new string[2] { "Value", "SetValue" }, count); } else { // 无限重复的监听器 listener = new JobChainingJobListenerWithDataMap("Modbus.Net.DataQuery.Chain." + triggerKey, new string[2] { "Value", "SetValue" }); } // 添加作业监听器到调度器 scheduler.ListenerManager.AddJobListener(listener, GroupMatcher.GroupEquals("Modbus.Net.DataQuery.Group." + triggerKey)); // 如果触发器已存在,先删除 if (await scheduler.GetTrigger(new TriggerKey(triggerKey)) != null) { await scheduler.UnscheduleJob(new TriggerKey(triggerKey, "Modbus.Net.DataQuery.Group." + triggerKey)); } // 删除已存在的作业 var jobKeys = await scheduler.GetJobKeys(GroupMatcher.GroupEquals("Modbus.Net.DataQuery.Group." + triggerKey)); await scheduler.DeleteJobs(jobKeys); // 创建数据传递作业 var job = JobBuilder.Create() .WithIdentity(jobKey) .StoreDurably(true) .Build(); // 将工具实例放入作业数据映射 job.JobDataMap.Put("UtilityRead", readUtility); job.JobDataMap.Put("UtilityReadWriteGroup", readWriteGroup); job.JobDataMap.Put("UtilityWrite", writeUtility); // 调度作业 await scheduler.ScheduleJob(job, trigger); } public override Task StopAsync(CancellationToken cancellationToken) { // 停止所有作业调度 return Task.Run(() => MultipleMachinesJobScheduler.CancelJob()); } } /// /// Modbus 数据传递作业 /// 从读取工具读取数据并写入到写入工具 /// public class UtilityPassDataJob : IJob { public async Task Execute(IJobExecutionContext context) { // 从作业数据映射获取工具实例 object utilityReadObject; object utilityWriteObject; object utilityReadWriteGroupObject; context.JobDetail.JobDataMap.TryGetValue("UtilityRead", out utilityReadObject); context.JobDetail.JobDataMap.TryGetValue("UtilityWrite", out utilityWriteObject); context.JobDetail.JobDataMap.TryGetValue("UtilityReadWriteGroup", out utilityReadWriteGroupObject); var readUtility = (BaseUtility)utilityReadObject; var writeUtility = (BaseUtility)utilityWriteObject; var utilityReadWriteGroup = (List)utilityReadWriteGroupObject; // 连接读取工具 if (readUtility.IsConnected != true) await readUtility.ConnectAsync(); // 连接写入工具 if (writeUtility.IsConnected != true) await writeUtility.ConnectAsync(); // 遍历每个读写组 foreach (var rwGroup in utilityReadWriteGroup) { // 从 Modbus TCP 读取数据 // 地址格式:"4X 1" = 区域 + 地址 var datas = await readUtility.GetDatasAsync(rwGroup.ReadStart / 10000 + "X " + rwGroup.ReadStart % 10000, rwGroup.ReadCount * 2, rwGroup.ReadCount); if (datas.IsSuccess == true) { // 将字节数组转换为对象数组并写入 Modbus RTU var ans = await writeUtility.SetDatasAsync(rwGroup.WriteStart / 10000 + "X " + rwGroup.WriteStart % 10000, ByteArrayToObjectArray(datas.Datas), rwGroup.ReadCount); if (ans.Datas) { Console.WriteLine("success"); } } else { Console.WriteLine("failed"); } } } /// /// 字节数组转对象数组 /// 将每个字节转换为独立的对象 /// public static object[] ByteArrayToObjectArray(byte[] arrBytes) { List objArray = new List(); foreach (byte b in arrBytes) { objArray.Add(b); } return objArray.ToArray(); } } /// /// 读写配置组 /// 定义从哪个地址读取多少个数据,写入到哪个地址 /// public class ReadWriteGroup { /// /// 读取起始地址 /// 格式:区域*10000 + 地址,如 40001 表示 4X 区地址 1 /// public int ReadStart { get; set; } /// /// 读取数量 /// public int ReadCount { get; set; } /// /// 写入起始地址 /// 格式:区域*10000 + 地址 /// public int WriteStart { get; set; } } }