.NET 8實現(xiàn)modbus通訊工具類封裝的操作方法
引言
Modbus 協(xié)議是工業(yè)自動化領域應用最廣泛的通信協(xié)議之一,廣泛應用于 PLC、傳感器、儀表等設備之間的數(shù)據(jù)交換。在 .NET 8 中實現(xiàn) Modbus 通訊工具類可以大大簡化工業(yè)控制系統(tǒng)的開發(fā)工作。本文將詳細介紹如何封裝一個功能完整的 Modbus 工具類,支持 RTU 和 TCP 兩種傳輸模式。
1. Modbus 協(xié)議基礎
Modbus 協(xié)議主要有兩種傳輸模式:
- ??Modbus RTU??:基于串行通信(RS232/RS485),使用二進制數(shù)據(jù)格式
- ??Modbus TCP??:基于以太網(wǎng)通信,使用 TCP/IP 協(xié)議棧
1.1 Modbus 功能碼
常用功能碼:
- 0x01:讀線圈狀態(tài)
- 0x02:讀輸入狀態(tài)
- 0x03:讀保持寄存器
- 0x04:讀輸入寄存器
- 0x05:寫單個線圈
- 0x06:寫單個寄存器
- 0x0F:寫多個線圈
- 0x10:寫多個寄存器
2. Modbus 工具類設計
2.1 基礎接口和枚舉
public enum ModbusType
{
RTU,
TCP
}
public enum ModbusFunctionCode : byte
{
ReadCoils = 0x01,
ReadDiscreteInputs = 0x02,
ReadHoldingRegisters = 0x03,
ReadInputRegisters = 0x04,
WriteSingleCoil = 0x05,
WriteSingleRegister = 0x06,
WriteMultipleCoils = 0x0F,
WriteMultipleRegisters = 0x10
}
public interface IModbusClient : IDisposable
{
Task<bool> ConnectAsync();
void Disconnect();
bool IsConnected { get; }
// 讀取操作
Task<bool[]> ReadCoilsAsync(byte slaveId, ushort startAddress, ushort numberOfCoils);
Task<bool[]> ReadDiscreteInputsAsync(byte slaveId, ushort startAddress, ushort numberOfInputs);
Task<ushort[]> ReadHoldingRegistersAsync(byte slaveId, ushort startAddress, ushort numberOfRegisters);
Task<ushort[]> ReadInputRegistersAsync(byte slaveId, ushort startAddress, ushort numberOfRegisters);
// 寫入操作
Task<bool> WriteSingleCoilAsync(byte slaveId, ushort coilAddress, bool value);
Task<bool> WriteSingleRegisterAsync(byte slaveId, ushort registerAddress, ushort value);
Task<bool> WriteMultipleCoilsAsync(byte slaveId, ushort startAddress, bool[] values);
Task<bool> WriteMultipleRegistersAsync(byte slaveId, ushort startAddress, ushort[] values);
}2.2 Modbus 異常處理
public class ModbusException : Exception
{
public byte ExceptionCode { get; }
public ModbusException(byte exceptionCode, string message)
: base(message)
{
ExceptionCode = exceptionCode;
}
}
public static class ModbusErrorCodes
{
public static readonly Dictionary<byte, string> Errors = new Dictionary<byte, string>
{
{ 0x01, "非法功能碼" },
{ 0x02, "非法數(shù)據(jù)地址" },
{ 0x03, "非法數(shù)據(jù)值" },
{ 0x04, "從站設備故障" },
{ 0x05, "確認" },
{ 0x06, "從屬設備忙" },
{ 0x07, "存儲奇偶性錯誤" },
{ 0x08, "不可用網(wǎng)關路徑" },
{ 0x09, "網(wǎng)關目標設備響應失敗" },
{ 0x0A, "網(wǎng)關目標設備響應失敗" }
};
}3. Modbus RTU 實現(xiàn)
3.1 ModbusRtuClient 類
using System.IO.Ports;
using System.Threading.Tasks;
public class ModbusRtuClient : IModbusClient
{
private readonly SerialPort _serialPort;
private readonly int _responseTimeout;
private readonly byte _defaultSlaveId;
public bool IsConnected => _serialPort?.IsOpen ?? false;
public ModbusRtuClient(string portName, int baudRate = 9600, Parity parity = Parity.None,
int dataBits = 8, StopBits stopBits = StopBits.One,
int responseTimeout = 1000, byte defaultSlaveId = 1)
{
_serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits)
{
ReadTimeout = responseTimeout,
WriteTimeout = responseTimeout
};
_responseTimeout = responseTimeout;
_defaultSlaveId = defaultSlaveId;
}
public async Task<bool> ConnectAsync()
{
if (IsConnected) return true;
try
{
_serialPort.Open();
return true;
}
catch
{
return false;
}
}
public void Disconnect()
{
if (IsConnected)
{
_serialPort.Close();
}
}
public void Dispose()
{
Disconnect();
_serialPort.Dispose();
}
// 核心通信方法
private async Task<byte[]> SendReceiveAsync(byte slaveId, byte[] request)
{
if (!IsConnected) throw new InvalidOperationException("串口未連接");
// 添加CRC校驗
byte[] frame = new byte[request.Length + 2];
Array.Copy(request, frame, request.Length);
ushort crc = CalculateCrc(request);
frame[request.Length] = (byte)(crc & 0xFF);
frame[request.Length + 1] = (byte)((crc >> 8) & 0xFF);
// 發(fā)送請求
_serialPort.DiscardInBuffer();
_serialPort.Write(frame, 0, frame.Length);
// 接收響應
int expectedLength = GetExpectedResponseLength(request[1]);
byte[] response = new byte[expectedLength + 2]; // +2 for CRC
int bytesRead = 0;
int totalBytesToRead = response.Length;
DateTime startTime = DateTime.Now;
while (bytesRead < totalBytesToRead)
{
if ((DateTime.Now - startTime).TotalMilliseconds > _responseTimeout)
{
throw new TimeoutException("讀取響應超時");
}
if (_serialPort.BytesToRead > 0)
{
bytesRead += _serialPort.Read(response, bytesRead, totalBytesToRead - bytesRead);
}
else
{
await Task.Delay(10);
}
}
// 驗證CRC
ushort receivedCrc = (ushort)(response[response.Length - 2] | (response[response.Length - 1] << 8));
ushort calculatedCrc = CalculateCrc(response, 0, response.Length - 2);
if (receivedCrc != calculatedCrc)
{
throw new ModbusException(0, "CRC校驗失敗");
}
// 檢查異常響應
if ((response[1] & 0x80) != 0)
{
byte exceptionCode = response[2];
string errorMessage = ModbusErrorCodes.Errors.TryGetValue(exceptionCode, out string msg)
? msg : $"未知異常代碼: 0x{exceptionCode:X2}";
throw new ModbusException(exceptionCode, errorMessage);
}
return response;
}
private ushort CalculateCrc(byte[] data, int offset = 0, int length = -1)
{
if (length == -1) length = data.Length - offset;
ushort crc = 0xFFFF;
for (int i = offset; i < offset + length; i++)
{
crc ^= data[i];
for (int j = 0; j < 8; j++)
{
if ((crc & 0x0001) != 0)
{
crc >>= 1;
crc ^= 0xA001;
}
else
{
crc >>= 1;
}
}
}
return crc;
}
private int GetExpectedResponseLength(byte functionCode)
{
switch (functionCode)
{
case (byte)ModbusFunctionCode.ReadCoils:
case (byte)ModbusFunctionCode.ReadDiscreteInputs:
return 3; // SlaveId + FC + ByteCount + (Data bytes) + CRC
case (byte)ModbusFunctionCode.ReadHoldingRegisters:
case (byte)ModbusFunctionCode.ReadInputRegisters:
return 3; // SlaveId + FC + ByteCount + (Data bytes) + CRC
case (byte)ModbusFunctionCode.WriteSingleCoil:
case (byte)ModbusFunctionCode.WriteSingleRegister:
return 6; // SlaveId + FC + Address + Value + CRC
case (byte)ModbusFunctionCode.WriteMultipleCoils:
case (byte)ModbusFunctionCode.WriteMultipleRegisters:
return 6; // SlaveId + FC + Address + Quantity + CRC
default:
throw new ArgumentException($"未知功能碼: 0x{functionCode:X2}");
}
}
// 具體功能實現(xiàn)將在下面展開...
}3.2 Modbus RTU 功能實現(xiàn)
// 在 ModbusRtuClient 類中添加以下方法
public async Task<bool[]> ReadCoilsAsync(byte slaveId, ushort startAddress, ushort numberOfCoils)
{
if (numberOfCoils < 1 || numberOfCoils > 2000)
throw new ArgumentException("線圈數(shù)量必須在1-2000之間");
byte[] request = new byte[6];
request[0] = slaveId;
request[1] = (byte)ModbusFunctionCode.ReadCoils;
request[2] = (byte)(startAddress >> 8);
request[3] = (byte)(startAddress & 0xFF);
request[4] = (byte)(numberOfCoils >> 8);
request[5] = (byte)(numberOfCoils & 0xFF);
byte[] response = await SendReceiveAsync(slaveId, request);
int byteCount = response[2];
bool[] coils = new bool[numberOfCoils];
for (int i = 0; i < numberOfCoils; i++)
{
int byteIndex = 3 + i / 8;
int bitIndex = i % 8;
coils[i] = (response[byteIndex] & (1 << bitIndex)) != 0;
}
return coils;
}
public async Task<ushort[]> ReadHoldingRegistersAsync(byte slaveId, ushort startAddress, ushort numberOfRegisters)
{
if (numberOfRegisters < 1 || numberOfRegisters > 125)
throw new ArgumentException("寄存器數(shù)量必須在1-125之間");
byte[] request = new byte[6];
request[0] = slaveId;
request[1] = (byte)ModbusFunctionCode.ReadHoldingRegisters;
request[2] = (byte)(startAddress >> 8);
request[3] = (byte)(startAddress & 0xFF);
request[4] = (byte)(numberOfRegisters >> 8);
request[5] = (byte)(numberOfRegisters & 0xFF);
byte[] response = await SendReceiveAsync(slaveId, request);
int byteCount = response[2];
ushort[] registers = new ushort[numberOfRegisters];
for (int i = 0; i < numberOfRegisters; i++)
{
int offset = 3 + i * 2;
registers[i] = (ushort)((response[offset] << 8) | response[offset + 1]);
}
return registers;
}
public async Task<bool> WriteSingleCoilAsync(byte slaveId, ushort coilAddress, bool value)
{
byte[] request = new byte[6];
request[0] = slaveId;
request[1] = (byte)ModbusFunctionCode.WriteSingleCoil;
request[2] = (byte)(coilAddress >> 8);
request[3] = (byte)(coilAddress & 0xFF);
request[4] = value ? (byte)0xFF : (byte)0x00;
request[5] = 0x00;
byte[] response = await SendReceiveAsync(slaveId, request);
// 驗證響應
if (response.Length != 6) return false;
if (response[2] != request[2] || response[3] != request[3]) return false;
if (response[4] != request[4] || response[5] != request[5]) return false;
return true;
}
public async Task<bool> WriteMultipleRegistersAsync(byte slaveId, ushort startAddress, ushort[] values)
{
if (values == null || values.Length == 0 || values.Length > 123)
throw new ArgumentException("寄存器數(shù)量必須在1-123之間");
byte[] request = new byte[7 + values.Length * 2];
request[0] = slaveId;
request[1] = (byte)ModbusFunctionCode.WriteMultipleRegisters;
request[2] = (byte)(startAddress >> 8);
request[3] = (byte)(startAddress & 0xFF);
request[4] = (byte)(values.Length >> 8);
request[5] = (byte)(values.Length & 0xFF);
request[6] = (byte)(values.Length * 2);
for (int i = 0; i < values.Length; i++)
{
request[7 + i * 2] = (byte)(values[i] >> 8);
request[8 + i * 2] = (byte)(values[i] & 0xFF);
}
byte[] response = await SendReceiveAsync(slaveId, request);
// 驗證響應
if (response.Length != 6) return false;
if (response[2] != request[2] || response[3] != request[3]) return false;
if (response[4] != request[4] || response[5] != request[5]) return false;
return true;
}
// 其他功能實現(xiàn)類似...4. Modbus TCP 實現(xiàn)
4.1 ModbusTcpClient 類
using System.Net.Sockets;
using System.Threading.Tasks;
public class ModbusTcpClient : IModbusClient
{
private TcpClient _tcpClient;
private NetworkStream _networkStream;
private readonly string _host;
private readonly int _port;
private readonly int _responseTimeout;
private readonly byte _defaultSlaveId;
private ushort _transactionId = 0;
public bool IsConnected => _tcpClient?.Connected ?? false;
public ModbusTcpClient(string host, int port = 502, int responseTimeout = 1000, byte defaultSlaveId = 1)
{
_host = host;
_port = port;
_responseTimeout = responseTimeout;
_defaultSlaveId = defaultSlaveId;
}
public async Task<bool> ConnectAsync()
{
if (IsConnected) return true;
try
{
_tcpClient = new TcpClient();
await _tcpClient.ConnectAsync(_host, _port);
_networkStream = _tcpClient.GetStream();
_networkStream.ReadTimeout = _responseTimeout;
return true;
}
catch
{
return false;
}
}
public void Disconnect()
{
if (IsConnected)
{
_networkStream?.Close();
_tcpClient?.Close();
_tcpClient = null;
_networkStream = null;
}
}
public void Dispose()
{
Disconnect();
_tcpClient?.Dispose();
_networkStream?.Dispose();
}
// 核心通信方法
private async Task<byte[]> SendReceiveAsync(byte slaveId, byte[] pdu)
{
if (!IsConnected) throw new InvalidOperationException("TCP連接未建立");
// 構(gòu)建MBAP頭
byte[] mbapHeader = new byte[7];
ushort transactionId = _transactionId++;
mbapHeader[0] = (byte)(transactionId >> 8);
mbapHeader[1] = (byte)(transactionId & 0xFF);
mbapHeader[2] = 0x00; // 協(xié)議標識符高位
mbapHeader[3] = 0x00; // 協(xié)議標識符低位
mbapHeader[4] = (byte)((pdu.Length + 1) >> 8); // 長度高位
mbapHeader[5] = (byte)((pdu.Length + 1) & 0xFF); // 長度低位
mbapHeader[6] = slaveId; // 單元標識符
// 構(gòu)建完整請求
byte[] request = new byte[mbapHeader.Length + pdu.Length];
Array.Copy(mbapHeader, request, mbapHeader.Length);
Array.Copy(pdu, 0, request, mbapHeader.Length, pdu.Length);
// 發(fā)送請求
await _networkStream.WriteAsync(request, 0, request.Length);
// 接收MBAP頭
byte[] mbapResponse = new byte[7];
int bytesRead = await _networkStream.ReadAsync(mbapResponse, 0, mbapResponse.Length);
if (bytesRead != mbapResponse.Length)
{
throw new IOException("讀取MBAP頭不完整");
}
// 驗證事務ID
ushort responseTransactionId = (ushort)((mbapResponse[0] << 8) | mbapResponse[1]);
if (responseTransactionId != transactionId)
{
throw new ModbusException(0, "事務ID不匹配");
}
// 獲取PDU長度
int pduLength = (mbapResponse[4] << 8) | mbapResponse[5];
// 接收PDU
byte[] pduResponse = new byte[pduLength - 1]; // 減去單元標識符
bytesRead = await _networkStream.ReadAsync(pduResponse, 0, pduResponse.Length);
if (bytesRead != pduResponse.Length)
{
throw new IOException("讀取PDU不完整");
}
// 檢查異常響應
if ((pduResponse[0] & 0x80) != 0)
{
byte exceptionCode = pduResponse[1];
string errorMessage = ModbusErrorCodes.Errors.TryGetValue(exceptionCode, out string msg)
? msg : $"未知異常代碼: 0x{exceptionCode:X2}";
throw new ModbusException(exceptionCode, errorMessage);
}
return pduResponse;
}
// 具體功能實現(xiàn)與RTU類似,只是使用PDU而不是完整幀
public async Task<ushort[]> ReadHoldingRegistersAsync(byte slaveId, ushort startAddress, ushort numberOfRegisters)
{
if (numberOfRegisters < 1 || numberOfRegisters > 125)
throw new ArgumentException("寄存器數(shù)量必須在1-125之間");
byte[] pdu = new byte[5];
pdu[0] = (byte)ModbusFunctionCode.ReadHoldingRegisters;
pdu[1] = (byte)(startAddress >> 8);
pdu[2] = (byte)(startAddress & 0xFF);
pdu[3] = (byte)(numberOfRegisters >> 8);
pdu[4] = (byte)(numberOfRegisters & 0xFF);
byte[] response = await SendReceiveAsync(slaveId, pdu);
int byteCount = response[1];
ushort[] registers = new ushort[numberOfRegisters];
for (int i = 0; i < numberOfRegisters; i++)
{
int offset = 2 + i * 2;
registers[i] = (ushort)((response[offset] << 8) | response[offset + 1]);
}
return registers;
}
// 其他功能實現(xiàn)類似...
}5. 工廠模式創(chuàng)建 Modbus 客戶端
public static class ModbusClientFactory
{
public static IModbusClient CreateClient(ModbusType type, string connectionString, byte defaultSlaveId = 1)
{
switch (type)
{
case ModbusType.RTU:
// 連接字符串格式: COM1,9600,None,8,One
string[] rtuParams = connectionString.Split(',');
if (rtuParams.Length < 1) throw new ArgumentException("無效的RTU連接字符串");
string portName = rtuParams[0];
int baudRate = rtuParams.Length > 1 ? int.Parse(rtuParams[1]) : 9600;
Parity parity = rtuParams.Length > 2 ? (Parity)Enum.Parse(typeof(Parity), rtuParams[2]) : Parity.None;
int dataBits = rtuParams.Length > 3 ? int.Parse(rtuParams[3]) : 8;
StopBits stopBits = rtuParams.Length > 4 ? (StopBits)Enum.Parse(typeof(StopBits), rtuParams[4]) : StopBits.One;
return new ModbusRtuClient(portName, baudRate, parity, dataBits, stopBits, defaultSlaveId: defaultSlaveId);
case ModbusType.TCP:
// 連接字符串格式: 192.168.1.100:502
string[] tcpParams = connectionString.Split(':');
if (tcpParams.Length < 1) throw new ArgumentException("無效的TCP連接字符串");
string host = tcpParams[0];
int port = tcpParams.Length > 1 ? int.Parse(tcpParams[1]) : 502;
return new ModbusTcpClient(host, port, defaultSlaveId: defaultSlaveId);
default:
throw new ArgumentException("不支持的Modbus類型");
}
}
}6. 高級功能擴展
6.1 批量讀取優(yōu)化
public static class ModbusExtensions
{
public static async Task<Dictionary<ushort, ushort>> ReadHoldingRegistersRangeAsync(
this IModbusClient client, byte slaveId,
ushort startAddress, ushort endAddress, int maxPerRequest = 100)
{
var results = new Dictionary<ushort, ushort>();
ushort current = startAddress;
while (current <= endAddress)
{
ushort count = (ushort)Math.Min(maxPerRequest, endAddress - current + 1);
ushort[] values = await client.ReadHoldingRegistersAsync(slaveId, current, count);
for (int i = 0; i < values.Length; i++)
{
results[(ushort)(current + i)] = values[i];
}
current += count;
}
return results;
}
}6.2 自動重連機制
public class AutoReconnectModbusClient : IModbusClient
{
private readonly IModbusClient _innerClient;
private readonly int _maxRetries;
private readonly int _retryDelay;
public bool IsConnected => _innerClient.IsConnected;
public AutoReconnectModbusClient(IModbusClient innerClient, int maxRetries = 3, int retryDelay = 1000)
{
_innerClient = innerClient;
_maxRetries = maxRetries;
_retryDelay = retryDelay;
}
public async Task<bool> ConnectAsync()
{
for (int i = 0; i < _maxRetries; i++)
{
if (await _innerClient.ConnectAsync())
return true;
await Task.Delay(_retryDelay);
}
return false;
}
public void Disconnect() => _innerClient.Disconnect();
public void Dispose() => _innerClient.Dispose();
private async Task<T> ExecuteWithRetry<T>(Func<Task<T>> operation)
{
for (int i = 0; i < _maxRetries; i++)
{
try
{
if (!_innerClient.IsConnected)
{
await ConnectAsync();
}
return await operation();
}
catch (IOException) when (i < _maxRetries - 1)
{
// 連接錯誤,嘗試重連
Disconnect();
await Task.Delay(_retryDelay);
}
catch (TimeoutException) when (i < _maxRetries - 1)
{
// 超時錯誤,嘗試重試
await Task.Delay(_retryDelay);
}
}
// 最后一次嘗試
return await operation();
}
public async Task<ushort[]> ReadHoldingRegistersAsync(byte slaveId, ushort startAddress, ushort numberOfRegisters)
{
return await ExecuteWithRetry(() =>
_innerClient.ReadHoldingRegistersAsync(slaveId, startAddress, numberOfRegisters));
}
// 其他方法實現(xiàn)類似...
}7. 使用示例
class Program
{
static async Task Main(string[] args)
{
// 創(chuàng)建Modbus TCP客戶端
var tcpClient = ModbusClientFactory.CreateClient(
ModbusType.TCP, "192.168.1.100:502", defaultSlaveId: 1);
// 創(chuàng)建帶自動重連的客戶端
var autoReconnectClient = new AutoReconnectModbusClient(tcpClient);
try
{
// 連接設備
if (await autoReconnectClient.ConnectAsync())
{
Console.WriteLine("Modbus連接成功");
// 讀取保持寄存器
ushort[] registers = await autoReconnectClient.ReadHoldingRegistersAsync(1, 0, 10);
Console.WriteLine("寄存器值: " + string.Join(", ", registers));
// 寫入單個寄存器
bool writeResult = await autoReconnectClient.WriteSingleRegisterAsync(1, 5, 1234);
Console.WriteLine($"寫入寄存器結(jié)果: {writeResult}");
// 批量讀取寄存器范圍
var registerMap = await autoReconnectClient.ReadHoldingRegistersRangeAsync(1, 0, 100);
foreach (var kvp in registerMap)
{
Console.WriteLine($"地址 {kvp.Key}: {kvp.Value}");
}
}
else
{
Console.WriteLine("Modbus連接失敗");
}
}
catch (ModbusException ex)
{
Console.WriteLine($"Modbus錯誤: {ex.Message} (代碼: 0x{ex.ExceptionCode:X2})");
}
catch (Exception ex)
{
Console.WriteLine($"發(fā)生錯誤: {ex.Message}");
}
finally
{
autoReconnectClient.Disconnect();
}
// 使用Modbus RTU
var rtuClient = ModbusClientFactory.CreateClient(
ModbusType.RTU, "COM3,9600,None,8,One", defaultSlaveId: 2);
try
{
if (await rtuClient.ConnectAsync())
{
// 讀取線圈狀態(tài)
bool[] coils = await rtuClient.ReadCoilsAsync(2, 0, 8);
Console.WriteLine("線圈狀態(tài): " + string.Join(", ", coils.Select(c => c ? "1" : "0")));
}
}
finally
{
rtuClient.Disconnect();
}
}
}8. 最佳實踐
1.??連接管理??:
- 保持連接時間盡可能短
- 使用自動重連機制處理網(wǎng)絡波動
- 合理設置超時時間
2.??錯誤處理??:
- 捕獲并處理所有Modbus異常
- 實現(xiàn)重試機制處理臨時錯誤
- 記錄詳細錯誤日志
3.?性能優(yōu)化??:
- 批量讀取數(shù)據(jù)減少請求次數(shù)
- 使用異步方法避免阻塞
- 合理設置請求大小(避免過大PDU)
4.?線程安全??:
- Modbus客戶端不是線程安全的
- 在多線程環(huán)境中使用鎖或創(chuàng)建多個實例
- 避免并發(fā)訪問同一連接
5.??配置管理??:
- 將Modbus配置存儲在配置文件或數(shù)據(jù)庫中
- 支持動態(tài)更新配置
- 提供配置驗證功能
9. 總結(jié)
本文介紹了如何使用 .NET 8 實現(xiàn)一個功能完整的 Modbus 通訊工具類,主要特點包括:
1.??雙協(xié)議支持??:
- 完整實現(xiàn) Modbus RTU 和 Modbus TCP 協(xié)議
- 支持所有常用功能碼
2.??優(yōu)雅封裝??:
- 統(tǒng)一的接口設計
- 工廠模式創(chuàng)建客戶端
- 擴展方法提供高級功能
3.??健壯性設計??:
- 完善的錯誤處理機制
- 自動重連功能
- CRC 和事務ID驗證
4.??易用性??:
- 簡潔的API設計
- 詳細的異常信息
- 豐富的使用示例
通過這種封裝,開發(fā)者可以快速集成 Modbus 通訊功能到各種工業(yè)控制系統(tǒng)中,大大提高了開發(fā)效率和系統(tǒng)可靠性。該工具類適用于 SCADA 系統(tǒng)、MES 系統(tǒng)、設備監(jiān)控平臺等各種工業(yè)自動化場景。
以上就是.NET 8實現(xiàn)modbus通訊工具類封裝的操作方法的詳細內(nèi)容,更多關于.NET 8 modbus工具類封裝的資料請關注腳本之家其它相關文章!
相關文章
為ASP.NET MVC及WebApi添加路由優(yōu)先級
這是一個對Asp.Net Mvc的一個很小的功能拓展,小項目可能不太需要這個功能,但有時候項目大了注冊的路由不生效時你應該要想到有可能是因為路由順序的原因,這時這個路由優(yōu)先級的功能有可能就會給你帶來便利。2015-10-10
使用HttpClient增刪改查ASP.NET Web API服務
這篇文章介紹了使用HttpClient增刪改查ASP.NET Web API服務的方法,文中通過示例代碼介紹的非常詳細。對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-10-10
使用pdfbox實現(xiàn)pdf文本提取和合并功能示例
這篇文章主要介紹了使用pdfbox實現(xiàn)pdf文本提取和合并功能示例,大家參考使用吧2014-01-01
Asp.net core Web Api配置swagger中文的實現(xiàn)
swagger是一個api文檔自動生動工具,還集成了在線調(diào)試. 可以為項目自動生成接口文檔, 非常的方便快捷,具有一定的參考價值,感興趣的可以了解一下2023-09-09
asp.net 簡單實現(xiàn)禁用或啟用頁面中的某一類型的控件
最近在一個winform項目中碰到的一個功能,勾選一個checkbox后窗體中的其他控件不可用。由此想到asp.net項目中有時候也要用到這種功能。2009-11-11

