using Microsoft.Extensions.Logging; using System.Text.RegularExpressions; using WIDESEAWCS_S7Simulator.Application.Protocol; using WIDESEAWCS_S7Simulator.Core.Entities; using WIDESEAWCS_S7Simulator.Core.Enums; using WIDESEAWCS_S7Simulator.Core.Interfaces; using WIDESEAWCS_S7Simulator.Core.Protocol; namespace WIDESEAWCS_S7Simulator.Application; /// /// 实例同步服务:协调数据库读取、协议模板生成、实例创建 /// public class InstanceSyncService { private readonly DatabaseDeviceService _deviceService; private readonly ISimulatorInstanceManager _instanceManager; private readonly IProtocolTemplateService _templateService; private readonly ILogger _logger; private DateTime? _lastSyncTime; public DateTime? LastSyncTime => _lastSyncTime; public InstanceSyncService( DatabaseDeviceService deviceService, ISimulatorInstanceManager instanceManager, IProtocolTemplateService templateService, ILogger logger) { _deviceService = deviceService; _instanceManager = instanceManager; _templateService = templateService; _logger = logger; } /// /// 执行同步:从数据库读取设备,创建实例和协议模板 /// public async Task SyncInstancesAsync() { _logger.LogInformation("开始同步实例..."); _lastSyncTime = DateTime.Now; // 1. 获取所有 SiemensS7 设备 var devices = await _deviceService.GetSiemensS7DevicesAsync(); if (devices.Count == 0) { _logger.LogWarning("未找到 SiemensS7 设备,同步取消"); return; } // 2. 获取现有实例ID列表 var existingInstanceIds = _instanceManager.GetAllInstances().Select(i => i.Config.Id).ToHashSet(StringComparer.OrdinalIgnoreCase); var dbDeviceCodes = devices.Select(d => d.DeviceCode).ToHashSet(StringComparer.OrdinalIgnoreCase); // 3. 清理已不存在的实例(数据库中没有但内存中有) foreach (var instanceId in existingInstanceIds) { if (!dbDeviceCodes.Contains(instanceId)) { _logger.LogInformation("数据库中已删除设备 {InstanceId},从内存移除", instanceId); await _instanceManager.DeleteInstanceAsync(instanceId, deleteConfig: false); } } // 4. 创建或更新实例 foreach (var device in devices) { try { // 4.1 获取设备协议 var protocols = await _deviceService.GetDeviceProtocolsAsync(device.Id); // 预计算一个缓存字典:协议对象 -> DB编号 var dbCache = protocols.ToDictionary( p => p, p => int.TryParse(Regex.Match(p.DeviceProDataBlock, @"\d+").Value, out var n) ? n : 50 ); // 1. 获取唯一编号列表 var dbNumbers = dbCache.Values.Distinct().ToList(); // 4.2 创建协议模板 var templateId = $"protocol-{device.DeviceCode}"; var template = new ProtocolTemplate { Id = templateId, Name = $"{device.DeviceName} 协议模板", Version = "1.0", Fields = protocols.Select(p => new ProtocolFieldMapping { FieldKey = $"{p.DeviceChildCode}_{p.DeviceProParamName}", DbNumber = dbCache[p], Offset = (int)p.DeviceProOffset, Bit = 1, DataType = MapDataType(p.DeviceProDataType), Length = p.DeviceProDataLength, Direction = ProtocolFieldDirection.Bidirectional }).ToList() }; await _templateService.UpsertAsync(template); // 4.3 创建实例配置 var config = new InstanceConfig { Id = device.DeviceCode, Name = device.DeviceName, PLCType = SiemensPLCType.S71500, Port = device.DevicePort, AutoStart = false, ProtocolTemplateId = templateId, MemoryConfig = GetDefaultMemoryConfig(dbNumbers) }; // 4.4 创建或更新实例 if (_instanceManager.InstanceExists(device.DeviceCode)) { _logger.LogInformation("实例 {DeviceCode} 已存在,跳过创建", device.DeviceCode); } else { await _instanceManager.CreateInstanceAsync(config); _logger.LogInformation("已创建实例 {DeviceCode} (端口:{Port})", device.DeviceCode, device.DevicePort); } } catch (Exception ex) { _logger.LogError(ex, "同步设备 {DeviceCode} 失败", device.DeviceCode); } } _logger.LogInformation("同步完成"); } /// /// 数据类型映射:数据库字符串 -> ProtocolDataType 枚举 /// ProtocolDataType: Byte=0, Int=1, DInt=2, String=3, Bool=4 /// private static ProtocolDataType MapDataType(string? dbDataType) { return dbDataType?.ToLowerInvariant() switch { "bit" => ProtocolDataType.Bool, // Bool=4 "byte" => ProtocolDataType.Byte, // Byte=0 "int" or "word" => ProtocolDataType.Int, // Int=1 "dint" => ProtocolDataType.DInt, // DInt=2 "string" or "string8" or "string16" => ProtocolDataType.String, // String=3 _ => ProtocolDataType.Byte }; } /// /// 获取默认内存配置 /// private static MemoryRegionConfig GetDefaultMemoryConfig(List dbBlockNumbers) { return new MemoryRegionConfig { MRegionSize = 1024, DBBlockCount = dbBlockNumbers.Count, DBBlockNumbers = dbBlockNumbers, DBBlockSize = 65536, IRegionSize = 256, QRegionSize = 256, TRegionCount = 64, CRegionCount = 64 }; } }