Send Data From Meadow to an ASP. NET Server via WiFi

Story
In this project, we're going to learn how to connect Meadow to a WiFi network and log the room temperature with an LM35 analog temperature sensor sending the data periodically to an ASP.NET server running on a PC.
Everything you need to build this project is included in the Wilderness Labs Meadow F7 w/Hack Kit Pro. We'll see how easy is to program these peripherals using Meadow.Foundation.
Meadow.Foundation is 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.
IMPORTANT NOTE: For this project, we're gonna need the latest version (b4.0) of Meadow.OS running on our boards Learn how to upgrade your Meadow to the latest version using this guide.
Things used in this project
Hardware components |
||||||
![]() |
|
× | 1 | |||
![]() |
|
× | 1 | |||
|
× | 1 | ||||
|
× | 1 | ||||
Software apps and online services |
||||||
|
Microsoft |
Step 1 - Setting up the Clima Server
Go to the Clima github repo and download it on a zip (or clone it if you wish). Open the Clima.ServerHosts.Mini solution and run the project named WildernessLabs.Clima.Server.Mini on your computer. It should open a Command Prompt window with information about the app. Take note of the port number, as you will need this later on your Meadow application later on.
Clima Server running
To test the server handling requests, you can use an app to request data to this server like Postman. Make a GET request to the following URL:
http://localhost:[PORT NUMBER]/ClimateData
You should see it will return the following sample data in json format:
[
{
"id": 1,
"tempC": 22,
"barometricPressureMillibarHg": 200,
"relativeHumdity": 0.5
}
]
If you are getting the sample data above, your server is all set and you can continue on building the Meadow client project. Keep this project running and open a new instance of visual studio to create and run the Meadow client project.
Step 2 - Building the Meadow Clima Client
Circuit Diagram
Wire your project like the following Fritzing diagram:
Step 2.1 - Create a Meadow Application project
Create a new Meadow Application project in Visual Studio 2019 for Windows or macOS and name it Clima.
Step 2.2 - Add the required NuGet packages
For this project, search and install the following NuGet packages:
Step 2.3 - Add an image asset
Download the following image (meadow.jpg), and add it to your project:
Step 2.4 - Write the code for Clima
For this project, we're going to need to add a few classes. We'll go through each one and explain the role they play on the app.
DisplayController
class
Add a new class and name it DisplayController and copy the code below:
public class DisplayController
{
protected St7789 display;
protected GraphicsLibrary graphics;
protected AtmosphericConditions conditions;
protected bool isRendering = false;
protected object renderLock = new object();
public DisplayController()
{
InitializeDisplay();
}
protected void InitializeDisplay()
{
var config = new SpiClockConfiguration(
6000, SpiClockConfiguration.Mode.Mode3);
display = new St7789
(
device: MeadowApp.Device,
spiBus: MeadowApp.Device.CreateSpiBus(
MeadowApp.Device.Pins.SCK,
MeadowApp.Device.Pins.MOSI,
MeadowApp.Device.Pins.MISO,
config),
chipSelectPin: null,
dcPin: MeadowApp.Device.Pins.D01,
resetPin: MeadowApp.Device.Pins.D00,
width: 240, height: 240
);
graphics = new GraphicsLibrary(display) {
CurrentFont = new Font12x20(),
};
graphics.Clear(true);
//Render();
}
public void UpdateDisplay(AtmosphericConditions conditions) {
this.conditions = conditions;
Render();
}
protected void Render()
{
lock (renderLock)
{
if (isRendering)
{
Console.WriteLine("Already in a rendering loop, bailing out.");
return;
}
isRendering = true;
}
graphics.Clear(true);
graphics.Stroke = 1;
graphics.DrawRectangle(
xLeft: 0,
yTop: 0,
width: (int)display.Width,
height: (int)display.Height,
color: Color.White);
graphics.DrawRectangle(
xLeft: 5,
yTop: 5,
width: (int)display.Width - 10,
height: (int)display.Height - 10,
color: Color.White);
graphics.DrawCircle(
centerX: (int)display.Width / 2,
centerY: (int)display.Height / 2,
radius: (int)(display.Width / 2) - 10,
color: Color.FromHex("#23abe3"),
filled: true);
DisplayJPG(55, 40);
string text = $"{conditions.Temperature?.ToString("##.#")}°C";
graphics.CurrentFont = new Font12x20();
graphics.DrawText(
x: (int)(display.Width - text.Length * 24) / 2,
y: 140,
text: text,
color: Color.Black,
scaleFactor: GraphicsLibrary.ScaleFactor.X2);
graphics.Rotation = GraphicsLibrary.RotationType._270Degrees;
graphics.Show();
isRendering = false;
}
protected void DisplayJPG(int x, int y)
{
var jpgData = LoadResource("meadow.jpg");
var decoder = new JpegDecoder();
var jpg = decoder.DecodeJpeg(jpgData);
int imageX = 0;
int imageY = 0;
byte r, g, b;
for (int i = 0; i < jpg.Length; i += 3)
{
r = jpg[i];
g = jpg[i + 1];
b = jpg[i + 2];
graphics.DrawPixel(imageX + x, imageY + y, Color.FromRgb(r, g, b));
imageX++;
if (imageX % decoder.Width == 0)
{
imageY++;
imageX = 0;
}
}
display.Show();
}
protected byte[] LoadResource(string filename)
{
var assembly = Assembly.GetExecutingAssembly();
var resourceName = $"Clima.Meadow.HackKit.{filename}";
using (Stream stream = assembly.GetManifestResourceStream(resourceName))
{
using (var ms = new MemoryStream())
{
stream.CopyTo(ms);
return ms.ToArray();
}
}
}
}
DisplayController works as an abstraction to encapsulate all the logic related to load and draw the user interface to the ST7789 SPI display. It's comprised of the following methods:
InitializeDisplay
- this method has all the logic to initialize both the display and the graphics library which is used to draw the UI in theRender
method.UpdateDisplay
- Public method that updates the temperature value and calls Render to redraw the UI.Render
- Draws the entire UI along with the Temperature value.DisplayJPG
- this method uses theSimpleJpegDecoder
to decode the array of bytes returned fromLoadResource
method, which are assigned to thejpg
byte array that has all the red, green, and blue values on each pixel one after another.LoadResource
- Opens the image file to get the stream and returns an array of bytes of raw data.
ClimateServiceFacade
class
Add a static class named ClimateServicesFacade and copy the code below:
public static class ClimateServiceFacade
{
static string climateDataUri = "http://[IP ADDRESS HERE]:2792/ClimateData";
static ClimateServiceFacade() { }
/// <summary>
/// Posts a temperature reading to the web API endpoint
/// </summary>
/// <param name="tempC"></param>
/// <returns></returns>
public static async Task PostTempReading(decimal tempC)
{
ClimateReading climateReading = new ClimateReading() { TempC = tempC };
using (HttpClient client = new HttpClient()) {
client.Timeout = new TimeSpan(0, 5, 0);
string json = System.Text.Json.JsonSerializer
.Serialize<ClimateReading>(climateReading);
HttpResponseMessage response = await client.PostAsync(
climateDataUri, new StringContent(
json, Encoding.UTF8, "application/json"));
try {
response.EnsureSuccessStatusCode();
} catch (TaskCanceledException) {
Console.WriteLine("Request time out.");
} catch (Exception e) {
Console.WriteLine($"Request went sideways: {e.Message}");
}
}
}
/// <summary>
/// Fetches the climate readings from the Web API Endpoint
/// </summary>
/// <returns></returns>
public static async Task FetchReadings()
{
using (HttpClient client = new HttpClient()) {
client.Timeout = new TimeSpan(0, 5, 0);
HttpResponseMessage response = await client.GetAsync(climateDataUri);
try {
response.EnsureSuccessStatusCode();
//System.Json[old skool]
string json = await response.Content.ReadAsStringAsync();
Console.WriteLine(json);
var stuff = System.Text.Json.JsonSerializer
.Deserialize(json, typeof(ClimateReading[]));
Console.WriteLine("deserialized to object");
var reading = stuff as ClimateReading[];
Console.WriteLine($"Temp: {reading[0].TempC}");
} catch (TaskCanceledException) {
Console.WriteLine("Request time out.");
} catch (Exception e) {
Console.WriteLine($"Request went sideways: {e.Message}");
}
}
}
}
This static method is used to make send the GET/POST requests to the server:
PostTempReading
- Posts a temperature reading to the web API endpoint. Notice how we're using an HttpClient just like we would on a mobile or desktop app, and we're using System.Text.Json to serialize our temperature value to send the request to the server.FetchReadings
- this method gets the climate readings from the Web API Endpoint. We're again using an HttpClient to a GET request, and System.Text.Json to deserialize the server's response, and printing the value in the output console.
ClimateReading
class
Add a ClimateReading class and copy the following code:
public class ClimateReading
{
public long? ID { get; set; }
public decimal? TempC { get; set; }
public decimal? BarometricPressureMillibarHg { get; set; }
public decimal? RelativeHumdity { get; set; }
}
This is a model class we'll use to send data to the ASP.NET server project.
Secrets
class
Add a Secrets class and copy the code below:
public class Secrets
{
/// <summary>
/// Name of the WiFi network to use.
/// </summary>
public const string WIFI_NAME = string.Empty;
/// <summary>
/// Password for the WiFi network names in WIFI_NAME.
/// </summary>
public const string WIFI_PASSWORD = string.Empty;
}
Initialize both WIFI_NAME
and WIFI_PASSWORD
fields with your WiFi credentials.
MeadowApp
class
In your main MeadowApp class, copy the following code:
public class MeadowApp : App<F7Micro, MeadowApp>
{
AnalogTemperature analogTemperature;
DisplayController displayController;
public MeadowApp()
{
Initialize();
var tA = ReadTemp();
tA.Wait();
var conditions = tA.Result;
this.displayController.UpdateDisplay(conditions);
ClimateServiceFacade.FetchReadings().Wait();
ClimateServiceFacade.PostTempReading((decimal)conditions.Temperature).Wait();
ClimateServiceFacade.FetchReadings().Wait();
Console.WriteLine("App finished running.");
}
void Initialize()
{
var rgbLed = new RgbLed(
Device,
Device.Pins.OnboardLedRed,
Device.Pins.OnboardLedGreen,
Device.Pins.OnboardLedBlue
);
rgbLed.SetColor(RgbLed.Colors.Red);
analogTemperature = new AnalogTemperature(
device: Device,
analogPin: Device.Pins.A00,
sensorType: AnalogTemperature.KnownSensorType.LM35
);
displayController = new DisplayController();
rgbLed.SetColor(RgbLed.Colors.Blue);
Device.InitWiFiAdapter().Wait();
var result = Device.WiFiAdapter.Connect(
Secrets.WIFI_NAME, Secrets.WIFI_PASSWORD);
if (result.ConnectionStatus != ConnectionStatus.Success)
{
rgbLed.SetColor(RgbLed.Colors.Magenta);
throw new Exception($"Cannot connect to network:
{result.ConnectionStatus}");
}
rgbLed.SetColor(RgbLed.Colors.Green);
}
protected async Task<AtmosphericConditions> ReadTemp()
{
var conditions = await analogTemperature.Read();
return conditions;
}
}
In
the MeadowApp Main class, you can notice the logic is split into the following methods:
Constructor
- First thing the Meadow Application does is call theInitialize
method to set up all the peripherals and network connectivity. After that, it calls the ReadTemp method to get the current room temperature from the analog sensor. Next you'll see the app use theClimateServiceFacade
static class to get a list of reading values from the server with theFetchReadings
method proceeds to callPostTempReading
to send the current temperature value to the server and finally callsFetchReadings
to get the most recent list of readings from the server, and the app finally finishes executing.Initialize
- This method first initializes the onboard RGB LED and sets the color to red to indicate the app has started the initialize process. Then the app proceeds to initialize the analog temperature sensor and instantiated a DisplayController object, setting the onboard RGB LED to blue to indicate that its now ready to connect to the WiFi network.If there was a problem trying to connect to the network, the LED will turn magenta and you should see the Exception thrown in your output console. Otherwise, the LED will turn green, and the project is now fully initialized.ReadTemp
- it will call an asynchronous Read method on theAnalogTemperature
sensor and return anAtmosphericConditions
value.
Step 3 - Run the project
While your server project is running, click the Run button in Visual Studio to deploy the Meadow app to your board, and if your Meadow is successfully connected to the network and is able to use the LM35 analog sensor, you should see your room temperature on the display:
Check out Meadow.Foundation!
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.
References
- Meadow Guides
- Meadow.Foundation
- Meadow.Foundation.TftSpi.St7789 API
- Meadow.Foundation.Graphics.GraphicsLibrary API
- Meadow.Foundation.AnalogTemperature API
Schematics
Clima circuit diagram
Meadow Logo
Code
Clima complete solution
Climate Station App — Read More
Latest commit to the master branch on 12-17-2020 - Download as zip
Credits
Jorge Ramirez
Software developer getting into Hardware in the .Net world! Music lover, guitar enthusiast, intermediate hiker, and bike commuter!

This content is provided by our content partner Hackster.io, an Avnet developer community for learning, programming, and building hardware. Visit them online for more great content like this.