Configuring WiFi via Bluetooth using NanoFramework on Microcontrollers

Configuring WiFi via Bluetooth using NanoFramework on Microcontrollers

Using Nanoframework to configure the WiFi name and password via Bluetooth

Here are the basic tools needed

  1. One ESP32 device that needs to support Bluetooth and WiFi. Generally, ESP32 supports both WiFi and Bluetooth. The ESP32 interface used in this tutorial is Type-C. Here is the physical image of the device:
Configuring WiFi via Bluetooth using NanoFramework on Microcontrollers
  1. Deploy the ESP32 with the NanoFramework environment

    Flash the firmware that supports Bluetooth

    nanoff --update --target ESP32_BLE_REV0 --serialport COM5 --fwversion 1.8.1.292 --baud 1500000
    
  2. Install the NanoFramework extension in VS

  3. Prepare to download a Bluetooth debugging APP on your phone, I used this one

Configuring WiFi via Bluetooth using NanoFramework on Microcontrollers

Realize WiFi Configuration via Bluetooth on the Microcontroller

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();

            // If WiFi configuration exists, attempt to connect to 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("Failed to connect to WiFi");
                }

            }
            // BluetoothLEServer is a singleton object, so get its instance. The object is created when you first access it
            // and can be released from memory.
            BluetoothLEServer server = BluetoothLEServer.Instance;

            // Name the device
            server.DeviceName = "TokenIOT";

            // Define some custom UIDs
            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 is used to create and publish the main service definition.
            // An additional device information service will be automatically created.
            GattServiceProviderResult result = GattServiceProvider.Create(serviceUuid);
            if (result.Error != BluetoothError.Success)
            {
                return;
            }

            GattServiceProvider serviceProvider = result.ServiceProvider;

            // Create the main service from the provider
            GattLocalService service = serviceProvider.Service;

            #region Static read characteristic
            // Now we add a characteristic to the service

            // If the read value does not change, you can use a static value
            DataWriter sw = new DataWriter();
            sw.WriteString("This is Bluetooth example 1");

            GattLocalCharacteristicResult characteristicResult = service.CreateCharacteristic(readStaticCharUuid,
                 new GattLocalCharacteristicParameters()
                 {
                     CharacteristicProperties = GattCharacteristicProperties.Read,
                     UserDescription = "My Static Characteristic",
                     StaticValue = sw.DetachBuffer()
                 });

            if (characteristicResult.Error != BluetoothError.Success)
            {
                // An error occurred.
                return;
            }
            #endregion

            #region Create Characteristic for dynamic Reads 

            // For data that changes with business, add a "read characteristic"
            // We also want the connected client to be notified when the value changes, so we add the notify property
            characteristicResult = service.CreateCharacteristic(readCharUuid,
                new GattLocalCharacteristicParameters()
                {
                    CharacteristicProperties = GattCharacteristicProperties.Read | GattCharacteristicProperties.Notify,
                    UserDescription = "My Read Characteristic"
                });

            if (characteristicResult.Error != BluetoothError.Success)
            {
                // An error occurred.
                return;
            }

            // Refer to our read characteristic
            _readCharacteristic = characteristicResult.Characteristic;

            // This event is called each time the client requests this value
            _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;
            }

            // Refer to our read characteristic
            _readWriteCharacteristic = characteristicResult.Characteristic;

            // This event is called each time the client requests this value
            _readWriteCharacteristic.WriteRequested += _readWriteCharacteristic_WriteRequested;
            _readWriteCharacteristic.ReadRequested += _readWriteCharacteristic_ReadRequested;

            #endregion

            #region Start Advertising

            // Once all characteristics have been created, you need to advertise the service so
            // that other devices can see it. Here we also say the device is connectable
            // The device can be seen.
            serviceProvider.StartAdvertising(new GattServiceProviderAdvertisingParameters()
            {
                IsConnectable = true,
                IsDiscoverable = true
            });

            #endregion 

            Thread.Sleep(Timeout.Infinite);
        }

        /// <summary>
        /// Event handler for reading characteristics.
        /// </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>
        /// Read event handler for read/write characteristic.
        /// </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>
        /// Write event handler for read/write characteristic.
        /// </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;
            // Unpack data from buffer
            DataReader rdr = DataReader.FromBuffer(request.Value);
            var buffer = new byte[request.Value.Length];
            rdr.ReadBytes(buffer);

            // If Write requires a response, respond
            if (request.Option == GattWriteOption.WriteWithResponse)
            {
                request.Respond();
            }

            // Define the format for setting password n:wifi name;p:dd666666
            var value = Encoding.UTF8.GetString(buffer, 0, buffer.Length);
            SetWifi(value);
        }

        /// <summary>
        /// Set WiFi persistence
        /// </summary>
        /// <param name="value"></param>
        public static void SetWifi(string value)
        {
            var wifiValue = value.Split(';');
            if (wifiValue.Length > 1)
            {
                // Skip if not a WiFi configuration
                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("Successfully connected to WiFi");
                        }
                        catch (Exception)
                        {
                            Debug.WriteLine("Failed to connect to 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 };
        }
    }
}

