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.SwapNetworkOrder();
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.SwapNetworkOrder();
_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.SwapNetworkOrder();
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.SwapNetworkOrder();
_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
}
}
}