Example #1
0
        public void UpdateUtilityAppendsAddComponentOp()
        {
            var    op       = new UpdateOperationsUtility();
            string path     = "testPath";
            string property = "someProperty";
            int    value    = 10;

            op.AppendAddComponentOp(path, new Dictionary <string, object> {
                { property, value }
            });
            string operations = op.Serialize();

            // There should be a single operation added.
            var jArray = JArray.Parse(operations);

            jArray.Count.Should().Be(1);

            // The patch operation added should be an "add" operation.
            JToken jObject = jArray.First;

            jObject.Value <string>(Op).Should().Be(Add);

            // The value should have a "$metadata" : {} mapping.
            JObject patchValue = jObject.Value <JObject>(Value);

            patchValue[Metadata].Should().NotBeNull();
            patchValue[Metadata].Should().BeEmpty();
        }
        private async Task UpdateDigitalTwinComponentPropertyAsync()
        {
            // Choose a random value to assign to the targetTemperature property in thermostat1 component
            int desiredTargetTemperature = Random.Next(0, 100);

            const string targetTemperaturePropertyName = "targetTemperature";
            var          updateOperation = new UpdateOperationsUtility();

            // First let's take a look at when the property was updated and what was it set to.
            HttpOperationResponse <TemperatureControllerTwin, DigitalTwinGetHeaders> getDigitalTwinResponse = await _digitalTwinClient
                                                                                                              .GetDigitalTwinAsync <TemperatureControllerTwin>(_digitalTwinId);

            ThermostatTwin thermostat1 = getDigitalTwinResponse.Body.Thermostat1;

            if (thermostat1 != null)
            {
                // Thermostat1 is present in the TemperatureController twin. We can add/replace the component-level property "targetTemperature"
                double?currentComponentTargetTemperature = getDigitalTwinResponse.Body.Thermostat1.TargetTemperature;
                if (currentComponentTargetTemperature != null)
                {
                    DateTimeOffset targetTemperatureDesiredLastUpdateTime = getDigitalTwinResponse.Body.Thermostat1.Metadata.TargetTemperature.LastUpdateTime;
                    _logger.LogDebug($"The property {targetTemperaturePropertyName} under component {Thermostat1Component} was last updated on `" +
                                     $"{targetTemperatureDesiredLastUpdateTime.ToLocalTime()} `" +
                                     $" with a value of {getDigitalTwinResponse.Body.Thermostat1.Metadata.TargetTemperature.DesiredValue}.");

                    // The property path to be replaced should be prepended with a '/'
                    updateOperation.AppendReplacePropertyOp($"/{Thermostat1Component}/{targetTemperaturePropertyName}", desiredTargetTemperature);
                }
                else
                {
                    _logger.LogDebug($"The property {targetTemperaturePropertyName} under component {Thermostat1Component} `" +
                                     $"was never set on the {_digitalTwinId} digital twin.");

                    // The property path to be added should be prepended with a '/'
                    updateOperation.AppendAddPropertyOp($"/{Thermostat1Component}/{targetTemperaturePropertyName}", desiredTargetTemperature);
                }
            }
            else
            {
                // Thermostat1 is not present in the TemperatureController twin. We will add the component
                var componentProperty = new Dictionary <string, object> {
                    { targetTemperaturePropertyName, desiredTargetTemperature }, { "$metadata", new object() }
                };
                _logger.LogDebug($"The component {Thermostat1Component} does not exist on the {_digitalTwinId} digital twin.");

                // The property path to be replaced should be prepended with a '/'
                updateOperation.AppendAddComponentOp($"/{Thermostat1Component}", componentProperty);
            }

            _logger.LogDebug($"Update the {targetTemperaturePropertyName} property under component {Thermostat1Component} on the {_digitalTwinId} `" +
                             $"digital twin to {desiredTargetTemperature}.");
            HttpOperationHeaderResponse <DigitalTwinUpdateHeaders> updateDigitalTwinResponse = await _digitalTwinClient
                                                                                               .UpdateDigitalTwinAsync(_digitalTwinId, updateOperation.Serialize());

            _logger.LogDebug($"Update {_digitalTwinId} digital twin response: {updateDigitalTwinResponse.Response.StatusCode}.");

            // Print the TemperatureController digital twin
            await GetAndPrintDigitalTwinAsync <TemperatureControllerTwin>();
        }
        public async Task DigitalTwinWithComponentOperationsAsync()
        {
            // Create a new test device instance.
            TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false);

            string deviceId = testDevice.Id;

            try
            {
                // Create a device client instance over Mqtt, initializing it with the "TemperatureController" model which has "Thermostat" components.
                var options = new ClientOptions
                {
                    ModelId = TemperatureControllerModelId,
                };
                using DeviceClient deviceClient = testDevice.CreateDeviceClient(Client.TransportType.Mqtt, options);

                // Call openAsync() to open the device's connection, so that the ModelId is sent over Mqtt CONNECT packet.
                await deviceClient.OpenAsync().ConfigureAwait(false);

                // Perform operations on the digital twin.
                using var digitalTwinClient = DigitalTwinClient.CreateFromConnectionString(s_connectionString);

                // Retrieve the digital twin.
                HttpOperationResponse <TemperatureControllerTwin, DigitalTwinGetHeaders> response =
                    await digitalTwinClient.GetDigitalTwinAsync <TemperatureControllerTwin>(deviceId).ConfigureAwait(false);

                TemperatureControllerTwin twin = response.Body;
                twin.Metadata.ModelId.Should().Be(TemperatureControllerModelId);

                string componentName = "thermostat1";

                // Set callback handler for receiving twin property updates.
                await deviceClient.SetDesiredPropertyUpdateCallbackAsync((patch, context) =>
                {
                    Logger.Trace($"{nameof(DigitalTwinWithComponentOperationsAsync)}: DesiredProperty update received: {patch}, {context}");
                    return(Task.FromResult(true));
                }, deviceClient);

                // Update the property "targetTemperature" under component "thermostat1" on the digital twin.
                // NOTE: since this is the first operation on the digital twin, the component "thermostat1" doesn't exist on it yet.
                // So we will create a property patch that "adds" a component, and updates the property in it.
                string propertyName   = "targetTemperature";
                double propertyValue  = new Random().Next(0, 100);
                var    propertyValues = new Dictionary <string, object> {
                    { propertyName, propertyValue }
                };

                var ops = new UpdateOperationsUtility();
                ops.AppendAddComponentOp($"/{componentName}", propertyValues);
                string patch = ops.Serialize();
                HttpOperationHeaderResponse <DigitalTwinUpdateHeaders> updateResponse =
                    await digitalTwinClient.UpdateDigitalTwinAsync(deviceId, patch);

                updateResponse.Response.StatusCode.Should().Be(HttpStatusCode.Accepted);

                // Set callbacks to handle command requests.
                int expectedCommandStatus = 200;

                // Set callback to handle root-level command invocation request.
                string rootCommandName = "reboot";
                await deviceClient.SetMethodHandlerAsync(rootCommandName,
                                                         (request, context) =>
                {
                    Logger.Trace($"{nameof(DigitalTwinWithComponentOperationsAsync)}: Digital twin command {request.Name} received.");
                    string payload = JsonConvert.SerializeObject(request.Name);
                    return(Task.FromResult(new MethodResponse(Encoding.UTF8.GetBytes(payload), expectedCommandStatus)));
                },
                                                         null);

                // Invoke the root-level command "reboot" on the digital twin.
                int    delay = 1;
                string rootCommandPayload = JsonConvert.SerializeObject(delay);
                HttpOperationResponse <DigitalTwinCommandResponse, DigitalTwinInvokeCommandHeaders> rootCommandResponse =
                    await digitalTwinClient.InvokeCommandAsync(deviceId, rootCommandName, rootCommandPayload).ConfigureAwait(false);

                rootCommandResponse.Body.Status.Should().Be(expectedCommandStatus);
                rootCommandResponse.Body.Payload.Should().Be(JsonConvert.SerializeObject(rootCommandName));

                // Set callback to handle component-level command invocation request.
                // For a component-level command, the command name is in the format "<component-name>*<command-name>".
                string componentCommandName    = "getMaxMinReport";
                string componentCommandNamePnp = $"{componentName}*{componentCommandName}";
                await deviceClient.SetMethodHandlerAsync(componentCommandNamePnp,
                                                         (request, context) =>
                {
                    Logger.Trace($"{nameof(DigitalTwinWithComponentOperationsAsync)}: Digital twin command {request.Name} received.");
                    string payload = JsonConvert.SerializeObject(request.Name);
                    return(Task.FromResult(new MethodResponse(Encoding.UTF8.GetBytes(payload), expectedCommandStatus)));
                },
                                                         null);

                // Invoke the command "getMaxMinReport" under component "thermostat1" on the digital twin.
                DateTimeOffset since = DateTimeOffset.Now.Subtract(TimeSpan.FromMinutes(1));
                string         componentCommandPayload = JsonConvert.SerializeObject(since);
                HttpOperationResponse <DigitalTwinCommandResponse, DigitalTwinInvokeCommandHeaders> componentCommandResponse =
                    await digitalTwinClient.InvokeComponentCommandAsync(deviceId, componentName, componentCommandName, componentCommandPayload).ConfigureAwait(false);

                componentCommandResponse.Body.Status.Should().Be(expectedCommandStatus);
                componentCommandResponse.Body.Payload.Should().Be(JsonConvert.SerializeObject(componentCommandNamePnp));
            }
            finally
            {
                // Delete the device.
                await testDevice.RemoveDeviceAsync().ConfigureAwait(false);
            }
        }