/* * smslib.m * * SMSLib Sudden Motion Sensor Access Library * Copyright (c) 2010 Suitable Systems * All rights reserved. * * Developed by: Daniel Griscom * Suitable Systems * http://www.suitable.com * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal with the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * - Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimers. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimers in the * documentation and/or other materials provided with the distribution. * * - Neither the names of Suitable Systems nor the names of its * contributors may be used to endorse or promote products derived from * this Software without specific prior written permission. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE. * * For more information about SMSLib, see * <http://www.suitable.com/tools/smslib.html> * or contact * Daniel Griscom * Suitable Systems * 1 Centre Street, Suite 204 * Wakefield, MA 01880 * (781) 665-0053 * */ #import <IOKit/IOKitLib.h> #import <sys/sysctl.h> #import <math.h> #import "smslib.h" #pragma mark Internal structures // Represents a single axis of a type of sensor. typedef struct axisStruct { int enabled; // Non-zero if axis is valid in this sensor int index; // Location in struct of first byte int size; // Number of bytes float zerog; // Value meaning "zero g" float oneg; // Change in value meaning "increase of one g" // (can be negative if axis sensor reversed) } axisStruct; // Represents the configuration of a type of sensor. typedef struct sensorSpec { const char *model; // Prefix of model to be tested const char *name; // Name of device to be read unsigned int function; // Kernel function index int recordSize; // Size of record to be sent/received axisStruct axes[3]; // Description of three axes (X, Y, Z) } sensorSpec; // Configuration of all known types of sensors. The configurations are // tried in order until one succeeds in returning data. // All default values are set here, but each axis' zerog and oneg values // may be changed to saved (calibrated) values. // // These values came from SeisMaCalibrate calibration reports. In general I've // found the following: // - All Intel-based SMSs have 250 counts per g, centered on 0, but the signs // are different (and in one case two axes are swapped) // - PowerBooks and iBooks all have sensors centered on 0, and reading // 50-53 steps per gravity (but with differing polarities!) // - PowerBooks and iBooks of the same model all have the same axis polarities // - PowerBook and iBook access methods are model- and OS version-specific // // So, the sequence of tests is: // - Try model-specific access methods. Note that the test is for a match to the // beginning of the model name, e.g. the record with model name "MacBook" // matches computer models "MacBookPro1,2" and "MacBook1,1" (and "" // matches any model). // - If no model-specific record's access fails, then try each model-independent // access method in order, stopping when one works. static const sensorSpec sensors[] = { // ****** Model-dependent methods ****** // The PowerBook5,6 is one of the G4 models that seems to lose // SMS access until the next reboot. {"PowerBook5,6", "IOI2CMotionSensor", 21, 60, { {1, 0, 1, 0, 51.5}, {1, 1, 1, 0, -51.5}, {1, 2, 1, 0, -51.5} } }, // The PowerBook5,7 is one of the G4 models that seems to lose // SMS access until the next reboot. {"PowerBook5,7", "IOI2CMotionSensor", 21, 60, { {1, 0, 1, 0, 51.5}, {1, 1, 1, 0, 51.5}, {1, 2, 1, 0, 51.5} } }, // Access seems to be reliable on the PowerBook5,8 {"PowerBook5,8", "PMUMotionSensor", 21, 60, { {1, 0, 1, 0, -51.5}, {1, 1, 1, 0, 51.5}, {1, 2, 1, 0, -51.5} } }, // Access seems to be reliable on the PowerBook5,9 {"PowerBook5,9", "PMUMotionSensor", 21, 60, { {1, 0, 1, 0, 51.5}, {1, 1, 1, 0, -51.5}, {1, 2, 1, 0, -51.5} } }, // The PowerBook6,7 is one of the G4 models that seems to lose // SMS access until the next reboot. {"PowerBook6,7", "IOI2CMotionSensor", 21, 60, { {1, 0, 1, 0, 51.5}, {1, 1, 1, 0, 51.5}, {1, 2, 1, 0, 51.5} } }, // The PowerBook6,8 is one of the G4 models that seems to lose // SMS access until the next reboot. {"PowerBook6,8", "IOI2CMotionSensor", 21, 60, { {1, 0, 1, 0, 51.5}, {1, 1, 1, 0, 51.5}, {1, 2, 1, 0, 51.5} } }, // MacBook Pro Core 2 Duo 17". Note the reversed Y and Z axes. {"MacBookPro2,1", "SMCMotionSensor", 5, 40, { {1, 0, 2, 0, 251}, {1, 2, 2, 0, -251}, {1, 4, 2, 0, -251} } }, // MacBook Pro Core 2 Duo 15" AND 17" with LED backlight, introduced June '07. // NOTE! The 17" machines have the signs of their X and Y axes reversed // from this calibration, but there's no clear way to discriminate between // the two machines. {"MacBookPro3,1", "SMCMotionSensor", 5, 40, { {1, 0, 2, 0, -251}, {1, 2, 2, 0, 251}, {1, 4, 2, 0, -251} } }, // ... specs? {"MacBook5,2", "SMCMotionSensor", 5, 40, { {1, 0, 2, 0, -251}, {1, 2, 2, 0, 251}, {1, 4, 2, 0, -251} } }, // ... specs? {"MacBookPro5,1", "SMCMotionSensor", 5, 40, { {1, 0, 2, 0, -251}, {1, 2, 2, 0, -251}, {1, 4, 2, 0, 251} } }, // ... specs? {"MacBookPro5,2", "SMCMotionSensor", 5, 40, { {1, 0, 2, 0, -251}, {1, 2, 2, 0, -251}, {1, 4, 2, 0, 251} } }, // This is speculative, based on a single user's report. Looks like the X and Y axes // are swapped. This is true for no other known Appple laptop. {"MacBookPro5,3", "SMCMotionSensor", 5, 40, { {1, 2, 2, 0, -251}, {1, 0, 2, 0, -251}, {1, 4, 2, 0, -251} } }, // ... specs? {"MacBookPro5,4", "SMCMotionSensor", 5, 40, { {1, 0, 2, 0, -251}, {1, 2, 2, 0, -251}, {1, 4, 2, 0, 251} } }, // ****** Model-independent methods ****** // Seen once with PowerBook6,8 under system 10.3.9; I suspect // other G4-based 10.3.* systems might use this {"", "IOI2CMotionSensor", 24, 60, { {1, 0, 1, 0, 51.5}, {1, 1, 1, 0, 51.5}, {1, 2, 1, 0, 51.5} } }, // PowerBook5,6 , PowerBook5,7 , PowerBook6,7 , PowerBook6,8 // under OS X 10.4.* {"", "IOI2CMotionSensor", 21, 60, { {1, 0, 1, 0, 51.5}, {1, 1, 1, 0, 51.5}, {1, 2, 1, 0, 51.5} } }, // PowerBook5,8 , PowerBook5,9 under OS X 10.4.* {"", "PMUMotionSensor", 21, 60, { // Each has two out of three gains negative, but it's different // for the different models. So, this will be right in two out // of three axis for either model. {1, 0, 1, 0, -51.5}, {1, 1, 1, -6, -51.5}, {1, 2, 1, 0, -51.5} } }, // All MacBook, MacBookPro models. Hardware (at least on early MacBookPro 15") // is Kionix KXM52-1050 three-axis accelerometer chip. Data is at // http://kionix.com/Product-Index/product-index.htm. Specific MB and MBP models // that use this are: // MacBook1,1 // MacBook2,1 // MacBook3,1 // MacBook4,1 // MacBook5,1 // MacBook6,1 // MacBookAir1,1 // MacBookPro1,1 // MacBookPro1,2 // MacBookPro4,1 // MacBookPro5,5 {"", "SMCMotionSensor", 5, 40, { {1, 0, 2, 0, 251}, {1, 2, 2, 0, 251}, {1, 4, 2, 0, 251} } } }; #define SENSOR_COUNT (sizeof(sensors)/sizeof(sensorSpec)) #pragma mark Internal prototypes static int getData(sms_acceleration *accel, int calibrated, id logObject, SEL logSelector); static float getAxis(int which, int calibrated); static int signExtend(int value, int size); static NSString *getModelName(void); static NSString *getOSVersion(void); static BOOL loadCalibration(void); static void storeCalibration(void); static void defaultCalibration(void); static void deleteCalibration(void); static int prefIntRead(NSString *prefName, BOOL *success); static void prefIntWrite(NSString *prefName, int prefValue); static float prefFloatRead(NSString *prefName, BOOL *success); static void prefFloatWrite(NSString *prefName, float prefValue); static void prefDelete(NSString *prefName); static void prefSynchronize(void); // static long getMicroseconds(void); float fakeData(NSTimeInterval time); #pragma mark Static variables static int debugging = NO; // True if debugging (synthetic data) static io_connect_t connection; // Connection for reading accel values static int running = NO; // True if we successfully started static unsigned int sensorNum = 0; // The current index into sensors[] static const char *serviceName; // The name of the current service static char *iRecord, *oRecord; // Pointers to read/write records for sensor static int recordSize; // Size of read/write records static unsigned int function; // Which kernel function should be used static float zeros[3]; // X, Y and Z zero calibration values static float onegs[3]; // X, Y and Z one-g calibration values #pragma mark Defines // Pattern for building axis letter from axis number #define INT_TO_AXIS(a) (a == 0 ? @"X" : a == 1 ? @"Y" : @"Z") // Name of configuration for given axis' zero (axis specified by integer) #define ZERO_NAME(a) [NSString stringWithFormat:@"%@-Axis-Zero", INT_TO_AXIS(a)] // Name of configuration for given axis' oneg (axis specified by integer) #define ONEG_NAME(a) [NSString stringWithFormat:@"%@-Axis-One-g", INT_TO_AXIS(a)] // Name of "Is calibrated" preference #define CALIBRATED_NAME (@"Calibrated") // Application domain for SeisMac library #define APP_ID ((CFStringRef)@"com.suitable.SeisMacLib") // These #defines make the accelStartup code a LOT easier to read. #undef LOG #define LOG(message) \ if (logObject) { \ [logObject performSelector:logSelector withObject:message]; \ } #define LOG_ARG(format, var1) \ if (logObject) { \ [logObject performSelector:logSelector \ withObject:[NSString stringWithFormat:format, var1]]; \ } #define LOG_2ARG(format, var1, var2) \ if (logObject) { \ [logObject performSelector:logSelector \ withObject:[NSString stringWithFormat:format, var1, var2]]; \ } #define LOG_3ARG(format, var1, var2, var3) \ if (logObject) { \ [logObject performSelector:logSelector \ withObject:[NSString stringWithFormat:format, var1, var2, var3]]; \ } #pragma mark Function definitions // This starts up the accelerometer code, trying each possible sensor // specification. Note that for logging purposes it // takes an object and a selector; the object's selector is then invoked // with a single NSString as argument giving progress messages. Example // logging method: // - (void)logMessage: (NSString *)theString // which would be used in accelStartup's invocation thusly: // result = accelStartup(self, @selector(logMessage:)); // If the object is nil, then no logging is done. Sets calibation from built-in // value table. Returns ACCEL_SUCCESS for success, and other (negative) // values for various failures (returns value indicating result of // most successful trial). int smsStartup(id logObject, SEL logSelector) { io_iterator_t iterator; io_object_t device; kern_return_t result; sms_acceleration accel; int failure_result = SMS_FAIL_MODEL; running = NO; debugging = NO; NSString *modelName = getModelName(); LOG_ARG(@"Machine model: %@\n", modelName); LOG_ARG(@"OS X version: %@\n", getOSVersion()); LOG_ARG(@"Accelerometer library version: %s\n", SMSLIB_VERSION); for (sensorNum = 0; sensorNum < SENSOR_COUNT; sensorNum++) { // Set up all specs for this type of sensor serviceName = sensors[sensorNum].name; recordSize = sensors[sensorNum].recordSize; function = sensors[sensorNum].function; LOG_3ARG(@"Trying service \"%s\" with selector %d and %d byte record:\n", serviceName, function, recordSize); NSString *targetName = [NSString stringWithCString:sensors[sensorNum].model encoding:NSMacOSRomanStringEncoding]; LOG_ARG(@" Comparing model name to target \"%@\": ", targetName); if ([targetName length] == 0 || [modelName hasPrefix:targetName]) { LOG(@"success.\n"); } else { LOG(@"failure.\n"); // Don't need to increment failure_result. continue; } LOG(@" Fetching dictionary for service: "); CFMutableDictionaryRef dict = IOServiceMatching(serviceName); if (dict) { LOG(@"success.\n"); } else { LOG(@"failure.\n"); if (failure_result < SMS_FAIL_DICTIONARY) { failure_result = SMS_FAIL_DICTIONARY; } continue; } LOG(@" Getting list of matching services: "); result = IOServiceGetMatchingServices(kIOMasterPortDefault, dict, &iterator); if (result == KERN_SUCCESS) { LOG(@"success.\n"); } else { LOG_ARG(@"failure, with return value 0x%x.\n", result); if (failure_result < SMS_FAIL_LIST_SERVICES) { failure_result = SMS_FAIL_LIST_SERVICES; } continue; } LOG(@" Getting first device in list: "); device = IOIteratorNext(iterator); if (device == 0) { LOG(@"failure.\n"); if (failure_result < SMS_FAIL_NO_SERVICES) { failure_result = SMS_FAIL_NO_SERVICES; } continue; } else { LOG(@"success.\n"); LOG(@" Opening device: "); } result = IOServiceOpen(device, mach_task_self(), 0, &connection); if (result != KERN_SUCCESS) { LOG_ARG(@"failure, with return value 0x%x.\n", result); IOObjectRelease(device); if (failure_result < SMS_FAIL_OPENING) { failure_result = SMS_FAIL_OPENING; } continue; } else if (connection == 0) { LOG_ARG(@"'success', but didn't get a connection (return value was: 0x%x).\n", result); IOObjectRelease(device); if (failure_result < SMS_FAIL_CONNECTION) { failure_result = SMS_FAIL_CONNECTION; } continue; } else { IOObjectRelease(device); LOG(@"success.\n"); } LOG(@" Testing device.\n"); defaultCalibration(); iRecord = (char*) malloc(recordSize); oRecord = (char*) malloc(recordSize); running = YES; result = getData(&accel, true, logObject, logSelector); running = NO; if (result) { LOG_ARG(@" Failure testing device, with result 0x%x.\n", result); free(iRecord); iRecord = 0; free(oRecord); oRecord = 0; if (failure_result < SMS_FAIL_ACCESS) { failure_result = SMS_FAIL_ACCESS; } continue; } else { LOG(@" Success testing device!\n"); running = YES; return SMS_SUCCESS; } } return failure_result; } // This starts up the library in debug mode, ignoring the actual hardware. // Returned data is in the form of 1Hz sine waves, with the X, Y and Z // axes 120 degrees out of phase; "calibrated" data has range +/- (1.0/5); // "uncalibrated" data has range +/- (256/5). X and Y axes centered on 0.0, // Z axes centered on 1 (calibrated) or 256 (uncalibrated). // Don't use smsGetBufferLength or smsGetBufferData. Always returns SMS_SUCCESS. int smsDebugStartup(id logObject, SEL logSelector) { LOG(@"Starting up in debug mode\n"); debugging = YES; return SMS_SUCCESS; } // Returns the current calibration values. void smsGetCalibration(sms_calibration *calibrationRecord) { int x; for (x = 0; x < 3; x++) { calibrationRecord->zeros[x] = (debugging ? 0 : zeros[x]); calibrationRecord->onegs[x] = (debugging ? 256 : onegs[x]); } } // Sets the calibration, but does NOT store it as a preference. If the argument // is nil then the current calibration is set from the built-in value table. void smsSetCalibration(sms_calibration *calibrationRecord) { int x; if (!debugging) { if (calibrationRecord) { for (x = 0; x < 3; x++) { zeros[x] = calibrationRecord->zeros[x]; onegs[x] = calibrationRecord->onegs[x]; } } else { defaultCalibration(); } } } // Stores the current calibration values as a stored preference. void smsStoreCalibration(void) { if (!debugging) storeCalibration(); } // Loads the stored preference values into the current calibration. // Returns YES if successful. BOOL smsLoadCalibration(void) { if (debugging) { return YES; } else if (loadCalibration()) { return YES; } else { defaultCalibration(); return NO; } } // Deletes any stored calibration, and then takes the current calibration values // from the built-in value table. void smsDeleteCalibration(void) { if (!debugging) { deleteCalibration(); defaultCalibration(); } } // Fills in the accel record with calibrated acceleration data. Takes // 1-2ms to return a value. Returns 0 if success, error number if failure. int smsGetData(sms_acceleration *accel) { NSTimeInterval time; if (debugging) { usleep(1500); // Usually takes 1-2 milliseconds time = [NSDate timeIntervalSinceReferenceDate]; accel->x = fakeData(time)/5; accel->y = fakeData(time - 1)/5; accel->z = fakeData(time - 2)/5 + 1.0; return true; } else { return getData(accel, true, nil, nil); } } // Fills in the accel record with uncalibrated acceleration data. // Returns 0 if success, error number if failure. int smsGetUncalibratedData(sms_acceleration *accel) { NSTimeInterval time; if (debugging) { usleep(1500); // Usually takes 1-2 milliseconds time = [NSDate timeIntervalSinceReferenceDate]; accel->x = fakeData(time) * 256 / 5; accel->y = fakeData(time - 1) * 256 / 5; accel->z = fakeData(time - 2) * 256 / 5 + 256; return true; } else { return getData(accel, false, nil, nil); } } // Returns the length of a raw block of data for the current type of sensor. int smsGetBufferLength(void) { if (debugging) { return 0; } else if (running) { return sensors[sensorNum].recordSize; } else { return 0; } } // Takes a pointer to accelGetRawLength() bytes; sets those bytes // to return value from sensor. Make darn sure the buffer length is right! void smsGetBufferData(char *buffer) { IOItemCount iSize = recordSize; IOByteCount oSize = recordSize; kern_return_t result; if (debugging || running == NO) { return; } memset(iRecord, 1, iSize); memset(buffer, 0, oSize); #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 const size_t InStructSize = recordSize; size_t OutStructSize = recordSize; result = IOConnectCallStructMethod(connection, function, // magic kernel function number (const void *)iRecord, InStructSize, (void *)buffer, &OutStructSize ); #else // __MAC_OS_X_VERSION_MIN_REQUIRED 1050 result = IOConnectMethodStructureIStructureO(connection, function, // magic kernel function number iSize, &oSize, iRecord, buffer ); #endif // __MAC_OS_X_VERSION_MIN_REQUIRED 1050 if (result != KERN_SUCCESS) { running = NO; } } // This returns an NSString describing the current calibration in // human-readable form. Also include a description of the machine. NSString *smsGetCalibrationDescription(void) { BOOL success; NSMutableString *s = [[NSMutableString alloc] init]; if (debugging) { [s release]; return @"Debugging!"; } [s appendString:@"---- SeisMac Calibration Record ----\n \n"]; [s appendFormat:@"Machine model: %@\n", getModelName()]; [s appendFormat:@"OS X build: %@\n", getOSVersion()]; [s appendFormat:@"SeisMacLib version %s, record %d\n \n", SMSLIB_VERSION, sensorNum]; [s appendFormat:@"Using service \"%s\", function index %d, size %d\n \n", serviceName, function, recordSize]; if (prefIntRead(CALIBRATED_NAME, &success) && success) { [s appendString:@"Calibration values (from calibration):\n"]; } else { [s appendString:@"Calibration values (from defaults):\n"]; } [s appendFormat:@" X-Axis-Zero = %.2f\n", zeros[0]]; [s appendFormat:@" X-Axis-One-g = %.2f\n", onegs[0]]; [s appendFormat:@" Y-Axis-Zero = %.2f\n", zeros[1]]; [s appendFormat:@" Y-Axis-One-g = %.2f\n", onegs[1]]; [s appendFormat:@" Z-Axis-Zero = %.2f\n", zeros[2]]; [s appendFormat:@" Z-Axis-One-g = %.2f\n \n", onegs[2]]; [s appendString:@"---- End Record ----\n"]; return s; } // Shuts down the accelerometer. void smsShutdown(void) { if (!debugging) { running = NO; if (iRecord) free(iRecord); if (oRecord) free(oRecord); IOServiceClose(connection); } } #pragma mark Internal functions // Loads the current calibration from the stored preferences. // Returns true iff successful. BOOL loadCalibration(void) { BOOL thisSuccess, allSuccess; int x; prefSynchronize(); if (prefIntRead(CALIBRATED_NAME, &thisSuccess) && thisSuccess) { // Calibrated. Set all values from saved values. allSuccess = YES; for (x = 0; x < 3; x++) { zeros[x] = prefFloatRead(ZERO_NAME(x), &thisSuccess); allSuccess &= thisSuccess; onegs[x] = prefFloatRead(ONEG_NAME(x), &thisSuccess); allSuccess &= thisSuccess; } return allSuccess; } return NO; } // Stores the current calibration into the stored preferences. static void storeCalibration(void) { int x; prefIntWrite(CALIBRATED_NAME, 1); for (x = 0; x < 3; x++) { prefFloatWrite(ZERO_NAME(x), zeros[x]); prefFloatWrite(ONEG_NAME(x), onegs[x]); } prefSynchronize(); } // Sets the calibration to its default values. void defaultCalibration(void) { int x; for (x = 0; x < 3; x++) { zeros[x] = sensors[sensorNum].axes[x].zerog; onegs[x] = sensors[sensorNum].axes[x].oneg; } } // Deletes the stored preferences. static void deleteCalibration(void) { int x; prefDelete(CALIBRATED_NAME); for (x = 0; x < 3; x++) { prefDelete(ZERO_NAME(x)); prefDelete(ONEG_NAME(x)); } prefSynchronize(); } // Read a named floating point value from the stored preferences. Sets // the success boolean based on, you guessed it, whether it succeeds. static float prefFloatRead(NSString *prefName, BOOL *success) { float result = 0.0f; CFPropertyListRef ref = CFPreferencesCopyAppValue((CFStringRef)prefName, APP_ID); // If there isn't such a preference, fail if (ref == NULL) { *success = NO; return result; } CFTypeID typeID = CFGetTypeID(ref); // Is it a number? if (typeID == CFNumberGetTypeID()) { // Is it a floating point number? if (CFNumberIsFloatType((CFNumberRef)ref)) { // Yup: grab it. *success = CFNumberGetValue((__CFNumber*)ref, kCFNumberFloat32Type, &result); } else { // Nope: grab as an integer, and convert to a float. long num; if (CFNumberGetValue((CFNumberRef)ref, kCFNumberLongType, &num)) { result = num; *success = YES; } else { *success = NO; } } // Or is it a string (e.g. set by the command line "defaults" command)? } else if (typeID == CFStringGetTypeID()) { result = (float)CFStringGetDoubleValue((CFStringRef)ref); *success = YES; } else { // Can't convert to a number: fail. *success = NO; } CFRelease(ref); return result; } // Writes a named floating point value to the stored preferences. static void prefFloatWrite(NSString *prefName, float prefValue) { CFNumberRef cfFloat = CFNumberCreate(kCFAllocatorDefault, kCFNumberFloatType, &prefValue); CFPreferencesSetAppValue((CFStringRef)prefName, cfFloat, APP_ID); CFRelease(cfFloat); } // Reads a named integer value from the stored preferences. static int prefIntRead(NSString *prefName, BOOL *success) { Boolean internalSuccess; CFIndex result = CFPreferencesGetAppIntegerValue((CFStringRef)prefName, APP_ID, &internalSuccess); *success = internalSuccess; return result; } // Writes a named integer value to the stored preferences. static void prefIntWrite(NSString *prefName, int prefValue) { CFPreferencesSetAppValue((CFStringRef)prefName, (CFNumberRef)[NSNumber numberWithInt:prefValue], APP_ID); } // Deletes the named preference values. static void prefDelete(NSString *prefName) { CFPreferencesSetAppValue((CFStringRef)prefName, NULL, APP_ID); } // Synchronizes the local preferences with the stored preferences. static void prefSynchronize(void) { CFPreferencesAppSynchronize(APP_ID); } // Internal version of accelGetData, with logging int getData(sms_acceleration *accel, int calibrated, id logObject, SEL logSelector) { IOItemCount iSize = recordSize; IOByteCount oSize = recordSize; kern_return_t result; if (running == NO) { return -1; } memset(iRecord, 1, iSize); memset(oRecord, 0, oSize); LOG_2ARG(@" Querying device (%u, %d): ", sensors[sensorNum].function, sensors[sensorNum].recordSize); #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 const size_t InStructSize = recordSize; size_t OutStructSize = recordSize; result = IOConnectCallStructMethod(connection, function, // magic kernel function number (const void *)iRecord, InStructSize, (void *)oRecord, &OutStructSize ); #else // __MAC_OS_X_VERSION_MIN_REQUIRED 1050 result = IOConnectMethodStructureIStructureO(connection, function, // magic kernel function number iSize, &oSize, iRecord, oRecord ); #endif // __MAC_OS_X_VERSION_MIN_REQUIRED 1050 if (result != KERN_SUCCESS) { LOG(@"failed.\n"); running = NO; return result; } else { LOG(@"succeeded.\n"); accel->x = getAxis(0, calibrated); accel->y = getAxis(1, calibrated); accel->z = getAxis(2, calibrated); return 0; } } // Given the returned record, extracts the value of the given axis. If // calibrated, then zero G is 0.0, and one G is 1.0. float getAxis(int which, int calibrated) { // Get various values (to make code cleaner) int indx = sensors[sensorNum].axes[which].index; int size = sensors[sensorNum].axes[which].size; float zerog = zeros[which]; float oneg = onegs[which]; // Storage for value to be returned int value = 0; // Although the values in the returned record should have the proper // endianness, we still have to get it into the proper end of value. #if (BYTE_ORDER == BIG_ENDIAN) // On PowerPC processors memcpy(((char *)&value) + (sizeof(int) - size), &oRecord[indx], size); #endif #if (BYTE_ORDER == LITTLE_ENDIAN) // On Intel processors memcpy(&value, &oRecord[indx], size); #endif value = signExtend(value, size); if (calibrated) { // Scale and shift for zero. return ((float)(value - zerog)) / oneg; } else { return value; } } // Extends the sign, given the length of the value. int signExtend(int value, int size) { // Extend sign switch (size) { case 1: if (value & 0x00000080) value |= 0xffffff00; break; case 2: if (value & 0x00008000) value |= 0xffff0000; break; case 3: if (value & 0x00800000) value |= 0xff000000; break; } return value; } // Returns the model name of the computer (e.g. "MacBookPro1,1") NSString *getModelName(void) { char model[32]; size_t len = sizeof(model); int name[2] = {CTL_HW, HW_MODEL}; NSString *result; if (sysctl(name, 2, &model, &len, NULL, 0) == 0) { result = [NSString stringWithFormat:@"%s", model]; } else { result = @""; } return result; } // Returns the current OS X version and build (e.g. "10.4.7 (build 8J2135a)") NSString *getOSVersion(void) { NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile: @"/System/Library/CoreServices/SystemVersion.plist"]; NSString *versionString = [dict objectForKey:@"ProductVersion"]; NSString *buildString = [dict objectForKey:@"ProductBuildVersion"]; NSString *wholeString = [NSString stringWithFormat:@"%@ (build %@)", versionString, buildString]; return wholeString; } // Returns time within the current second in microseconds. // long getMicroseconds() { // struct timeval t; // gettimeofday(&t, 0); // return t.tv_usec; //} // Returns fake data given the time. Range is +/-1. float fakeData(NSTimeInterval time) { long secs = lround(floor(time)); int secsMod3 = secs % 3; double angle = time * 10 * M_PI * 2; double mag = exp(-(time - (secs - secsMod3)) * 2); return sin(angle) * mag; }