You are on page 1of 11

10/3/21, 11:46 PM Exercise - Create a programming project for a real device - Learn | Microsoft Docs

R Previous Unit 5 of 9 S Next T

" 100 XP

Create a programming project for a real device


20 minutes

Choose your Integrated Development Environment (IDE) and development language

Visual Studio Code + Node.js Visual Studio Code + C# Visual Studio + Node.js Visual Studio + C#

In this unit, you'll create a programming project to simulate a sensor device in a refrigerated truck. This simulation enables you to test the
code long before you need a real truck!

IoT Central treats this simulation as real. The communication code between the device app and the IoT Central app is the same for a real
truck.

In other words, if you actually run a refrigerated truck company, you would start with simulation code like the code in this unit. After this
code works to your satisfaction, you would replace the simulation code with code that receives sensor data. Because the final code
replacement is a simple switch, learning to write the following code is a valuable experience.

Create the device app


Use your preferred development environment to build the device sensor app. This unit provides examples in Visual Studio Code and Visual
Studio.

1. In Visual Studio, create a new Visual C#/Windows Desktop project. Select Console App (.NET Framework).

2. Give the project a friendly name, such as RefrigeratedTruck.

3. Under Tools/NuGet Package Manager, select Manage NuGet Packages for Solution. Install the following libraries:

AzureMapsRestToolkit
Microsoft.Azure.Devices.Client
Microsoft.Azure.Devices.Provisioning.Client
Microsoft.Azure.Devices.Provisioning.Transport.Mqtt

4. Delete the default contents of the Program.cs file.

5. In the Program.cs file, add all the code in the following section.

Write the device app


In the blank Program.cs file, insert the following code. Add each section of code to the end of the file, in order.

7 Note

If want to skip this unit and load all of the code into your app, download all of the contents of Program.cs from the GitHub location .
Then copy the contents into your project's Program.cs file. Be sure to replace the connection and subscription strings. Then go straight
to the next unit and start testing!

1. Add the using statements, including the statements for Azure IoT Central and Azure Maps.

C# = Copy

using System;

using System.Text.Json;

using System.Text;

using System.Threading;

using System.Threading.Tasks;

using Microsoft.Azure.Devices.Client;

using Microsoft.Azure.Devices.Shared;

using Microsoft.Azure.Devices.Provisioning.Client;

https://docs.microsoft.com/en-us/learn/modules/create-your-first-iot-central-app/5-create-real-device-nodejs?pivots=vs-csharp 1/11
10/3/21, 11:46 PM Exercise - Create a programming project for a real device - Learn | Microsoft Docs

using Microsoft.Azure.Devices.Provisioning.Client.Transport;

using AzureMapsToolkit;

using AzureMapsToolkit.Common;

2. Add the namespace, class, and global variables. Replace the four <your...> strings with the keys you saved in the Truck keys.txt file.

C# = Copy

namespace refrigerated_truck

class Program

enum StateEnum

ready,

enroute,

delivering,

returning,

loading,

dumping

};

enum ContentsEnum

full,

melting,

empty

enum FanEnum

on,

off,

failed

// Azure Maps service globals.

static AzureMapsServices azureMapsServices;

// Telemetry globals.

const int intervalInMilliseconds = 5000; // Time interval required by wait function.

// Refrigerated truck globals.

static int truckNum = 1;

static string truckIdentification = "Truck number " + truckNum;

const double deliverTime = 600; // Time to complete delivery, in seconds.

const double loadingTime = 800; // Time to load contents.

const double dumpingTime = 400; // Time to dump melted contents.

const double tooWarmThreshold = 2; // Degrees C temperature that is too warm for contents.

const double tooWarmtooLong = 60; // Time in seconds for contents to start melting if temperatures
are above threshold.

static double timeOnCurrentTask = 0; // Time on current task, in seconds.

static double interval = 60; // Simulated time interval, in seconds.

static double tooWarmPeriod = 0; // Time that contents are too warm, in seconds.

static double tempContents = -2; // Current temperature of contents, in degrees C.

static double baseLat = 47.644702; // Base position latitude.

static double baseLon = -122.130137; // Base position longitude.

static double currentLat; // Current position latitude.

