using MagFlux6200_metrology_reg_list; using XYLEM.Device; using XYLEM.Communication.ValueEncoders; using System; using System.Collections.Generic; using System.IO.Ports; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Numerics; using System.Xml.Linq; using static System.Net.Mime.MediaTypeNames; using XYLEM.Base; using System.Text.RegularExpressions; using System.Data; using System.Security.Policy; using System.Threading; namespace XYLEM.Communication { /// /// Modbus read and write return data type /// /// public class ModbusDataResult where T : IComparable, IFormattable, IComparable, IEquatable/*, IConvertible is not compatible with BigInteger*/ { /// /// Has the task succeded /// internal bool IsOkay; /// /// Data readed from or written to device /// internal T Data; /// /// What device value info for Data /// internal Device_value Info; public ModbusDataResult(bool IsOkay, T DataRW, Device_value Info) { this.IsOkay = IsOkay; this.Data = DataRW; this.Info = Info; } } public class ModbusDataRequest { /// /// Has the task succeded /// internal bool IsOkay; /// /// Data for return read in from device /// internal object Data; /// /// What device value info for Data /// internal Device_value Info; public ModbusDataRequest(bool IsOkay, object returnReadIn, Device_value Info) { this.IsOkay = IsOkay; this.Data = returnReadIn; this.Info = Info; } } public class ModbusCom { const int ch_lng_dv = -15; const int ch_lng_Version = -3; const int ch_lng_MD_adr = -7; const int dig_value = 10; /// /// Min delay before a DUT message is expected faild // Is 1s for testing out that default modbus settings com don't fail /// private readonly int _MaxWaitForComRX_ms = 1000; /// /// Message ended detection delay /// Min delay before bytes count received and haven't changed result in modbus message ended /// The 50ms is found to be needed when using DUT RS485 and modbus messages is transparent to Metrology /// private readonly int _DelayBetweenRxByteCheck_ms = 50; SerialPort _serial; int _retry; public int MsgErrorCount { get; private set; } public int MsgCount { get; private set; } public void ResetMsgCounters() { MsgCount = MsgErrorCount = 0; } public ModbusCom(string serial_port, int baudRate, Parity parity, int retry = 3) { ResetMsgCounters(); this._serial = new SerialPort(); _serial.PortName = serial_port; _serial.BaudRate = baudRate; _serial.Parity= parity; _serial.DtrEnable = _serial.RtsEnable = true; // Need to be on for Application USB Serial com to detect a connection //_serial.Handshake = Handshake.RequestToSendXOnXOff; _serial.ReadTimeout = 100; if (!_serial.IsOpen) { _serial.Open(); } _retry=retry; Console.WriteLine($"Serial connection {_serial.PortName}, {_serial.BaudRate}, {_serial.Parity}, retry = {_retry}"); } /// /// Is communication port connected /// /// public bool IsConnected() { return _serial.IsOpen; } /// /// Generic value Read of a modbus value ex. "var ret = await Read(_serial, _device_id, req, new Single());" /// /// UInt16, Single ... /// device ID to use for read /// device value to read /// a new value of the type to read, to use for init and return /// as ModbusDataResult where Item1 is true if okay and Item2 is the value read public ModbusDataResult Read(int device_id, Device_value dv, T retValue) where T : IComparable, IFormattable, IComparable, IEquatable/*, IConvertible is not compatible with BigInteger*/ { ModbusDataResult ret = null; try { byte[] answer = Read_bytes(device_id, dv.MD_adr, dv.Datatype.Length()); retValue = (T)DeviceValueEncoder.Decode(answer, dv.Datatype); ret = new ModbusDataResult(true, retValue, dv); // Okay } catch (Exception ex) { var message = $"Read error! from ID={device_id} name={dv.Name} adr={dv.MD_adr} with {dv.Datatype.Length()}\n\r" + ex.Message + "\n\r"; Console.WriteLine(message); AppConst.Logger.AddFuncLog(true, this.ToString(), "Read()", message); ret = new ModbusDataResult(false, retValue, dv); // Error } return ret; } /// /// Read multiple value optimized in one or more telegrams from device /// /// Device ID to use for read /// A list of values to read /// List with read okay or failed values public List Read(int device_id, List dvList) { List ret = null; try { // ----------- Generate blocks to read ----------- dvList.Sort((dvA, dvB) => { return dvA.MD_adr.CompareTo(dvB.MD_adr); }); List> blocks = new List>(); foreach (var dv in dvList) { // if first item, just add it if ((blocks.Count == 0) || (blocks.Last().Count == 0)) { if (blocks.Count == 0) { blocks.Add(new List()); } blocks.Last().Add(new ModbusDataRequest(false, null, dv)); } else { var dvLast = blocks.Last().Last().Info; // Only add it if a new value not already to be read if (dvLast.Name != dv.Name) { var nextAdr = dvLast.MD_adr + (dvLast.Datatype.Length()/2); // If not next to read in current block if (dv.MD_adr != nextAdr) { // then add it to the next read block blocks.Add(new List()); } blocks.Last().Add(new ModbusDataRequest(false, null, dv)); } } } // ----------- Read blocks ----------- foreach (var dv_group in blocks) { try { var dvi_first = dv_group.First().Info; var dvi_last = dv_group.Last().Info; var startAdr = dvi_first.MD_adr; var LenBytes = (dvi_last.MD_adr+(dvi_last.Datatype.Length()/2)-startAdr)*2; if ((LenBytes < 0) || (LenBytes > 250)) { throw new Exception($"Block length limit Error is {LenBytes}"); } byte[] answer = Read_bytes(device_id, startAdr, LenBytes); if (answer == null) { throw new Exception($"Read block error! from ID={device_id} from {dvi_first} to {dvi_last}"); } foreach (var dv_data in dv_group) { var dv = dv_data.Info; var start_idx = (dv.MD_adr-startAdr)*2; var value_bytes = answer.Copy(start_idx, dv.Datatype.Length()); var value = DeviceValueEncoder.Decode(value_bytes, dv.Datatype); if (ret == null) { ret = new List(); } ret.Add(new ModbusDataRequest(true, value, dv)); // Okay } } //catch (Exception ex) catch { // Retry read it as single values foreach (var dv_data in dv_group) { var dv = dv_data.Info; byte[] answer = Read_bytes(device_id, dv.MD_adr, dv.Datatype.Length()); var value = DeviceValueEncoder.Decode(answer, dv.Datatype); if (ret == null) { ret = new List(); } ret.Add(new ModbusDataRequest(true, value, dv)); // Okay } } } return ret; } catch (Exception ex) { if ((dvList != null) && (dvList.Count() > 0)) { foreach (var dv in dvList) { var message = $"Read error! from ID={device_id} name={dv.Name} adr={dv.MD_adr}\n\r" + ex.Message + "\n\r"; Console.WriteLine(message); AppConst.Logger.AddFuncLog(true, this.ToString(), "Read()", message); } } else { var message = $"Read error! from ID={device_id}\n\r" + ex.Message + "\n\r"; Console.WriteLine(message); AppConst.Logger.AddFuncLog(true, this.ToString(), "Read()", message); } ret = null; // Error } return ret; } /// /// Generic value Write of a modbus value ex. "await Write(_serial, _device_id, req, value)" /// /// UInt16, Single .. /// device ID to use for write /// device value to write /// the value to write /// public bool Write(int device_id, Device_value dv, T value) where T : IComparable, IFormattable, IConvertible, IComparable, IEquatable { bool isOkay = true; try { byte[] tx_data = DeviceValueEncoder.Encode(value, dv.Datatype); if (!Write_bytes(device_id, dv.MD_adr, tx_data)) { var message = $"Write failed={dv,ch_lng_dv}: Reg_Ver={dv.Version,ch_lng_Version} Adr={dv.MD_adr,-7} value={value,dig_value}"; Console.WriteLine(message); AppConst.Logger.AddFuncLog(true, this.ToString(), "Write()", message); isOkay = false; } } catch (Exception ex) { var message = $"Write error! to ID={device_id} name={dv.Name} adr={dv.MD_adr} with {dv.Datatype.Length()}\n\r" + ex.Message + "\n\r"; Console.WriteLine(message); AppConst.Logger.AddFuncLog(true, this.ToString(), "Write()", message); isOkay = false; } return isOkay; } /// /// Write multiple value optimized in one or more telegrams to device /// /// device ID to use for write /// a list of values to write /// A list of values written okay or failed to device public List Write(int device_id, List reqList) { List ret = null; try { // ----------- Generate blocks to write ----------- reqList.Sort((dvA, dvB) => { return dvA.Info.MD_adr.CompareTo(dvB.Info.MD_adr); }); List> blocks = new List>(); foreach (var req in reqList) { // if first item, just add it if ((blocks.Count == 0) || (blocks.Last().Count == 0)) { if (blocks.Count == 0) { blocks.Add(new List()); } blocks.Last().Add(req); } else { var req_Last = blocks.Last().Last(); // Only add it if a new value not already written if (req_Last.Info.Name != req.Info.Name) { var nextAdr = req_Last.Info.MD_adr + (req_Last.Info.Datatype.Length()/2); // If not next to read in current block if (req.Info.MD_adr != nextAdr) { // then add it to the next read block blocks.Add(new List()); } blocks.Last().Add(req); } else { throw new Exception($"Only a single Write object to {req_Last.Info.Name} is allowed!"); } } } // ----------- Write blocks ----------- foreach (var req_group in blocks) { try { var dvi_first = req_group.First().Info; var dvi_last = req_group.Last().Info; var startAdr = dvi_first.MD_adr; var LenBytes = (dvi_last.MD_adr+(dvi_last.Datatype.Length()/2)-startAdr)*2; if ((LenBytes < 0) || (LenBytes > 250)) { throw new Exception($"Block length limit Error is {LenBytes}"); } var tx_group_data = new List(); foreach (var wr in req_group) { byte[] tx_data = DeviceValueEncoder.Encode(wr.Data, wr.Info.Datatype); if (tx_data == null) { throw new Exception($"Fail getting write byte data for {wr.Info.Name}"); } tx_group_data.AddRange(tx_data); } if (LenBytes != tx_group_data.Count) { throw new Exception($"Group write from {dvi_first.Name} to {dvi_last.Name} expected={LenBytes} bytes, encoded was={tx_group_data.Count} bytes"); } var isOkay = Write_bytes(device_id, startAdr, tx_group_data.ToArray()); if (!isOkay) { throw new Exception($"Write block error! from ID={device_id} from {dvi_first} to {dvi_last}"); } ret = new List(); foreach (var req in req_group) { req.IsOkay = true; ret.Add(req); // add request that succeed } } //catch (Exception ex) //For debugging catch { ret = new List(); // Retry write as single values foreach (var req in req_group) { var dv = req.Info; byte[] tx_data = DeviceValueEncoder.Encode(req.Data, req.Info.Datatype); req.IsOkay = Write_bytes(device_id, dv.MD_adr, tx_data); ret.Add(req); // } } } return ret; } catch (Exception ex) { if ((reqList != null) && (reqList.Count() > 0)) { foreach (var dv in reqList) { var message = $"Write error! from ID={device_id} name={dv.Info.Name} adr={dv.Info.MD_adr}\n\r" + ex.Message + "\n\r"; Console.WriteLine(message); AppConst.Logger.AddFuncLog(true, this.ToString(), "Write()", message); } } else { var message = $"Write error! from ID={device_id}\n\r" + ex.Message + "\n\r"; Console.WriteLine(message); AppConst.Logger.AddFuncLog(true, this.ToString(), "Write()", message); } ret = null; // Error } return ret; } /// /// A general modbus read function for just a bunch of bytes /// /// device ID to use for modbus command /// Start address to data accessed /// Number of bytes to request /// byte data read from device and is null if none / error public byte[] Read_bytes(int device_id, int md_adr, int num_of_bytes) { var is_write = false; try { // Read address 0 device var p_transate = new ProtocolTranslateModbusRTUClass(); byte[] tx_data = p_transate.CreateTxData(device_id, (int)Func_RD.No3, md_adr, num_of_bytes, null, is_write); return modbus_com(md_adr, tx_data, is_write, p_transate); } catch (Exception ex) { var message = $"!error No data received from ID={device_id} adr={md_adr} with {num_of_bytes} to {(is_write ? "Write" : "Read")}\n\r" + ex.Message + "\n\r"; Console.WriteLine(message); AppConst.Logger.AddFuncLog(true, this.ToString(), "Read_bytes()", message); return null; } } /// /// A general modbus write function for just a bunch of bytes /// /// device ID to use for modbus command /// Start address to data accessed /// Byte data to Write /// Is true on write is okay public bool Write_bytes(int device_id, int md_adr, byte[] write_data) { var is_write = true; try { // Read address 0 device var p_transate = new ProtocolTranslateModbusRTUClass(); byte[] tx_data = p_transate.CreateTxData(device_id, (int)Func_WR.No16, md_adr, write_data.Length, write_data, is_write); var result = modbus_com(md_adr, tx_data, is_write, p_transate); if (result != null) { return true; // Okay } } catch (Exception ex) { var message = $"!error writting data to ID={device_id} adr={md_adr} to {(is_write ? "Write" : "Read")}\n\r" + ex.Message + "\n\r"; Console.WriteLine(message); AppConst.Logger.AddFuncLog(true, this.ToString(), "Write_bytes()", message); } return false; // Error } /// /// A general modbus read / write function for just a bunch of bytes /// /// Start address to data accessed /// a complet modbus telegram byte data to transmit /// is the modbus telegram a write or read type to use for eval received result /// Modbus protocol translater for eval received result /// will return null on error or received message on okay public byte[] modbus_com(int md_adr, byte[] tx_data, bool is_write, ProtocolTranslateModbusRTUClass p_transate) { lock (_serial) { try { byte[] answer = null; var retry = 0; do { MsgCount++; if (!_serial.IsOpen) { _serial.Open(); } _serial.DiscardInBuffer(); _serial.Write(tx_data, 0, tx_data.Length); int received = 0; int max_count = _MaxWaitForComRX_ms/_DelayBetweenRxByteCheck_ms; do { received = _serial.BytesToRead; Thread.Sleep(_DelayBetweenRxByteCheck_ms); } while (((received == 0) || (received != _serial.BytesToRead)) && (--max_count>0)); if (_serial.BytesToRead <= 0) { MsgErrorCount++; } } while ((_serial.BytesToRead <= 0) && (++retry < _retry)); if (_serial.BytesToRead <= 0) { throw new Exception("!error No data received from device"); } #if (DEBUG) // TODO just testing if (retry > 1) { Console.WriteLine($"Warning modbus com {retry-1} retry needed for ID={md_adr} to {(is_write ? "Write" : "Read")}"); } #endif //#if (DEBUG) var bytesToRead = _serial.BytesToRead; var rx_data = new byte[bytesToRead]; var rx_data_len = _serial.Read(rx_data, 0, bytesToRead); bool isRxRW = p_transate.IsDataRW(rx_data, true); // Check data func is Read or write if (isRxRW) { // Read if (!is_write) { if (p_transate.IsRead(rx_data, true)) { answer = p_transate.GetReadData(rx_data, true); //buf.CallBackOK(bDataTemp, txData, CallBackOK_Single); } else { throw new Exception("Data Rx Wrong"); } } else // Write { bool isWrite = p_transate.IsWrite(rx_data, true); int adr = p_transate.GetRxAdr(rx_data, true); if (isWrite && (!p_transate.IsAdrPossible(rx_data, true) ? true : (adr == md_adr))) { //buf.CallBackOK(null, txData, CallBackOK_Single); return rx_data; // Is okay return received message } else { throw new Exception("Data Rx Wrong"); } } } else // Exception received { // Normal if (!p_transate.IsRxWaitForCommand(rx_data, true)) { int exception = p_transate.GetRxAcknowledgeCode(rx_data, true); //buf.CallBackError(exception, txData, ""); // If read .. try again after wait } else { if (!is_write) { //buf.Add(txData); // if Write.. then all must be okay and put event on delay } else { //buf.CallBackOK(null, txData, CallBackOK_Single); } if (p_transate.IsRxWaitForFinishMax60S(rx_data, true)) { Thread.Sleep((int)(60 * 1000)); } else if (p_transate.IsRxWaitForFinishMax5Min(rx_data, true)) { Thread.Sleep((int)(5 * 60 * 1000)); } else if (p_transate.IsRxWaitForFinishMax5S(rx_data, true)) { Thread.Sleep((int)(5 * 1000)); } else { int waitTime = 5 * 1000; // default Thread.Sleep(waitTime); // Wait for reset } } } return answer; } catch (Exception ex) { var message = "Error: \n\r" + ex.Message + "\n\r"; Console.WriteLine(message); AppConst.Logger.AddFuncLog(true, this.ToString(), "modbus_com()", message); return null; } finally { _serial.Close(); } } } } }