Exemplo n.º 1
0
        /// <summary>
        /// Gets device state as a Google device state object.
        /// </summary>
        /// <param name="stateCache">Current state cache.</param>
        /// <returns>A Google device state object.</returns>
        public IDictionary <string, object> GetGoogleState(IDictionary <string, string> stateCache)
        {
            var results = new Dictionary <string, object>();
            var schemas = TraitSchemaProvider.GetTraitSchemas();

            foreach (var trait in Traits)
            {
                // Dont include "stateless" traits
                var schema = schemas.FirstOrDefault(x => x.Trait == trait.Trait);
                if (schema?.StateSchema == null)
                {
                    continue;
                }

                var validState = trait.GetGoogleStateFlattened(stateCache, schema)
                                 .Where(kvp => schema.StateSchema.Validator.FlattenedPathExists(kvp.Key))
                                 .ToDictionary(kvp => kvp.Key, kvp => kvp.Value);

                foreach (var googleState in validState.ToNestedDictionary())
                {
                    // Note: there explicitly shouldnt be overlap here requiring TryAdd
                    results.Add(googleState.Key, googleState.Value);
                }
            }

            // Add explicit online if not specified by state mappings
            if (!results.ContainsKey("online"))
            {
                results.Add("online", true);
            }

            return(results);
        }
Exemplo n.º 2
0
        /// <summary>
        /// Get trait examples.
        /// </summary>
        /// <param name="traitId">Trait id.</param>
        /// <returns>Response.</returns>
        public IActionResult Examples([Required] string traitId)
        {
            var schemas      = TraitSchemaProvider.GetTraitSchemas();
            var targetSchema = schemas.FirstOrDefault(x => x.Trait == Enum.Parse <TraitType>(traitId));

            if (targetSchema == null)
            {
                return(NotFound());
            }

            var stateExamples = new List <SchemaExample>();

            if (targetSchema.StateSchema != null)
            {
                stateExamples.AddRange(targetSchema.StateSchema.Examples);
            }

            // Flatten out command examples
            var commandExamples = new List <SchemaExample>();

            foreach (var commandSchema in targetSchema.CommandSchemas)
            {
                var commandName = commandSchema.Command.ToEnumString();

                // Add a "command delegation mode" example
                commandExamples.Add(new SchemaExample
                {
                    Comment = $"{commandName}<br/>Pure command delegation mode.",
                    Example = GetWrappedCommandExample(commandName)
                });

                foreach (var commandExample in commandSchema.Examples)
                {
                    // Transform on the way out to keep pure examples in schemas
                    commandExamples.Add(new SchemaExample
                    {
                        Comment = $"{commandName}<br/>{commandExample.Comment}",
                        Example = GetWrappedCommandExample(commandName, commandExample.Example)
                    });
                }

                // Add result examples to states
                if (commandSchema.ResultsExamples != null)
                {
                    stateExamples.AddRange(commandSchema.ResultsExamples);
                }
            }

            var examples = new
            {
                AttributeExamples = targetSchema.AttributeSchema?.Examples,
                StateExamples     = stateExamples.Any() ? stateExamples : null,
                CommandExamples   = commandExamples
            };

            return(Json(examples));
        }
        public void ValidateFlattenedPathReturnsFalse(TraitType traitType, string target)
        {
            // Arrange
            var schemas = TraitSchemaProvider.GetTraitSchemas();
            var schema  = schemas.FirstOrDefault(x => x.Trait == traitType);

            // Act
            var result = schema.StateSchema.Validator.FlattenedPathExists(target);

            // Assert
            Assert.False(result);
        }
        public void GetGoogleTypeForFlattenedPathReturns(TraitType traitType, string target, GoogleType googleType)
        {
            // Arrange
            var schemas = TraitSchemaProvider.GetTraitSchemas();
            var schema  = schemas.FirstOrDefault(x => x.Trait == traitType);

            // Act
            var result = schema.StateSchema.Validator.GetGoogleTypeForFlattenedPath(target);

            // Assert
            Assert.Equal(googleType, result);
        }
        /// <summary>
        /// Validates a <see cref="DeviceTrait"/>.
        /// </summary>
        /// <param name="deviceTrait">The <see cref="DeviceTrait"/> to validate.</param>
        /// <returns>Validation errors.</returns>
        public static IEnumerable <string> Validate(DeviceTrait deviceTrait)
        {
            var validationErrors = new List <string>();

            var traitSchemas = TraitSchemaProvider.GetTraitSchemas();
            var traitSchema  = traitSchemas.FirstOrDefault(x => x.Trait == deviceTrait.Trait);

            if (traitSchema != null)
            {
                // Attribute validation
                if (deviceTrait.Attributes != null && traitSchema.AttributeSchema?.Validator != null)
                {
                    var attributeJson   = JsonConvert.SerializeObject(deviceTrait.Attributes);
                    var attributeErrors = traitSchema.AttributeSchema.Validator.Validate(attributeJson);

                    validationErrors.AddRange(attributeErrors.Select(x => $"Attributes: {x.Path}: {x.Kind}"));
                }

                //// State validation
                ////if (deviceTrait.State != null && traitSchema.StateSchema?.Validator != null)
                ////{
                ////    var stateJson = JsonConvert.SerializeObject(GetFakeGoogleState(deviceTrait.State, traitSchema));
                ////    var stateErrors = traitSchema.StateSchema.Validator.Validate(stateJson);

                ////    validationErrors.AddRange(stateErrors.Select(x => $"State: {x.Path}: {x.Kind}"));
                ////}

                //// Command validations
                ////var deviceCommands = deviceTrait.Commands.ToDictionary(
                ////    k => k.Key,
                ////    v => v.Value?.ToDictionary(
                ////        x => x.Key,
                ////        x => (object)x.Value).ToNestedDictionary());

                ////foreach (var command in deviceCommands)
                ////{
                ////    var commandType = command.Key.ToEnum<CommandType>();
                ////    if (command.Value != null && traitSchema.CommandSchemas.Any(x => x.Command == commandType))
                ////    {
                ////        var commandValidator = traitSchema.CommandSchemas.First(x => x.Command == commandType).Validator;
                ////        var commandJson = JsonConvert.SerializeObject(command.Value);
                ////        var commandErrors = commandValidator.Validate(commandJson);

                ////        validationErrors.AddRange(commandErrors.Select(x => $"Commands ({command.Key}): {x.Path}: {x.Kind}"));
                ////    }
                ////}
            }

            return(validationErrors);
        }
