Nanoframework 操作单片机蓝牙配置WIFI的案例
通过Nanoframework的蓝牙配置Wifi的名称和密码
下面是基本需要的工具
-
ESP32设备一个 需要支持蓝牙和wifi,一般情况的ESP32都支持wifi和蓝牙,当前教程使用的ESP32的接口是
Type-C
设备实物图片:

-
部署好的
ESP32的NanoFramework环境刷支持蓝牙的固件
nanoff --update --target ESP32_BLE_REV0 --serialport COM5 --fwversion 1.8.1.292 --baud 1500000 -
VS扩展安装好
NanoFramework扩展 -
准备好手机下载蓝牙调试APP,我用的是这个

-
实现通过蓝牙配置单片机的WIFI
using nanoFramework.Device.Bluetooth; using nanoFramework.Device.Bluetooth.GenericAttributeProfile; using nanoFramework.Networking; using System; using System.Diagnostics; using System.IO; using System.Text; using System.Threading; using static Gotrays.Program; namespace Gotrays { public class Program { static GattLocalCharacteristic _readCharacteristic; static GattLocalCharacteristic _readWriteCharacteristic; // Read/Write Characteristic value static byte _redValue = 128; static byte _greenValue = 128; static byte _blueValue = 128; public static void Main() { var wifi = ConfigHelper.GetWifi(); // 如果存在wifi配置尝试链接wifi if (wifi.Password != null) { try { CancellationTokenSource cs = new(60000); var success = WifiNetworkHelper.ConnectDhcp(wifi.Name, wifi.Password, requiresDateTime: true, token: cs.Token); } catch (Exception) { Debug.WriteLine("尝试链接WiFi失败"); } } // BluetoothLEServer是一个单例对象,因此获得它的实例。对象是在您第一次访问它时创建的 // 可以释放内存。 BluetoothLEServer server = BluetoothLEServer.Instance; // 给设备命名 server.DeviceName = "TokenIOT"; // 定义一些自定义uid Guid serviceUuid = new Guid("A7EEDF2C-DA87-4CB5-A9C5-5151C78B0057"); Guid readCharUuid = new Guid("A7EEDF2C-DA88-4CB5-A9C5-5151C78B0057"); Guid readStaticCharUuid = new Guid("A7EEDF2C-DA89-4CB5-A9C5-5151C78B0057"); Guid readWriteCharUuid = new Guid("A7EEDF2C-DA8A-4CB5-A9C5-5151C78B0057"); // GattServiceProvider用于创建和发布主服务定义。 // 将自动创建一个额外的设备信息服务。 GattServiceProviderResult result = GattServiceProvider.Create(serviceUuid); if (result.Error != BluetoothError.Success) { return; } GattServiceProvider serviceProvider = result.ServiceProvider; // 从提供者处创建主服务 GattLocalService service = serviceProvider.Service; #region Static read characteristic // 现在我们给服务添加一个特性 //如果读取值不会改变,那么你可以使用Static值 DataWriter sw = new DataWriter(); sw.WriteString("这是蓝牙示例1"); GattLocalCharacteristicResult characteristicResult = service.CreateCharacteristic(readStaticCharUuid, new GattLocalCharacteristicParameters() { CharacteristicProperties = GattCharacteristicProperties.Read, UserDescription = "我的静态特性", StaticValue = sw.DetachBuffer() }); if (characteristicResult.Error != BluetoothError.Success) { // An error occurred. return; } #endregion #region Create Characteristic for dynamic Reads // 对于变更为业务的数据,增加“读特性” // 我们还希望连接的客户端在值改变时得到通知,所以我们添加了notify属性 characteristicResult = service.CreateCharacteristic(readCharUuid, new GattLocalCharacteristicParameters() { CharacteristicProperties = GattCharacteristicProperties.Read | GattCharacteristicProperties.Notify, UserDescription = "我的阅读特点" }); if (characteristicResult.Error != BluetoothError.Success) { // An error occurred. return; } // 查阅我们的read特性 _readCharacteristic = characteristicResult.Characteristic; // 每次从客户端请求该值时,都会调用此事件 _readCharacteristic.ReadRequested += ReadCharacteristic_ReadRequested; #endregion #region Create Characteristic for RGB read/write characteristicResult = service.CreateCharacteristic(readWriteCharUuid, new GattLocalCharacteristicParameters() { CharacteristicProperties = GattCharacteristicProperties.Read | GattCharacteristicProperties.Write, UserDescription = "My Read/Write Characteristic" }); if (characteristicResult.Error != BluetoothError.Success) { // An error occurred. return; } // 查阅我们的read特性 _readWriteCharacteristic = characteristicResult.Characteristic; //每次从客户端请求该值时,都会调用此事件 _readWriteCharacteristic.WriteRequested += _readWriteCharacteristic_WriteRequested; _readWriteCharacteristic.ReadRequested += _readWriteCharacteristic_ReadRequested; #endregion #region Start Advertising // 一旦所有的特征已经创建,你需要广告的服务,所以 // 其他设备可以看到它。这里我们也说设备也可以连接 // 设备可以看到它。 serviceProvider.StartAdvertising(new GattServiceProviderAdvertisingParameters() { IsConnectable = true, IsDiscoverable = true }); #endregion Thread.Sleep(Timeout.Infinite); } /// <summary> /// 读取特性的事件处理程序。 /// </summary> /// <param name="sender">GattLocalCharacteristic object</param> /// <param name="ReadRequestEventArgs"></param> private static void ReadCharacteristic_ReadRequested(GattLocalCharacteristic sender, GattReadRequestedEventArgs ReadRequestEventArgs) { GattReadRequest request = ReadRequestEventArgs.GetRequest(); // Get Buffer with hour/minute/second //request.RespondWithValue(GetTimeBuffer()); } /// <summary> /// 读/写特性的读事件处理程序。 /// </summary> /// <param name="sender"></param> /// <param name="ReadRequestEventArgs"></param> private static void _readWriteCharacteristic_ReadRequested(GattLocalCharacteristic sender, GattReadRequestedEventArgs ReadRequestEventArgs) { GattReadRequest request = ReadRequestEventArgs.GetRequest(); DataWriter dw = new DataWriter(); dw.WriteByte((Byte)_redValue); dw.WriteByte((Byte)_greenValue); dw.WriteByte((Byte)_blueValue); request.RespondWithValue(dw.DetachBuffer()); Debug.WriteLine($"RGB read"); } /// <summary> /// 读写特性的写处理程序。 /// </summary> /// <param name="sender"></param> /// <param name="WriteRequestEventArgs"></param> private static void _readWriteCharacteristic_WriteRequested(GattLocalCharacteristic sender, GattWriteRequestedEventArgs WriteRequestEventArgs) { GattWriteRequest request = WriteRequestEventArgs.GetRequest(); var size = request.Value.Length; // 从缓冲区解包数据 DataReader rdr = DataReader.FromBuffer(request.Value); var buffer = new byte[request.Value.Length]; rdr.ReadBytes(buffer); // 如果Write需要响应,则响应 if (request.Option == GattWriteOption.WriteWithResponse) { request.Respond(); } // 定义设置密码格式 n:wifi名称;p:dd666666 var value = Encoding.UTF8.GetString(buffer, 0, buffer.Length); SetWifi(value); } /// <summary> /// 设置Wifi持久化 /// </summary> /// <param name="value"></param> public static void SetWifi(string value) { var wifiValue = value.Split(';'); if (wifiValue.Length > 1) { // 如果不是wifi配置则跳过 if (wifiValue[0].StartsWith("n:")) { var name = wifiValue[0].Substring(2); var password = wifiValue[1].Substring(2); if (password.Length > 0) { ConfigHelper.SetWifi(value); try { CancellationTokenSource cs = new(60000); var success = WifiNetworkHelper.ConnectDhcp(name, password, requiresDateTime: true, token: cs.Token); Debug.WriteLine("链接WIFI成功"); } catch (Exception) { Debug.WriteLine("尝试链接WiFi失败"); } } } } } public struct WifiModule { public string Name { get; set; } public string Password { get; set; } } } public class ConfigHelper { private const string WifiName = "I:\Wifi.ini"; public static void SetWifi(string value) { var buffer = Encoding.UTF8.GetBytes(value); var file = File.Exists(WifiName) ? new FileStream(WifiName, FileMode.Open, FileAccess.Write) : File.Create(WifiName); file.Write(buffer, 0, buffer.Length); file.Close(); } public static WifiModule GetWifi() { if (File.Exists(WifiName)) { var file = new FileStream(WifiName, FileMode.Open, FileAccess.Read); if (file.Length > 0) { var bytes = new byte[file.Length]; file.Read(bytes, 0, bytes.Length); file.Close(); var value = Encoding.UTF8.GetString(bytes, 0, bytes.Length); var wifiValue = value.Split(';'); if (wifiValue.Length > 1) { var name = wifiValue[0].Substring(2); var password = wifiValue[1].Substring(2); return new WifiModule { Name = name, Password = password, }; } } file.Close(); } return new WifiModule { Name = null, Password = null }; } } }
上面是所有的代码,下面将一步一步的讲解
当我们接收到信息的时候会触发_readWriteCharacteristic_WriteRequested的事件_readWriteCharacteristic_WriteRequested的代码如下,第一步先读取数据,然后接收读,在得到数据转换字符串,通过调用SetWifi方法去解析设置WIFI
GattWriteRequest request = WriteRequestEventArgs.GetRequest(); var size = request.Value.Length; // 从缓冲区解包数据 DataReader rdr = DataReader.FromBuffer(request.Value); var buffer = new byte[request.Value.Length]; rdr.ReadBytes(buffer); // 如果Write需要响应,则响应 if (request.Option == GattWriteOption.WriteWithResponse) { request.Respond(); } // 定义设置密码格式 n:wifi名称;p:dd666666 var value = Encoding.UTF8.GetString(buffer, 0, buffer.Length); SetWifi(value);
SetWifi,这里是用于解析数据格式并且处理持久化和链接WIFI,wifi的格式也是规范好的n:wifi名称;p:WiFi密码,不使用json格式从而减少包的依赖。
/// <summary> /// 设置Wifi持久化 /// </summary> /// <param name="value"></param> public static void SetWifi(string value) { var wifiValue = value.Split(';'); if (wifiValue.Length > 1) { // 如果不是wifi配置则跳过 if (wifiValue[0].StartsWith("n:")) { var name = wifiValue[0].Substring(2); var password = wifiValue[1].Substring(2); if (password.Length > 0) { ConfigHelper.SetWifi(value); try { CancellationTokenSource cs = new(60000); var success = WifiNetworkHelper.ConnectDhcp(name, password, requiresDateTime: true, token: cs.Token); Debug.WriteLine("链接WIFI成功"); } catch (Exception) { Debug.WriteLine("尝试链接WiFi失败"); } } } } }
ConfigHelper则是操作文件将wifi的配置持久化,nanoframework的文件系统默认是I,文件路径则是I:\Wifi.ini这样就可以将数据持久化了。
public class ConfigHelper { private const string WifiName = "I:\Wifi.ini"; public static void SetWifi(string value) { var buffer = Encoding.UTF8.GetBytes(value); var file = File.Exists(WifiName) ? new FileStream(WifiName, FileMode.Open, FileAccess.Write) : File.Create(WifiName); file.Write(buffer, 0, buffer.Length); file.Close(); } public static WifiModule GetWifi() { if (File.Exists(WifiName)) { var file = new FileStream(WifiName, FileMode.Open, FileAccess.Read); if (file.Length > 0) { var bytes = new byte[file.Length]; file.Read(bytes, 0, bytes.Length); file.Close(); var value = Encoding.UTF8.GetString(bytes, 0, bytes.Length); var wifiValue = value.Split(';'); if (wifiValue.Length > 1) { var name = wifiValue[0].Substring(2); var password = wifiValue[1].Substring(2); return new WifiModule { Name = name, Password = password, }; } } file.Close(); } return new WifiModule { Name = null, Password = null }; } }
下面是程序运行的效果
效果


这样就可以动态配置WIFI了,也可以用蓝牙做设备的初始化。
介绍Nanoframework
.NET nanoFramework是一个免费且开源的平台,可以用于编写针对受限嵌入式设备的托管代码应用程序。它适用于多种类型的项目,包括物联网传感器、可穿戴设备、学术概念验证、机器人技术、爱好者/创客创作甚至复杂的工业设备。它通过为嵌入式开发人员提供桌面应用程序开发人员使用的现代技术和工具,使这些平台的开发更加简单、快速和成本更低。
开发人员可以利用强大且熟悉的Microsoft Visual Studio集成开发环境和他们对.NET C#的了解,快速编写代码,无需担心微控制器的底层硬件细节。桌面.NET开发人员将感到“如在家中”,并能够在嵌入式系统开发中运用他们的技能,扩大合格的嵌入式开发人员的队伍。
它包括了.NET通用语言运行时(CLR)的精简版本,并配备了.NET基类库的子集,以及包含在.NET IoT中的最常用API,允许从.NET IoT应用程序中重用代码、数以千计的代码示例和开源项目。使用Microsoft Visual Studio,开发人员可以直接在真实硬件上部署和调试代码。
.NET nanoFramework平台在.NET Micro Framework的基础上进行了扩展,并使用了其中的一些构建模块。许多原始组件被完全重写,其他组件得到改进,还有一些组件被简单地重用。进行了大量的代码清理和改进,使.NET nanoFramework适应未来发展!
技术交流
如果你也对Nanoframework感兴趣的话可以联系wx:wk28u9123456789并且备注Nanoframework就可以加入Nanoframework中文社区交流群,由于群人数过多不能使用二维码加入敬请谅解!
来自token的分享