diff options
Diffstat (limited to '')
-rw-r--r-- | hal/cocoa/CocoaBattery.cpp | 317 | ||||
-rw-r--r-- | hal/cocoa/CocoaSensor.mm | 137 | ||||
-rw-r--r-- | hal/cocoa/smslib.h | 158 | ||||
-rw-r--r-- | hal/cocoa/smslib.mm | 870 |
4 files changed, 1482 insertions, 0 deletions
diff --git a/hal/cocoa/CocoaBattery.cpp b/hal/cocoa/CocoaBattery.cpp new file mode 100644 index 0000000000..a6c4b689e1 --- /dev/null +++ b/hal/cocoa/CocoaBattery.cpp @@ -0,0 +1,317 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim set: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#import <CoreFoundation/CoreFoundation.h> +#import <IOKit/ps/IOPowerSources.h> +#import <IOKit/ps/IOPSKeys.h> + +#include <mozilla/Hal.h> +#include <mozilla/dom/battery/Constants.h> +#include <mozilla/Services.h> + +#include <nsIObserverService.h> +#include <nsIObserver.h> + +#include <dlfcn.h> + +#define IOKIT_FRAMEWORK_PATH "/System/Library/Frameworks/IOKit.framework/IOKit" + +#ifndef kIOPSTimeRemainingUnknown +# define kIOPSTimeRemainingUnknown ((CFTimeInterval)-1.0) +#endif +#ifndef kIOPSTimeRemainingUnlimited +# define kIOPSTimeRemainingUnlimited ((CFTimeInterval)-2.0) +#endif + +using namespace mozilla::dom::battery; + +namespace mozilla { +namespace hal_impl { + +typedef CFTimeInterval (*IOPSGetTimeRemainingEstimateFunc)(void); + +class MacPowerInformationService { + public: + static MacPowerInformationService* GetInstance(); + static void Shutdown(); + static bool IsShuttingDown(); + + void BeginListening(); + void StopListening(); + + static void HandleChange(void* aContext); + + ~MacPowerInformationService(); + + private: + MacPowerInformationService(); + + // The reference to the runloop that is notified of power changes. + CFRunLoopSourceRef mRunLoopSource; + + double mLevel; + bool mCharging; + double mRemainingTime; + bool mShouldNotify; + + friend void GetCurrentBatteryInformation( + hal::BatteryInformation* aBatteryInfo); + + static MacPowerInformationService* sInstance; + static bool sShuttingDown; + + static void* sIOKitFramework; + static IOPSGetTimeRemainingEstimateFunc sIOPSGetTimeRemainingEstimate; +}; + +void* MacPowerInformationService::sIOKitFramework; +IOPSGetTimeRemainingEstimateFunc + MacPowerInformationService::sIOPSGetTimeRemainingEstimate; + +/* + * Implementation of mozilla::hal_impl::EnableBatteryNotifications, + * mozilla::hal_impl::DisableBatteryNotifications, + * and mozilla::hal_impl::GetCurrentBatteryInformation. + */ + +void EnableBatteryNotifications() { + if (!MacPowerInformationService::IsShuttingDown()) { + MacPowerInformationService::GetInstance()->BeginListening(); + } +} + +void DisableBatteryNotifications() { + if (!MacPowerInformationService::IsShuttingDown()) { + MacPowerInformationService::GetInstance()->StopListening(); + } +} + +void GetCurrentBatteryInformation(hal::BatteryInformation* aBatteryInfo) { + MacPowerInformationService* powerService = + MacPowerInformationService::GetInstance(); + + aBatteryInfo->level() = powerService->mLevel; + aBatteryInfo->charging() = powerService->mCharging; + aBatteryInfo->remainingTime() = powerService->mRemainingTime; +} + +bool MacPowerInformationService::sShuttingDown = false; + +/* + * Following is the implementation of MacPowerInformationService. + */ + +MacPowerInformationService* MacPowerInformationService::sInstance = nullptr; + +namespace { +struct SingletonDestroyer final : public nsIObserver { + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + private: + ~SingletonDestroyer() {} +}; + +NS_IMPL_ISUPPORTS(SingletonDestroyer, nsIObserver) + +NS_IMETHODIMP +SingletonDestroyer::Observe(nsISupports*, const char* aTopic, const char16_t*) { + MOZ_ASSERT(!strcmp(aTopic, "xpcom-shutdown")); + MacPowerInformationService::Shutdown(); + return NS_OK; +} +} // namespace + +/* static */ +MacPowerInformationService* MacPowerInformationService::GetInstance() { + if (sInstance) { + return sInstance; + } + + sInstance = new MacPowerInformationService(); + + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->AddObserver(new SingletonDestroyer(), "xpcom-shutdown", false); + } + + return sInstance; +} + +bool MacPowerInformationService::IsShuttingDown() { return sShuttingDown; } + +void MacPowerInformationService::Shutdown() { + sShuttingDown = true; + delete sInstance; + sInstance = nullptr; +} + +MacPowerInformationService::MacPowerInformationService() + : mRunLoopSource(nullptr), + mLevel(kDefaultLevel), + mCharging(kDefaultCharging), + mRemainingTime(kDefaultRemainingTime), + mShouldNotify(false) { + // IOPSGetTimeRemainingEstimate (and the related constants) are only available + // on 10.7, so we test for their presence at runtime. + sIOKitFramework = dlopen(IOKIT_FRAMEWORK_PATH, RTLD_LAZY | RTLD_LOCAL); + if (sIOKitFramework) { + sIOPSGetTimeRemainingEstimate = (IOPSGetTimeRemainingEstimateFunc)dlsym( + sIOKitFramework, "IOPSGetTimeRemainingEstimate"); + } else { + sIOPSGetTimeRemainingEstimate = nullptr; + } +} + +MacPowerInformationService::~MacPowerInformationService() { + MOZ_ASSERT(!mRunLoopSource, + "The observers have not been correctly removed! " + "(StopListening should have been called)"); + + if (sIOKitFramework) { + dlclose(sIOKitFramework); + } +} + +void MacPowerInformationService::BeginListening() { + // Set ourselves up to be notified about changes. + MOZ_ASSERT(!mRunLoopSource, + "IOPS Notification Loop Source already set up. " + "(StopListening should have been called)"); + + mRunLoopSource = ::IOPSNotificationCreateRunLoopSource(HandleChange, this); + if (mRunLoopSource) { + ::CFRunLoopAddSource(::CFRunLoopGetCurrent(), mRunLoopSource, + kCFRunLoopDefaultMode); + + // Invoke our callback now so we have data if GetCurrentBatteryInformation + // is called before a change happens. + HandleChange(this); + mShouldNotify = true; + } +} + +void MacPowerInformationService::StopListening() { + if (mRunLoopSource) { + ::CFRunLoopRemoveSource(::CFRunLoopGetCurrent(), mRunLoopSource, + kCFRunLoopDefaultMode); + mRunLoopSource = nullptr; + } +} + +void MacPowerInformationService::HandleChange(void* aContext) { + MacPowerInformationService* power = + static_cast<MacPowerInformationService*>(aContext); + + CFTypeRef data = ::IOPSCopyPowerSourcesInfo(); + if (!data) { + ::CFRelease(data); + return; + } + + // Get the list of power sources. + CFArrayRef list = ::IOPSCopyPowerSourcesList(data); + if (!list) { + ::CFRelease(list); + return; + } + + // Default values. These will be used if there are 0 sources or we can't find + // better information. + double level = kDefaultLevel; + double charging = kDefaultCharging; + double remainingTime = kDefaultRemainingTime; + + // Look for the first battery power source to give us the information we need. + // Usually there's only 1 available, depending on current power source. + for (CFIndex i = 0; i < ::CFArrayGetCount(list); ++i) { + CFTypeRef source = ::CFArrayGetValueAtIndex(list, i); + CFDictionaryRef currPowerSourceDesc = + ::IOPSGetPowerSourceDescription(data, source); + if (!currPowerSourceDesc) { + continue; + } + + // Get a battery level estimate. This key is required but does not always + // exist. + int currentCapacity = 0; + const void* cfRef = ::CFDictionaryGetValue(currPowerSourceDesc, + CFSTR(kIOPSCurrentCapacityKey)); + if (!cfRef) { + continue; + } + ::CFNumberGetValue((CFNumberRef)cfRef, kCFNumberSInt32Type, + ¤tCapacity); + + // This key is also required. + int maxCapacity = 0; + cfRef = + ::CFDictionaryGetValue(currPowerSourceDesc, CFSTR(kIOPSMaxCapacityKey)); + ::CFNumberGetValue((CFNumberRef)cfRef, kCFNumberSInt32Type, &maxCapacity); + + if (maxCapacity > 0) { + level = static_cast<double>(currentCapacity) / + static_cast<double>(maxCapacity); + } + + // Find out if we're charging. + // This key is optional, we fallback to kDefaultCharging if the current + // power source doesn't have that info. + if (::CFDictionaryGetValueIfPresent(currPowerSourceDesc, + CFSTR(kIOPSIsChargingKey), &cfRef)) { + charging = ::CFBooleanGetValue((CFBooleanRef)cfRef); + + // Get an estimate of how long it's going to take until we're fully + // charged. This key is optional. + if (charging) { + // Default value that will be changed if we happen to find the actual + // remaining time. + remainingTime = + level == 1.0 ? kDefaultRemainingTime : kUnknownRemainingTime; + + if (::CFDictionaryGetValueIfPresent( + currPowerSourceDesc, CFSTR(kIOPSTimeToFullChargeKey), &cfRef)) { + int timeToCharge; + ::CFNumberGetValue((CFNumberRef)cfRef, kCFNumberIntType, + &timeToCharge); + if (timeToCharge != kIOPSTimeRemainingUnknown) { + remainingTime = timeToCharge * 60; + } + } + } else if (sIOPSGetTimeRemainingEstimate) { // not charging + // See if we can get a time estimate. + CFTimeInterval estimate = sIOPSGetTimeRemainingEstimate(); + if (estimate == kIOPSTimeRemainingUnlimited || + estimate == kIOPSTimeRemainingUnknown) { + remainingTime = kUnknownRemainingTime; + } else { + remainingTime = estimate; + } + } + } + + break; + } + + bool isNewData = level != power->mLevel || charging != power->mCharging || + remainingTime != power->mRemainingTime; + + power->mRemainingTime = remainingTime; + power->mCharging = charging; + power->mLevel = level; + + // Notify the observers if stuff changed. + if (power->mShouldNotify && isNewData) { + hal::NotifyBatteryChange(hal::BatteryInformation( + power->mLevel, power->mCharging, power->mRemainingTime)); + } + + ::CFRelease(data); + ::CFRelease(list); +} + +} // namespace hal_impl +} // namespace mozilla diff --git a/hal/cocoa/CocoaSensor.mm b/hal/cocoa/CocoaSensor.mm new file mode 100644 index 0000000000..96e00369e8 --- /dev/null +++ b/hal/cocoa/CocoaSensor.mm @@ -0,0 +1,137 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "Hal.h" +#include "nsITimer.h" +#include "smslib.h" +#include "nsComponentManagerUtils.h" + +#include <mach/mach.h> +#include <cmath> +#import <IOKit/IOKitLib.h> + +#define MEAN_GRAVITY 9.80665 +#define DEFAULT_SENSOR_POLL 100 +using namespace mozilla::hal; +namespace mozilla { +namespace hal_impl { +static nsITimer* sUpdateTimer = nullptr; +static bool sActiveSensors[NUM_SENSOR_TYPE]; +static io_connect_t sDataPort = IO_OBJECT_NULL; +static uint64_t sLastMean = -1; +static float LMUvalueToLux(uint64_t aValue) { + // Conversion formula from regression. See Bug 793728. + // -3*(10^-27)*x^4 + 2.6*(10^-19)*x^3 + -3.4*(10^-12)*x^2 + 3.9*(10^-5)*x - 0.19 + long double powerC4 = 1 / pow((long double)10, 27); + long double powerC3 = 1 / pow((long double)10, 19); + long double powerC2 = 1 / pow((long double)10, 12); + long double powerC1 = 1 / pow((long double)10, 5); + + long double term4 = -3.0 * powerC4 * pow(aValue, 4); + long double term3 = 2.6 * powerC3 * pow(aValue, 3); + long double term2 = -3.4 * powerC2 * pow(aValue, 2); + long double term1 = 3.9 * powerC1 * aValue; + + float lux = ceil(static_cast<float>(term4 + term3 + term2 + term1 - 0.19)); + return lux > 0 ? lux : 0; +} +void UpdateHandler(nsITimer* aTimer, void* aClosure) { + for (int i = 0; i < NUM_SENSOR_TYPE; i++) { + if (!sActiveSensors[i]) { + continue; + } + SensorType sensor = static_cast<SensorType>(i); + nsTArray<float> values; + if (sensor == SENSOR_ACCELERATION) { + sms_acceleration accel; + smsGetData(&accel); + + values.AppendElement(accel.x * MEAN_GRAVITY); + values.AppendElement(accel.y * MEAN_GRAVITY); + values.AppendElement(accel.z * MEAN_GRAVITY); + } else if (sensor == SENSOR_LIGHT && sDataPort != IO_OBJECT_NULL) { + kern_return_t kr; + uint32_t outputs = 2; + uint64_t lightLMU[outputs]; + + kr = IOConnectCallMethod(sDataPort, 0, nil, 0, nil, 0, lightLMU, &outputs, nil, 0); + if (kr == KERN_SUCCESS) { + uint64_t mean = (lightLMU[0] + lightLMU[1]) / 2; + if (mean == sLastMean) { + continue; + } + sLastMean = mean; + values.AppendElement(LMUvalueToLux(mean)); + } else if (kr == kIOReturnBusy) { + continue; + } + } + + hal::SensorData sdata(sensor, PR_Now(), values); + hal::NotifySensorChange(sdata); + } +} +void EnableSensorNotifications(SensorType aSensor) { + if (aSensor == SENSOR_ACCELERATION) { + int result = smsStartup(nil, nil); + + if (result != SMS_SUCCESS) { + return; + } + + if (!smsLoadCalibration()) { + return; + } + } else if (aSensor == SENSOR_LIGHT) { + io_service_t serviceObject; + serviceObject = + IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("AppleLMUController")); + if (!serviceObject) { + return; + } + kern_return_t kr; + kr = IOServiceOpen(serviceObject, mach_task_self(), 0, &sDataPort); + IOObjectRelease(serviceObject); + if (kr != KERN_SUCCESS) { + return; + } + } else { + NS_WARNING("EnableSensorNotifications called on an unknown sensor type"); + return; + } + sActiveSensors[aSensor] = true; + + if (!sUpdateTimer) { + CallCreateInstance("@mozilla.org/timer;1", &sUpdateTimer); + if (sUpdateTimer) { + sUpdateTimer->InitWithNamedFuncCallback(UpdateHandler, nullptr, DEFAULT_SENSOR_POLL, + nsITimer::TYPE_REPEATING_SLACK, + "hal_impl::UpdateHandler"); + } + } +} +void DisableSensorNotifications(SensorType aSensor) { + if (!sActiveSensors[aSensor] || (aSensor != SENSOR_ACCELERATION && aSensor != SENSOR_LIGHT)) { + return; + } + + sActiveSensors[aSensor] = false; + + if (aSensor == SENSOR_ACCELERATION) { + smsShutdown(); + } else if (aSensor == SENSOR_LIGHT) { + IOServiceClose(sDataPort); + } + // If all sensors are disabled, cancel the update timer. + if (sUpdateTimer) { + for (int i = 0; i < NUM_SENSOR_TYPE; i++) { + if (sActiveSensors[i]) { + return; + } + } + sUpdateTimer->Cancel(); + NS_RELEASE(sUpdateTimer); + } +} +} // namespace hal_impl +} // namespace mozilla diff --git a/hal/cocoa/smslib.h b/hal/cocoa/smslib.h new file mode 100644 index 0000000000..41604cebec --- /dev/null +++ b/hal/cocoa/smslib.h @@ -0,0 +1,158 @@ +/* + * smslib.h + * + * SMSLib Sudden Motion Sensor Access Library + * Copyright (c) 2010 Suitable Systems + * All rights reserved. + * + * Developed by: Daniel Griscom + * Suitable Systems + * http://www.suitable.com + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal with the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimers. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimers in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the names of Suitable Systems nor the names of its + * contributors may be used to endorse or promote products derived from + * this Software without specific prior written permission. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE. + * + * For more information about SMSLib, see + * <http://www.suitable.com/tools/smslib.html> + * or contact + * Daniel Griscom + * Suitable Systems + * 1 Centre Street, Suite 204 + * Wakefield, MA 01880 + * (781) 665-0053 + * + */ + +#import <Foundation/Foundation.h> + +#define SMSLIB_VERSION "1.8" + +#pragma mark Structure definitions + +// Structure for specifying a 3-axis acceleration. 0.0 means "zero gravities", +// 1.0 means "one gravity". +typedef struct sms_acceleration { + float x; // Right-left acceleration (positive is rightwards) + float y; // Front-rear acceleration (positive is rearwards) + float z; // Up-down acceleration (positive is upwards) +} sms_acceleration; + +// Structure for specifying a calibration. +typedef struct sms_calibration { + float zeros[3]; // Zero points for three axes (X, Y, Z) + float onegs[3]; // One gravity values for three axes +} sms_calibration; + +#pragma mark Return value definitions + +// These are the return values for accelStartup(), giving the +// various stages where the most successful attempt at accessing +// the accelerometer failed. The higher the value, the further along the +// software progressed before failing. The options are: +// - Didn't match model name +#define SMS_FAIL_MODEL (-7) +// - Failure getting dictionary matching desired services +#define SMS_FAIL_DICTIONARY (-6) +// - Failure getting list of services +#define SMS_FAIL_LIST_SERVICES (-5) +// - Failure if list of services is empty. The process generally fails +// here if run on a machine without a Sudden Motion Sensor. +#define SMS_FAIL_NO_SERVICES (-4) +// - Failure if error opening device. +#define SMS_FAIL_OPENING (-3) +// - Failure if opened, but didn't get a connection +#define SMS_FAIL_CONNECTION (-2) +// - Failure if couldn't access connction using given function and size. This +// is where the process would probably fail with a change in Apple's API. +// Driver problems often also cause failures here. +#define SMS_FAIL_ACCESS (-1) +// - Success! +#define SMS_SUCCESS (0) + +#pragma mark Function declarations + +// This starts up the accelerometer code, trying each possible sensor +// specification. Note that for logging purposes it +// takes an object and a selector; the object's selector is then invoked +// with a single NSString as argument giving progress messages. Example +// logging method: +// - (void)logMessage: (NSString *)theString +// which would be used in accelStartup's invocation thusly: +// result = accelStartup(self, @selector(logMessage:)); +// If the object is nil, then no logging is done. Sets calibation from built-in +// value table. Returns ACCEL_SUCCESS for success, and other (negative) +// values for various failures (returns value indicating result of +// most successful trial). +int smsStartup(id logObject, SEL logSelector); + +// This starts up the library in debug mode, ignoring the actual hardware. +// Returned data is in the form of 1Hz sine waves, with the X, Y and Z +// axes 120 degrees out of phase; "calibrated" data has range +/- (1.0/5); +// "uncalibrated" data has range +/- (256/5). X and Y axes centered on 0.0, +// Z axes centered on 1 (calibrated) or 256 (uncalibrated). +// Don't use smsGetBufferLength or smsGetBufferData. Always returns SMS_SUCCESS. +int smsDebugStartup(id logObject, SEL logSelector); + +// Returns the current calibration values. +void smsGetCalibration(sms_calibration* calibrationRecord); + +// Sets the calibration, but does NOT store it as a preference. If the argument +// is nil then the current calibration is set from the built-in value table. +void smsSetCalibration(sms_calibration* calibrationRecord); + +// Stores the current calibration values as a stored preference. +void smsStoreCalibration(void); + +// Loads the stored preference values into the current calibration. +// Returns YES if successful. +BOOL smsLoadCalibration(void); + +// Deletes any stored calibration, and then takes the current calibration values +// from the built-in value table. +void smsDeleteCalibration(void); + +// Fills in the accel record with calibrated acceleration data. Takes +// 1-2ms to return a value. Returns 0 if success, error number if failure. +int smsGetData(sms_acceleration* accel); + +// Fills in the accel record with uncalibrated acceleration data. +// Returns 0 if success, error number if failure. +int smsGetUncalibratedData(sms_acceleration* accel); + +// Returns the length of a raw block of data for the current type of sensor. +int smsGetBufferLength(void); + +// Takes a pointer to accelGetRawLength() bytes; sets those bytes +// to return value from sensor. Make darn sure the buffer length is right! +void smsGetBufferData(char* buffer); + +// This returns an NSString describing the current calibration in +// human-readable form. Also include a description of the machine. +NSString* smsGetCalibrationDescription(void); + +// Shuts down the accelerometer. +void smsShutdown(void); diff --git a/hal/cocoa/smslib.mm b/hal/cocoa/smslib.mm new file mode 100644 index 0000000000..1a27d404a3 --- /dev/null +++ b/hal/cocoa/smslib.mm @@ -0,0 +1,870 @@ +/* + * smslib.m + * + * SMSLib Sudden Motion Sensor Access Library + * Copyright (c) 2010 Suitable Systems + * All rights reserved. + * + * Developed by: Daniel Griscom + * Suitable Systems + * http://www.suitable.com + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal with the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimers. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimers in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the names of Suitable Systems nor the names of its + * contributors may be used to endorse or promote products derived from + * this Software without specific prior written permission. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE. + * + * For more information about SMSLib, see + * <http://www.suitable.com/tools/smslib.html> + * or contact + * Daniel Griscom + * Suitable Systems + * 1 Centre Street, Suite 204 + * Wakefield, MA 01880 + * (781) 665-0053 + * + */ + +#import <IOKit/IOKitLib.h> +#import <sys/sysctl.h> +#import <math.h> +#import "smslib.h" + +#pragma mark Internal structures + +// Represents a single axis of a type of sensor. +typedef struct axisStruct { + int enabled; // Non-zero if axis is valid in this sensor + int index; // Location in struct of first byte + int size; // Number of bytes + float zerog; // Value meaning "zero g" + float oneg; // Change in value meaning "increase of one g" + // (can be negative if axis sensor reversed) +} axisStruct; + +// Represents the configuration of a type of sensor. +typedef struct sensorSpec { + const char* model; // Prefix of model to be tested + const char* name; // Name of device to be read + unsigned int function; // Kernel function index + int recordSize; // Size of record to be sent/received + axisStruct axes[3]; // Description of three axes (X, Y, Z) +} sensorSpec; + +// Configuration of all known types of sensors. The configurations are +// tried in order until one succeeds in returning data. +// All default values are set here, but each axis' zerog and oneg values +// may be changed to saved (calibrated) values. +// +// These values came from SeisMaCalibrate calibration reports. In general I've +// found the following: +// - All Intel-based SMSs have 250 counts per g, centered on 0, but the signs +// are different (and in one case two axes are swapped) +// - PowerBooks and iBooks all have sensors centered on 0, and reading +// 50-53 steps per gravity (but with differing polarities!) +// - PowerBooks and iBooks of the same model all have the same axis polarities +// - PowerBook and iBook access methods are model- and OS version-specific +// +// So, the sequence of tests is: +// - Try model-specific access methods. Note that the test is for a match to the +// beginning of the model name, e.g. the record with model name "MacBook" +// matches computer models "MacBookPro1,2" and "MacBook1,1" (and "" +// matches any model). +// - If no model-specific record's access fails, then try each model-independent +// access method in order, stopping when one works. +static const sensorSpec sensors[] = { + // ****** Model-dependent methods ****** + // The PowerBook5,6 is one of the G4 models that seems to lose + // SMS access until the next reboot. + {"PowerBook5,6", + "IOI2CMotionSensor", + 21, + 60, + {{1, 0, 1, 0, 51.5}, {1, 1, 1, 0, -51.5}, {1, 2, 1, 0, -51.5}}}, + // The PowerBook5,7 is one of the G4 models that seems to lose + // SMS access until the next reboot. + {"PowerBook5,7", + "IOI2CMotionSensor", + 21, + 60, + {{1, 0, 1, 0, 51.5}, {1, 1, 1, 0, 51.5}, {1, 2, 1, 0, 51.5}}}, + // Access seems to be reliable on the PowerBook5,8 + {"PowerBook5,8", + "PMUMotionSensor", + 21, + 60, + {{1, 0, 1, 0, -51.5}, {1, 1, 1, 0, 51.5}, {1, 2, 1, 0, -51.5}}}, + // Access seems to be reliable on the PowerBook5,9 + {"PowerBook5,9", + "PMUMotionSensor", + 21, + 60, + {{1, 0, 1, 0, 51.5}, {1, 1, 1, 0, -51.5}, {1, 2, 1, 0, -51.5}}}, + // The PowerBook6,7 is one of the G4 models that seems to lose + // SMS access until the next reboot. + {"PowerBook6,7", + "IOI2CMotionSensor", + 21, + 60, + {{1, 0, 1, 0, 51.5}, {1, 1, 1, 0, 51.5}, {1, 2, 1, 0, 51.5}}}, + // The PowerBook6,8 is one of the G4 models that seems to lose + // SMS access until the next reboot. + {"PowerBook6,8", + "IOI2CMotionSensor", + 21, + 60, + {{1, 0, 1, 0, 51.5}, {1, 1, 1, 0, 51.5}, {1, 2, 1, 0, 51.5}}}, + // MacBook Pro Core 2 Duo 17". Note the reversed Y and Z axes. + {"MacBookPro2,1", + "SMCMotionSensor", + 5, + 40, + {{1, 0, 2, 0, 251}, {1, 2, 2, 0, -251}, {1, 4, 2, 0, -251}}}, + // MacBook Pro Core 2 Duo 15" AND 17" with LED backlight, introduced June '07. + // NOTE! The 17" machines have the signs of their X and Y axes reversed + // from this calibration, but there's no clear way to discriminate between + // the two machines. + {"MacBookPro3,1", + "SMCMotionSensor", + 5, + 40, + {{1, 0, 2, 0, -251}, {1, 2, 2, 0, 251}, {1, 4, 2, 0, -251}}}, + // ... specs? + {"MacBook5,2", + "SMCMotionSensor", + 5, + 40, + {{1, 0, 2, 0, -251}, {1, 2, 2, 0, 251}, {1, 4, 2, 0, -251}}}, + // ... specs? + {"MacBookPro5,1", + "SMCMotionSensor", + 5, + 40, + {{1, 0, 2, 0, -251}, {1, 2, 2, 0, -251}, {1, 4, 2, 0, 251}}}, + // ... specs? + {"MacBookPro5,2", + "SMCMotionSensor", + 5, + 40, + {{1, 0, 2, 0, -251}, {1, 2, 2, 0, -251}, {1, 4, 2, 0, 251}}}, + // This is speculative, based on a single user's report. Looks like the X and Y axes + // are swapped. This is true for no other known Appple laptop. + {"MacBookPro5,3", + "SMCMotionSensor", + 5, + 40, + {{1, 2, 2, 0, -251}, {1, 0, 2, 0, -251}, {1, 4, 2, 0, -251}}}, + // ... specs? + {"MacBookPro5,4", + "SMCMotionSensor", + 5, + 40, + {{1, 0, 2, 0, -251}, {1, 2, 2, 0, -251}, {1, 4, 2, 0, 251}}}, + // ****** Model-independent methods ****** + // Seen once with PowerBook6,8 under system 10.3.9; I suspect + // other G4-based 10.3.* systems might use this + {"", "IOI2CMotionSensor", 24, 60, {{1, 0, 1, 0, 51.5}, {1, 1, 1, 0, 51.5}, {1, 2, 1, 0, 51.5}}}, + // PowerBook5,6 , PowerBook5,7 , PowerBook6,7 , PowerBook6,8 + // under OS X 10.4.* + {"", "IOI2CMotionSensor", 21, 60, {{1, 0, 1, 0, 51.5}, {1, 1, 1, 0, 51.5}, {1, 2, 1, 0, 51.5}}}, + // PowerBook5,8 , PowerBook5,9 under OS X 10.4.* + {"", + "PMUMotionSensor", + 21, + 60, + {// Each has two out of three gains negative, but it's different + // for the different models. So, this will be right in two out + // of three axis for either model. + {1, 0, 1, 0, -51.5}, + {1, 1, 1, -6, -51.5}, + {1, 2, 1, 0, -51.5}}}, + // All MacBook, MacBookPro models. Hardware (at least on early MacBookPro 15") + // is Kionix KXM52-1050 three-axis accelerometer chip. Data is at + // http://kionix.com/Product-Index/product-index.htm. Specific MB and MBP models + // that use this are: + // MacBook1,1 + // MacBook2,1 + // MacBook3,1 + // MacBook4,1 + // MacBook5,1 + // MacBook6,1 + // MacBookAir1,1 + // MacBookPro1,1 + // MacBookPro1,2 + // MacBookPro4,1 + // MacBookPro5,5 + {"", "SMCMotionSensor", 5, 40, {{1, 0, 2, 0, 251}, {1, 2, 2, 0, 251}, {1, 4, 2, 0, 251}}}}; + +#define SENSOR_COUNT (sizeof(sensors) / sizeof(sensorSpec)) + +#pragma mark Internal prototypes + +static int getData(sms_acceleration* accel, int calibrated, id logObject, SEL logSelector); +static float getAxis(int which, int calibrated); +static int signExtend(int value, int size); +static NSString* getModelName(void); +static NSString* getOSVersion(void); +static BOOL loadCalibration(void); +static void storeCalibration(void); +static void defaultCalibration(void); +static void deleteCalibration(void); +static int prefIntRead(NSString* prefName, BOOL* success); +static void prefIntWrite(NSString* prefName, int prefValue); +static float prefFloatRead(NSString* prefName, BOOL* success); +static void prefFloatWrite(NSString* prefName, float prefValue); +static void prefDelete(NSString* prefName); +static void prefSynchronize(void); +// static long getMicroseconds(void); +float fakeData(NSTimeInterval time); + +#pragma mark Static variables + +static int debugging = NO; // True if debugging (synthetic data) +static io_connect_t connection; // Connection for reading accel values +static int running = NO; // True if we successfully started +static unsigned int sensorNum = 0; // The current index into sensors[] +static const char* serviceName; // The name of the current service +static char *iRecord, *oRecord; // Pointers to read/write records for sensor +static int recordSize; // Size of read/write records +static unsigned int function; // Which kernel function should be used +static float zeros[3]; // X, Y and Z zero calibration values +static float onegs[3]; // X, Y and Z one-g calibration values + +#pragma mark Defines + +// Pattern for building axis letter from axis number +#define INT_TO_AXIS(a) (a == 0 ? @"X" : a == 1 ? @"Y" : @"Z") +// Name of configuration for given axis' zero (axis specified by integer) +#define ZERO_NAME(a) [NSString stringWithFormat:@"%@-Axis-Zero", INT_TO_AXIS(a)] +// Name of configuration for given axis' oneg (axis specified by integer) +#define ONEG_NAME(a) [NSString stringWithFormat:@"%@-Axis-One-g", INT_TO_AXIS(a)] +// Name of "Is calibrated" preference +#define CALIBRATED_NAME (@"Calibrated") +// Application domain for SeisMac library +#define APP_ID ((CFStringRef) @"com.suitable.SeisMacLib") + +// These #defines make the accelStartup code a LOT easier to read. +#undef LOG +#define LOG(message) \ + if (logObject) { \ + [logObject performSelector:logSelector withObject:message]; \ + } +#define LOG_ARG(format, var1) \ + if (logObject) { \ + [logObject performSelector:logSelector withObject:[NSString stringWithFormat:format, var1]]; \ + } +#define LOG_2ARG(format, var1, var2) \ + if (logObject) { \ + [logObject performSelector:logSelector \ + withObject:[NSString stringWithFormat:format, var1, var2]]; \ + } +#define LOG_3ARG(format, var1, var2, var3) \ + if (logObject) { \ + [logObject performSelector:logSelector \ + withObject:[NSString stringWithFormat:format, var1, var2, var3]]; \ + } + +#pragma mark Function definitions + +// This starts up the accelerometer code, trying each possible sensor +// specification. Note that for logging purposes it +// takes an object and a selector; the object's selector is then invoked +// with a single NSString as argument giving progress messages. Example +// logging method: +// - (void)logMessage: (NSString *)theString +// which would be used in accelStartup's invocation thusly: +// result = accelStartup(self, @selector(logMessage:)); +// If the object is nil, then no logging is done. Sets calibation from built-in +// value table. Returns ACCEL_SUCCESS for success, and other (negative) +// values for various failures (returns value indicating result of +// most successful trial). +int smsStartup(id logObject, SEL logSelector) { + io_iterator_t iterator; + io_object_t device; + kern_return_t result; + sms_acceleration accel; + int failure_result = SMS_FAIL_MODEL; + + running = NO; + debugging = NO; + + NSString* modelName = getModelName(); + + LOG_ARG(@"Machine model: %@\n", modelName); + LOG_ARG(@"OS X version: %@\n", getOSVersion()); + LOG_ARG(@"Accelerometer library version: %s\n", SMSLIB_VERSION); + + for (sensorNum = 0; sensorNum < SENSOR_COUNT; sensorNum++) { + // Set up all specs for this type of sensor + serviceName = sensors[sensorNum].name; + recordSize = sensors[sensorNum].recordSize; + function = sensors[sensorNum].function; + + LOG_3ARG(@"Trying service \"%s\" with selector %d and %d byte record:\n", serviceName, function, + recordSize); + + NSString* targetName = [NSString stringWithCString:sensors[sensorNum].model + encoding:NSMacOSRomanStringEncoding]; + LOG_ARG(@" Comparing model name to target \"%@\": ", targetName); + if ([targetName length] == 0 || [modelName hasPrefix:targetName]) { + LOG(@"success.\n"); + } else { + LOG(@"failure.\n"); + // Don't need to increment failure_result. + continue; + } + + LOG(@" Fetching dictionary for service: "); + CFMutableDictionaryRef dict = IOServiceMatching(serviceName); + + if (dict) { + LOG(@"success.\n"); + } else { + LOG(@"failure.\n"); + if (failure_result < SMS_FAIL_DICTIONARY) { + failure_result = SMS_FAIL_DICTIONARY; + } + continue; + } + + LOG(@" Getting list of matching services: "); + result = IOServiceGetMatchingServices(kIOMasterPortDefault, dict, &iterator); + + if (result == KERN_SUCCESS) { + LOG(@"success.\n"); + } else { + LOG_ARG(@"failure, with return value 0x%x.\n", result); + if (failure_result < SMS_FAIL_LIST_SERVICES) { + failure_result = SMS_FAIL_LIST_SERVICES; + } + continue; + } + + LOG(@" Getting first device in list: "); + device = IOIteratorNext(iterator); + + if (device == 0) { + LOG(@"failure.\n"); + if (failure_result < SMS_FAIL_NO_SERVICES) { + failure_result = SMS_FAIL_NO_SERVICES; + } + continue; + } else { + LOG(@"success.\n"); + LOG(@" Opening device: "); + } + + result = IOServiceOpen(device, mach_task_self(), 0, &connection); + + if (result != KERN_SUCCESS) { + LOG_ARG(@"failure, with return value 0x%x.\n", result); + IOObjectRelease(device); + if (failure_result < SMS_FAIL_OPENING) { + failure_result = SMS_FAIL_OPENING; + } + continue; + } else if (connection == 0) { + LOG_ARG(@"'success', but didn't get a connection (return value was: 0x%x).\n", result); + IOObjectRelease(device); + if (failure_result < SMS_FAIL_CONNECTION) { + failure_result = SMS_FAIL_CONNECTION; + } + continue; + } else { + IOObjectRelease(device); + LOG(@"success.\n"); + } + LOG(@" Testing device.\n"); + + defaultCalibration(); + + iRecord = (char*)malloc(recordSize); + oRecord = (char*)malloc(recordSize); + + running = YES; + result = getData(&accel, true, logObject, logSelector); + running = NO; + + if (result) { + LOG_ARG(@" Failure testing device, with result 0x%x.\n", result); + free(iRecord); + iRecord = 0; + free(oRecord); + oRecord = 0; + if (failure_result < SMS_FAIL_ACCESS) { + failure_result = SMS_FAIL_ACCESS; + } + continue; + } else { + LOG(@" Success testing device!\n"); + running = YES; + return SMS_SUCCESS; + } + } + return failure_result; +} + +// This starts up the library in debug mode, ignoring the actual hardware. +// Returned data is in the form of 1Hz sine waves, with the X, Y and Z +// axes 120 degrees out of phase; "calibrated" data has range +/- (1.0/5); +// "uncalibrated" data has range +/- (256/5). X and Y axes centered on 0.0, +// Z axes centered on 1 (calibrated) or 256 (uncalibrated). +// Don't use smsGetBufferLength or smsGetBufferData. Always returns SMS_SUCCESS. +int smsDebugStartup(id logObject, SEL logSelector) { + LOG(@"Starting up in debug mode\n"); + debugging = YES; + return SMS_SUCCESS; +} + +// Returns the current calibration values. +void smsGetCalibration(sms_calibration* calibrationRecord) { + int x; + + for (x = 0; x < 3; x++) { + calibrationRecord->zeros[x] = (debugging ? 0 : zeros[x]); + calibrationRecord->onegs[x] = (debugging ? 256 : onegs[x]); + } +} + +// Sets the calibration, but does NOT store it as a preference. If the argument +// is nil then the current calibration is set from the built-in value table. +void smsSetCalibration(sms_calibration* calibrationRecord) { + int x; + + if (!debugging) { + if (calibrationRecord) { + for (x = 0; x < 3; x++) { + zeros[x] = calibrationRecord->zeros[x]; + onegs[x] = calibrationRecord->onegs[x]; + } + } else { + defaultCalibration(); + } + } +} + +// Stores the current calibration values as a stored preference. +void smsStoreCalibration(void) { + if (!debugging) storeCalibration(); +} + +// Loads the stored preference values into the current calibration. +// Returns YES if successful. +BOOL smsLoadCalibration(void) { + if (debugging) { + return YES; + } else if (loadCalibration()) { + return YES; + } else { + defaultCalibration(); + return NO; + } +} + +// Deletes any stored calibration, and then takes the current calibration values +// from the built-in value table. +void smsDeleteCalibration(void) { + if (!debugging) { + deleteCalibration(); + defaultCalibration(); + } +} + +// Fills in the accel record with calibrated acceleration data. Takes +// 1-2ms to return a value. Returns 0 if success, error number if failure. +int smsGetData(sms_acceleration* accel) { + NSTimeInterval time; + if (debugging) { + usleep(1500); // Usually takes 1-2 milliseconds + time = [NSDate timeIntervalSinceReferenceDate]; + accel->x = fakeData(time) / 5; + accel->y = fakeData(time - 1) / 5; + accel->z = fakeData(time - 2) / 5 + 1.0; + return true; + } else { + return getData(accel, true, nil, nil); + } +} + +// Fills in the accel record with uncalibrated acceleration data. +// Returns 0 if success, error number if failure. +int smsGetUncalibratedData(sms_acceleration* accel) { + NSTimeInterval time; + if (debugging) { + usleep(1500); // Usually takes 1-2 milliseconds + time = [NSDate timeIntervalSinceReferenceDate]; + accel->x = fakeData(time) * 256 / 5; + accel->y = fakeData(time - 1) * 256 / 5; + accel->z = fakeData(time - 2) * 256 / 5 + 256; + return true; + } else { + return getData(accel, false, nil, nil); + } +} + +// Returns the length of a raw block of data for the current type of sensor. +int smsGetBufferLength(void) { + if (debugging) { + return 0; + } else if (running) { + return sensors[sensorNum].recordSize; + } else { + return 0; + } +} + +// Takes a pointer to accelGetRawLength() bytes; sets those bytes +// to return value from sensor. Make darn sure the buffer length is right! +void smsGetBufferData(char* buffer) { + IOItemCount iSize = recordSize; + IOByteCount oSize = recordSize; + kern_return_t result; + + if (debugging || running == NO) { + return; + } + + memset(iRecord, 1, iSize); + memset(buffer, 0, oSize); +#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 + const size_t InStructSize = recordSize; + size_t OutStructSize = recordSize; + result = + IOConnectCallStructMethod(connection, + function, // magic kernel function number + (const void*)iRecord, InStructSize, (void*)buffer, &OutStructSize); +#else // __MAC_OS_X_VERSION_MIN_REQUIRED 1050 + result = IOConnectMethodStructureIStructureO(connection, + function, // magic kernel function number + iSize, &oSize, iRecord, buffer); +#endif // __MAC_OS_X_VERSION_MIN_REQUIRED 1050 + + if (result != KERN_SUCCESS) { + running = NO; + } +} + +// This returns an NSString describing the current calibration in +// human-readable form. Also include a description of the machine. +NSString* smsGetCalibrationDescription(void) { + BOOL success; + NSMutableString* s = [[NSMutableString alloc] init]; + + if (debugging) { + [s release]; + return @"Debugging!"; + } + + [s appendString:@"---- SeisMac Calibration Record ----\n \n"]; + [s appendFormat:@"Machine model: %@\n", getModelName()]; + [s appendFormat:@"OS X build: %@\n", getOSVersion()]; + [s appendFormat:@"SeisMacLib version %s, record %d\n \n", SMSLIB_VERSION, sensorNum]; + [s appendFormat:@"Using service \"%s\", function index %d, size %d\n \n", serviceName, function, + recordSize]; + if (prefIntRead(CALIBRATED_NAME, &success) && success) { + [s appendString:@"Calibration values (from calibration):\n"]; + } else { + [s appendString:@"Calibration values (from defaults):\n"]; + } + [s appendFormat:@" X-Axis-Zero = %.2f\n", zeros[0]]; + [s appendFormat:@" X-Axis-One-g = %.2f\n", onegs[0]]; + [s appendFormat:@" Y-Axis-Zero = %.2f\n", zeros[1]]; + [s appendFormat:@" Y-Axis-One-g = %.2f\n", onegs[1]]; + [s appendFormat:@" Z-Axis-Zero = %.2f\n", zeros[2]]; + [s appendFormat:@" Z-Axis-One-g = %.2f\n \n", onegs[2]]; + [s appendString:@"---- End Record ----\n"]; + return s; +} + +// Shuts down the accelerometer. +void smsShutdown(void) { + if (!debugging) { + running = NO; + if (iRecord) free(iRecord); + if (oRecord) free(oRecord); + IOServiceClose(connection); + } +} + +#pragma mark Internal functions + +// Loads the current calibration from the stored preferences. +// Returns true iff successful. +BOOL loadCalibration(void) { + BOOL thisSuccess, allSuccess; + int x; + + prefSynchronize(); + + if (prefIntRead(CALIBRATED_NAME, &thisSuccess) && thisSuccess) { + // Calibrated. Set all values from saved values. + allSuccess = YES; + for (x = 0; x < 3; x++) { + zeros[x] = prefFloatRead(ZERO_NAME(x), &thisSuccess); + allSuccess &= thisSuccess; + onegs[x] = prefFloatRead(ONEG_NAME(x), &thisSuccess); + allSuccess &= thisSuccess; + } + return allSuccess; + } + + return NO; +} + +// Stores the current calibration into the stored preferences. +static void storeCalibration(void) { + int x; + prefIntWrite(CALIBRATED_NAME, 1); + for (x = 0; x < 3; x++) { + prefFloatWrite(ZERO_NAME(x), zeros[x]); + prefFloatWrite(ONEG_NAME(x), onegs[x]); + } + prefSynchronize(); +} + +// Sets the calibration to its default values. +void defaultCalibration(void) { + int x; + for (x = 0; x < 3; x++) { + zeros[x] = sensors[sensorNum].axes[x].zerog; + onegs[x] = sensors[sensorNum].axes[x].oneg; + } +} + +// Deletes the stored preferences. +static void deleteCalibration(void) { + int x; + + prefDelete(CALIBRATED_NAME); + for (x = 0; x < 3; x++) { + prefDelete(ZERO_NAME(x)); + prefDelete(ONEG_NAME(x)); + } + prefSynchronize(); +} + +// Read a named floating point value from the stored preferences. Sets +// the success boolean based on, you guessed it, whether it succeeds. +static float prefFloatRead(NSString* prefName, BOOL* success) { + float result = 0.0f; + + CFPropertyListRef ref = CFPreferencesCopyAppValue((CFStringRef)prefName, APP_ID); + // If there isn't such a preference, fail + if (ref == NULL) { + *success = NO; + return result; + } + CFTypeID typeID = CFGetTypeID(ref); + // Is it a number? + if (typeID == CFNumberGetTypeID()) { + // Is it a floating point number? + if (CFNumberIsFloatType((CFNumberRef)ref)) { + // Yup: grab it. + *success = CFNumberGetValue((__CFNumber*)ref, kCFNumberFloat32Type, &result); + } else { + // Nope: grab as an integer, and convert to a float. + long num; + if (CFNumberGetValue((CFNumberRef)ref, kCFNumberLongType, &num)) { + result = num; + *success = YES; + } else { + *success = NO; + } + } + // Or is it a string (e.g. set by the command line "defaults" command)? + } else if (typeID == CFStringGetTypeID()) { + result = (float)CFStringGetDoubleValue((CFStringRef)ref); + *success = YES; + } else { + // Can't convert to a number: fail. + *success = NO; + } + CFRelease(ref); + return result; +} + +// Writes a named floating point value to the stored preferences. +static void prefFloatWrite(NSString* prefName, float prefValue) { + CFNumberRef cfFloat = CFNumberCreate(kCFAllocatorDefault, kCFNumberFloatType, &prefValue); + CFPreferencesSetAppValue((CFStringRef)prefName, cfFloat, APP_ID); + CFRelease(cfFloat); +} + +// Reads a named integer value from the stored preferences. +static int prefIntRead(NSString* prefName, BOOL* success) { + Boolean internalSuccess; + CFIndex result = CFPreferencesGetAppIntegerValue((CFStringRef)prefName, APP_ID, &internalSuccess); + *success = internalSuccess; + + return result; +} + +// Writes a named integer value to the stored preferences. +static void prefIntWrite(NSString* prefName, int prefValue) { + CFPreferencesSetAppValue((CFStringRef)prefName, (CFNumberRef)[NSNumber numberWithInt:prefValue], + APP_ID); +} + +// Deletes the named preference values. +static void prefDelete(NSString* prefName) { + CFPreferencesSetAppValue((CFStringRef)prefName, NULL, APP_ID); +} + +// Synchronizes the local preferences with the stored preferences. +static void prefSynchronize(void) { CFPreferencesAppSynchronize(APP_ID); } + +// Internal version of accelGetData, with logging +int getData(sms_acceleration* accel, int calibrated, id logObject, SEL logSelector) { + IOItemCount iSize = recordSize; + IOByteCount oSize = recordSize; + kern_return_t result; + + if (running == NO) { + return -1; + } + + memset(iRecord, 1, iSize); + memset(oRecord, 0, oSize); + + LOG_2ARG(@" Querying device (%u, %d): ", sensors[sensorNum].function, + sensors[sensorNum].recordSize); + +#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 + const size_t InStructSize = recordSize; + size_t OutStructSize = recordSize; + result = + IOConnectCallStructMethod(connection, + function, // magic kernel function number + (const void*)iRecord, InStructSize, (void*)oRecord, &OutStructSize); +#else // __MAC_OS_X_VERSION_MIN_REQUIRED 1050 + result = IOConnectMethodStructureIStructureO(connection, + function, // magic kernel function number + iSize, &oSize, iRecord, oRecord); +#endif // __MAC_OS_X_VERSION_MIN_REQUIRED 1050 + + if (result != KERN_SUCCESS) { + LOG(@"failed.\n"); + running = NO; + return result; + } else { + LOG(@"succeeded.\n"); + + accel->x = getAxis(0, calibrated); + accel->y = getAxis(1, calibrated); + accel->z = getAxis(2, calibrated); + return 0; + } +} + +// Given the returned record, extracts the value of the given axis. If +// calibrated, then zero G is 0.0, and one G is 1.0. +float getAxis(int which, int calibrated) { + // Get various values (to make code cleaner) + int indx = sensors[sensorNum].axes[which].index; + int size = sensors[sensorNum].axes[which].size; + float zerog = zeros[which]; + float oneg = onegs[which]; + // Storage for value to be returned + int value = 0; + + // Although the values in the returned record should have the proper + // endianness, we still have to get it into the proper end of value. +#if (BYTE_ORDER == BIG_ENDIAN) + // On PowerPC processors + memcpy(((char*)&value) + (sizeof(int) - size), &oRecord[indx], size); +#endif +#if (BYTE_ORDER == LITTLE_ENDIAN) + // On Intel processors + memcpy(&value, &oRecord[indx], size); +#endif + + value = signExtend(value, size); + + if (calibrated) { + // Scale and shift for zero. + return ((float)(value - zerog)) / oneg; + } else { + return value; + } +} + +// Extends the sign, given the length of the value. +int signExtend(int value, int size) { + // Extend sign + switch (size) { + case 1: + if (value & 0x00000080) value |= 0xffffff00; + break; + case 2: + if (value & 0x00008000) value |= 0xffff0000; + break; + case 3: + if (value & 0x00800000) value |= 0xff000000; + break; + } + return value; +} + +// Returns the model name of the computer (e.g. "MacBookPro1,1") +NSString* getModelName(void) { + char model[32]; + size_t len = sizeof(model); + int name[2] = {CTL_HW, HW_MODEL}; + NSString* result; + + if (sysctl(name, 2, &model, &len, NULL, 0) == 0) { + result = [NSString stringWithFormat:@"%s", model]; + } else { + result = @""; + } + + return result; +} + +// Returns the current OS X version and build (e.g. "10.4.7 (build 8J2135a)") +NSString* getOSVersion(void) { + NSDictionary* dict = [NSDictionary + dictionaryWithContentsOfFile:@"/System/Library/CoreServices/SystemVersion.plist"]; + NSString* versionString = [dict objectForKey:@"ProductVersion"]; + NSString* buildString = [dict objectForKey:@"ProductBuildVersion"]; + NSString* wholeString = [NSString stringWithFormat:@"%@ (build %@)", versionString, buildString]; + return wholeString; +} + +// Returns time within the current second in microseconds. +// long getMicroseconds() { +// struct timeval t; +// gettimeofday(&t, 0); +// return t.tv_usec; +//} + +// Returns fake data given the time. Range is +/-1. +float fakeData(NSTimeInterval time) { + long secs = lround(floor(time)); + int secsMod3 = secs % 3; + double angle = time * 10 * M_PI * 2; + double mag = exp(-(time - (secs - secsMod3)) * 2); + return sin(angle) * mag; +} |