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