Project updated to V1.0 Release Candidate 1 (October 23rd, 2022)
This project is a continuation of the Meadow Rover Part 1, that shows you how to control the motors with an HBridge and four LEDs to indicate the direction it moves. In this project we're focusing on showing how to use the onboard Bluetooth Low Energy (BLE) and use a MAUI app to control the rover.
Before going further, make sure you're running the latest Meadow.OS version (b6.0) on your board, which enables the ESP's BLE capability.
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.
Write the code for Bluetooth Connectivity on MeadowMeadowApp Class
For the main MeadowApp class, we'll no longer need the TestCar
method. Copy the following code, especifically the InitializeBluetooth
method:
// public class MeadowApp : App<F7FeatherV1> <- If you have a Meadow F7v1.*
public class MeadowApp : App<F7FeatherV2>
{
Definition bleTreeDefinition;
CharacteristicBool up, down, left, right;
RgbLed led;
Led ledUp, ledDown, ledLeft, ledRight;
CarController carController;
public override Task Initialize()
{
led = new RgbLed(
Device,
Device.Pins.OnboardLedRed,
Device.Pins.OnboardLedGreen,
Device.Pins.OnboardLedBlue);
led.SetColor(RgbLedColors.Red);
ledUp = new Led(Device, Device.Pins.D13);
ledDown = new Led(Device, Device.Pins.D10);
ledLeft = new Led(Device, Device.Pins.D11);
ledRight = new Led(Device, Device.Pins.D12);
ledUp.IsOn = ledDown.IsOn = ledLeft.IsOn = ledRight.IsOn = true;
var motorLeft = new HBridgeMotor
(
device: Device,
a1Pin: Device.Pins.D07,
a2Pin: Device.Pins.D08,
enablePin: Device.Pins.D09
);
var motorRight = new HBridgeMotor
(
device: Device,
a1Pin: Device.Pins.D02,
a2Pin: Device.Pins.D03,
enablePin: Device.Pins.D04
);
carController = new CarController(motorLeft, motorRight);
led.SetColor(RgbLedColors.Blue);
bleTreeDefinition = GetDefinition();
Device.BluetoothAdapter.StartBluetoothServer(bleTreeDefinition);
up.ValueSet += UpValueSet;
down.ValueSet += DownValueSet;
left.ValueSet += LeftValueSet;
right.ValueSet += RightValueSet;
led.SetColor(RgbLedColors.Green);
return base.Initialize();
}
void UpValueSet(ICharacteristic c, object data)
{
ledUp.IsOn = (bool)data;
if ((bool)data)
carController.MoveForward();
else
carController.Stop();
}
void DownValueSet(ICharacteristic c, object data)
{
ledDown.IsOn = (bool)data;
if ((bool)data)
carController.MoveBackward();
else
carController.Stop();
}
void LeftValueSet(ICharacteristic c, object data)
{
ledLeft.IsOn = (bool)data;
if ((bool)data)
carController.TurnLeft();
else
carController.Stop();
}
void RightValueSet(ICharacteristic c, object data)
{
ledRight.IsOn = (bool)data;
if ((bool)data)
carController.TurnRight();
else
carController.Stop();
}
Definition GetDefinition()
{
up = new CharacteristicBool(
"Up",
uuid: "017e99d6-8a61-11eb-8dcd-0242ac1300aa",
permissions: CharacteristicPermission.Write | CharacteristicPermission.Read,
properties: CharacteristicProperty.Write | CharacteristicProperty.Read
);
down = new CharacteristicBool(
"Down",
uuid: "017e99d6-8a61-11eb-8dcd-0242ac1300bb",
permissions: CharacteristicPermission.Write | CharacteristicPermission.Read,
properties: CharacteristicProperty.Write | CharacteristicProperty.Read
);
left = new CharacteristicBool(
"Left",
uuid: "017e99d6-8a61-11eb-8dcd-0242ac1300cc",
permissions: CharacteristicPermission.Write | CharacteristicPermission.Read,
properties: CharacteristicProperty.Write | CharacteristicProperty.Read
);
right = new CharacteristicBool(
"Right",
uuid: "017e99d6-8a61-11eb-8dcd-0242ac1300dd",
permissions: CharacteristicPermission.Write | CharacteristicPermission.Read,
properties: CharacteristicProperty.Write | CharacteristicProperty.Read
);
var service = new Service(
name: "ServiceA",
uuid: 253,
up, down, left, right
);
return new Definition("MeadowRover", service);
}
}
In the Initialize
method, you can see how all the four LEDs are initialized as PwmLed (which we'll use to turn them on and off depending on the direction the car is going), two HBridgeMotor objects (one for each motor), and are passed to a new CarController
object.
We then create our BLE Definition tree with four boolean characteristics, one for each direction to move the rover, and we pass the bleTreeDefinition
to StartBluetoothServer
. You can read in more detail about Meadow's BLE connectivity guides in our docs.
For each boolean characteristic we set an event handler which will get triggered whenever the client (phone connected via BLE) changes the values to control the direction to move the rover. The corresponding direction will turn on an LED to give user feedback of the command its receiving.
.NET MAUI ProjectAs we mentioned, in this project we included a MAUI app that runs on iOS and Android, which we'll use to control the Meadow Rover.
To communicate to our Meadow device from our phones, we use the Plugin.BLE NuGet Package. They have a great guide on their GitHub repo to show how to do things like scanning for devices, connecting, getting services and characteristics, reading and writing values.
Some things to understand in this app:
MainViewModel
public class MainViewModel : BaseViewModel
{
IAdapter adapter;
ICharacteristic up, down, left, right;
bool isConnected;
public bool IsConnected
{
get => isConnected;
set { isConnected = value; OnPropertyChanged(nameof(IsConnected)); }
}
public MainViewModel()
{
IBluetoothLE ble = CrossBluetoothLE.Current;
adapter = CrossBluetoothLE.Current.Adapter;
adapter.ScanMode = ScanMode.LowLatency;
adapter.DeviceConnected += async (s,e) =>
{
IsConnected = true;
IDevice device = e.Device;
var services = await device.GetServicesAsync();
up = await services[2].GetCharacteristicAsync(
new Guid("017e99d6-8a61-11eb-8dcd-0242ac1300aa"));
down = await services[2].GetCharacteristicAsync(
new Guid("017e99d6-8a61-11eb-8dcd-0242ac1300bb"));
left = await services[2].GetCharacteristicAsync(
new Guid("017e99d6-8a61-11eb-8dcd-0242ac1300cc"));
right = await services[2].GetCharacteristicAsync(
new Guid("017e99d6-8a61-11eb-8dcd-0242ac1300dd"));
};
adapter.DeviceDisconnected += (s, e) =>
{
IsConnected = false;
};
}
public async Task Connect()
{
try
{
Guid guid = new Guid("00000000-0000-0000-0000-d8a01d697eaa");
await adapter.ConnectToKnownDeviceAsync(guid);
}
catch (DeviceConnectionException ex)
{
Debug.WriteLine(ex.Message);
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
public async Task MoveForward(bool go)
{
byte[] array = new byte[1];
array[0] = go ? (byte)1 : (byte)0;
await up.WriteAsync(array);
}
public async Task MoveBackward(bool go)
{
byte[] array = new byte[1];
array[0] = go ? (byte)1 : (byte)0;
await down.WriteAsync(array);
}
public async Task TurnLeft(bool go)
{
byte[] array = new byte[1];
array[0] = go ? (byte)1 : (byte)0;
await left.WriteAsync(array);
}
public async Task TurnRight(bool go)
{
byte[] array = new byte[1];
array[0] = go ? (byte)1 : (byte)0;
await right.WriteAsync(array);
}
}
The MainViewModel uses the Connect method to connect to a specific GUID which is our Meadow Rover device, and once connected, it fired the DeviceConnected event handler, which gets all the services and characteristics which would be the four booleans to move the rover around.
We also have four async Task methods to move the rover around, and in each method, we're sending a byte array with 1 or 0 to activate/deactivate the rover motors.
Step 4 - Run the projectClick the Run both the Meadow App and MAUI app and start controlling the rover using the dpad buttons. 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