(Project updated to v1.1 (released on August 1st, 2023)
This project is an introduction to our awesome meadow-powered rapid prototyping board, Project Lab. In this guide, we're going to show you how you can use it with its own NuGet package that encapsulates the wiring of all its peripherals, letting you just focus on building your IoT Meadow solutions right away.
Before you dive in this project, ensure that your Meadow board and development environment is fully up to date. Check the Release Notes section to double check.
Step 1 - Create a Meadow Application projectCreate a new Meadow Application project in Visual Studio 2022 for Windows or macOS and name it ProjLab_Demo.
Step 2 - Add the required NuGet packagesFor this project, search and install the following NuGet package:
Step 3 - Write the code for ProjLab_DemoDisplayController.cs
Copy the following code below:
public class DisplayController
{
readonly MicroGraphics graphics;
public (Temperature? Temperature, RelativeHumidity? Humidity, Pressure? Pressure, Resistance? GasResistance)? AtmosphericConditions
{
get => atmosphericConditions;
set
{
atmosphericConditions = value;
Update();
}
}
(Temperature? Temperature, RelativeHumidity? Humidity, Pressure? Pressure, Resistance? GasResistance)? atmosphericConditions;
public Illuminance? LightConditions
{
get => lightConditions;
set
{
lightConditions = value;
Update();
}
}
Illuminance? lightConditions;
public (Acceleration3D? Acceleration3D, AngularVelocity3D? AngularVelocity3D, Temperature? Temperature) AccelerationConditions
{
get => accelerationConditions;
set
{
accelerationConditions = value;
Update();
}
}
(Acceleration3D? Acceleration3D, AngularVelocity3D? AngularVelocity3D, Temperature? Temperature) accelerationConditions;
public bool UpButtonState
{
get => upButtonState;
set
{
upButtonState = value;
Update();
}
}
bool upButtonState = false;
public bool DownButtonState
{
get => downButtonState;
set
{
downButtonState = value;
Update();
}
}
bool downButtonState = false;
public bool LeftButtonState
{
get => leftButtonState;
set
{
leftButtonState = value;
Update();
}
}
bool leftButtonState = false;
public bool RightButtonState
{
get => rightButtonState;
set
{
rightButtonState = value;
Update();
}
}
bool rightButtonState = false;
bool isUpdating = false;
bool needsUpdate = false;
public DisplayController(IGraphicsDisplay display)
{
graphics = new MicroGraphics(display)
{
CurrentFont = new Font12x16()
};
graphics.Clear(true);
}
public void Update()
{
if (isUpdating)
{ //queue up the next update
needsUpdate = true;
return;
}
isUpdating = true;
graphics.Clear();
Draw();
graphics.Show();
isUpdating = false;
if (needsUpdate)
{
needsUpdate = false;
Update();
}
}
void DrawStatus(string label, string value, Color color, int yPosition)
{
graphics.DrawText(x: 2, y: yPosition, label, color: color);
graphics.DrawText(x: 238, y: yPosition, value, alignmentH: HorizontalAlignment.Right, color: color);
}
void Draw()
{
graphics.DrawText(x: 2, y: 0, "Hello PROJ LAB!", WildernessLabsColors.AzureBlue);
if (AtmosphericConditions is { } conditions)
{
if (conditions.Temperature is { } temp)
{
DrawStatus("Temperature:", $"{temp.Celsius:N1}C", WildernessLabsColors.GalleryWhite, 35);
}
if (conditions.Pressure is { } pressure)
{
DrawStatus("Pressure:", $"{pressure.StandardAtmosphere:N1}atm", WildernessLabsColors.GalleryWhite, 55);
}
if (conditions.Humidity is { } humidity)
{
DrawStatus("Humidity:", $"{humidity.Percent:N1}%", WildernessLabsColors.GalleryWhite, 75);
}
}
if (LightConditions is { } light)
{
DrawStatus("Lux:", $"{light:N0}Lux", WildernessLabsColors.GalleryWhite, 95);
}
if (AccelerationConditions is { } acceleration)
{
if (acceleration.Acceleration3D is { } accel3D)
{
DrawStatus("Accel:", $"{accel3D.X.Gravity:0.#},{accel3D.Y.Gravity:0.#},{accel3D.Z.Gravity:0.#}g", WildernessLabsColors.AzureBlue, 115);
}
if (acceleration.AngularVelocity3D is { } angular3D)
{
DrawStatus("Gyro:", $"{angular3D.X:0},{angular3D.Y:0},{angular3D.Z:0}rpm", WildernessLabsColors.AzureBlue, 135);
}
}
DrawStatus("Left:", $"{(LeftButtonState ? "pressed" : "released")}", WildernessLabsColors.ChileanFire, 200);
DrawStatus("Down:", $"{(DownButtonState ? "pressed" : "released")}", WildernessLabsColors.ChileanFire, 180);
DrawStatus("Up:", $"{(UpButtonState ? "pressed" : "released")}", WildernessLabsColors.ChileanFire, 160);
DrawStatus("Right:", $"{(RightButtonState ? "pressed" : "released")}", WildernessLabsColors.ChileanFire, 220);
}
}
The purpose of this class is to control and display information from the Project Lab's onboard peripherals into its 240x240 display using MicroGraphics
. We have the following properties:
AtmosphericConditions
- This tuple stores the value returned by the BME688 atmospheric sensor, capable measuring temperature, humidity, pressure and gas resistance.LightConditions
- Stores the illuminance value from the BH1750 light sensor.AccelerationConditions
- Another tuple that stores the 3D acceleration, 3D angular velocity and even temperature given by the BMI270 sensor.UpButtonState
- boolean that stores the state of the up button.DownButtonState
- boolean that stores the state of the down button.LeftButtonState
- boolean that stores the state of the left button.RightButtonState
- boolean that stores the state of the right push button.
Notice that in the setter of all these properties it will call the Update()
method which will run the logic to update the values on the display using MicroGraphics.
MeadowApp.cs
Copy the following code below:
// Change F7FeatherV2 to F7FeatherV1 for V1.x boards
public class MeadowApp : App<F7FeatherV2>
{
DisplayController displayController;
RgbPwmLed onboardLed;
IProjectLabHardware projLab;
public override Task Initialize()
{
Resolver.Log.Loglevel = Meadow.Logging.LogLevel.Trace;
Resolver.Log.Info("Initialize hardware...");
//==== RGB LED
Resolver.Log.Info("Initializing onboard RGB LED");
onboardLed = new RgbPwmLed(
redPwmPin: Device.Pins.OnboardLedRed,
greenPwmPin: Device.Pins.OnboardLedGreen,
bluePwmPin: Device.Pins.OnboardLedBlue,
CommonType.CommonAnode);
Resolver.Log.Info("RGB LED up");
//==== instantiate the project lab hardware
projLab = ProjectLab.Create();
Resolver.Log.Info($"Running on ProjectLab Hardware {projLab.RevisionString}");
//---- display controller (handles display updates)
if (projLab.Display is { } display)
{
Resolver.Log.Trace("Creating DisplayController");
displayController = new DisplayController(display);
Resolver.Log.Trace("DisplayController up");
}
//---- BH1750 Light Sensor
if (projLab.LightSensor is { } bh1750)
{
bh1750.Updated += Bh1750Updated;
}
//---- BME688 Atmospheric sensor
if (projLab.EnvironmentalSensor is { } bme688)
{
bme688.Updated += Bme688Updated;
}
//---- BMI270 Accel/IMU
if (projLab.MotionSensor is { } bmi270)
{
bmi270.Updated += Bmi270Updated;
}
//---- buttons
if (projLab.RightButton is { } rightButton)
{
rightButton.PressStarted += (s, e) => displayController.RightButtonState = true;
rightButton.PressEnded += (s, e) => displayController.RightButtonState = false;
}
if (projLab.DownButton is { } downButton)
{
downButton.PressStarted += (s, e) => displayController.DownButtonState = true;
downButton.PressEnded += (s, e) => displayController.DownButtonState = false;
}
if (projLab.LeftButton is { } leftButton)
{
leftButton.PressStarted += (s, e) => displayController.LeftButtonState = true;
leftButton.PressEnded += (s, e) => displayController.LeftButtonState = false;
}
if (projLab.UpButton is { } upButton)
{
upButton.PressStarted += (s, e) => displayController.UpButtonState = true;
upButton.PressEnded += (s, e) => displayController.UpButtonState = false;
}
//---- heartbeat
onboardLed.StartPulse(WildernessLabsColors.PearGreen);
Resolver.Log.Info("Initialization complete");
return base.Initialize();
}
public override Task Run()
{
Resolver.Log.Info("Run...");
//---- BH1750 Light Sensor
if (projLab.LightSensor is { } bh1750)
{
bh1750.StartUpdating(TimeSpan.FromSeconds(5));
}
//---- BME688 Atmospheric sensor
if (projLab.EnvironmentalSensor is { } bme688)
{
bme688.StartUpdating(TimeSpan.FromSeconds(5));
}
//---- BMI270 Accel/IMU
if (projLab.MotionSensor is { } bmi270)
{
bmi270.StartUpdating(TimeSpan.FromSeconds(5));
}
if (displayController != null)
{
displayController.Update();
}
Resolver.Log.Info("starting blink");
onboardLed.StartBlink(WildernessLabsColors.PearGreen, TimeSpan.FromMilliseconds(500), TimeSpan.FromMilliseconds(2000), 0.5f);
return base.Run();
}
private void Bmi270Updated(object sender, IChangeResult<(Acceleration3D? Acceleration3D, AngularVelocity3D? AngularVelocity3D, Temperature? Temperature)> e)
{
Resolver.Log.Info($"BMI270: {e.New.Acceleration3D.Value.X.Gravity:0.0},{e.New.Acceleration3D.Value.Y.Gravity:0.0},{e.New.Acceleration3D.Value.Z.Gravity:0.0}g");
if (displayController != null)
{
displayController.AccelerationConditions = e.New;
}
}
private void Bme688Updated(object sender, IChangeResult<(Temperature? Temperature, RelativeHumidity? Humidity, Pressure? Pressure, Resistance? GasResistance)> e)
{
Resolver.Log.Info($"BME688: {(int)e.New.Temperature?.Celsius}C - {(int)e.New.Humidity?.Percent}% - {(int)e.New.Pressure?.Millibar}mbar");
if (displayController != null)
{
displayController.AtmosphericConditions = e.New;
}
}
private void Bh1750Updated(object sender, IChangeResult<Illuminance> e)
{
Resolver.Log.Info($"BH1750: {e.New.Lux}");
if (displayController != null)
{
displayController.LightConditions = e.New;
}
}
}
In our main class, the main things to consider are:
- In the
Initialize()
method, we first create a ProjectLab object (named projLab) when doingProjectLab.Create()
, which conveniently encapsulates all the peripherals it has onboard, so we only focus on using them rather than instantiating each component on it on every project. - Before accessing a peripheral, we first check if its available and if it is, we can use it. For example,
if (projLab.Display is { } display)
, means that if theDisplay
property ofprojLab
is not null, its object is assigned todisplay
, and we can send it as a parameter to ourDisplayController
class. - For the environmental, light and motion sensors, we're wiring them to their respective
Updated
events, which are triggered every 5 seconds (learn more about how we work with sensors here). - For the push buttons, we're wiring both the
PressStarted
andPressEnded
events on each and assigning the states to the corresponding properties shown on theDisplayController
, so they'll be immediately updated on the display as you press any of the buttons. - In the final
Run()
method, we callUpdate()
on theDisplayController
object to show the initial UI with all the peripherals with current values and make Meadow's onboard LED to blink to a green-ish color.
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
Please log in or sign up to comment.