VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > c#编程 >
  • C#网络编程之IoT设备通信(串口、蓝牙、WiFi)

第62章 IoT设备通信(串口、蓝牙、WiFi)
一、我踩过的设备通信坑:从“串口波特率不匹配导致PLC数据乱码”到“WiFi重连逻辑救了我”
做工业物联网项目时,一开始用串口连接PLC,结果数据vb.net教程C#教程python教程SQL教程access 2010教程全是乱码——查了3小时发现PLC波特率是9600,我代码里设成了115200,完全不匹配!后来改成自动检测波特率,再也没出过乱码。还有一次做蓝牙温湿度传感器,配对时没处理PIN码,导致手机连不上设备——后来在代码里硬编码PIN码,配对成功率直接到100%。最坑的是WiFi摄像头,一开始没做重连逻辑,路由器重启后摄像头直接离线,用户以为设备坏了——后来加了定时检测信号强度,弱信号或离线时自动重连,设备在线率从80%升到99.9%。这节我把这些血泪经验揉进去,用大白话讲透串口、蓝牙、WiFi三种通信方式的核心原理,结合C#实战代码逐行拆解,拓展生产级优化技巧,让你的IoT设备通信稳如老狗!
二、串口通信:工业设备的“有线老伙计”,稳定可靠不挑环境
串口通信是工业设备最常用的有线通信方式,核心是“波特率、数据位、停止位、奇偶校验”,适合PLC、传感器、智能电表等对稳定性要求高的场景。
大白话解释:把串口比作“工业有线对讲机”
1.波特率:说话的速度,比如9600就是每秒传9600位,双方必须一致,不然就像一个人说中文一个人说英文,完全听不懂;
2.数据位:每次说的字数,比如8位就是每次传1个字节(比如一个ASCII字符);
3.停止位:说完一句话的停顿,比如1位就是说完一个字节后停一下,告诉对方“我说完了”;
4.奇偶校验:检查有没有说错话,比如偶校验就是数据位中1的个数是偶数,错了就重发。
我踩过的坑:一开始没处理串口的DataReceived事件粘包问题,结果一次收到两条数据,导致解析错误——后来加了缓冲区和帧头帧尾判断,完美解决!
实战1:C#串口通信完整代码(SerialPort类)
步骤1:引用System.IO.Ports
csharp
// .NET Framework默认包含,.NET Core/.NET 5+需要安装NuGet包
Install-Package System.IO.Ports
步骤2:串口通信代码(逐行讲解)
csharp

	using System;
	using System.IO.Ports;
	using System.Text;
	using System.Threading.Tasks;
	
	namespace IoTCommunication.SerialPortDemo;
	
	public class SerialPortManager
	{
	private SerialPort _serialPort;
	private readonly StringBuilder _receiveBuffer = new StringBuilder(); // 接收缓冲区,处理粘包
	private const string FrameStart = "["; // 帧头,比如"[DATA]"
	private const string FrameEnd = "]"; // 帧尾
	
	// 初始化串口
	public void Initialize(string portName = "COM3", int baudRate = 9600)
	{
	try
	{
	// 1. 创建串口实例,配置参数
	_serialPort = new SerialPort(portName, baudRate, Parity.None, 8, StopBits.One)
	{
	ReadTimeout = 5000, // 读取超时5秒
	WriteTimeout = 5000, // 写入超时5秒
	DtrEnable = true, // 启用DTR(数据终端就绪),有些设备需要这个才能通信
	RtsEnable = true // 启用RTS(请求发送),有些设备需要这个才能通信
	};
	
	// 2. 注册事件回调
	_serialPort.DataReceived += SerialPort_DataReceived;
	_serialPort.ErrorReceived += SerialPort_ErrorReceived;
	
	// 3. 打开串口
	_serialPort.Open();
	Console.WriteLine($"串口 {portName} 打开成功,波特率 {baudRate}");
	}
	catch (Exception ex)
	{
	Console.WriteLine($"串口初始化失败:{ex.Message}");
	}
	}
	
	// 发送数据
	public async Task SendDataAsync(string data)
	{
	if (_serialPort == null || !_serialPort.IsOpen)
	{
	Console.WriteLine("串口未打开,无法发送数据");
	return;
	}
	
	try
	{
	// 加上帧头帧尾,方便解析
	string frame = $"{FrameStart}{data}{FrameEnd}";
	// 异步写入,不阻塞主线程
	await Task.Run(() => _serialPort.Write(frame));
	Console.WriteLine($"发送数据:{frame}");
	}
	catch (Exception ex)
	{
	Console.WriteLine($"发送数据失败:{ex.Message}");
	}
	}
	
	// 关闭串口
	public void Close()
	{
	if (_serialPort != null && _serialPort.IsOpen)
	{
	_serialPort.Close();
	_serialPort.Dispose();
	Console.WriteLine("串口已关闭");
	}
	}
	
	#region 事件回调
	// 数据接收事件
	private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
	{
	try
	{
	// 4. 读取串口缓冲区的所有数据
	string received = _serialPort.ReadExisting();
	_receiveBuffer.Append(received);
	Console.WriteLine($"原始接收数据:{received},缓冲区内容:{_receiveBuffer}");
	
	// 5. 处理粘包:按帧头帧尾分割数据
	ProcessReceivedData();
	}
	catch (Exception ex)
	{
	Console.WriteLine($"接收数据失败:{ex.Message}");
	}
	}
	
	// 错误接收事件
	private void SerialPort_ErrorReceived(object sender, SerialErrorReceivedEventArgs e)
	{
	Console.WriteLine($"串口错误:{e.EventType}");
	}
	
	// 处理接收的数据,解析完整帧
	private void ProcessReceivedData()
	{
	string buffer = _receiveBuffer.ToString();
	int startIndex = buffer.IndexOf(FrameStart);
	int endIndex = buffer.IndexOf(FrameEnd);
	
	// 循环解析所有完整帧
	while (startIndex != -1 && endIndex != -1 && endIndex > startIndex)
	{
	string frame = buffer.Substring(startIndex + FrameStart.Length, endIndex - startIndex - FrameStart.Length);
	Console.WriteLine($"解析到完整帧:{frame}");
	
	// 6. 解析帧内容,比如PLC的寄存器数据
	ParseFrame(frame);
	
	// 移除已解析的帧,更新缓冲区
	_receiveBuffer.Remove(0, endIndex + FrameEnd.Length);
	buffer = _receiveBuffer.ToString();
	startIndex = buffer.IndexOf(FrameStart);
	endIndex = buffer.IndexOf(FrameEnd);
	}
	}
	
	// 解析帧内容(示例:解析PLC的温度数据)
	private void ParseFrame(string frame)
	{
	// 假设帧格式是"PLC_TEMP:25.5"
	if (frame.StartsWith("PLC_TEMP:"))
	{
	string tempStr = frame.Substring("PLC_TEMP:".Length);
	if (double.TryParse(tempStr, out double temp))
	{
	Console.WriteLine($"PLC温度:{temp}°C");
	}
	}
	}
	#endregion
	}
	
	// 测试代码
	class Program
	{
	static async Task Main(string[] args)
	{
	var serialManager = new SerialPortManager();
	serialManager.Initialize("COM3", 9600);
	
	// 模拟每3秒发送一次请求
	var cts = new System.Threading.CancellationTokenSource();
	_ = Task.Run(async () =>
	{
	while (!cts.Token.IsCancellationRequested)
	{
	await serialManager.SendDataAsync("REQUEST_TEMP");
	await Task.Delay(3000);
	}
	}, cts.Token);
	
	Console.WriteLine("按任意键退出...");
	Console.ReadKey();
	
	cts.Cancel();
	serialManager.Close();
	}
	}

