Project updated to V1.0 Release Candidate 1 (October 23rd, 2022)
This project shows you how to control an RGB LED with a MAUI App by sending http requests to Meadow running as a server using 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 LED project to properly set up your development environment.
Step 1 - Assemble the circuitConnect an RGB LED as shown below. Optionally, you can use Meadow's on-board RGB LED.
Create a new Meadow Application project in Visual Studio 2019 for Windows or macOS and name it MeadowMapleLed.
Step 3 - Add the required NuGet packagesFor this project, search and install the following NuGet packages:
Step 4 - Write the code for MeadowMapleLedLet's go over each class to build this project:
It is a good practice to create a Controller class for every peripheral involved to make the project more scalable, maintainable and cleaner. These Controller classes abstracts all the peripheral's logic so the main program logic will remain cleaner and easier to understand.
LedController class
public class LedController
{
private static readonly Lazy<LedController> instance =
new Lazy<LedController>(() => new LedController());
public static LedController Instance => instance.Value;
RgbPwmLed rgbPwmLed;
Task animationTask = null;
CancellationTokenSource cancellationTokenSource = null;
private LedController()
{
Initialize();
}
private void Initialize()
{
rgbPwmLed = new RgbPwmLed(
device: MeadowApp.Device,
redPwmPin: MeadowApp.Device.Pins.D12,
greenPwmPin: MeadowApp.Device.Pins.D11,
bluePwmPin: MeadowApp.Device.Pins.D10);
}
void Stop()
{
rgbPwmLed.Stop();
cancellationTokenSource?.Cancel();
}
public void SetColor(Color color)
{
Stop();
rgbPwmLed.SetColor(color);
}
public void TurnOn()
{
Stop();
rgbPwmLed.SetColor(GetRandomColor());
rgbPwmLed.IsOn = true;
}
public void TurnOff()
{
Stop();
rgbPwmLed.IsOn = false;
}
public void StartBlink()
{
Stop();
rgbPwmLed.StartBlink(GetRandomColor());
}
public void StartPulse()
{
Stop();
rgbPwmLed.StartPulse(GetRandomColor());
}
public void StartRunningColors()
{
rgbPwmLed.Stop();
animationTask = new Task(async () =>
{
cancellationTokenSource = new CancellationTokenSource();
await StartRunningColors(cancellationTokenSource.Token);
});
animationTask.Start();
}
protected async Task StartRunningColors(CancellationToken cancellationToken)
{
while (true)
{
if (cancellationToken.IsCancellationRequested)
{
break;
}
rgbPwmLed.SetColor(GetRandomColor());
await Task.Delay(1000);
}
}
protected Color GetRandomColor()
{
var random = new Random();
return Color.FromHsba(random.NextDouble(), 1, 1);
}
}
This class exposes almost all the driver methods of the RgbPwmLed driver has, but with small tweaks like when doing a blink or pulse animation, it will do it with a random color from the GetRandomColor() function, and we include a StartRunningColors method, which cycles to random colors every 500 miliseconds.
LedControllerRequestHandler class
public class LedControllerRequestHandler : RequestHandlerBase
{
public LedControllerRequestHandler() { }
[HttpPost("/turnon")]
public IActionResult TurnOn()
{
LedController.Instance.TurnOn();
return new OkResult();
}
[HttpPost("/turnoff")]
public IActionResult TurnOff()
{
LedController.Instance.TurnOff();
return new OkResult();
}
[HttpPost("/startblink")]
public IActionResult StartBlink()
{
LedController.Instance.StartBlink();
return new OkResult();
}
[HttpPost("/startpulse")]
public IActionResult StartPulse()
{
LedController.Instance.StartPulse();
return new OkResult();
}
[HttpPost("/startrunningcolors")]
public IActionResult StartRunningColors()
{
LedController.Instance.StartRunningColors();
return new OkResult();
}
}
In this RequestHandler class we're exposing five URL endpoints: /TurnOn, /TurnOff, /StartBlink, /StartPulse
and /StartRunningColors
. When those API methods are invoked, we call the corresponding method in our LedController class, and we send a response message back to the client to confirm that the request has been received successfully,
Secrets class
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.
MeadowApp class
// public class MeadowApp : App<F7Micro, MeadowApp> <- If you have a Meadow F7 v1.*
public class MeadowApp : App<F7FeatherV2>
{
MapleServer mapleServer;
public override async Task Initialize()
{
LedController.Instance.SetColor(Color.Red);
var wifi = Device.NetworkAdapters.Primary<IWiFiNetworkAdapter>();
var connectionResult = await wifi.Connect(Secrets.WIFI_NAME, Secrets.WIFI_PASSWORD, TimeSpan.FromSeconds(45));
if (connectionResult.ConnectionStatus != ConnectionStatus.Success)
{
throw new Exception($"Cannot connect to network: {connectionResult.ConnectionStatus}");
}
mapleServer = new MapleServer(wifi.IpAddress, 5417, true, logger: Resolver.Log);
mapleServer.Start();
LedController.Instance.SetColor(Color.Green);
}
}
Initialize
() setups the LedController
, the WIFI adapter and join the network with the SSID and password credentials from the Secrets.cs class, and create a Maple object passing Meadow's IP Address, port number and broadcast boolean to true so our app is able to find it over the network.
We start Maple server when doing maple.Start(),
and we finally set the LED to green to indicate us that our project is ready and broadcasting Maple's IP address over the network.
As we mentioned, in this project we included a MAUI app that runs on iOS, Android and Windows.
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 BaseViewModel
Constructor, we can see how to create a MapleClient
object:
...
client = new ConnectedClient();
client.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 simply call:
await client.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.
Maple Client class extension
In this companion app project, consider the following class:
public class ConnectedClient : MapleClient
{
public ConnectedClient(int listenPort = 17756, int listenTimeout = 5000) :
base(listenPort, listenTimeout)
{ }
public async Task<bool> SendCommand(ServerModel server, int serverPort, string command)
{
return (await SendCommandAsync(command, $"{server.IpAddress}:{serverPort}"));
}
}
MapleClient NuGet package also has a SendCommand
that we can use to send POST requests directly to the Maple Server. It only needs to send the server object, along with the serverPort and the command string that needs to match the server's POST entry points. You can see these commands in the LedControllerViewModel
, in the SendLedCommand
method:
async Task SendLedCommand(string command)
{
if (IsBusy || string.IsNullOrEmpty(IpAddress))
return;
IsBusy = true;
try
{
bool response = await client.PostAsync(SelectedServer != null ? SelectedServer.IpAddress : IpAddress, ServerPort, command, string.Empty);
if (response)
{
IsOn = IsOff = IsBlinking = IsPulsing = IsRunningColors = false;
switch (command)
{
case "TurnOn": IsOn = true; break;
case "TurnOff": IsOff = true; break;
case "StartBlink": IsBlinking = true; break;
case "StartPulse": IsPulsing = true; break;
case "StartRunningColors": IsRunningColors = true; break;
}
}
else
{
Console.WriteLine("Request failed.");
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
IsBusy = false;
}
}
These commands strings are MAUI Command Parameters in the LedControllerPage
XAML code.
Click the Run button in Visual Studio. It should look like to the following GIF:
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