Project updated to V1.0 Release Candidate 1 (October 23rd, 2022)
In this project we're going to learn how to use Maple to get data logs from a Meadow board. In this case we'll log room temperature values from an analog LM35 temperature sensor.
Maple makes it easy to build connected devices with Meadow by exposing control via a Web API. Maple is an open source, ultra-lightweight, JSON enabled, RESTful web server. It also has an advertise feature, meaning that you could discover Maple servers in your network by listening to UDP broadcast messages with its name and IP Address.
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 - Assemble the circuitWire your project like this:
Create a new Meadow Application project in Visual Studio 2022 for Windows or macOS and name it MeadowMapleTemperature.
Step 3 - Add the required NuGet packagesFor this project, search and install the following NuGet packages:
Step 4 - Write the code for MeadowMapleTemperatureLet's go over each class to build this project:
TemperatureModel class
public class TemperatureModel
{
public string Temperature { get; set; }
public string DateTime { get; set; }
}
We'll use this class model to log a temperature value along with a date and time.
LedController class
public class LedController
{
RgbPwmLed led;
private static readonly Lazy<LedController> instance =
new Lazy<LedController>(() => new LedController());
public static LedController Instance => instance.Value;
private LedController()
{
Initialize();
}
private void Initialize()
{
led = new RgbPwmLed(
MeadowApp.Device,
MeadowApp.Device.Pins.OnboardLedRed,
MeadowApp.Device.Pins.OnboardLedGreen,
MeadowApp.Device.Pins.OnboardLedBlue
);
}
public void SetColor(Color color)
{
led.Stop();
led.SetColor(color);
}
public void StartBlink(Color color)
{
led.Stop();
led.StartBlink(color);
}
}
Singleton LED class helps us give user feedback anywhere in the app We can see how we use it further below.
TemperatureController class
public class TemperatureController
{
ITemperatureSensor analogTemperature;
private static readonly Lazy<TemperatureController> instance =
new Lazy<TemperatureController>(() => new TemperatureController());
public static TemperatureController Instance => instance.Value;
public ObservableCollection<TemperatureModel> TemperatureLogs { get; private set; }
private TemperatureController() { }
public void Initialize()
{
TemperatureLogs = new ObservableCollection<TemperatureModel>();
analogTemperature = new AnalogTemperature(MeadowApp.Device,
MeadowApp.Device.Pins.A01, AnalogTemperature.KnownSensorType.LM35);
analogTemperature.TemperatureUpdated += AnalogTemperatureUpdated;
analogTemperature.StartUpdating(TimeSpan.FromSeconds(30));
}
void AnalogTemperatureUpdated(object sender, Meadow.IChangeResult<Temperature> e)
{
int TIMEZONE_OFFSET = -8;
LedController.Instance.SetColor(Color.Magenta);
TemperatureLogs.Add(new TemperatureModel()
{
Temperature = e.New.Celsius.ToString("00"),
DateTime = DateTime.Now.AddHours(TIMEZONE_OFFSET).ToString("yyyy-MM-dd hh:mm:ss tt")
});
LedController.Instance.SetColor(Color.Green);
}
}
Singleton class that encapsulates the logic to control the analog temperature sensor and start sampling for readings every 30 seconds. In each AnalogTemperatureUpdated
event triggered we add the log value to a local list that will remain stored as long as the app is running (for demonstration purposes).
MapleRequestHandler class
public class MapleRequestHandler : RequestHandlerBase
{
public MapleRequestHandler() { }
[HttpGet("/gettemperaturelogs")]
public IActionResult GetTemperatureLogs()
{
LedController.Instance.SetColor(Color.Cyan);
var data = TemperatureController.Instance.TemperatureLogs;
LedController.Instance.SetColor(Color.Green);
return new JsonResult(data);
}
}
This is where our Maple server comes into play. Notice we extend the MapleRequestHandler
class from RequestHandlerBase
and with that we can expose an API endpoint, which in this case is /gettemperaturelogs
.
When a GET request to this endpoint has been received, we call our LedController singleton to make the onboard LED turn Cyan, we get the TemperatureLogs
from the TemperatureController
singleton and return it to its client. Finally we turn the onboard LED back to green to indicate its ready to receive another request.
Secrets class
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]";
}
Class used to store the WIFI network credentials.
meadow.config.yaml file
# Acceptable values for true: true, 1, yes
# Acceptable values for false: false, 0, no
#===============================================================================
# main device config
MonoControl:
Options: --jit
Device:
# Name of the device on the network.
Name: MeadowMapleTemperature
#===============================================================================
# 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:
- 0.pool.ntp.org
- 1.pool.ntp.org
- 2.pool.ntp.org
- 3.pool.ntp.org
# IP addresses of the DNS servers.
DnsServers:
- 1.1.1.1
- 8.8.8.8
Configuration file is used to enable Just-In-Time compilation and also get date and time via NTP the moment the board joins the network (in UTC).
MeadowApp class
public class MeadowApp : App<F7FeatherV2>
{
MapleServer mapleServer;
public override async Task Initialize()
{
LedController.Instance.SetColor(Color.Red);
var wifi = Device.NetworkAdapters.Primary<IWiFiNetworkAdapter>();
var connectionResult = await wifi.Connect(Secrets.WIFI_NAME, Secrets.WIFI_PASSWORD, TimeSpan.FromSeconds(45));
if (connectionResult.ConnectionStatus != ConnectionStatus.Success)
{
throw new Exception($"Cannot connect to network: {connectionResult.ConnectionStatus}");
}
TemperatureController.Instance.Initialize();
mapleServer = new MapleServer(wifi.IpAddress, 5417, true, logger: Resolver.Log);
mapleServer.Start();
LedController.Instance.SetColor(Color.Green);
}
}
In the Initialize
method in the main class, it will first try to join the network. Once it has joined it will initialize the TemperatureController
to start sampling and log room temperature data along with its date/time.
Finally, it will start the Maple server with the advertise server turned on so it can automatically be found by any app using MapleClient's NuGet package which has a UDP Listener looking for Maple servers in the network.
Step 5 - Understanding the MAUI AppAs we mentioned, in this project we included a MAUI app that runs on iOS, Android and Windows.
The basic things we need to understand here are:
Since we have a Maple Server NuGet package that runs on our Meadow board, we also have a Maple Client package that you can install in your.NET app.
Using Maple Client
In the BaseViewModel's Constructor, we can see how to create a MapleClient object:
...
client = new MapleClient();
client.Servers.CollectionChanged += ServersCollectionChanged;
...
The ServersCollectionChanged event handler is triggered when the app finds Maple servers in the network.
To find Maple servers that are advertising information over the network, you simply call:
await client.StartScanningForAdvertisingServers();
Once it finds servers, it will trigger the CollectionChanged event, which then the app populates a ServerModel list, that you can select with a Picker to which one you wish to send server requests.
SendingCommands to Maple server
In this TemperatureControllerViewModel
, the most important code section to take note is the GetTemperatureLogs
method:
async Task GetTemperatureLogs()
{
if(IsBusy)
return;
IsBusy= true;
try
{
var response = await client.GetAsync(
hostAddress: SelectedServer != null ? SelectedServer.IpAddress : IpAddress,
port: ServerPort,
endPoint: "gettemperaturelogs");
if (string.IsNullOrEmpty(response))
return;
var values = System.Text.Json.JsonSerializer.Deserialize<List<TemperatureModel>>(response);
TemperatureLog.Clear();
foreach (var value in values)
{
TemperatureLog.Add(value);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
IsBusy = false;
}
}
Notice that we use the MapleClient's GetAsync
method to send a GET request to Meadow passing the Meadow's IP address, port and the command gettemperaturelogs
, which we receive a list of TemperatureModel
we serialize and populate on the TemperatureLogObservableCollection
, finally showing it on the app's List View.
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