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) NSMutableDictionary *accelerometerHandlers;
11@property (nonatomic, strong) NSMutableDictionary *deviceMotionHandlers;
12@property (nonatomic, strong) NSMutableDictionary *gyroscopeHandlers;
13@property (nonatomic, strong) NSMutableDictionary *magnetometerHandlers;
14@property (nonatomic, strong) NSMutableDictionary *magnetometerUncalibratedHandlers;
15
16@end
17
18@implementation EXSensorManager
19
20- (instancetype)init
21{
22  if (self = [super init]) {
23    _accelerometerHandlers = [[NSMutableDictionary alloc] init];
24    _deviceMotionHandlers = [[NSMutableDictionary alloc] init];
25    _gyroscopeHandlers = [[NSMutableDictionary alloc] init];
26    _magnetometerHandlers = [[NSMutableDictionary alloc] init];
27    _magnetometerUncalibratedHandlers = [[NSMutableDictionary alloc] init];
28  }
29  return self;
30}
31
32- (CMMotionManager *)manager
33{
34  if (!_manager) {
35    _manager = [[CMMotionManager alloc] init];
36  }
37  return _manager;
38}
39
40- (void)dealloc
41{
42  [self.manager stopAccelerometerUpdates];
43  [self.manager stopDeviceMotionUpdates];
44  [self.manager stopGyroUpdates];
45  [self.manager stopMagnetometerUpdates];
46}
47
48- (void)sensorModuleDidSubscribeForAccelerometerUpdates:(id)scopedSensorModule
49                                            withHandler:(void (^)(NSDictionary *event))handlerBlock
50{
51  if ([self.manager isAccelerometerAvailable]) {
52    self.accelerometerHandlers[((EXScopedEventEmitter *)scopedSensorModule).experienceId] = handlerBlock;
53  }
54  if (![self.manager isAccelerometerActive]) {
55    [self.manager setAccelerometerUpdateInterval:0.1f];
56    [self.manager startAccelerometerUpdatesToQueue:[NSOperationQueue mainQueue] withHandler:^(CMAccelerometerData *data, NSError *error) {
57      for (void (^handler)(NSDictionary *) in self.accelerometerHandlers.allValues) {
58        handler(@{
59                  @"x": [NSNumber numberWithDouble:data.acceleration.x],
60                  @"y": [NSNumber numberWithDouble:data.acceleration.y],
61                  @"z": [NSNumber numberWithDouble:data.acceleration.z]
62                  });
63      }
64    }];
65  }
66}
67
68- (void)sensorModuleDidUnsubscribeForAccelerometerUpdates:(id)scopedSensorModule
69{
70  [self.accelerometerHandlers removeObjectForKey:((EXScopedEventEmitter *)scopedSensorModule).experienceId];
71  if (self.accelerometerHandlers.count == 0) {
72    [self.manager stopAccelerometerUpdates];
73  }
74}
75
76- (void)setAccelerometerUpdateInterval:(NSTimeInterval)intervalMs
77{
78  [self.manager setAccelerometerUpdateInterval:intervalMs];
79}
80
81- (void)sensorModuleDidSubscribeForDeviceMotionUpdates:(id)scopedSensorModule
82                                           withHandler:(void (^)(NSDictionary *event))handlerBlock
83{
84  if ([self.manager isDeviceMotionAvailable]) {
85    self.deviceMotionHandlers[((EXScopedEventEmitter *)scopedSensorModule).experienceId] = handlerBlock;
86  }
87  if (![self.manager isDeviceMotionActive]) {
88    [self activateDeviceMotionUpdates];
89  }
90}
91
92- (void)sensorModuleDidUnsubscribeForDeviceMotionUpdates:(id)scopedSensorModule
93{
94  [self.deviceMotionHandlers removeObjectForKey:((EXScopedEventEmitter *)scopedSensorModule).experienceId];
95  if (self.deviceMotionHandlers.count == 0 && self.magnetometerHandlers.count == 0) {
96    [self.manager stopDeviceMotionUpdates];
97  }
98}
99
100- (void)setDeviceMotionUpdateInterval:(NSTimeInterval)intervalMs
101{
102  [self.manager setDeviceMotionUpdateInterval:intervalMs];
103}
104
105- (void)sensorModuleDidSubscribeForGyroscopeUpdates:(id)scopedSensorModule
106                                        withHandler:(void (^)(NSDictionary *event))handlerBlock
107{
108  if ([self.manager isGyroAvailable]) {
109    self.gyroscopeHandlers[((EXScopedEventEmitter *)scopedSensorModule).experienceId] = handlerBlock;
110  }
111  if (![self.manager isGyroActive]) {
112    [self.manager setGyroUpdateInterval:0.1f];
113    [self.manager startGyroUpdatesToQueue:[NSOperationQueue mainQueue] withHandler:^(CMGyroData *data, NSError *error) {
114      for (void (^handler)(NSDictionary *) in self.gyroscopeHandlers.allValues) {
115        handler(@{
116                  @"x": [NSNumber numberWithDouble:data.rotationRate.x],
117                  @"y": [NSNumber numberWithDouble:data.rotationRate.y],
118                  @"z": [NSNumber numberWithDouble:data.rotationRate.z]
119                  });
120      }
121    }];
122  }
123}
124
125- (void)sensorModuleDidUnsubscribeForGyroscopeUpdates:(id)scopedSensorModule
126{
127  [self.gyroscopeHandlers removeObjectForKey:((EXScopedEventEmitter *)scopedSensorModule).experienceId];
128  if (self.gyroscopeHandlers.count == 0) {
129    [self.manager stopGyroUpdates];
130  }
131}
132
133- (void)setGyroscopeUpdateInterval:(NSTimeInterval)intervalMs
134{
135  [self.manager setGyroUpdateInterval:intervalMs];
136}
137
138- (void)sensorModuleDidSubscribeForMagnetometerUpdates:(id)scopedSensorModule
139                                           withHandler:(void (^)(NSDictionary *event))handlerBlock
140{
141  if ([self.manager isDeviceMotionAvailable]) {
142    self.magnetometerHandlers[((EXScopedEventEmitter *)scopedSensorModule).experienceId] = handlerBlock;
143  }
144  if (![self.manager isDeviceMotionActive]) {
145    [self activateDeviceMotionUpdates];
146  }
147}
148
149- (void)sensorModuleDidUnsubscribeForMagnetometerUpdates:(id)scopedSensorModule
150{
151  [self.magnetometerHandlers removeObjectForKey:((EXScopedEventEmitter *)scopedSensorModule).experienceId];
152  if (self.deviceMotionHandlers.count == 0 && self.magnetometerHandlers.count == 0) {
153    [self.manager stopDeviceMotionUpdates];
154  }
155}
156
157- (void)setMagnetometerUpdateInterval:(NSTimeInterval)intervalMs
158{
159  [self.manager setMagnetometerUpdateInterval:intervalMs];
160}
161
162- (void)sensorModuleDidSubscribeForMagnetometerUncalibratedUpdates:(id)scopedSensorModule
163                                                       withHandler:(void (^)(NSDictionary *event))handlerBlock
164{
165  if ([self.manager isMagnetometerAvailable]) {
166    self.magnetometerUncalibratedHandlers[((EXScopedEventEmitter *)scopedSensorModule).experienceId] = handlerBlock;
167  }
168  if (![self.manager isMagnetometerActive]) {
169    [self.manager setMagnetometerUpdateInterval:0.1f];
170    [self.manager startMagnetometerUpdatesToQueue:[NSOperationQueue mainQueue] withHandler:^(CMMagnetometerData *data, NSError *error) {
171      for (void (^handler)(NSDictionary *) in self.magnetometerUncalibratedHandlers.allValues) {
172        handler(@{
173                  @"x": [NSNumber numberWithDouble:data.magneticField.x],
174                  @"y": [NSNumber numberWithDouble:data.magneticField.y],
175                  @"z": [NSNumber numberWithDouble:data.magneticField.z]
176                  });
177      }
178    }];
179  }
180}
181
182- (void)sensorModuleDidUnsubscribeForMagnetometerUncalibratedUpdates:(id)scopedSensorModule
183{
184  [self.magnetometerUncalibratedHandlers removeObjectForKey:((EXScopedEventEmitter *)scopedSensorModule).experienceId];
185  if (self.magnetometerUncalibratedHandlers.count == 0) {
186    [self.manager stopMagnetometerUpdates];
187  }
188}
189
190- (void)setMagnetometerUncalibratedUpdateInterval:(NSTimeInterval)intervalMs
191{
192  [self.manager setMagnetometerUpdateInterval:intervalMs];
193}
194
195- (void)activateDeviceMotionUpdates
196{
197  [self.manager setDeviceMotionUpdateInterval:0.1f];
198  [self.manager
199   startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXArbitraryCorrectedZVertical
200   toQueue:[NSOperationQueue mainQueue]
201   withHandler:^(CMDeviceMotion *data, NSError *error) {
202     UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
203     int orientationDegrees;
204     switch (orientation) {
205       case UIDeviceOrientationPortrait:
206         orientationDegrees = 0;
207         break;
208       case UIDeviceOrientationLandscapeLeft:
209         orientationDegrees = -90;
210         break;
211       case UIDeviceOrientationLandscapeRight:
212         orientationDegrees = 90;
213         break;
214       case UIDeviceOrientationPortraitUpsideDown:
215         orientationDegrees = 180;
216         break;
217       default:
218         orientationDegrees = 0;
219         break;
220     }
221
222     NSDictionary *result = @{
223                              @"acceleration": @{
224                                  @"x": @(data.userAcceleration.x * EXGravity),
225                                  @"y": @(data.userAcceleration.y * EXGravity),
226                                  @"z": @(data.userAcceleration.z * EXGravity)
227                                  },
228                              @"accelerationIncludingGravity": @{
229                                  @"x": @((data.userAcceleration.x + data.gravity.x) * EXGravity),
230                                  @"y": @((data.userAcceleration.y + data.gravity.y) * EXGravity),
231                                  @"z": @((data.userAcceleration.z + data.gravity.z) * EXGravity)
232                                  },
233                              @"rotation": @{
234                                  @"alpha": @(data.attitude.yaw),
235                                  @"beta": @(data.attitude.pitch),
236                                  @"gamma": @(data.attitude.roll),
237                                  },
238                              @"rotationRate" :@{
239                                  @"alpha": @(data.rotationRate.z),
240                                  @"beta": @(data.rotationRate.y),
241                                  @"gamma": @(data.rotationRate.x)
242                                  },
243                              @"orientation": @(orientationDegrees)
244                              };
245
246     // DeviceMotionUpdates handle DeviceMotion data as well as magnetic field
247     for (void (^handler)(NSDictionary *) in self.deviceMotionHandlers.allValues) {
248       handler(result);
249     }
250
251     for (void (^handler)(NSDictionary *) in self.magnetometerHandlers.allValues) {
252       handler(@{
253                 @"x": [NSNumber numberWithDouble:data.magneticField.field.x],
254                 @"y": [NSNumber numberWithDouble:data.magneticField.field.y],
255                 @"z": [NSNumber numberWithDouble:data.magneticField.field.z]
256                 });
257     }
258   }];
259}
260
261@end
262