using System; using System.Collections.Generic; using System.Threading; namespace AMWD.Protocols.Modbus.Common.Models { /// /// Represents a Modbus device used in a Modbus server implementation. /// /// /// Initializes a new instance of the class. /// /// The ID. public class ModbusDevice(byte id) : IDisposable { private readonly ReaderWriterLockSlim _rwLockCoils = new(); private readonly ReaderWriterLockSlim _rwLockDiscreteInputs = new(); private readonly ReaderWriterLockSlim _rwLockHoldingRegisters = new(); private readonly ReaderWriterLockSlim _rwLockInputRegisters = new(); private readonly HashSet _coils = []; private readonly HashSet _discreteInputs = []; private readonly Dictionary _holdingRegisters = []; private readonly Dictionary _inputRegisters = []; private bool _isDisposed; /// /// Gets the ID of the . /// public byte Id { get; } = id; /// /// Releases the unmanaged resources used by the /// and optionally also discards the managed resources. /// public void Dispose() { if (_isDisposed) return; _isDisposed = true; _rwLockCoils.Dispose(); _rwLockDiscreteInputs.Dispose(); _rwLockHoldingRegisters.Dispose(); _rwLockInputRegisters.Dispose(); _coils.Clear(); _discreteInputs.Clear(); _holdingRegisters.Clear(); _inputRegisters.Clear(); } /// /// Gets a from the . /// /// The address of the . public Coil GetCoil(ushort address) { Assertions(); using (_rwLockCoils.GetReadLock()) { return new Coil { Address = address, HighByte = (byte)(_coils.Contains(address) ? 0xFF : 0x00) }; } } /// /// Sets a to the . /// /// The to set. public void SetCoil(Coil coil) { Assertions(); using (_rwLockCoils.GetWriteLock()) { if (coil.Value) _coils.Add(coil.Address); else _coils.Remove(coil.Address); } } /// /// Gets a from the . /// /// The address of the . public DiscreteInput GetDiscreteInput(ushort address) { Assertions(); using (_rwLockDiscreteInputs.GetReadLock()) { return new DiscreteInput { Address = address, HighByte = (byte)(_discreteInputs.Contains(address) ? 0xFF : 0x00) }; } } /// /// Sets a to the . /// /// The to set. public void SetDiscreteInput(DiscreteInput input) { using (_rwLockDiscreteInputs.GetWriteLock()) { if (input.Value) _discreteInputs.Add(input.Address); else _discreteInputs.Remove(input.Address); } } /// /// Gets a from the . /// /// The address of the . public HoldingRegister GetHoldingRegister(ushort address) { Assertions(); using (_rwLockHoldingRegisters.GetReadLock()) { if (!_holdingRegisters.TryGetValue(address, out ushort value)) value = 0x0000; byte[] blob = BitConverter.GetBytes(value); blob.SwapBigEndian(); return new HoldingRegister { Address = address, HighByte = blob[0], LowByte = blob[1] }; } } /// /// Sets a to the . /// /// The to set. public void SetHoldingRegister(HoldingRegister register) { Assertions(); using (_rwLockHoldingRegisters.GetWriteLock()) { if (register.Value == 0) { _holdingRegisters.Remove(register.Address); return; } byte[] blob = [register.HighByte, register.LowByte]; blob.SwapBigEndian(); _holdingRegisters[register.Address] = BitConverter.ToUInt16(blob, 0); } } /// /// Gets a from the . /// /// The address of the . public InputRegister GetInputRegister(ushort address) { Assertions(); using (_rwLockInputRegisters.GetReadLock()) { if (!_inputRegisters.TryGetValue(address, out ushort value)) value = 0x0000; byte[] blob = BitConverter.GetBytes(value); blob.SwapBigEndian(); return new InputRegister { Address = address, HighByte = blob[0], LowByte = blob[1] }; } } /// /// Sets a to the . /// /// The to set. public void SetInputRegister(InputRegister register) { Assertions(); using (_rwLockInputRegisters.GetWriteLock()) { if (register.Value == 0) { _inputRegisters.Remove(register.Address); return; } byte[] blob = [register.HighByte, register.LowByte]; blob.SwapBigEndian(); _inputRegisters[register.Address] = BitConverter.ToUInt16(blob, 0); } } private void Assertions() { #if NET8_0_OR_GREATER ObjectDisposedException.ThrowIf(_isDisposed, this); #else if (_isDisposed) throw new ObjectDisposedException(GetType().FullName); #endif } } }