Project updated to RC2-2 (released on March 4th, 2023)
In this project we're going to learn how to make a desk clock with an LCD Display that also monitors indoor temperature using an analog sensor and outdoor temperature using a public Weather Web Service.
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.
IMPORTANT: If you plan to building this project, be sure to create an account and get an API key from the OpenWeather app to get real time weather data. You can read more in this blog post.
Step 1 - Assemble the circuitWire your project like this:
Create a new Meadow Application project in Visual Studio 2019 for Windows or macOS and name it WifiWeatherClock.
Step 3 - Add the required NuGet packagesFor this project, search and install the following NuGet packages:
Step 4 - Add Configuration Files (if missing)In your project, create and add the following configuration files:
meadow.config.yaml
# Acceptable values for true: true, 1, yes
# Acceptable values for false: false, 0, no
#===============================================================================
# main device config
Device:
# Name of the device on the network.
Name: WifiWeatherClock
#===============================================================================
# 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, like GetNetworkTimeAtStartup set to true, to get the UTC date and time using the NtpServers and DnsServers listed.
IMPORTANT: Right click the config 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, the current structure should look like this:
We'll add each file and explain what do they do so you get the whole picture:
WeatherReadingEntity.cs
Add a new class named WeatherReadingEntity
to a Models project folder and copy the following code:
namespace WifiWeatherClock.Models
{
public class WeatherReadingEntity
{
public Coordinates coord { get; set; }
public Weather[] weather { get; set; }
public WeatherValues main { get; set; }
public int visibility { get; set; }
public Wind wind { get; set; }
public Clouds clouds { get; set; }
public int dt { get; set; }
public System sys { get; set; }
public long timezone { get; set; }
public int id { get; set; }
public string name { get; set; }
public int cod { get; set; }
}
public class Coordinates
{
public double lon { get; set; }
public double lat { get; set; }
}
public class Weather
{
public int id { get; set; }
public string nain { get; set; }
public string description { get; set; }
public string icon { get; set; }
}
public class WeatherValues
{
public double temp { get; set; }
public double feels_like { get; set; }
public double temp_min { get; set; }
public double temp_max { get; set; }
public int pressure { get; set; }
public int humidity { get; set; }
}
public class Wind
{
public decimal speed { get; set; }
public int deg { get; set; }
public double gust { get; set; }
}
public class Clouds
{
public int all { get; set; }
}
public class System
{
public int type { get; set; }
public int id { get; set; }
public string country { get; set; }
public long sunrise { get; set; }
public long sunset { get; set; }
}
}
We'll use this class to deserialize the server json response message from the OpenWeather web service.
WeatherService.cs
Add a new class named WeatherService
to a Services project folder and copy the following code:
public static class WeatherService
{
static string climateDataUri = "http://api.openweathermap.org/data/2.5/weather";
static string city = $"q=[CITY]";
static string apiKey = $"appid=[API KEY]";
static WeatherService() { }
public static async Task<WeatherReadingEntity> GetWeatherForecast()
{
using (HttpClient client = new HttpClient())
{
try
{
client.Timeout = new TimeSpan(0, 5, 0);
HttpResponseMessage response = await client.GetAsync($"{climateDataUri}?{city}&{apiKey}");
response.EnsureSuccessStatusCode();
string json = await response.Content.ReadAsStringAsync();
var values = System.Text.Json.JsonSerializer.Deserialize<WeatherReadingEntity>(json);
return values;
}
catch (TaskCanceledException)
{
Console.WriteLine("Request timed out.");
return null;
}
catch (Exception e)
{
Console.WriteLine($"Request went sideways: {e.Message}");
return null;
}
}
}
}
We'll use this class to do the GET request to the OpenWeather service. You'll need to provide the API key as well as the City to get local weather data in your area.
WeatherViewModel.cs
Add a new class named WeatherViewModel
to a ViewModels project folder and copy the following code:
public class WeatherViewModel
{
public int OutdoorTemperature { get; set; }
public int IndoorTemperature { get; set; }
public string Weather { get; set; }
public WeatherViewModel(WeatherReadingEntity outdoorConditions, Temperature? indoorTemperature)
{
var textCase = new CultureInfo("en-US", false).TextInfo;
Weather = textCase.ToTitleCase(outdoorConditions.weather[0].description);
OutdoorTemperature = (int)(outdoorConditions.main.temp - 273);
IndoorTemperature = (int)indoorTemperature?.Celsius;
}
}
We use this class to format the temperature data we plan to show in the character display.
DisplayView.cs
Add a new class named DisplayView
to a Models project folder and copy the following code:
public class DisplayView
{
CharacterDisplay display;
public DisplayView()
{
Initialize();
display.WriteLine($"--------------------", 0);
display.WriteLine($" WIFI Weather Clock ", 1);
display.WriteLine($" Loading... ", 2);
display.WriteLine($"--------------------", 3);
}
void Initialize()
{
display = new CharacterDisplay
(
pinRS: MeadowApp.Device.Pins.D10,
pinE: MeadowApp.Device.Pins.D09,
pinD4: MeadowApp.Device.Pins.D08,
pinD5: MeadowApp.Device.Pins.D07,
pinD6: MeadowApp.Device.Pins.D06,
pinD7: MeadowApp.Device.Pins.D05,
rows: 4, columns: 20
);
}
public void UpdateDisplay(WeatherViewModel model)
{
display.WriteLine($"{DateTime.Now.ToString("MMMM dd, yyyy")}", 0);
display.WriteLine($"{DateTime.Now.ToString("hh:mm:ss tt")}", 1);
display.WriteLine($"In: {model.IndoorTemperature.ToString("00")}C | Out: {model.OutdoorTemperature.ToString("00")}C", 2);
display.WriteLine($"{model.Weather}", 3);
}
public void WriteLine(string text, byte lineNumber)
{
display.WriteLine($"{text}", lineNumber);
}
}
This class is in charge of encapsulating all the functions that are related to our CharacterDisplay
, Notice we use the UpdateDisplay
method to draw the date and time along with room temperature and outdoor temperature acquired from the web service.
Secrets.cs
public class Secrets
{
/// <summary>
/// Name of the WiFi network to use.
/// </summary>
public const string WIFI_NAME = "SSID";
/// <summary>
/// Password for the WiFi network names in WIFI_NAME.
/// </summary>
public const string WIFI_PASSWORD = "PASSWORD";
}
This class will be used to pass in the Network SSID and Password to join our WIFI network.
MeadowApp.cs
In our main application class MeadowApp
, copy the following code:
// public class MeadowApp : App<F7FeatherV1> <- If you have a Meadow F7v1.*
public class MeadowApp : App<F7FeatherV2>
{
RgbPwmLed onboardLed;
DisplayView displayView;
AnalogTemperature analogTemperature;
public override async Task Initialize()
{
onboardLed = new RgbPwmLed(
redPwmPin: Device.Pins.OnboardLedRed,
greenPwmPin: Device.Pins.OnboardLedGreen,
bluePwmPin: Device.Pins.OnboardLedBlue);
onboardLed.SetColor(Color.Red);
displayView = new DisplayView();
var wifi = Device.NetworkAdapters.Primary<IWiFiNetworkAdapter>();
wifi.NetworkConnected += NetworkConnected;
await wifi.Connect(Secrets.WIFI_NAME, Secrets.WIFI_PASSWORD, TimeSpan.FromSeconds(45));
analogTemperature = new AnalogTemperature(Device.Pins.A00,
sensorType: AnalogTemperature.KnownSensorType.LM35);
await analogTemperature.Read();
analogTemperature.StartUpdating(TimeSpan.FromMinutes(5));
onboardLed.SetColor(Color.Green);
}
private async void NetworkConnected(INetworkAdapter sender, NetworkConnectionEventArgs args)
{
await GetTemperature();
while (true)
{
int TimeZoneOffSet = -7; // PST
var datetime = DateTime.Now.AddHours(TimeZoneOffSet);
if (datetime.Minute == 0 && datetime.Second == 0)
{
await GetTemperature();
}
displayView.WriteLine($"{datetime.ToString("ddd, MMM dd, yyyy")}", 0);
displayView.WriteLine($"{datetime.ToString("hh:mm:ss tt")}", 1);
await Task.Delay(1000);
}
}
async Task GetTemperature()
{
onboardLed.StartPulse(Color.Orange);
// Get outdoor conditions
var outdoorConditions = await WeatherService.GetWeatherForecast();
// Format indoor/outdoor conditions data
var model = new WeatherViewModel(outdoorConditions, analogTemperature.Temperature);
// Update Temperature values and weather description
displayView.WriteLine($"In: {model.IndoorTemperature.ToString("00")}C | Out: {model.OutdoorTemperature.ToString("00")}C", 2);
displayView.WriteLine($"{model.Weather}", 3);
onboardLed.StartPulse(Color.Green);
}
}
Finally, in our main MeadowApp
class, we call the Initialize
method which will join the WiFi network, initialize the CharacterDisplay
when creating a new DisplayView
object, and start the AnalogTemperature
sensor.
After everything is initialize, we call the Start
method which the app just enters an infinite loop that will update the display every second and it will refresh the indoor and outdoor temperature every hour by calling the GetTemperature
method.
The GetTemperature
method activates the analog sensor to check the room temperature and we GetWeatherForecast
to get the weather data from the web service. We pass these values to instantiate the WeatherViewModel
to format our data and ultimately we pass this to our DisplayView
to show it on the screen.
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