smslib.mm (30454B)
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 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 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 "" 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}}}}; 235 236 #define SENSOR_COUNT (sizeof(sensors) / sizeof(sensorSpec)) 237 238 #pragma mark Internal prototypes 239 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); 258 259 #pragma mark Static variables 260 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 271 272 #pragma mark Defines 273 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") 285 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 } 309 310 #pragma mark Function definitions 311 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; 330 331 running = NO; 332 debugging = NO; 333 334 NSString* modelName = getModelName(); 335 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); 339 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; 345 346 LOG_3ARG(@"Trying service \"%s\" with selector %d and %d byte record:\n", 347 serviceName, function, recordSize); 348 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 } 360 361 LOG(@" Fetching dictionary for service: "); 362 CFMutableDictionaryRef dict = IOServiceMatching(serviceName); 363 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 } 373 374 LOG(@" Getting list of matching services: "); 375 result = 376 IOServiceGetMatchingServices(kIOMasterPortDefault, dict, &iterator); 377 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 } 387 388 LOG(@" Getting first device in list: "); 389 device = IOIteratorNext(iterator); 390 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 } 401 402 result = IOServiceOpen(device, mach_task_self(), 0, &connection); 403 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"); 425 426 defaultCalibration(); 427 428 iRecord = (char*)malloc(recordSize); 429 oRecord = (char*)malloc(recordSize); 430 431 running = YES; 432 result = getData(&accel, true, logObject, logSelector); 433 running = NO; 434 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; 452 } 453 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; 464 } 465 466 // Returns the current calibration values. 467 void smsGetCalibration(sms_calibration* calibrationRecord) { 468 int x; 469 470 for (x = 0; x < 3; x++) { 471 calibrationRecord->zeros[x] = (debugging ? 0 : zeros[x]); 472 calibrationRecord->onegs[x] = (debugging ? 256 : onegs[x]); 473 } 474 } 475 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; 480 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 } 491 } 492 493 // Stores the current calibration values as a stored preference. 494 void smsStoreCalibration(void) { 495 if (!debugging) storeCalibration(); 496 } 497 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 } 509 } 510 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 } 518 } 519 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 } 534 } 535 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 } 550 } 551 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 } 561 } 562 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; 569 570 if (debugging || running == NO) { 571 return; 572 } 573 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 589 590 if (result != KERN_SUCCESS) { 591 running = NO; 592 } 593 } 594 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]; 600 601 if (debugging) { 602 [s release]; 603 return @"Debugging!"; 604 } 605 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; 626 } 627 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 } 636 } 637 638 #pragma mark Internal functions 639 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; 645 646 prefSynchronize(); 647 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 } 659 660 return NO; 661 } 662 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(); 672 } 673 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 } 681 } 682 683 // Deletes the stored preferences. 684 static void deleteCalibration(void) { 685 int x; 686 687 prefDelete(CALIBRATED_NAME); 688 for (x = 0; x < 3; x++) { 689 prefDelete(ZERO_NAME(x)); 690 prefDelete(ONEG_NAME(x)); 691 } 692 prefSynchronize(); 693 } 694 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; 699 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; 735 } 736 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); 743 } 744 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; 751 752 return result; 753 } 754 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); 760 } 761 762 // Deletes the named preference values. 763 static void prefDelete(NSString* prefName) { 764 CFPreferencesSetAppValue((CFStringRef)prefName, NULL, APP_ID); 765 } 766 767 // Synchronizes the local preferences with the stored preferences. 768 static void prefSynchronize(void) { CFPreferencesAppSynchronize(APP_ID); } 769 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; 776 777 if (running == NO) { 778 return -1; 779 } 780 781 memset(iRecord, 1, iSize); 782 memset(oRecord, 0, oSize); 783 784 LOG_2ARG(@" Querying device (%u, %d): ", sensors[sensorNum].function, 785 sensors[sensorNum].recordSize); 786 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 800 801 if (result != KERN_SUCCESS) { 802 LOG(@"failed.\n"); 803 running = NO; 804 return result; 805 } else { 806 LOG(@"succeeded.\n"); 807 808 accel->x = getAxis(0, calibrated); 809 accel->y = getAxis(1, calibrated); 810 accel->z = getAxis(2, calibrated); 811 return 0; 812 } 813 } 814 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; 825 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 836 837 value = signExtend(value, size); 838 839 if (calibrated) { 840 // Scale and shift for zero. 841 return ((float)(value - zerog)) / oneg; 842 } else { 843 return value; 844 } 845 } 846 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; 862 } 863 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; 870 871 if (sysctl(name, 2, &model, &len, NULL, 0) == 0) { 872 result = [NSString stringWithFormat:@"%s", model]; 873 } else { 874 result = @""; 875 } 876 877 return result; 878 } 879 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; 890 } 891 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; 897 //} 898 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; 906 }