Parcourir la source

Add new project ModbusSimulationProtocol

SenGao il y a 2 semaines
Parent
commit
6ddec9a9a3

+ 61 - 0
CommunicationProtocols/ModbusSimulationProtocol/Data/PointSource.cs

@@ -0,0 +1,61 @@
+using NModbus;
+
+namespace ModbusSimulationProtocol.Data;
+
+public class PointSource<TPoint> : IPointSource<TPoint>
+{
+    private readonly int _capacity;
+    private readonly TPoint[] _points;
+    private readonly bool _writeable;
+    private readonly object _lock = new object();
+
+    /// <summary>
+    /// A discription to a kind of register.
+    /// </summary>
+    /// <param name="capacity">A number between 1 and 65536 inclusive.</param>
+    /// <param name="writeable">True if this PointSource can be wrote.</param>
+    public PointSource(int capacity, bool? writeable = null)
+    {
+        ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(capacity, ushort.MinValue, nameof(capacity));
+        ArgumentOutOfRangeException.ThrowIfGreaterThan(capacity, ushort.MaxValue + 1, nameof(capacity));
+
+        _capacity = capacity;
+        _points = new TPoint[_capacity];
+        _writeable = writeable ?? false;
+    }
+
+    public TPoint[] ReadPoints(ushort startAddress, ushort numberOfPoints)
+    {
+        if (numberOfPoints == 0) return [];
+
+        int start = startAddress;
+        int count = numberOfPoints;
+
+        ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(start, _capacity, nameof(start));
+        ArgumentOutOfRangeException.ThrowIfGreaterThan(start + count, _capacity, $"{start}+{count}");
+
+        lock (_lock)
+        {
+            TPoint[] snapshot = new TPoint[count];
+            Array.Copy(_points, start, snapshot, 0, count);
+            return snapshot;
+        }
+    }
+
+    public void WritePoints(ushort startAddress, TPoint[] points)
+    {
+        if (!_writeable || points.Length == 0) return;
+
+        int start = startAddress;
+        int count = points.Length;
+
+        ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(start, _capacity, nameof(start));
+        ArgumentOutOfRangeException.ThrowIfGreaterThan(start + count, _capacity, $"{start}+{count}");
+
+        lock (_lock)
+        {
+            Array.Copy(points, 0, _points, start, count);
+        }
+    }
+}
+

+ 31 - 0
CommunicationProtocols/ModbusSimulationProtocol/Data/SlaveDataStore.cs

@@ -0,0 +1,31 @@
+using NModbus;
+
+namespace ModbusSimulationProtocol.Data;
+
+public class SlaveDataStore : ISlaveDataStore
+{
+    private readonly IPointSource<bool> _coilInputs;
+    private readonly IPointSource<bool> _coilDiscretes;
+    private readonly IPointSource<ushort> _holdingRegisters;
+    private readonly IPointSource<ushort> _inputRegisters;
+
+    public SlaveDataStore(
+        int coilCount,
+        int discreteInputCount,
+        int holdingRegisterCount,
+        int inputRegisterCount)
+    {
+        _coilInputs = new PointSource<bool>(coilCount, writeable: true);
+        _coilDiscretes = new PointSource<bool>(discreteInputCount, writeable: false);
+        _holdingRegisters = new PointSource<ushort>(holdingRegisterCount, writeable: true);
+        _inputRegisters = new PointSource<ushort>(inputRegisterCount, writeable: false);
+    }
+
+    public IPointSource<bool> CoilInputs => _coilInputs;
+
+    public IPointSource<bool> CoilDiscretes => _coilDiscretes;
+
+    public IPointSource<ushort> HoldingRegisters => _holdingRegisters;
+
+    public IPointSource<ushort> InputRegisters => _inputRegisters;
+}

+ 10 - 0
CommunicationProtocols/ModbusSimulationProtocol/Interface/IModbusSlaveService.cs

@@ -0,0 +1,10 @@
+namespace ModbusSimulationProtocol.Interface;
+
+public interface IModbusSlaveService
+{
+    void Initialize(string ip, ushort port);
+    void ActivateNetwork();
+    void AddSlave(byte slaveId, int coilCount, int discreteInputCount, int holdingRegisterCount, int inputRegisterCount);
+    void RemoveSlave(byte slaveId);
+    int GetConnectedMasterCount();
+}

+ 12 - 0
CommunicationProtocols/ModbusSimulationProtocol/ModbusSimulationProtocol.csproj

@@ -0,0 +1,12 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net8.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="NModbus" />
+  </ItemGroup>
+</Project>

+ 158 - 0
CommunicationProtocols/ModbusSimulationProtocol/Services/ModbusSlaveService.cs

