-
Notifications
You must be signed in to change notification settings - Fork 0
/
RadBeaconReader.cs
416 lines (384 loc) · 21.2 KB
/
RadBeaconReader.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
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UniversalBeacon.Library.Core.Entities;
using UniversalBeacon.Library.Core.Interop;
using UniversalBeacon.Library.UWP;
using System.Diagnostics;
using System.IO;
using Microsoft.AppCenter;
using Microsoft.AppCenter.Analytics;
//using System.Runtime.InteropServices;
//using System.Threading;
namespace TrainSpotter
{
sealed public class RadBeaconReader
{
//private WindowsBluetoothPacketProvider _provider;
private BeaconManager _beaconManager;
private bool _restartingBeaconWatch;
//private struct BLUETOOTH_FIND_RADIO_PARAM
//{
// internal UInt32 dwSize;
// internal void Initialize()
// {
// this.dwSize = (UInt32)Marshal.SizeOf(typeof(BLUETOOTH_FIND_RADIO_PARAM));
// }
//}
/// <summary>
/// Closes an open object handle.
/// </summary>
/// <param name="handle">[In] A valid handle to an open object.</param>
///// <returns>If the function succeeds, the return value is nonzero. If the function fails, the return value is zero. To get extended error information, call GetLastError.</returns>
//[DllImport("Kernel32.dll", SetLastError = true)]
//static extern bool CloseHandle(IntPtr handle);
/// <summary>
/// Finds the first bluetooth radio present in device manager
/// </summary>
/// <param name="pbtfrp">Pointer to a BLUETOOTH_FIND_RADIO_PARAMS structure</param>
/// <param name="phRadio">Pointer to where the first enumerated radio handle will be returned. When no longer needed, this handle must be closed via CloseHandle.</param>
/// <returns>In addition to the handle indicated by phRadio, calling this function will also create a HBLUETOOTH_RADIO_FIND handle for use with the BluetoothFindNextRadio function.
/// When this handle is no longer needed, it must be closed via the BluetoothFindRadioClose.
/// Returns NULL upon failure. Call the GetLastError function for more information on the error. The following table describe common errors:</returns>
//[DllImport("irprops.cpl", SetLastError = true)]
//static extern IntPtr BluetoothFindFirstRadio(ref BLUETOOTH_FIND_RADIO_PARAM pbtfrp, out IntPtr phRadio);
//[StructLayout(LayoutKind.Sequential)]
//private struct LE_SCAN_REQUEST
//{
// internal int scanType;
// internal ushort scanInterval;
// internal ushort scanWindow;
//}
//[DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Unicode)]
//static extern bool DeviceIoControl(IntPtr hDevice, uint dwIoControlCode,
//ref LE_SCAN_REQUEST lpInBuffer, uint nInBufferSize,
//IntPtr lpOutBuffer, uint nOutBufferSize,
//out uint lpBytesReturned, IntPtr lpOverlapped);
//public static void StartScanner(int scanType, ushort scanInterval, ushort scanWindow)
//{
// //Action<object> action = (object obj) => {
// // BLUETOOTH_FIND_RADIO_PARAM param = new BLUETOOTH_FIND_RADIO_PARAM();
// // param.Initialize();
// // IntPtr handle;
// // BluetoothFindFirstRadio(ref param, out handle);
// // uint outsize;
// // LE_SCAN_REQUEST req = new LE_SCAN_REQUEST
// // {
// // scanType = scanType,
// // scanInterval = scanInterval,
// // scanWindow = scanWindow
// // };
// // DeviceIoControl(handle, 0x41118c, ref req, 8, IntPtr.Zero, 0, out outsize, IntPtr.Zero);
// //};
// //Task task = new Task(action, "nothing");
// //task.Start();
//}
public void StartBeaconManager()
{
//StartScanner(0, 29, 29);
var provider = new WindowsBluetoothPacketProvider();
_beaconManager = new BeaconManager(provider);
_beaconManager.BeaconAdded += StartBeaconMonitoringAsync;
_beaconManager.Start();
if (provider.WatcherStatus == BLEAdvertisementWatcherStatusCodes.Started)
{
Analytics.TrackEvent("StartBeaconManager", new Dictionary<string, string> {
{ "Category", "Beacon:" + provider.WatcherStatus + ", " + BLEAdvertisementWatcherStatusCodes.Started},
{ "Function", "StartBeaconManager"}
});
//System.Diagnostics.Debug.WriteLine("WatchingForBeacons");
}
}
public void StopWatchingBeaconManager()
{
System.Diagnostics.Debug.WriteLine("StoppedWatchingBeacons");
_beaconManager.BeaconAdded -= StartBeaconMonitoringAsync;
_beaconManager.Stop();
_restartingBeaconWatch = false;
Analytics.TrackEvent("StopWatchingBeaconManager", new Dictionary<string, string> {
{ "Category", "Beacon:" + _restartingBeaconWatch + ", " + BLEAdvertisementWatcherStatusCodes.Started},
{ "Function", "StopWatchingBeaconManager"}
});
}
private async void StartBeaconMonitoringAsync(object sender, Beacon beacon)
{
Ts trainMetaData = new Ts();
trainMetaData.Date = beacon.Timestamp;
var trainData = new TrainSpotterMessage();
try
{
trainData.Rssi = beacon.Rssi;
trainData.Mac = beacon.BluetoothAddressAsString;
trainData.TrainBeacon = beacon.BluetoothAddressAsString;
trainData.LastUpdate = beacon.Timestamp;
Analytics.TrackEvent("StartBeaconMonitoringAsync", new Dictionary<string, string> {
{ "Category", "Beacon:" + trainData.TrainBeacon},
{ "Function", "StartBeaconMonitoringAsync"}
});
foreach (var bluetoothBeacon in _beaconManager.BluetoothBeacons.ToList())
{
foreach (var beaconFrame in bluetoothBeacon.BeaconFrames.ToList()) //beacon.BeaconFrames.ToList())
{
if (beaconFrame is UidEddystoneFrame) //https://github.com/google/eddystone/tree/master/eddystone-uid
{
trainData.BeaconType = "UidEddystoneFrame";
trainData.Name = "Eddystone UID Frame";
trainData.Distance = ((UidEddystoneFrame)beaconFrame).RangingData; //Calibrated Tx power at 0 m - value is an 8-bit integer and ranges from -100 dBm to +20 dBm
// Note to developers: the best way to determine the precise value to put into this field is to measure the actual output of your beacon from 1 meter away and then add 41 dBm to that. 41dBm is the signal loss that occurs over 1 meter.
trainData.Namespace = ((UidEddystoneFrame)beaconFrame).NamespaceIdAsNumber.ToString("X") + " / " + ((UidEddystoneFrame)beaconFrame).InstanceIdAsNumber.ToString("X");
}
else if (beaconFrame is UrlEddystoneFrame) //https://github.com/google/eddystone/tree/master/eddystone-url
{
trainData.BeaconType = "UrlEddystoneFrame";
trainData.Name = "Eddystone URL Frame";
trainData.Url = ((UrlEddystoneFrame)beaconFrame).CompleteUrl;
trainData.Distance = ((UrlEddystoneFrame)beaconFrame).RangingData;
}
else if (beaconFrame is TlmEddystoneFrame) //https://github.com/google/eddystone/tree/master/eddystone-tlm
{
trainData.BeaconType = "TlmEddystoneFrame";
trainData.Name = "Eddystone Telemetry Frame";
trainData.Temperature = ((TlmEddystoneFrame)beaconFrame).TemperatureInC;
trainData.Battery = ((TlmEddystoneFrame)beaconFrame).BatteryInMilliV;
trainData.Uptime = ((TlmEddystoneFrame)beaconFrame).TimeSincePowerUp;
trainData.PacketsSent = ((TlmEddystoneFrame)beaconFrame).AdvertisementFrameCount;
}
else if (beaconFrame is EidEddystoneFrame) //https://github.com/google/eddystone/tree/master/eddystone-eid
{
trainData.BeaconType = "EidEddystoneFrame";
trainData.Name = "Eddystone EID Frame";
trainData.Distance = ((EidEddystoneFrame)beaconFrame).RangingData;
//trainData.Eid = BitConverter.ToString(((EidEddystoneFrame)beaconFrame).EphemeralIdentifier);
}
await AzureIoTHub.SendDeviceToCloudMessageAsync(trainData); //send the data only for recognized data types
Analytics.TrackEvent("Sent Data", new Dictionary<string, string> {
{ "Category", "Beacon:" + trainData.LastUpdate + ", " + trainData.Name + ", " + trainData.Url},
{ "Function", "StartBeaconMonitoringAsync"}
});
await Task.Delay(1000);
}
}
}
catch (Exception ex)
{
Debug.WriteLine("Error in Parsing Data: " + ex.Message);
await AzureIoTHub.SendDeviceToCloudMessageAsync(trainData); //send what we can...
Analytics.TrackEvent("Error Sent Data", new Dictionary<string, string> {
{ "Category", "Error:" + ex.Message.ToString()},
{ "Function", "StartBeaconMonitoringAsync"}
});
}
//StopWatchingBeaconManager(); //causes it to crash
//await Task.Delay(1000);
//StartBeaconManager();
}
#region Bluetooth Beacons
/// <summary>
/// Method demonstrating how to handle individual new beacons found by the manager.
/// This event will only be invoked once, the very first time a beacon is discovered.
/// For more fine-grained status updates, subscribe to changes of the ObservableCollection in
/// BeaconManager.BluetoothBeacons (_beaconManager).
/// To handle all individual received Bluetooth packets in your main app and outside of the
/// library, subscribe to AdvertisementPacketReceived event of the IBluetoothPacketProvider
/// (_provider).
/// </summary>
/// <param name="sender">Reference to the sender instance of the event.</param>
/// <param name="beacon">Beacon class instance containing all known and parsed information about
/// the Bluetooth beacon.</param>
private void BeaconManagerOnBeaconAdded(object sender, Beacon beacon)
{
Debug.WriteLine("\nBeacon: " + beacon.BluetoothAddressAsString);
Debug.WriteLine("Type: " + beacon.BeaconType);
Debug.WriteLine("Last Update: " + beacon.Timestamp);
Debug.WriteLine("RSSI: " + beacon.Rssi);
Analytics.TrackEvent("Added Beacon", new Dictionary<string, string> {
{ "Beacon", "Error:" + beacon.BluetoothAddressAsString},
{ "Function", "BeaconManagerOnBeaconAdded"}
});
foreach (var beaconFrame in beacon.BeaconFrames.ToList())
{
// Print a small sample of the available data parsed by the library
if (beaconFrame is UidEddystoneFrame)
{
Debug.WriteLine("Eddystone UID Frame");
Debug.WriteLine("ID: " + ((UidEddystoneFrame)beaconFrame).NamespaceIdAsNumber.ToString("X") + " / " +
((UidEddystoneFrame)beaconFrame).InstanceIdAsNumber.ToString("X"));
}
else if (beaconFrame is UrlEddystoneFrame)
{
Debug.WriteLine("Eddystone URL Frame");
Debug.WriteLine("URL: " + ((UrlEddystoneFrame)beaconFrame).CompleteUrl);
}
else if (beaconFrame is TlmEddystoneFrame)
{
Debug.WriteLine("Eddystone Telemetry Frame");
Debug.WriteLine("Temperature [°C]: " + ((TlmEddystoneFrame)beaconFrame).TemperatureInC);
Debug.WriteLine("Battery [mV]: " + ((TlmEddystoneFrame)beaconFrame).BatteryInMilliV);
}
else if (beaconFrame is EidEddystoneFrame)
{
Debug.WriteLine("Eddystone EID Frame");
Debug.WriteLine("Ranging Data: " + ((EidEddystoneFrame)beaconFrame).RangingData);
Debug.WriteLine("Ephemeral Identifier: " + BitConverter.ToString(((EidEddystoneFrame)beaconFrame).EphemeralIdentifier));
}
else if (beaconFrame is ProximityBeaconFrame)
{
Debug.WriteLine("Proximity Beacon Frame (iBeacon compatible)");
Debug.WriteLine("Uuid: " + ((ProximityBeaconFrame)beaconFrame).UuidAsString);
Debug.WriteLine("Major: " + ((ProximityBeaconFrame)beaconFrame).MajorAsString);
Debug.WriteLine("Major: " + ((ProximityBeaconFrame)beaconFrame).MinorAsString);
}
else
{
Debug.WriteLine("Unknown frame - not parsed by the library, write your own derived beacon frame type!");
Debug.WriteLine("Payload: " + BitConverter.ToString(((UnknownBeaconFrame)beaconFrame).Payload));
}
}
}
private void WatcherOnStopped(object sender, BTError btError)
{
string errorMsg = null;
if (btError != null)
{
switch (btError.BluetoothErrorCode)
{
case BTError.BluetoothError.Success:
errorMsg = "WatchingSuccessfullyStopped";
break;
case BTError.BluetoothError.RadioNotAvailable:
errorMsg = "ErrorNoRadioAvailable";
break;
case BTError.BluetoothError.ResourceInUse:
errorMsg = "ErrorResourceInUse";
break;
case BTError.BluetoothError.DeviceNotConnected:
errorMsg = "ErrorDeviceNotConnected";
break;
case BTError.BluetoothError.DisabledByPolicy:
errorMsg = "ErrorDisabledByPolicy";
break;
case BTError.BluetoothError.NotSupported:
errorMsg = "ErrorNotSupported";
break;
case BTError.BluetoothError.OtherError:
errorMsg = "ErrorOtherError";
break;
case BTError.BluetoothError.DisabledByUser:
errorMsg = "ErrorDisabledByUser";
break;
case BTError.BluetoothError.ConsentRequired:
errorMsg = "ErrorConsentRequired";
break;
case BTError.BluetoothError.TransportNotSupported:
errorMsg = "ErrorTransportNotSupported";
break;
default:
throw new ArgumentOutOfRangeException();
}
}
if (errorMsg == null)
{
// All other errors - generic error message
errorMsg = _restartingBeaconWatch
? "FailedRestartingBluetoothWatch"
: "AbortedWatchingBeacons";
}
Analytics.TrackEvent("Watcher Stopped", new Dictionary<string, string> {
{ "Beacon", "Error:" + errorMsg},
{ "Function", "WatcherOnStopped"}
});
//SetStatusOutput(_resourceLoader.GetString(errorMsg));
}
#if DEBUG
private void PrintBeaconInfoExample()
{
Debug.WriteLine("Beacons discovered so far\n-------------------------");
foreach (var bluetoothBeacon in _beaconManager.BluetoothBeacons.ToList())
{
Debug.WriteLine("\nBeacon: " + bluetoothBeacon.BluetoothAddressAsString);
Debug.WriteLine("Type: " + bluetoothBeacon.BeaconType);
Debug.WriteLine("Last Update: " + bluetoothBeacon.Timestamp);
Debug.WriteLine("RSSI: " + bluetoothBeacon.Rssi);
foreach (var beaconFrame in bluetoothBeacon.BeaconFrames.ToList())
{
// Print a small sample of the available data parsed by the library
if (beaconFrame is UidEddystoneFrame)
{
Debug.WriteLine("Eddystone UID Frame");
Debug.WriteLine("ID: " + ((UidEddystoneFrame)beaconFrame).NamespaceIdAsNumber.ToString("X") + " / " +
((UidEddystoneFrame)beaconFrame).InstanceIdAsNumber.ToString("X"));
}
else if (beaconFrame is UrlEddystoneFrame)
{
Debug.WriteLine("Eddystone URL Frame");
Debug.WriteLine("URL: " + ((UrlEddystoneFrame)beaconFrame).CompleteUrl);
}
else if (beaconFrame is TlmEddystoneFrame)
{
Debug.WriteLine("Eddystone Telemetry Frame");
Debug.WriteLine("Temperature [°C]: " + ((TlmEddystoneFrame)beaconFrame).TemperatureInC);
Debug.WriteLine("Battery [mV]: " + ((TlmEddystoneFrame)beaconFrame).BatteryInMilliV);
}
else if (beaconFrame is EidEddystoneFrame)
{
Debug.WriteLine("Eddystone EID Frame");
Debug.WriteLine("Ranging Data: " + ((EidEddystoneFrame)beaconFrame).RangingData);
Debug.WriteLine("Ephemeral Identifier: " + BitConverter.ToString(((EidEddystoneFrame)beaconFrame).EphemeralIdentifier));
}
else if (beaconFrame is ProximityBeaconFrame)
{
Debug.WriteLine("Proximity Beacon Frame (iBeacon compatible)");
Debug.WriteLine("Uuid: " + ((ProximityBeaconFrame)beaconFrame).UuidAsString);
Debug.WriteLine("Major: " + ((ProximityBeaconFrame)beaconFrame).MajorAsString);
Debug.WriteLine("Major: " + ((ProximityBeaconFrame)beaconFrame).MinorAsString);
}
else
{
Debug.WriteLine("Unknown frame - not parsed by the library, write your own derived beacon frame type!");
Debug.WriteLine("Payload: " + BitConverter.ToString(((UnknownBeaconFrame)beaconFrame).Payload));
}
}
}
}
#endif
#endregion
#region Tools
/// <summary>
/// Convert minus-separated hex string to a byte array. Format example: "4E-66-63"
/// </summary>
/// <param name="hex"></param>
/// <returns></returns>
public static byte[] HexStringToByteArray(string hex)
{
// Remove all space characters
var hexPure = hex.Replace("-", "");
if (hexPure.Length % 2 != 0)
{
// No even length of the string
throw new Exception("No valid hex string");
}
var numberChars = hexPure.Length / 2;
var bytes = new byte[numberChars];
var sr = new StringReader(hexPure);
try
{
for (var i = 0; i < numberChars; i++)
{
bytes[i] = Convert.ToByte(new string(new[] { (char)sr.Read(), (char)sr.Read() }), 16);
}
}
catch (Exception)
{
throw new Exception("No valid hex string");
}
finally
{
sr.Dispose();
}
return bytes;
}
#endregion
}
}