/// <summary> /// Handles a <see cref="Models.Request.ExecuteIntent"/>. /// </summary> /// <param name="intent">Intent to process.</param> /// <returns>A <see cref="Models.Response.ExecutionResponsePayload"/>.</returns> private Models.Response.ExecutionResponsePayload HandleExecuteIntent(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(); foreach (var command in intent.Payload.Commands) { // Convert command to a event to publish _messageHub.Publish(command); // Build response payload var commandResponse = new Models.Response.Command { Status = Models.Response.CommandStatus.Success, Ids = command.Devices.Select(x => x.Id).ToList() }; // Generate states var states = new Dictionary <string, object>(); foreach (var execution in command.Execution) { // Handle camera stream commands if (execution.Command == "action.devices.commands.GetCameraStream") { // Only allow a single cast command at once if (command.Devices.Count() == 1) { // Get the first trait for the camera, as this should be the only trait available var trait = _deviceConfiguration[command.Devices[0].Id].Traits.FirstOrDefault(); if (trait != null) { foreach (var state in trait.State) { states.Add(state.Key, state.Value.MapValueToGoogle(state.Key, null)); } } } } else { // Copy the incoming state values, rather than getting current foreach (var param in execution.Params) { // Handle remapping of Modes and Toggles if (param.Key == "updateModeSettings") { states.Add("currentModeSettings", param.Value); } else if (param.Key == "updateToggleSettings") { states.Add("currentToggleSettings", param.Value); } else { states.Add(param.Key, param.Value); } } } } commandResponse.States = states; executionResponsePayload.Commands.Add(commandResponse); } return(executionResponsePayload); }
/// <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(); foreach (var command in intent.Payload.Commands) { // Convert command to a event to publish _messageHub.Publish(command); // Build response payload var commandResponse = new Models.Response.Command { Status = Models.Response.CommandStatus.Success, Ids = command.Devices.Select(x => x.Id).ToList() }; // Generate states var states = new Dictionary <string, object>(); foreach (var execution in command.Execution) { // Handle camera stream commands if (execution.Command == "action.devices.commands.GetCameraStream") { // Only allow a single cast command at once if (command.Devices.Count() == 1) { // Get the first trait for the camera, as this should be the only trait available var trait = _deviceRepository.Get(command.Devices[0].Id).Traits.FirstOrDefault(); if (trait != null) { foreach (var state in trait.State) { states.Add(state.Key, state.Value.MapValueToGoogle(null)); } } } } else { // Copy the incoming state values, rather than getting current as they won't be updated yet var replacedParams = execution.Params .ToFlatDictionary() .ToDictionary(kvp => CommandToStateKeyMapper.Map(kvp.Key), kvp => kvp.Value) .ToNestedDictionary(); foreach (var param in replacedParams) { states.Add(param.Key, param.Value); } } } commandResponse.States = states; executionResponsePayload.Commands.Add(commandResponse); } return(executionResponsePayload); }
/// <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); }
/// <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(); foreach (var command in intent.Payload.Commands) { // Build response payload var commandResponse = new Models.Response.Command { Status = Models.Response.CommandStatus.Success, Ids = command.Devices.Select(x => x.Id).ToList() }; // Generate states var states = new Dictionary <string, object>(); foreach (var execution in command.Execution) { // Validate challenges var challengeResult = ValidateChallenges(command, execution); if (challengeResult != null) { commandResponse.Status = Models.Response.CommandStatus.Error; if (execution.Challenge != null) { // Challenge failed commandResponse.ErrorCode = challengeResult; commandResponse.ChallengeNeeded = new Models.Response.ChallengeResponse { Type = challengeResult }; } else { // Challenge required commandResponse.ErrorCode = "challengeNeeded"; commandResponse.ChallengeNeeded = new Models.Response.ChallengeResponse { Type = challengeResult }; } break; } // Dont bother with states for command delegation if (IsDelegatedCommand(command, execution)) { break; } // Handle camera stream commands if (execution.Command == "action.devices.commands.GetCameraStream") { // Only allow a single cast command at once if (command.Devices.Count() == 1) { // Get the first trait for the camera, as this should be the only trait available var trait = _deviceRepository.Get(command.Devices[0].Id).Traits.FirstOrDefault(); if (trait != null) { foreach (var state in trait.State) { states.Add(state.Key, state.Value.MapValueToGoogle(null)); } } } } else { // Copy the incoming state values, rather than getting current as they won't be updated yet var replacedParams = execution.Params .ToFlatDictionary() .ToDictionary(kvp => CommandToStateKeyMapper.Map(kvp.Key), kvp => kvp.Value) .ToNestedDictionary(); foreach (var param in replacedParams) { states.Add(param.Key, param.Value); } } } if (commandResponse.Status == Models.Response.CommandStatus.Success) { // Only add any processed states if there were no challenge or validation errors commandResponse.States = states; // Convert command to a event to publish now that its passed all verifications _messageHub.Publish(command); } executionResponsePayload.Commands.Add(commandResponse); } return(executionResponsePayload); }