hal/cocoa/smslib.mm

branch
TOR_BUG_9701
changeset 8
97036ab72558
equal deleted inserted replaced
-1:000000000000 0:9f0dd5a449ea
1 /*
2 * smslib.m
3 *
4 * SMSLib Sudden Motion Sensor Access Library
5 * Copyright (c) 2010 Suitable Systems
6 * All rights reserved.
7 *
8 * Developed by: Daniel Griscom
9 * Suitable Systems
10 * http://www.suitable.com
11 *
12 * Permission is hereby granted, free of charge, to any person obtaining a
13 * copy of this software and associated documentation files (the
14 * "Software"), to deal with the Software without restriction, including
15 * without limitation the rights to use, copy, modify, merge, publish,
16 * distribute, sublicense, and/or sell copies of the Software, and to
17 * permit persons to whom the Software is furnished to do so, subject to
18 * the following conditions:
19 *
20 * - Redistributions of source code must retain the above copyright notice,
21 * this list of conditions and the following disclaimers.
22 *
23 * - Redistributions in binary form must reproduce the above copyright
24 * notice, this list of conditions and the following disclaimers in the
25 * documentation and/or other materials provided with the distribution.
26 *
27 * - Neither the names of Suitable Systems nor the names of its
28 * contributors may be used to endorse or promote products derived from
29 * this Software without specific prior written permission.
30 *
31 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
32 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
33 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
34 * IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR
35 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
36 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
37 * SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE.
38 *
39 * For more information about SMSLib, see
40 * <http://www.suitable.com/tools/smslib.html>
41 * or contact
42 * Daniel Griscom
43 * Suitable Systems
44 * 1 Centre Street, Suite 204
45 * Wakefield, MA 01880
46 * (781) 665-0053
47 *
48 */
49
50 #import <IOKit/IOKitLib.h>
51 #import <sys/sysctl.h>
52 #import <math.h>
53 #import "smslib.h"
54
55 #pragma mark Internal structures
56
57 // Represents a single axis of a type of sensor.
58 typedef struct axisStruct {
59 int enabled; // Non-zero if axis is valid in this sensor
60 int index; // Location in struct of first byte
61 int size; // Number of bytes
62 float zerog; // Value meaning "zero g"
63 float oneg; // Change in value meaning "increase of one g"
64 // (can be negative if axis sensor reversed)
65 } axisStruct;
66
67 // Represents the configuration of a type of sensor.
68 typedef struct sensorSpec {
69 const char *model; // Prefix of model to be tested
70 const char *name; // Name of device to be read
71 unsigned int function; // Kernel function index
72 int recordSize; // Size of record to be sent/received
73 axisStruct axes[3]; // Description of three axes (X, Y, Z)
74 } sensorSpec;
75
76 // Configuration of all known types of sensors. The configurations are
77 // tried in order until one succeeds in returning data.
78 // All default values are set here, but each axis' zerog and oneg values
79 // may be changed to saved (calibrated) values.
80 //
81 // These values came from SeisMaCalibrate calibration reports. In general I've
82 // found the following:
83 // - All Intel-based SMSs have 250 counts per g, centered on 0, but the signs
84 // are different (and in one case two axes are swapped)
85 // - PowerBooks and iBooks all have sensors centered on 0, and reading
86 // 50-53 steps per gravity (but with differing polarities!)
87 // - PowerBooks and iBooks of the same model all have the same axis polarities
88 // - PowerBook and iBook access methods are model- and OS version-specific
89 //
90 // So, the sequence of tests is:
91 // - Try model-specific access methods. Note that the test is for a match to the
92 // beginning of the model name, e.g. the record with model name "MacBook"
93 // matches computer models "MacBookPro1,2" and "MacBook1,1" (and ""
94 // matches any model).
95 // - If no model-specific record's access fails, then try each model-independent
96 // access method in order, stopping when one works.
97 static const sensorSpec sensors[] = {
98 // ****** Model-dependent methods ******
99 // The PowerBook5,6 is one of the G4 models that seems to lose
100 // SMS access until the next reboot.
101 {"PowerBook5,6", "IOI2CMotionSensor", 21, 60, {
102 {1, 0, 1, 0, 51.5},
103 {1, 1, 1, 0, -51.5},
104 {1, 2, 1, 0, -51.5}
105 }
106 },
107 // The PowerBook5,7 is one of the G4 models that seems to lose
108 // SMS access until the next reboot.
109 {"PowerBook5,7", "IOI2CMotionSensor", 21, 60, {
110 {1, 0, 1, 0, 51.5},
111 {1, 1, 1, 0, 51.5},
112 {1, 2, 1, 0, 51.5}
113 }
114 },
115 // Access seems to be reliable on the PowerBook5,8
116 {"PowerBook5,8", "PMUMotionSensor", 21, 60, {
117 {1, 0, 1, 0, -51.5},
118 {1, 1, 1, 0, 51.5},
119 {1, 2, 1, 0, -51.5}
120 }
121 },
122 // Access seems to be reliable on the PowerBook5,9
123 {"PowerBook5,9", "PMUMotionSensor", 21, 60, {
124 {1, 0, 1, 0, 51.5},
125 {1, 1, 1, 0, -51.5},
126 {1, 2, 1, 0, -51.5}
127 }
128 },
129 // The PowerBook6,7 is one of the G4 models that seems to lose
130 // SMS access until the next reboot.
131 {"PowerBook6,7", "IOI2CMotionSensor", 21, 60, {
132 {1, 0, 1, 0, 51.5},
133 {1, 1, 1, 0, 51.5},
134 {1, 2, 1, 0, 51.5}
135 }
136 },
137 // The PowerBook6,8 is one of the G4 models that seems to lose
138 // SMS access until the next reboot.
139 {"PowerBook6,8", "IOI2CMotionSensor", 21, 60, {
140 {1, 0, 1, 0, 51.5},
141 {1, 1, 1, 0, 51.5},
142 {1, 2, 1, 0, 51.5}
143 }
144 },
145 // MacBook Pro Core 2 Duo 17". Note the reversed Y and Z axes.
146 {"MacBookPro2,1", "SMCMotionSensor", 5, 40, {
147 {1, 0, 2, 0, 251},
148 {1, 2, 2, 0, -251},
149 {1, 4, 2, 0, -251}
150 }
151 },
152 // MacBook Pro Core 2 Duo 15" AND 17" with LED backlight, introduced June '07.
153 // NOTE! The 17" machines have the signs of their X and Y axes reversed
154 // from this calibration, but there's no clear way to discriminate between
155 // the two machines.
156 {"MacBookPro3,1", "SMCMotionSensor", 5, 40, {
157 {1, 0, 2, 0, -251},
158 {1, 2, 2, 0, 251},
159 {1, 4, 2, 0, -251}
160 }
161 },
162 // ... specs?
163 {"MacBook5,2", "SMCMotionSensor", 5, 40, {
164 {1, 0, 2, 0, -251},
165 {1, 2, 2, 0, 251},
166 {1, 4, 2, 0, -251}
167 }
168 },
169 // ... specs?
170 {"MacBookPro5,1", "SMCMotionSensor", 5, 40, {
171 {1, 0, 2, 0, -251},
172 {1, 2, 2, 0, -251},
173 {1, 4, 2, 0, 251}
174 }
175 },
176 // ... specs?
177 {"MacBookPro5,2", "SMCMotionSensor", 5, 40, {
178 {1, 0, 2, 0, -251},
179 {1, 2, 2, 0, -251},
180 {1, 4, 2, 0, 251}
181 }
182 },
183 // This is speculative, based on a single user's report. Looks like the X and Y axes
184 // are swapped. This is true for no other known Appple laptop.
185 {"MacBookPro5,3", "SMCMotionSensor", 5, 40, {
186 {1, 2, 2, 0, -251},
187 {1, 0, 2, 0, -251},
188 {1, 4, 2, 0, -251}
189 }
190 },
191 // ... specs?
192 {"MacBookPro5,4", "SMCMotionSensor", 5, 40, {
193 {1, 0, 2, 0, -251},
194 {1, 2, 2, 0, -251},
195 {1, 4, 2, 0, 251}
196 }
197 },
198 // ****** Model-independent methods ******
199 // Seen once with PowerBook6,8 under system 10.3.9; I suspect
200 // other G4-based 10.3.* systems might use this
201 {"", "IOI2CMotionSensor", 24, 60, {
202 {1, 0, 1, 0, 51.5},
203 {1, 1, 1, 0, 51.5},
204 {1, 2, 1, 0, 51.5}
205 }
206 },
207 // PowerBook5,6 , PowerBook5,7 , PowerBook6,7 , PowerBook6,8
208 // under OS X 10.4.*
209 {"", "IOI2CMotionSensor", 21, 60, {
210 {1, 0, 1, 0, 51.5},
211 {1, 1, 1, 0, 51.5},
212 {1, 2, 1, 0, 51.5}
213 }
214 },
215 // PowerBook5,8 , PowerBook5,9 under OS X 10.4.*
216 {"", "PMUMotionSensor", 21, 60, {
217 // Each has two out of three gains negative, but it's different
218 // for the different models. So, this will be right in two out
219 // of three axis for either model.
220 {1, 0, 1, 0, -51.5},
221 {1, 1, 1, -6, -51.5},
222 {1, 2, 1, 0, -51.5}
223 }
224 },
225 // All MacBook, MacBookPro models. Hardware (at least on early MacBookPro 15")
226 // is Kionix KXM52-1050 three-axis accelerometer chip. Data is at
227 // http://kionix.com/Product-Index/product-index.htm. Specific MB and MBP models
228 // that use this are:
229 // MacBook1,1
230 // MacBook2,1
231 // MacBook3,1
232 // MacBook4,1
233 // MacBook5,1
234 // MacBook6,1
235 // MacBookAir1,1
236 // MacBookPro1,1
237 // MacBookPro1,2
238 // MacBookPro4,1
239 // MacBookPro5,5
240 {"", "SMCMotionSensor", 5, 40, {
241 {1, 0, 2, 0, 251},
242 {1, 2, 2, 0, 251},
243 {1, 4, 2, 0, 251}
244 }
245 }
246 };
247
248 #define SENSOR_COUNT (sizeof(sensors)/sizeof(sensorSpec))
249
250 #pragma mark Internal prototypes
251
252 static int getData(sms_acceleration *accel, int calibrated, id logObject, SEL logSelector);
253 static float getAxis(int which, int calibrated);
254 static int signExtend(int value, int size);
255 static NSString *getModelName(void);
256 static NSString *getOSVersion(void);
257 static BOOL loadCalibration(void);
258 static void storeCalibration(void);
259 static void defaultCalibration(void);
260 static void deleteCalibration(void);
261 static int prefIntRead(NSString *prefName, BOOL *success);
262 static void prefIntWrite(NSString *prefName, int prefValue);
263 static float prefFloatRead(NSString *prefName, BOOL *success);
264 static void prefFloatWrite(NSString *prefName, float prefValue);
265 static void prefDelete(NSString *prefName);
266 static void prefSynchronize(void);
267 // static long getMicroseconds(void);
268 float fakeData(NSTimeInterval time);
269
270 #pragma mark Static variables
271
272 static int debugging = NO; // True if debugging (synthetic data)
273 static io_connect_t connection; // Connection for reading accel values
274 static int running = NO; // True if we successfully started
275 static unsigned int sensorNum = 0; // The current index into sensors[]
276 static const char *serviceName; // The name of the current service
277 static char *iRecord, *oRecord; // Pointers to read/write records for sensor
278 static int recordSize; // Size of read/write records
279 static unsigned int function; // Which kernel function should be used
280 static float zeros[3]; // X, Y and Z zero calibration values
281 static float onegs[3]; // X, Y and Z one-g calibration values
282
283 #pragma mark Defines
284
285 // Pattern for building axis letter from axis number
286 #define INT_TO_AXIS(a) (a == 0 ? @"X" : a == 1 ? @"Y" : @"Z")
287 // Name of configuration for given axis' zero (axis specified by integer)
288 #define ZERO_NAME(a) [NSString stringWithFormat:@"%@-Axis-Zero", INT_TO_AXIS(a)]
289 // Name of configuration for given axis' oneg (axis specified by integer)
290 #define ONEG_NAME(a) [NSString stringWithFormat:@"%@-Axis-One-g", INT_TO_AXIS(a)]
291 // Name of "Is calibrated" preference
292 #define CALIBRATED_NAME (@"Calibrated")
293 // Application domain for SeisMac library
294 #define APP_ID ((CFStringRef)@"com.suitable.SeisMacLib")
295
296 // These #defines make the accelStartup code a LOT easier to read.
297 #undef LOG
298 #define LOG(message) \
299 if (logObject) { \
300 [logObject performSelector:logSelector withObject:message]; \
301 }
302 #define LOG_ARG(format, var1) \
303 if (logObject) { \
304 [logObject performSelector:logSelector \
305 withObject:[NSString stringWithFormat:format, var1]]; \
306 }
307 #define LOG_2ARG(format, var1, var2) \
308 if (logObject) { \
309 [logObject performSelector:logSelector \
310 withObject:[NSString stringWithFormat:format, var1, var2]]; \
311 }
312 #define LOG_3ARG(format, var1, var2, var3) \
313 if (logObject) { \
314 [logObject performSelector:logSelector \
315 withObject:[NSString stringWithFormat:format, var1, var2, var3]]; \
316 }
317
318 #pragma mark Function definitions
319
320 // This starts up the accelerometer code, trying each possible sensor
321 // specification. Note that for logging purposes it
322 // takes an object and a selector; the object's selector is then invoked
323 // with a single NSString as argument giving progress messages. Example
324 // logging method:
325 // - (void)logMessage: (NSString *)theString
326 // which would be used in accelStartup's invocation thusly:
327 // result = accelStartup(self, @selector(logMessage:));
328 // If the object is nil, then no logging is done. Sets calibation from built-in
329 // value table. Returns ACCEL_SUCCESS for success, and other (negative)
330 // values for various failures (returns value indicating result of
331 // most successful trial).
332 int smsStartup(id logObject, SEL logSelector) {
333 io_iterator_t iterator;
334 io_object_t device;
335 kern_return_t result;
336 sms_acceleration accel;
337 int failure_result = SMS_FAIL_MODEL;
338
339 running = NO;
340 debugging = NO;
341
342 NSString *modelName = getModelName();
343
344 LOG_ARG(@"Machine model: %@\n", modelName);
345 LOG_ARG(@"OS X version: %@\n", getOSVersion());
346 LOG_ARG(@"Accelerometer library version: %s\n", SMSLIB_VERSION);
347
348 for (sensorNum = 0; sensorNum < SENSOR_COUNT; sensorNum++) {
349
350 // Set up all specs for this type of sensor
351 serviceName = sensors[sensorNum].name;
352 recordSize = sensors[sensorNum].recordSize;
353 function = sensors[sensorNum].function;
354
355 LOG_3ARG(@"Trying service \"%s\" with selector %d and %d byte record:\n",
356 serviceName, function, recordSize);
357
358 NSString *targetName = [NSString stringWithCString:sensors[sensorNum].model
359 encoding:NSMacOSRomanStringEncoding];
360 LOG_ARG(@" Comparing model name to target \"%@\": ", targetName);
361 if ([targetName length] == 0 || [modelName hasPrefix:targetName]) {
362 LOG(@"success.\n");
363 } else {
364 LOG(@"failure.\n");
365 // Don't need to increment failure_result.
366 continue;
367 }
368
369 LOG(@" Fetching dictionary for service: ");
370 CFMutableDictionaryRef dict = IOServiceMatching(serviceName);
371
372 if (dict) {
373 LOG(@"success.\n");
374 } else {
375 LOG(@"failure.\n");
376 if (failure_result < SMS_FAIL_DICTIONARY) {
377 failure_result = SMS_FAIL_DICTIONARY;
378 }
379 continue;
380 }
381
382 LOG(@" Getting list of matching services: ");
383 result = IOServiceGetMatchingServices(kIOMasterPortDefault,
384 dict,
385 &iterator);
386
387 if (result == KERN_SUCCESS) {
388 LOG(@"success.\n");
389 } else {
390 LOG_ARG(@"failure, with return value 0x%x.\n", result);
391 if (failure_result < SMS_FAIL_LIST_SERVICES) {
392 failure_result = SMS_FAIL_LIST_SERVICES;
393 }
394 continue;
395 }
396
397 LOG(@" Getting first device in list: ");
398 device = IOIteratorNext(iterator);
399
400 if (device == 0) {
401 LOG(@"failure.\n");
402 if (failure_result < SMS_FAIL_NO_SERVICES) {
403 failure_result = SMS_FAIL_NO_SERVICES;
404 }
405 continue;
406 } else {
407 LOG(@"success.\n");
408 LOG(@" Opening device: ");
409 }
410
411 result = IOServiceOpen(device, mach_task_self(), 0, &connection);
412
413 if (result != KERN_SUCCESS) {
414 LOG_ARG(@"failure, with return value 0x%x.\n", result);
415 IOObjectRelease(device);
416 if (failure_result < SMS_FAIL_OPENING) {
417 failure_result = SMS_FAIL_OPENING;
418 }
419 continue;
420 } else if (connection == 0) {
421 LOG_ARG(@"'success', but didn't get a connection (return value was: 0x%x).\n", result);
422 IOObjectRelease(device);
423 if (failure_result < SMS_FAIL_CONNECTION) {
424 failure_result = SMS_FAIL_CONNECTION;
425 }
426 continue;
427 } else {
428 IOObjectRelease(device);
429 LOG(@"success.\n");
430 }
431 LOG(@" Testing device.\n");
432
433 defaultCalibration();
434
435 iRecord = (char*) malloc(recordSize);
436 oRecord = (char*) malloc(recordSize);
437
438 running = YES;
439 result = getData(&accel, true, logObject, logSelector);
440 running = NO;
441
442 if (result) {
443 LOG_ARG(@" Failure testing device, with result 0x%x.\n", result);
444 free(iRecord);
445 iRecord = 0;
446 free(oRecord);
447 oRecord = 0;
448 if (failure_result < SMS_FAIL_ACCESS) {
449 failure_result = SMS_FAIL_ACCESS;
450 }
451 continue;
452 } else {
453 LOG(@" Success testing device!\n");
454 running = YES;
455 return SMS_SUCCESS;
456 }
457 }
458 return failure_result;
459 }
460
461 // This starts up the library in debug mode, ignoring the actual hardware.
462 // Returned data is in the form of 1Hz sine waves, with the X, Y and Z
463 // axes 120 degrees out of phase; "calibrated" data has range +/- (1.0/5);
464 // "uncalibrated" data has range +/- (256/5). X and Y axes centered on 0.0,
465 // Z axes centered on 1 (calibrated) or 256 (uncalibrated).
466 // Don't use smsGetBufferLength or smsGetBufferData. Always returns SMS_SUCCESS.
467 int smsDebugStartup(id logObject, SEL logSelector) {
468 LOG(@"Starting up in debug mode\n");
469 debugging = YES;
470 return SMS_SUCCESS;
471 }
472
473 // Returns the current calibration values.
474 void smsGetCalibration(sms_calibration *calibrationRecord) {
475 int x;
476
477 for (x = 0; x < 3; x++) {
478 calibrationRecord->zeros[x] = (debugging ? 0 : zeros[x]);
479 calibrationRecord->onegs[x] = (debugging ? 256 : onegs[x]);
480 }
481 }
482
483 // Sets the calibration, but does NOT store it as a preference. If the argument
484 // is nil then the current calibration is set from the built-in value table.
485 void smsSetCalibration(sms_calibration *calibrationRecord) {
486 int x;
487
488 if (!debugging) {
489 if (calibrationRecord) {
490 for (x = 0; x < 3; x++) {
491 zeros[x] = calibrationRecord->zeros[x];
492 onegs[x] = calibrationRecord->onegs[x];
493 }
494 } else {
495 defaultCalibration();
496 }
497 }
498 }
499
500 // Stores the current calibration values as a stored preference.
501 void smsStoreCalibration(void) {
502 if (!debugging)
503 storeCalibration();
504 }
505
506 // Loads the stored preference values into the current calibration.
507 // Returns YES if successful.
508 BOOL smsLoadCalibration(void) {
509 if (debugging) {
510 return YES;
511 } else if (loadCalibration()) {
512 return YES;
513 } else {
514 defaultCalibration();
515 return NO;
516 }
517 }
518
519 // Deletes any stored calibration, and then takes the current calibration values
520 // from the built-in value table.
521 void smsDeleteCalibration(void) {
522 if (!debugging) {
523 deleteCalibration();
524 defaultCalibration();
525 }
526 }
527
528 // Fills in the accel record with calibrated acceleration data. Takes
529 // 1-2ms to return a value. Returns 0 if success, error number if failure.
530 int smsGetData(sms_acceleration *accel) {
531 NSTimeInterval time;
532 if (debugging) {
533 usleep(1500); // Usually takes 1-2 milliseconds
534 time = [NSDate timeIntervalSinceReferenceDate];
535 accel->x = fakeData(time)/5;
536 accel->y = fakeData(time - 1)/5;
537 accel->z = fakeData(time - 2)/5 + 1.0;
538 return true;
539 } else {
540 return getData(accel, true, nil, nil);
541 }
542 }
543
544 // Fills in the accel record with uncalibrated acceleration data.
545 // Returns 0 if success, error number if failure.
546 int smsGetUncalibratedData(sms_acceleration *accel) {
547 NSTimeInterval time;
548 if (debugging) {
549 usleep(1500); // Usually takes 1-2 milliseconds
550 time = [NSDate timeIntervalSinceReferenceDate];
551 accel->x = fakeData(time) * 256 / 5;
552 accel->y = fakeData(time - 1) * 256 / 5;
553 accel->z = fakeData(time - 2) * 256 / 5 + 256;
554 return true;
555 } else {
556 return getData(accel, false, nil, nil);
557 }
558 }
559
560 // Returns the length of a raw block of data for the current type of sensor.
561 int smsGetBufferLength(void) {
562 if (debugging) {
563 return 0;
564 } else if (running) {
565 return sensors[sensorNum].recordSize;
566 } else {
567 return 0;
568 }
569 }
570
571 // Takes a pointer to accelGetRawLength() bytes; sets those bytes
572 // to return value from sensor. Make darn sure the buffer length is right!
573 void smsGetBufferData(char *buffer) {
574 IOItemCount iSize = recordSize;
575 IOByteCount oSize = recordSize;
576 kern_return_t result;
577
578 if (debugging || running == NO) {
579 return;
580 }
581
582 memset(iRecord, 1, iSize);
583 memset(buffer, 0, oSize);
584 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1050
585 const size_t InStructSize = recordSize;
586 size_t OutStructSize = recordSize;
587 result = IOConnectCallStructMethod(connection,
588 function, // magic kernel function number
589 (const void *)iRecord,
590 InStructSize,
591 (void *)buffer,
592 &OutStructSize
593 );
594 #else // __MAC_OS_X_VERSION_MIN_REQUIRED 1050
595 result = IOConnectMethodStructureIStructureO(connection,
596 function, // magic kernel function number
597 iSize,
598 &oSize,
599 iRecord,
600 buffer
601 );
602 #endif // __MAC_OS_X_VERSION_MIN_REQUIRED 1050
603
604 if (result != KERN_SUCCESS) {
605 running = NO;
606 }
607 }
608
609 // This returns an NSString describing the current calibration in
610 // human-readable form. Also include a description of the machine.
611 NSString *smsGetCalibrationDescription(void) {
612 BOOL success;
613 NSMutableString *s = [[NSMutableString alloc] init];
614
615 if (debugging) {
616 [s release];
617 return @"Debugging!";
618 }
619
620 [s appendString:@"---- SeisMac Calibration Record ----\n \n"];
621 [s appendFormat:@"Machine model: %@\n",
622 getModelName()];
623 [s appendFormat:@"OS X build: %@\n",
624 getOSVersion()];
625 [s appendFormat:@"SeisMacLib version %s, record %d\n \n",
626 SMSLIB_VERSION, sensorNum];
627 [s appendFormat:@"Using service \"%s\", function index %d, size %d\n \n",
628 serviceName, function, recordSize];
629 if (prefIntRead(CALIBRATED_NAME, &success) && success) {
630 [s appendString:@"Calibration values (from calibration):\n"];
631 } else {
632 [s appendString:@"Calibration values (from defaults):\n"];
633 }
634 [s appendFormat:@" X-Axis-Zero = %.2f\n", zeros[0]];
635 [s appendFormat:@" X-Axis-One-g = %.2f\n", onegs[0]];
636 [s appendFormat:@" Y-Axis-Zero = %.2f\n", zeros[1]];
637 [s appendFormat:@" Y-Axis-One-g = %.2f\n", onegs[1]];
638 [s appendFormat:@" Z-Axis-Zero = %.2f\n", zeros[2]];
639 [s appendFormat:@" Z-Axis-One-g = %.2f\n \n", onegs[2]];
640 [s appendString:@"---- End Record ----\n"];
641 return s;
642 }
643
644 // Shuts down the accelerometer.
645 void smsShutdown(void) {
646 if (!debugging) {
647 running = NO;
648 if (iRecord) free(iRecord);
649 if (oRecord) free(oRecord);
650 IOServiceClose(connection);
651 }
652 }
653
654 #pragma mark Internal functions
655
656 // Loads the current calibration from the stored preferences.
657 // Returns true iff successful.
658 BOOL loadCalibration(void) {
659 BOOL thisSuccess, allSuccess;
660 int x;
661
662 prefSynchronize();
663
664 if (prefIntRead(CALIBRATED_NAME, &thisSuccess) && thisSuccess) {
665 // Calibrated. Set all values from saved values.
666 allSuccess = YES;
667 for (x = 0; x < 3; x++) {
668 zeros[x] = prefFloatRead(ZERO_NAME(x), &thisSuccess);
669 allSuccess &= thisSuccess;
670 onegs[x] = prefFloatRead(ONEG_NAME(x), &thisSuccess);
671 allSuccess &= thisSuccess;
672 }
673 return allSuccess;
674 }
675
676 return NO;
677 }
678
679 // Stores the current calibration into the stored preferences.
680 static void storeCalibration(void) {
681 int x;
682 prefIntWrite(CALIBRATED_NAME, 1);
683 for (x = 0; x < 3; x++) {
684 prefFloatWrite(ZERO_NAME(x), zeros[x]);
685 prefFloatWrite(ONEG_NAME(x), onegs[x]);
686 }
687 prefSynchronize();
688 }
689
690
691 // Sets the calibration to its default values.
692 void defaultCalibration(void) {
693 int x;
694 for (x = 0; x < 3; x++) {
695 zeros[x] = sensors[sensorNum].axes[x].zerog;
696 onegs[x] = sensors[sensorNum].axes[x].oneg;
697 }
698 }
699
700 // Deletes the stored preferences.
701 static void deleteCalibration(void) {
702 int x;
703
704 prefDelete(CALIBRATED_NAME);
705 for (x = 0; x < 3; x++) {
706 prefDelete(ZERO_NAME(x));
707 prefDelete(ONEG_NAME(x));
708 }
709 prefSynchronize();
710 }
711
712 // Read a named floating point value from the stored preferences. Sets
713 // the success boolean based on, you guessed it, whether it succeeds.
714 static float prefFloatRead(NSString *prefName, BOOL *success) {
715 float result = 0.0f;
716
717 CFPropertyListRef ref = CFPreferencesCopyAppValue((CFStringRef)prefName,
718 APP_ID);
719 // If there isn't such a preference, fail
720 if (ref == NULL) {
721 *success = NO;
722 return result;
723 }
724 CFTypeID typeID = CFGetTypeID(ref);
725 // Is it a number?
726 if (typeID == CFNumberGetTypeID()) {
727 // Is it a floating point number?
728 if (CFNumberIsFloatType((CFNumberRef)ref)) {
729 // Yup: grab it.
730 *success = CFNumberGetValue((__CFNumber*)ref, kCFNumberFloat32Type, &result);
731 } else {
732 // Nope: grab as an integer, and convert to a float.
733 long num;
734 if (CFNumberGetValue((CFNumberRef)ref, kCFNumberLongType, &num)) {
735 result = num;
736 *success = YES;
737 } else {
738 *success = NO;
739 }
740 }
741 // Or is it a string (e.g. set by the command line "defaults" command)?
742 } else if (typeID == CFStringGetTypeID()) {
743 result = (float)CFStringGetDoubleValue((CFStringRef)ref);
744 *success = YES;
745 } else {
746 // Can't convert to a number: fail.
747 *success = NO;
748 }
749 CFRelease(ref);
750 return result;
751 }
752
753 // Writes a named floating point value to the stored preferences.
754 static void prefFloatWrite(NSString *prefName, float prefValue) {
755 CFNumberRef cfFloat = CFNumberCreate(kCFAllocatorDefault,
756 kCFNumberFloatType,
757 &prefValue);
758 CFPreferencesSetAppValue((CFStringRef)prefName,
759 cfFloat,
760 APP_ID);
761 CFRelease(cfFloat);
762 }
763
764 // Reads a named integer value from the stored preferences.
765 static int prefIntRead(NSString *prefName, BOOL *success) {
766 Boolean internalSuccess;
767 CFIndex result = CFPreferencesGetAppIntegerValue((CFStringRef)prefName,
768 APP_ID,
769 &internalSuccess);
770 *success = internalSuccess;
771
772 return result;
773 }
774
775 // Writes a named integer value to the stored preferences.
776 static void prefIntWrite(NSString *prefName, int prefValue) {
777 CFPreferencesSetAppValue((CFStringRef)prefName,
778 (CFNumberRef)[NSNumber numberWithInt:prefValue],
779 APP_ID);
780 }
781
782 // Deletes the named preference values.
783 static void prefDelete(NSString *prefName) {
784 CFPreferencesSetAppValue((CFStringRef)prefName,
785 NULL,
786 APP_ID);
787 }
788
789 // Synchronizes the local preferences with the stored preferences.
790 static void prefSynchronize(void) {
791 CFPreferencesAppSynchronize(APP_ID);
792 }
793
794 // Internal version of accelGetData, with logging
795 int getData(sms_acceleration *accel, int calibrated, id logObject, SEL logSelector) {
796 IOItemCount iSize = recordSize;
797 IOByteCount oSize = recordSize;
798 kern_return_t result;
799
800 if (running == NO) {
801 return -1;
802 }
803
804 memset(iRecord, 1, iSize);
805 memset(oRecord, 0, oSize);
806
807 LOG_2ARG(@" Querying device (%u, %d): ",
808 sensors[sensorNum].function, sensors[sensorNum].recordSize);
809
810 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1050
811 const size_t InStructSize = recordSize;
812 size_t OutStructSize = recordSize;
813 result = IOConnectCallStructMethod(connection,
814 function, // magic kernel function number
815 (const void *)iRecord,
816 InStructSize,
817 (void *)oRecord,
818 &OutStructSize
819 );
820 #else // __MAC_OS_X_VERSION_MIN_REQUIRED 1050
821 result = IOConnectMethodStructureIStructureO(connection,
822 function, // magic kernel function number
823 iSize,
824 &oSize,
825 iRecord,
826 oRecord
827 );
828 #endif // __MAC_OS_X_VERSION_MIN_REQUIRED 1050
829
830 if (result != KERN_SUCCESS) {
831 LOG(@"failed.\n");
832 running = NO;
833 return result;
834 } else {
835 LOG(@"succeeded.\n");
836
837 accel->x = getAxis(0, calibrated);
838 accel->y = getAxis(1, calibrated);
839 accel->z = getAxis(2, calibrated);
840 return 0;
841 }
842 }
843
844 // Given the returned record, extracts the value of the given axis. If
845 // calibrated, then zero G is 0.0, and one G is 1.0.
846 float getAxis(int which, int calibrated) {
847 // Get various values (to make code cleaner)
848 int indx = sensors[sensorNum].axes[which].index;
849 int size = sensors[sensorNum].axes[which].size;
850 float zerog = zeros[which];
851 float oneg = onegs[which];
852 // Storage for value to be returned
853 int value = 0;
854
855 // Although the values in the returned record should have the proper
856 // endianness, we still have to get it into the proper end of value.
857 #if (BYTE_ORDER == BIG_ENDIAN)
858 // On PowerPC processors
859 memcpy(((char *)&value) + (sizeof(int) - size), &oRecord[indx], size);
860 #endif
861 #if (BYTE_ORDER == LITTLE_ENDIAN)
862 // On Intel processors
863 memcpy(&value, &oRecord[indx], size);
864 #endif
865
866 value = signExtend(value, size);
867
868 if (calibrated) {
869 // Scale and shift for zero.
870 return ((float)(value - zerog)) / oneg;
871 } else {
872 return value;
873 }
874 }
875
876 // Extends the sign, given the length of the value.
877 int signExtend(int value, int size) {
878 // Extend sign
879 switch (size) {
880 case 1:
881 if (value & 0x00000080)
882 value |= 0xffffff00;
883 break;
884 case 2:
885 if (value & 0x00008000)
886 value |= 0xffff0000;
887 break;
888 case 3:
889 if (value & 0x00800000)
890 value |= 0xff000000;
891 break;
892 }
893 return value;
894 }
895
896 // Returns the model name of the computer (e.g. "MacBookPro1,1")
897 NSString *getModelName(void) {
898 char model[32];
899 size_t len = sizeof(model);
900 int name[2] = {CTL_HW, HW_MODEL};
901 NSString *result;
902
903 if (sysctl(name, 2, &model, &len, NULL, 0) == 0) {
904 result = [NSString stringWithFormat:@"%s", model];
905 } else {
906 result = @"";
907 }
908
909 return result;
910 }
911
912 // Returns the current OS X version and build (e.g. "10.4.7 (build 8J2135a)")
913 NSString *getOSVersion(void) {
914 NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:
915 @"/System/Library/CoreServices/SystemVersion.plist"];
916 NSString *versionString = [dict objectForKey:@"ProductVersion"];
917 NSString *buildString = [dict objectForKey:@"ProductBuildVersion"];
918 NSString *wholeString = [NSString stringWithFormat:@"%@ (build %@)",
919 versionString, buildString];
920 return wholeString;
921 }
922
923 // Returns time within the current second in microseconds.
924 // long getMicroseconds() {
925 // struct timeval t;
926 // gettimeofday(&t, 0);
927 // return t.tv_usec;
928 //}
929
930 // Returns fake data given the time. Range is +/-1.
931 float fakeData(NSTimeInterval time) {
932 long secs = lround(floor(time));
933 int secsMod3 = secs % 3;
934 double angle = time * 10 * M_PI * 2;
935 double mag = exp(-(time - (secs - secsMod3)) * 2);
936 return sin(angle) * mag;
937 }
938

mercurial