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
};
}
}