Files
file-watcher/Helpers/LabJackT7Helper.cs

700 lines
27 KiB
C#

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;
}
/// <summary>
/// Write a byte array of data to the Modbus device.
/// </summary>
/// <param name="address">The starting register address.</param>
/// <param name="data">The byte array of data to write.</param>
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);
}
/// <summary>
/// Read a byte array of data from the Modbus device.
/// </summary>
/// <param name="address">The starting register address.</param>
/// <param name="data">The read byte array of data. The array length is
/// the amount of bytes to read.</param>
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);
}
/// <summary>
/// Write an ushort array of data to the Modbus device.
/// </summary>
/// <param name="address">The starting register address.</param>
/// <param name="data">The ushort array of data to write.</param>
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);
}
/// <summary>
/// Read an ushort array of data from the Modbus device.
/// </summary>
/// <param name="address">The starting register address.</param>
/// <param name="data">The read ushort array of data. The array length
/// is the amount of shorts to read.</param>
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);
}
}
/// <summary>
/// Write an uint array of data to the Modbus device.
/// </summary>
/// <param name="address">The starting register address.</param>
/// <param name="data">The uint array of data to write.</param>
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);
}
/// <summary>
/// Read an uint array of data from the Modbus device.
/// </summary>
/// <param name="address">The starting register address.</param>
/// <param name="data">The read uint array of data. The array length is
/// the amount of ints to read.</param>
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);
}
}
/// <summary>
/// Write an int array of data to the Modbus device.
/// </summary>
/// <param name="address">The starting register address.</param>
/// <param name="data">The int array of data to write.</param>
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);
}
/// <summary>
/// Read an int array of data from the Modbus device.
/// </summary>
/// <param name="address">The starting register address.</param>
/// <param name="data">The read int array of data. The array length is
/// the amount of ints to read.</param>
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);
}
}
/// <summary>
/// Write a float array of data to the Modbus device.
/// </summary>
/// <param name="address">The starting register address.</param>
/// <param name="data">The float array of data to write.</param>
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);
}
/// <summary>
/// Reads a float array of data from the Modbus device.
/// </summary>
/// <param name="address">The starting register address.</param>
/// <param name="data">The read float array of data. The array length is
/// the amount of floats to read.</param>
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);
}
}
/// <summary>
/// Write a single ushort of data to the Modbus device.
/// </summary>
/// <param name="address">The register address.</param>
/// <param name="data">The ushort data to write.</param>
public void Write(ushort address, ushort data)
{
ushort[] dataArray = new ushort[1];
dataArray[0] = data;
Write(address, dataArray);
}
/// <summary>
/// Read a single ushort of data from the Modbus device.
/// </summary>
/// <param name="address">The register address.</param>
/// <param name="data">The read ushort data.</param>
public void Read(ushort address, ref ushort data)
{
ushort[] dataArray = new ushort[1];
Read(address, dataArray);
data = dataArray[0];
}
/// <summary>
/// Write a single uint of data to the Modbus device.
/// </summary>
/// <param name="address">The starting register address.</param>
/// <param name="data">The uint data to write.</param>
public void Write(ushort address, uint data)
{
uint[] dataArray = new uint[1];
dataArray[0] = data;
Write(address, dataArray);
}
/// <summary>
/// Read a single uint of data from the Modbus device.
/// </summary>
/// <param name="address">The starting register address.</param>
/// <param name="value">The read uint data.</param>
public void Read(ushort address, ref uint data)
{
uint[] dataArray = new uint[1];
Read(address, dataArray);
data = dataArray[0];
}
/// <summary>
/// Write a single int of data to the Modbus device.
/// </summary>
/// <param name="address">The starting register address.</param>
/// <param name="data">The int data to write.</param>
public void Write(ushort address, int data)
{
int[] dataArray = new int[1];
dataArray[0] = data;
Write(address, dataArray);
}
/// <summary>
/// Read a single int of data from the Modbus device.
/// </summary>
/// <param name="address">The starting register address.</param>
/// <param name="data">The read int data.</param>
public void Read(ushort address, ref int data)
{
int[] dataArray = new int[1];
Read(address, dataArray);
data = dataArray[0];
}
/// <summary>
/// Write a single float of data to the Modbus device.
/// 1 uint = 2 registers.
/// </summary>
/// <param name="address">The starting register address.</param>
/// <param name="data">The float data to write.</param>
public void Write(ushort address, float data)
{
float[] dataArray = new float[1];
dataArray[0] = data;
Write(address, dataArray);
}
/// <summary>
/// Read a single float of data from the Modbus device.
/// </summary>
/// <param name="address">The starting register address.</param>
/// <param name="data">The read float data.</param>
public void Read(ushort address, ref float data)
{
float[] dataArray = new float[1];
Read(address, dataArray);
data = dataArray[0];
}
/// <summary>
/// Sets the MBAP header of the Modbus TCP command.
/// </summary>
/// <param name="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.
/// </param>
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;
}
/// <summary>
/// Checks the Modbus response for errors.
/// </summary>
/// <param name="response">The Modbus response byte array.</param>
/// <param name="expectedSize">The expected response byte array length.</param>
/// <param name="command">The Modbus command byte array.</param>
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.");
}
}
/// <summary>
/// Get the Modbus exception name.
/// </summary>
/// <param name="code">The exception code.</param>
/// <returns>The exception name.</returns>
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<Worker> 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;
}
/// <summary>
/// Displays "AINxx : Values" readings.
/// </summary>
/// <param name="startingAddress">Starting Modbus address of
/// readings.</param>
/// <param name="values">Float analog input (AIN) readings.</param>
private static void DisplayAnalogInputReadings(ILogger<Worker> logger, ushort startingAddress, float[] values)
{
for (int i = 0; i < values.Length; i++)
{
logger.LogInformation("AIN" + (startingAddress + i * 2) / 2 + " : " + values[i] + " V");
}
}
/// <summary>
/// Displays all digital I/O readings.
/// </summary>
/// <param name="directions">Reading from T7 Modbus address
/// 2850 (DIO_DIRECTION).</param>
/// <param name="states">Reading from T7 Modbus address
/// 2800 (DIO_STATE).</param>
private static void DisplayDigitalIOReadings(ILogger<Worker> 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);
}
/// <summary>
/// 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
/// </summary>
/// <param name="mb">The ModbusTransmissionControlProtocolClient to the connected T7 </param>
/// <param name="range">AIN_ALL_RANGE setting.</param>
/// <param name="negativeChannel">AIN_ALL_NEGATIVE_CH setting.</param>
/// <param name="resolutionIndex">AIN_ALL_RESOLUTION_INDEX setting.</param>
/// <param name="settling">AIN_ALL_SETTLING_US setting.</param>
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);
}
/// <summary>
/// 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
/// </summary>
/// <param name="mb">The ModbusTransmissionControlProtocolClient to the connected T7.</param>
private static void ReadAllAnalog(ModbusTransmissionControlProtocolClient modbusTransmissionControlProtocolClient, ILogger<Worker> 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("");
}
/// <summary>
/// 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
/// </summary>
/// <param name="modbusTransmissionControlProtocolClient">The ModbusTransmissionControlProtocolClient to the connected T7.</param>
private static void ReadAllDigitalIO(ModbusTransmissionControlProtocolClient modbusTransmissionControlProtocolClient, ILogger<Worker> 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("");
}
/// <summary>
/// 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
/// </summary>
/// <param name="modbusTransmissionControlProtocolClient">The ModbusTransmissionControlProtocolClient to the connected T7.</param>
private static void ReadAllAnalogMux80(ModbusTransmissionControlProtocolClient modbusTransmissionControlProtocolClient, ILogger<Worker> 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("");
}
}