1// Copyright 2015-present 650 Industries. All rights reserved.
2
3#import "EXKernel.h"
4#import "EXSensorManager.h"
5#import <CoreMotion/CoreMotion.h>
6
7@interface EXSensorManager ()
8
9@property (nonatomic, strong) CMMotionManager *manager;
10@property (nonatomic, strong) CMAltimeter *altimeter;
11@property (nonatomic, strong) NSMutableDictionary *accelerometerHandlers;
12@property (nonatomic, strong) NSMutableDictionary *barometerHandlers;
13@property (nonatomic, strong) NSMutableDictionary *deviceMotionHandlers;
14@property (nonatomic, strong) NSMutableDictionary *gyroscopeHandlers;
15@property (nonatomic, strong) NSMutableDictionary *magnetometerHandlers;
16@property (nonatomic, strong) NSMutableDictionary *magnetometerUncalibratedHandlers;
17
18@end
19
20@implementation EXSensorManager
21
22- (instancetype)init
23{
24  if (self = [super init]) {
25    _accelerometerHandlers = [[NSMutableDictionary alloc] init];
26    _barometerHandlers = [[NSMutableDictionary alloc] init];
27    _deviceMotionHandlers = [[NSMutableDictionary alloc] init];
28    _gyroscopeHandlers = [[NSMutableDictionary alloc] init];
29    _magnetometerHandlers = [[NSMutableDictionary alloc] init];
30    _magnetometerUncalibratedHandlers = [[NSMutableDictionary alloc] init];
31  }
32  return self;
33}
34
35- (CMMotionManager *)manager
36{
37  if (!_manager) {
38    _manager = [[CMMotionManager alloc] init];
39  }
40  return _manager;
41}
42
43- (CMAltimeter *)altimeter
44{
45  if (!_altimeter) {
46    _altimeter = [[CMAltimeter alloc] init];
47  }
48  return _altimeter;
49}
50
51
52- (void)dealloc
53{
54  [self.manager stopAccelerometerUpdates];
55  [self.manager stopDeviceMotionUpdates];
56  [self.manager stopGyroUpdates];
57  [self.manager stopMagnetometerUpdates];
58  [self.altimeter stopRelativeAltitudeUpdates];
59}
60
61- (void)sensorModuleDidSubscribeForAccelerometerUpdatesOfExperience:scopeKey
62                                            withHandler:(void (^)(NSDictionary *event))handlerBlock
63{
64  if ([self.manager isAccelerometerAvailable]) {
65    self.accelerometerHandlers[scopeKey] = handlerBlock;
66  }
67  if (![self.manager isAccelerometerActive]) {
68    [self.manager setAccelerometerUpdateInterval:0.1f];
69    [self.manager startAccelerometerUpdatesToQueue:[NSOperationQueue mainQueue] withHandler:^(CMAccelerometerData *data, NSError *error) {
70      for (void (^handler)(NSDictionary *) in self.accelerometerHandlers.allValues) {
71        handler(@{
72                  @"x": [NSNumber numberWithDouble:data.acceleration.x],
73                  @"y": [NSNumber numberWithDouble:data.acceleration.y],
74                  @"z": [NSNumber numberWithDouble:data.acceleration.z]
75                  });
76      }
77    }];
78  }
79}
80
81- (void)sensorModuleDidUnsubscribeForAccelerometerUpdatesOfExperience:scopeKey
82{
83  [self.accelerometerHandlers removeObjectForKey:scopeKey];
84  if (self.accelerometerHandlers.count == 0) {
85    [self.manager stopAccelerometerUpdates];
86  }
87}
88
89- (void)setAccelerometerUpdateInterval:(NSTimeInterval)intervalMs
90{
91  [self.manager setAccelerometerUpdateInterval:intervalMs];
92}
93
94- (void)sensorModuleDidSubscribeForDeviceMotionUpdatesOfExperience:(NSString *)scopeKey
95                                           withHandler:(void (^)(NSDictionary *event))handlerBlock
96{
97  if ([self.manager isDeviceMotionAvailable]) {
98    self.deviceMotionHandlers[scopeKey] = handlerBlock;
99  }
100  if (![self.manager isDeviceMotionActive]) {
101    [self activateDeviceMotionUpdates];
102  }
103}
104
105- (void)sensorModuleDidUnsubscribeForDeviceMotionUpdatesOfExperience:(NSString *)scopeKey
106{
107  [self.deviceMotionHandlers removeObjectForKey:scopeKey];
108  if (self.deviceMotionHandlers.count == 0 && self.magnetometerHandlers.count == 0) {
109    [self.manager stopDeviceMotionUpdates];
110  }
111}
112
113- (void)setDeviceMotionUpdateInterval:(NSTimeInterval)intervalMs
114{
115  [self.manager setDeviceMotionUpdateInterval:intervalMs];
116}
117
118- (void)sensorModuleDidSubscribeForGyroscopeUpdatesOfExperience:(NSString *)scopeKey
119                                        withHandler:(void (^)(NSDictionary *event))handlerBlock
120{
121  if ([self.manager isGyroAvailable]) {
122    self.gyroscopeHandlers[scopeKey] = handlerBlock;
123  }
124  if (![self.manager isGyroActive]) {
125    [self.manager setGyroUpdateInterval:0.1f];
126    [self.manager startGyroUpdatesToQueue:[NSOperationQueue mainQueue] withHandler:^(CMGyroData *data, NSError *error) {
127      for (void (^handler)(NSDictionary *) in self.gyroscopeHandlers.allValues) {
128        handler(@{
129                  @"x": [NSNumber numberWithDouble:data.rotationRate.x],
130                  @"y": [NSNumber numberWithDouble:data.rotationRate.y],
131                  @"z": [NSNumber numberWithDouble:data.rotationRate.z]
132                  });
133      }
134    }];
135  }
136}
137
138- (void)sensorModuleDidUnsubscribeForGyroscopeUpdatesOfExperience:(NSString *)scopeKey
139{
140  [self.gyroscopeHandlers removeObjectForKey:scopeKey];
141  if (self.gyroscopeHandlers.count == 0) {
142    [self.manager stopGyroUpdates];
143  }
144}
145
146- (void)setGyroscopeUpdateInterval:(NSTimeInterval)intervalMs
147{
148  [self.manager setGyroUpdateInterval:intervalMs];
149}
150
151- (void)sensorModuleDidSubscribeForMagnetometerUpdatesOfExperience:(NSString *)scopeKey
152                                           withHandler:(void (^)(NSDictionary *event))handlerBlock
153{
154  if ([self.manager isDeviceMotionAvailable]) {
155    self.magnetometerHandlers[scopeKey] = handlerBlock;
156  }
157  if (![self.manager isDeviceMotionActive]) {
158    [self activateDeviceMotionUpdates];
159  }
160}
161
162- (void)sensorModuleDidUnsubscribeForMagnetometerUpdatesOfExperience:(NSString *)scopeKey
163{
164  [self.magnetometerHandlers removeObjectForKey:scopeKey];
165  if (self.deviceMotionHandlers.count == 0 && self.magnetometerHandlers.count == 0) {
166    [self.manager stopDeviceMotionUpdates];
167  }
168}
169
170- (void)setMagnetometerUpdateInterval:(NSTimeInterval)intervalMs
171{
172  [self.manager setDeviceMotionUpdateInterval:intervalMs];
173}
174
175- (void)sensorModuleDidSubscribeForMagnetometerUncalibratedUpdatesOfExperience:(NSString *)scopeKey
176                                                       withHandler:(void (^)(NSDictionary *event))handlerBlock
177{
178  if ([self.manager isMagnetometerAvailable]) {
179    self.magnetometerUncalibratedHandlers[scopeKey] = handlerBlock;
180  }
181  if (![self.manager isMagnetometerActive]) {
182    [self.manager setMagnetometerUpdateInterval:0.1f];
183    [self.manager startMagnetometerUpdatesToQueue:[NSOperationQueue mainQueue] withHandler:^(CMMagnetometerData *data, NSError *error) {
184      for (void (^handler)(NSDictionary *) in self.magnetometerUncalibratedHandlers.allValues) {
185        handler(@{
186                  @"x": [NSNumber numberWithDouble:data.magneticField.x],
187                  @"y": [NSNumber numberWithDouble:data.magneticField.y],
188                  @"z": [NSNumber numberWithDouble:data.magneticField.z]
189                  });
190      }
191    }];
192  }
193}
194
195- (void)sensorModuleDidUnsubscribeForMagnetometerUncalibratedUpdatesOfExperience:(NSString *)scopeKey
196{
197  [self.magnetometerUncalibratedHandlers removeObjectForKey:scopeKey];
198  if (self.magnetometerUncalibratedHandlers.count == 0) {
199    [self.manager stopMagnetometerUpdates];
200  }
201}
202
203- (void)setMagnetometerUncalibratedUpdateInterval:(NSTimeInterval)intervalMs
204{
205  [self.manager setMagnetometerUpdateInterval:intervalMs];
206}
207
208- (float)getGravity
209{
210  return EXGravity;
211}
212
213- (void)activateDeviceMotionUpdates
214{
215  [self.manager setDeviceMotionUpdateInterval:0.1f];
216  [self.manager
217   startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXArbitraryCorrectedZVertical
218   toQueue:[NSOperationQueue mainQueue]
219   withHandler:^(CMDeviceMotion *data, NSError *error) {
220     UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
221     int orientationDegrees;
222     switch (orientation) {
223       case UIDeviceOrientationPortrait:
224         orientationDegrees = 0;
225         break;
226       case UIDeviceOrientationLandscapeLeft:
227         orientationDegrees = -90;
228         break;
229       case UIDeviceOrientationLandscapeRight:
230         orientationDegrees = 90;
231         break;
232       case UIDeviceOrientationPortraitUpsideDown:
233         orientationDegrees = 180;
234         break;
235       default:
236         orientationDegrees = 0;
237         break;
238     }
239
240     NSDictionary *result = @{
241                              @"acceleration": @{
242                                  @"x": @(data.userAcceleration.x * EXGravity),
243                                  @"y": @(data.userAcceleration.y * EXGravity),
244                                  @"z": @(data.userAcceleration.z * EXGravity)
245                                  },
246                              @"accelerationIncludingGravity": @{
247                                  @"x": @((data.userAcceleration.x + data.gravity.x) * EXGravity),
248                                  @"y": @((data.userAcceleration.y + data.gravity.y) * EXGravity),
249                                  @"z": @((data.userAcceleration.z + data.gravity.z) * EXGravity)
250                                  },
251                              @"rotation": @{
252                                  @"alpha": @(data.attitude.yaw),
253                                  @"beta": @(data.attitude.pitch),
254                                  @"gamma": @(data.attitude.roll),
255                                  },
256                              @"rotationRate" :@{
257                                  @"alpha": @(data.rotationRate.z),
258                                  @"beta": @(data.rotationRate.y),
259                                  @"gamma": @(data.rotationRate.x)
260                                  },
261                              @"orientation": @(orientationDegrees)
262                              };
263
264     // DeviceMotionUpdates handle DeviceMotion data as well as magnetic field
265     for (void (^handler)(NSDictionary *) in self.deviceMotionHandlers.allValues) {
266       handler(result);
267     }
268
269     for (void (^handler)(NSDictionary *) in self.magnetometerHandlers.allValues) {
270       handler(@{
271                 @"x": [NSNumber numberWithDouble:data.magneticField.field.x],
272                 @"y": [NSNumber numberWithDouble:data.magneticField.field.y],
273                 @"z": [NSNumber numberWithDouble:data.magneticField.field.z]
274                 });
275     }
276   }];
277}
278
279- (void)sensorModuleDidSubscribeForBarometerUpdatesOfExperience:(NSString *)scopeKey withHandler:(void (^)(NSDictionary *event))handlerBlock
280{
281  if ([self isBarometerAvailable]) {
282    _barometerHandlers[scopeKey] = handlerBlock;
283  }
284
285  __weak EXSensorManager *weakSelf = self;
286  [[self altimeter] startRelativeAltitudeUpdatesToQueue:[NSOperationQueue mainQueue] withHandler:^(CMAltitudeData * _Nullable data, NSError * _Nullable error) {
287    __strong EXSensorManager *strongSelf = weakSelf;
288    if (strongSelf && data) {
289      for (void (^handler)(NSDictionary *) in strongSelf.barometerHandlers.allValues) {
290        handler(@{
291                  @"pressure": @([data.pressure intValue] * 10), // conversion from kPa to hPa
292                  @"relativeAltitude": data.relativeAltitude,
293                  });
294      }
295    }
296  }];
297}
298
299- (void)sensorModuleDidUnsubscribeForBarometerUpdatesOfExperience:(NSString *)scopeKey
300{
301  [_barometerHandlers removeObjectForKey:scopeKey];
302  if (_barometerHandlers.count == 0) {
303    [_altimeter stopRelativeAltitudeUpdates];
304  }
305}
306
307- (void)setBarometerUpdateInterval:(NSTimeInterval)intervalMs
308{
309  // Do nothing
310}
311
312- (BOOL)isBarometerAvailable
313{
314  return [CMAltimeter isRelativeAltitudeAvailable];
315}
316
317- (BOOL)isAccelerometerAvailable {
318  return [self.manager isAccelerometerAvailable];
319}
320
321- (BOOL)isDeviceMotionAvailable {
322  return [self.manager isDeviceMotionAvailable];
323}
324
325- (BOOL)isGyroAvailable {
326  return [self.manager isGyroAvailable];
327}
328
329- (BOOL)isMagnetometerAvailable {
330  return [self.manager isMagnetometerAvailable];
331}
332
333- (BOOL)isMagnetometerUncalibratedAvailable {
334  return [self.manager isMagnetometerAvailable];
335}
336
337@end
338