Project updated to beta 6.2 (released on March 5th, 2022)
The purpose of this project is to learn how to connect your Meadow device to your local network, and send GET requests to a public free weather web service periodically to show the forecast, outdoor and indoor temperature (with an analog temperature sensor) in a TFT SPI display.
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 - Register and create a OpenWeather API KeyBefore we start working on our Meadow project, we first need to create an account in the OpenWeatherMap site to get a API Key so we can call the API to get the weather data. Once signed up, go to the API section, and click Subscribe on the Current Weather Data API. You can also check the API doc to have a better understanding of the URL your Meadow Client can call, along with all the different parameters you can send over t
This takes you to the pricing section. Lets click Get API Key on the Free tier, since we just plan to use this for test purposes.
Finally, go ahead and copy your API Key somewhere locally. You will need to send it when doing a GET request.
Step 2 - Assemble the circuitWire your project like this:
Create a new Meadow Application project in Visual Studio 2019 for Windows or macOS and name it WifiWeather.
Step 4 - Add Configuration Files (if missing)In your project, create and add the following configuration files:
Device:
# Name of the device on the network.
Name: WifiWeather
#===============================================================================
# Network configuration.
Network:
# Automatically attempt to get the time at startup?
GetNetworkTimeAtStartup: 1
# Time synchronization period in seconds.
NtpRefreshPeriod: 600
# Name of the NTP servers.
NtpServers:
- time.google.com
- time1.google.com
- time2.google.com
- time3.google.com
# IP addresses of the DNS servers.
DnsServers:
- 142.103.1.1
- 65.39.139.53
This configuration file is used for when Meadow is powered on, it looks for these values here and takes action depending on what's defined. In this case, notice GetNetworkTimeAtStartup set to true, to get the UTC date and time using the NtpServers and DnsServers listed.
IMPORTANT: Right click file and go to the Properties, and make sure the Build Action is set to Content and Copy to Output Directory to Copy Always or Copy If Newer.
For this project, search and install the following NuGet packages:
Step 6 - Add Weather IconsNext, download these seven weather icons from our repo:
Add them to your project. Make sure you go through each one's properties and set its Build Action to Embedded Resource.
Step 7 - Write the code for WifiWeatherThe structure for this project looks like this:
We will go through each folder, and at the same time we can add the class and copy over the logic for each file to put the project together.
Models folder
In the Models folder, we have a Constants
class, which has all the weather codes that OpenWeather returns from their GetCurrentWeather API.
WeatherReading
class is a just a Data Transfer Object (DTO) we use to deserialize the API response.
Services folder
Inside Services folder we have WeatherService, which is a static class that its sole purpose is to instantiate an HttpClient
and send a GET request to OpenWeather API to give us the current weather.
Copy the following code below:
static string climateDataUri = "http://api.openweathermap.org/data/2.5/weather?";
static string city = $"q=[CITY HERE]";
static string apiKey = $"appid=[API KEY HERE]";
static WeatherService() { }
public static async Task<WeatherReading> GetWeatherForecast()
{
using (HttpClient client = new HttpClient())
{
client.Timeout = new TimeSpan(0, 5, 0);
HttpResponseMessage response = await client.GetAsync
($"{climateDataUri}?{city}&{apiKey}");
try
{
response.EnsureSuccessStatusCode();
string json = await response.Content.ReadAsStringAsync();
var values = System.Text.Json.JsonSerializer.Deserialize
(json, typeof(WeatherReading));
var reading = values as WeatherReading;
return reading;
}
catch (TaskCanceledException)
{
Console.WriteLine("Request timed out.");
return null;
}
catch (Exception e)
{
Console.WriteLine($"Request went sideways: {e.Message}");
return null;
}
}
}
Make sure to replace both city
and apiKey
string fields with your city and apiKey generated in your OpenWeather account.
ViewModels folder
In this folder we have a WeatherViewModel
class, which receives the deserialized json object and we extract and format the data with the relevant fields we want to show on our display, which in this case it would be the date, time, weather code (to show the corresponding icon), outdoor and indoor temperature.
public class WeatherViewModel
{
public int WeatherCode { get; set; }
public int OutdoorTemperature { get; set; }
public int IndoorTemperature { get; set; }
public WeatherViewModel
(WeatherReading outdoorConditions, AtmosphericConditions indoorConditions)
{
WeatherCode = outdoorConditions.Weather[0].Id;
OutdoorTemperature = (int) (outdoorConditions.main.temp - 273);
IndoorTemperature = (int) indoorTemperature.Celsius;
}
}
The temperature value that's in OutdoorConditions
object comes in Kelvin degrees, so we substract 273 to convert it to Celsius
.
IndoorTemperature
comes from the Temperature
object created by the AnalogTemperature
sensor which is unitized, so we can easily get the value in Celsius.
Views folder
In the Views folder, we have the WeatherView class, which is in charge in receiving the formatted data and displaying it on the ST7789 display. You'll notice the view has the following methods:
Initialize
- Initializes both the display and the MicroGraphics, which is used to draw the entire UI with theUpdateDisplay
method.UpdateDisplay
- Receiving theWeatherViewModel
object, this method will draw the both the icon and all the text fields in its corresponding coordinates and font sizes onto the display.DisplayJPG
- It callsLoadResource
to get the array of bytes of the icon to draw, and using the SimpleJpeg decoder, it will draw image pixel by pixel on the screen, with x and y offset coordinates.LoadResource
- this method will load the corresponding weather icon depending on theweatherCode
provided, and will return an array of bytes to DisplayJPG.
MeadowApp class
The MeadowApp
class is where the main logic happens using all the components mentioned above. From the constructor it first calls Initialize
and finally Start
.
Initialize()
async Task Initialize()
{
onboardLed = new RgbPwmLed(
device: Device,
redPwmPin: Device.Pins.OnboardLedRed,
greenPwmPin: Device.Pins.OnboardLedGreen,
bluePwmPin: Device.Pins.OnboardLedBlue);
onboardLed.SetColor(Color.Red);
var connectionResult = await Device.WiFiAdapter.Connect(
Secrets.WIFI_NAME, Secrets.WIFI_PASSWORD);
if (connectionResult.ConnectionStatus != ConnectionStatus.Success)
{
throw new Exception($"Cannot connect to network: {connectionResult.ConnectionStatus}");
}
analogTemperature = new AnalogTemperature(
device: Device,
analogPin: Device.Pins.A00,
sensorType: AnalogTemperature.KnownSensorType.LM35
);
displayController = new WeatherView();
onboardLed.StartPulse(Color.Green);
}
This method instantiates an RgbPwmLed
object to control the onboard LED which will serves us as an app indicator so we can tell at what point the app is running throughout its entire execution. It starts glowing Red to indicate the app has started the initialization process.
Next, the AnalogTemperature
sensor, the WeatherView
and the WiFi
adapter are all initialized, and the onboard LED should now have a Blue pulsing effect.
Once Meadow is successfully connected to your WiFi network, the onboard LED should now have a pulsing Green effect.
Start()
async Task GetTemperature()
{
onboardLed.StartPulse(Color.Magenta);
// Get indoor conditions
var roomTemperature = await analogTemperature.Read();
// Get outdoor conditions
var outdoorConditions = await WeatherService.GetWeatherForecast();
onboardLed.StartPulse(Color.Orange);
// Format indoor/outdoor conditions data
var model = new WeatherViewModel(outdoorConditions, roomTemperature);
// Send formatted data to display to render
displayController.UpdateDisplay(model);
onboardLed.StartPulse(Color.Green);
}
After initializing all the hardware components and Meadow is connected to your network, the onboard LED should have a Magenta pulsing effect to indicate that it has started activating the AnalogTemperature
sensor to sense the room temperature, and also get the WeatherForecast from the OpenWeather API. This should take about 5 minutes, so please be patient for the app to continue once its completed. Once the values are returned, the onboard LED should turn Orange.
With both indoor and outdoor conditions, the app creates a WeatherViewModel
object to format the data and send this object to the WeatherView's Update
method to draw the UI with all its values.
Finally the onboard LED should turn green to indicate it has completed its execution.
Step 8 - Run the projectClick the Run button in Visual Studio. Once the App is finished executing and the UI is rendered to the display it should look something like this:
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