Above is all the code, below will explain step by step

When we receive information, the _readWriteCharacteristic_WriteRequested event will be triggered. The code for _readWriteCharacteristic_WriteRequested is as follows: first read the data, then receive the read, convert the data into a string, and call the SetWifi method to parse and set the WiFi.

            GattWriteRequest request = WriteRequestEventArgs.GetRequest();
            var size = request.Value.Length;
            // Unpack data from buffer
            DataReader rdr = DataReader.FromBuffer(request.Value);
            var buffer = new byte[request.Value.Length];
            rdr.ReadBytes(buffer);

            // If Write requires a response, respond
            if (request.Option == GattWriteOption.WriteWithResponse)
            {
                request.Respond();
            }

            // Define the format for setting password n:wifi name;p:dd666666
            var value = Encoding.UTF8.GetString(buffer, 0, buffer.Length);
            SetWifi(value);

SetWifi is used to parse the data format and handle persistence and connection to WIFI. The format of the WiFi is also standardized as n:wifi name;p:WiFi password, not using JSON format to reduce package dependencies.

        /// <summary>
        /// Set WiFi persistence
        /// </summary>
        /// <param name="value"></param>
        public static void SetWifi(string value)
        {
            var wifiValue = value.Split(';');
            if (wifiValue.Length > 1)
            {
                // Skip if not a WiFi configuration
                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("Successfully connected to WiFi");
                        }
                        catch (Exception)
                        {
                            Debug.WriteLine("Failed to connect to WiFi");
                        }
                    }
                }

            }

        }

ConfigHelper handles the file to persist the WiFi configuration. The nanoframework file system defaults to I, and the file path is I:\Wifi.ini, which allows for data persistence.

    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 };
        }
    }

Below is the effect of the program running

Effect

Configuring WiFi via Bluetooth using NanoFramework on Microcontrollers
Configuring WiFi via Bluetooth using NanoFramework on Microcontrollers

This allows for dynamic WiFi configuration and can also use Bluetooth for device initialization.

Introduction to NanoFramework

.NET nanoFramework is a free and open-source platform for writing managed code applications for constrained embedded devices. It is suitable for various types of projects, including IoT sensors, wearable devices, academic proof of concepts, robotics, hobbyist/maker creations, and even complex industrial devices. It simplifies, speeds up, and reduces the cost of development for these platforms by providing embedded developers with modern technologies and tools used by desktop application developers.

Developers can leverage the powerful and familiar Microsoft Visual Studio Integrated Development Environment and their knowledge of .NET C# to quickly write code without worrying about the underlying hardware details of microcontrollers. Desktop .NET developers will feel “at home” and be able to apply their skills in embedded system development, expanding the pool of qualified embedded developers.

It includes a trimmed-down version of the .NET Common Language Runtime (CLR) and a subset of the .NET Base Class Library, along with the most commonly used APIs included in .NET IoT, allowing code reuse from .NET IoT applications, thousands of code examples, and open-source projects. Using Microsoft Visual Studio, developers can deploy and debug code directly on real hardware.

The .NET nanoFramework platform is built upon the .NET Micro Framework and extends it, using some of its building blocks. Many original components have been completely rewritten, others have been improved, and some have been simply reused. A significant amount of code cleanup and improvements have been made to adapt .NET nanoFramework for future development!

Technical Communication

If you are also interested in Nanoframework, you can contact wx: wk28u9123456789 and note Nanoframework to join the Nanoframework Chinese community group. Due to the large number of group members, QR code joining is not available, please understand!

Shared by Token

Leave a Comment