static double currentLon; // Current position longitude.

static double destinationLat; // Destination position latitude.


static double destinationLon; // Destination position longitude.

static FanEnum fan = FanEnum.on; // Cooling fan state.

static ContentsEnum contents = ContentsEnum.full; // Truck contents state.

static StateEnum state = StateEnum.ready; // Truck is full and ready to go!

static double optimalTemperature = -5; // Setting - can be changed by the operator from IoT Central.

static double outsideTemperature = 12; // Outside ambient temperature.

const string noEvent = "none";

static string eventText = noEvent; // Event text sent to IoT Central.

static double[,] customer = new double[,]

// Latitude and longitude position of customers.

// Gasworks Park

{47.645892, -122.336954},

// Golden Gardens Park

{47.688741, -122.402965},

// Seward Park

https://docs.microsoft.com/en-us/learn/modules/create-your-first-iot-central-app/5-create-real-device-nodejs?pivots=vs-csharp 2/11
10/3/21, 11:46 PM Exercise - Create a programming project for a real device - Learn | Microsoft Docs

{47.551093, -122.249266},

// Lake Sammamish Park

{47.555698, -122.065996},

// Marymoor Park

{47.663747, -122.120879},

// Meadowdale Beach Park

{47.857295, -122.316355},

// Lincoln Park

{47.530250, -122.393055},

// Gene Coulon Park

{47.503266, -122.200194},

// Luther Bank Park

{47.591094, -122.226833},

// Pioneer Park

{47.544120, -122.221673 }

};

static double[,] path; // Latitude and longitude steps for the route.

static double[] timeOnPath; // Time in seconds for each section of the route.

static int truckOnSection; // The current path section the truck is on.

static double truckSectionsCompletedTime; // The time the truck has spent on previous completed sections.

static Random rand;

// IoT Central global variables.

static DeviceClient s_deviceClient;

static CancellationTokenSource cts;

static string GlobalDeviceEndpoint = "global.azure-devices-provisioning.net";

static TwinCollection reportedProperties = new TwinCollection();

// User IDs.

static string IDScope = "<your ID Scope>";

static string DeviceID = "<your Device ID>";

static string PrimaryKey = "<your device Primary Key>";

static string AzureMapsKey = "<your Azure Maps key>";

3. Add the methods to get a route by using Azure Maps.

C# = Copy

static double Degrees2Radians(double deg)

return deg * Math.PI / 180;

// Returns the distance in meters between two locations on Earth.

static double DistanceInMeters(double lat1, double lon1, double lat2, double lon2)

var dlon = Degrees2Radians(lon2 - lon1);

var dlat = Degrees2Radians(lat2 - lat1);

var a = (Math.Sin(dlat / 2) * Math.Sin(dlat / 2)) + Math.Cos(Degrees2Radians(lat1)) *


Math.Cos(Degrees2Radians(lat2)) * (Math.Sin(dlon / 2) * Math.Sin(dlon / 2));

var angle = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a));

var meters = angle * 6371000;

return meters;

static bool Arrived()

// If the truck is within 10 meters of the destination, call it good.

if (DistanceInMeters(currentLat, currentLon, destinationLat, destinationLon) < 10)

return true;

return false;

static void UpdatePosition()

while ((truckSectionsCompletedTime + timeOnPath[truckOnSection] < timeOnCurrentTask) && (truckOnSection <


timeOnPath.Length - 1))

// Truck has moved on to the next section.

truckSectionsCompletedTime += timeOnPath[truckOnSection];

++truckOnSection;
}

https://docs.microsoft.com/en-us/learn/modules/create-your-first-iot-central-app/5-create-real-device-nodejs?pivots=vs-csharp 3/11
10/3/21, 11:46 PM Exercise - Create a programming project for a real device - Learn | Microsoft Docs

// Ensure remainder is less than or equal to 1, because interval may take count over what is needed.

var remainderFraction = Math.Min(1, (timeOnCurrentTask - truckSectionsCompletedTime) /


timeOnPath[truckOnSection]);

// The path should be one entry longer than the timeOnPath array.

// Find how far along the section the truck has moved.
currentLat = path[truckOnSection, 0] + remainderFraction * (path[truckOnSection + 1, 0] -
path[truckOnSection, 0]);

