public IActionResult Create([Required] string deviceId, TraitViewModel viewModel)
        {
            if (!_deviceRepository.Contains(deviceId))
            {
                return(NotFound());
            }

            var device = _deviceRepository.Get(deviceId);

            if (device.Traits.Any(x => x.Trait == viewModel.Trait))
            {
                ModelState.AddModelError("Trait", "Device already contains trait");
            }

            if (!ModelState.IsValid)
            {
                return(RedirectToAction("Create", new { deviceId }));
            }

            // Set values
            var trait = new DeviceTrait
            {
                Trait      = viewModel.Trait,
                Attributes = !string.IsNullOrEmpty(viewModel.Attributes) ? JsonConvert.DeserializeObject <Dictionary <string, object> >(viewModel.Attributes, new ObjectDictionaryConverter()) : null,
                Commands   = !string.IsNullOrEmpty(viewModel.Commands) ? JsonConvert.DeserializeObject <Dictionary <string, IDictionary <string, string> > >(viewModel.Commands) : null,
                State      = !string.IsNullOrEmpty(viewModel.State) ? JsonConvert.DeserializeObject <Dictionary <string, DeviceState> >(viewModel.State) : null
            };

            device.Traits.Add(trait);

            // Save changes
            _deviceRepository.Persist();

            return(RedirectToAction("Edit", "Devices", new { deviceId }));
        }
Esempio n. 2
0
        public IActionResult Create([Required] string deviceId, TraitViewModel viewModel)
        {
            if (!_deviceRepository.Contains(deviceId))
            {
                return(NotFound());
            }

            var device = _deviceRepository.GetDetached(deviceId);

            if (device.Traits.Any(x => x.Trait == viewModel.Trait))
            {
                ModelState.AddModelError("Trait", "Device already contains trait");
            }

            // Set values
            var trait = new DeviceTrait
            {
                Trait      = viewModel.Trait,
                Attributes = !string.IsNullOrEmpty(viewModel.Attributes) ? JsonConvert.DeserializeObject <Dictionary <string, object> >(viewModel.Attributes, new ObjectDictionaryConverter()) : null,
                Commands   = !string.IsNullOrEmpty(viewModel.Commands) ? JsonConvert.DeserializeObject <Dictionary <string, IDictionary <string, string> > >(viewModel.Commands) : new Dictionary <string, IDictionary <string, string> >(),
                State      = !string.IsNullOrEmpty(viewModel.State) ? JsonConvert.DeserializeObject <Dictionary <string, DeviceState> >(viewModel.State) : null
            };

            // Handle any challenges
            switch (viewModel.ChallengeType)
            {
            case ChallengeType.Acknowledge:
                trait.Challenge = new AcknowledgeChallenge();
                break;

            case ChallengeType.Pin:
                trait.Challenge = new PinChallenge
                {
                    Pin = viewModel.ChallengePin
                };
                break;

            case ChallengeType.None:
            default:
                trait.Challenge = null;
                break;
            }

            // Final validation
            foreach (var error in DeviceTraitValidator.Validate(trait))
            {
                ModelState.AddModelError(string.Empty, error);
            }

            if (!ModelState.IsValid)
            {
                return(RedirectToAction("Create", new { deviceId }));
            }

            // Save changes
            device.Traits.Add(trait);
            _deviceRepository.Update(deviceId, device);

            return(RedirectToAction("Edit", "GoogleDevice", new { deviceId }));
        }
