-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathDevice.cs
253 lines (218 loc) · 8.63 KB
/
Device.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
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using SmartGlass.Common;
using SmartGlass.Connection;
using SmartGlass.Messaging;
using SmartGlass.Messaging.Discovery;
using SmartGlass.Messaging.Power;
using Org.BouncyCastle.X509;
using Newtonsoft.Json;
namespace SmartGlass
{
public class Device
{
private static readonly TimeSpan discoveryListenTime = TimeSpan.FromSeconds(1);
private static readonly TimeSpan pingTimeout = TimeSpan.FromSeconds(1);
private static readonly TimeSpan[] pingRetries = new TimeSpan[]
{
TimeSpan.FromMilliseconds(100),
TimeSpan.FromMilliseconds(250),
TimeSpan.FromMilliseconds(500)
};
public static JsonSerializerSettings GetJsonSerializerSettings()
{
var settings = new JsonSerializerSettings();
settings.Converters.Add(new Json.IPAddressConverter());
settings.Formatting = Formatting.Indented;
return settings;
}
public static Task<IEnumerable<Device>> DiscoverAsync(string liveId = null)
{
return Task.Run(() =>
{
using (var messageTransport = new MessageTransport())
{
var requestMessage = new PresenceRequestMessage();
return messageTransport.ReadMessages(discoveryListenTime,
() => messageTransport.SendAsync(requestMessage))
.OfType<PresenceResponseMessage>()
.DistinctBy(m => m.HardwareId)
.Where(m => liveId == null || m.Certificate.GetLiveId() == liveId)
.Select(m => new Device(m)).ToArray().AsEnumerable();
}
});
}
public static async Task<Device> PingAsync(IPAddress address)
{
return await PingAsync(address.ToString());
}
public static async Task<Device> PingAsync(string addressOrHostname)
{
using (var messageTransport = new MessageTransport(addressOrHostname))
{
var requestMessage = new PresenceRequestMessage();
var response = await Common.TaskExtensions.WithRetries(() =>
messageTransport.WaitForMessageAsync<PresenceResponseMessage>(pingTimeout,
() => messageTransport.SendAsync(requestMessage)),
pingRetries);
return new Device(response);
}
}
public static async Task<Device> PowerOnAsync(string liveId, int times = 5, int delay = 1000,
string addressOrHostname = null)
{
using (var messageTransport = new MessageTransport(addressOrHostname))
{
var poweronRequestMessage = new PowerOnMessage { LiveId = liveId };
for (var i = 0; i < times; i++)
{
await messageTransport.SendAsync(poweronRequestMessage);
await Task.Delay(delay);
}
var presenceRequestMessage = new PresenceRequestMessage();
var response = await Common.TaskExtensions.WithRetries(() =>
messageTransport.WaitForMessageAsync<PresenceResponseMessage>(pingTimeout,
() => messageTransport.SendAsync(presenceRequestMessage)),
pingRetries);
return new Device(response);
}
}
[JsonProperty("address")]
public IPAddress Address { get; private set; }
[JsonProperty("type")]
public DeviceType DeviceType { get; private set; }
[JsonProperty("name")]
public string Name { get; private set; }
[JsonProperty("liveid")]
public string LiveId { get; private set; }
[JsonProperty("uuid")]
public Guid HardwareId { get; private set; }
[JsonIgnore]
public DeviceFlags Flags { get; private set; }
[JsonIgnore]
public X509Certificate Certificate { get; private set; }
[JsonIgnore]
public DeviceState State { get; private set; }
/// <summary>
/// Initialize Device manually
/// </summary>
/// <param name="type">Type of device</param>
/// <param name="address">IP address</param>
/// <param name="name">Friendly name</param>
/// <param name="liveId">Live ID</param>
/// <param name="hardwareId">Hardware Id</param>
[JsonConstructor]
public Device(DeviceType type, IPAddress address, string name, string liveId, Guid hardwareId)
{
Address = address;
Flags = DeviceFlags.None;
DeviceType = type;
Name = name;
HardwareId = hardwareId;
Certificate = null;
LiveId = liveId;
State = DeviceState.Unavailable;
}
/// <summary>
/// Initializes a new instance of the <see cref="T:SmartGlass.Device"/>
/// class by copying from an existing instance.
/// NOTE: The new instance will have an initial state of
/// DeviceState.Unavailable
/// </summary>
/// <param name="copyFrom">Device object to copy from.</param>
public Device(Device copyFrom)
: this(copyFrom.DeviceType, copyFrom.Address, copyFrom.Name,
copyFrom.LiveId, copyFrom.HardwareId)
{
}
internal Device(PresenceResponseMessage message)
{
Address = message.Origin.Address;
Flags = message.Flags;
DeviceType = message.DeviceType;
Name = message.Name;
HardwareId = message.HardwareId;
Certificate = message.Certificate;
LiveId = message.Certificate.GetLiveId();
State = DeviceState.Available;
}
/// <summary>
/// Update instance's address, flags, name and certificate.
/// Also sets DeviceState to "Available"
/// </summary>
/// <param name="newParameters">Freshly discovered device</param>
void Update(Device newParameters)
{
if (LiveId != newParameters.LiveId)
throw new InvalidOperationException();
Address = newParameters.Address;
Flags = newParameters.Flags;
Name = newParameters.Name;
Certificate = newParameters.Certificate;
State = DeviceState.Available;
}
/// <summary>
/// Power on console by sending a PowerOn packet via multicast
/// </summary>
/// <returns>true if poweron was successful, false otherwise</returns>
public async Task<bool> PowerOnAsync()
{
var ipAddress = Address.ToString();
try
{
var device = await Device.PowerOnAsync(LiveId);
// Successfully powered on
Update(device);
}
catch (TimeoutException)
{
// Try again via IP address
try
{
var device = await Device.PowerOnAsync(
LiveId, addressOrHostname: ipAddress);
Update(device);
}
catch (TimeoutException)
{
return false;
}
}
return true;
}
/// <summary>
/// Ping console by LiveID and IP address.
/// Update certificate, name, flags and address if necessary.
/// On success, State is changed to "Available".
/// </summary>
/// <returns>true is console responded, false otherwise</returns>
public async Task<bool> PingAsync()
{
try
{
// Try to reach console by last known IP address
var deviceByAddress = await Device.PingAsync(Address.ToString());
// Console found
Update(deviceByAddress);
return true;
}
catch (TimeoutException)
{
// pass
}
// Also try to discover via multicast presence request
var deviceByLiveId = await Device.DiscoverAsync(LiveId);
if (deviceByLiveId.Count() > 0)
{
// Console found via multicast
Update(deviceByLiveId.First());
return true;
}
State = DeviceState.Unavailable;
return false;
}
}
}