currentLon = path[truckOnSection, 1] + remainderFraction * (path[truckOnSection + 1, 1] -


path[truckOnSection, 1]);

static void GetRoute(StateEnum newState)

// Set the state to ready, until the new route arrives.

state = StateEnum.ready;

var req = new RouteRequestDirections

Query = FormattableString.Invariant($"{currentLat},{currentLon}:{destinationLat},{destinationLon}")

};

var directions = azureMapsServices.GetRouteDirections(req).Result;

if (directions.Error != null || directions.Result == null)

// Handle any error.

redMessage("Failed to find map route");

else

int nPoints = directions.Result.Routes[0].Legs[0].Points.Length;

greenMessage($"Route found. Number of points = {nPoints}");

// Clear the path. Add two points for the start point and destination.

path = new double[nPoints + 2, 2];

int c = 0;

// Start with the current location.

path[c, 0] = currentLat;

path[c, 1] = currentLon;

++c;

// Retrieve the route and push the points onto the array.

for (var n = 0; n < nPoints; n++)

var x = directions.Result.Routes[0].Legs[0].Points[n].Latitude;

var y = directions.Result.Routes[0].Legs[0].Points[n].Longitude;

path[c, 0] = x;

path[c, 1] = y;

++c;

// Finish with the destination.

path[c, 0] = destinationLat;

path[c, 1] = destinationLon;

// Store the path length and time taken, to calculate the average speed.

var meters = directions.Result.Routes[0].Summary.LengthInMeters;

var seconds = directions.Result.Routes[0].Summary.TravelTimeInSeconds;

var pathSpeed = meters / seconds;

double distanceApartInMeters;

double timeForOneSection;

// Clear the time on the path array. The path array is 1 less than the points array.

timeOnPath = new double[nPoints + 1];

// Calculate how much time is required for each section of the path.

for (var t = 0; t < nPoints + 1; t++)

// Calculate distance between the two path points, in meters.

distanceApartInMeters = DistanceInMeters(path[t, 0], path[t, 1], path[t + 1, 0], path[t + 1, 1]);

// Calculate the time for each section of the path.

timeForOneSection = distanceApartInMeters / pathSpeed;

timeOnPath[t] = timeForOneSection;

truckOnSection = 0;

truckSectionsCompletedTime = 0;

timeOnCurrentTask = 0;

// Update the state now the route has arrived. Either: enroute or returning.

state = newState;

https://docs.microsoft.com/en-us/learn/modules/create-your-first-iot-central-app/5-create-real-device-nodejs?pivots=vs-csharp 4/11
10/3/21, 11:46 PM Exercise - Create a programming project for a real device - Learn | Microsoft Docs

7 Note

The key call here is var directions = azureMapsServices.GetRouteDirections(req).Result; . The directions structure is complex.
Consider setting a breakpoint in this method and examining the contents of directions .

4. Add the direct method to deliver to a customer.

C# = Copy

static Task<MethodResponse> CmdGoToCustomer(MethodRequest methodRequest, object userContext)

try

// Pick up variables from the request payload by using the name specified in IoT Central.

var payloadString = Encoding.UTF8.GetString(methodRequest.Data);

int customerNumber = Int32.Parse(payloadString);

// Check for a valid key and customer ID.

if (customerNumber >= 0 && customerNumber < customer.Length)

switch (state)

case StateEnum.dumping:

case StateEnum.loading:

case StateEnum.delivering:

eventText = "Unable to act - " + state;

break;

case StateEnum.ready:

case StateEnum.enroute:

case StateEnum.returning:

if (contents == ContentsEnum.empty)

eventText = "Unable to act - empty";

else

// Set event only when all is good.

eventText = "New customer: " + customerNumber.ToString();

destinationLat = customer[customerNumber, 0];

destinationLon = customer[customerNumber, 1];

// Find route from current position to destination, and store the route.

GetRoute(StateEnum.enroute);

break;

// Acknowledge the direct method call with a 200 success message.

string result = "{\"result\":\"Executed direct method: " + methodRequest.Name + "\"}";

return Task.FromResult(new MethodResponse(Encoding.UTF8.GetBytes(result), 200));

