# S7 PLC模拟器实施计划 > **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking. **目标:** 构建一个基于HSL Communication库的西门子S7 PLC模拟器系统,支持多实例、Web管理界面和数据持久化。 **架构:** 采用DDD分层架构,Core层实现领域逻辑,Application层提供应用服务,Server层提供Web API,Web层提供管理界面。使用HSL Communication库实现S7服务器,SignalR实现实时状态推送。 **技术栈:** ASP.NET Core 6.0, HslCommunication 12.6.3, SignalR, Razor Pages, Serilog, AutoMapper, xUnit --- ## 文件结构映射 ### Core项目 | 文件 | 职责 | |------|------| | `Enums/SiemensPLCType.cs` | PLC型号枚举 | | `Enums/InstanceStatus.cs` | 实例状态枚举 | | `Entities/InstanceConfig.cs` | 实例配置实体 | | `Entities/InstanceState.cs` | 实例状态实体 | | `Entities/S7ClientConnection.cs` | 客户端连接实体 | | `Entities/MemoryRegionConfig.cs` | 内存区域配置 | | `Interfaces/IMemoryRegion.cs` | 内存区域接口 | | `Interfaces/IMemoryStore.cs` | 内存存储接口 | | `Interfaces/IS7ServerInstance.cs` | 服务器实例接口 | | `Interfaces/ISimulatorInstanceManager.cs` | 实例管理器接口 | | `Interfaces/IPersistenceService.cs` | 持久化服务接口 | | `Memory/MemoryRegion.cs` | 内存区域基类 | | `Memory/MRegion.cs` | M区实现 | | `Memory/DBRegion.cs` | DB区实现 | | `Memory/IRegion.cs` | I区实现 | | `Memory/QRegion.cs` | Q区实现 | | `Memory/TRegion.cs` | T区实现 | | `Memory/CRegion.cs` | C区实现 | | `Memory/MemoryStore.cs` | 内存存储实现 | | `Persistence/FilePersistenceService.cs` | 文件持久化服务 | | `Server/S7ServerInstance.cs` | S7服务器实例实现 | | `Manager/SimulatorInstanceManager.cs` | 实例管理器实现 | ### Application项目 | 文件 | 职责 | |------|------| | `DTOs/InstanceDTO.cs` | 实例数据传输对象 | | `DTOs/CreateInstanceDTO.cs` | 创建实例DTO | | `DTOs/UpdateInstanceDTO.cs` | 更新实例DTO | | `DTOs/InstanceDetailDTO.cs` | 实例详情DTO | | `DTOs/MemoryReadDTO.cs` | 内存读取DTO | | `DTOs/MemoryWriteDTO.cs` | 内存写入DTO | | `DTOs/ClientConnectionDTO.cs` | 客户端连接DTO | | `DTOs/InstanceStateEventArgs.cs` | 状态事件参数 | | `DTOs/ClientConnectionEventArgs.cs` | 客户端连接事件参数 | | `Services/SimulatorInstanceAppService.cs` | 实例应用服务 | | `Services/MemoryAppService.cs` | 内存应用服务 | | `Services/ClientAppService.cs` | 客户端应用服务 | | `Profiles/MappingProfile.cs` | AutoMapper配置 | ### Server项目 | 文件 | 职责 | |------|------| | `Controllers/SimulatorInstancesController.cs` | 实例列表API | | `Controllers/SimulatorInstanceController.cs` | 实例控制API | | `Controllers/MemoryController.cs` | 内存操作API | | `Controllers/ClientsController.cs` | 客户端管理API | | `Hubs/SimulatorHub.cs` | SignalR实时推送 | | `Infrastructure/DependencyInjection.cs` | DI配置 | | `Infrastructure/Middleware/ExceptionMiddleware.cs` | 异常处理中间件 | | `Program.cs` | 程序入口 | ### Web项目 | 文件 | 职责 | |------|------| | `Pages/Index.cshtml` | 实例列表页 | | `Pages/Index.cshtml.cs` | 实例列表页模型 | | `Pages/Create.cshtml` | 创建实例页 | | `Pages/Create.cshtml.cs` | 创建实例页模型 | | `Pages/Edit.cshtml` | 编辑实例页 | | `Pages/Edit.cshtml.cs` | 编辑实例页模型 | | `Pages/Details.cshtml` | 实例详情页 | | `Pages/Details.cshtml.cs` | 实例详情页模型 | | `Pages/Shared/_Layout.cshtml` | 布局页 | | `wwwroot/css/site.css` | 样式文件 | | `wwwroot/js/site.js` | JavaScript文件 | ### Tests项目 | 文件 | 职责 | |------|------| | `Memory/MRegionTests.cs` | M区单元测试 | | `Memory/DBRegionTests.cs` | DB区单元测试 | | `Memory/MemoryStoreTests.cs` | 内存存储单元测试 | | `Server/S7ServerInstanceTests.cs` | 服务器实例单元测试 | | `Persistence/FilePersistenceServiceTests.cs` | 持久化服务单元测试 | --- ## Chunk 1: 项目基础设施 ### Task 1: 创建解决方案和项目结构 - [ ] **Step 1: 在WCS目录下创建S7模拟器解决方案** ```bash cd D:\Git\ShanMeiXinNengYuan\Code\WCS dotnet new sln -n WIDESEAWCS_S7Simulator ``` 预期输出: 创建 `WIDESEAWCS_S7Simulator.sln` - [ ] **Step 2: 创建Core项目** ```bash cd WIDESEAWCS_S7Simulator dotnet new classlib -n WIDESEAWCS_S7Simulator.Core -f net6.0 dotnet sln add WIDESEAWCS_S7Simulator.Core/WIDESEAWCS_S7Simulator.Core.csproj ``` - [ ] **Step 3: 创建Application项目** ```bash dotnet new classlib -n WIDESEAWCS_S7Simulator.Application -f net6.0 dotnet sln add WIDESEAWCS_S7Simulator.Application/WIDESEAWCS_S7Simulator.Application.csproj ``` - [ ] **Step 4: 创建Server项目** ```bash dotnet new webapi -n WIDESEAWCS_S7Simulator.Server -f net6.0 dotnet sln add WIDESEAWCS_S7Simulator.Server/WIDESEAWCS_S7Simulator.Server.csproj ``` - [ ] **Step 5: 创建Web项目** ```bash dotnet new webapp -n WIDESEAWCS_S7Simulator.Web -f net6.0 dotnet sln add WIDESEAWCS_S7Simulator.Web/WIDESEAWCS_S7Simulator.Web.csproj ``` - [ ] **Step 6: 创建Tests项目** ```bash dotnet new xunit -n WIDESEAWCS_S7Simulator.UnitTests -f net6.0 dotnet sln add WIDESEAWCS_S7Simulator.UnitTests/WIDESEAWCS_S7Simulator.UnitTests.csproj ``` - [ ] **Step 7: 添加项目引用** ```bash cd WIDESEAWCS_S7Simulator.Application dotnet add reference ../WIDESEAWCS_S7Simulator.Core/WIDESEAWCS_S7Simulator.Core.csproj cd ../WIDESEAWCS_S7Simulator.Server dotnet add reference ../WIDESEAWCS_S7Simulator.Core/WIDESEAWCS_S7Simulator.Core.csproj dotnet add reference ../WIDESEAWCS_S7Simulator.Application/WIDESEAWCS_S7Simulator.Application.csproj cd ../WIDESEAWCS_S7Simulator.Web dotnet add reference ../WIDESEAWCS_S7Simulator.Application/WIDESEAWCS_S7Simulator.Application.csproj cd ../WIDESEAWCS_S7Simulator.UnitTests dotnet add reference ../WIDESEAWCS_S7Simulator.Core/WIDESEAWCS_S7Simulator.Core.csproj ``` - [ ] **Step 8: 添加NuGet包到Core项目** ```bash cd ../WIDESEAWCS_S7Simulator.Core dotnet add package HslCommunication --version 12.6.3 dotnet add package Microsoft.Extensions.Logging.Abstractions dotnet add package Microsoft.Extensions.Configuration.Abstractions dotnet add package Newtonsoft.Json ``` - [ ] **Step 9: 添加NuGet包到Server项目** ```bash cd ../WIDESEAWCS_S7Simulator.Server dotnet add package Serilog.AspNetCore dotnet add package AutoMapper dotnet add package Microsoft.AspNetCore.SignalR ``` - [ ] **Step 10: 添加NuGet包到Web项目** ```bash cd ../WIDESEAWCS_S7Simulator.Web dotnet add package Microsoft.AspNetCore.SignalR.Client ``` - [ ] **Step 11: 验证解决方案构建** ```bash cd .. dotnet build ``` 预期输出: 构建成功 - [ ] **Step 12: 提交项目结构** ```bash git add . git commit -m "feat: create S7 simulator solution structure - Add Core, Application, Server, Web projects - Configure project references - Add required NuGet packages Co-Authored-By: Claude Opus 4.6 " ``` --- ## Chunk 2: 核心枚举和实体 ### Task 2: 创建枚举类型 **Files:** - Create: `WIDESEAWCS_S7Simulator.Core/Enums/SiemensPLCType.cs` - Create: `WIDESEAWCS_S7Simulator.Core/Enums/InstanceStatus.cs` - [ ] **Step 1: 创建PLC类型枚举** ```csharp namespace WIDESEAWCS_S7Simulator.Core.Enums { /// /// 西门子PLC型号 /// public enum SiemensPLCType { /// /// S7-200 Smart /// S7200Smart = 0, /// /// S7-1200 /// S71200 = 1, /// /// S7-1500 /// S71500 = 2, /// /// S7-300 /// S7300 = 3, /// /// S7-400 /// S7400 = 4 } } ``` - [ ] **Step 2: 创建实例状态枚举** ```csharp namespace WIDESEAWCS_S7Simulator.Core.Enums { /// /// S7服务器实例运行状态 /// public enum InstanceStatus { /// /// 已停止 /// Stopped = 0, /// /// 启动中 /// Starting = 1, /// /// 运行中 /// Running = 2, /// /// 停止中 /// Stopping = 3, /// /// 错误 /// Error = 4 } } ``` - [ ] **Step 3: 删除默认生成的Class1.cs文件** ```bash rm WIDESEAWCS_S7Simulator.Core/Class1.cs ``` - [ ] **Step 4: 验证构建** ```bash cd WIDESEAWCS_S7Simulator.Core dotnet build ``` - [ ] **Step 5: 提交枚举类型** ```bash git add . git commit -m "feat: add PLC type and instance status enums - Add SiemensPLCType enum (S7-200/1200/1500/300/400) - Add InstanceStatus enum (Stopped/Starting/Running/Stopping/Error) Co-Authored-By: Claude Opus 4.6 " ``` ### Task 3: 创建核心实体 **Files:** - Create: `WIDESEAWCS_S7Simulator.Core/Entities/MemoryRegionConfig.cs` - Create: `WIDESEAWCS_S7Simulator.Core/Entities/InstanceConfig.cs` - Create: `WIDESEAWCS_S7Simulator.Core/Entities/S7ClientConnection.cs` - Create: `WIDESEAWCS_S7Simulator.Core/Entities/InstanceState.cs` - [ ] **Step 1: 创建内存区域配置实体** ```csharp using Newtonsoft.Json; namespace WIDESEAWCS_S7Simulator.Core.Entities { /// /// 内存区域配置 /// public class MemoryRegionConfig { /// /// M区大小(字节),默认1KB /// [JsonProperty("mRegionSize")] public int MRegionSize { get; set; } = 1024; /// /// DB块数量,默认100个 /// [JsonProperty("dbBlockCount")] public int DBBlockCount { get; set; } = 100; /// /// 每个DB块大小(字节),默认1KB /// [JsonProperty("dbBlockSize")] public int DBBlockSize { get; set; } = 1024; /// /// I区大小(字节),默认256字节 /// [JsonProperty("iRegionSize")] public int IRegionSize { get; set; } = 256; /// /// Q区大小(字节),默认256字节 /// [JsonProperty("qRegionSize")] public int QRegionSize { get; set; } = 256; /// /// T区数量,默认64个 /// [JsonProperty("tRegionCount")] public int TRegionCount { get; set; } = 64; /// /// C区数量,默认64个 /// [JsonProperty("cRegionCount")] public int CRegionCount { get; set; } = 64; } } ``` - [ ] **Step 2: 创建实例配置实体** ```csharp using Newtonsoft.Json; using WIDESEAWCS_S7Simulator.Core.Enums; namespace WIDESEAWCS_S7Simulator.Core.Entities { /// /// S7服务器实例配置 /// public class InstanceConfig { /// /// 实例唯一标识 /// [JsonProperty("id")] public string Id { get; set; } = string.Empty; /// /// 实例名称 /// [JsonProperty("name")] public string Name { get; set; } = string.Empty; /// /// PLC型号 /// [JsonProperty("plcType")] public SiemensPLCType PLCType { get; set; } /// /// 监听端口 /// [JsonProperty("port")] public int Port { get; set; } /// /// HSL激活码 /// [JsonProperty("activationKey")] public string ActivationKey { get; set; } = string.Empty; /// /// 是否自动启动 /// [JsonProperty("autoStart")] public bool AutoStart { get; set; } /// /// 内存区域配置 /// [JsonProperty("memoryConfig")] public MemoryRegionConfig MemoryConfig { get; set; } = new(); } } ``` - [ ] **Step 3: 创建客户端连接实体** ```csharp namespace WIDESEAWCS_S7Simulator.Core.Entities { /// /// S7客户端连接信息 /// public class S7ClientConnection { /// /// 客户端唯一标识 /// public string ClientId { get; set; } = string.Empty; /// /// 客户端IP地址和端口 /// public string RemoteEndPoint { get; set; } = string.Empty; /// /// 连接时间 /// public DateTime ConnectedTime { get; set; } /// /// 最后活动时间 /// public DateTime LastActivityTime { get; set; } } } ``` - [ ] **Step 4: 创建实例状态实体** ```csharp using WIDESEAWCS_S7Simulator.Core.Enums; namespace WIDESEAWCS_S7Simulator.Core.Entities { /// /// S7服务器实例状态 /// public class InstanceState { /// /// 实例ID /// public string InstanceId { get; set; } = string.Empty; /// /// 运行状态 /// public InstanceStatus Status { get; set; } /// /// 当前连接的客户端数量 /// public int ClientCount { get; set; } /// /// 累计处理请求数 /// public long TotalRequests { get; set; } /// /// 启动时间 /// public DateTime? StartTime { get; set; } /// /// 最后活动时间 /// public DateTime? LastActivityTime { get; set; } /// /// 连接的客户端列表 /// public List Clients { get; set; } = new(); /// /// 错误信息(当状态为Error时) /// public string? ErrorMessage { get; set; } } } ``` - [ ] **Step 5: 验证构建** ```bash cd WIDESEAWCS_S7Simulator.Core dotnet build ``` - [ ] **Step 6: 提交核心实体** ```bash git add . git commit -m "feat: add core entities - Add MemoryRegionConfig for memory region sizes - Add InstanceConfig for server configuration - Add S7ClientConnection for client info - Add InstanceState for server state tracking Co-Authored-By: Claude Opus 4.6 " ``` --- ## Chunk 3: 内存存储实现 ### Task 4: 创建内存区域接口和基类 **Files:** - Create: `WIDESEAWCS_S7Simulator.Core/Interfaces/IMemoryRegion.cs` - Create: `WIDESEAWCS_S7Simulator.Core/Memory/MemoryRegion.cs` - [ ] **Step 1: 创建内存区域接口** ```csharp namespace WIDESEAWCS_S7Simulator.Core.Interfaces { /// /// 内存区域接口 /// public interface IMemoryRegion { /// /// 区域类型(M/DB/I/Q/T/C) /// string RegionType { get; } /// /// 区域大小(字节) /// int Size { get; } /// /// 读取字节数据 /// /// 偏移量 /// 长度 /// 字节数组 byte[] Read(ushort offset, ushort length); /// /// 写入字节数据 /// /// 偏移量 /// 数据 void Write(ushort offset, byte[] data); /// /// 清空区域 /// void Clear(); } } ``` - [ ] **Step 2: 创建内存区域基类** ```csharp using System.Threading; using WIDESEAWCS_S7Simulator.Core.Interfaces; namespace WIDESEAWCS_S7Simulator.Core.Memory { /// /// 内存区域基类 /// public abstract class MemoryRegion : IMemoryRegion { /// /// 内存数据 /// protected readonly byte[] _memory; /// /// 读写锁(支持并发访问) /// protected readonly ReaderWriterLockSlim _lock; /// /// 区域类型 /// public abstract string RegionType { get; } /// /// 区域大小(字节) /// public int Size { get; } /// /// 构造函数 /// /// 区域大小 protected MemoryRegion(int size) { Size = size; _memory = new byte[size]; _lock = new ReaderWriterLockSlim(); } /// /// 读取字节数据 /// public virtual byte[] Read(ushort offset, ushort length) { _lock.EnterReadLock(); try { if (offset + length > Size) throw new ArgumentOutOfRangeException( $"读取超出{RegionType}区范围: offset={offset}, length={length}, size={Size}"); byte[] result = new byte[length]; Array.Copy(_memory, offset, result, 0, length); return result; } finally { _lock.ExitReadLock(); } } /// /// 写入字节数据 /// public virtual void Write(ushort offset, byte[] data) { _lock.EnterWriteLock(); try { if (offset + data.Length > Size) throw new ArgumentOutOfRangeException( $"写入超出{RegionType}区范围: offset={offset}, length={data.Length}, size={Size}"); Array.Copy(data, 0, _memory, offset, data.Length); } finally { _lock.ExitWriteLock(); } } /// /// 清空区域 /// public virtual void Clear() { _lock.EnterWriteLock(); try { Array.Clear(_memory, 0, Size); } finally { _lock.ExitWriteLock(); } } /// /// 释放资源 /// public virtual void Dispose() { _lock?.Dispose(); } } } ``` - [ ] **Step 3: 验证构建** ```bash cd WIDESEAWCS_S7Simulator.Core dotnet build ``` - [ ] **Step 4: 提交内存区域基类** ```bash git add . git commit -m "feat: add memory region interface and base class - Add IMemoryRegion interface - Add MemoryRegion base class with thread-safe read/write - Support offset-based read/write operations Co-Authored-By: Claude Opus 4.6 " ``` ### Task 5: 实现M区(位存储器) **Files:** - Create: `WIDESEAWCS_S7Simulator.Core/Memory/MRegion.cs` - Test: `WIDESEAWCS_S7Simulator.UnitTests/Memory/MRegionTests.cs` - [ ] **Step 1: 编写M区测试** ```csharp using Xunit; using WIDESEAWCS_S7Simulator.Core.Memory; namespace WIDESEAWCS_S7Simulator.UnitTests.Memory { public class MRegionTests { [Fact] public void Constructor_WithValidSize_CreatesRegion() { // Arrange & Act var region = new MRegion(1024); // Assert Assert.Equal("M", region.RegionType); Assert.Equal(1024, region.Size); } [Fact] public void Read_WithinBounds_ReturnsData() { // Arrange var region = new MRegion(1024); var testData = new byte[] { 0x12, 0x34, 0x56, 0x78 }; region.Write(0, testData); // Act var result = region.Read(0, 4); // Assert Assert.Equal(testData, result); } [Fact] public void Read_OutOfBounds_ThrowsArgumentOutOfRange() { // Arrange var region = new MRegion(100); // Act & Assert Assert.Throws(() => region.Read(0, 101)); } [Fact] public void Write_WithinBounds_WritesData() { // Arrange var region = new MRegion(1024); var testData = new byte[] { 0xAA, 0xBB, 0xCC, 0xDD }; // Act region.Write(100, testData); var result = region.Read(100, 4); // Assert Assert.Equal(testData, result); } [Fact] public void Write_OutOfBounds_ThrowsArgumentOutOfRange() { // Arrange var region = new MRegion(100); var testData = new byte[] { 0x01, 0x02 }; // Act & Assert Assert.Throws(() => region.Write(99, testData)); } [Fact] public void ReadBit_ValidBit_ReturnsCorrectValue() { // Arrange var region = new MRegion(1024); region.Write(0, new byte[] { 0xFF }); // 所有位为1 // Act var result = region.ReadBit(0, 0); // Assert Assert.True(result); } [Fact] public void WriteBit_ValidBit_SetsCorrectValue() { // Arrange var region = new MRegion(1024); // Act region.WriteBit(0, 3, true); var result = region.ReadBit(0, 3); // Assert Assert.True(result); } [Fact] public void WriteBit_InvalidBitOffset_ThrowsArgumentOutOfRange() { // Arrange var region = new MRegion(1024); // Act & Assert Assert.Throws(() => region.WriteBit(0, 8, true)); } [Fact] public void Clear_ZerosAllMemory() { // Arrange var region = new MRegion(100); region.Write(0, new byte[] { 0xFF, 0xFF, 0xFF }); // Act region.Clear(); var result = region.Read(0, 3); // Assert Assert.Equal(new byte[] { 0, 0, 0 }, result); } [Fact] public void ConcurrentReadWrite_ThreadSafe() { // Arrange var region = new MRegion(1024); var exceptions = new System.Collections.Concurrent.ConcurrentBag(); var cts = new CancellationTokenSource(); cts.CancelAfter(1000); // 1秒后取消 // Act var writeTask = Task.Run(() => { try { var data = new byte[] { 0xAA, 0xBB }; while (!cts.Token.IsCancellationRequested) { region.Write(0, data); } } catch (Exception ex) { exceptions.Add(ex); } }, cts.Token); var readTask = Task.Run(() => { try { while (!cts.Token.IsCancellationRequested) { region.Read(0, 2); } } catch (Exception ex) { exceptions.Add(ex); } }, cts.Token); Task.WaitAll(writeTask, readTask); // Assert Assert.Empty(exceptions); } } } ``` - [ ] **Step 2: 运行测试验证失败** ```bash cd WIDESEAWCS_S7Simulator.UnitTests dotnet test Memory/MRegionTests.cs -v n ``` 预期输出: 测试失败(MRegion类不存在) - [ ] **Step 3: 实现MRegion类** ```csharp using System; using WIDESEAWCS_S7Simulator.Core.Interfaces; namespace WIDESEAWCS_S7Simulator.Core.Memory { /// /// M区(位存储器/Merker)实现 /// public class MRegion : MemoryRegion, IMemoryRegion { /// /// 区域类型 /// public override string RegionType => "M"; /// /// 构造函数 /// /// 区域大小(字节) public MRegion(int size) : base(size) { } /// /// 读取位 /// /// 字节偏移 /// 位偏移(0-7) /// 位值 public bool ReadBit(ushort byteOffset, byte bitOffset) { if (bitOffset > 7) throw new ArgumentOutOfRangeException(nameof(bitOffset), "位偏移必须在0-7之间"); _lock.EnterReadLock(); try { if (byteOffset >= Size) throw new ArgumentOutOfRangeException(nameof(byteOffset), "字节偏移超出范围"); return (_memory[byteOffset] & (1 << bitOffset)) != 0; } finally { _lock.ExitReadLock(); } } /// /// 写入位 /// /// 字节偏移 /// 位偏移(0-7) /// 位值 public void WriteBit(ushort byteOffset, byte bitOffset, bool value) { if (bitOffset > 7) throw new ArgumentOutOfRangeException(nameof(bitOffset), "位偏移必须在0-7之间"); _lock.EnterWriteLock(); try { if (byteOffset >= Size) throw new ArgumentOutOfRangeException(nameof(byteOffset), "字节偏移超出范围"); if (value) _memory[byteOffset] |= (byte)(1 << bitOffset); else _memory[byteOffset] &= (byte)~(1 << bitOffset); } finally { _lock.ExitWriteLock(); } } /// /// 读取字(Word,2字节) /// public ushort ReadWord(ushort byteOffset) { var data = Read(byteOffset, 2); return (ushort)((data[0] << 8) | data[1]); } /// /// 写入字(Word,2字节) /// public void WriteWord(ushort byteOffset, ushort value) { var data = new byte[] { (byte)(value >> 8), (byte)(value & 0xFF) }; Write(byteOffset, data); } /// /// 读取双字(DWord,4字节) /// public uint ReadDWord(ushort byteOffset) { var data = Read(byteOffset, 4); return (uint)((data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3]); } /// /// 写入双字(DWord,4字节) /// public void WriteDWord(ushort byteOffset, uint value) { var data = new byte[] { (byte)(value >> 24), (byte)((value >> 16) & 0xFF), (byte)((value >> 8) & 0xFF), (byte)(value & 0xFF) }; Write(byteOffset, data); } /// /// 读取整数(Int,2字节,有符号) /// public short ReadInt(ushort byteOffset) { return (short)ReadWord(byteOffset); } /// /// 写入整数(Int,2字节,有符号) /// public void WriteInt(ushort byteOffset, short value) { WriteWord(byteOffset, (ushort)value); } /// /// 读取双整数(DInt,4字节,有符号) /// public int ReadDInt(ushort byteOffset) { return (int)ReadDWord(byteOffset); } /// /// 写入双整数(DInt,4字节,有符号) /// public void WriteDInt(ushort byteOffset, int value) { WriteDWord(byteOffset, (uint)value); } /// /// 读取浮点数(Real,4字节) /// public float ReadReal(ushort byteOffset) { var bytes = Read(byteOffset, 4); return BitConverter.ToSingle(bytes, 0); } /// /// 写入浮点数(Real,4字节) /// public void WriteReal(ushort byteOffset, float value) { var bytes = BitConverter.GetBytes(value); Write(byteOffset, bytes); } } } ``` - [ ] **Step 4: 运行测试验证通过** ```bash dotnet test Memory/MRegionTests.cs -v n ``` 预期输出: 所有测试通过 - [ ] **Step 5: 提交M区实现** ```bash git add . git commit -m "feat: implement M region (Merker memory) - Add MRegion class with bit/word/dword/int/dint/real operations - Support individual bit read/write with thread-safety - Add comprehensive unit tests including concurrent access test Co-Authored-By: Claude Opus 4.6 " ``` ### Task 6: 实现DB区(数据块) **Files:** - Create: `WIDESEAWCS_S7Simulator.Core/Memory/DBRegion.cs` - Test: `WIDESEAWCS_S7Simulator.UnitTests/Memory/DBRegionTests.cs` - [ ] **Step 1: 编写DB区测试** ```csharp using Xunit; using WIDESEAWCS_S7Simulator.Core.Memory; namespace WIDESEAWCS_S7Simulator.UnitTests.Memory { public class DBRegionTests { [Fact] public void Constructor_WithValidParameters_CreatesRegion() { // Arrange & Act var region = new DBRegion(100, 1024); // Assert Assert.Equal("DB", region.RegionType); Assert.Equal(100 * 1024, region.Size); } [Fact] public void Read_ValidDBNumberAndOffset_ReturnsData() { // Arrange var region = new DBRegion(10, 1024); var testData = new byte[] { 0x12, 0x34, 0x56, 0x78 }; region.Write(1, 0, testData); // Act var result = region.Read(1, 0, 4); // Assert Assert.Equal(testData, result); } [Fact] public void Read_InvalidDBNumber_ThrowsArgumentException() { // Arrange var region = new DBRegion(10, 1024); // Act & Assert Assert.Throws(() => region.Read(99, 0, 1)); } [Fact] public void Read_OutOfBounds_ThrowsArgumentOutOfRange() { // Arrange var region = new DBRegion(10, 100); // Act & Assert Assert.Throws(() => region.Read(1, 0, 101)); } [Fact] public void Write_MultipleDBs_StoresSeparately() { // Arrange var region = new DBRegion(10, 100); var data1 = new byte[] { 0x01, 0x02 }; var data2 = new byte[] { 0x03, 0x04 }; // Act region.Write(1, 0, data1); region.Write(2, 0, data2); var result1 = region.Read(1, 0, 2); var result2 = region.Read(2, 0, 2); // Assert Assert.Equal(data1, result1); Assert.Equal(data2, result2); } [Fact] public void Clear_AllBlocks_ZerosAllMemory() { // Arrange var region = new DBRegion(5, 100); region.Write(1, 0, new byte[] { 0xFF }); region.Write(2, 0, new byte[] { 0xFF }); // Act region.Clear(); // Assert Assert.Equal(new byte[] { 0x00 }, region.Read(1, 0, 1)); Assert.Equal(new byte[] { 0x00 }, region.Read(2, 0, 1)); } [Fact] public void Read_IntType_ReturnsCorrectValue() { // Arrange var region = new DBRegion(10, 1024); region.WriteInt(1, 0, 12345); // Act var result = region.ReadInt(1, 0); // Assert Assert.Equal(12345, result); } [Fact] public void Read_DIntType_ReturnsCorrectValue() { // Arrange var region = new DBRegion(10, 1024); region.WriteDInt(1, 0, 123456789); // Act var result = region.ReadDInt(1, 0); // Assert Assert.Equal(123456789, result); } [Fact] public void Read_RealType_ReturnsCorrectValue() { // Arrange var region = new DBRegion(10, 1024); region.WriteReal(1, 0, 3.14159f); // Act var result = region.ReadReal(1, 0); // Assert Assert.Equal(3.14159f, result, 4); } [Fact] public void Read_BoolType_ReturnsCorrectValue() { // Arrange var region = new DBRegion(10, 1024); region.WriteBool(1, 0, 0, true); // Act var result = region.ReadBool(1, 0, 0); // Assert Assert.True(result); } [Fact] public void ReadString_WriteAndReadString_ReturnsOriginalString() { // Arrange var region = new DBRegion(10, 1024); var testString = "Hello"; // Act region.WriteString(1, 0, 254, testString); // 最大长度254 var result = region.ReadString(1, 0, 254); // Assert Assert.Equal(testString, result); } } } ``` - [ ] **Step 2: 运行测试验证失败** ```bash dotnet test Memory/DBRegionTests.cs -v n ``` 预期输出: 测试失败 - [ ] **Step 3: 实现DBRegion类** ```csharp using System; using System.Collections.Generic; using System.Text; using WIDESEAWCS_S7Simulator.Core.Interfaces; namespace WIDESEAWCS_S7Simulator.Core.Memory { /// /// DB区(数据块)实现 /// public class DBRegion : MemoryRegion, IMemoryRegion { /// /// DB块字典(块号 -> 数据) /// private readonly Dictionary _blocks; /// /// DB块数量 /// private readonly ushort _blockCount; /// /// 每个DB块大小 /// private readonly int _blockSize; /// /// 区域类型 /// public override string RegionType => "DB"; /// /// 构造函数 /// /// DB块数量 /// 每个DB块大小 public DBRegion(ushort blockCount, int blockSize) : base(blockCount * blockSize) { _blockCount = blockCount; _blockSize = blockSize; _blocks = new Dictionary(); // 预分配所有DB块 for (ushort i = 1; i <= blockCount; i++) { _blocks[i] = new byte[blockSize]; } } /// /// 读取指定DB块的数据 /// /// DB块号 /// 块内偏移 /// 长度 /// 字节数组 public byte[] Read(ushort dbNumber, ushort offset, ushort length) { _lock.EnterReadLock(); try { if (!_blocks.ContainsKey(dbNumber)) throw new ArgumentException($"DB块不存在: DB{dbNumber}"); if (offset + length > _blockSize) throw new ArgumentOutOfRangeException( $"读取超出DB{dbNumber}范围: offset={offset}, length={length}, size={_blockSize}"); byte[] block = _blocks[dbNumber]; byte[] result = new byte[length]; Array.Copy(block, offset, result, 0, length); return result; } finally { _lock.ExitReadLock(); } } /// /// 写入指定DB块的数据 /// /// DB块号 /// 块内偏移 /// 数据 public void Write(ushort dbNumber, ushort offset, byte[] data) { _lock.EnterWriteLock(); try { if (!_blocks.ContainsKey(dbNumber)) throw new ArgumentException($"DB块不存在: DB{dbNumber}"); if (offset + data.Length > _blockSize) throw new ArgumentOutOfRangeException( $"写入超出DB{dbNumber}范围: offset={offset}, length={data.Length}, size={_blockSize}"); byte[] block = _blocks[dbNumber]; Array.Copy(data, 0, block, offset, data.Length); } finally { _lock.ExitWriteLock(); } } /// /// 读取数据(默认DB1) /// public override byte[] Read(ushort offset, ushort length) { return Read(1, offset, length); } /// /// 写入数据(默认DB1) /// public override void Write(ushort offset, byte[] data) { Write(1, offset, data); } /// /// 清空所有DB块 /// public override void Clear() { _lock.EnterWriteLock(); try { foreach (var block in _blocks.Values) { Array.Clear(block, 0, block.Length); } } finally { _lock.ExitWriteLock(); } } /// /// 读取位 /// public bool ReadBool(ushort dbNumber, ushort byteOffset, byte bitOffset) { if (bitOffset > 7) throw new ArgumentOutOfRangeException(nameof(bitOffset), "位偏移必须在0-7之间"); var data = Read(dbNumber, byteOffset, 1); return (data[0] & (1 << bitOffset)) != 0; } /// /// 写入位 /// public void WriteBool(ushort dbNumber, ushort byteOffset, byte bitOffset, bool value) { if (bitOffset > 7) throw new ArgumentOutOfRangeException(nameof(bitOffset), "位偏移必须在0-7之间"); var data = Read(dbNumber, byteOffset, 1); if (value) data[0] |= (byte)(1 << bitOffset); else data[0] &= (byte)~(1 << bitOffset); Write(dbNumber, byteOffset, data); } /// /// 读取整数(Int,2字节) /// public short ReadInt(ushort dbNumber, ushort offset) { var data = Read(dbNumber, offset, 2); return (short)((data[0] << 8) | data[1]); } /// /// 写入整数(Int,2字节) /// public void WriteInt(ushort dbNumber, ushort offset, short value) { var data = new byte[] { (byte)(value >> 8), (byte)(value & 0xFF) }; Write(dbNumber, offset, data); } /// /// 读取双整数(DInt,4字节) /// public int ReadDInt(ushort dbNumber, ushort offset) { var data = Read(dbNumber, offset, 4); return (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3]; } /// /// 写入双整数(DInt,4字节) /// public void WriteDInt(ushort dbNumber, ushort offset, int value) { var data = new byte[] { (byte)(value >> 24), (byte)((value >> 16) & 0xFF), (byte)((value >> 8) & 0xFF), (byte)(value & 0xFF) }; Write(dbNumber, offset, data); } /// /// 读取浮点数(Real,4字节) /// public float ReadReal(ushort dbNumber, ushort offset) { var bytes = Read(dbNumber, offset, 4); return BitConverter.ToSingle(bytes, 0); } /// /// 写入浮点数(Real,4字节) /// public void WriteReal(ushort dbNumber, ushort offset, float value) { var bytes = BitConverter.GetBytes(value); Write(dbNumber, offset, bytes); } /// /// 读取字符串(西门子S7字符串格式:首字节是最大长度,第二字节是实际长度) /// public string ReadString(ushort dbNumber, ushort offset, byte maxLength) { var data = Read(dbNumber, offset, (ushort)(maxLength + 2)); byte actualLength = data[1]; if (actualLength > maxLength) actualLength = maxLength; return Encoding.ASCII.GetString(data, 2, actualLength); } /// /// 写入字符串(西门子S7字符串格式) /// public void WriteString(ushort dbNumber, ushort offset, byte maxLength, string value) { if (string.IsNullOrEmpty(value)) value = string.Empty; byte[] bytes = Encoding.ASCII.GetBytes(value); byte length = (byte)Math.Min(bytes.Length, maxLength); byte[] data = new byte[maxLength + 2]; data[0] = maxLength; data[1] = length; Array.Copy(bytes, 0, data, 2, length); Write(dbNumber, offset, data); } /// /// 释放资源 /// public override void Dispose() { _blocks.Clear(); base.Dispose(); } } } ``` - [ ] **Step 4: 运行测试验证通过** ```bash dotnet test Memory/DBRegionTests.cs -v n ``` 预期输出: 所有测试通过 - [ ] **Step 5: 提交DB区实现** ```bash git add . git commit -m "feat: implement DB region (Data Blocks) - Add DBRegion class supporting multiple data blocks - Support bool/int/dint/real/string data types - Add comprehensive unit tests for all data types Co-Authored-By: Claude Opus 4.6 " ``` ### Task 7: 实现I/Q/T/C区 **Files:** - Create: `WIDESEAWCS_S7Simulator.Core/Memory/IRegion.cs` - Create: `WIDESEAWCS_S7Simulator.Core/Memory/QRegion.cs` - Create: `WIDESEAWCS_S7Simulator.Core/Memory/TRegion.cs` - Create: `WIDESEAWCS_S7Simulator.Core/Memory/CRegion.cs` - [ ] **Step 1: 实现I区(输入区)** ```csharp namespace WIDESEAWCS_S7Simulator.Core.Memory { /// /// I区(输入区/Input)实现 /// public class IRegion : MemoryRegion { /// /// 区域类型 /// public override string RegionType => "I"; /// /// 构造函数 /// /// 区域大小(字节) public IRegion(int size) : base(size) { } /// /// 读取位 /// public bool ReadBit(ushort byteOffset, byte bitOffset) { if (bitOffset > 7) throw new ArgumentOutOfRangeException(nameof(bitOffset), "位偏移必须在0-7之间"); _lock.EnterReadLock(); try { if (byteOffset >= Size) throw new ArgumentOutOfRangeException(nameof(byteOffset)); return (_memory[byteOffset] & (1 << bitOffset)) != 0; } finally { _lock.ExitReadLock(); } } /// /// 写入位 /// public void WriteBit(ushort byteOffset, byte bitOffset, bool value) { if (bitOffset > 7) throw new ArgumentOutOfRangeException(nameof(bitOffset), "位偏移必须在0-7之间"); _lock.EnterWriteLock(); try { if (byteOffset >= Size) throw new ArgumentOutOfRangeException(nameof(byteOffset)); if (value) _memory[byteOffset] |= (byte)(1 << bitOffset); else _memory[byteOffset] &= (byte)~(1 << bitOffset); } finally { _lock.ExitWriteLock(); } } } } ``` - [ ] **Step 2: 实现Q区(输出区)** ```csharp namespace WIDESEAWCS_S7Simulator.Core.Memory { /// /// Q区(输出区/Output)实现 /// public class QRegion : MemoryRegion { /// /// 区域类型 /// public override string RegionType => "Q"; /// /// 构造函数 /// /// 区域大小(字节) public QRegion(int size) : base(size) { } /// /// 读取位 /// public bool ReadBit(ushort byteOffset, byte bitOffset) { if (bitOffset > 7) throw new ArgumentOutOfRangeException(nameof(bitOffset), "位偏移必须在0-7之间"); _lock.EnterReadLock(); try { if (byteOffset >= Size) throw new ArgumentOutOfRangeException(nameof(byteOffset)); return (_memory[byteOffset] & (1 << bitOffset)) != 0; } finally { _lock.ExitReadLock(); } } /// /// 写入位 /// public void WriteBit(ushort byteOffset, byte bitOffset, bool value) { if (bitOffset > 7) throw new ArgumentOutOfRangeException(nameof(bitOffset), "位偏移必须在0-7之间"); _lock.EnterWriteLock(); try { if (byteOffset >= Size) throw new ArgumentOutOfRangeException(nameof(byteOffset)); if (value) _memory[byteOffset] |= (byte)(1 << bitOffset); else _memory[byteOffset] &= (byte)~(1 << bitOffset); } finally { _lock.ExitWriteLock(); } } } } ``` - [ ] **Step 3: 实现T区(定时器区)** ```csharp namespace WIDESEAWCS_S7Simulator.Core.Memory { /// /// T区(定时器区/Timer)实现 /// public class TRegion : MemoryRegion { /// /// 区域类型 /// public override string RegionType => "T"; /// /// 每个定时器占用的字节数(S7定时器为2字节) /// private const int TimerSize = 2; /// /// 构造函数 /// /// 定时器数量 public TRegion(int timerCount) : base(timerCount * TimerSize) { } /// /// 读取定时器值 /// /// 定时器号 /// 定时器值(毫秒) public ushort ReadTimer(ushort timerNumber) { ushort offset = (ushort)((timerNumber - 1) * TimerSize); var data = Read(offset, TimerSize); return (ushort)((data[0] << 8) | data[1]); } /// /// 写入定时器值 /// /// 定时器号 /// 定时器值(毫秒) public void WriteTimer(ushort timerNumber, ushort value) { ushort offset = (ushort)((timerNumber - 1) * TimerSize); var data = new byte[] { (byte)(value >> 8), (byte)(value & 0xFF) }; Write(offset, data); } } } ``` - [ ] **Step 4: 实现C区(计数器区)** ```csharp namespace WIDESEAWCS_S7Simulator.Core.Memory { /// /// C区(计数器区/Counter)实现 /// public class CRegion : MemoryRegion { /// /// 区域类型 /// public override string RegionType => "C"; /// /// 每个计数器占用的字节数(S7计数器为2字节) /// private const int CounterSize = 2; /// /// 构造函数 /// /// 计数器数量 public CRegion(int counterCount) : base(counterCount * CounterSize) { } /// /// 读取计数器值 /// /// 计数器号 /// 计数器值 public ushort ReadCounter(ushort counterNumber) { ushort offset = (ushort)((counterNumber - 1) * CounterSize); var data = Read(offset, CounterSize); return (ushort)((data[0] << 8) | data[1]); } /// /// 写入计数器值 /// /// 计数器号 /// 计数器值 public void WriteCounter(ushort counterNumber, ushort value) { ushort offset = (ushort)((counterNumber - 1) * CounterSize); var data = new byte[] { (byte)(value >> 8), (byte)(value & 0xFF) }; Write(offset, data); } } } ``` - [ ] **Step 5: 验证构建** ```bash cd WIDESEAWCS_S7Simulator.Core dotnet build ``` - [ ] **Step 6: 提交I/Q/T/C区实现** ```bash git add . git commit -m "feat: implement I/Q/T/C regions - Add IRegion (Input) with bit operations - Add QRegion (Output) with bit operations - Add TRegion (Timer) with timer read/write - Add CRegion (Counter) with counter read/write Co-Authored-By: Claude Opus 4.6 " ``` ### Task 8: 实现内存存储 **Files:** - Create: `WIDESEAWCS_S7Simulator.Core/Interfaces/IMemoryStore.cs` - Create: `WIDESEAWCS_S7Simulator.Core/Memory/MemoryStore.cs` - Test: `WIDESEAWCS_S7Simulator.UnitTests/Memory/MemoryStoreTests.cs` - [ ] **Step 1: 创建内存存储接口** ```csharp namespace WIDESEAWCS_S7Simulator.Core.Interfaces { /// /// 内存存储接口 /// public interface IMemoryStore { /// /// 读取字节数据 /// /// 地址(如 "M100", "DB1.DBD0") /// 长度 /// 字节数组 byte[] ReadBytes(string address, ushort length); /// /// 读取指定类型数据 /// T Read(string address) where T : struct; /// /// 写入字节数据 /// /// 地址 /// 数据 void WriteBytes(string address, byte[] data); /// /// 写入指定类型数据 /// void Write(string address, T value) where T : struct; /// /// 获取内存区域 /// IMemoryRegion GetRegion(string regionType); /// /// 清空所有内存 /// void Clear(); /// /// 导出内存数据 /// Dictionary Export(); /// /// 导入内存数据 /// void Import(Dictionary data); } } ``` - [ ] **Step 2: 编写内存存储测试** ```csharp using Xunit; using WIDESEAWCS_S7Simulator.Core.Memory; using WIDESEAWCS_S7Simulator.Core.Entities; namespace WIDESEAWCS_S7Simulator.UnitTests.Memory { public class MemoryStoreTests { [Fact] public void Constructor_WithConfig_CreatesAllRegions() { // Arrange var config = new MemoryRegionConfig { MRegionSize = 1024, DBBlockCount = 10, DBBlockSize = 1024, IRegionSize = 256, QRegionSize = 256, TRegionCount = 32, CRegionCount = 32 }; // Act var store = new MemoryStore(config); // Assert Assert.NotNull(store.GetRegion("M")); Assert.NotNull(store.GetRegion("DB")); Assert.NotNull(store.GetRegion("I")); Assert.NotNull(store.GetRegion("Q")); Assert.NotNull(store.GetRegion("T")); Assert.NotNull(store.GetRegion("C")); } [Fact] public void WriteBytes_MRegion_WritesToMRegion() { // Arrange var store = new MemoryStore(new MemoryRegionConfig()); var data = new byte[] { 0x12, 0x34 }; // Act store.WriteBytes("M100", data); var result = store.ReadBytes("M100", 2); // Assert Assert.Equal(data, result); } [Fact] public void Write_ReadInt_ReturnsCorrectValue() { // Arrange var store = new MemoryStore(new MemoryRegionConfig()); // Act store.Write(100, 12345); var result = store.Read("M100"); // Assert Assert.Equal(12345, result); } [Fact] public void Write_ReadDInt_ReturnsCorrectValue() { // Arrange var store = new MemoryStore(new MemoryRegionConfig()); // Act store.Write("M100", 123456789); var result = store.Read("M100"); // Assert Assert.Equal(123456789, result); } [Fact] public void Write_ReadReal_ReturnsCorrectValue() { // Arrange var store = new MemoryStore(new MemoryRegionConfig()); // Act store.Write("M100", 3.14159f); var result = store.Read("M100"); // Assert Assert.Equal(3.14159f, result, 4); } [Fact] public void WriteBytes_DBRegion_WritesToSpecificDB() { // Arrange var store = new MemoryStore(new MemoryRegionConfig()); var data = new byte[] { 0xAA, 0xBB }; // Act store.WriteBytes("DB1.DBD0", data); var result = store.ReadBytes("DB1.DBD0", 2); // Assert Assert.Equal(data, result); } [Fact] public void Write_IRegion_WritesToIRegion() { // Arrange var store = new MemoryStore(new MemoryRegionConfig()); // Act store.Write("I0", true); var result = store.Read("I0"); // Assert Assert.True(result); } [Fact] public void Clear_AllRegions_ZerosAllMemory() { // Arrange var store = new MemoryStore(new MemoryRegionConfig()); store.Write("M100", 12345); // Act store.Clear(); var result = store.Read("M100"); // Assert Assert.Equal(0, result); } [Fact] public void ExportImport_PreservesAllMemory() { // Arrange var store = new MemoryStore(new MemoryRegionConfig()); store.Write("M100", 12345); store.Write("DB1.DBD0", 67890); // Act var exported = store.Export(); var newStore = new MemoryStore(new MemoryRegionConfig()); newStore.Import(exported); // Assert Assert.Equal(12345, newStore.Read("M100")); Assert.Equal(67890, newStore.Read("DB1.DBD0")); } } } ``` - [ ] **Step 3: 运行测试验证失败** ```bash dotnet test Memory/MemoryStoreTests.cs -v n ``` 预期输出: 测试失败 - [ ] **Step 4: 实现MemoryStore类** ```csharp using System.Collections.Generic; using System.Text.RegularExpressions; using WIDESEAWCS_S7Simulator.Core.Entities; using WIDESEAWCS_S7Simulator.Core.Interfaces; namespace WIDESEAWCS_S7Simulator.Core.Memory { /// /// 内存存储实现 /// public class MemoryStore : IMemoryStore { /// /// 内存区域字典 /// private readonly Dictionary _regions; /// /// 内存配置 /// private readonly MemoryRegionConfig _config; /// /// 地址解析正则表达式 /// private static readonly Regex AddressRegex = new Regex( @"^(M|DB(\d+)\.)?(I|Q|T|C)?(\d+)(?:\.([BWD]))?$", RegexOptions.IgnoreCase | RegexOptions.Compiled); /// /// 构造函数 /// /// 内存配置 public MemoryStore(MemoryRegionConfig config) { _config = config; _regions = new Dictionary(); InitializeRegions(); } /// /// 初始化所有内存区域 /// private void InitializeRegions() { _regions["M"] = new MRegion(_config.MRegionSize); _regions["DB"] = new DBRegion( (ushort)_config.DBBlockCount, _config.DBBlockSize); _regions["I"] = new IRegion(_config.IRegionSize); _regions["Q"] = new QRegion(_config.QRegionSize); _regions["T"] = new TRegion(_config.TRegionCount); _regions["C"] = new CRegion(_config.CRegionCount); } /// /// 获取内存区域 /// public IMemoryRegion GetRegion(string regionType) { return _regions.TryGetValue(regionType.ToUpper(), out var region) ? region : throw new KeyNotFoundException($"内存区域不存在: {regionType}"); } /// /// 读取字节数据 /// public byte[] ReadBytes(string address, ushort length) { var (regionType, offset, dbNumber) = ParseAddress(address); var region = GetRegion(regionType); if (regionType == "DB" && dbNumber.HasValue) { return ((DBRegion)region).Read(dbNumber.Value, offset, length); } return region.Read(offset, length); } /// /// 读取指定类型数据 /// public T Read(string address) where T : struct { var type = typeof(T); var (regionType, offset, dbNumber) = ParseAddress(address); var region = GetRegion(regionType); if (type == typeof(bool)) { byte bitOffset = (byte)(offset % 8); ushort byteOffset = (ushort)(offset / 8); if (regionType == "M") return (T)(object)((MRegion)region).ReadBit(byteOffset, bitOffset); else if (regionType == "I") return (T)(object)((IRegion)region).ReadBit(byteOffset, bitOffset); else if (regionType == "Q") return (T)(object)((QRegion)region).ReadBit(byteOffset, bitOffset); else if (regionType == "DB" && dbNumber.HasValue) return (T)(object)((DBRegion)region).ReadBool(dbNumber.Value, byteOffset, bitOffset); } else if (type == typeof(short) || type == typeof(int)) { if (regionType == "M") return (T)(object)((MRegion)region).ReadInt(offset); else if (regionType == "DB" && dbNumber.HasValue) return (T)(object)((DBRegion)region).ReadInt(dbNumber.Value, offset); } else if (type == typeof(int)) { if (regionType == "M") return (T)(object)((MRegion)region).ReadDInt(offset); else if (regionType == "DB" && dbNumber.HasValue) return (T)(object)((DBRegion)region).ReadDInt(dbNumber.Value, offset); } else if (type == typeof(float)) { if (regionType == "M") return (T)(object)((MRegion)region).ReadReal(offset); else if (regionType == "DB" && dbNumber.HasValue) return (T)(object)((DBRegion)region).ReadReal(dbNumber.Value, offset); } // 默认读取字节 var bytes = ReadBytes(address, 2); if (type == typeof(short)) return (T)(object)((short)((bytes[0] << 8) | bytes[1])); throw new NotSupportedException($"不支持的类型: {type.Name}"); } /// /// 写入字节数据 /// public void WriteBytes(string address, byte[] data) { var (regionType, offset, dbNumber) = ParseAddress(address); var region = GetRegion(regionType); if (regionType == "DB" && dbNumber.HasValue) { ((DBRegion)region).Write(dbNumber.Value, offset, data); } else { region.Write(offset, data); } } /// /// 写入指定类型数据 /// public void Write(string address, T value) where T : struct { var type = typeof(T); var (regionType, offset, dbNumber) = ParseAddress(address); var region = GetRegion(regionType); if (type == typeof(bool)) { byte bitOffset = (byte)(offset % 8); ushort byteOffset = (ushort)(offset / 8); bool boolValue = (bool)(object)value; if (regionType == "M") ((MRegion)region).WriteBit(byteOffset, bitOffset, boolValue); else if (regionType == "I") ((IRegion)region).WriteBit(byteOffset, bitOffset, boolValue); else if (regionType == "Q") ((QRegion)region).WriteBit(byteOffset, bitOffset, boolValue); else if (regionType == "DB" && dbNumber.HasValue) ((DBRegion)region).WriteBool(dbNumber.Value, byteOffset, bitOffset, boolValue); } else if (type == typeof(short)) { short shortValue = (short)(object)value; if (regionType == "M") ((MRegion)region).WriteInt(offset, shortValue); else if (regionType == "DB" && dbNumber.HasValue) ((DBRegion)region).WriteInt(dbNumber.Value, offset, shortValue); } else if (type == typeof(int)) { int intValue = (int)(object)value; if (regionType == "M") ((MRegion)region).WriteDInt(offset, intValue); else if (regionType == "DB" && dbNumber.HasValue) ((DBRegion)region).WriteDInt(dbNumber.Value, offset, intValue); } else if (type == typeof(float)) { float floatValue = (float)(object)value; if (regionType == "M") ((MRegion)region).WriteReal(offset, floatValue); else if (regionType == "DB" && dbNumber.HasValue) ((DBRegion)region).WriteReal(dbNumber.Value, offset, floatValue); } else { throw new NotSupportedException($"不支持的类型: {type.Name}"); } } /// /// 清空所有内存 /// public void Clear() { foreach (var region in _regions.Values) { region.Clear(); } } /// /// 导出内存数据 /// public Dictionary Export() { var data = new Dictionary(); foreach (var kvp in _regions) { var region = kvp.Value; var regionData = region.Read(0, (ushort)region.Size); data[$"{kvp.Key}_data"] = regionData; } return data; } /// /// 导入内存数据 /// public void Import(Dictionary data) { foreach (var kvp in data) { if (kvp.Key.EndsWith("_data")) { var regionType = kvp.Key.Replace("_data", ""); if (_regions.ContainsKey(regionType)) { _regions[regionType].Write(0, kvp.Value); } } } } /// /// 解析地址 /// /// 地址字符串 /// (区域类型, 偏移量, DB块号) private (string regionType, ushort offset, ushort? dbNumber) ParseAddress(string address) { address = address.ToUpper().Trim(); // 解析 M100 if (address.StartsWith("M")) { var offset = ushort.Parse(address.Substring(1)); return ("M", offset, null); } // 解析 I0, Q0, T1, C1 if (address.StartsWith("I")) return ("I", ushort.Parse(address.Substring(1)), null); if (address.StartsWith("Q")) return ("Q", ushort.Parse(address.Substring(1)), null); if (address.StartsWith("T")) return ("T", ushort.Parse(address.Substring(1)), null); if (address.StartsWith("C")) return ("C", ushort.Parse(address.Substring(1)), null); // 解析 DB1.DBD0 或 DB1.DBW0 if (address.StartsWith("DB")) { var parts = address.Substring(2).Split('.'); var dbNumber = ushort.Parse(parts[0]); var offsetPart = parts[1]; // DBD = DWord (4字节), DBW = Word (2字节), DBB = Byte (1字节) ushort offset; if (offsetPart.StartsWith("DBD")) offset = ushort.Parse(offsetPart.Substring(3)); else if (offsetPart.StartsWith("DBW")) offset = ushort.Parse(offsetPart.Substring(3)); else if (offsetPart.StartsWith("DBB")) offset = ushort.Parse(offsetPart.Substring(3)); else if (offsetPart.StartsWith("DBX")) { // DBX = Bit (位地址) var bitAddress = offsetPart.Substring(3).Split('.'); offset = ushort.Parse(bitAddress[0]); // 转换为字节偏移+位偏移 offset = (ushort)(offset * 8 + byte.Parse(bitAddress[1])); } else offset = ushort.Parse(offsetPart); return ("DB", offset, dbNumber); } throw new ArgumentException($"无效的地址格式: {address}"); } /// /// 释放资源 /// public void Dispose() { foreach (var region in _regions.Values) { if (region is IDisposable disposable) { disposable.Dispose(); } } _regions.Clear(); } } } ``` - [ ] **Step 5: 运行测试验证通过** ```bash dotnet test Memory/MemoryStoreTests.cs -v n ``` 预期输出: 所有测试通过 - [ ] **Step 6: 提交内存存储实现** ```bash git add . git commit -m "feat: implement MemoryStore with address parsing - Add MemoryStore class managing all memory regions - Support M/DB/I/Q/T/C address parsing - Support bool/short/int/float data types - Add export/import functionality for persistence - Add comprehensive unit tests Co-Authored-By: Claude Opus 4.6 " ``` --- **计划继续中...** 由于计划很长,我将分多个chunk保存。这是Chunk 1-3,涵盖了项目基础设施、核心实体和内存存储实现。 **是否继续执行下一部分(持久化服务和服务器实例)?**