using GeneralData; using HardwareData; using Mini8Communicator; using Mini8CommunicatorData; using ModBusTcp; using ProtocalGeneral; using System.Collections.Concurrent; using Universal; namespace Mini8CommunicatorModbus; public class Mini8Communcator_Modbus_FluentModbus : IMini8Communicator, ITcpConnectNority { //Mini8 can not handle commands faster than this interval frequency (~200ms) private int _interval = 200; //Modbus Max request length is 125 private ushort _groupeCount = 125; private ushort _totalCount = 500; //The first Address of Mini8 communication is 15500 private ushort _startIndex = 15500; //A mini8 Data Area for Limits private ushort _limitCount = 64; private ushort _limitStartIndex = 5193; private int _port = -1; private string _ip = string.Empty; private IDictionary? _addresses; private readonly ConcurrentDictionary _outputCache = []; private Timer? _updateDataTimer; private IMini8Provider? _provider; private DataSpilterByte? _spilter; private Modbus_Tcp? _modbus; bool IMini8Communicator.Initialize(IMini8Provider provider, object mini8Config, object address) { if (provider is null || this._modbus is not null) return false; if (mini8Config is not Mini8Address mini8) return false; if (address is not IDictionary addresses) return false; if (string.IsNullOrEmpty(mini8.Address) || !mini8.Port.InRange(0, 65535)) return false; this._interval = mini8.Interval; this._groupeCount = mini8.GroupeCount; this._totalCount = mini8.TotalCount; this._startIndex = mini8.StartIndex; this._limitCount = mini8.LimitCount; this._limitStartIndex = mini8.LimitStartIndex; this._ip = mini8.Address; this._port = mini8.Port; this._addresses = addresses; this._provider = provider; return true; } bool IMini8Communicator.Open() { if (this._modbus is not null) return false; if (this._provider is null) return false; if (this._addresses is null) return false; if (string.IsNullOrEmpty(this._ip) || !this._port.InRange(0, 65535)) return false; Modbus_Tcp modbus = new(); modbus.Initialize($"Mini8Communicator_Modbus {this._ip} : {this._port}", this); if (!modbus.Open(this._ip, (ushort)this._port)) return false; this._modbus = modbus; this._spilter = new DataSpilterByte(_startIndex); this._addresses.Foreach(t => _outputCache[t.Value.ChannelIndex] = new()); GetMini8Output(false); return true; } bool IMini8Communicator.GetRealtimeDataFromDevice(out Dictionary? data) { data = null; if (Checker.IsNull(_provider, _modbus, _addresses, _spilter, _outputCache)) return false; lock (_outputCache) data = _outputCache.ToDictionary(); return true; } bool IMini8Communicator.GetRealtimeLimit(out Dictionary? data) { data = null; if (Checker.IsNull(_provider, _modbus, _addresses)) return false; data = []; foreach (ChannelAddress item in _addresses!.Values) { data[item.ChannelIndex] = new(); byte[]? caps = this._modbus!.GetBuffer(item.Caps, 1); byte[]? floor = this._modbus.GetBuffer(item.Floor, 1); byte[]? capsWarning = this._modbus.GetBuffer(item.CapsWarning, 1); byte[]? floorWarning = this._modbus.GetBuffer(item.FloorWarning, 1); data[item.ChannelIndex].Caps = BufferToUshort(caps); data[item.ChannelIndex].Floor = BufferToUshort(floor); data[item.ChannelIndex].CapsWarning = BufferToUshort(capsWarning); data[item.ChannelIndex].FloorWarning = BufferToUshort(floorWarning); } return true; } bool IMini8Communicator.SendMini8Data(byte channelIndex, Mini8Input output) { if (Checker.IsNull(_provider, _modbus, _addresses)) return false; if (!this._addresses!.TryGetValueNotNull(channelIndex, out ChannelAddress value)) return false; bool b = true; b &= this._modbus!.SetFloat(value.SetPoint, output.SetPoint); b &= this._modbus.SetFloat(value.Running_P, output.Running_P); b &= this._modbus.SetFloat(value.Running_I, output.Running_I); b &= this._modbus.SetFloat(value.Running_D, output.Running_D); b &= this._modbus.SetFloat(value.SetpointUpRate, output.SetpointUpRate); b &= this._modbus.SetFloat(value.SetpointDownRate, output.SetpointDownRate); try { b &= this._modbus.SetUshort(value.Caps, Convert.ToUInt16(output.Caps)); b &= this._modbus.SetUshort(value.Floor, Convert.ToUInt16(output.Floor)); b &= this._modbus.SetUshort(value.CapsWarning, Convert.ToUInt16(output.CapsWarning)); b &= this._modbus.SetUshort(value.FloorWarning, Convert.ToUInt16(output.FloorWarning)); } catch { } return b; } bool IMini8Communicator.EnableChannel(byte channelIndex, Inhibit inhibit) { if (Checker.IsNull(_provider, _modbus, _addresses, _spilter)) return false; if (!this._addresses!.TryGetValueNotNull(channelIndex, out ChannelAddress value)) return false; return this._modbus!.SetUshort(value.Inhibit, (ushort)inhibit); } bool IMini8Communicator.EnableChannelAutoTune(byte channelIndex, bool isEnable) { if (Checker.IsNull(_provider, _modbus, _addresses, _spilter)) return false; if (!this._addresses!.TryGetValueNotNull(channelIndex, out ChannelAddress value)) return false; (ActiveTuneSet set, AutotuneActive active) = isEnable switch { true => (ActiveTuneSet.AutoTune, AutotuneActive.Active), false => (ActiveTuneSet.Running, AutotuneActive.OFF), }; if (!this._modbus!.SetUshort(value.ActiveTuneSet, (ushort)set)) return false; Thread.Sleep(_interval); if (!this._modbus!.SetUshort(value.ActiveAutoTune, (ushort)active)) return false; return true; } bool IMini8Communicator.SelectPID(byte channelIndex, ActiveTuneSet activeTuneSet) { if (Checker.IsNull(_provider, _modbus, _addresses)) return false; if (!this._addresses!.TryGetValueNotNull(channelIndex, out ChannelAddress value)) return false; return this._modbus!.SetUshort(value.ActiveTuneSet, (ushort)activeTuneSet); } bool IMini8Communicator.Pause() { this._updateDataTimer?.Dispose(); this._provider?.OnDisconnected(this._ip, this._port); return true; } bool IMini8Communicator.StartDataCollector() { StartDataCollection(); this._provider?.OnConnected(this._ip, this._port); return true; } bool IMini8Communicator.Close() { this._updateDataTimer?.Dispose(); this._modbus?.Close(); this._provider = null; this._modbus = null; return true; } private bool StartDataCollection() { if (Checker.IsNull(_provider, _modbus, _addresses, _spilter)) return false; _updateDataTimer?.Dispose(); _updateDataTimer = new(GetMini8Output, true, 1000, 1000); return true; } private void GetMini8Output(object? requireNotify) { if (requireNotify is not bool notify) return; if (this._spilter is null || this._addresses is null) return; if (!this.TryGetMini8Data(out byte[]? data) || data is null || data.Length < _totalCount) return; this._spilter.Source = data; lock (_outputCache) foreach (ChannelAddress item in _addresses.Values) { Mini8Output output = _outputCache[item.ChannelIndex]; output.CollectTime = DateTime.Now; GetMini8Input(output, item, this._spilter); if (!notify) continue; output.ChannelIndex = item.ChannelIndex; this._provider?.ChannelInfoNotify(item.ChannelIndex, output); } } private readonly byte[] _cache = new byte[1000]; private bool TryGetMini8Data(out byte[]? data) { data = null; if (this._modbus is null) return false; for (int index = 0; index < _totalCount; index += _groupeCount) { byte[]? result = this._modbus?.GetBuffer((ushort)(_startIndex + index), _groupeCount); if (result is null) return false; Array.Copy(result, 0, _cache, index * 2, result.Length); Thread.Sleep(_interval); } data = this._cache; return true; } private unsafe ushort BufferToUshort(byte[]? bytes) { if (bytes is null) return ushort.MaxValue; Span sb = bytes.AsSpan(); sb.Reverse(); fixed (byte* pByte = sb) { ushort* pResult = (ushort*)pByte; return *pResult; } } private static void GetMini8Input(Mini8Output ref_source, ChannelAddress address, DataSpilterByte spilter) { ref_source.PV = spilter.GetFloat(address.PV); ref_source.WorkingOutput = spilter.GetFloat(address.WorkingOutput); ref_source.AutoTuneStatus = (AutoTuneStatus)spilter.GetUshort(address.AutoTuneStatus); ref_source.AutoTune_P = spilter.GetFloat(address.AutoTune_P); ref_source.AutoTune_I = spilter.GetFloat(address.AutoTune_I); ref_source.AutoTune_D = spilter.GetFloat(address.AutoTune_D); ref_source.SetPoint = spilter.GetFloat(address.SetPoint); ref_source.ActiveTuneSet = (ActiveTuneSet)spilter.GetUshort(address.ActiveTuneSet); ref_source.Running_P = spilter.GetFloat(address.Running_P); ref_source.Running_I = spilter.GetFloat(address.Running_I); ref_source.Running_D = spilter.GetFloat(address.Running_D); ref_source.SetpointUpRate = spilter.GetFloat(address.SetpointUpRate); ref_source.SetpointDownRate = spilter.GetFloat(address.SetpointDownRate); ref_source.Inhibit = (Inhibit)spilter.GetUshort(address.Inhibit); ref_source.AutotuneActive = (AutotuneActive)spilter.GetUshort(address.ActiveAutoTune); ref_source.SensorBreakAlarm = address.ChannelIndex switch { <= 8 => (TcBorken)spilter.GetBit(address.SensorBreakAlarm1, address.ChannelIndex), _ => (TcBorken)spilter.GetBit(address.SensorBreakAlarm2, address.ChannelIndex - 8), }; } void ITcpConnectNority.Connect(string ip, int port) { this._provider?.OnConnected(this._ip, this._port); StartDataCollection(); } void ITcpConnectNority.DisConnect(string ip, int port) { this._updateDataTimer?.Dispose(); this._provider?.OnDisconnected(this._ip, this._port); } } internal class DataSpilterByte(ushort startIndex) { public byte[]? Source { get; set; } public unsafe float GetFloat(ushort address) { address -= startIndex; if (Source is null || Source.Length < address * 2 + 4) return -1f; Span buffer = Source.AsSpan().Slice(address * 2, 4); buffer.Reverse(); fixed (byte* pByte = buffer) { float* pResult = (float*)pByte; return *pResult; } } public unsafe Half GetHalf(ushort address) { address -= startIndex; if (Source is null || Source.Length < address * 2 + 2) return Half.MaxValue; Span buffer = Source.AsSpan().Slice(address * 2, 2); buffer.Reverse(); fixed (byte* pByte = buffer) { Half* pResult = (Half*)pByte; return *pResult; } } public unsafe ushort GetUshort(ushort address) { address -= startIndex; if (Source is null || Source.Length < address * 2 + 2) return ushort.MaxValue; Span buffer = Source.AsSpan().Slice(address * 2, 2); buffer.Reverse(); fixed (byte* pByte = buffer) { ushort* pResult = (ushort*)pByte; return *pResult; } } public unsafe byte GetBit(ushort address, int index) { address -= startIndex; if (Source is null || Source.Length < address + 1) return byte.MaxValue; if (index > 8 || index < 0) return byte.MaxValue; return (byte)((Source[address * 2 + 1] >> (index - 1)) & 0b01); } }