This project will show you how to connect your Meadow to any WIFI using your phone via Bluetooth with a MAUI sample app. This project is particularly useful for those cases where you bring a Meadow board with you to a place where you dont know the WIFI credentials and you don't have your computer handy to update the app.
The takeaway of this project is to learn how to work with Meadow's configuration files. There's the common meadow.config.yaml
which you can specify certain features turned on by default immediately when the board is powered on like automatically join the network, get the UTC time and date from a certain NTP server, restart the board automatically upon any app failures, and more.
Another handy config file is wifi.config.yaml, which stores the wifi credentials that at startup, they're immediately sent to the ESP32 so the board joins the network even faster in subsequent reboots.
If you're new working with Meadow, I suggest you go to the Getting Started w/ Meadow by Controlling the Onboard RGB LEDproject to properly set up your development environment.
Step 1 - Assemble the circuitWire your project like this:
Create a new Meadow Application project in Visual Studio 2022 for Windows or macOS and name it MeadowWifi.
Step 3 - Add the required NuGet packagesFor this project, search and install the following NuGet packages:
Step 4 - Write the code for MeadowWifiCopy the following code belo
MeadowConfigFile
namespace MeadowWifi
{
public class MeadowConfigFile
{
public Device Device { get; set; } = new Device();
public Coprocessor Coprocessor { get; set; } = new Coprocessor();
public Network Network { get; set; } = new Network();
}
public class Device
{
public string Name { get; set; } = "MeadowWifi";
}
public class Coprocessor
{
public bool AutomaticallyStartNetwork { get; set; } = true;
public bool AutomaticallyReconnect { get; set; } = true;
public int MaximumRetryCount { get; set; } = 7;
}
public class Network
{
public int GetNetworkTimeAtStartup { get; set; } = 1;
public int NtpRefreshPeriod { get; set; } = 600;
public string[] NtpServers { get; set; } =
{
"0.pool.ntp.org",
"1.pool.ntp.org",
"2.pool.ntp.org",
"3.pool.ntp.org",
};
public string[] DnsServers { get; set; } =
{
"1.1.1.1",
"8.8.8.8"
};
}
}
We'll use this class to serialize in to YAML and write our config file which we'll save in Meadow's root file directory. Notice we can name our device, automatically start and join the network at startup, get time and date along with a list of NTP and DNS servers.
WifiConfigFile
namespace MeadowWifi
{
public class WifiConfigFile
{
public Credentials Credentials { get; set; }
public WifiConfigFile(string ssid, string password)
{
Credentials = new Credentials()
{
Ssid = ssid,
Password = password
};
}
}
public class Credentials
{
public string Ssid { get; set; }
public string Password { get; set; }
}
}
We'll use this class to serialize in to YAML and write our config file which we'll save in Meadow's root file directory. This class stores the Wifi credentials.
ConfigFileManager
namespace MeadowWifi
{
public static class ConfigFileManager
{
public static void CreateConfigFiles(string ssid, string password)
{
CreateMeadowConfigFile();
CreateWifiConfigFile(ssid, password);
}
public static void DeleteConfigFiles()
{
DeleteMeadowConfigFile();
DeleteWifiConfigFile();
}
private static void CreateMeadowConfigFile()
{
try
{
var configFile = new MeadowConfigFile();
var serializer = new SerializerBuilder().Build();
var yaml = serializer.Serialize(configFile);
using (var fs = File.CreateText(Path.Combine(MeadowOS.FileSystem.UserFileSystemRoot, "meadow.config.yaml")))
{
fs.WriteLine(yaml);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
private static void CreateWifiConfigFile(string ssid, string password)
{
try
{
var configFile = new WifiConfigFile(ssid, password);
var serializer = new SerializerBuilder().Build();
var yaml = serializer.Serialize(configFile);
using (var fs = File.CreateText(Path.Combine(MeadowOS.FileSystem.UserFileSystemRoot, "wifi.config.yaml")))
{
fs.WriteLine(yaml);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
private static void DeleteMeadowConfigFile()
{
try
{
File.Delete($"{MeadowOS.FileSystem.UserFileSystemRoot}meadow.config.yaml");
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
private static void DeleteWifiConfigFile()
{
try
{
File.Delete($"{MeadowOS.FileSystem.UserFileSystemRoot}wifi.config.yaml");
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
}
}
This static class handles all the config file functions:
- CreateConfigFiles - This method calls the methods to create both
meadow.config.yaml
andwifi.config.yaml
files. - DeleteConfigFiles - This method calls the methods to deletes both
meadow.config.yaml
andwifi.config.yaml
files. - CreateMeadowConfigFile - Serializes a
MeadowConfigFile
object toyaml
and saves it into Meadow's root directory. - CreateWifiConfigFile - Serializes a
WifiConfigFile
object toyaml
and saves it into Meadow's root directory. - DeleteMeadowConfigFile - Deletes the
meadow.config.yaml
file. - DeleteWifiConfigFile - Deletes the
wifi.config.yaml
file.
DisplayControllers
public class DisplayController
{
private static readonly Lazy<DisplayController> instance =
new Lazy<DisplayController>(() => new DisplayController());
public static DisplayController Instance => instance.Value;
MicroGraphics graphics;
public DisplayController()
{
Initialize();
}
void Initialize()
{
var display = new Ssd1306(
i2cBus: MeadowApp.Device.CreateI2cBus(),
displayType: Ssd130xBase.DisplayType.OLED128x64);
graphics = new MicroGraphics(display)
{
Stroke = 1,
CurrentFont = new Font8x12(),
};
UpdateStatus();
}
public void UpdateStatus()
{
graphics.Clear();
graphics.DrawText(3, 3, "Bluetooth:");
graphics.DrawText(7, 18, "Not Paired");
graphics.DrawText(3, 33, "WIFI:");
graphics.DrawText(7, 48, "Disconnected");
graphics.Show();
}
public void UpdateBluetoothStatus(string status)
{
graphics.DrawRectangle(3, 18, 122, 12, false, true);
graphics.DrawText(7, 18, status);
graphics.Show();
}
public void UpdateWifiStatus(string status)
{
graphics.DrawRectangle(3, 48, 122, 12, false, true);
graphics.DrawText(7, 48, status);
graphics.Show();
}
}
This class controls the Ssd1306 I2c display, and creates a MicroGraphics object to easily draw shapes and text.
MeadowApp
public class MeadowApp : App<F7FeatherV2>
{
readonly string SSID = "8c3bb16c0d954fb8b37999e1f040b279";
readonly string PASSWORD = "b1cb00bd69424cba937a597fabb93052";
readonly string IS_BLE_PAIRED = "8ee2de4ced7c4f2c92a1f984507d87e3";
readonly string HAS_JOINED_WIFI = "98dea8431f7443a3bf24f78650672361";
IDefinition bleTreeDefinition;
ICharacteristic Ssid;
ICharacteristic Password;
ICharacteristic IsBlePaired;
ICharacteristic HasJoinedWifi;
string ssid;
string password;
IWiFiNetworkAdapter wifi;
RgbPwmLed onboardLed;
public override Task Initialize()
{
onboardLed = new RgbPwmLed(
redPwmPin: Device.Pins.OnboardLedRed,
greenPwmPin: Device.Pins.OnboardLedGreen,
bluePwmPin: Device.Pins.OnboardLedBlue);
onboardLed.StartPulse(Color.Red);
wifi = Device.NetworkAdapters.Primary<IWiFiNetworkAdapter>();
wifi.NetworkConnected += WifiNetworkConnected;
wifi.NetworkDisconnected += WifiNetworkDisconnected;
bleTreeDefinition = GetDefinition();
Device.BluetoothAdapter.StartBluetoothServer(bleTreeDefinition);
Ssid.ValueSet += (s, e) => { ssid = (string) e; };
Password.ValueSet += (s, e) => { password = (string) e; };
IsBlePaired.ValueSet += (s, e) =>
{
DisplayController.Instance.UpdateBluetoothStatus((bool)e
? "Paired"
: "Not Paired");
};
HasJoinedWifi.ValueSet += async (s, e) =>
{
onboardLed.StartPulse(Color.Yellow);
if ((bool) e)
{
DisplayController.Instance.UpdateWifiStatus("Connecting");
await wifi.Connect(ssid, password, TimeSpan.FromSeconds(45));
if (wifi.IsConnected)
{
ConfigFileManager.CreateConfigFiles(ssid, password);
}
}
else
{
DisplayController.Instance.UpdateWifiStatus("Disconnecting");
await wifi.Disconnect(false);
}
};
DisplayController.Instance.UpdateStatus();
onboardLed.StartPulse(Color.Green);
return base.Initialize();
}
private void WifiNetworkConnected(INetworkAdapter sender, NetworkConnectionEventArgs args)
{
HasJoinedWifi.SetValue(true);
DisplayController.Instance.UpdateWifiStatus("Connected");
onboardLed.StartPulse(Color.Magenta);
}
private void WifiNetworkDisconnected(INetworkAdapter sender)
{
HasJoinedWifi.SetValue(false);
ConfigFileManager.DeleteConfigFiles();
DisplayController.Instance.UpdateBluetoothStatus("Not Paired");
DisplayController.Instance.UpdateWifiStatus("Disconnected");
onboardLed.StartPulse(Color.Cyan);
Device.PlatformOS.Reset();
}
Definition GetDefinition()
{
var service = new Service(
name: "MeadowWifiService",
uuid: 253,
Ssid = new CharacteristicString(
name: nameof(Ssid),
uuid: SSID,
permissions: CharacteristicPermission.Read | CharacteristicPermission.Write,
properties: CharacteristicProperty.Read | CharacteristicProperty.Write,
maxLength: 256),
Password = new CharacteristicString(
name: nameof(Password),
uuid: PASSWORD,
permissions: CharacteristicPermission.Read | CharacteristicPermission.Write,
properties: CharacteristicProperty.Read | CharacteristicProperty.Write,
maxLength: 256),
IsBlePaired = new CharacteristicBool(
name: nameof(IsBlePaired),
uuid: IS_BLE_PAIRED,
permissions: CharacteristicPermission.Read | CharacteristicPermission.Write,
properties: CharacteristicProperty.Read | CharacteristicProperty.Write),
HasJoinedWifi = new CharacteristicBool(
name: nameof(HasJoinedWifi),
uuid: HAS_JOINED_WIFI,
permissions: CharacteristicPermission.Read | CharacteristicPermission.Write,
properties: CharacteristicProperty.Read | CharacteristicProperty.Write)
);
return new Definition("MeadowWifi", service);
}
}
In this class, the initialize method does the follow:
- Initialize - First it initializes the RGB LED, and it turns on in Red color to indicate the app has started. Next we get the board's onboard WiFi adapter and register the events when its connected (WifiNetworkConnected) and disconnected (WifiNetworkDisconnected) to a network. After we setup the Bluetooth server, we do a first draw on the Display and finally turn the onboard LED green to indicate the app has finish initializing all the peripherals
- WifiNetworkConnected - When joining the network, it will write
true
to theHasJoinedWifi
Bluetooth characteristic to notify the MAUI app, update the Wifi status on the display and the onboard LED will pulse in a yellow color. - WifiNetworkDisconnected - When disconnecting from the network, it will write
false
to theHasJoinedWifi
Bluetooth characteristic to notify the MAUI app, delete the configuration files, update BLE and WIFI status on the display and reset the board programmatically.
As we mentioned, in this project we included a MAUI app available on our GitHub repo that runs on iOS and Android.
The basic things we need to understand here are:
If you check their official GitHub docs, you'll see how to add the required permissions in the Android's manifest file and iOS's info.plist file, how to scan for devices and handle the event of discovered devices.
If you check the BaseViewModel
class, in the AdapterDeviceDiscovered
event handler we have:
async void AdapterDeviceDiscovered(object sender, DeviceEventArgs e)
{
if (DeviceList.FirstOrDefault(x => x.Name == e.Device.Name) == null &&
!string.IsNullOrEmpty(e.Device.Name))
{
DeviceList.Add(e.Device);
}
if (e != null &&
e.Device != null &&
!string.IsNullOrEmpty(e.Device.Name) &&
e.Device.Name.Contains("Meadow"))
{
await adapter.StopScanningForDevicesAsync();
IsDeviceListEmpty = false;
DeviceSelected = e.Device;
}
}
In every device discovered, if its named Meadow
, it'll be set as DeviceSelected
, which you'll see it selected automatically in the device Picker.
Connecting/Disconnecting to Meadow
Once Meadow is selected, you can now tap on the connect/disconnect toggle button on the right next to the search for devices button.
async Task ToggleConnection()
{
try
{
if (IsBlePaired)
{
await CharacteristicIsBlePaired.WriteAsync(FALSE);
await adapter.DisconnectDeviceAsync(DeviceSelected);
IsBlePaired = false;
}
else
{
await adapter.ConnectToDeviceAsync(DeviceSelected);
IsBlePaired = true;
}
}
catch (DeviceConnectionException ex)
{
Debug.WriteLine(ex.Message);
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
Once connected to the Meadow board, IsBlePaired
property becomes true
, and you'll see the connected icon.
When running both the MAUI app and Meadow app, once connected successfully, you should be enter the WiFi's credentials and when tapping the connect button, in about 30s or so your board should connect to the network, updating the display and the onboard LED pulsing a Magenta color.
Once the board is connected, if its turned off or resets, it will pick up the stored configuration files to automatically rejoin the network without needing to reconfigure it over Bluetooth.
While connected, if you tap Disconnect, it will delete the configuration files and the board will reset, restarting the application, ready to pair via Bluetooth to enter WIFI credentials.
Check out Meadow.Foundation!This project is only the tip of the iceberg in terms of the extensive exciting things you can do with Meadow.Foundation.
- It comes with a huge peripheral driver library with drivers for the most common sensors and peripherals.
- The peripheral drivers encapsulate the core logic and expose a simple, clean, modern API.
- This project is backed by a growing community that is constantly working on building cool connected things and are always excited to help new-comers and discuss new projects.
Comments