Esempio n. 3
0
        /// <summary>
        /// Validates a ColorAbsolute trait.
        /// </summary>
        /// <param name="deviceTrait">Device trait to validate.</param>
        /// <returns>Validation errors.</returns>
        private static IEnumerable <string> ValidateColorSetting(DeviceTrait deviceTrait)
        {
            var command       = CommandType.ColorAbsolute;
            var commandParams = new List <string>();
            var stateKeys     = new List <string>();
            var attributeKeys = new List <string>();

            if (deviceTrait.Attributes.ContainsKey("colorTemperatureRange"))
            {
                // Temperature range
                attributeKeys.AddRange(new List <string> {
                    "colorTemperatureRange.temperatureMinK", "colorTemperatureRange.temperatureMaxK"
                });
                commandParams.AddRange(new List <string> {
                    "color.temperature"
                });
                stateKeys.AddRange(new List <string> {
                    "color.temperatureK"
                });
            }

            if (deviceTrait.Attributes.ContainsKey("colorModel") && (string)deviceTrait.Attributes["colorModel"] == "hsv")
            {
                // HSV requirements
                commandParams.AddRange(new List <string> {
                    "color.spectrumHSV.hue", "color.spectrumHSV.saturation", "color.spectrumHSV.value"
                });
                stateKeys.AddRange(new List <string> {
                    "color.spectrumHsv.hue", "color.spectrumHsv.saturation", "color.spectrumHsv.value"
                });
            }
            else if (deviceTrait.Attributes.ContainsKey("colorModel") && (string)deviceTrait.Attributes["colorModel"] == "rgb")
            {
                // RGB requirements
                commandParams.AddRange(new List <string> {
                    "color.spectrumRGB"
                });
                stateKeys.AddRange(new List <string> {
                    "color.spectrumRgb"
                });
            }

            if (deviceTrait.Attributes.ContainsKey("commandOnlyColorSetting") && (bool)deviceTrait.Attributes["commandOnlyColorSetting"] == true)
            {
                // Command checks only, wipe expeced states
                stateKeys = null;
            }

            return(ValidateTrait(deviceTrait, command, commandParams, stateKeys, attributeKeys));
        }
        /// <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);
        }
Esempio n. 5
0
        /// <summary>
        /// Validates a RunCycle trait.
        /// </summary>
        /// <param name="deviceTrait">Device trait to validate.</param>
        /// <returns>Validation errors.</returns>
        private static IEnumerable <string> ValidateRunCycle(DeviceTrait deviceTrait)
        {
            var validationErrors = new List <string>();

            var stateKeys = new List <string> {
                "currentRunCycle", "currentTotalRemainingTime", "currentCycleRemainingTime"
            };

            foreach (var stateKey in stateKeys)
            {
                if (!deviceTrait.State.ContainsKey(stateKey))
                {
                    validationErrors.Add($"Trait '{deviceTrait.Trait}' is missing state '{stateKey}'");
                }
            }

            return(validationErrors);
        }
Esempio n. 6
0
        /// <summary>
        /// Validates a brightness trait.
        /// </summary>
        /// <param name="deviceTrait">Device trait to validate.</param>
        /// <param name="command">Command to verify.</param>
        /// <param name="commandParams">Command params expected for command.</param>
        /// <param name="stateKeys">State keys expected for command.</param>
        /// <param name="attributeKeys">Attribute keys expected for command.</param>
        /// <returns>Validation errors.</returns>
        private static IEnumerable <string> ValidateTrait(
            DeviceTrait deviceTrait,
            CommandType command,
            IEnumerable <string> commandParams,
            IEnumerable <string> stateKeys,
            IEnumerable <string> attributeKeys = null)
        {
            var validationErrors = new List <string>();

            var commandName = command.ToEnumString();

            if (command != CommandType.Unknown && !deviceTrait.Commands.ContainsKey(commandName))
            {
                validationErrors.Add($"Trait '{deviceTrait.Trait}' is missing required command '{commandName}'");
            }

            if (commandParams != null)
            {
                foreach (var commandParam in commandParams)
                {
                    if (commandParam.EndsWith(".*"))
                    {
                        if (!deviceTrait.Commands[commandName].Keys.Any(x => x.StartsWith(commandParam.Substring(0, commandParam.Length - 2))))
                        {
                            validationErrors.Add($"Trait '{deviceTrait.Trait}' is missing command param '{commandParam}' for command '{commandName}'");
                        }
                    }
                    else
                    {
                        if (!deviceTrait.Commands[commandName].ContainsKey(commandParam))
                        {
                            validationErrors.Add($"Trait '{deviceTrait.Trait}' is missing command param '{commandParam}' for command '{commandName}'");
                        }
                    }
                }
            }

            if (stateKeys != null)
            {
                foreach (var stateKey in stateKeys)
                {
                    if (stateKey.EndsWith(".*"))
                    {
                        if (!deviceTrait.State.Keys.Any(x => x.StartsWith(stateKey.Substring(0, stateKey.Length - 2))))
                        {
                            validationErrors.Add($"Trait '{deviceTrait.Trait}' is missing state '{stateKey}' for command '{commandName}'");
                        }
                    }
                    else
                    {
                        if (!deviceTrait.State.ContainsKey(stateKey))
                        {
                            validationErrors.Add($"Trait '{deviceTrait.Trait}' is missing state '{stateKey}' for command '{commandName}'");
                        }
                    }
                }
            }

            if (attributeKeys != null)
            {
                var flattenedAttributes = deviceTrait.Attributes != null
                    ? deviceTrait.Attributes.ToFlatDictionary()
                    : new Dictionary <string, object>();

                foreach (var attributeKey in attributeKeys)
                {
                    if (attributeKey.EndsWith(".*"))
                    {
                        if (!flattenedAttributes.Keys.Any(x => x.StartsWith(attributeKey.Substring(0, attributeKey.Length - 2))))
                        {
                            validationErrors.Add($"Trait '{deviceTrait.Trait}' is missing attribute '{attributeKey}' for command '{commandName}'");
                        }
                    }
                    else
                    {
                        if (!flattenedAttributes.ContainsKey(attributeKey))
                        {
                            validationErrors.Add($"Trait '{deviceTrait.Trait}' is missing attribute '{attributeKey}' for command '{commandName}'");
                        }
                    }
                }
            }

            return(validationErrors);
        }
