Exercise: Use the .NET IoT Libraries
In this unit, you'll use the .NET IoT Libraries to write code that interacts with your cave-monitor device. Although the .NET SDK is supported on Raspberry Pi and other IoT boards, we recommend using a computer. Using a computer lets you use fully featured IDEs and editors like Visual Studio and Visual Studio Code.
Create the app
Complete the following steps from a command shell on your development machine.
Create a new console app named cheesecave.net.
dotnet new console -o cheesecave.net cd ./cheesecave.net
The preceding commands:
- Create a new .NET console app in a folder named cheesecave.net.
- Set the current location to the cheesecave.net folder.
Add the .NET IoT Libraries to the project.
dotnet add package System.Device.Gpio --version 2.1.0 dotnet add package IoT.Device.Bindings --version 2.1.0
The preceding commands:
- Add the
System.Device.Gpio
andIot.Device.Bindings
packages to the project. - Specifies version
2.1.0
is added for both packages.
- Add the
Add code
Using your preferred IDE or editor, replace the contents of Program.cs with the following code:
using System; using System.Device.Gpio; using System.Device.Gpio.Drivers; using System.Device.I2c; using Iot.Device.Bmxx80; using Iot.Device.Bmxx80.ReadResult; bool _fanOn = false; bool _exit = false; int _pin = 21; // Initialize the GPIO controller using GpioController gpio = new GpioController(); // Open the GPIO pin for output gpio.OpenPin(_pin, PinMode.Output); gpio.Write(_pin, PinValue.Low); // Get a reference to a device on the I2C bus var i2cSettings = new I2cConnectionSettings(1, Bme280.DefaultI2cAddress); using I2cDevice i2cDevice = I2cDevice.Create(i2cSettings); // Create a reference to the BME280 using var bme280 = new Bme280(i2cDevice); // Write the fan, temperature, and humidity statuses to the console WriteStatus(); // Main control loop while (!_exit) { string commandText = Console.ReadLine() ?? string.Empty; DoCommand(commandText); } // Close the pin before exit gpio.ClosePin(_pin); // Exit return; void DoCommand(string commandText) { switch (commandText) { case "exit": Console.WriteLine("Exiting!"); _exit = true; break; case "fan": if (!_fanOn) { // Turn on the fan gpio.Write(_pin, PinValue.High); Console.WriteLine("Turned fan on!"); } else { // Turn off the fan gpio.Write(_pin, PinValue.Low); Console.WriteLine("Turned fan off!"); } _fanOn = !_fanOn; WriteStatus(); break; case "status": WriteStatus(); break; default: Console.WriteLine("Command not recognized! Try again."); return; } } void WriteStatus() { // Read the BME280 Bme280ReadResult output = bme280.Read(); double temperatureF = output.Temperature?.DegreesFahrenheit ?? double.NaN; double humidityPercent = output.Humidity?.Percent ?? double.NaN; // Print statuses Console.WriteLine(); Console.WriteLine("DEVICE STATUS"); Console.WriteLine("-------------"); Console.WriteLine($"Fan: {(_fanOn ? "ON" : "OFF")}"); Console.WriteLine($"Temperature: {temperatureF:0.#}°F"); Console.WriteLine($"Relative humidity: {humidityPercent:#.##}%"); Console.WriteLine(); Console.WriteLine("Enter command (status/fan/exit):"); }
Examine the code to understand how it interacts with the GPIO pin. In the preceding code:
An instance of
GpioController
is created for use in GPIO operations.Pin 21 is opened with
gpio.OpenPin()
. The pin is opened for output withPinMode.Output
.The value of Pin 21 is set to
PinValue.Low
. This value represents the lowest amount of voltage the pin can emit. For purposes of illuminating the LED,PinValue.Low
represents off, andPinValue.High
represents on. As a result, the LED representing the fan is turned off.The code toggles the fan/LED on and off by writing
PinValue.Low
andPinValue.High
.Tip
Many real-world relays use
PinValue.High
for off andPinValue.Low
for on. That's the opposite of what you're implementing for the LED that's representing the fan relay in this exercise.Before the code exits, Pin 21 is closed with
gpio.ClosePin()
.
Examine the code to understand how it interacts with the BME280. In the preceding code:
An instance of
I2cConnectionSettings
is created. The first constructor parameter,busId
, is set to1
, the I2C bus ID on the Raspberry Pi. The second constructor parameter,deviceAddress
, is set toBme280.DefaultI2cAddress
.Warning
Some BME280 breakouts use
Bme280.SecondaryI2cAddress
for the device address. If your app throwsSystem.IO.IOException: Error 121 performing I2C data transfer.
, try this value instead.An instance of
I2cDevice
is created using theI2cConnectionSettings
object.An instance of
Bme280
is created using theI2cDevice
object. This object represents the physical BME280.In the
WriteStatus()
method, aBme280ReadResult
object is created by callingBme280.Read()
.The
Bme280ReadResult
object containsTemperature
andHumidity
properties.- The
Temperature
andHumidity
properties are both nullable, which means they might containnull
. Accordingly, the null-conditional operator?.
is used to access their members. - The properties themselves expose properties that do automatic unit conversions, such as
DegreesFahrenheit
andPercent
. - In both cases, the null-coalescing operator
??
checks the return value, and if it'snull
, replaces it withdouble.NaN
.
- The
Tip
If you need help understanding null safety, see Null safety in C#.
Build the app
Run the following command to build the app.
dotnet build
The build completes with no errors or warnings.
In the next unit, you'll deploy your app to the Raspberry Pi and test it.