@@ -0,0 +1,158 @@
+using ModbusSimulationProtocol.Data;
+using ModbusSimulationProtocol.Interface;
+using NModbus;
+using System.Net;
+using System.Net.Sockets;
+
+namespace ModbusSimulationProtocol.Services;
+
+public class ModbusSlaveService : IModbusSlaveService, IDisposable
+{
+    private readonly IModbusLogger? _logger;
+
+    private readonly ModbusFactory _factory;
+    private IModbusTcpSlaveNetwork? _network = null;
+
+    private bool disposedValue;
+
+    public ModbusSlaveService(IModbusLogger? logger)
+    {
+        _logger = logger;
+
+        _factory = new ModbusFactory(logger: _logger);
+    }
+
+    public void Initialize(string ip, ushort port)
+    {
+        ArgumentNullException.ThrowIfNullOrWhiteSpace(ip);
+        if (_network is not null)
+        {
+            WriteLog(LoggingLevel.Information, "The slave network has been created.");
+            return;
+        }
+
+        try
+        {
+            TcpListener listener = new TcpListener(IPAddress.Parse(ip), (int)port);
+            _network = _factory.CreateSlaveNetwork(listener);
+        }
+        catch (Exception ex)
+        {
+            WriteLog(LoggingLevel.Error, ex.ToString());
+        }
+    }
+
+    public void ActivateNetwork()
+    {
+        if (_network is null)
+        {
+            WriteLog(LoggingLevel.Warning, "Performs initializing before activating network.");
+            return;
+        }
+
+        Task.Run(async () =>
+        {
+            try
+            {
+                WriteLog(LoggingLevel.Information, $"Starts async listening.");
+                await _network.ListenAsync();
+            }
+            catch (Exception ex)
+            {
+                WriteLog(LoggingLevel.Error, ex.ToString());
+            }
+        });
+    }
+
+    public void AddSlave(byte slaveId,
+                         int coilCount, int discreteInputCount, int holdingRegisterCount, int inputRegisterCount)
+    {
+        if (_network is null)
+        {
+            WriteLog(LoggingLevel.Warning, "Performs initializing before adding any slave.");
+            return;
+        }
+
+        try
+        {
+            var slave = _factory.CreateSlave(slaveId, new SlaveDataStore(coilCount, discreteInputCount, holdingRegisterCount, inputRegisterCount));
+            _network.AddSlave(slave);
+        }
+        catch (Exception ex)
+        {
+            WriteLog(LoggingLevel.Error, ex.ToString());
+        }
+    }
+
+    public void RemoveSlave(byte slaveId)
+    {
+        if (_network is null)
+        {
+            WriteLog(LoggingLevel.Warning, "The slave network do not exist even.");
+            return;
+        }
+
+        if (_network.GetSlave(slaveId) is null)
+        {
+            WriteLog(LoggingLevel.Warning, "The specified slave do not exist. Failed to remove.");
+            return;
+        }
+
+        try
+        {
+            _network.RemoveSlave(slaveId);
+            WriteLog(LoggingLevel.Information, $"The slave {slaveId} has been removed.");
+        }
+        catch (Exception ex)
+        {
+            WriteLog(LoggingLevel.Error, ex.ToString());
+        }
+    }
+
+    public int GetConnectedMasterCount()
+    {
+        if (_network is null)
+        {
+            WriteLog(LoggingLevel.Warning, "The slave network do not exist even.");
+            return 0;
+        }
+
+        return _network.Masters.Count;
+    }
+    private void WriteLog(LoggingLevel loggingLevel, string log)
+    {
+        _logger?.Log(loggingLevel, $"[{nameof(ModbusSlaveService)}]:{log}");
+    }
+
+    protected virtual void Dispose(bool disposing)
+    {
+        if (!disposedValue)
+        {
+            if (disposing)
+            {
+                // TODO: dispose managed state (managed objects)
+                _network?.Dispose();
+                _network = null;
+            }
+
+            // TODO: free unmanaged resources (unmanaged objects) and override finalizer
+            // TODO: set large fields to null
+            disposedValue = true;
+        }
+    }
+
+    // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
+    // ~ModbusSlaveService()
+    // {
+    //     // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+    //     Dispose(disposing: false);
+    // }
+
+    public void Dispose()
+    {
+        // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+        Dispose(disposing: true);
+        GC.SuppressFinalize(this);
+    }
+}
+

+ 7 - 0
Minics.sln

@@ -95,6 +95,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PLCIOPointTool", "Tools\PLC
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModbusSlaveSim", "Tools\ModbusSlaveSim\ModbusSlaveSim.csproj", "{722495E5-E221-47F9-9045-7FC9E08948BC}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModbusSimulationProtocol", "CommunicationProtocols\ModbusSimulationProtocol\ModbusSimulationProtocol.csproj", "{2B31769C-22A4-41CF-97B0-16A18488E81F}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -241,6 +243,10 @@ Global
 		{722495E5-E221-47F9-9045-7FC9E08948BC}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{722495E5-E221-47F9-9045-7FC9E08948BC}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{722495E5-E221-47F9-9045-7FC9E08948BC}.Release|Any CPU.Build.0 = Release|Any CPU
+		{2B31769C-22A4-41CF-97B0-16A18488E81F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{2B31769C-22A4-41CF-97B0-16A18488E81F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{2B31769C-22A4-41CF-97B0-16A18488E81F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{2B31769C-22A4-41CF-97B0-16A18488E81F}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -277,6 +283,7 @@ Global
 		{D784466C-1F32-4621-1B3D-D1AC0CC08709} = {165A518A-753D-4062-A100-9367AF27F900}
 		{9C3E268E-4268-479E-9508-EFD76639B899} = {2783D72A-0926-427D-B70D-5F3F5FA757FC}
 		{722495E5-E221-47F9-9045-7FC9E08948BC} = {2783D72A-0926-427D-B70D-5F3F5FA757FC}
+		{2B31769C-22A4-41CF-97B0-16A18488E81F} = {165A518A-753D-4062-A100-9367AF27F900}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {2E8CB4A6-1AFA-4CB2-BA42-1BAFC71AD68B}

+ 0 - 1
Tools/ModbusSlaveSim/ModbusSlaveSim.csproj

@@ -14,6 +14,5 @@
   <ItemGroup>
     <PackageReference Include="Prism.DryIoc" />
     <PackageReference Include="CommunityToolkit.Mvvm" />
-    <PackageReference Include="NModbus" />
   </ItemGroup>
 </Project>