else

eventText = $"Invalid customer: {customerNumber}";

// Acknowledge the direct method call with a 400 error message.

string result = "{\"result\":\"Invalid customer\"}";

return Task.FromResult(new MethodResponse(Encoding.UTF8.GetBytes(result), 400));

catch

// Acknowledge the direct method call with a 400 error message.

string result = "{\"result\":\"Invalid call\"}";

return Task.FromResult(new MethodResponse(Encoding.UTF8.GetBytes(result), 400));

7 Note

https://docs.microsoft.com/en-us/learn/modules/create-your-first-iot-central-app/5-create-real-device-nodejs?pivots=vs-csharp 5/11
10/3/21, 11:46 PM Exercise - Create a programming project for a real device - Learn | Microsoft Docs

The device responds with a conflict if the device isn't in the correct state. The command itself is acknowledged at the end of the
method. The recall command in the next step handles things similarly.

5. Add the recall direct method.

C# = Copy

static void ReturnToBase()

destinationLat = baseLat;
destinationLon = baseLon;

// Find route from current position to base, and store the route.

GetRoute(StateEnum.returning);

static Task<MethodResponse> CmdRecall(MethodRequest methodRequest, object userContext)

switch (state)

case StateEnum.ready:

case StateEnum.loading:

case StateEnum.dumping:

eventText = "Already at base";

break;

case StateEnum.returning:

eventText = "Already returning";

break;

case StateEnum.delivering:

eventText = "Unable to recall - " + state;

break;

case StateEnum.enroute:

ReturnToBase();

break;

// Acknowledge the command.

