using File_Watcher.Models; using System.Diagnostics; using System.Net.Sockets; namespace File_Watcher.Helpers; #pragma warning disable IDE0300 internal static partial class LabJackT7Helper { private static ModbusTransmissionControlProtocolClient? _ModbusTransmissionControlProtocolClient; private class ModbusTransmissionControlProtocolClient : IDisposable { private ushort _TransactionID; private NetworkStream? _NetworkStream; private TcpClient? _TransmissionControlProtocolClient; public ModbusTransmissionControlProtocolClient(string hostname, int port) => Connect(hostname, port); public void Connect(string hostname, int port) { if (_TransmissionControlProtocolClient is not null) Close(); _TransmissionControlProtocolClient = new TcpClient(hostname, port); _NetworkStream = _TransmissionControlProtocolClient.GetStream(); _TransactionID = 0; } public void Close() { _NetworkStream?.Close(); _NetworkStream = null; _TransmissionControlProtocolClient?.Close(); _TransmissionControlProtocolClient = null; } public bool IsConnected() { if (_TransmissionControlProtocolClient is not null) return _TransmissionControlProtocolClient.Connected; return false; } public void SetTimeouts(int sendTimeout, int receiveTimeout) { if (_TransmissionControlProtocolClient == null) throw new Exception("Not connected."); _TransmissionControlProtocolClient.ReceiveTimeout = receiveTimeout; _TransmissionControlProtocolClient.SendTimeout = sendTimeout; } /// /// Write a byte array of data to the Modbus device. /// /// The starting register address. /// The byte array of data to write. public void Write(ushort address, byte[] data) { if (_NetworkStream is null) throw new Exception("Not connected."); //Using Modbus function 16 //Create Modbus Command if (data.Length > 254) throw new Exception("Too many bytes. The maximum is 254."); if (data.Length % 2 != 0) throw new Exception("The number of bytes needs to be a multiple of 2."); byte[] com = new byte[13 + data.Length]; com[7] = 16; com[8] = (byte)(address >> 8); com[9] = (byte)(address & 0xFF); com[10] = 0; com[11] = (byte)(data.Length / 2); com[12] = (byte)data.Length; Array.Copy(data, 0, com, 13, data.Length); SetHeader(com); _NetworkStream.Write(com, 0, com.Length); byte[] res = new byte[12]; int expectedSize = res.Length; int size = _NetworkStream.Read(res, 0, res.Length); Array.Resize(ref res, size); ResponseErrorChecks(res, expectedSize, com); } /// /// Read a byte array of data from the Modbus device. /// /// The starting register address. /// The read byte array of data. The array length is /// the amount of bytes to read. public void Read(ushort address, byte[] data) { if (_NetworkStream is null) throw new Exception("Not connected."); //Using Modbus function 3 //Create Modbus Command if (data.Length > 254) throw new Exception("Too many bytes. The maximum is 254."); if (data.Length % 2 != 0) throw new Exception("The number of bytes needs to be a multiple of 2."); byte[] com = new byte[12]; com[7] = 3; com[8] = (byte)(address >> 8); com[9] = (byte)(address & 0xFF); com[10] = 0; com[11] = (byte)(data.Length / 2); SetHeader(com); _NetworkStream.Write(com, 0, com.Length); byte[] res = new byte[9 + data.Length]; int expectedSize = res.Length; int size = _NetworkStream.Read(res, 0, res.Length); Array.Resize(ref res, size); ResponseErrorChecks(res, expectedSize, com); Array.Copy(res, 9, data, 0, data.Length); } /// /// Write an ushort array of data to the Modbus device. /// /// The starting register address. /// The ushort array of data to write. public void Write(ushort address, ushort[] data) { if (data.Length > 127) throw new Exception("Too many shorts. The maximum is 127."); byte[] bytes = new byte[data.Length * 2]; for (int i = 0; i < data.Length; i++) { BitConverter.GetBytes(data[i]).CopyTo(bytes, i * 2); if (BitConverter.IsLittleEndian) Array.Reverse(bytes, i * 2, 2); } Write(address, bytes); } /// /// Read an ushort array of data from the Modbus device. /// /// The starting register address. /// The read ushort array of data. The array length /// is the amount of shorts to read. public void Read(ushort address, ushort[] data) { if (data.Length > 127) throw new Exception("Too many shorts. The maximum is 127."); byte[] bytes = new byte[data.Length * 2]; Read(address, bytes); for (int i = 0; i < data.Length; i++) { if (BitConverter.IsLittleEndian) Array.Reverse(bytes, i * 2, 2); data[i] = BitConverter.ToUInt16(bytes, i * 2); } } /// /// Write an uint array of data to the Modbus device. /// /// The starting register address. /// The uint array of data to write. public void Write(ushort address, uint[] data) { if (data.Length > 63) throw new Exception("Too many uint. The maximum is 63."); byte[] bytes = new byte[data.Length * 4]; for (int i = 0; i < data.Length; i++) { BitConverter.GetBytes(data[i]).CopyTo(bytes, i * 4); if (BitConverter.IsLittleEndian) Array.Reverse(bytes, i * 4, 4); } Write(address, bytes); } /// /// Read an uint array of data from the Modbus device. /// /// The starting register address. /// The read uint array of data. The array length is /// the amount of ints to read. public void Read(ushort address, uint[] data) { if (data.Length > 63) throw new Exception("Too many ints. The maximum is 63."); byte[] bytes = new byte[data.Length * 4]; Read(address, bytes); for (int i = 0; i < data.Length; i++) { if (BitConverter.IsLittleEndian) Array.Reverse(bytes, i * 4, 4); data[i] = BitConverter.ToUInt32(bytes, i * 4); } } /// /// Write an int array of data to the Modbus device. /// /// The starting register address. /// The int array of data to write. public void Write(ushort address, int[] data) { if (data.Length > 63) throw new Exception("Too many ints. The maximum is 63."); byte[] bytes = new byte[data.Length * 4]; for (int i = 0; i < data.Length; i++) { BitConverter.GetBytes(data[i]).CopyTo(bytes, i * 4); if (BitConverter.IsLittleEndian) Array.Reverse(bytes, i * 4, 4); } Write(address, bytes); } /// /// Read an int array of data from the Modbus device. /// /// The starting register address. /// The read int array of data. The array length is /// the amount of ints to read. public void Read(ushort address, int[] data) { if (data.Length > 63) throw new Exception("Too many ints. The maximum is 63."); byte[] bytes = new byte[data.Length * 4]; Read(address, bytes); for (int i = 0; i < data.Length; i++) { if (BitConverter.IsLittleEndian) Array.Reverse(bytes, i * 4, 4); data[i] = BitConverter.ToInt32(bytes, i * 4); } } /// /// Write a float array of data to the Modbus device. /// /// The starting register address. /// The float array of data to write. public void Write(ushort address, float[] data) { if (data.Length > 63) throw new Exception("Too many floats. The maximum is 63."); byte[] bytes = new byte[data.Length * 4]; for (int i = 0; i < data.Length; i++) { BitConverter.GetBytes(data[i]).CopyTo(bytes, i * 4); if (BitConverter.IsLittleEndian) Array.Reverse(bytes, i * 4, 4); } Write(address, bytes); } /// /// Reads a float array of data from the Modbus device. /// /// The starting register address. /// The read float array of data. The array length is /// the amount of floats to read. public void Read(ushort address, float[] data) { if (data.Length > 63) throw new Exception("Too many floats. The maximum is 63."); byte[] bytes = new byte[data.Length * 4]; Read(address, bytes); for (int i = 0; i < data.Length; i++) { if (BitConverter.IsLittleEndian) Array.Reverse(bytes, i * 4, 4); data[i] = BitConverter.ToSingle(bytes, i * 4); } } /// /// Write a single ushort of data to the Modbus device. /// /// The register address. /// The ushort data to write. public void Write(ushort address, ushort data) { ushort[] dataArray = new ushort[1]; dataArray[0] = data; Write(address, dataArray); } /// /// Read a single ushort of data from the Modbus device. /// /// The register address. /// The read ushort data. public void Read(ushort address, ref ushort data) { ushort[] dataArray = new ushort[1]; Read(address, dataArray); data = dataArray[0]; } /// /// Write a single uint of data to the Modbus device. /// /// The starting register address. /// The uint data to write. public void Write(ushort address, uint data) { uint[] dataArray = new uint[1]; dataArray[0] = data; Write(address, dataArray); } /// /// Read a single uint of data from the Modbus device. /// /// The starting register address. /// The read uint data. public void Read(ushort address, ref uint data) { uint[] dataArray = new uint[1]; Read(address, dataArray); data = dataArray[0]; } /// /// Write a single int of data to the Modbus device. /// /// The starting register address. /// The int data to write. public void Write(ushort address, int data) { int[] dataArray = new int[1]; dataArray[0] = data; Write(address, dataArray); } /// /// Read a single int of data from the Modbus device. /// /// The starting register address. /// The read int data. public void Read(ushort address, ref int data) { int[] dataArray = new int[1]; Read(address, dataArray); data = dataArray[0]; } /// /// Write a single float of data to the Modbus device. /// 1 uint = 2 registers. /// /// The starting register address. /// The float data to write. public void Write(ushort address, float data) { float[] dataArray = new float[1]; dataArray[0] = data; Write(address, dataArray); } /// /// Read a single float of data from the Modbus device. /// /// The starting register address. /// The read float data. public void Read(ushort address, ref float data) { float[] dataArray = new float[1]; Read(address, dataArray); data = dataArray[0]; } /// /// Sets the MBAP header of the Modbus TCP command. /// /// The byte array for the Modbus TCP command. /// The Modbus request bytes 7+ need to be set beforehand. The MBAP /// header bytes 0 to 6 will be updated based on the request bytes. /// private void SetHeader(byte[] command) { //Transaction ID ushort transID = _TransactionID; if (_TransactionID >= 65535) { //Rollover global transaction ID to 0. _TransactionID = 0; } else { //Increment global transaction ID. _TransactionID++; } command[0] = (byte)(transID >> 8); command[1] = (byte)(transID & 0xFF); //Protocol ID command[2] = 0; command[3] = 0; //Length ushort length = (ushort)(command.Length - 6); command[4] = (byte)(length >> 8); command[5] = (byte)(length & 0xFF); //Unit ID command[6] = 1; } /// /// Checks the Modbus response for errors. /// /// The Modbus response byte array. /// The expected response byte array length. /// The Modbus command byte array. private void ResponseErrorChecks(byte[] response, int expectedLength, byte[] command) { if (response.Length < expectedLength) { if (response.Length < 9) { throw new Exception("Invalid Modbus response."); } if ((response[7] & 0x80) > 0) { //Bit 7 set, indicating Modbus error throw new Exception("Modbus exception code " + response[8] + ", " + GetExceptionCodeString(response[8]) + "."); } throw new Exception("Other Modbus response error."); } if (response[0] != command[0] || response[1] != command[1]) { throw new Exception("Modbus transaction ID mismatch."); } } /// /// Get the Modbus exception name. /// /// The exception code. /// The exception name. private string GetExceptionCodeString(uint code) => code switch { 1 => "Illegal Function", 2 => "Illegal Data Address", 3 => "Illegal Data Value", 4 => "Slave Device Failure", 5 => "Acknowledge", 6 => "Slave Device Busy", 7 => "Negative Acknowledge", 8 => "Memory Parity Error", 10 => "Gateway Path Unavailable", 11 => "Gateway Target Device Failed to Respond", _ => "" }; void IDisposable.Dispose() => Close(); } internal static bool ReadAll(AppSettings appSettings, ILogger logger) { LabJackT7Configuration configuration = appSettings.LabJackT7Configuration; _ModbusTransmissionControlProtocolClient ??= new(configuration.InternetProtocolAddress, configuration.Port); ReadAllAnalog(_ModbusTransmissionControlProtocolClient, logger); ReadAllDigitalIO(_ModbusTransmissionControlProtocolClient, logger); if (configuration.InternetProtocolAddress == "false") ReadAllAnalogMux80(_ModbusTransmissionControlProtocolClient, logger); return true; } /// /// Displays "AINxx : Values" readings. /// /// Starting Modbus address of /// readings. /// Float analog input (AIN) readings. private static void DisplayAnalogInputReadings(ILogger logger, ushort startingAddress, float[] values) { for (int i = 0; i < values.Length; i++) { logger.LogInformation("AIN" + (startingAddress + i * 2) / 2 + " : " + values[i] + " V"); } } /// /// Displays all digital I/O readings. /// /// Reading from T7 Modbus address /// 2850 (DIO_DIRECTION). /// Reading from T7 Modbus address /// 2800 (DIO_STATE). private static void DisplayDigitalIOReadings(ILogger logger, uint directions, uint states) { string fioDirs = ""; string fioStates = ""; string eioDirs = ""; string eioStates = ""; string cioDirs = ""; string cioStates = ""; string mioDirs = ""; string mioStates = ""; for (int i = 0; i < 8; i++) { fioDirs += Convert.ToString((directions >> i) & 1); fioStates += Convert.ToString((states >> i) & 1); } logger.LogInformation("FIO0-FIO7 directions = " + fioDirs + ", states = " + fioStates); for (int i = 8; i < 16; i++) { eioDirs += Convert.ToString((directions >> i) & 1); eioStates += Convert.ToString((states >> i) & 1); } logger.LogInformation("EIO0-EIO7 directions = " + eioDirs + ", states = " + eioStates); for (int i = 16; i < 20; i++) { cioDirs += Convert.ToString((directions >> i) & 1); cioStates += Convert.ToString((states >> i) & 1); } logger.LogInformation("CIO0-CIO3 directions = " + cioDirs + ", states = " + cioStates); for (int i = 20; i < 23; i++) { mioDirs += Convert.ToString((directions >> i) & 1); mioStates += Convert.ToString((states >> i) & 1); } logger.LogInformation("MIO0-MIO2 directions = " + mioDirs + ", states = " + mioStates); } /// /// Configures range, negative channel, resolution index and settling /// time for all analog inputs. /// AIN_ALL_RANGE, AIN_ALL_NEGATIVE_CH, AIN_ALL_RESOLUTION_INDEX, and /// AIN_ALL_SETTLING_US registers/settings are documented here: /// https://labjack.com/support/datasheets/t-series/ain /// /// The ModbusTransmissionControlProtocolClient to the connected T7 /// AIN_ALL_RANGE setting. /// AIN_ALL_NEGATIVE_CH setting. /// AIN_ALL_RESOLUTION_INDEX setting. /// AIN_ALL_SETTLING_US setting. private static void ConfigureAllAnalog(ModbusTransmissionControlProtocolClient mb, float range, ushort negativeChannel, ushort resolutionIndex, float settling) { ushort address; ushort uint16Value; float float32Value; // Configure all analog input ranges. address = 43900; // 43900 = AIN_ALL_RANGE float32Value = range; mb.Write(address, float32Value); // Configure all analog input negative channels. address = 43902; // 43902 = AIN_ALL_NEGATIVE_CH uint16Value = negativeChannel; mb.Write(address, uint16Value); // Configure all analog input resolution indexes. address = 43903; // 43903 = AIN_ALL_RESOLUTION_INDEX uint16Value = resolutionIndex; mb.Write(address, uint16Value); // Configure all analog input settling times. address = 43904; // 43904 = AIN_ALL_SETTLING_US float32Value = settling; mb.Write(address, float32Value); } /// /// Example that configures, reads and displays all the analog /// inputs (AIN0-AIN13) on the T7. /// Analog inputs (AIN) registers used are documented here: /// https://labjack.com/support/datasheets/t-series/ain /// /// The ModbusTransmissionControlProtocolClient to the connected T7. private static void ReadAllAnalog(ModbusTransmissionControlProtocolClient modbusTransmissionControlProtocolClient, ILogger logger) { logger.LogInformation("Reading AIN0-AIN13."); // Configure all analog inputs. // Ranges = +/-10 to. // Negative Channels = 199 (single-ended) // Resolution Indexes = 8 // Settlings = 0 (auto) ConfigureAllAnalog(modbusTransmissionControlProtocolClient, 10.0f, 199, 8, 0); // Read all 14 analog inputs. ushort startAddress = 0; // 0 = AIN0 float[] analogInputReadings = new float[14]; // 14 analog input readings modbusTransmissionControlProtocolClient.Read(startAddress, analogInputReadings); DisplayAnalogInputReadings(logger, startAddress, analogInputReadings); logger.LogInformation(""); } /// /// Example that reads and displays all the digital I/O (FIOs, EIOs, /// CIOs, MIOs) on the T7. /// Digital I/O registers used are documented here: /// https://labjack.com/support/datasheets/t-series/digital-io /// /// The ModbusTransmissionControlProtocolClient to the connected T7. private static void ReadAllDigitalIO(ModbusTransmissionControlProtocolClient modbusTransmissionControlProtocolClient, ILogger logger) { logger.LogInformation("Reading FIOs, EIOs, CIOs and MIO directions and states."); ushort address; uint directions = 0; uint states = 0; // Read all digital I/O directions and states. address = 2850; // 2850 = DIO_DIRECTION modbusTransmissionControlProtocolClient.Read(address, ref directions); address = 2800; // 2800 = DIO_STATE modbusTransmissionControlProtocolClient.Read(address, ref states); DisplayDigitalIOReadings(logger, directions, states); logger.LogInformation(""); } /// /// Example that configures, reads and displays all the analog /// inputs on the T7 with a Mux80 (AIN0-AIN3, AIN48-AIN127). /// Analog inputs (AIN) registers used are documented here: /// https://labjack.com/support/datasheets/t-series/ain /// Extended channels AIN48+ are further documented here: /// https://labjack.com/support/datasheets/t-series/ain/extended-channels /// Mux80 data sheet can be found here: /// https://labjack.com/support/datasheets/accessories/mux80 /// /// The ModbusTransmissionControlProtocolClient to the connected T7. private static void ReadAllAnalogMux80(ModbusTransmissionControlProtocolClient modbusTransmissionControlProtocolClient, ILogger logger) { // Many registers to channels are incorrect. Check with Steve. logger.LogInformation("Reading AIN0-AIN3, AIN48-AIN127."); // Configure all analog inputs. // Ranges = +/-10 to. // Negative Channels = 199 (single-ended) // Resolution Indexes = 1 // Settlings = 0 (auto) ConfigureAllAnalog(modbusTransmissionControlProtocolClient, 10.0f, 199, 1, 0); //Reading from 84 analog inputs with the Mux80. ushort startAddress; float[] analogInputReadings; // Read from AIN0-AIN3 on the T7 terminals. startAddress = 0; // 0 = AIN0 analogInputReadings = new float[4]; // 4 analog input readings. modbusTransmissionControlProtocolClient.Read(startAddress, analogInputReadings); DisplayAnalogInputReadings(logger, startAddress, analogInputReadings); // Read from AIN48-AIN87 on Mux80. startAddress = 96; // 96 = AIN48 analogInputReadings = new float[40]; // 40 analog input readings modbusTransmissionControlProtocolClient.Read(startAddress, analogInputReadings); DisplayAnalogInputReadings(logger, startAddress, analogInputReadings); // Read from AIN88-AIN127 on Mux80. startAddress = 176; // 176 = AIN88 analogInputReadings = new float[40]; // 40 analog input readings modbusTransmissionControlProtocolClient.Read(startAddress, analogInputReadings); DisplayAnalogInputReadings(logger, startAddress, analogInputReadings); logger.LogInformation(""); } }