forked from microsoft/Windows-appsample-huelightcontroller
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Cortana.cs
223 lines (209 loc) · 9.36 KB
/
Cortana.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Windows.ApplicationModel.AppService;
using Windows.ApplicationModel.Background;
using Windows.ApplicationModel.VoiceCommands;
using HueLibrary;
using System.Linq;
using Windows.Storage;
namespace BackgroundTasks
{
/// <summary>
/// Handler for background Cortana interactions with Hue.
/// </summary>
public sealed class LightControllerVoiceCommandService : IBackgroundTask
{
private VoiceCommandServiceConnection _voiceServiceConnection;
private VoiceCommand _voiceCommand;
private BackgroundTaskDeferral _deferral;
private Bridge _bridge;
private IEnumerable<Light> _lights;
private Dictionary<string, HsbColor> _colors;
/// <summary>
/// Entry point for the background task.
/// </summary>
public async void Run(IBackgroundTaskInstance taskInstance)
{
var triggerDetails = taskInstance.TriggerDetails as AppServiceTriggerDetails;
if (null != triggerDetails && triggerDetails.Name == "LightControllerVoiceCommandService")
{
_deferral = taskInstance.GetDeferral();
taskInstance.Canceled += (s, e) => _deferral.Complete();
if (true != await InitializeAsync(triggerDetails))
{
return;
}
// These command phrases are coded in the VoiceCommands.xml file.
switch (_voiceCommand.CommandName)
{
case "changeLightsState": await ChangeLightStateAsync(); break;
case "changeLightsColor": await SelectColorAsync(); break;
case "changeLightStateByName": await ChangeSpecificLightStateAsync(); break;
default: await _voiceServiceConnection.RequestAppLaunchAsync(
CreateCortanaResponse("Launching HueLightController")); break;
}
// keep alive for 1 second to ensure all HTTP requests sent.
await Task.Delay(1000);
_deferral.Complete();
}
}
/// <summary>
/// Handles the command to change the state of a specific light.
/// </summary>
private async Task ChangeSpecificLightStateAsync()
{
string name = _voiceCommand.Properties["name"][0];
string state = _voiceCommand.Properties["state"][0];
Light light = _lights.FirstOrDefault(x =>
x.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
if (null != light)
{
await ExecutePhrase(light, state);
var response = CreateCortanaResponse($"Turned {name} {state}.");
await _voiceServiceConnection.ReportSuccessAsync(response);
}
}
/// <summary>
/// Handles the command to change the state of all the lights.
/// </summary>
private async Task ChangeLightStateAsync()
{
string phrase = _voiceCommand.Properties["state"][0];
foreach (Light light in _lights)
{
await ExecutePhrase(light, phrase);
await Task.Delay(500); // wait 500ms between pings to ensure bridge isn't overloaded.
}
var response = CreateCortanaResponse($"Turned your lights {phrase.ToLower()}.");
await _voiceServiceConnection.ReportSuccessAsync(response);
}
/// <summary>
/// Handles an interaction with Cortana where the user selects
/// from randomly chosen colors to change the lights to.
/// </summary>
private async Task SelectColorAsync()
{
var userPrompt = new VoiceCommandUserMessage();
userPrompt.DisplayMessage = userPrompt.SpokenMessage =
"Here's some colors you can choose from.";
var userReprompt = new VoiceCommandUserMessage();
userReprompt.DisplayMessage = userReprompt.SpokenMessage =
"Sorry, didn't catch that. What color would you like to use?";
// Randomly select 6 colors for Cortana to show
var random = new Random();
var colorContentTiles = _colors.Select(x => new VoiceCommandContentTile
{
ContentTileType = VoiceCommandContentTileType.TitleOnly,
Title = x.Value.Name
}).OrderBy(x => random.Next()).Take(6);
var colorResponse = VoiceCommandResponse.CreateResponseForPrompt(
userPrompt, userReprompt, colorContentTiles);
var disambiguationResult = await
_voiceServiceConnection.RequestDisambiguationAsync(colorResponse);
if (null != disambiguationResult)
{
var selectedColor = disambiguationResult.SelectedItem.Title;
foreach (Light light in _lights)
{
await ExecutePhrase(light, selectedColor);
await Task.Delay(500);
}
var response = CreateCortanaResponse($"Turned your lights {selectedColor}.");
await _voiceServiceConnection.ReportSuccessAsync(response);
}
}
/// <summary>
/// Converts a phrase to a light command and executes it.
/// </summary>
private async Task ExecutePhrase(Light light, string phrase)
{
if (phrase == "On")
{
light.State.On = true;
}
else if (phrase == "Off")
{
light.State.On = false;
}
else if (_colors.ContainsKey(phrase))
{
light.State.Hue = _colors[phrase].H;
light.State.Saturation = _colors[phrase].S;
light.State.Brightness = _colors[phrase].B;
}
else
{
var response = CreateCortanaResponse("Launching HueLightController");
await _voiceServiceConnection.RequestAppLaunchAsync(response);
}
}
/// <summary>
/// Helper method for initalizing the voice service, bridge, and lights. Returns if successful.
/// </summary>
private async Task<bool> InitializeAsync(AppServiceTriggerDetails triggerDetails)
{
_voiceServiceConnection =
VoiceCommandServiceConnection.FromAppServiceTriggerDetails(triggerDetails);
_voiceServiceConnection.VoiceCommandCompleted += (s, e) => _deferral.Complete();
_voiceCommand = await _voiceServiceConnection.GetVoiceCommandAsync();
_colors = HsbColor.CreateAll().ToDictionary(x => x.Name);
var localStorage = ApplicationData.Current.LocalSettings.Values;
_bridge = new Bridge(
localStorage["bridgeIp"].ToString(), localStorage["userId"].ToString());
try
{
_lights = await _bridge.GetLightsAsync();
}
catch (Exception)
{
var response = CreateCortanaResponse("Sorry, I couldn't connect to your bridge.");
await _voiceServiceConnection.ReportFailureAsync(response);
return false;
}
if (!_lights.Any())
{
var response = CreateCortanaResponse("Sorry, I couldn't find any lights.");
await _voiceServiceConnection.ReportFailureAsync(response);
return false;
}
return true;
}
/// <summary>
/// Helper method for creating a message for Cortana to speak and write to the user.
/// </summary>
private VoiceCommandResponse CreateCortanaResponse(string message)
{
var userMessage = new VoiceCommandUserMessage()
{
DisplayMessage = message,
SpokenMessage = message
};
var response = VoiceCommandResponse.CreateResponse(userMessage);
return response;
}
}
}