if (eventText == noEvent)
{

// Acknowledge the direct method call with a 200 success message.

string result = "{\"result\":\"Executed direct method: " + methodRequest.Name + "\"}";

return Task.FromResult(new MethodResponse(Encoding.UTF8.GetBytes(result), 200));

else

// Acknowledge the direct method call with a 400 error message.

string result = "{\"result\":\"Invalid call\"}";

return Task.FromResult(new MethodResponse(Encoding.UTF8.GetBytes(result), 400));

6. Add the method that updates the truck simulation at each time interval.

C# = Copy

static double DieRoll(double max)

return rand.NextDouble() * max;

static void UpdateTruck()

if (contents == ContentsEnum.empty)

// Turn the cooling system off, if possible, when the contents are empty.

if (fan == FanEnum.on)

fan = FanEnum.off;

tempContents += -2.9 + DieRoll(6);

else

// Contents are full or melting.

if (fan != FanEnum.failed)

if (tempContents < optimalTemperature - 5)

https://docs.microsoft.com/en-us/learn/modules/create-your-first-iot-central-app/5-create-real-device-nodejs?pivots=vs-csharp 6/11
10/3/21, 11:46 PM Exercise - Create a programming project for a real device - Learn | Microsoft Docs

// Turn the cooling system off because contents are getting too cold.

fan = FanEnum.off;

else

if (tempContents > optimalTemperature)

// Temperature is getting higher, so turn cooling system back on.

fan = FanEnum.on;

// Randomly fail the cooling system.

if (DieRoll(100) < 1)

fan = FanEnum.failed;

// Set the contents temperature. Maintain a cooler temperature if the cooling system is on.

if (fan == FanEnum.on)

tempContents += -3 + DieRoll(5);

else

tempContents += -2.9 + DieRoll(6);

// If the temperature is above a threshold, count the seconds of duration. Melt the contents if it goes on
too long.

if (tempContents >= tooWarmThreshold)

// Contents are warming.

tooWarmPeriod += interval;

if (tooWarmPeriod >= tooWarmtooLong)

// Contents are melting.

contents = ContentsEnum.melting;

else

// Contents are cooling.

tooWarmPeriod = Math.Max(0, tooWarmPeriod - interval);

// Ensure temperature of contents does not exceed ambient temperature.

tempContents = Math.Min(tempContents, outsideTemperature);

timeOnCurrentTask += interval;

switch (state)

case StateEnum.loading:

if (timeOnCurrentTask >= loadingTime)

// Finished loading.

state = StateEnum.ready;

contents = ContentsEnum.full;

timeOnCurrentTask = 0;

// Turn on the cooling fan.

// If the fan is in a failed state, assume it has been fixed because it is at the base.

fan = FanEnum.on;

tempContents = -2;

break;

case StateEnum.ready:

timeOnCurrentTask = 0;

break;

case StateEnum.delivering:

if (timeOnCurrentTask >= deliverTime)

// Finished delivering.

contents = ContentsEnum.empty;

ReturnToBase();

https://docs.microsoft.com/en-us/learn/modules/create-your-first-iot-central-app/5-create-real-device-nodejs?pivots=vs-csharp 7/11
10/3/21, 11:46 PM Exercise - Create a programming project for a real device - Learn | Microsoft Docs

break;

case StateEnum.returning:

// Update the truck position.

UpdatePosition();

// Check to see if the truck has arrived back at base.

if (Arrived())

switch (contents)

case ContentsEnum.empty:

state = StateEnum.loading;

break;

case ContentsEnum.full:

state = StateEnum.ready;

break;

case ContentsEnum.melting:

state = StateEnum.dumping;

break;

timeOnCurrentTask = 0;

break;

case StateEnum.enroute:

// Move the truck.

UpdatePosition();

// Check to see if the truck has arrived at the customer.

if (Arrived())

state = StateEnum.delivering;

timeOnCurrentTask = 0;

break;

case StateEnum.dumping:

if (timeOnCurrentTask >= dumpingTime)

// Finished dumping.

state = StateEnum.loading;

contents = ContentsEnum.empty;

timeOnCurrentTask = 0;

break;

7 Note

This function is called at every time interval. The actual time interval is set at 5 seconds. But the simulated time (the number of
simulated seconds you specify that have passed each time this function is called) is set by the global static double interval = 60 .
So the simulation runs at a rate of 60 divided by 5, or 12 times the speed of real time.

To shorten the simulated time, reduce interval to, say, 30 (for a simulation that runs 6 times faster than real time). If you set
interval at 5, the simulation will run in real time. This would be realistic but slow, given the real driving times to the customer
destinations.

7. Add the methods to send truck telemetry. Send events too, if any have occurred.

C# = Copy

static void colorMessage(string text, ConsoleColor clr)

Console.ForegroundColor = clr;

Console.WriteLine(text + "\n");

Console.ResetColor();

static void greenMessage(string text)

colorMessage(text, ConsoleColor.Green);

https://docs.microsoft.com/en-us/learn/modules/create-your-first-iot-central-app/5-create-real-device-nodejs?pivots=vs-csharp 8/11
10/3/21, 11:46 PM Exercise - Create a programming project for a real device - Learn | Microsoft Docs

static void redMessage(string text)

colorMessage(text, ConsoleColor.Red);

static async void SendTruckTelemetryAsync(Random rand, CancellationToken token)

while (true)

UpdateTruck();

// Create the telemetry JSON message.

var telemetryDataPoint = new

ContentsTemperature = Math.Round(tempContents, 2),


TruckState = state.ToString(),

CoolingSystemState = fan.ToString(),

ContentsState = contents.ToString(),

Location = new { lon = currentLon, lat = currentLat },

Event = eventText,

};

var telemetryMessageString = JsonSerializer.Serialize(telemetryDataPoint);

var telemetryMessage = new Message(Encoding.ASCII.GetBytes(telemetryMessageString));

// Clear the events because the message has been sent.


eventText = noEvent;

Console.WriteLine($"Telemetry data: {telemetryMessageString}");

// Bail if requested.
token.ThrowIfCancellationRequested();

// Send the telemetry message.

await s_deviceClient.SendEventAsync(telemetryMessage);
greenMessage($"Telemetry sent {DateTime.Now.ToShortTimeString()}");

await Task.Delay(intervalInMilliseconds);

7 Note

The SendTruckTelemetryAsync function is important. It sends telemetry, states, and events to IoT Central. Notice the use of JSON
strings to send the data.

8. Add the code to handle properties. You have only one writeable property and one read-only property in the app. But you could easily
add more if you need to.

C# = Copy

static async Task SendDevicePropertiesAsync()

reportedProperties["TruckID"] = truckIdentification;

await s_deviceClient.UpdateReportedPropertiesAsync(reportedProperties);

