1
+ //
2
+ // HeartRateMonitor.cs
3
+ //
4
+ // Author:
5
+ // Aaron Bockover <[email protected] >
6
+ //
7
+ // Copyright 2013 Xamarin, Inc.
8
+ //
9
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ // of this software and associated documentation files (the "Software"), to deal
11
+ // in the Software without restriction, including without limitation the rights
12
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ // copies of the Software, and to permit persons to whom the Software is
14
+ // furnished to do so, subject to the following conditions:
15
+ //
16
+ // The above copyright notice and this permission notice shall be included in
17
+ // all copies or substantial portions of the Software.
18
+ //
19
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25
+ // THE SOFTWARE.
26
+
27
+ using System ;
28
+
29
+ #if XAMMAC
30
+ using MonoMac . Foundation ;
31
+ using MonoMac . CoreBluetooth ;
32
+ #else
33
+ using MonoTouch . Foundation ;
34
+ using MonoTouch . CoreBluetooth ;
35
+ #endif
36
+
37
+ namespace Xamarin . HeartMonitor
38
+ {
39
+ public class HeartRateMonitor : CBPeripheralDelegate
40
+ {
41
+ static readonly CBUUID PeripheralUUID = CBUUID . FromPartial ( 0x180D ) ;
42
+ static readonly CBUUID HeartRateMeasurementCharacteristicUUID = CBUUID . FromPartial ( 0x2A37 ) ;
43
+ static readonly CBUUID BodySensorLocationCharacteristicUUID = CBUUID . FromPartial ( 0x2A38 ) ;
44
+ static readonly CBUUID HeartRateControlPointCharacteristicUUID = CBUUID . FromPartial ( 0x2A39 ) ;
45
+
46
+ public static void ScanForHeartRateMonitors ( CBCentralManager manager )
47
+ {
48
+ if ( manager == null ) {
49
+ throw new ArgumentNullException ( "manager" ) ;
50
+ }
51
+
52
+ manager . ScanForPeripherals ( PeripheralUUID ) ;
53
+ }
54
+
55
+ NSTimer beatTimer ;
56
+ bool disposed ;
57
+
58
+ public CBCentralManager Manager { get ; private set ; }
59
+ public CBPeripheral Peripheral { get ; private set ; }
60
+
61
+ public HeartRateMonitorLocation Location { get ; private set ; }
62
+ public HeartBeat CurrentHeartBeat { get ; private set ; }
63
+ public HeartBeat PreviousHeartBeat { get ; private set ; }
64
+
65
+ public event EventHandler < HeartBeatEventArgs > HeartRateUpdated ;
66
+ public event EventHandler HeartBeat ;
67
+ public event EventHandler LocationUpdated ;
68
+
69
+ public string Name {
70
+ get { return Peripheral . Name ; }
71
+ }
72
+
73
+ protected override void Dispose ( bool disposing )
74
+ {
75
+ disposed = true ;
76
+ if ( beatTimer != null ) {
77
+ beatTimer . Dispose ( ) ;
78
+ beatTimer = null ;
79
+ }
80
+
81
+ base . Dispose ( disposing ) ;
82
+ }
83
+
84
+ public HeartRateMonitor ( CBCentralManager manager , CBPeripheral peripheral )
85
+ {
86
+ if ( manager == null ) {
87
+ throw new ArgumentNullException ( "manager" ) ;
88
+ } else if ( peripheral == null ) {
89
+ throw new ArgumentNullException ( "peripheral" ) ;
90
+ }
91
+
92
+ Location = HeartRateMonitorLocation . Unknown ;
93
+
94
+ Manager = manager ;
95
+
96
+ Peripheral = peripheral ;
97
+ Peripheral . Delegate = this ;
98
+ Peripheral . DiscoverServices ( ) ;
99
+ }
100
+
101
+ public void Connect ( )
102
+ {
103
+ if ( disposed ) {
104
+ return ;
105
+ }
106
+
107
+ Manager . ConnectPeripheral ( Peripheral , new PeripheralConnectionOptions {
108
+ NotifyOnDisconnectionKey = true
109
+ } ) ;
110
+ }
111
+
112
+ public override void DiscoveredService ( CBPeripheral peripheral , NSError error )
113
+ {
114
+ if ( disposed ) {
115
+ return ;
116
+ }
117
+
118
+ foreach ( var service in peripheral . Services ) {
119
+ if ( service . UUID == PeripheralUUID ) {
120
+ peripheral . DiscoverCharacteristics ( service ) ;
121
+ }
122
+ }
123
+ }
124
+
125
+ public override void DiscoverCharacteristic ( CBPeripheral peripheral , CBService service , NSError error )
126
+ {
127
+ if ( disposed ) {
128
+ return ;
129
+ }
130
+
131
+ foreach ( var characteristic in service . Characteristics ) {
132
+ if ( characteristic . UUID == HeartRateMeasurementCharacteristicUUID ) {
133
+ service . Peripheral . SetNotifyValue ( true , characteristic ) ;
134
+ } else if ( characteristic . UUID == BodySensorLocationCharacteristicUUID ) {
135
+ service . Peripheral . ReadValue ( characteristic ) ;
136
+ } else if ( characteristic . UUID == HeartRateControlPointCharacteristicUUID ) {
137
+ service . Peripheral . WriteValue ( NSData . FromBytes ( ( IntPtr ) 1 , 1 ) ,
138
+ characteristic , CBCharacteristicWriteType . WithResponse ) ;
139
+ }
140
+ }
141
+ }
142
+
143
+ public override void UpdatedCharacterteristicValue ( CBPeripheral peripheral , CBCharacteristic characteristic , NSError error )
144
+ {
145
+ if ( disposed || error != null || characteristic . Value == null ) {
146
+ return ;
147
+ }
148
+
149
+ if ( characteristic . UUID == HeartRateMeasurementCharacteristicUUID ) {
150
+ UpdateHeartRate ( characteristic . Value ) ;
151
+ } else if ( characteristic . UUID == BodySensorLocationCharacteristicUUID ) {
152
+ UpdateBodySensorLocation ( characteristic . Value ) ;
153
+ }
154
+ }
155
+
156
+ protected virtual void OnHeartRateUpdated ( )
157
+ {
158
+ var handler = HeartRateUpdated ;
159
+ if ( handler != null ) {
160
+ handler ( this , new HeartBeatEventArgs ( PreviousHeartBeat , CurrentHeartBeat ) ) ;
161
+ }
162
+ }
163
+
164
+ protected virtual void OnHeartBeat ( )
165
+ {
166
+ var handler = HeartBeat ;
167
+ if ( handler != null ) {
168
+ handler ( this , EventArgs . Empty ) ;
169
+ }
170
+ }
171
+
172
+ protected virtual void OnLocationUpdated ( )
173
+ {
174
+ var handler = LocationUpdated ;
175
+ if ( handler != null ) {
176
+ handler ( this , EventArgs . Empty ) ;
177
+ }
178
+ }
179
+
180
+ void ScheduleBeatTimer ( )
181
+ {
182
+ if ( disposed ) {
183
+ return ;
184
+ }
185
+
186
+ if ( beatTimer != null ) {
187
+ beatTimer . Dispose ( ) ;
188
+ }
189
+
190
+ OnHeartBeat ( ) ;
191
+ beatTimer = NSTimer . CreateScheduledTimer ( 60 / ( double ) CurrentHeartBeat . Rate , ScheduleBeatTimer ) ;
192
+ }
193
+
194
+ /* to use the unsafe version of this method,
195
+ * 'tick' **Project > Options > Build > General > Allow 'unsafe' code** */
196
+ /*unsafe*/ void UpdateHeartRate ( NSData hr )
197
+ {
198
+ var now = DateTime . Now ;
199
+
200
+ // unsafe line
201
+ // var data = (byte *)hr.Bytes;
202
+
203
+ // replaced by safe lines
204
+ byte [ ] data = new byte [ hr . Length ] ;
205
+ System . Runtime . InteropServices . Marshal . Copy ( hr . Bytes , data , 0 , Convert . ToInt32 ( hr . Length ) ) ;
206
+ // end safe lines
207
+
208
+ ushort bpm = 0 ;
209
+ if ( ( data [ 0 ] & 0x01 ) == 0 ) {
210
+ bpm = data [ 1 ] ;
211
+ } else {
212
+ bpm = ( ushort ) data [ 1 ] ;
213
+ bpm = ( ushort ) ( ( ( bpm >> 8 ) & 0xFF ) | ( ( bpm << 8 ) & 0xFF00 ) ) ;
214
+ }
215
+
216
+ PreviousHeartBeat = CurrentHeartBeat ;
217
+ CurrentHeartBeat = new HeartBeat { Time = now , Rate = bpm } ;
218
+
219
+ OnHeartRateUpdated ( ) ;
220
+
221
+ if ( PreviousHeartBeat . Rate == 0 && CurrentHeartBeat . Rate != 0 ) {
222
+ OnHeartBeat ( ) ;
223
+ ScheduleBeatTimer ( ) ;
224
+ }
225
+ }
226
+
227
+ /* to use the unsafe version of this method,
228
+ * 'tick' **Project > Options > Build > General > Allow 'unsafe' code** */
229
+ /*unsafe*/ void UpdateBodySensorLocation ( NSData location )
230
+ {
231
+ // unsafe line
232
+ // var value = ((byte *)location.Bytes) [0];
233
+
234
+ // replaced by safe lines
235
+ byte [ ] data = new byte [ location . Length ] ;
236
+ System . Runtime . InteropServices . Marshal . Copy ( location . Bytes , data , 0 , Convert . ToInt32 ( location . Length ) ) ;
237
+ var value = ( int ) data [ 0 ] ;
238
+ // end safe lines
239
+
240
+ if ( value < 0 || value > ( byte ) HeartRateMonitorLocation . Reserved ) {
241
+ Location = HeartRateMonitorLocation . Unknown ;
242
+ } else {
243
+ Location = ( HeartRateMonitorLocation ) value ;
244
+ }
245
+
246
+ OnLocationUpdated ( ) ;
247
+ }
248
+ }
249
+ }
0 commit comments