Exemplo n.º 6
0
        /// <summary>
        /// Handles a <see cref="Models.Request.ExecuteIntent"/>.
        /// </summary>
        /// <param name="intent">Intent to process.</param>
        /// <returns>A <see cref="Models.Response.ExecutionResponsePayload"/>.</returns>
        public Models.Response.ExecutionResponsePayload Handle(Models.Request.ExecuteIntent intent)
        {
            _log.LogInformation(string.Format(
                                    "Received EXECUTE intent for commands: {0}",
                                    string.Join(",", intent.Payload.Commands
                                                .SelectMany(x => x.Execution)
                                                .Select(x => x.Command))));

            var executionResponsePayload = new Models.Response.ExecutionResponsePayload();

            // Get all device ids from commands to split into per-device responses
            var deviceIds = intent.Payload.Commands
                            .SelectMany(x => x.Devices.Select(y => y.Id))
                            .Distinct();

            foreach (var deviceId in deviceIds)
            {
                var device = _deviceRepository.Get(deviceId);
                if (device == null)
                {
                    // Device not found
                    executionResponsePayload.Commands.Add(new Models.Response.Command
                    {
                        Ids = new List <string> {
                            deviceId
                        },
                        ErrorCode = "deviceNotFound",
                        Status    = Models.Response.CommandStatus.Error
                    });

                    continue;
                }

                var commands   = intent.Payload.Commands.Where(x => x.Devices.Any(y => y.Id == deviceId));
                var executions = commands.SelectMany(x => x.Execution);

                // Prepare device response payload
                var deviceCommandResponse = new Models.Response.Command
                {
                    Ids = new List <string> {
                        deviceId
                    },
                    Status = Models.Response.CommandStatus.Success
                };

                // Validate challenge check
                foreach (var execution in executions)
                {
                    var challengeResult = ValidateChallenges(device, execution);
                    if (challengeResult != null)
                    {
                        deviceCommandResponse.Status = Models.Response.CommandStatus.Error;

                        if (execution.Challenge != null)
                        {
                            // Challenge failed
                            deviceCommandResponse.ErrorCode       = challengeResult;
                            deviceCommandResponse.ChallengeNeeded = new Models.Response.ChallengeResponse
                            {
                                Type = challengeResult
                            };
                        }
                        else
                        {
                            // Challenge required
                            deviceCommandResponse.ErrorCode       = "challengeNeeded";
                            deviceCommandResponse.ChallengeNeeded = new Models.Response.ChallengeResponse
                            {
                                Type = challengeResult
                            };
                        }

                        break;
                    }
                }

                // Challenge missing or failed
                if (deviceCommandResponse.Status != Models.Response.CommandStatus.Success)
                {
                    executionResponsePayload.Commands.Add(deviceCommandResponse);
                    continue;
                }

                // Publish command and build state response
                var schemas = TraitSchemaProvider.GetTraitSchemas();
                foreach (var command in commands)
                {
                    foreach (var execution in command.Execution)
                    {
                        // Convert command to an event to publish now that its passed all verifications
                        var commandEvent = new DeviceCommandExecutionEvent {
                            DeviceId = deviceId, Execution = execution
                        };
                        _messageHub.Publish(commandEvent);

                        // Generate state for response
                        var states = new Dictionary <string, object>();
                        var trait  = device.Traits.FirstOrDefault(x => x.Commands.ContainsKey(execution.Command));
                        if (trait != null)
                        {
                            var traitSchema   = schemas.FirstOrDefault(x => x.Trait == trait.Trait);
                            var commandSchema = traitSchema.CommandSchemas.FirstOrDefault(x => x.Command == execution.Command.ToEnum <CommandType>());

                            var googleState = trait.GetGoogleStateFlattened(_stateCache, traitSchema);

                            // Map incoming params to "fake" state changes to override existing state value
                            var replacedParams = execution.Params != null
                                ? execution.Params.ToFlatDictionary().ToDictionary(kvp => CommandToStateKeyMapper.Map(kvp.Key), kvp => kvp.Value)
                                : new Dictionary <string, object>();

                            foreach (var state in googleState)
                            {
                                // Decide to use existing state cache value, or attempt to take from transformed execution params
                                var value = replacedParams.ContainsKey(state.Key)
                                    ? replacedParams[state.Key]
                                    : state.Value;

                                // Only add to state response if specified in the command result schema, or fallback state schema
                                if (commandSchema.ResultsValidator != null)
                                {
                                    if (commandSchema.ResultsValidator.FlattenedPathExists(state.Key))
                                    {
                                        states.Add(state.Key, value);
                                    }
                                }
                                else if (traitSchema.StateSchema != null)
                                {
                                    if (traitSchema.StateSchema.Validator.FlattenedPathExists(state.Key))
                                    {
                                        states.Add(state.Key, value);
                                    }
                                }
                            }
                        }

                        // Add explicit online if not specified by state mappings
                        if (!states.ContainsKey("online"))
                        {
                            states.Add("online", true);
                        }

                        // Add any processed states
                        deviceCommandResponse.States = states.ToNestedDictionary();
                    }
                }

                executionResponsePayload.Commands.Add(deviceCommandResponse);
            }

            return(executionResponsePayload);
        }