Esempio n. 7
0
        /// <summary>
        /// Validates a RunCycle trait.
        /// </summary>
        /// <param name="deviceTrait">Device trait to validate.</param>
        /// <returns>Validation errors.</returns>
        private static IEnumerable <string> ValidateTemperatureSetting(DeviceTrait deviceTrait)
        {
            var validationErrors = new List <string>();

            // Command only mode
            var commandOnlyTemperatureSetting = deviceTrait.Attributes.ContainsKey("commandOnlyTemperatureSetting")
                ? (bool)deviceTrait.Attributes["commandOnlyTemperatureSetting"]
                : false;

            // Query only mode
            var queryOnlyTemperatureSetting = deviceTrait.Attributes.ContainsKey("queryOnlyTemperatureSetting")
                ? (bool)deviceTrait.Attributes["queryOnlyTemperatureSetting"]
                : false;

            // ThermostatSetMode
            if (deviceTrait.Commands.ContainsKey(CommandType.ThermostatSetMode.ToEnumString()))
            {
                var command       = queryOnlyTemperatureSetting ? CommandType.Unknown : CommandType.ThermostatSetMode;
                var commandParams = queryOnlyTemperatureSetting ? null : new List <string> {
                    "thermostatMode"
                };
                var stateKeys = commandOnlyTemperatureSetting ? null : new List <string> {
                    "thermostatMode"
                };
                validationErrors.AddRange(ValidateTrait(
                                              deviceTrait,
                                              command,
                                              commandParams,
                                              stateKeys,
                                              new List <string> {
                    "availableThermostatModes"
                }));
            }

            // ThermostatTemperatureSetpoint
            if (deviceTrait.Commands.ContainsKey(CommandType.ThermostatTemperatureSetpoint.ToEnumString()))
            {
                var command       = queryOnlyTemperatureSetting ? CommandType.Unknown : CommandType.ThermostatTemperatureSetpoint;
                var commandParams = queryOnlyTemperatureSetting ? null : new List <string> {
                    "thermostatTemperatureSetpoint"
                };
                var stateKeys = commandOnlyTemperatureSetting ? null : new List <string> {
                    "thermostatTemperatureSetpoint"
                };
                validationErrors.AddRange(ValidateTrait(
                                              deviceTrait,
                                              command,
                                              commandParams,
                                              stateKeys,
                                              new List <string> {
                    "thermostatTemperatureUnit"
                }));
            }

            // ThermostatTemperatureSetRange
            if (deviceTrait.Commands.ContainsKey(CommandType.ThermostatTemperatureSetRange.ToEnumString()))
            {
                // Only supported if heatcool mode is supported
                var availableThermostatModes = ((string)deviceTrait.Attributes["availableThermostatModes"]).Split(',').ToList();
                if (availableThermostatModes.Contains("heatcool"))
                {
                    var command       = queryOnlyTemperatureSetting ? CommandType.Unknown : CommandType.ThermostatTemperatureSetRange;
                    var commandParams = queryOnlyTemperatureSetting ? null : new List <string> {
                        "thermostatTemperatureSetpointHigh", "thermostatTemperatureSetpointLow"
                    };
                    var stateKeys = commandOnlyTemperatureSetting ? null : new List <string> {
                        "thermostatTemperatureSetpointHigh", "thermostatTemperatureSetpointLow"
                    };
                    validationErrors.AddRange(ValidateTrait(
                                                  deviceTrait,
                                                  command,
                                                  commandParams,
                                                  stateKeys,
                                                  new List <string> {
                        "thermostatTemperatureUnit"
                    }));
                }
            }

            // TemperatureRelative
            var temperatureRelativeCommandName = CommandType.TemperatureRelative.ToEnumString();

            if (commandOnlyTemperatureSetting && deviceTrait.Commands.ContainsKey(temperatureRelativeCommandName))
            {
                // This command is only available if the commandOnlyTemperatureSetting attribute of the device is set to true. Only one of the following parameters will be set:
                var command = queryOnlyTemperatureSetting ? CommandType.Unknown : CommandType.TemperatureRelative;
                if (!deviceTrait.Commands[temperatureRelativeCommandName].ContainsKey("thermostatTemperatureRelativeDegree"))
                {
                    var commandParams = queryOnlyTemperatureSetting ? null : new List <string> {
                        "thermostatTemperatureRelativeDegree"
                    };
                    validationErrors.AddRange(ValidateTrait(
                                                  deviceTrait,
                                                  command,
                                                  commandParams,
                                                  null,
                                                  new List <string> {
                        "commandOnlyTemperatureSetting"
                    }));
                }
                else
                {
                    var commandParams = queryOnlyTemperatureSetting ? null : new List <string> {
                        "thermostatTemperatureRelativeWeight"
                    };
                    validationErrors.AddRange(ValidateTrait(
                                                  deviceTrait,
                                                  command,
                                                  commandParams,
                                                  null,
                                                  new List <string> {
                        "commandOnlyTemperatureSetting"
                    }));
                }
            }

            return(validationErrors);
        }
