Azure Digital twins - Twin to Twin update

ricardo pereira 1 Reputation point
2022-07-20T13:29:41.087+00:00

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;  
            }  
        }  
    }  
}  

}

Azure Digital Twins
Azure Digital Twins
An Azure platform that is used to create digital representations of real-world things, places, business processes, and people.
219 questions
{count} votes

1 answer

Sort by: Most helpful
  1. QuantumCache 20,031 Reputation points
    2022-10-21T04:56:43.927+00:00

    Posting the proposed Stackoverflow response over here!

    Refence link: https://stackoverflow.com/questions/73052487/azure-digital-twins-twin-to-twin-update

    252843-image.png

    0 comments No comments