Posting the proposed Stackoverflow response over here!
Refence link: https://stackoverflow.com/questions/73052487/azure-digital-twins-twin-to-twin-update
This browser is no longer supported.
Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.
first of all, I'd like to make it clear I have no experience at all with C#, so it might be the case that this is a very trivial question.
I am trying to follow this tutorial: https://learn.microsoft.com/en-us/azure/digital-twins/how-to-send-twin-to-twin-events
using this function sample: https://github.com/Azure-Samples/azure-digital-twins-getting-started/blob/main/azure-functions/twin-updates/ProcessDTRoutedData.cs
However, I am facing a weird error. The idea of this code is that every time a child twin is updated somehow, for example with data coming from an IoT HUB, the parent twin would also be updated. For example, we have a room with 2 thermostats, when there is an update on the thermostats the room temperature would be updated with an average of the temperatures of the thermostats. My issue is the following: if my thermostat 1 has a value of 25oC and thermostat 2 has a value of 27oC at timestep 1 my room temperature should be 26oC, however, I am getting no data, I only get data on the parent twin on timestep 2, when a new update is done on the thermostats, so if at the timestep 2 thermostat 1 has a temperature of 30oC and thermostat 2, a temp of 32oC, the room should show 31oC, however, it shows the 26oC from timestep 1, as you can see we keep having this delayed reaction. to be a bit more specific in my case, I have a device that aggregates (sums) readings from other 4 devices, the total on the aggregator looks off by one iteration every time this function is called
// Default URL for triggering event grid function in the local environment.
// http://localhost:7071/runtime/webhooks/EventGrid?functionName={functionname}
using IoTHubTrigger = Microsoft.Azure.WebJobs.EventHubTriggerAttribute;
using Azure;
using Azure.Core.Pipeline;
using Azure.DigitalTwins.Core;
using Azure.Identity;
using Microsoft.Azure.EventGrid.Models;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.EventGrid;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Net.Http;
using System.Linq;
using System.Reflection.Metadata.Ecma335;
using System.Threading.Tasks;
using System.Collections.Generic;
using TwinUpdatesSample.Dto;
namespace TwinUpdatesSample
{
public class ProcessDTRoutedData
{
private static HttpClient _httpClient = new HttpClient();
private static string _adtServiceUrl = Environment.GetEnvironmentVariable("ADT_SERVICE_URL");
/// <summary>
/// The outcome of this function is to get the average floor temperature and humidity values based on the rooms on that floor.
///
/// 1) Get the incoming relationship of the room. This will get the floor twin ID
/// 2) Get a list of all the rooms on the floor and get the humidity and temperature properties for each
/// 3) Calculate the average temperature and humidity across all the rooms
/// 4) Update the temperature and humidity properties on the floor
/// </summary>
/// <param name="eventGridEvent"></param>
/// <param name="log"></param>
/// <returns></returns>
[FunctionName("ProcessDTRoutedData")]
public async Task Run([EventGridTrigger] EventGridEvent eventGridEvent, ILogger log)
{
log.LogInformation("ProcessDTRoutedData (Start)...");
DigitalTwinsClient client;
DefaultAzureCredential credentials;
// if no Azure Digital Twins service URL, log error and exit method
if (_adtServiceUrl == null)
{
log.LogError("Application setting \"ADT_SERVICE_URL\" not set");
return;
}
try
{
//Authenticate with Azure Digital Twins
credentials = new DefaultAzureCredential();
client = new DigitalTwinsClient(new Uri(_adtServiceUrl), credentials, new DigitalTwinsClientOptions { Transport = new HttpClientTransport(_httpClient) });
}
catch (Exception ex)
{
log.LogError($"Exception: {ex.Message}");
client = null;
credentials = null;
return;
}
if (client != null)
{
if (eventGridEvent != null && eventGridEvent.Data != null)
{
JObject message = (JObject)JsonConvert.DeserializeObject(eventGridEvent.Data.ToString());
log.LogInformation($"A state change in the Helix Building DT has been detected");
//log.LogInformation($"Message: {message}");
string twinId = eventGridEvent.Subject.ToString();
log.LogInformation($"TwinId: {twinId}");
string modelId = message["data"]["modelId"].ToString();
log.LogInformation($"ModelId: {modelId}");
string smartPlugAggregatorId = null;
if (modelId.Contains("dtmi:digitaltwins:rec_3_3:core:logicalDevice:SmartPlug;1"))
{
log.LogInformation($"Updating ProjectSmartPlug state with new state from {twinId}");
// logicalDevice SmartPlug should always report to a logicalDevice SmartPlug Aggregator;
// go get the sourceId for the logicalDevice SmartPlug Aggregator the logicalDevice SmartPlug is related to
AsyncPageable<IncomingRelationship> smartPlugAggregatorList = client.GetIncomingRelationshipsAsync(twinId);
// get the sourceId (parentId)
await foreach (IncomingRelationship smartPlugAggregator in smartPlugAggregatorList)
if (smartPlugAggregator.RelationshipName == "observes")
{
smartPlugAggregatorId = smartPlugAggregator.SourceId;
}
log.LogInformation($"{smartPlugAggregatorId} observes to {twinId} for change in state during this iteration");
// if the parentId (SourceId) is null or empty, then something went wrong
if (string.IsNullOrEmpty(smartPlugAggregatorId))
{
log.LogError($"'SourceId' for observes relationship is missing from GetIncomingRelationships({twinId}) call. This should never happen.");
return;
}
AsyncPageable<BasicDigitalTwin> queryResponse = client.QueryAsync<BasicDigitalTwin>($"SELECT smartPlug FROM digitaltwins smartPlugAggregator JOIN smartPlug RELATED smartPlugAggregator.observes WHERE smartPlugAggregator.$dtId = '{smartPlugAggregatorId}'");
List<SmartPlug> SmartPlugList = new List<SmartPlug>();
// loop through each smartPlugSensor and build a list of smartPlugSensors
await foreach(BasicDigitalTwin twin in queryResponse)
{
JObject smartPlugPayload = (JObject)JsonConvert.DeserializeObject(twin.Contents["smartPlug"].ToString());
log.LogInformation($"Smart Plug {twin.Id} payload: {smartPlugPayload}");
SmartPlugList.Add(new SmartPlug() {
id = twin.Id,
ActiveEnergyWh = Convert.ToDouble(smartPlugPayload["ActiveEnergyWh"]),
ActivePowerW = Convert.ToDouble(smartPlugPayload["ActivePowerW"]),
ReActiveEnergyVARh = Convert.ToDouble(smartPlugPayload["ReActiveEnergyVARh"]),
ReActivePowerVAR = Convert.ToDouble(smartPlugPayload["ReActivePowerVAR"]),
});
}
// if no rooms, then something went wrong and method should exit
if (SmartPlugList.Count < 1)
{
log.LogError($"'roomList' is empty for floor ({smartPlugAggregatorId}). This should never happen.");
return;
}
// get the sum from the list of smartPlug Logical Devices
double sumActiveEnergyWh = SmartPlugList.Sum(x => x.ActiveEnergyWh);
log.LogInformation($"Sum ActiveEnergyWh : {sumActiveEnergyWh.ToString()}");
double sumActivePowerW = SmartPlugList.Sum(x => x.ActivePowerW);
log.LogInformation($"Sum ActivePowerW : {sumActivePowerW.ToString()}");
double sumReActiveEnergyVARh = SmartPlugList.Sum(x => x.ReActiveEnergyVARh);
log.LogInformation($"Sum ReActiveEnergyVARh : {sumReActiveEnergyVARh.ToString()}");
double sumReActivePowerVAR = SmartPlugList.Sum(x => x.ReActivePowerVAR);
log.LogInformation($"Sum ReActivePowerVAR : {sumReActivePowerVAR.ToString()}");
var updateTwinData = new JsonPatchDocument();
updateTwinData.AppendReplace("/ActiveEnergyWh", Math.Round(sumActiveEnergyWh, 2));
updateTwinData.AppendReplace("/ActivePowerW", Math.Round(sumActivePowerW, 2));
try
{
log.LogInformation(updateTwinData.ToString());
await client.UpdateDigitalTwinAsync(smartPlugAggregatorId, updateTwinData);
log.LogInformation("ProcessDTRoutedData (Done)...");
log.LogInformation(" ");
}
catch (Exception ex)
{
log.LogError($"Error: {ex.Message}");
}
return;
}
}
}
}
}
Posting the proposed Stackoverflow response over here!
Refence link: https://stackoverflow.com/questions/73052487/azure-digital-twins-twin-to-twin-update