greenMessage($"Sent device properties: {reportedProperties["TruckID"]}");

static async Task HandleSettingChanged(TwinCollection desiredProperties, object userContext)

string setting = "OptimalTemperature";

if (desiredProperties.Contains(setting))

optimalTemperature = reportedProperties[setting] = desiredProperties[setting];

greenMessage($"Optimal temperature updated: {optimalTemperature}");

await s_deviceClient.UpdateReportedPropertiesAsync(reportedProperties);

7 Note

This section of code is generic to most C# apps that communicate with IoT Central. You can add more read-only properties to
reportedProperties . To create a new writeable property, set the setting string to the new property name and create an if
statement like the one in this code section.

https://docs.microsoft.com/en-us/learn/modules/create-your-first-iot-central-app/5-create-real-device-nodejs?pivots=vs-csharp 9/11
10/3/21, 11:46 PM Exercise - Create a programming project for a real device - Learn | Microsoft Docs

9. Add the Main function.

C# = Copy

static void Main(string[] args)

rand = new Random();

colorMessage($"Starting {truckIdentification}", ConsoleColor.Yellow);

currentLat = baseLat;
currentLon = baseLon;

// Connect to Azure Maps.

azureMapsServices = new AzureMapsServices(AzureMapsKey);

try

using (var security = new SecurityProviderSymmetricKey(DeviceID, PrimaryKey, null))

DeviceRegistrationResult result = RegisterDeviceAsync(security).GetAwaiter().GetResult();


if (result.Status != ProvisioningRegistrationStatusType.Assigned)

Console.WriteLine("Failed to register device");

return;

IAuthenticationMethod auth = new DeviceAuthenticationWithRegistrySymmetricKey(result.DeviceId, (se‐


curity as SecurityProviderSymmetricKey).GetPrimaryKey());

s_deviceClient = DeviceClient.Create(result.AssignedHub, auth, TransportType.Mqtt);

greenMessage("Device successfully connected to Azure IoT Central");

SendDevicePropertiesAsync().GetAwaiter().GetResult();

Console.Write("Register settings changed handler...");

s_deviceClient.SetDesiredPropertyUpdateCallbackAsync(HandleSettingChanged,
null).GetAwaiter().GetResult();

Console.WriteLine("Done");

cts = new CancellationTokenSource();

// Create a handler for the direct method calls.

s_deviceClient.SetMethodHandlerAsync("GoToCustomer", CmdGoToCustomer, null).Wait();

s_deviceClient.SetMethodHandlerAsync("Recall", CmdRecall, null).Wait();

SendTruckTelemetryAsync(rand, cts.Token);

Console.WriteLine("Press any key to exit...");

Console.ReadKey();

cts.Cancel();

catch (Exception ex)

Console.WriteLine();

Console.WriteLine(ex.Message);

public static async Task<DeviceRegistrationResult> RegisterDeviceAsync(SecurityProviderSymmetricKey security)

Console.WriteLine("Register device...");

using (var transport = new ProvisioningTransportHandlerMqtt(TransportFallbackType.TcpOnly))

ProvisioningDeviceClient provClient =

ProvisioningDeviceClient.Create(GlobalDeviceEndpoint, IDScope, security, transport);

Console.WriteLine($"RegistrationID = {security.GetRegistrationID()}");

Console.Write("ProvisioningClient RegisterAsync...");

DeviceRegistrationResult result = await provClient.RegisterAsync();

Console.WriteLine($"{result.Status}");

return result;

https://docs.microsoft.com/en-us/learn/modules/create-your-first-iot-central-app/5-create-real-device-nodejs?pivots=vs-csharp 10/11
10/3/21, 11:46 PM Exercise - Create a programming project for a real device - Learn | Microsoft Docs

7 Note

You can set direct methods in the client by using statements such as s_deviceClient.SetMethodHandlerAsync("cmdGoTo",
CmdGoToCustomer, null).Wait(); .

Fantastic! You're now ready to test your code.

Next unit: Test your IoT Central device

Continue T

https://docs.microsoft.com/en-us/learn/modules/create-your-first-iot-central-app/5-create-real-device-nodejs?pivots=vs-csharp 11/11

You might also like