hal/cocoa/smslib.mm

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

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

mercurial