hal/cocoa/smslib.mm

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/hal/cocoa/smslib.mm	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,938 @@
     1.4 +/*
     1.5 + * smslib.m
     1.6 + * 
     1.7 + * SMSLib Sudden Motion Sensor Access Library
     1.8 + * Copyright (c) 2010 Suitable Systems
     1.9 + * All rights reserved.
    1.10 + * 
    1.11 + * Developed by: Daniel Griscom
    1.12 + *               Suitable Systems
    1.13 + *               http://www.suitable.com
    1.14 + * 
    1.15 + * Permission is hereby granted, free of charge, to any person obtaining a
    1.16 + * copy of this software and associated documentation files (the
    1.17 + * "Software"), to deal with the Software without restriction, including
    1.18 + * without limitation the rights to use, copy, modify, merge, publish,
    1.19 + * distribute, sublicense, and/or sell copies of the Software, and to
    1.20 + * permit persons to whom the Software is furnished to do so, subject to
    1.21 + * the following conditions:
    1.22 + * 
    1.23 + * - Redistributions of source code must retain the above copyright notice,
    1.24 + * this list of conditions and the following disclaimers.
    1.25 + * 
    1.26 + * - Redistributions in binary form must reproduce the above copyright
    1.27 + * notice, this list of conditions and the following disclaimers in the
    1.28 + * documentation and/or other materials provided with the distribution.
    1.29 + * 
    1.30 + * - Neither the names of Suitable Systems nor the names of its
    1.31 + * contributors may be used to endorse or promote products derived from
    1.32 + * this Software without specific prior written permission.
    1.33 + * 
    1.34 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
    1.35 + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    1.36 + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    1.37 + * IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR
    1.38 + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    1.39 + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    1.40 + * SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE.
    1.41 + *
    1.42 + * For more information about SMSLib, see
    1.43 + *		<http://www.suitable.com/tools/smslib.html>
    1.44 + * or contact
    1.45 + *		Daniel Griscom
    1.46 + *		Suitable Systems
    1.47 + *		1 Centre Street, Suite 204
    1.48 + *		Wakefield, MA 01880
    1.49 + *		(781) 665-0053
    1.50 + *
    1.51 + */
    1.52 +
    1.53 +#import <IOKit/IOKitLib.h>
    1.54 +#import <sys/sysctl.h>
    1.55 +#import <math.h>
    1.56 +#import "smslib.h"
    1.57 +
    1.58 +#pragma mark Internal structures
    1.59 +
    1.60 +// Represents a single axis of a type of sensor.
    1.61 +typedef struct axisStruct {
    1.62 +	int enabled;				// Non-zero if axis is valid in this sensor
    1.63 +	int index;					// Location in struct of first byte
    1.64 +	int size;					// Number of bytes
    1.65 +	float zerog;				// Value meaning "zero g"
    1.66 +	float oneg;					// Change in value meaning "increase of one g"
    1.67 +								// (can be negative if axis sensor reversed)
    1.68 +} axisStruct;
    1.69 +
    1.70 +// Represents the configuration of a type of sensor.
    1.71 +typedef struct sensorSpec {
    1.72 +	const char *model;			// Prefix of model to be tested
    1.73 +	const char *name;			// Name of device to be read
    1.74 +	unsigned int function;		// Kernel function index
    1.75 +	int recordSize;				// Size of record to be sent/received
    1.76 +	axisStruct axes[3];			// Description of three axes (X, Y, Z)
    1.77 +} sensorSpec;
    1.78 +	
    1.79 +// Configuration of all known types of sensors. The configurations are
    1.80 +// tried in order until one succeeds in returning data.
    1.81 +// All default values are set here, but each axis' zerog and oneg values 
    1.82 +// may be changed to saved (calibrated) values.
    1.83 +//
    1.84 +// These values came from SeisMaCalibrate calibration reports. In general I've
    1.85 +// found the following:
    1.86 +//	- All Intel-based SMSs have 250 counts per g, centered on 0, but the signs
    1.87 +//		are different (and in one case two axes are swapped)
    1.88 +//	- PowerBooks and iBooks all have sensors centered on 0, and reading
    1.89 +//		50-53 steps per gravity (but with differing polarities!)
    1.90 +//	- PowerBooks and iBooks of the same model all have the same axis polarities
    1.91 +//	- PowerBook and iBook access methods are model- and OS version-specific
    1.92 +//
    1.93 +// So, the sequence of tests is:
    1.94 +//	- Try model-specific access methods. Note that the test is for a match to the
    1.95 +//		beginning of the model name, e.g. the record with model name "MacBook"
    1.96 +//		matches computer models "MacBookPro1,2" and "MacBook1,1" (and ""
    1.97 +//		matches any model).
    1.98 +//	- If no model-specific record's access fails, then try each model-independent
    1.99 +//		access method in order, stopping when one works.
   1.100 +static const sensorSpec sensors[] = {
   1.101 +	// ****** Model-dependent methods ******
   1.102 +	// The PowerBook5,6 is one of the G4 models that seems to lose
   1.103 +	// SMS access until the next reboot.
   1.104 +	{"PowerBook5,6", "IOI2CMotionSensor", 21, 60, {
   1.105 +			{1, 0, 1, 0,  51.5},
   1.106 +			{1, 1, 1, 0, -51.5},
   1.107 +			{1, 2, 1, 0, -51.5}
   1.108 +		}
   1.109 +	},
   1.110 +	// The PowerBook5,7 is one of the G4 models that seems to lose
   1.111 +	// SMS access until the next reboot.
   1.112 +	{"PowerBook5,7", "IOI2CMotionSensor", 21, 60, {
   1.113 +			{1, 0, 1, 0,  51.5},
   1.114 +			{1, 1, 1, 0,  51.5},
   1.115 +			{1, 2, 1, 0,  51.5}
   1.116 +		}
   1.117 +	},
   1.118 +	// Access seems to be reliable on the PowerBook5,8
   1.119 +	{"PowerBook5,8", "PMUMotionSensor", 21, 60, {
   1.120 +			{1, 0, 1, 0, -51.5},
   1.121 +			{1, 1, 1, 0,  51.5},
   1.122 +			{1, 2, 1, 0, -51.5}
   1.123 +		}
   1.124 +	},
   1.125 +	// Access seems to be reliable on the PowerBook5,9
   1.126 +	{"PowerBook5,9", "PMUMotionSensor", 21, 60, {
   1.127 +			{1, 0, 1, 0,  51.5},
   1.128 +			{1, 1, 1, 0, -51.5},
   1.129 +			{1, 2, 1, 0, -51.5}
   1.130 +		}
   1.131 +	},
   1.132 +	// The PowerBook6,7 is one of the G4 models that seems to lose
   1.133 +	// SMS access until the next reboot.
   1.134 +	{"PowerBook6,7", "IOI2CMotionSensor", 21, 60, {
   1.135 +			{1, 0, 1, 0,  51.5},
   1.136 +			{1, 1, 1, 0,  51.5},
   1.137 +			{1, 2, 1, 0,  51.5}
   1.138 +		}
   1.139 +	},
   1.140 +	// The PowerBook6,8 is one of the G4 models that seems to lose
   1.141 +	// SMS access until the next reboot.
   1.142 +	{"PowerBook6,8", "IOI2CMotionSensor", 21, 60, {
   1.143 +			{1, 0, 1, 0,  51.5},
   1.144 +			{1, 1, 1, 0,  51.5},
   1.145 +			{1, 2, 1, 0,  51.5}
   1.146 +		}
   1.147 +	},
   1.148 +	// MacBook Pro Core 2 Duo 17". Note the reversed Y and Z axes.
   1.149 +	{"MacBookPro2,1", "SMCMotionSensor", 5, 40, {
   1.150 +			{1, 0, 2, 0,  251},
   1.151 +			{1, 2, 2, 0, -251},
   1.152 +			{1, 4, 2, 0, -251}
   1.153 +		}
   1.154 +	},
   1.155 +	// MacBook Pro Core 2 Duo 15" AND 17" with LED backlight, introduced June '07.
   1.156 +	// NOTE! The 17" machines have the signs of their X and Y axes reversed
   1.157 +	// from this calibration, but there's no clear way to discriminate between
   1.158 +	// the two machines.
   1.159 +	{"MacBookPro3,1", "SMCMotionSensor", 5, 40, {
   1.160 +			{1, 0, 2, 0, -251},
   1.161 +			{1, 2, 2, 0,  251},
   1.162 +			{1, 4, 2, 0, -251}
   1.163 +		}
   1.164 +	},
   1.165 +	// ... specs?
   1.166 +	{"MacBook5,2", "SMCMotionSensor", 5, 40, {
   1.167 +			{1, 0, 2, 0, -251},
   1.168 +			{1, 2, 2, 0,  251},
   1.169 +			{1, 4, 2, 0, -251}
   1.170 +		}
   1.171 +	},
   1.172 +	// ... specs?
   1.173 +	{"MacBookPro5,1", "SMCMotionSensor", 5, 40, {
   1.174 +			{1, 0, 2, 0, -251},
   1.175 +			{1, 2, 2, 0, -251},
   1.176 +			{1, 4, 2, 0,  251}
   1.177 +		}
   1.178 +	},
   1.179 +	// ... specs?
   1.180 +	{"MacBookPro5,2", "SMCMotionSensor", 5, 40, {
   1.181 +			{1, 0, 2, 0, -251},
   1.182 +			{1, 2, 2, 0, -251},
   1.183 +			{1, 4, 2, 0,  251}
   1.184 +		}
   1.185 +	},
   1.186 +	// This is speculative, based on a single user's report. Looks like the X and Y axes
   1.187 +	// are swapped. This is true for no other known Appple laptop.
   1.188 +	{"MacBookPro5,3", "SMCMotionSensor", 5, 40, {
   1.189 +			{1, 2, 2, 0, -251},
   1.190 +			{1, 0, 2, 0, -251},
   1.191 +			{1, 4, 2, 0, -251}
   1.192 +		}
   1.193 +	},
   1.194 +	// ... specs?
   1.195 +	{"MacBookPro5,4", "SMCMotionSensor", 5, 40, {
   1.196 +			{1, 0, 2, 0, -251},
   1.197 +			{1, 2, 2, 0, -251},
   1.198 +			{1, 4, 2, 0,  251}
   1.199 +		}
   1.200 +	},
   1.201 +	// ****** Model-independent methods ******
   1.202 +	// Seen once with PowerBook6,8 under system 10.3.9; I suspect
   1.203 +	// other G4-based 10.3.* systems might use this
   1.204 +	{"", "IOI2CMotionSensor", 24, 60, {
   1.205 +			{1, 0, 1, 0, 51.5},
   1.206 +			{1, 1, 1, 0, 51.5},
   1.207 +			{1, 2, 1, 0, 51.5}
   1.208 +		}
   1.209 +	},
   1.210 +	// PowerBook5,6 , PowerBook5,7 , PowerBook6,7 , PowerBook6,8
   1.211 +	// under OS X 10.4.*
   1.212 +	{"", "IOI2CMotionSensor", 21, 60, {
   1.213 +			{1, 0, 1, 0, 51.5},
   1.214 +			{1, 1, 1, 0, 51.5},
   1.215 +			{1, 2, 1, 0, 51.5}
   1.216 +		}
   1.217 +	},
   1.218 +	// PowerBook5,8 , PowerBook5,9 under OS X 10.4.*
   1.219 +	{"", "PMUMotionSensor", 21, 60, {
   1.220 +			// Each has two out of three gains negative, but it's different
   1.221 +			// for the different models. So, this will be right in two out
   1.222 +			// of three axis for either model.
   1.223 +			{1, 0, 1,  0, -51.5},
   1.224 +			{1, 1, 1, -6, -51.5},
   1.225 +			{1, 2, 1,  0, -51.5}
   1.226 +		}
   1.227 +	},
   1.228 +	// All MacBook, MacBookPro models. Hardware (at least on early MacBookPro 15")
   1.229 +	// is Kionix KXM52-1050 three-axis accelerometer chip. Data is at
   1.230 +	// http://kionix.com/Product-Index/product-index.htm. Specific MB and MBP models
   1.231 +	// that use this are: 
   1.232 +	//		MacBook1,1
   1.233 +	//		MacBook2,1
   1.234 +	//		MacBook3,1
   1.235 +	//		MacBook4,1
   1.236 +	//		MacBook5,1
   1.237 +	//		MacBook6,1
   1.238 +	//		MacBookAir1,1
   1.239 +	//		MacBookPro1,1
   1.240 +	//		MacBookPro1,2
   1.241 +	//		MacBookPro4,1
   1.242 +	//		MacBookPro5,5
   1.243 +	{"", "SMCMotionSensor", 5, 40, {
   1.244 +			{1, 0, 2, 0, 251},
   1.245 +			{1, 2, 2, 0, 251},
   1.246 +			{1, 4, 2, 0, 251}
   1.247 +		}
   1.248 +	}
   1.249 +};
   1.250 +
   1.251 +#define SENSOR_COUNT (sizeof(sensors)/sizeof(sensorSpec))
   1.252 +
   1.253 +#pragma mark Internal prototypes
   1.254 +
   1.255 +static int getData(sms_acceleration *accel, int calibrated, id logObject, SEL logSelector);
   1.256 +static float getAxis(int which, int calibrated);
   1.257 +static int signExtend(int value, int size);
   1.258 +static NSString *getModelName(void);
   1.259 +static NSString *getOSVersion(void);
   1.260 +static BOOL loadCalibration(void);
   1.261 +static void storeCalibration(void);
   1.262 +static void defaultCalibration(void);
   1.263 +static void deleteCalibration(void);
   1.264 +static int prefIntRead(NSString *prefName, BOOL *success);
   1.265 +static void prefIntWrite(NSString *prefName, int prefValue);
   1.266 +static float prefFloatRead(NSString *prefName, BOOL *success);
   1.267 +static void prefFloatWrite(NSString *prefName, float prefValue);
   1.268 +static void prefDelete(NSString *prefName);
   1.269 +static void prefSynchronize(void);
   1.270 +// static long getMicroseconds(void);
   1.271 +float fakeData(NSTimeInterval time);
   1.272 +
   1.273 +#pragma mark Static variables
   1.274 +
   1.275 +static int debugging = NO;		// True if debugging (synthetic data)
   1.276 +static io_connect_t connection;	// Connection for reading accel values
   1.277 +static int running = NO;		// True if we successfully started
   1.278 +static unsigned int sensorNum = 0;		// The current index into sensors[]
   1.279 +static const char *serviceName;	// The name of the current service
   1.280 +static char *iRecord, *oRecord;	// Pointers to read/write records for sensor
   1.281 +static int recordSize;			// Size of read/write records
   1.282 +static unsigned int function;	// Which kernel function should be used
   1.283 +static float zeros[3];			// X, Y and Z zero calibration values
   1.284 +static float onegs[3];			// X, Y and Z one-g calibration values
   1.285 +
   1.286 +#pragma mark Defines
   1.287 +
   1.288 +// Pattern for building axis letter from axis number
   1.289 +#define INT_TO_AXIS(a) (a == 0 ? @"X" : a == 1 ? @"Y" : @"Z")
   1.290 +// Name of configuration for given axis' zero (axis specified by integer)
   1.291 +#define ZERO_NAME(a) [NSString stringWithFormat:@"%@-Axis-Zero", INT_TO_AXIS(a)]
   1.292 +// Name of configuration for given axis' oneg (axis specified by integer)
   1.293 +#define ONEG_NAME(a) [NSString stringWithFormat:@"%@-Axis-One-g", INT_TO_AXIS(a)]
   1.294 +// Name of "Is calibrated" preference
   1.295 +#define CALIBRATED_NAME (@"Calibrated")
   1.296 +// Application domain for SeisMac library
   1.297 +#define APP_ID ((CFStringRef)@"com.suitable.SeisMacLib")
   1.298 +
   1.299 +// These #defines make the accelStartup code a LOT easier to read.
   1.300 +#undef LOG
   1.301 +#define LOG(message) \
   1.302 +	if (logObject) { \
   1.303 +		[logObject performSelector:logSelector withObject:message]; \
   1.304 +	}
   1.305 +#define LOG_ARG(format, var1) \
   1.306 +	if (logObject) { \
   1.307 +		[logObject performSelector:logSelector \
   1.308 +			withObject:[NSString stringWithFormat:format, var1]]; \
   1.309 +	}
   1.310 +#define LOG_2ARG(format, var1, var2) \
   1.311 +	if (logObject) { \
   1.312 +		[logObject performSelector:logSelector \
   1.313 +			withObject:[NSString stringWithFormat:format, var1, var2]]; \
   1.314 +	}
   1.315 +#define LOG_3ARG(format, var1, var2, var3) \
   1.316 +	if (logObject) { \
   1.317 +		[logObject performSelector:logSelector \
   1.318 +			withObject:[NSString stringWithFormat:format, var1, var2, var3]]; \
   1.319 +	}
   1.320 +
   1.321 +#pragma mark Function definitions
   1.322 +
   1.323 +// This starts up the accelerometer code, trying each possible sensor
   1.324 +// specification. Note that for logging purposes it
   1.325 +// takes an object and a selector; the object's selector is then invoked
   1.326 +// with a single NSString as argument giving progress messages. Example
   1.327 +// logging method:
   1.328 +//		- (void)logMessage: (NSString *)theString
   1.329 +// which would be used in accelStartup's invocation thusly:
   1.330 +//		result = accelStartup(self, @selector(logMessage:));
   1.331 +// If the object is nil, then no logging is done. Sets calibation from built-in
   1.332 +// value table. Returns ACCEL_SUCCESS for success, and other (negative)
   1.333 +// values for various failures (returns value indicating result of
   1.334 +// most successful trial).
   1.335 +int smsStartup(id logObject, SEL logSelector) {
   1.336 +	io_iterator_t iterator;
   1.337 +	io_object_t device;
   1.338 +	kern_return_t result;
   1.339 +	sms_acceleration accel;
   1.340 +	int failure_result = SMS_FAIL_MODEL;
   1.341 +		
   1.342 +	running = NO;
   1.343 +	debugging = NO;
   1.344 +	
   1.345 +	NSString *modelName = getModelName();
   1.346 +	
   1.347 +	LOG_ARG(@"Machine model: %@\n", modelName);
   1.348 +	LOG_ARG(@"OS X version: %@\n", getOSVersion());
   1.349 +	LOG_ARG(@"Accelerometer library version: %s\n", SMSLIB_VERSION);
   1.350 +		
   1.351 +	for (sensorNum = 0; sensorNum < SENSOR_COUNT; sensorNum++) {
   1.352 +		
   1.353 +		// Set up all specs for this type of sensor
   1.354 +		serviceName = sensors[sensorNum].name;
   1.355 +		recordSize = sensors[sensorNum].recordSize;
   1.356 +		function = sensors[sensorNum].function;
   1.357 +		
   1.358 +		LOG_3ARG(@"Trying service \"%s\" with selector %d and %d byte record:\n",
   1.359 +				serviceName, function, recordSize);
   1.360 +		
   1.361 +		NSString *targetName = [NSString stringWithCString:sensors[sensorNum].model
   1.362 +												  encoding:NSMacOSRomanStringEncoding];
   1.363 +		LOG_ARG(@"    Comparing model name to target \"%@\": ", targetName);
   1.364 +		if ([targetName length] == 0 || [modelName hasPrefix:targetName]) {
   1.365 +			LOG(@"success.\n");
   1.366 +		} else {
   1.367 +			LOG(@"failure.\n");
   1.368 +			// Don't need to increment failure_result.
   1.369 +			continue;
   1.370 +		}
   1.371 +		
   1.372 +		LOG(@"    Fetching dictionary for service: ");
   1.373 +		CFMutableDictionaryRef dict = IOServiceMatching(serviceName);
   1.374 +		
   1.375 +		if (dict) {
   1.376 +			LOG(@"success.\n");
   1.377 +		} else {
   1.378 +			LOG(@"failure.\n");
   1.379 +			if (failure_result < SMS_FAIL_DICTIONARY) {
   1.380 +				failure_result = SMS_FAIL_DICTIONARY;
   1.381 +			}
   1.382 +			continue;
   1.383 +		}
   1.384 +		
   1.385 +		LOG(@"    Getting list of matching services: ");
   1.386 +		result = IOServiceGetMatchingServices(kIOMasterPortDefault, 
   1.387 +										 dict, 
   1.388 +										 &iterator);
   1.389 +		
   1.390 +		if (result == KERN_SUCCESS) {
   1.391 +			LOG(@"success.\n");
   1.392 +		} else {
   1.393 +			LOG_ARG(@"failure, with return value 0x%x.\n", result);
   1.394 +			if (failure_result < SMS_FAIL_LIST_SERVICES) {
   1.395 +				failure_result = SMS_FAIL_LIST_SERVICES;
   1.396 +			}
   1.397 +			continue;
   1.398 +		}
   1.399 +		
   1.400 +		LOG(@"    Getting first device in list: ");
   1.401 +		device = IOIteratorNext(iterator);	
   1.402 +		
   1.403 +		if (device == 0) {
   1.404 +			LOG(@"failure.\n");
   1.405 +			if (failure_result < SMS_FAIL_NO_SERVICES) {
   1.406 +				failure_result = SMS_FAIL_NO_SERVICES;
   1.407 +			}
   1.408 +			continue;
   1.409 +		} else {
   1.410 +			LOG(@"success.\n");
   1.411 +			LOG(@"    Opening device: ");
   1.412 +		}
   1.413 +		
   1.414 +		result = IOServiceOpen(device, mach_task_self(), 0, &connection);
   1.415 +		
   1.416 +		if (result != KERN_SUCCESS) {
   1.417 +			LOG_ARG(@"failure, with return value 0x%x.\n", result);
   1.418 +			IOObjectRelease(device);
   1.419 +			if (failure_result < SMS_FAIL_OPENING) {
   1.420 +				failure_result = SMS_FAIL_OPENING;
   1.421 +			}
   1.422 +			continue;
   1.423 +		} else if (connection == 0) {
   1.424 +			LOG_ARG(@"'success', but didn't get a connection (return value was: 0x%x).\n", result);
   1.425 +			IOObjectRelease(device);
   1.426 +			if (failure_result < SMS_FAIL_CONNECTION) {
   1.427 +				failure_result = SMS_FAIL_CONNECTION;
   1.428 +			}
   1.429 +			continue;
   1.430 +		} else {
   1.431 +			IOObjectRelease(device);
   1.432 +			LOG(@"success.\n");
   1.433 +		}
   1.434 +		LOG(@"    Testing device.\n");
   1.435 +		
   1.436 +		defaultCalibration();
   1.437 +		
   1.438 +		iRecord = (char*) malloc(recordSize);
   1.439 +		oRecord = (char*) malloc(recordSize);
   1.440 +		
   1.441 +		running = YES;
   1.442 +		result = getData(&accel, true, logObject, logSelector);
   1.443 +		running = NO;
   1.444 +		
   1.445 +		if (result) {
   1.446 +			LOG_ARG(@"    Failure testing device, with result 0x%x.\n", result);
   1.447 +			free(iRecord);
   1.448 +			iRecord = 0;
   1.449 +			free(oRecord);
   1.450 +			oRecord = 0;
   1.451 +			if (failure_result < SMS_FAIL_ACCESS) {
   1.452 +				failure_result = SMS_FAIL_ACCESS;
   1.453 +			}
   1.454 +			continue;
   1.455 +		} else {
   1.456 +			LOG(@"    Success testing device!\n");
   1.457 +			running = YES;
   1.458 +			return SMS_SUCCESS;
   1.459 +		}
   1.460 +	}
   1.461 +	return failure_result;
   1.462 +}
   1.463 +
   1.464 +// This starts up the library in debug mode, ignoring the actual hardware.
   1.465 +// Returned data is in the form of 1Hz sine waves, with the X, Y and Z
   1.466 +// axes 120 degrees out of phase; "calibrated" data has range +/- (1.0/5);
   1.467 +// "uncalibrated" data has range +/- (256/5). X and Y axes centered on 0.0,
   1.468 +// Z axes centered on 1 (calibrated) or 256 (uncalibrated). 
   1.469 +// Don't use smsGetBufferLength or smsGetBufferData. Always returns SMS_SUCCESS.
   1.470 +int smsDebugStartup(id logObject, SEL logSelector) {
   1.471 +	LOG(@"Starting up in debug mode\n");
   1.472 +	debugging = YES;
   1.473 +	return SMS_SUCCESS;
   1.474 +}
   1.475 +
   1.476 +// Returns the current calibration values.
   1.477 +void smsGetCalibration(sms_calibration *calibrationRecord) {
   1.478 +	int x;
   1.479 +	
   1.480 +	for (x = 0; x < 3; x++) {
   1.481 +		calibrationRecord->zeros[x] = (debugging ? 0 : zeros[x]);
   1.482 +		calibrationRecord->onegs[x] = (debugging ? 256 : onegs[x]);
   1.483 +	}
   1.484 +}
   1.485 +
   1.486 +// Sets the calibration, but does NOT store it as a preference. If the argument
   1.487 +// is nil then the current calibration is set from the built-in value table.
   1.488 +void smsSetCalibration(sms_calibration *calibrationRecord) {
   1.489 +	int x;
   1.490 +	
   1.491 +	if (!debugging) {
   1.492 +		if (calibrationRecord) {
   1.493 +			for (x = 0; x < 3; x++) {
   1.494 +				zeros[x] = calibrationRecord->zeros[x];
   1.495 +				onegs[x] = calibrationRecord->onegs[x];
   1.496 +			}
   1.497 +		} else {
   1.498 +			defaultCalibration();
   1.499 +		}
   1.500 +	}
   1.501 +}
   1.502 +
   1.503 +// Stores the current calibration values as a stored preference.
   1.504 +void smsStoreCalibration(void) {
   1.505 +	if (!debugging)
   1.506 +		storeCalibration();
   1.507 +}
   1.508 +
   1.509 +// Loads the stored preference values into the current calibration.
   1.510 +// Returns YES if successful.
   1.511 +BOOL smsLoadCalibration(void) {
   1.512 +	if (debugging) {
   1.513 +		return YES;
   1.514 +	} else if (loadCalibration()) {
   1.515 +		return YES;
   1.516 +	} else {
   1.517 +		defaultCalibration();
   1.518 +		return NO;
   1.519 +	}
   1.520 +}
   1.521 +
   1.522 +// Deletes any stored calibration, and then takes the current calibration values
   1.523 +// from the built-in value table.
   1.524 +void smsDeleteCalibration(void) {
   1.525 +	if (!debugging) {
   1.526 +		deleteCalibration();
   1.527 +		defaultCalibration();
   1.528 +	}
   1.529 +}
   1.530 +
   1.531 +// Fills in the accel record with calibrated acceleration data. Takes
   1.532 +// 1-2ms to return a value. Returns 0 if success, error number if failure.
   1.533 +int smsGetData(sms_acceleration *accel) {
   1.534 +	NSTimeInterval time;
   1.535 +	if (debugging) {
   1.536 +		usleep(1500);						// Usually takes 1-2 milliseconds
   1.537 +		time = [NSDate timeIntervalSinceReferenceDate];
   1.538 +		accel->x = fakeData(time)/5;
   1.539 +		accel->y = fakeData(time - 1)/5;
   1.540 +		accel->z = fakeData(time - 2)/5 + 1.0;
   1.541 +		return true;
   1.542 +	} else {
   1.543 +		return getData(accel, true, nil, nil);
   1.544 +	}
   1.545 +}
   1.546 +
   1.547 +// Fills in the accel record with uncalibrated acceleration data.
   1.548 +// Returns 0 if success, error number if failure.
   1.549 +int smsGetUncalibratedData(sms_acceleration *accel) {
   1.550 +	NSTimeInterval time;
   1.551 +	if (debugging) {
   1.552 +		usleep(1500);						// Usually takes 1-2 milliseconds
   1.553 +		time = [NSDate timeIntervalSinceReferenceDate];
   1.554 +		accel->x = fakeData(time) * 256 / 5;
   1.555 +		accel->y = fakeData(time - 1) * 256 / 5;
   1.556 +		accel->z = fakeData(time - 2) * 256 / 5 + 256;
   1.557 +		return true;
   1.558 +	} else {
   1.559 +		return getData(accel, false, nil, nil);
   1.560 +	}
   1.561 +}
   1.562 +
   1.563 +// Returns the length of a raw block of data for the current type of sensor.
   1.564 +int smsGetBufferLength(void) {
   1.565 +	if (debugging) {
   1.566 +		return 0;
   1.567 +	} else if (running) {
   1.568 +		return sensors[sensorNum].recordSize;
   1.569 +	} else {
   1.570 +		return 0;
   1.571 +	}
   1.572 +}
   1.573 +
   1.574 +// Takes a pointer to accelGetRawLength() bytes; sets those bytes
   1.575 +// to return value from sensor. Make darn sure the buffer length is right!
   1.576 +void smsGetBufferData(char *buffer) {
   1.577 +	IOItemCount iSize = recordSize;
   1.578 +	IOByteCount oSize = recordSize;
   1.579 +	kern_return_t result;
   1.580 +
   1.581 +	if (debugging || running == NO) {
   1.582 +		return;
   1.583 +	}
   1.584 +
   1.585 +	memset(iRecord, 1, iSize);
   1.586 +	memset(buffer, 0, oSize);
   1.587 +#if __MAC_OS_X_VERSION_MIN_REQUIRED  >= 1050
   1.588 +	const size_t InStructSize = recordSize;
   1.589 +	size_t OutStructSize = recordSize;
   1.590 +	result = IOConnectCallStructMethod(connection,
   1.591 +						function,				// magic kernel function number
   1.592 +						(const void *)iRecord,
   1.593 +						InStructSize,
   1.594 +						(void *)buffer,
   1.595 +						&OutStructSize
   1.596 +					);
   1.597 +#else // __MAC_OS_X_VERSION_MIN_REQUIRED 1050
   1.598 +	result = IOConnectMethodStructureIStructureO(connection,
   1.599 +						function,				// magic kernel function number
   1.600 +						iSize,
   1.601 +						&oSize,
   1.602 +						iRecord,
   1.603 +						buffer
   1.604 +					);
   1.605 +#endif // __MAC_OS_X_VERSION_MIN_REQUIRED 1050
   1.606 +	
   1.607 +	if (result != KERN_SUCCESS) {
   1.608 +		running = NO;
   1.609 +	}
   1.610 +}
   1.611 +
   1.612 +// This returns an NSString describing the current calibration in
   1.613 +// human-readable form. Also include a description of the machine.
   1.614 +NSString *smsGetCalibrationDescription(void) {
   1.615 +	BOOL success;
   1.616 +	NSMutableString *s = [[NSMutableString alloc] init];
   1.617 +	
   1.618 +	if (debugging) {
   1.619 +		[s release];
   1.620 +		return @"Debugging!";
   1.621 +	}
   1.622 +	
   1.623 +	[s appendString:@"---- SeisMac Calibration Record ----\n \n"];
   1.624 +	[s appendFormat:@"Machine model: %@\n", 
   1.625 +		getModelName()];
   1.626 +	[s appendFormat:@"OS X build: %@\n", 
   1.627 +		getOSVersion()];
   1.628 +	[s appendFormat:@"SeisMacLib version %s, record %d\n \n", 
   1.629 +		SMSLIB_VERSION, sensorNum];
   1.630 +	[s appendFormat:@"Using service \"%s\", function index %d, size %d\n \n",
   1.631 +		serviceName, function, recordSize];
   1.632 +	if (prefIntRead(CALIBRATED_NAME, &success) && success) {
   1.633 +		[s appendString:@"Calibration values (from calibration):\n"];
   1.634 +	} else {
   1.635 +		[s appendString:@"Calibration values (from defaults):\n"];
   1.636 +	}
   1.637 +	[s appendFormat:@"    X-Axis-Zero  = %.2f\n", zeros[0]];
   1.638 +	[s appendFormat:@"    X-Axis-One-g = %.2f\n", onegs[0]];
   1.639 +	[s appendFormat:@"    Y-Axis-Zero  = %.2f\n", zeros[1]];
   1.640 +	[s appendFormat:@"    Y-Axis-One-g = %.2f\n", onegs[1]];
   1.641 +	[s appendFormat:@"    Z-Axis-Zero  = %.2f\n", zeros[2]];
   1.642 +	[s appendFormat:@"    Z-Axis-One-g = %.2f\n \n", onegs[2]];
   1.643 +	[s appendString:@"---- End Record ----\n"];
   1.644 +	return s;
   1.645 +}
   1.646 +
   1.647 +// Shuts down the accelerometer.
   1.648 +void smsShutdown(void) {
   1.649 +	if (!debugging) {
   1.650 +		running = NO;
   1.651 +		if (iRecord) free(iRecord);
   1.652 +		if (oRecord) free(oRecord);
   1.653 +		IOServiceClose(connection);
   1.654 +	}
   1.655 +}
   1.656 +
   1.657 +#pragma mark Internal functions
   1.658 +
   1.659 +// Loads the current calibration from the stored preferences.
   1.660 +// Returns true iff successful.
   1.661 +BOOL loadCalibration(void) {
   1.662 +	BOOL thisSuccess, allSuccess;
   1.663 +	int x;
   1.664 +	
   1.665 +	prefSynchronize();
   1.666 +	
   1.667 +	if (prefIntRead(CALIBRATED_NAME, &thisSuccess) && thisSuccess) {
   1.668 +		// Calibrated. Set all values from saved values.
   1.669 +		allSuccess = YES;
   1.670 +		for (x = 0; x < 3; x++) {
   1.671 +			zeros[x] = prefFloatRead(ZERO_NAME(x), &thisSuccess);
   1.672 +			allSuccess &= thisSuccess;
   1.673 +			onegs[x] = prefFloatRead(ONEG_NAME(x), &thisSuccess);
   1.674 +			allSuccess &= thisSuccess;
   1.675 +		}
   1.676 +		return allSuccess;
   1.677 +	}
   1.678 +	
   1.679 +	return NO;
   1.680 +}
   1.681 +
   1.682 +// Stores the current calibration into the stored preferences.
   1.683 +static void storeCalibration(void) {
   1.684 +	int x;
   1.685 +	prefIntWrite(CALIBRATED_NAME, 1);
   1.686 +	for (x = 0; x < 3; x++) {
   1.687 +		prefFloatWrite(ZERO_NAME(x), zeros[x]);
   1.688 +		prefFloatWrite(ONEG_NAME(x), onegs[x]);
   1.689 +	}	
   1.690 +	prefSynchronize();
   1.691 +}
   1.692 +
   1.693 +
   1.694 +// Sets the calibration to its default values.
   1.695 +void defaultCalibration(void) {
   1.696 +	int x;
   1.697 +	for (x = 0; x < 3; x++) {
   1.698 +		zeros[x] = sensors[sensorNum].axes[x].zerog;
   1.699 +		onegs[x] = sensors[sensorNum].axes[x].oneg;
   1.700 +	}
   1.701 +}
   1.702 +
   1.703 +// Deletes the stored preferences.
   1.704 +static void deleteCalibration(void) {
   1.705 +	int x;
   1.706 +	
   1.707 +	prefDelete(CALIBRATED_NAME);
   1.708 +	for (x = 0; x < 3; x++) {
   1.709 +		prefDelete(ZERO_NAME(x));
   1.710 +		prefDelete(ONEG_NAME(x));
   1.711 +	}
   1.712 +	prefSynchronize();
   1.713 +}
   1.714 +
   1.715 +// Read a named floating point value from the stored preferences. Sets
   1.716 +// the success boolean based on, you guessed it, whether it succeeds.
   1.717 +static float prefFloatRead(NSString *prefName, BOOL *success) {
   1.718 +	float result = 0.0f;
   1.719 +	
   1.720 +	CFPropertyListRef ref = CFPreferencesCopyAppValue((CFStringRef)prefName, 
   1.721 +													   APP_ID);
   1.722 +	// If there isn't such a preference, fail
   1.723 +	if (ref == NULL) {
   1.724 +		*success = NO;
   1.725 +		return result;
   1.726 +	}
   1.727 +	CFTypeID typeID = CFGetTypeID(ref);
   1.728 +	// Is it a number?
   1.729 +	if (typeID == CFNumberGetTypeID()) {
   1.730 +		// Is it a floating point number?
   1.731 +		if (CFNumberIsFloatType((CFNumberRef)ref)) {
   1.732 +			// Yup: grab it.
   1.733 +			*success = CFNumberGetValue((__CFNumber*)ref, kCFNumberFloat32Type, &result);
   1.734 +		} else {
   1.735 +			// Nope: grab as an integer, and convert to a float.
   1.736 +			long num;
   1.737 +			if (CFNumberGetValue((CFNumberRef)ref, kCFNumberLongType, &num)) {
   1.738 +				result = num;
   1.739 +				*success = YES;
   1.740 +			} else {
   1.741 +				*success = NO;
   1.742 +			}
   1.743 +		}
   1.744 +	// Or is it a string (e.g. set by the command line "defaults" command)?
   1.745 +	} else if (typeID == CFStringGetTypeID()) {
   1.746 +		result = (float)CFStringGetDoubleValue((CFStringRef)ref);
   1.747 +		*success = YES;
   1.748 +	} else {
   1.749 +		// Can't convert to a number: fail.
   1.750 +		*success = NO;
   1.751 +	}
   1.752 +	CFRelease(ref);
   1.753 +	return result;
   1.754 +}
   1.755 +
   1.756 +// Writes a named floating point value to the stored preferences.
   1.757 +static void prefFloatWrite(NSString *prefName, float prefValue) {
   1.758 +	CFNumberRef cfFloat = CFNumberCreate(kCFAllocatorDefault,
   1.759 +										 kCFNumberFloatType,
   1.760 +										 &prefValue);
   1.761 +	CFPreferencesSetAppValue((CFStringRef)prefName,
   1.762 +							 cfFloat,
   1.763 +							 APP_ID);
   1.764 +	CFRelease(cfFloat);
   1.765 +}
   1.766 +
   1.767 +// Reads a named integer value from the stored preferences.
   1.768 +static int prefIntRead(NSString *prefName, BOOL *success) {
   1.769 +	Boolean internalSuccess;
   1.770 +	CFIndex result = CFPreferencesGetAppIntegerValue((CFStringRef)prefName,
   1.771 +													 APP_ID, 
   1.772 +													 &internalSuccess);
   1.773 +	*success = internalSuccess;
   1.774 +	
   1.775 +	return result;
   1.776 +}
   1.777 +
   1.778 +// Writes a named integer value to the stored preferences.
   1.779 +static void prefIntWrite(NSString *prefName, int prefValue) {
   1.780 +	CFPreferencesSetAppValue((CFStringRef)prefName,
   1.781 +							 (CFNumberRef)[NSNumber numberWithInt:prefValue],
   1.782 +							 APP_ID);
   1.783 +}
   1.784 +
   1.785 +// Deletes the named preference values.
   1.786 +static void prefDelete(NSString *prefName) {
   1.787 +		CFPreferencesSetAppValue((CFStringRef)prefName,
   1.788 +								 NULL,
   1.789 +								 APP_ID);
   1.790 +}
   1.791 +
   1.792 +// Synchronizes the local preferences with the stored preferences.
   1.793 +static void prefSynchronize(void) {
   1.794 +	CFPreferencesAppSynchronize(APP_ID);
   1.795 +}
   1.796 +
   1.797 +// Internal version of accelGetData, with logging
   1.798 +int getData(sms_acceleration *accel, int calibrated, id logObject, SEL logSelector) {
   1.799 +	IOItemCount iSize = recordSize;
   1.800 +	IOByteCount oSize = recordSize;
   1.801 +	kern_return_t result;
   1.802 +	
   1.803 +	if (running == NO) {
   1.804 +		return -1;
   1.805 +	}
   1.806 +	
   1.807 +	memset(iRecord, 1, iSize);
   1.808 +	memset(oRecord, 0, oSize);
   1.809 +	
   1.810 +	LOG_2ARG(@"    Querying device (%u, %d): ", 
   1.811 +			 sensors[sensorNum].function, sensors[sensorNum].recordSize);
   1.812 +	
   1.813 +#if __MAC_OS_X_VERSION_MIN_REQUIRED  >= 1050
   1.814 +	const size_t InStructSize = recordSize;
   1.815 +	size_t OutStructSize = recordSize;
   1.816 +	result = IOConnectCallStructMethod(connection,
   1.817 +						function,				// magic kernel function number
   1.818 +						(const void *)iRecord,
   1.819 +						InStructSize,
   1.820 +						(void *)oRecord,
   1.821 +						&OutStructSize
   1.822 +					);
   1.823 +#else // __MAC_OS_X_VERSION_MIN_REQUIRED 1050
   1.824 +	result = IOConnectMethodStructureIStructureO(connection,
   1.825 +						function,				// magic kernel function number
   1.826 +						iSize,
   1.827 +						&oSize,
   1.828 +						iRecord,
   1.829 +						oRecord
   1.830 +					);
   1.831 +#endif // __MAC_OS_X_VERSION_MIN_REQUIRED 1050
   1.832 +
   1.833 +	if (result != KERN_SUCCESS) {
   1.834 +		LOG(@"failed.\n");
   1.835 +		running = NO;
   1.836 +		return result;
   1.837 +	} else {
   1.838 +		LOG(@"succeeded.\n");
   1.839 +		
   1.840 +		accel->x = getAxis(0, calibrated);
   1.841 +		accel->y = getAxis(1, calibrated);
   1.842 +		accel->z = getAxis(2, calibrated);
   1.843 +		return 0;
   1.844 +	}
   1.845 +}
   1.846 +
   1.847 +// Given the returned record, extracts the value of the given axis. If
   1.848 +// calibrated, then zero G is 0.0, and one G is 1.0.
   1.849 +float getAxis(int which, int calibrated) {
   1.850 +	// Get various values (to make code cleaner)
   1.851 +	int indx = sensors[sensorNum].axes[which].index;
   1.852 +	int size = sensors[sensorNum].axes[which].size;
   1.853 +	float zerog = zeros[which];
   1.854 +	float oneg = onegs[which];
   1.855 +	// Storage for value to be returned
   1.856 +	int value = 0;
   1.857 +	
   1.858 +	// Although the values in the returned record should have the proper
   1.859 +	// endianness, we still have to get it into the proper end of value.
   1.860 +#if (BYTE_ORDER == BIG_ENDIAN)
   1.861 +	// On PowerPC processors
   1.862 +	memcpy(((char *)&value) + (sizeof(int) - size), &oRecord[indx], size);
   1.863 +#endif
   1.864 +#if (BYTE_ORDER == LITTLE_ENDIAN)
   1.865 +	// On Intel processors
   1.866 +	memcpy(&value, &oRecord[indx], size);
   1.867 +#endif
   1.868 +	
   1.869 +	value = signExtend(value, size);
   1.870 +	
   1.871 +	if (calibrated) {
   1.872 +		// Scale and shift for zero.
   1.873 +		return ((float)(value - zerog)) / oneg;
   1.874 +	} else {
   1.875 +		return value;
   1.876 +	}
   1.877 +}
   1.878 +
   1.879 +// Extends the sign, given the length of the value.
   1.880 +int signExtend(int value, int size) {
   1.881 +	// Extend sign
   1.882 +	switch (size) {
   1.883 +		case 1:
   1.884 +			if (value & 0x00000080)
   1.885 +				value |= 0xffffff00;
   1.886 +			break;
   1.887 +		case 2:
   1.888 +			if (value & 0x00008000)
   1.889 +				value |= 0xffff0000;
   1.890 +			break;
   1.891 +		case 3:
   1.892 +			if (value & 0x00800000)
   1.893 +				value |= 0xff000000;
   1.894 +			break;
   1.895 +	}
   1.896 +	return value;
   1.897 +}
   1.898 +
   1.899 +// Returns the model name of the computer (e.g. "MacBookPro1,1")
   1.900 +NSString *getModelName(void) {
   1.901 +	char model[32];
   1.902 +	size_t len = sizeof(model);
   1.903 +	int name[2] = {CTL_HW, HW_MODEL};
   1.904 +	NSString *result;
   1.905 +	
   1.906 +	if (sysctl(name, 2, &model, &len, NULL, 0) == 0) {
   1.907 +		result = [NSString stringWithFormat:@"%s", model];
   1.908 +	} else {
   1.909 +		result = @"";
   1.910 +	}
   1.911 +	
   1.912 +	return result;
   1.913 +}
   1.914 +
   1.915 +// Returns the current OS X version and build (e.g. "10.4.7 (build 8J2135a)")
   1.916 +NSString *getOSVersion(void) {
   1.917 +	NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:
   1.918 +		@"/System/Library/CoreServices/SystemVersion.plist"];
   1.919 +	NSString *versionString = [dict objectForKey:@"ProductVersion"];
   1.920 +	NSString *buildString = [dict objectForKey:@"ProductBuildVersion"];
   1.921 +	NSString *wholeString = [NSString stringWithFormat:@"%@ (build %@)", 
   1.922 +		versionString, buildString];
   1.923 +	return wholeString;
   1.924 +}
   1.925 +
   1.926 +// Returns time within the current second in microseconds.
   1.927 +// long getMicroseconds() {
   1.928 +//	struct timeval t;
   1.929 +//	gettimeofday(&t, 0);
   1.930 +//	return t.tv_usec;
   1.931 +//}
   1.932 +
   1.933 +// Returns fake data given the time. Range is +/-1.
   1.934 +float fakeData(NSTimeInterval time) {
   1.935 +	long secs = lround(floor(time));
   1.936 +	int secsMod3 = secs % 3;
   1.937 +	double angle = time * 10 * M_PI * 2;
   1.938 +	double mag = exp(-(time - (secs - secsMod3)) * 2);
   1.939 +	return sin(angle) * mag;
   1.940 +}
   1.941 +	

mercurial