Project updated to V1.0 Release Candidate 1 (October 23rd, 2022)
This project shows you how to control a MicroServo 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 circuitWire your project as shown below:
Create a new Meadow Application project in Visual Studio 2019 for Windows or macOS and name it MeadowMapleServo.
Step 3 - Add the required NuGet packagesFor this project, search and install the following NuGet packages:
Step 4 - Write the code for MeadowMapleServoLet'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.
ServoController class
public class ServoController
{
private static readonly Lazy<ServoController> instance =
new Lazy<ServoController>(() => new ServoController());
public static ServoController Instance => instance.Value;
Servo servo;
Task animationTask = null;
CancellationTokenSource cancellationTokenSource = null;
protected int _rotationAngle;
private ServoController()
{
Initialize();
}
private void Initialize()
{
servo = new Servo(
device: MeadowApp.Device,
pwmPort: MeadowApp.Device.Pins.D10,
config: NamedServoConfigs.SG90);
servo.RotateTo(NamedServoConfigs.SG90.MinimumAngle);
}
public void RotateTo(Angle angle)
{
servo.RotateTo(angle);
}
public void StopSweep()
{
cancellationTokenSource?.Cancel();
}
public void StartSweep()
{
animationTask = new Task(async () =>
{
cancellationTokenSource = new CancellationTokenSource();
await StartSweep(cancellationTokenSource.Token);
});
animationTask.Start();
}
protected async Task StartSweep(CancellationToken cancellationToken)
{
while (true)
{
if (cancellationToken.IsCancellationRequested) { break; }
while (_rotationAngle < 180)
{
if (cancellationToken.IsCancellationRequested) { break; }
_rotationAngle++;
servo.RotateTo(new Angle(_rotationAngle, Angle.UnitType.Degrees));
await Task.Delay(50);
}
while (_rotationAngle > 0)
{
if (cancellationToken.IsCancellationRequested) { break; }
_rotationAngle--;
servo.RotateTo(new Angle(_rotationAngle, Angle.UnitType.Degrees));
await Task.Delay(50);
}
}
}
}
As you can see, this class consists on the following methods:
Initialize()
- Used to setup the MicroServo and set it to its initial position by rotating it at 0 degrees.RotateTo(Angle angle)
- Makes se servo rotate at the especified angle.StartSweep()
- Makes the servo rotate back and forth to its maximum angle range.StopSweep()
- Stops the sweep motion.
ServoControllerRequestHandler class
public class ServoControllerRequestHandler : RequestHandlerBase
{
public ServoControllerRequestHandler() { }
[HttpPost("/rotateto")]
public IActionResult RotateTo()
{
int angle = int.Parse(Body);
ServoController.Instance.RotateTo(new Angle(angle, Angle.UnitType.Degrees));
return new OkResult();
}
[HttpPost("/startsweep")]
public IActionResult StartSweep()
{
ServoController.Instance.StartSweep();
return new OkResult();
}
[HttpPost("/stopsweep")]
public IActionResult StopSweep()
{
ServoController.Instance.StopSweep();
return new OkResult();
}
}
This class is used to expose all the server entry points so the Client can send Http POST requests to control the servo. Notice there's one for each method in the Servo Controller class.
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]";
}
We'll need this class to enter our WiFi credentials so Meadow can join our network.
MeadowApp class
// public class MeadowApp : App<F7FeatherV1> <- If you have a Meadow F7v1.*
public class MeadowApp : App<F7FeatherV2>
{
MapleServer mapleServer;
public override async Task Initialize()
{
var onboardLed = new RgbPwmLed(device: Device,
redPwmPin: Device.Pins.OnboardLedRed,
greenPwmPin: Device.Pins.OnboardLedGreen,
bluePwmPin: Device.Pins.OnboardLedBlue);
onboardLed.SetColor(Color.Red);
ServoController.Instance.RotateTo(new Angle(NamedServoConfigs.SG90.MinimumAngle));
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();
onboardLed.SetColor(Color.Green);
}
}
The most important part in this main class is the Initialize method, where we first initialize our ServoController, we activate our network adapter to join our network, we create a new MapleServer object with the Advertise parameter set to true, and once the onboard LED turns green, the app finally starts the maple server at the end of MeadowApp's constructor.
Thats everything we need to do on the Meadow side. Now lets take a quick look at the Xamarin companion app.
Step 5 - Understanding the MAUI AppAs 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's Constructor, we can see how to create a MapleClient object:
...
client = new MapleClient();
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 server requests.
Sending Commands to Maple server
In this ServoControllerViewModel
, the most important code section to take note is the SendServoCommand method:
async Task SendServoCommand(string command)
{
if (IsBusy)
return;
IsBusy = true;
try
{
bool response = await client.PostAsync(SelectedServer != null ? SelectedServer.IpAddress : IpAddress, ServerPort, command, AngleDegrees.ToString());
if (response)
{
IsCyclingStart = IsCyclingStop = IsRotateTo = false;
switch (command)
{
case "RotateTo": IsRotateTo = true; break;
case "StartSweep": IsCyclingStart = true; break;
case "StopSweep": IsCyclingStop = true; break;
}
}
else
{
Console.WriteLine("Request failed.");
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
IsBusy = false;
}
}
You can see that the method receives the command as a string, and we use the MapleClient object's PostAsync method to send the command by passing the Meadow's IP address, port, the command and finally the Angle value to rotate the servo if calling the RotateTo API.
Step 6 - Run the projectsClick 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.
- 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