Bug 1883912: Enable Intl.ListFormat test for "unit" style. r=spidermonkey-reviewers...
[gecko.git] / hal / cocoa / smslib.mm
blobbd72b800321d698424fa51ca2fdc771214bf2655
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  */
50 #import <IOKit/IOKitLib.h>
51 #import <sys/sysctl.h>
52 #import <math.h>
53 #import "smslib.h"
55 #pragma mark Internal structures
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;
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;
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.
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 50-53
86 //   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
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 "" matches
94 //   any model).
95 // - If no model-specific record's access fails, then try each model-independent
96 //   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",
102      "IOI2CMotionSensor",
103      21,
104      60,
105      {{1, 0, 1, 0, 51.5}, {1, 1, 1, 0, -51.5}, {1, 2, 1, 0, -51.5}}},
106     // The PowerBook5,7 is one of the G4 models that seems to lose
107     // SMS access until the next reboot.
108     {"PowerBook5,7",
109      "IOI2CMotionSensor",
110      21,
111      60,
112      {{1, 0, 1, 0, 51.5}, {1, 1, 1, 0, 51.5}, {1, 2, 1, 0, 51.5}}},
113     // Access seems to be reliable on the PowerBook5,8
114     {"PowerBook5,8",
115      "PMUMotionSensor",
116      21,
117      60,
118      {{1, 0, 1, 0, -51.5}, {1, 1, 1, 0, 51.5}, {1, 2, 1, 0, -51.5}}},
119     // Access seems to be reliable on the PowerBook5,9
120     {"PowerBook5,9",
121      "PMUMotionSensor",
122      21,
123      60,
124      {{1, 0, 1, 0, 51.5}, {1, 1, 1, 0, -51.5}, {1, 2, 1, 0, -51.5}}},
125     // The PowerBook6,7 is one of the G4 models that seems to lose
126     // SMS access until the next reboot.
127     {"PowerBook6,7",
128      "IOI2CMotionSensor",
129      21,
130      60,
131      {{1, 0, 1, 0, 51.5}, {1, 1, 1, 0, 51.5}, {1, 2, 1, 0, 51.5}}},
132     // The PowerBook6,8 is one of the G4 models that seems to lose
133     // SMS access until the next reboot.
134     {"PowerBook6,8",
135      "IOI2CMotionSensor",
136      21,
137      60,
138      {{1, 0, 1, 0, 51.5}, {1, 1, 1, 0, 51.5}, {1, 2, 1, 0, 51.5}}},
139     // MacBook Pro Core 2 Duo 17". Note the reversed Y and Z axes.
140     {"MacBookPro2,1",
141      "SMCMotionSensor",
142      5,
143      40,
144      {{1, 0, 2, 0, 251}, {1, 2, 2, 0, -251}, {1, 4, 2, 0, -251}}},
145     // MacBook Pro Core 2 Duo 15" AND 17" with LED backlight, introduced June
146     // '07.
147     // NOTE! The 17" machines have the signs of their X and Y axes reversed
148     // from this calibration, but there's no clear way to discriminate between
149     // the two machines.
150     {"MacBookPro3,1",
151      "SMCMotionSensor",
152      5,
153      40,
154      {{1, 0, 2, 0, -251}, {1, 2, 2, 0, 251}, {1, 4, 2, 0, -251}}},
155     // ... specs?
156     {"MacBook5,2",
157      "SMCMotionSensor",
158      5,
159      40,
160      {{1, 0, 2, 0, -251}, {1, 2, 2, 0, 251}, {1, 4, 2, 0, -251}}},
161     // ... specs?
162     {"MacBookPro5,1",
163      "SMCMotionSensor",
164      5,
165      40,
166      {{1, 0, 2, 0, -251}, {1, 2, 2, 0, -251}, {1, 4, 2, 0, 251}}},
167     // ... specs?
168     {"MacBookPro5,2",
169      "SMCMotionSensor",
170      5,
171      40,
172      {{1, 0, 2, 0, -251}, {1, 2, 2, 0, -251}, {1, 4, 2, 0, 251}}},
173     // This is speculative, based on a single user's report. Looks like the X
174     // and Y axes
175     // are swapped. This is true for no other known Appple laptop.
176     {"MacBookPro5,3",
177      "SMCMotionSensor",
178      5,
179      40,
180      {{1, 2, 2, 0, -251}, {1, 0, 2, 0, -251}, {1, 4, 2, 0, -251}}},
181     // ... specs?
182     {"MacBookPro5,4",
183      "SMCMotionSensor",
184      5,
185      40,
186      {{1, 0, 2, 0, -251}, {1, 2, 2, 0, -251}, {1, 4, 2, 0, 251}}},
187     // ****** Model-independent methods ******
188     // Seen once with PowerBook6,8 under system 10.3.9; I suspect
189     // other G4-based 10.3.* systems might use this
190     {"",
191      "IOI2CMotionSensor",
192      24,
193      60,
194      {{1, 0, 1, 0, 51.5}, {1, 1, 1, 0, 51.5}, {1, 2, 1, 0, 51.5}}},
195     // PowerBook5,6 , PowerBook5,7 , PowerBook6,7 , PowerBook6,8
196     // under OS X 10.4.*
197     {"",
198      "IOI2CMotionSensor",
199      21,
200      60,
201      {{1, 0, 1, 0, 51.5}, {1, 1, 1, 0, 51.5}, {1, 2, 1, 0, 51.5}}},
202     // PowerBook5,8 , PowerBook5,9 under OS X 10.4.*
203     {"",
204      "PMUMotionSensor",
205      21,
206      60,
207      {// Each has two out of three gains negative, but it's different
208       // for the different models. So, this will be right in two out
209       // of three axis for either model.
210       {1, 0, 1, 0, -51.5},
211       {1, 1, 1, -6, -51.5},
212       {1, 2, 1, 0, -51.5}}},
213     // All MacBook, MacBookPro models. Hardware (at least on early MacBookPro
214     // 15")
215     // is Kionix KXM52-1050 three-axis accelerometer chip. Data is at
216     // http://kionix.com/Product-Index/product-index.htm. Specific MB and MBP
217     // models
218     // that use this are:
219     //          MacBook1,1
220     //          MacBook2,1
221     //          MacBook3,1
222     //          MacBook4,1
223     //          MacBook5,1
224     //          MacBook6,1
225     //          MacBookAir1,1
226     //          MacBookPro1,1
227     //          MacBookPro1,2
228     //          MacBookPro4,1
229     //          MacBookPro5,5
230     {"",
231      "SMCMotionSensor",
232      5,
233      40,
234      {{1, 0, 2, 0, 251}, {1, 2, 2, 0, 251}, {1, 4, 2, 0, 251}}}};
236 #define SENSOR_COUNT (sizeof(sensors) / sizeof(sensorSpec))
238 #pragma mark Internal prototypes
240 static int getData(sms_acceleration* accel, int calibrated, id logObject,
241                    SEL logSelector);
242 static float getAxis(int which, int calibrated);
243 static int signExtend(int value, int size);
244 static NSString* getModelName(void);
245 static NSString* getOSVersion(void);
246 static BOOL loadCalibration(void);
247 static void storeCalibration(void);
248 static void defaultCalibration(void);
249 static void deleteCalibration(void);
250 static int prefIntRead(NSString* prefName, BOOL* success);
251 static void prefIntWrite(NSString* prefName, int prefValue);
252 static float prefFloatRead(NSString* prefName, BOOL* success);
253 static void prefFloatWrite(NSString* prefName, float prefValue);
254 static void prefDelete(NSString* prefName);
255 static void prefSynchronize(void);
256 // static long getMicroseconds(void);
257 float fakeData(NSTimeInterval time);
259 #pragma mark Static variables
261 static int debugging = NO;          // True if debugging (synthetic data)
262 static io_connect_t connection;     // Connection for reading accel values
263 static int running = NO;            // True if we successfully started
264 static unsigned int sensorNum = 0;  // The current index into sensors[]
265 static const char* serviceName;     // The name of the current service
266 static char *iRecord, *oRecord;     // Pointers to read/write records for sensor
267 static int recordSize;              // Size of read/write records
268 static unsigned int function;       // Which kernel function should be used
269 static float zeros[3];              // X, Y and Z zero calibration values
270 static float onegs[3];              // X, Y and Z one-g calibration values
272 #pragma mark Defines
274 // Pattern for building axis letter from axis number
275 #define INT_TO_AXIS(a) (a == 0 ? @"X" : a == 1 ? @"Y" : @"Z")
276 // Name of configuration for given axis' zero (axis specified by integer)
277 #define ZERO_NAME(a) [NSString stringWithFormat:@"%@-Axis-Zero", INT_TO_AXIS(a)]
278 // Name of configuration for given axis' oneg (axis specified by integer)
279 #define ONEG_NAME(a) \
280   [NSString stringWithFormat:@"%@-Axis-One-g", INT_TO_AXIS(a)]
281 // Name of "Is calibrated" preference
282 #define CALIBRATED_NAME (@"Calibrated")
283 // Application domain for SeisMac library
284 #define APP_ID ((CFStringRef) @"com.suitable.SeisMacLib")
286 // These #defines make the accelStartup code a LOT easier to read.
287 #undef LOG
288 #define LOG(message)                                            \
289   if (logObject) {                                              \
290     [logObject performSelector:logSelector withObject:message]; \
291   }
292 #define LOG_ARG(format, var1)                                             \
293   if (logObject) {                                                        \
294     [logObject performSelector:logSelector                                \
295                     withObject:[NSString stringWithFormat:format, var1]]; \
296   }
297 #define LOG_2ARG(format, var1, var2)                                     \
298   if (logObject) {                                                       \
299     [logObject                                                           \
300         performSelector:logSelector                                      \
301              withObject:[NSString stringWithFormat:format, var1, var2]]; \
302   }
303 #define LOG_3ARG(format, var1, var2, var3)                                     \
304   if (logObject) {                                                             \
305     [logObject                                                                 \
306         performSelector:logSelector                                            \
307              withObject:[NSString stringWithFormat:format, var1, var2, var3]]; \
308   }
310 #pragma mark Function definitions
312 // This starts up the accelerometer code, trying each possible sensor
313 // specification. Note that for logging purposes it
314 // takes an object and a selector; the object's selector is then invoked
315 // with a single NSString as argument giving progress messages. Example
316 // logging method:
317 //              - (void)logMessage: (NSString *)theString
318 // which would be used in accelStartup's invocation thusly:
319 //              result = accelStartup(self, @selector(logMessage:));
320 // If the object is nil, then no logging is done. Sets calibation from built-in
321 // value table. Returns ACCEL_SUCCESS for success, and other (negative)
322 // values for various failures (returns value indicating result of
323 // most successful trial).
324 int smsStartup(id logObject, SEL logSelector) {
325   io_iterator_t iterator;
326   io_object_t device;
327   kern_return_t result;
328   sms_acceleration accel;
329   int failure_result = SMS_FAIL_MODEL;
331   running = NO;
332   debugging = NO;
334   NSString* modelName = getModelName();
336   LOG_ARG(@"Machine model: %@\n", modelName);
337   LOG_ARG(@"OS X version: %@\n", getOSVersion());
338   LOG_ARG(@"Accelerometer library version: %s\n", SMSLIB_VERSION);
340   for (sensorNum = 0; sensorNum < SENSOR_COUNT; sensorNum++) {
341     // Set up all specs for this type of sensor
342     serviceName = sensors[sensorNum].name;
343     recordSize = sensors[sensorNum].recordSize;
344     function = sensors[sensorNum].function;
346     LOG_3ARG(@"Trying service \"%s\" with selector %d and %d byte record:\n",
347              serviceName, function, recordSize);
349     NSString* targetName =
350         [NSString stringWithCString:sensors[sensorNum].model
351                            encoding:NSMacOSRomanStringEncoding];
352     LOG_ARG(@"    Comparing model name to target \"%@\": ", targetName);
353     if ([targetName length] == 0 || [modelName hasPrefix:targetName]) {
354       LOG(@"success.\n");
355     } else {
356       LOG(@"failure.\n");
357       // Don't need to increment failure_result.
358       continue;
359     }
361     LOG(@"    Fetching dictionary for service: ");
362     CFMutableDictionaryRef dict = IOServiceMatching(serviceName);
364     if (dict) {
365       LOG(@"success.\n");
366     } else {
367       LOG(@"failure.\n");
368       if (failure_result < SMS_FAIL_DICTIONARY) {
369         failure_result = SMS_FAIL_DICTIONARY;
370       }
371       continue;
372     }
374     LOG(@"    Getting list of matching services: ");
375     result =
376         IOServiceGetMatchingServices(kIOMasterPortDefault, dict, &iterator);
378     if (result == KERN_SUCCESS) {
379       LOG(@"success.\n");
380     } else {
381       LOG_ARG(@"failure, with return value 0x%x.\n", result);
382       if (failure_result < SMS_FAIL_LIST_SERVICES) {
383         failure_result = SMS_FAIL_LIST_SERVICES;
384       }
385       continue;
386     }
388     LOG(@"    Getting first device in list: ");
389     device = IOIteratorNext(iterator);
391     if (device == 0) {
392       LOG(@"failure.\n");
393       if (failure_result < SMS_FAIL_NO_SERVICES) {
394         failure_result = SMS_FAIL_NO_SERVICES;
395       }
396       continue;
397     } else {
398       LOG(@"success.\n");
399       LOG(@"    Opening device: ");
400     }
402     result = IOServiceOpen(device, mach_task_self(), 0, &connection);
404     if (result != KERN_SUCCESS) {
405       LOG_ARG(@"failure, with return value 0x%x.\n", result);
406       IOObjectRelease(device);
407       if (failure_result < SMS_FAIL_OPENING) {
408         failure_result = SMS_FAIL_OPENING;
409       }
410       continue;
411     } else if (connection == 0) {
412       LOG_ARG(
413           @"'success', but didn't get a connection (return value was: 0x%x).\n",
414           result);
415       IOObjectRelease(device);
416       if (failure_result < SMS_FAIL_CONNECTION) {
417         failure_result = SMS_FAIL_CONNECTION;
418       }
419       continue;
420     } else {
421       IOObjectRelease(device);
422       LOG(@"success.\n");
423     }
424     LOG(@"    Testing device.\n");
426     defaultCalibration();
428     iRecord = (char*)malloc(recordSize);
429     oRecord = (char*)malloc(recordSize);
431     running = YES;
432     result = getData(&accel, true, logObject, logSelector);
433     running = NO;
435     if (result) {
436       LOG_ARG(@"    Failure testing device, with result 0x%x.\n", result);
437       free(iRecord);
438       iRecord = 0;
439       free(oRecord);
440       oRecord = 0;
441       if (failure_result < SMS_FAIL_ACCESS) {
442         failure_result = SMS_FAIL_ACCESS;
443       }
444       continue;
445     } else {
446       LOG(@"    Success testing device!\n");
447       running = YES;
448       return SMS_SUCCESS;
449     }
450   }
451   return failure_result;
454 // This starts up the library in debug mode, ignoring the actual hardware.
455 // Returned data is in the form of 1Hz sine waves, with the X, Y and Z
456 // axes 120 degrees out of phase; "calibrated" data has range +/- (1.0/5);
457 // "uncalibrated" data has range +/- (256/5). X and Y axes centered on 0.0,
458 // Z axes centered on 1 (calibrated) or 256 (uncalibrated).
459 // Don't use smsGetBufferLength or smsGetBufferData. Always returns SMS_SUCCESS.
460 int smsDebugStartup(id logObject, SEL logSelector) {
461   LOG(@"Starting up in debug mode\n");
462   debugging = YES;
463   return SMS_SUCCESS;
466 // Returns the current calibration values.
467 void smsGetCalibration(sms_calibration* calibrationRecord) {
468   int x;
470   for (x = 0; x < 3; x++) {
471     calibrationRecord->zeros[x] = (debugging ? 0 : zeros[x]);
472     calibrationRecord->onegs[x] = (debugging ? 256 : onegs[x]);
473   }
476 // Sets the calibration, but does NOT store it as a preference. If the argument
477 // is nil then the current calibration is set from the built-in value table.
478 void smsSetCalibration(sms_calibration* calibrationRecord) {
479   int x;
481   if (!debugging) {
482     if (calibrationRecord) {
483       for (x = 0; x < 3; x++) {
484         zeros[x] = calibrationRecord->zeros[x];
485         onegs[x] = calibrationRecord->onegs[x];
486       }
487     } else {
488       defaultCalibration();
489     }
490   }
493 // Stores the current calibration values as a stored preference.
494 void smsStoreCalibration(void) {
495   if (!debugging) storeCalibration();
498 // Loads the stored preference values into the current calibration.
499 // Returns YES if successful.
500 BOOL smsLoadCalibration(void) {
501   if (debugging) {
502     return YES;
503   } else if (loadCalibration()) {
504     return YES;
505   } else {
506     defaultCalibration();
507     return NO;
508   }
511 // Deletes any stored calibration, and then takes the current calibration values
512 // from the built-in value table.
513 void smsDeleteCalibration(void) {
514   if (!debugging) {
515     deleteCalibration();
516     defaultCalibration();
517   }
520 // Fills in the accel record with calibrated acceleration data. Takes
521 // 1-2ms to return a value. Returns 0 if success, error number if failure.
522 int smsGetData(sms_acceleration* accel) {
523   NSTimeInterval time;
524   if (debugging) {
525     usleep(1500);  // Usually takes 1-2 milliseconds
526     time = [NSDate timeIntervalSinceReferenceDate];
527     accel->x = fakeData(time) / 5;
528     accel->y = fakeData(time - 1) / 5;
529     accel->z = fakeData(time - 2) / 5 + 1.0;
530     return true;
531   } else {
532     return getData(accel, true, nil, nil);
533   }
536 // Fills in the accel record with uncalibrated acceleration data.
537 // Returns 0 if success, error number if failure.
538 int smsGetUncalibratedData(sms_acceleration* accel) {
539   NSTimeInterval time;
540   if (debugging) {
541     usleep(1500);  // Usually takes 1-2 milliseconds
542     time = [NSDate timeIntervalSinceReferenceDate];
543     accel->x = fakeData(time) * 256 / 5;
544     accel->y = fakeData(time - 1) * 256 / 5;
545     accel->z = fakeData(time - 2) * 256 / 5 + 256;
546     return true;
547   } else {
548     return getData(accel, false, nil, nil);
549   }
552 // Returns the length of a raw block of data for the current type of sensor.
553 int smsGetBufferLength(void) {
554   if (debugging) {
555     return 0;
556   } else if (running) {
557     return sensors[sensorNum].recordSize;
558   } else {
559     return 0;
560   }
563 // Takes a pointer to accelGetRawLength() bytes; sets those bytes
564 // to return value from sensor. Make darn sure the buffer length is right!
565 void smsGetBufferData(char* buffer) {
566   IOItemCount iSize = recordSize;
567   IOByteCount oSize = recordSize;
568   kern_return_t result;
570   if (debugging || running == NO) {
571     return;
572   }
574   memset(iRecord, 1, iSize);
575   memset(buffer, 0, oSize);
576 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1050
577   const size_t InStructSize = recordSize;
578   size_t OutStructSize = recordSize;
579   result = IOConnectCallStructMethod(connection,
580                                      function,  // magic kernel function number
581                                      (const void*)iRecord, InStructSize,
582                                      (void*)buffer, &OutStructSize);
583 #else   // __MAC_OS_X_VERSION_MIN_REQUIRED 1050
584   result = IOConnectMethodStructureIStructureO(
585       connection,
586       function,  // magic kernel function number
587       iSize, &oSize, iRecord, buffer);
588 #endif  // __MAC_OS_X_VERSION_MIN_REQUIRED 1050
590   if (result != KERN_SUCCESS) {
591     running = NO;
592   }
595 // This returns an NSString describing the current calibration in
596 // human-readable form. Also include a description of the machine.
597 NSString* smsGetCalibrationDescription(void) {
598   BOOL success;
599   NSMutableString* s = [[NSMutableString alloc] init];
601   if (debugging) {
602     [s release];
603     return @"Debugging!";
604   }
606   [s appendString:@"---- SeisMac Calibration Record ----\n \n"];
607   [s appendFormat:@"Machine model: %@\n", getModelName()];
608   [s appendFormat:@"OS X build: %@\n", getOSVersion()];
609   [s appendFormat:@"SeisMacLib version %s, record %d\n \n", SMSLIB_VERSION,
610                   sensorNum];
611   [s appendFormat:@"Using service \"%s\", function index %d, size %d\n \n",
612                   serviceName, function, recordSize];
613   if (prefIntRead(CALIBRATED_NAME, &success) && success) {
614     [s appendString:@"Calibration values (from calibration):\n"];
615   } else {
616     [s appendString:@"Calibration values (from defaults):\n"];
617   }
618   [s appendFormat:@"    X-Axis-Zero  = %.2f\n", zeros[0]];
619   [s appendFormat:@"    X-Axis-One-g = %.2f\n", onegs[0]];
620   [s appendFormat:@"    Y-Axis-Zero  = %.2f\n", zeros[1]];
621   [s appendFormat:@"    Y-Axis-One-g = %.2f\n", onegs[1]];
622   [s appendFormat:@"    Z-Axis-Zero  = %.2f\n", zeros[2]];
623   [s appendFormat:@"    Z-Axis-One-g = %.2f\n \n", onegs[2]];
624   [s appendString:@"---- End Record ----\n"];
625   return s;
628 // Shuts down the accelerometer.
629 void smsShutdown(void) {
630   if (!debugging) {
631     running = NO;
632     if (iRecord) free(iRecord);
633     if (oRecord) free(oRecord);
634     IOServiceClose(connection);
635   }
638 #pragma mark Internal functions
640 // Loads the current calibration from the stored preferences.
641 // Returns true iff successful.
642 BOOL loadCalibration(void) {
643   BOOL thisSuccess, allSuccess;
644   int x;
646   prefSynchronize();
648   if (prefIntRead(CALIBRATED_NAME, &thisSuccess) && thisSuccess) {
649     // Calibrated. Set all values from saved values.
650     allSuccess = YES;
651     for (x = 0; x < 3; x++) {
652       zeros[x] = prefFloatRead(ZERO_NAME(x), &thisSuccess);
653       allSuccess &= thisSuccess;
654       onegs[x] = prefFloatRead(ONEG_NAME(x), &thisSuccess);
655       allSuccess &= thisSuccess;
656     }
657     return allSuccess;
658   }
660   return NO;
663 // Stores the current calibration into the stored preferences.
664 static void storeCalibration(void) {
665   int x;
666   prefIntWrite(CALIBRATED_NAME, 1);
667   for (x = 0; x < 3; x++) {
668     prefFloatWrite(ZERO_NAME(x), zeros[x]);
669     prefFloatWrite(ONEG_NAME(x), onegs[x]);
670   }
671   prefSynchronize();
674 // Sets the calibration to its default values.
675 void defaultCalibration(void) {
676   int x;
677   for (x = 0; x < 3; x++) {
678     zeros[x] = sensors[sensorNum].axes[x].zerog;
679     onegs[x] = sensors[sensorNum].axes[x].oneg;
680   }
683 // Deletes the stored preferences.
684 static void deleteCalibration(void) {
685   int x;
687   prefDelete(CALIBRATED_NAME);
688   for (x = 0; x < 3; x++) {
689     prefDelete(ZERO_NAME(x));
690     prefDelete(ONEG_NAME(x));
691   }
692   prefSynchronize();
695 // Read a named floating point value from the stored preferences. Sets
696 // the success boolean based on, you guessed it, whether it succeeds.
697 static float prefFloatRead(NSString* prefName, BOOL* success) {
698   float result = 0.0f;
700   CFPropertyListRef ref =
701       CFPreferencesCopyAppValue((CFStringRef)prefName, APP_ID);
702   // If there isn't such a preference, fail
703   if (ref == NULL) {
704     *success = NO;
705     return result;
706   }
707   CFTypeID typeID = CFGetTypeID(ref);
708   // Is it a number?
709   if (typeID == CFNumberGetTypeID()) {
710     // Is it a floating point number?
711     if (CFNumberIsFloatType((CFNumberRef)ref)) {
712       // Yup: grab it.
713       *success =
714           CFNumberGetValue((__CFNumber*)ref, kCFNumberFloat32Type, &result);
715     } else {
716       // Nope: grab as an integer, and convert to a float.
717       long num;
718       if (CFNumberGetValue((CFNumberRef)ref, kCFNumberLongType, &num)) {
719         result = num;
720         *success = YES;
721       } else {
722         *success = NO;
723       }
724     }
725     // Or is it a string (e.g. set by the command line "defaults" command)?
726   } else if (typeID == CFStringGetTypeID()) {
727     result = (float)CFStringGetDoubleValue((CFStringRef)ref);
728     *success = YES;
729   } else {
730     // Can't convert to a number: fail.
731     *success = NO;
732   }
733   CFRelease(ref);
734   return result;
737 // Writes a named floating point value to the stored preferences.
738 static void prefFloatWrite(NSString* prefName, float prefValue) {
739   CFNumberRef cfFloat =
740       CFNumberCreate(kCFAllocatorDefault, kCFNumberFloatType, &prefValue);
741   CFPreferencesSetAppValue((CFStringRef)prefName, cfFloat, APP_ID);
742   CFRelease(cfFloat);
745 // Reads a named integer value from the stored preferences.
746 static int prefIntRead(NSString* prefName, BOOL* success) {
747   Boolean internalSuccess;
748   CFIndex result = CFPreferencesGetAppIntegerValue((CFStringRef)prefName,
749                                                    APP_ID, &internalSuccess);
750   *success = internalSuccess;
752   return result;
755 // Writes a named integer value to the stored preferences.
756 static void prefIntWrite(NSString* prefName, int prefValue) {
757   CFPreferencesSetAppValue((CFStringRef)prefName,
758                            (CFNumberRef)[NSNumber numberWithInt:prefValue],
759                            APP_ID);
762 // Deletes the named preference values.
763 static void prefDelete(NSString* prefName) {
764   CFPreferencesSetAppValue((CFStringRef)prefName, NULL, APP_ID);
767 // Synchronizes the local preferences with the stored preferences.
768 static void prefSynchronize(void) { CFPreferencesAppSynchronize(APP_ID); }
770 // Internal version of accelGetData, with logging
771 int getData(sms_acceleration* accel, int calibrated, id logObject,
772             SEL logSelector) {
773   IOItemCount iSize = recordSize;
774   IOByteCount oSize = recordSize;
775   kern_return_t result;
777   if (running == NO) {
778     return -1;
779   }
781   memset(iRecord, 1, iSize);
782   memset(oRecord, 0, oSize);
784   LOG_2ARG(@"    Querying device (%u, %d): ", sensors[sensorNum].function,
785            sensors[sensorNum].recordSize);
787 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1050
788   const size_t InStructSize = recordSize;
789   size_t OutStructSize = recordSize;
790   result = IOConnectCallStructMethod(connection,
791                                      function,  // magic kernel function number
792                                      (const void*)iRecord, InStructSize,
793                                      (void*)oRecord, &OutStructSize);
794 #else   // __MAC_OS_X_VERSION_MIN_REQUIRED 1050
795   result = IOConnectMethodStructureIStructureO(
796       connection,
797       function,  // magic kernel function number
798       iSize, &oSize, iRecord, oRecord);
799 #endif  // __MAC_OS_X_VERSION_MIN_REQUIRED 1050
801   if (result != KERN_SUCCESS) {
802     LOG(@"failed.\n");
803     running = NO;
804     return result;
805   } else {
806     LOG(@"succeeded.\n");
808     accel->x = getAxis(0, calibrated);
809     accel->y = getAxis(1, calibrated);
810     accel->z = getAxis(2, calibrated);
811     return 0;
812   }
815 // Given the returned record, extracts the value of the given axis. If
816 // calibrated, then zero G is 0.0, and one G is 1.0.
817 float getAxis(int which, int calibrated) {
818   // Get various values (to make code cleaner)
819   int indx = sensors[sensorNum].axes[which].index;
820   int size = sensors[sensorNum].axes[which].size;
821   float zerog = zeros[which];
822   float oneg = onegs[which];
823   // Storage for value to be returned
824   int value = 0;
826   // Although the values in the returned record should have the proper
827   // endianness, we still have to get it into the proper end of value.
828 #if (BYTE_ORDER == BIG_ENDIAN)
829   // On PowerPC processors
830   memcpy(((char*)&value) + (sizeof(int) - size), &oRecord[indx], size);
831 #endif
832 #if (BYTE_ORDER == LITTLE_ENDIAN)
833   // On Intel processors
834   memcpy(&value, &oRecord[indx], size);
835 #endif
837   value = signExtend(value, size);
839   if (calibrated) {
840     // Scale and shift for zero.
841     return ((float)(value - zerog)) / oneg;
842   } else {
843     return value;
844   }
847 // Extends the sign, given the length of the value.
848 int signExtend(int value, int size) {
849   // Extend sign
850   switch (size) {
851     case 1:
852       if (value & 0x00000080) value |= 0xffffff00;
853       break;
854     case 2:
855       if (value & 0x00008000) value |= 0xffff0000;
856       break;
857     case 3:
858       if (value & 0x00800000) value |= 0xff000000;
859       break;
860   }
861   return value;
864 // Returns the model name of the computer (e.g. "MacBookPro1,1")
865 NSString* getModelName(void) {
866   char model[32];
867   size_t len = sizeof(model);
868   int name[2] = {CTL_HW, HW_MODEL};
869   NSString* result;
871   if (sysctl(name, 2, &model, &len, NULL, 0) == 0) {
872     result = [NSString stringWithFormat:@"%s", model];
873   } else {
874     result = @"";
875   }
877   return result;
880 // Returns the current OS X version and build (e.g. "10.4.7 (build 8J2135a)")
881 NSString* getOSVersion(void) {
882   NSDictionary* dict =
883       [NSDictionary dictionaryWithContentsOfFile:
884                         @"/System/Library/CoreServices/SystemVersion.plist"];
885   NSString* versionString = [dict objectForKey:@"ProductVersion"];
886   NSString* buildString = [dict objectForKey:@"ProductBuildVersion"];
887   NSString* wholeString =
888       [NSString stringWithFormat:@"%@ (build %@)", versionString, buildString];
889   return wholeString;
892 // Returns time within the current second in microseconds.
893 // long getMicroseconds() {
894 //      struct timeval t;
895 //      gettimeofday(&t, 0);
896 //      return t.tv_usec;
899 // Returns fake data given the time. Range is +/-1.
900 float fakeData(NSTimeInterval time) {
901   long secs = lround(floor(time));
902   int secsMod3 = secs % 3;
903   double angle = time * 10 * M_PI * 2;
904   double mag = exp(-(time - (secs - secsMod3)) * 2);
905   return sin(angle) * mag;