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)sensorModuleDidSubscribeForAccelerometerUpdatesOfExperience:experienceId
49                                            withHandler:(void (^)(NSDictionary *event))handlerBlock
50{
51  if ([self.manager isAccelerometerAvailable]) {
52    self.accelerometerHandlers[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)sensorModuleDidUnsubscribeForAccelerometerUpdatesOfExperience:experienceId
69{
70  [self.accelerometerHandlers removeObjectForKey: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)sensorModuleDidSubscribeForDeviceMotionUpdatesOfExperience:(NSString *)experienceId
82                                           withHandler:(void (^)(NSDictionary *event))handlerBlock
83{
84  if ([self.manager isDeviceMotionAvailable]) {
85    self.deviceMotionHandlers[experienceId] = handlerBlock;
86  }
87  if (![self.manager isDeviceMotionActive]) {
88    [self activateDeviceMotionUpdates];
89  }
90}
91
92- (void)sensorModuleDidUnsubscribeForDeviceMotionUpdatesOfExperience:(NSString *)experienceId
93{
94  [self.deviceMotionHandlers removeObjectForKey: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)sensorModuleDidSubscribeForGyroscopeUpdatesOfExperience:(NSString *)experienceId
106                                        withHandler:(void (^)(NSDictionary *event))handlerBlock
107{
108  if ([self.manager isGyroAvailable]) {
109    self.gyroscopeHandlers[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)sensorModuleDidUnsubscribeForGyroscopeUpdatesOfExperience:(NSString *)experienceId
126{
127  [self.gyroscopeHandlers removeObjectForKey: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)sensorModuleDidSubscribeForMagnetometerUpdatesOfExperience:(NSString *)experienceId
139                                           withHandler:(void (^)(NSDictionary *event))handlerBlock
140{
141  if ([self.manager isDeviceMotionAvailable]) {
142    self.magnetometerHandlers[experienceId] = handlerBlock;
143  }
144  if (![self.manager isDeviceMotionActive]) {
145    [self activateDeviceMotionUpdates];
146  }
147}
148
149- (void)sensorModuleDidUnsubscribeForMagnetometerUpdatesOfExperience:(NSString *)experienceId
150{
151  [self.magnetometerHandlers removeObjectForKey: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 setDeviceMotionUpdateInterval:intervalMs];
160}
161
162- (void)sensorModuleDidSubscribeForMagnetometerUncalibratedUpdatesOfExperience:(NSString *)experienceId
163                                                       withHandler:(void (^)(NSDictionary *event))handlerBlock
164{
165  if ([self.manager isMagnetometerAvailable]) {
166    self.magnetometerUncalibratedHandlers[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)sensorModuleDidUnsubscribeForMagnetometerUncalibratedUpdatesOfExperience:(NSString *)experienceId
183{
184  [self.magnetometerUncalibratedHandlers removeObjectForKey: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- (float)getGravity
196{
197  return EXGravity;
198}
199
200- (void)activateDeviceMotionUpdates
201{
202  [self.manager setDeviceMotionUpdateInterval:0.1f];
203  [self.manager
204   startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXArbitraryCorrectedZVertical
205   toQueue:[NSOperationQueue mainQueue]
206   withHandler:^(CMDeviceMotion *data, NSError *error) {
207     UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
208     int orientationDegrees;
209     switch (orientation) {
210       case UIDeviceOrientationPortrait:
211         orientationDegrees = 0;
212         break;
213       case UIDeviceOrientationLandscapeLeft:
214         orientationDegrees = -90;
215         break;
216       case UIDeviceOrientationLandscapeRight:
217         orientationDegrees = 90;
218         break;
219       case UIDeviceOrientationPortraitUpsideDown:
220         orientationDegrees = 180;
221         break;
222       default:
223         orientationDegrees = 0;
224         break;
225     }
226
227     NSDictionary *result = @{
228                              @"acceleration": @{
229                                  @"x": @(data.userAcceleration.x * EXGravity),
230                                  @"y": @(data.userAcceleration.y * EXGravity),
231                                  @"z": @(data.userAcceleration.z * EXGravity)
232                                  },
233                              @"accelerationIncludingGravity": @{
234                                  @"x": @((data.userAcceleration.x + data.gravity.x) * EXGravity),
235                                  @"y": @((data.userAcceleration.y + data.gravity.y) * EXGravity),
236                                  @"z": @((data.userAcceleration.z + data.gravity.z) * EXGravity)
237                                  },
238                              @"rotation": @{
239                                  @"alpha": @(data.attitude.yaw),
240                                  @"beta": @(data.attitude.pitch),
241                                  @"gamma": @(data.attitude.roll),
242                                  },
243                              @"rotationRate" :@{
244                                  @"alpha": @(data.rotationRate.z),
245                                  @"beta": @(data.rotationRate.y),
246                                  @"gamma": @(data.rotationRate.x)
247                                  },
248                              @"orientation": @(orientationDegrees)
249                              };
250
251     // DeviceMotionUpdates handle DeviceMotion data as well as magnetic field
252     for (void (^handler)(NSDictionary *) in self.deviceMotionHandlers.allValues) {
253       handler(result);
254     }
255
256     for (void (^handler)(NSDictionary *) in self.magnetometerHandlers.allValues) {
257       handler(@{
258                 @"x": [NSNumber numberWithDouble:data.magneticField.field.x],
259                 @"y": [NSNumber numberWithDouble:data.magneticField.field.y],
260                 @"z": [NSNumber numberWithDouble:data.magneticField.field.z]
261                 });
262     }
263   }];
264}
265
266@end
267