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 +