Project updated to V1.0 Release Candidate 1 (December 31st, 2022)
In this project, we're controlling a MAX7219 LED display to show messages over Wi-Fi or Bluetooth using a .NET MAUI application. To control it via Wi-Fi, the companion app will act as a client to send the requests and Meadow configured as a server, using Meadow.Foundation's connectivity library, Maple.
Maple makes it easy to build connected devices with Meadow by exposing control via a Web API. Maple is an open source, ultra-lightweight, JSON enabled, RESTful web server. It also has an advertise feature, meaning that you could discover Maple servers in your network by listening to UDP broadcast messages with its name and IP Address.
Meadow.Foundation a platform for quickly and easily building connected things using.NET on Meadow. Created by Wilderness Labs, it's completely open source and maintained by the Wilderness Labs community.
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 2019 for Windows or macOS and name it MeadowOnAir_Sign.
Step 3 - Add the required NuGet packagesFor this project, search and install the following NuGet packages:
Step 4 - Write the code for WifiSignLets go over each class file included in this Meadow Application:
Secrets.cs
public class Secrets
{
/// <summary>
/// Name of the WiFi network to use.
/// </summary>
public const string WIFI_NAME = "SSID";
/// <summary>
/// Password for the WiFi network names in WIFI_NAME.
/// </summary>
public const string WIFI_PASSWORD = "PASSWORD";
}
This class will be used to pass in the Network SSID and Password to join our WIFI network.
DisplayController.cs
public class DisplayController
{
private static readonly Lazy<DisplayController> instance =
new Lazy<DisplayController>(() => new DisplayController());
public static DisplayController Instance => instance.Value;
MicroGraphics graphics;
public string Text { get; protected set; }
private DisplayController()
{
Initialize();
}
private void Initialize()
{
var ledDisplay = new Max7219(
MeadowApp.Device,
MeadowApp.Device.CreateSpiBus(),
MeadowApp.Device.Pins.D00,
deviceCount: 4,
maxMode: Max7219.Max7219Mode.Display);
graphics = new MicroGraphics(ledDisplay)
{
IgnoreOutOfBoundsPixels = true,
Rotation = RotationType._90Degrees,
CurrentFont = new Font4x8()
};
}
public void ShowSplashScreen()
{
graphics.Clear();
graphics.DrawRectangle(0, 0, graphics.Width, graphics.Height);
graphics.DrawText(graphics.Width / 2, 2, "OnAir", alignmentH: HorizontalAlignment.Center);
graphics.Show();
}
public void ShowTextStatic(string text)
{
Text = text;
graphics.Clear();
graphics.DrawText(0, 1, text);
graphics.Show();
}
public void ShowText(string text)
{
int textLenInPixels = graphics.MeasureText(text).Width;
if(textLenInPixels < graphics.Width)
{
ShowTextStatic(text);
return;
}
for (int i = 0; i < textLenInPixels - graphics.Width; i++)
{
graphics.Clear();
graphics.DrawText(0 - i, 1, text);
graphics.Show();
Thread.Sleep(50);
}
}
}
This
class is used to control the Max7219 LED display. In the Initialize method, we instanciate create a Max7219 object, which in turn we pass that on to the Meadow.Foundation's GraphicsLibrary so we can easily draw text the same way we've done it in other projects where can easily draw shapes, lines and texts.
The public ShowText(string)
method draws text in a font size 4x8 pixels in the x=0 and y=1 coordinates, and it would scroll if the string passed does not fit the display.
SignControllerRequestHandler.cs
public class SignControllerRequestHandler : RequestHandlerBase
{
[HttpPost("/signtext")]
public IActionResult SignText()
{
DisplayController.Instance.ShowText(Body);
return new OkResult();
}
}
In this class we're declaring our Web API endpoints, which in this case is only signtext
GET request. We'll receive a text parameter in the query's Body message. We then invoke DisplayController's ShowText(string)
method to draw the text in the display, and finally send a code 200 (OK) response to the client with the text received.
Bluetooth.cs
public class BluetoothServer
{
private static readonly Lazy<BluetoothServer> instance =
new Lazy<BluetoothServer>(() => new BluetoothServer());
public static BluetoothServer Instance => instance.Value;
Definition bleTreeDefinition;
CharacteristicBool pairingCharacteristic;
CharacteristicString OnAirTextCharacteristic;
private BluetoothServer() { }
public void Initialize()
{
bleTreeDefinition = GetDefinition();
pairingCharacteristic.ValueSet += PairingCharacteristicValueSet;
OnAirTextCharacteristic.ValueSet += OnAirTextCharacteristicValueSet;
MeadowApp.Device.BluetoothAdapter.StartBluetoothServer(bleTreeDefinition);
}
private void PairingCharacteristicValueSet(ICharacteristic c, object data)
{
DisplayController.Instance.ShowText((bool)data ? " Paired" : " Not Paired");
}
private void OnAirTextCharacteristicValueSet(ICharacteristic c, object data)
{
DisplayController.Instance.ShowText((string)data);
}
Definition GetDefinition()
{
pairingCharacteristic = new CharacteristicBool(
name: "PAIRING",
uuid: CharacteristicsConstants.PAIRING,
permissions: CharacteristicPermission.Read | CharacteristicPermission.Write,
properties: CharacteristicProperty.Read | CharacteristicProperty.Write);
OnAirTextCharacteristic = new CharacteristicString(
name: "ON_AIR_SIGN_TEXT",
uuid: CharacteristicsConstants.ON_AIR_SIGN_TEXT,
maxLength: 20,
permissions: CharacteristicPermission.Read | CharacteristicPermission.Write,
properties: CharacteristicProperty.Read | CharacteristicProperty.Write);
var service = new Service(
name: "Service",
uuid: 253,
pairingCharacteristic,
OnAirTextCharacteristic
);
return new Definition("OnAirSign", service);
}
}
Alternatively, you we could activate Meadow Bluetooth capabilities and put it as a Server, exposing a boolean characteristic for pairing, and a string characteristic to receive incoming text values to draw on the display.
For these two characteristics, we'll need a couple of ID values, which you can generate new GUIDs, or use the ones for this project:
public static class CharacteristicsConstants
{
public const string PAIRING = "017e99d6-8a61-11eb-8dcd-0242ac1300aa";
public const string ON_AIR_SIGN_TEXT = "017e99d6-8a61-11eb-8dcd-0242ac1300bb";
}
MeadowApp.cs
public class MeadowApp : App<F7FeatherV2>
{
bool useWiFi = true;
public override async Task Initialize()
{
var onboardLed = new RgbPwmLed(
Device,
Device.Pins.OnboardLedRed,
Device.Pins.OnboardLedGreen,
Device.Pins.OnboardLedBlue);
onboardLed.SetColor(Color.Red);
DisplayController.Instance.ShowSplashScreen();
if (useWiFi)
{
var wifi = Device.NetworkAdapters.Primary<IWiFiNetworkAdapter>();
wifi.NetworkConnected += NetworkConnected;
await wifi.Connect(Secrets.WIFI_NAME, Secrets.WIFI_PASSWORD, TimeSpan.FromSeconds(45));
}
else
{
BluetoothServer.Instance.Initialize();
DisplayController.Instance.ShowText("BLE READY!!!");
}
onboardLed.SetColor(Color.Green);
}
private void NetworkConnected(INetworkAdapter sender, NetworkConnectionEventArgs args)
{
var mapleServer = new MapleServer(sender.IpAddress, 5417, true);
mapleServer.Start();
DisplayController.Instance.ShowText(sender.IpAddress.ToString());
}
}
In
our main class of the project, we first call the Initialize()
method to first initialize our DisplayController
, the onboard LED to red to indicate the app has started running and depending on useWiFi
boolean value, we either start the WIFI adapter to connect to the WIFI network, and create a MapleServer
object, passing the device's IP Address, or Initialize and start the Bluetooth server instead.
To give the user feedback that the initialize method has completed, we make our DisplayController
draw a READY string in our Max7219 display, and finally we start the Maple server so it can broadcast its information through the network and its ready to take client requests to control the display.
As we mentioned, in this project we included a .NET MAUI app that runs on Android and iOS.
The basic things we need to understand here are:
Since we have a Maple Server NuGet package that runs on our Meadow board, we also have a Maple Client package that you can install in your .NET app.
Using Maple Client
In the MainViewModel's Constructor, we can see how to create a MapleClient object:
...
signClient = new SignClient();
signClient.Servers.CollectionChanged += ServersCollectionChanged;
...
The ServersCollectionChanged event handler is triggered when the app finds Maple servers in the network.
To find Maple servers that are advertising information over the network, you invoke the following async method:
await signClient.StartScanningForAdvertisingServers();
Once it finds servers, it will trigger the CollectionChanged
event, which then the app populates a ServerModel
list, that you can select with a Picker to which one you wish to send GET requests.
Once the Maple server has been found over the network, you can use Maple client's PostAsync method to send POST requests to your Meadow over your WI-FI:
...
await client.PostAsync(SelectedServer.IpAddress, ServerPort, "SignText", TextSign);
...
Using Bluetooth
To communicate to Meadow over Bluetooth with the MAUI app, we're using plugin.BLE which makes it easy to scan for BLE devices, get the devices service along with its exposed characteristics, and write values on them which in turn it will trigger Meadow to act accordingly, which in this case, is to show the text passed on the display.
Step 6 - Run the projectRun your Meadow application along with your MAUI companion app and start changing the display text over WI-FI or Bluetooth:
The full source code of this project can be found in its own GitHub repo: WildernessLabs/OnAir_Sign (github.com)
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