简体   繁体   中英

Azure Digital twins - Twin to Twin update

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://docs.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 on my case, I have a device that aggregates (sums) readings from other 4 devices, the total on the aggregator looks off by one iteration everytime 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;
                    }
                }
            }
        }
    }       
}

As your query is making a projection (ie specifying one or more columns you want to return, as opposed to doing a SELECT * ), the query returns an AsyncPageable<IDictionary<string, BasicDigitalTwin>> (as opposed to AsyncPageable<BasicDigitalTwin> for wildcard queries).

You are looking at changing your code to:

AsyncPageable<IDictionary<string, BasicDigitalTwin>> queryResponse = client.QueryAsync<IDictionary<string, 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(IDictionary<string, BasicDigitalTwin> d in queryResponse)
{
    if (d.ContainsKey("smartPlug")) {
        SmartPlugList.Add(JsonConvert.DeserializeObject<SmartPlug>(d["smartPlug"].ToString()));
    }

}

Please see https://docs.microsoft.com/en-us/azure/digital-twins/how-to-query-graph#run-queries-with-the-api for more info.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM