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.