internal async Task DiffAndReportAsync(AgentState currentState, AgentState agentState) { try { JToken currentJson = JToken.FromObject(currentState); JToken reportedJson = JToken.FromObject(agentState); JObject patch = JsonEx.Diff(reportedJson, currentJson); if (patch.HasValues) { // send reported props await this.edgeAgentConnection.UpdateReportedPropertiesAsync(new TwinCollection(patch.ToString())); // update our cached copy of reported properties this.SetReported(currentState); Events.UpdatedReportedProperties(); } } catch (Exception e) { Events.UpdateReportedPropertiesFailed(e); // Swallow the exception as the device could be offline. The reported properties will get updated // during the next reconcile when we have connectivity. } }
async Task SyncTwinAndSendDesiredPropertyUpdates(string id, Twin storeTwin) { Option <Twin> twinOption = await this.cloudSync.GetTwin(id); await twinOption.ForEachAsync( async cloudTwin => { Events.UpdatingTwinOnDeviceConnect(id); await this.StoreTwinInStore(id, cloudTwin); JObject diffPatch = JsonEx.Diff(JToken.FromObject(storeTwin.Properties.Desired), JToken.FromObject(cloudTwin.Properties.Desired)); if (diffPatch != null && diffPatch.HasValues) { var patch = new TwinCollection(diffPatch.ToString()); IMessage patchMessage = this.twinCollectionConverter.ToMessage(patch); await this.SendPatchToDevice(id, patchMessage); } }); }
async Task SyncTwinAndSendDesiredPropertyUpdates(string id, Twin storeTwin) { Option <Twin> twinOption = await this.cloudSync.GetTwin(id); await twinOption.ForEachAsync( async cloudTwin => { Events.UpdatingTwinOnDeviceConnect(id); await this.StoreTwinInStore(id, cloudTwin); string diffPatch = JsonEx.Diff(storeTwin.Properties.Desired, cloudTwin.Properties.Desired); if (!string.IsNullOrWhiteSpace(diffPatch)) { var patch = new TwinCollection(diffPatch); IMessage patchMessage = this.twinCollectionConverter.ToMessage(patch); await this.SendPatchToDevice(id, patchMessage); } }); }
internal async Task <TwinInfo> GetTwinInfoWhenCloudOnlineAsync(string id, ICloudProxy cp, bool sendDesiredPropertyUpdate) { TwinCollection diff = null; // Used for returning value to caller TwinInfo cached; using (await this.twinLock.LockAsync()) { IMessage twinMessage = await cp.GetTwinAsync(); Twin cloudTwin = this.twinConverter.FromMessage(twinMessage); Events.GotTwinFromCloudSuccess(id, cloudTwin.Properties.Desired.Version, cloudTwin.Properties.Reported.Version); var newTwin = new TwinInfo(cloudTwin, null); cached = newTwin; IEntityStore <string, TwinInfo> twinStore = this.TwinStore.Expect(() => new InvalidOperationException("Missing twin store")); await twinStore.PutOrUpdate( id, newTwin, t => { // If the new twin is more recent than the cached twin, update the cached copy. // If not, reject the cloud twin if (t.Twin == null || cloudTwin.Properties.Desired.Version > t.Twin.Properties.Desired.Version || cloudTwin.Properties.Reported.Version > t.Twin.Properties.Reported.Version) { if (t.Twin != null) { Events.UpdateCachedTwin( id, t.Twin.Properties.Desired.Version, cloudTwin.Properties.Desired.Version, t.Twin.Properties.Reported.Version, cloudTwin.Properties.Reported.Version); cached = new TwinInfo(cloudTwin, t.ReportedPropertiesPatch); // If the device is subscribed to desired property updates and we are refreshing twin as a result // of a connection reset or desired property update, send a patch to the downstream device if (sendDesiredPropertyUpdate) { Option <IReadOnlyDictionary <DeviceSubscription, bool> > subscriptions = this.connectionManager.GetSubscriptions(id); subscriptions.ForEach( s => { if (s.TryGetValue(DeviceSubscription.DesiredPropertyUpdates, out bool hasDesiredPropertyUpdatesSubscription) && hasDesiredPropertyUpdatesSubscription) { Events.SendDesiredPropertyUpdateToSubscriber( id, t.Twin.Properties.Desired.Version, cloudTwin.Properties.Desired.Version); diff = new TwinCollection(JsonEx.Diff(t.Twin.Properties.Desired, cloudTwin.Properties.Desired)); } }); } } } else { Events.PreserveCachedTwin(id, t.Twin.Properties.Desired.Version, cloudTwin.Properties.Desired.Version, t.Twin.Properties.Reported.Version, cloudTwin.Properties.Reported.Version); cached = t; } return(cached); }); } if ((diff != null) && (diff.Count != 0)) { Events.SendDiffToDeviceProxy(diff.ToString(), id); IMessage message = this.twinCollectionConverter.ToMessage(diff); await this.SendDesiredPropertiesToDeviceProxy(id, message); } return(cached); }
public void TestDiffAllCases() { // Arrange var baseline = new { name = new { level0 = "nochange", level1 = "value1", level2 = new { level3 = "value3" }, level6 = (Dictionary <string, string>)null, }, overwrite = new { level1 = "value1" }, create = "yes", arrayWithObjects = new object[] { new object[] { 100L, false } }, arrayWithValue = new string[] { "a" }, arrayChangeType = new string[] { "a" }, arrayNoChange = new string[] { "a" } }; var patch = new { name = new { // ["level0"] = "nochange", // unchanged level1 = (Dictionary <string, string>)null, // existing in base. remove property level2 = new { level3 = "newvalue3" // existing in base, update property }, level4 = "value4", // non existant in base, add new property }, overwrite = "yes", // overwrite object with value create = new // overwrite value with object { level1 = "value1", }, arrayWithObjects = new object[] { new object[] { 100L, true }, new { a = 0, b = "doom" } }, arrayWithValue = new string[] { "b" }, arrayChangeType = "a", // arrayNoChange = new string[] { "a" } // unchanged }; var mergedExcludeNull = new { name = new { level0 = "nochange", // unchanged level2 = new { level3 = "newvalue3" }, level4 = "value4", level6 = (Dictionary <string, string>)null, }, overwrite = "yes", create = new { level1 = "value1", }, arrayWithObjects = new object[] { new object[] { 100L, true }, new { a = 0, b = "doom" } }, arrayWithValue = new string[] { "b" }, arrayChangeType = "a", arrayNoChange = new string[] { "a" } }; var removeAll = new { name = (Dictionary <string, string>)null, overwrite = (Dictionary <string, string>)null, create = (Dictionary <string, string>)null, arrayWithObjects = (int[])null, arrayWithValue = (string[])null, arrayChangeType = (string[])null, arrayNoChange = (string[])null, }; var emptyBaseline = new { }; var emptyPatch = new { }; // Act JToken resultCollection = JsonEx.Diff(JToken.FromObject(baseline), JToken.FromObject(mergedExcludeNull)); // Assert Assert.True(JToken.DeepEquals(resultCollection, JToken.FromObject(patch)), JToken.FromObject(patch).ToString()); // Act resultCollection = JsonEx.Diff(JToken.FromObject(baseline), JToken.FromObject(baseline)); // Assert Assert.True(JToken.DeepEquals(resultCollection, JToken.FromObject(emptyPatch)), resultCollection.ToString()); // Act resultCollection = JsonEx.Diff(JToken.FromObject(emptyBaseline), JToken.FromObject(emptyBaseline)); // Assert Assert.True(JToken.DeepEquals(resultCollection, JToken.FromObject(emptyPatch))); // Act resultCollection = JsonEx.Diff(JToken.FromObject(emptyBaseline), JToken.FromObject(mergedExcludeNull)); // Assert Assert.True(JToken.DeepEquals(resultCollection, JToken.FromObject(mergedExcludeNull))); // Act resultCollection = JsonEx.Diff(JToken.FromObject(baseline), JToken.FromObject(emptyBaseline)); // Assert Assert.True(JToken.DeepEquals(resultCollection, JToken.FromObject(removeAll))); }
public void TestDiffAllCases() { // Arrange var baseline = new { name = new { level0 = "nochange", level1 = "value1", level2 = new { level3 = "value3" }, level6 = (Dictionary <string, string>)null, }, overwrite = new { level1 = "value1" }, create = "yes" }; var patch = new { name = new { // ["level0"] = "nochange", // unchanged level1 = (Dictionary <string, string>)null, // existing in base. remove property level2 = new { level3 = "newvalue3" // existing in base, update property }, level4 = "value4", // non existant in base, add new property }, overwrite = "yes", // overwrite object with value create = new // overwrite value with object { level1 = "value1", }, }; var mergedExcludeNull = new { name = new { level0 = "nochange", // unchanged level2 = new { level3 = "newvalue3" }, level4 = "value4", level6 = (Dictionary <string, string>)null, }, overwrite = "yes", create = new { level1 = "value1", } }; var removeAll = new { name = (Dictionary <string, string>)null, overwrite = (Dictionary <string, string>)null, create = (Dictionary <string, string>)null }; var emptyBaseline = new { }; var emptyPatch = new { }; // Act JToken resultCollection = JsonEx.Diff(JToken.FromObject(baseline), JToken.FromObject(mergedExcludeNull)); // Assert Assert.True(JToken.DeepEquals(resultCollection, JToken.FromObject(patch))); // Act resultCollection = JsonEx.Diff(JToken.FromObject(baseline), JToken.FromObject(baseline)); // Assert Assert.True(JToken.DeepEquals(resultCollection, JToken.FromObject(emptyPatch))); // Act resultCollection = JsonEx.Diff(JToken.FromObject(emptyBaseline), JToken.FromObject(emptyBaseline)); // Assert Assert.True(JToken.DeepEquals(resultCollection, JToken.FromObject(emptyPatch))); // Act resultCollection = JsonEx.Diff(JToken.FromObject(emptyBaseline), JToken.FromObject(mergedExcludeNull)); // Assert Assert.True(JToken.DeepEquals(resultCollection, JToken.FromObject(mergedExcludeNull))); // Act resultCollection = JsonEx.Diff(JToken.FromObject(baseline), JToken.FromObject(emptyBaseline)); // Assert Assert.True(JToken.DeepEquals(resultCollection, JToken.FromObject(removeAll))); }