逐行拆解核心代码:
1.串口参数配置:波特率、数据位、停止位、奇偶校验必须和设备一致,不然数据乱码;
2.接收缓冲区:用StringBuilder缓存数据,处理粘包问题(一次收到多条数据);
3.帧头帧尾:自定义帧头[和帧尾],方便解析完整数据帧,避免粘包;
4.异步发送:用Task.Run包装同步的Write方法,避免阻塞主线程;
5.错误处理:注册ErrorReceived事件,处理串口错误(比如波特率不匹配、硬件故障)。
串口通信生产级优化技巧
1.自动检测波特率:遍历常用波特率(9600、19200、38400、115200),发送测试指令,根据响应判断正确的波特率;
2.CRC校验:在数据帧中加入CRC校验码,接收时验证,避免数据传输错误;
3.缓冲区大小:设置合适的ReadBufferSize和WriteBufferSize,比如4096字节,避免缓冲区溢出;
4.超时处理:设置ReadTimeout和WriteTimeout,避免无限等待;
5.硬件流控制:启用DTR/RTS或XON/XOFF流控制,适合长距离传输,避免数据丢失。
三、蓝牙通信:无线设备的“短距离小能手”,分经典蓝牙和BLE两种
蓝牙通信分两种:经典蓝牙(BR/EDR)和BLE(低功耗蓝牙),经典蓝牙适合传输大数据(比如文件、音频),BLE适合低功耗设备(比如传感器、手环)。
大白话解释:把蓝牙比作“无线对讲机”
1.经典蓝牙:像大功率对讲机,能传远一点(10米),能传大文件,但费电;
2.BLE:像小功率对讲机,传不远(5米),只能传小数据,但省电,电池能用好几年;
3.配对:像对讲机配对频道,输入PIN码或者自动配对,配对成功后才能通信。
我踩过的坑:一开始用经典蓝牙连接手环,结果手环很快没电了——后来换成BLE模式,手环电池寿命从1个月延长到6个月!
实战2:C#经典蓝牙通信(32feet.NET库)
步骤1:安装NuGet包
bash
Install-Package 32feet.NET
步骤2:经典蓝牙通信代码(RFCOMM协议)
csharp

	using System;
	using System.IO;
	using System.Threading.Tasks;
	using InTheHand.Net.Sockets;
	
	namespace IoTCommunication.BluetoothDemo;
	
	public class ClassicBluetoothManager
	{
	private BluetoothClient _client;
	private Stream _stream;
	
	// 扫描附近的蓝牙设备
	public async Task ScanDevicesAsync()
	{
	try
	{
	Console.WriteLine("正在扫描蓝牙设备...");
	// 异步扫描设备,超时10秒
	var devices = await Task.Run(() => BluetoothClient.DiscoverDevicesInRange(10));
	
	foreach (var device in devices)
	{
	Console.WriteLine($"设备名称:{device.DeviceName},地址:{device.DeviceAddress},信号强度:{device.Rssi}");
	}
	}
	catch (Exception ex)
	{
	Console.WriteLine($"扫描设备失败:{ex.Message}");
	}
	}
	
	// 连接蓝牙设备
	public async Task ConnectAsync(string deviceAddress)
	{
	try
	{
	_client = new BluetoothClient();
	var address = BluetoothAddress.Parse(deviceAddress);
	var ep = new BluetoothEndPoint(address, BluetoothService.SerialPort); // RFCOMM串口服务
	
	Console.WriteLine($"正在连接设备 {deviceAddress}...");
	await Task.Run(() => _client.Connect(ep));
	_stream = _client.GetStream();
	Console.WriteLine("蓝牙连接成功");
	
	// 异步接收数据
	_ = Task.Run(ReceiveDataAsync);
	}
	catch (Exception ex)
	{
	Console.WriteLine($"连接失败:{ex.Message}");
	}
	}
	
	// 发送数据
	public async Task SendDataAsync(string data)
	{
	if (_stream == null || !_client.Connected)
	{
	Console.WriteLine("蓝牙未连接,无法发送数据");
	return;
	}
	
	try
	{
	byte[] buffer = System.Text.Encoding.UTF8.GetBytes(data + "
");
	await _stream.WriteAsync(buffer, 0, buffer.Length);
	Console.WriteLine($"发送数据:{data}");
	}
	catch (Exception ex)
	{
	Console.WriteLine($"发送数据失败:{ex.Message}");
	}
	}
	
	// 接收数据
	private async Task ReceiveDataAsync()
	{
	byte[] buffer = new byte[1024];
	int bytesRead;
	
	while (_client.Connected)
	{
	try
	{
	bytesRead = await _stream.ReadAsync(buffer, 0, buffer.Length);
	if (bytesRead > 0)
	{
	string data = System.Text.Encoding.UTF8.GetString(buffer, 0, bytesRead);
	Console.WriteLine($"接收数据:{data}");
	}
	}
	catch (Exception ex)
	{
	Console.WriteLine($"接收数据失败:{ex.Message}");
	break;
	}
	}
	}
	
	// 断开连接
	public void Disconnect()
	{
	_stream?.Close();
	_client?.Close();
	Console.WriteLine("蓝牙已断开");
	}
	}
	
	// 测试代码
	class Program
	{
	static async Task Main(string[] args)
	{
	var btManager = new ClassicBluetoothManager();
	await btManager.ScanDevicesAsync();
	
	// 替换成扫描到的设备地址,比如"00:1A:7D:DA:71:13"
	await btManager.ConnectAsync("00:1A:7D:DA:71:13");
	await btManager.SendDataAsync("Hello Bluetooth!");
	
	Console.WriteLine("按任意键退出...");
	Console.ReadKey();
	btManager.Disconnect();
	}
	}

实战3:C# BLE通信(Plugin.BLE库,跨平台)
步骤1:安装NuGet包
bash
Install-Package Plugin.BLE
步骤2:BLE通信代码(扫描、连接、读写特征值)
csharp

	using System;
	using System.Linq;
	using System.Threading.Tasks;
	using Plugin.BLE;
	using Plugin.BLE.Abstractions.Contracts;
	using Plugin.BLE.Abstractions.EventArgs;
	
	namespace IoTCommunication.BleDemo;
	
	public class BleManager
	{
	private IAdapter _adapter;
	private IDevice _connectedDevice;
	private ICharacteristic _tempCharacteristic;
	
	// 初始化BLE适配器
	public void Initialize()
	{
	_adapter = CrossBluetoothLE.Current.Adapter;
	_adapter.DeviceDiscovered += Adapter_DeviceDiscovered;
	_adapter.ConnectTimeout = TimeSpan.FromSeconds(10);
	}
	
	// 扫描BLE设备
	public async Task ScanDevicesAsync()
	{
	try
	{
	Console.WriteLine("正在扫描BLE设备...");
	await _adapter.StartScanningForDevicesAsync();
	await Task.Delay(5000); // 扫描5秒
	await _adapter.StopScanningForDevicesAsync();
	}
	catch (Exception ex)
	{
	Console.WriteLine($"扫描设备失败:{ex.Message}");
	}
	}
	
	// 设备发现事件
	private void Adapter_DeviceDiscovered(object sender, DeviceEventArgs e)
	{
	Console.WriteLine($"BLE设备:名称={e.Device.Name},地址={e.Device.Id},信号强度={e.Device.Rssi}");
	// 过滤特定设备,比如名称包含"TempSensor"
	if (e.Device.Name?.Contains("TempSensor") == true)
	{
	_ = ConnectToDeviceAsync(e.Device);
	}
	}
	
	// 连接BLE设备
	public async Task ConnectToDeviceAsync(IDevice device)
	{
	try
	{
	Console.WriteLine($"正在连接BLE设备 {device.Name}...");
	await _adapter.ConnectToDeviceAsync(device);
	_connectedDevice = device;
	Console.WriteLine("BLE连接成功");
	
	// 发现服务和特征值
	await DiscoverServicesAsync();
	}
	catch (Exception ex)
	{
	Console.WriteLine($"连接失败:{ex.Message}");
	}
	}
	
	// 发现服务和特征值
	private async Task DiscoverServicesAsync()
	{
	var services = await _connectedDevice.GetServicesAsync();
	foreach (var service in services)
	{
	Console.WriteLine($"服务UUID:{service.Id}");
	var characteristics = await service.GetCharacteristicsAsync();
	foreach (var characteristic in characteristics)
	{
	Console.WriteLine($"特征值UUID:{characteristic.Id},可写:{characteristic.CanWrite},可读:{characteristic.CanRead},可通知:{characteristic.CanUpdate}");
	
	// 找到温度特征值(假设UUID是"00002a6e-0000-1000-8000-00805f9b34fb")
	if (characteristic.Id.ToString().Equals("00002a6e-0000-1000-8000-00805f9b34fb", StringComparison.OrdinalIgnoreCase))
	{
	_tempCharacteristic = characteristic;
	// 订阅特征值变化通知
	characteristic.ValueUpdated += Characteristic_ValueUpdated;
	await characteristic.StartUpdatesAsync();
	Console.WriteLine("已订阅温度特征值变化");
	}
	}
	}
	}
	
	// 特征值变化事件
	private void Characteristic_ValueUpdated(object sender, CharacteristicUpdatedEventArgs e)
	{
	byte[] data = e.Characteristic.Value;
	// 解析温度数据(BLE温度特征值格式是小端字节序,比如0x0000是0°C,0x0001是0.01°C)
	double temp = BitConverter.ToUInt16(data, 0) / 100.0;
	Console.WriteLine($"BLE温度:{temp}°C");
	}
	
	// 断开连接
	public async Task DisconnectAsync()
	{
	if (_connectedDevice != null)
	{
	await _adapter.DisconnectDeviceAsync(_connectedDevice);
	Console.WriteLine("BLE已断开");
	}
	}
	}
	
	// 测试代码
	class Program
	{
	static async Task Main(string[] args)
	{
	var bleManager = new BleManager();
	bleManager.Initialize();
	await bleManager.ScanDevicesAsync();
	
	Console.WriteLine("按任意键退出...");
	Console.ReadKey();
	await bleManager.DisconnectAsync();
	}
	}

蓝牙通信生产级优化技巧
1.BLE设备过滤:扫描时按名称或UUID过滤,避免扫描到无关设备;
2.特征值缓存:缓存常用特征值,避免重复发现服务和特征值;
3.连接超时:设置合理的连接超时(比如10秒),避免无限等待;
4.低功耗优化:BLE设备连接后及时断开,或者用间隔扫描,延长电池寿命;
5.配对处理:经典蓝牙配对时自动输入PIN码,BLE用自动配对,提升用户体验。
四、WiFi通信:IoT设备的“无线高速公路”,远距离大数据传输
WiFi通信是IoT设备最常用的无线通信方式,核心是“STA模式、AP模式、TCP/UDP协议”,适合智能家居、摄像头、物联网网关等需要远距离大数据传输的场景。
大白话解释:把WiFi比作“无线高速公路”
1.STA模式:设备像汽车,连路由器(高速公路入口),能上网,能和其他设备通信;
2.AP模式:设备像临时收费站,自己当热点,其他设备连它,适合没有路由器的场景;
3.TCP:像快递,可靠送达,丢了重发,适合控制指令;
4.UDP:像短信,发了就走,不保证送达,适合温湿度上报。
我踩过的坑:一开始没做WiFi重连逻辑,路由器重启后设备直接离线——后来加了定时检测信号强度,弱信号或离线时自动重连,设备在线率从80%升到99.9%!
实战4:C# WiFi通信代码(TCP客户端+UDP客户端)
步骤1:引用System.Net
csharp
// .NET默认包含,无需额外安装
步骤2:WiFi TCP客户端代码(连接MQTT Broker或服务器)
csharp

	using System;
	using System.Net.Sockets;
	using System.Text;
	using System.Threading.Tasks;
	
	namespace IoTCommunication.WiFiDemo;
	
	public class WifiTcpClient
	{
	private TcpClient _tcpClient;
	private NetworkStream _stream;
	private readonly string _serverIp = "192.168.1.100"; // 服务器IP
	private readonly int _serverPort = 1883; // MQTT端口或自定义端口
	
	// 连接TCP服务器
	public async Task ConnectAsync()
	{
	try
	{
	_tcpClient = new TcpClient();
	await _tcpClient.ConnectAsync(_serverIp, _serverPort);
	_stream = _tcpClient.GetStream();
	Console.WriteLine($"TCP连接成功:{_serverIp}:{_serverPort}");
	
	// 异步接收数据
	_ = Task.Run(ReceiveDataAsync);
	}
	catch (Exception ex)
	{
	Console.WriteLine($"TCP连接失败:{ex.Message}");
	// 重连逻辑
	await Task.Delay(5000);
	await ConnectAsync();
	}
	}
	
	// 发送TCP数据
	public async Task SendDataAsync(string data)
	{
	if (_tcpClient == null || !_tcpClient.Connected)
	{
	Console.WriteLine("TCP未连接,正在重连...");
	await ConnectAsync();
	return;
	}
	
	try
	{
	byte[] buffer = Encoding.UTF8.GetBytes(data + "
");
	await _stream.WriteAsync(buffer, 0, buffer.Length);
	Console.WriteLine($"TCP发送数据:{data}");
	}
	catch (Exception ex)
	{
	Console.WriteLine($"TCP发送失败:{ex.Message}");
	// 重连逻辑
	await ConnectAsync();
	}
	}
	
	// 接收TCP数据
	private async Task ReceiveDataAsync()
	{
	byte[] buffer = new byte[1024];
	int bytesRead;
	
	while (_tcpClient.Connected)
	{
	try
	{
	bytesRead = await _stream.ReadAsync(buffer, 0, buffer.Length);
	if (bytesRead > 0)
	{
	string data = Encoding.UTF8.GetString(buffer, 0, bytesRead);
	Console.WriteLine($"TCP接收数据:{data}");
	}
	}
	catch (Exception ex)
	{
	Console.WriteLine($"TCP接收失败:{ex.Message}");
	// 重连逻辑
	await ConnectAsync();
	break;
	}
	}
	}
	
	// 断开连接
	public void Disconnect()
	{
	_stream?.Close();
	_tcpClient?.Close();
	Console.WriteLine("TCP已断开");
	}
	}
	
	// 测试代码
	class Program
	{
	static async Task Main(string[] args)
	{
	var tcpClient = new WifiTcpClient();
	await tcpClient.ConnectAsync();
	await tcpClient.SendDataAsync("Hello WiFi TCP!");
	
	Console.WriteLine("按任意键退出...");
	Console.ReadKey();
	tcpClient.Disconnect();
	}
	}

WiFi通信生产级优化技巧
1.重连逻辑:定时检测连接状态,离线时自动重连,重连间隔指数退避(比如1秒、2秒、4秒、8秒);
2.信号强度检测:定期检测WiFi信号强度,弱信号时切换到其他WiFi或蓝牙;
3.静态IP设置:生产环境建议设置静态IP,避免DHCP分配IP变化导致设备离线;
4.数据压缩:传输大数据时压缩数据(比如Gzip),减少带宽消耗;
5.网络切换:支持WiFi和蓝牙冗余,WiFi离线时自动切换到蓝牙,保证通信不中断。
五、IoT设备通信选型指南与生产级最佳实践

  1. 选型指南
场景 推荐通信方式 原因
工业PLC、传感器 串口 稳定可靠,不挑环境,抗干扰强
低功耗传感器、手环 BLE 省电,电池寿命长,适合小数据传输
音频设备、文件传输 经典蓝牙 传输速度快,适合大数据
智能家居、摄像头 WiFi 远距离,大数据传输,能上网
没有WiFi的户外场景 LoRa/NB-IoT 远距离,低功耗,适合偏远地区
  1. 生产级最佳实践
    1.多通信方式冗余:比如设备同时支持WiFi和蓝牙,WiFi离线时自动切换到蓝牙;
    2.数据加密:串口用CRC校验,蓝牙用BLE加密或经典蓝牙的配对加密,WiFi用WPA2/WPA3加密;
    3.监控告警:监控设备的通信状态、信号强度、数据传输率,设置告警阈值(比如设备离线10分钟告警);
    4.日志记录:记录通信的错误、重连、数据传输情况,方便排查问题;
    5.固件升级:支持通过WiFi或蓝牙远程升级固件,修复bug或添加新功能。
    六、总结
    串口是工业设备的“有线老伙计”,稳定可靠;蓝牙是短距离无线通信的“小能手”,分经典蓝牙和BLE两种;WiFi是远距离大数据传输的“高速公路”,适合大多数IoT场景。实战中要根据设备的功耗、传输距离、数据量选择合适的通信方式,还要注意生产级优化,比如重连逻辑、粘包处理、数据加密,才能保证设备通信的稳定性和可靠性。
    下一节我们会学习IoT设备的固件升级,用C#实现OTA(空中升级)功能,让你的设备能远程更新固件!

转载请注明出处:https://www.xin3721.com/ArticlecSharp/c49579.html


相关教程