Esempio n. 8
0
        /// <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>();

            if (deviceTrait.Trait == TraitType.Unknown)
            {
                validationErrors.Add("Trait is missing or not a valid type");
            }

            switch (deviceTrait.Trait)
            {
            case TraitType.Brightness:
                validationErrors.AddRange(ValidateTrait(
                                              deviceTrait,
                                              CommandType.BrightnessAbsolute,
                                              new List <string> {
                    "brightness"
                },
                                              new List <string> {
                    "brightness"
                }));
                break;

            case TraitType.CameraStream:
                validationErrors.AddRange(ValidateTrait(
                                              deviceTrait,
                                              CommandType.CameraStream,
                                              null,
                                              new List <string> {
                    "cameraStreamAccessUrl"
                },
                                              new List <string> {
                    "cameraStreamSupportedProtocols", "cameraStreamNeedAuthToken", "cameraStreamNeedDrmEncryption"
                }));
                break;

            case TraitType.Channel:
                validationErrors.AddRange(ValidateTrait(
                                              deviceTrait,
                                              CommandType.SelectChannel,
                                              new List <string> {
                    "channelNumber"
                },
                                              new List <string> {
                    "channelNumber"
                },
                                              null));
                break;

            case TraitType.ColorSetting:
                validationErrors.AddRange(ValidateColorSetting(deviceTrait));
                break;

            case TraitType.Dock:
                validationErrors.AddRange(ValidateTrait(
                                              deviceTrait,
                                              CommandType.Dock,
                                              null,
                                              new List <string> {
                    "isDocked"
                },
                                              null));
                break;

            case TraitType.FanSpeed:
                if (deviceTrait.Attributes.ContainsKey("reversible") && (bool)deviceTrait.Attributes["reversible"])
                {
                    validationErrors.AddRange(ValidateTrait(
                                                  deviceTrait,
                                                  CommandType.Reverse,
                                                  null,
                                                  null,
                                                  new List <string> {
                        "reversible"
                    }));
                }

                validationErrors.AddRange(ValidateTrait(
                                              deviceTrait,
                                              CommandType.SetFanSpeed,
                                              new List <string> {
                    "fanSpeed"
                },
                                              new List <string> {
                    "currentFanSpeedSetting"
                },
                                              new List <string> {
                    "availableFanSpeeds.*", "reversible"
                }));
                break;

            case TraitType.Locator:
                validationErrors.AddRange(ValidateTrait(
                                              deviceTrait,
                                              CommandType.Locate,
                                              null,
                                              null,
                                              null));
                break;

            case TraitType.Modes:
                validationErrors.AddRange(ValidateTrait(
                                              deviceTrait,
                                              CommandType.SetModes,
                                              new List <string> {
                    "updateModeSettings.*"
                },
                                              new List <string> {
                    "currentModeSettings.*"
                },
                                              new List <string> {
                    "availableModes"
                }));
                break;

            case TraitType.OnOff:
                validationErrors.AddRange(ValidateTrait(
                                              deviceTrait,
                                              CommandType.OnOff,
                                              new List <string> {
                    "on"
                },
                                              new List <string> {
                    "on"
                }));
                break;

            case TraitType.OpenClose:
                if (deviceTrait.Attributes.ContainsKey("queryOnlyOpenClose") && (bool)deviceTrait.Attributes["queryOnlyOpenClose"])
                {
                    validationErrors.AddRange(ValidateTrait(
                                                  deviceTrait,
                                                  CommandType.Unknown,
                                                  null,
                                                  new List <string> {
                        "openPercent"
                    },
                                                  new List <string> {
                        "queryOnlyOpenClose"
                    }));
                }
                else
                {
                    validationErrors.AddRange(ValidateTrait(
                                                  deviceTrait,
                                                  CommandType.OpenClose,
                                                  new List <string> {
                        "openPercent"
                    },
                                                  new List <string> {
                        "openPercent"
                    }));
                }

                break;

            case TraitType.RunCycle:
                validationErrors.AddRange(ValidateRunCycle(deviceTrait));
                break;

            case TraitType.Scene:
                validationErrors.AddRange(ValidateTrait(
                                              deviceTrait,
                                              CommandType.ActivateScene,
                                              new List <string> {
                    "deactivate"
                },
                                              null,
                                              new List <string> {
                    "sceneReversible"
                }));
                break;

            case TraitType.StartStop:
                if (deviceTrait.Attributes.ContainsKey("pausable") && (bool)deviceTrait.Attributes["pausable"])
                {
                    validationErrors.AddRange(ValidateTrait(
                                                  deviceTrait,
                                                  CommandType.PauseUnpause,
                                                  new List <string> {
                        "pause"
                    },
                                                  new List <string> {
                        "isPaused"
                    },
                                                  new List <string> {
                        "pausable"
                    }));
                }

                validationErrors.AddRange(ValidateTrait(
                                              deviceTrait,
                                              CommandType.StartStop,
                                              new List <string> {
                    "start"
                },
                                              new List <string> {
                    "isRunning"
                },
                                              null));
                break;

            case TraitType.TemperatureControl:
                validationErrors.AddRange(ValidateTrait(
                                              deviceTrait,
                                              CommandType.SetTemperature,
                                              new List <string> {
                    "temperature"
                },
                                              new List <string> {
                    "temperatureSetpointCelsius", "temperatureAmbientCelsius"
                },
                                              new List <string> {
                    "temperatureRange.*", "temperatureUnitForUX"
                }));
                break;

            case TraitType.TemperatureSetting:
                validationErrors.AddRange(ValidateTemperatureSetting(deviceTrait));
                break;

            case TraitType.Toggles:
                validationErrors.AddRange(ValidateTrait(
                                              deviceTrait,
                                              CommandType.SetToggles,
                                              new List <string> {
                    "updateToggleSettings.*"
                },
                                              new List <string> {
                    "currentToggleSettings.*"
                },
                                              new List <string> {
                    "availableToggles"
                }));
                break;

            case TraitType.Volume:
                validationErrors.AddRange(ValidateTrait(
                                              deviceTrait,
                                              CommandType.SetVolume,
                                              new List <string> {
                    "volumeLevel"
                },
                                              new List <string> {
                    "currentVolume", "isMuted"
                },
                                              null));
                break;
            }

            return(validationErrors);
        }