Commits

Joris Kluivers committed 10bea4c

Sensor abstraction, connect to device

  • Participants
  • Parent commits 8090dcf

Comments (0)

Files changed (5)

File Heartrates.xcodeproj/project.pbxproj

 	objects = {
 
 /* Begin PBXBuildFile section */
+		A970903717300C3100E84DD9 /* IOBluetooth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A970903617300C3100E84DD9 /* IOBluetooth.framework */; };
+		A970903917300F7B00E84DD9 /* PluginSettingsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = A970903817300F7B00E84DD9 /* PluginSettingsViewController.xib */; };
+		A970903C1730198000E84DD9 /* JKHRSensor.m in Sources */ = {isa = PBXBuildFile; fileRef = A970903B1730198000E84DD9 /* JKHRSensor.m */; };
 		A9F0A32116ECDCB6006C9A67 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A9F0A32016ECDCB6006C9A67 /* Cocoa.framework */; };
 		A9F0A32316ECDCB6006C9A67 /* Quartz.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A9F0A32216ECDCB6006C9A67 /* Quartz.framework */; };
 		A9F0A32F16ECDCB6006C9A67 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = A9F0A32D16ECDCB6006C9A67 /* InfoPlist.strings */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXFileReference section */
+		A970903617300C3100E84DD9 /* IOBluetooth.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOBluetooth.framework; path = System/Library/Frameworks/IOBluetooth.framework; sourceTree = SDKROOT; };
+		A970903817300F7B00E84DD9 /* PluginSettingsViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = PluginSettingsViewController.xib; sourceTree = "<group>"; };
+		A970903A1730198000E84DD9 /* JKHRSensor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JKHRSensor.h; sourceTree = "<group>"; };
+		A970903B1730198000E84DD9 /* JKHRSensor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JKHRSensor.m; sourceTree = "<group>"; };
 		A9F0A31D16ECDCB6006C9A67 /* Heartrates.plugin */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Heartrates.plugin; sourceTree = BUILT_PRODUCTS_DIR; };
 		A9F0A32016ECDCB6006C9A67 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
 		A9F0A32216ECDCB6006C9A67 /* Quartz.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Quartz.framework; path = System/Library/Frameworks/Quartz.framework; sourceTree = SDKROOT; };
 			files = (
 				A9F0A32116ECDCB6006C9A67 /* Cocoa.framework in Frameworks */,
 				A9F0A32316ECDCB6006C9A67 /* Quartz.framework in Frameworks */,
+				A970903717300C3100E84DD9 /* IOBluetooth.framework in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
 			children = (
 				A9F0A32016ECDCB6006C9A67 /* Cocoa.framework */,
 				A9F0A32216ECDCB6006C9A67 /* Quartz.framework */,
+				A970903617300C3100E84DD9 /* IOBluetooth.framework */,
 				A9F0A32416ECDCB6006C9A67 /* Other Frameworks */,
 			);
 			name = Frameworks;
 			children = (
 				A9F0A33016ECDCB6006C9A67 /* HeartratesPlugIn.h */,
 				A9F0A33116ECDCB6006C9A67 /* HeartratesPlugIn.m */,
+				A970903A1730198000E84DD9 /* JKHRSensor.h */,
+				A970903B1730198000E84DD9 /* JKHRSensor.m */,
+				A970903817300F7B00E84DD9 /* PluginSettingsViewController.xib */,
 				A9F0A32B16ECDCB6006C9A67 /* Supporting Files */,
 			);
 			path = Heartrates;
 			buildActionMask = 2147483647;
 			files = (
 				A9F0A32F16ECDCB6006C9A67 /* InfoPlist.strings in Resources */,
+				A970903917300F7B00E84DD9 /* PluginSettingsViewController.xib in Resources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
 			buildActionMask = 2147483647;
 			files = (
 				A9F0A33216ECDCB6006C9A67 /* HeartratesPlugIn.m in Sources */,
+				A970903C1730198000E84DD9 /* JKHRSensor.m in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};

File Heartrates/HeartratesPlugIn.m

 //  Copyright (c) 2013 Joris Kluivers. All rights reserved.
 //
 
-// It's highly recommended to use CGL macros instead of changing the current context for plug-ins that perform OpenGL rendering
-#import <OpenGL/CGLMacro.h>
+#import <IOBluetooth/IOBluetooth.h>
 
 #import "HeartratesPlugIn.h"
+#import "JKHRSensor.h"
 
 #define	kQCPlugIn_Name				@"Heartrates"
 #define	kQCPlugIn_Description		@"Connects to a hartrate monitor and gives access to person heartrate"
 #define kQCPlugIn_Copyright         @"Joris Kluivers"
 
-@implementation HeartratesPlugIn
+@interface HeartratesPlugIn () <CBCentralManagerDelegate>
+@property(nonatomic, strong) CBCentralManager *manager;
+@property(nonatomic, strong) CBPeripheral *peripheral;
+@property(nonatomic, strong) JKHRSensor *sensor;
+@property(nonatomic, readonly) dispatch_queue_t queue;
+@property(nonatomic, assign) NSUInteger heartrate;
+@end
+
+@implementation HeartratesPlugIn {
+    BOOL _shouldKeepRunning;
+}
 
 @dynamic outputHeartrate;
 
++ (BOOL)isSafe
+{
+    return YES;
+}
+
 + (NSDictionary *)attributes
 {
 	return @{
 + (QCPlugInTimeMode)timeMode
 {
 	// Return the time dependency mode of the plug-in: kQCPlugInTimeModeNone, kQCPlugInTimeModeIdle or kQCPlugInTimeModeTimeBase.
-	return kQCPlugInTimeModeNone;
+	return kQCPlugInTimeModeIdle;
 }
 
 - (id)init
 {
 	self = [super init];
 	if (self) {
-        
+        _queue = dispatch_queue_create("ble-queue", 0);
 	}
 	
 	return self;
 }
 
+- (QCPlugInViewController *) createViewController
+{
+    return [[QCPlugInViewController alloc] initWithPlugIn:self viewNibName:@"PluginSettingsViewController"];
+}
 
-@end
+- (void) findAndConnectDevice
+{
+    NSLog(@"Find and connect");
+    [self.manager scanForPeripheralsWithServices:@[
+        [CBUUID UUIDWithString:@"180D"]
+    ] options:nil];
+}
 
-@implementation HeartratesPlugIn (Execution)
+- (void) stopScan
+{
+    if (self.sensor) {
+        [self.manager cancelPeripheralConnection:self.sensor.peripheral];
+        
+        [self.sensor removeObserver:self forKeyPath:@"heartRate"];
+        self.sensor = nil;
+    }
+	[self.manager stopScan];
+}
 
-- (BOOL)startExecution:(id <QCPlugInContext>)context
+#pragma mark - Central Manager
+
+- (void) centralManagerDidUpdateState:(CBCentralManager *)central
 {
-	// Called by Quartz Composer when rendering of the composition starts: perform any required setup for the plug-in.
-	// Return NO in case of fatal failure (this will prevent rendering of the composition to start).
-	
-	return YES;
+    NSLog(@"Update state");
+    if (central.state == CBCentralManagerStatePoweredOn) {
+        [self findAndConnectDevice];
+    }
+}
+
+- (void) centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
+{
+    CBUUID *serviceUUID = [CBUUID UUIDWithString:@"180D"];
+    
+    NSArray *services = advertisementData[CBAdvertisementDataServiceUUIDsKey];
+    if (![services containsObject:serviceUUID]) {
+        return;
+    }
+    
+    if (self.sensor) {
+        // already connected
+        return;
+    }
+    
+    
+    NSLog(@"Connect peripheral: %@", peripheral);
+    // retain peripheral, or will fail without notice
+    self.peripheral = peripheral;
+    [self.manager connectPeripheral:peripheral options:nil];
 }
 
-- (void)enableExecution:(id <QCPlugInContext>)context
+- (void) centralManager:(CBCentralManager *)central didRetrievePeripherals:(NSArray *)peripherals
 {
-	// Called by Quartz Composer when the plug-in instance starts being used by Quartz Composer.
+    NSLog(@"Retrieved: %@", peripherals);
 }
 
-- (BOOL)execute:(id <QCPlugInContext>)context atTime:(NSTimeInterval)time withArguments:(NSDictionary *)arguments
+- (void) centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
 {
-	NSLog(@"%s", __func__);
-    NSLog(@"Arguments: %@", arguments);
+    NSLog(@"Did connect sensor");
     
-    self.outputHeartrate = 98;
+    self.sensor = [[JKHRSensor alloc] initWithPeripheral:peripheral];
+    [self.sensor addObserver:self forKeyPath:@"heartRate" options:nil context:nil];
+}
+
+- (void) centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
+{
+    NSLog(@"Disconnected");
+    [self.sensor removeObserver:self forKeyPath:@"heartRate"];
+    self.sensor = nil;
+}
+
+- (void) centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
+{
+    NSLog(@"Failed to connect to sensor: %@", error);
+}
+
+- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
+{
+    if (self.sensor == object && [keyPath isEqualToString:@"heartRate"]) {
+        NSUInteger heartrate = self.sensor.heartRate;
+        
+        self.heartrate = heartrate;
+        
+    } else {
+        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
+    }
+}
+
+- (BOOL)startExecution:(id <QCPlugInContext>)context
+{
+    _manager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
+//    dispatch_sync(dispatch_get_main_queue(), ^{
+//        
+//    });
+//    
+//    dispatch_queue_t current = dispatch_get_current_queue();
+//    dispatch_async(_queue, ^{
+//        NSRunLoop *theRL = [NSRunLoop currentRunLoop];
+//        while (_shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
+//        
+//        NSLog(@"Runloop done running");
+//    });
 	
 	return YES;
 }
 
-- (void)disableExecution:(id <QCPlugInContext>)context
+- (BOOL)execute:(id <QCPlugInContext>)context atTime:(NSTimeInterval)time withArguments:(NSDictionary *)arguments
 {
-	// Called by Quartz Composer when the plug-in instance stops being used by Quartz Composer.
+    
+    self.outputHeartrate = self.heartrate;
+    
+    return YES;
 }
 
+
 - (void)stopExecution:(id <QCPlugInContext>)context
 {
-	// Called by Quartz Composer when rendering of the composition stops: perform any required cleanup for the plug-in.
+    NSLog(@"Stop execution");
+    _shouldKeepRunning = NO;
+	[self stopScan];
+    _manager = nil;
 }
 
 @end

File Heartrates/JKHRSensor.h

+//
+//  JKHRSensor.h
+//  Heartrates
+//
+//  Created by Joris Kluivers on 4/30/13.
+//  Copyright (c) 2013 Joris Kluivers. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@class CBPeripheral;
+
+@interface JKHRSensor : NSObject
+
+@property(nonatomic, readonly) NSUInteger heartRate;
+@property(nonatomic, strong) CBPeripheral *peripheral;
+
+- (id) initWithPeripheral:(CBPeripheral *)peripheral;
+
+@end

File Heartrates/JKHRSensor.m

+//
+//  JKHRSensor.m
+//  Heartrates
+//
+//  Created by Joris Kluivers on 4/30/13.
+//  Copyright (c) 2013 Joris Kluivers. All rights reserved.
+//
+
+#import <IOBluetooth/IOBluetooth.h>
+
+#import "JKHRSensor.h"
+
+@interface JKHRSensor () <CBPeripheralDelegate>
+
+@end
+
+@implementation JKHRSensor
+
+- (id) initWithPeripheral:(CBPeripheral *)peripheral
+{
+	self = [super init];
+	
+	if (self) {
+		NSLog(@"New sensor");
+		self.peripheral = peripheral;
+		self.peripheral.delegate = self;
+		[self.peripheral discoverServices:nil];
+	}
+	
+	return self;
+}
+
+#pragma mark - Peripheral
+
+- (void) updateWithHRMData:(NSData *)data  {
+    const uint8_t *reportData = [data bytes];
+    uint16_t bpm = 0;
+    
+    if ((reportData[0] & 0x01) == 0) {
+        /* uint8 bpm */
+        bpm = reportData[1];
+    } else {
+        /* uint16 bpm */
+        bpm = CFSwapInt16LittleToHost(*(uint16_t *)(&reportData[1]));
+    }
+	
+    NSLog(@"new heartrate: %d", bpm);
+	[self willChangeValueForKey:@"heartRate"];
+	_heartRate = bpm;
+	[self didChangeValueForKey:@"heartRate"];
+}
+
+- (void) peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
+	for (CBService *service in peripheral.services) {
+		NSLog(@"Service found with UUID: %@", service.UUID);
+		
+		/* Heart Rate Service */
+        if ([service.UUID isEqual:[CBUUID UUIDWithString:@"180D"]]) {
+            [peripheral discoverCharacteristics:nil forService:service];
+        }
+        
+        /* Device Information Service */
+        if ([service.UUID isEqual:[CBUUID UUIDWithString:@"180A"]]) {
+            [peripheral discoverCharacteristics:nil forService:service];
+        }
+        
+        /* GAP (Generic Access Profile) for Device Name */
+        if ([service.UUID isEqual:[CBUUID UUIDWithString:CBUUIDGenericAccessProfileString]]){
+            [peripheral discoverCharacteristics:nil forService:service];
+        }
+	}
+}
+
+- (void) peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
+    if ([service.UUID isEqual:[CBUUID UUIDWithString:@"180D"]]) {
+        for (CBCharacteristic *aChar in service.characteristics) {
+            /* Set notification on heart rate measurement */
+            if ([aChar.UUID isEqual:[CBUUID UUIDWithString:@"2A37"]]) {
+                [peripheral setNotifyValue:YES forCharacteristic:aChar];
+                NSLog(@"Found a Heart Rate Measurement Characteristic");
+            }
+			
+            /* Read body sensor location */
+            if ([aChar.UUID isEqual:[CBUUID UUIDWithString:@"2A38"]]) {
+                [peripheral readValueForCharacteristic:aChar];
+                NSLog(@"Found a Body Sensor Location Characteristic");
+            }
+            
+            /* Write heart rate control point */
+            if ([aChar.UUID isEqual:[CBUUID UUIDWithString:@"2A39"]]) {
+                uint8_t val = 1;
+                NSData* valData = [NSData dataWithBytes:(void*)&val length:sizeof(val)];
+                [peripheral writeValue:valData forCharacteristic:aChar type:CBCharacteristicWriteWithResponse];
+            }
+        }
+    }
+    
+    if ([service.UUID isEqual:[CBUUID UUIDWithString:CBUUIDGenericAccessProfileString]]) {
+        for (CBCharacteristic *aChar in service.characteristics) {
+            /* Read device name */
+            if ([aChar.UUID isEqual:[CBUUID UUIDWithString:CBUUIDDeviceNameString]]) {
+                [peripheral readValueForCharacteristic:aChar];
+                NSLog(@"Found a Device Name Characteristic");
+            }
+        }
+    }
+    
+    if ([service.UUID isEqual:[CBUUID UUIDWithString:@"180A"]]) {
+        for (CBCharacteristic *aChar in service.characteristics) {
+            /* Read manufacturer name */
+            if ([aChar.UUID isEqual:[CBUUID UUIDWithString:@"2A29"]]) {
+                [peripheral readValueForCharacteristic:aChar];
+                NSLog(@"Found a Device Manufacturer Name Characteristic");
+            }
+        }
+    }
+}
+
+- (void) peripheral:(CBPeripheral *)aPeripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
+	
+    /* Updated value for heart rate measurement received */
+    if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"2A37"]]) {
+        if ((characteristic.value)  || !error) {
+            /* Update UI with heart rate data */
+            [self updateWithHRMData:characteristic.value];
+        }
+    }
+	
+    /* Value for body sensor location received */
+    else if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"2A38"]]) {
+        NSData * updatedValue = characteristic.value;
+        uint8_t* dataPointer = (uint8_t*)[updatedValue bytes];
+        if(dataPointer) {
+            uint8_t location = dataPointer[0];
+            NSString*  locationString;
+            switch (location) {
+                case 0:
+                    locationString = @"Other";
+                    break;
+                case 1:
+                    locationString = @"Chest";
+                    break;
+                case 2:
+                    locationString = @"Wrist";
+                    break;
+                case 3:
+                    locationString = @"Finger";
+                    break;
+                case 4:
+                    locationString = @"Hand";
+                    break;
+                case 5:
+                    locationString = @"Ear Lobe";
+                    break;
+                case 6:
+                    locationString = @"Foot";
+                    break;
+                default:
+                    locationString = @"Reserved";
+                    break;
+            }
+            NSLog(@"Body Sensor Location = %@ (%d)", locationString, location);
+        }
+    } else if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:CBUUIDDeviceNameString]]) {
+        NSString * deviceName = [[NSString alloc] initWithData:characteristic.value encoding:NSUTF8StringEncoding];
+        NSLog(@"Device Name = %@", deviceName);
+    } else if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"2A29"]]) {
+		/* Manufacturer name received */
+        NSString *manufacturer = [[NSString alloc] initWithData:characteristic.value encoding:NSUTF8StringEncoding];
+        NSLog(@"Manufacturer Name = %@", manufacturer);
+    }
+}
+
+@end

File Heartrates/PluginSettingsViewController.xib

+<?xml version="1.0" encoding="UTF-8"?>
+<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="8.00">
+	<data>
+		<int key="IBDocument.SystemTarget">1080</int>
+		<string key="IBDocument.SystemVersion">12D78</string>
+		<string key="IBDocument.InterfaceBuilderVersion">3084</string>
+		<string key="IBDocument.AppKitVersion">1187.37</string>
+		<string key="IBDocument.HIToolboxVersion">626.00</string>
+		<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
+			<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string>
+			<string key="NS.object.0">3084</string>
+		</object>
+		<array key="IBDocument.IntegratedClassDependencies">
+			<string>IBNSLayoutConstraint</string>
+			<string>NSCustomObject</string>
+			<string>NSCustomView</string>
+			<string>NSScrollView</string>
+			<string>NSScroller</string>
+			<string>NSTableCellView</string>
+			<string>NSTableColumn</string>
+			<string>NSTableHeaderView</string>
+			<string>NSTableView</string>
+			<string>NSTextField</string>
+			<string>NSTextFieldCell</string>
+		</array>
+		<array key="IBDocument.PluginDependencies">
+			<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+		</array>
+		<object class="NSMutableDictionary" key="IBDocument.Metadata">
+			<string key="NS.key.0">PluginDependencyRecalculationVersion</string>
+			<integer value="1" key="NS.object.0"/>
+		</object>
+		<array class="NSMutableArray" key="IBDocument.RootObjects" id="1000">
+			<object class="NSCustomObject" id="1001">
+				<string key="NSClassName">QCPlugInViewController</string>
+			</object>
+			<object class="NSCustomObject" id="1003">
+				<string key="NSClassName">FirstResponder</string>
+			</object>
+			<object class="NSCustomObject" id="1004">
+				<string key="NSClassName">NSApplication</string>
+			</object>
+			<object class="NSCustomView" id="507049193">
+				<reference key="NSNextResponder"/>
+				<int key="NSvFlags">268</int>
+				<array class="NSMutableArray" key="NSSubviews">
+					<object class="NSScrollView" id="284012109">
+						<reference key="NSNextResponder" ref="507049193"/>
+						<int key="NSvFlags">268</int>
+						<array class="NSMutableArray" key="NSSubviews">
+							<object class="NSClipView" id="388988368">
+								<reference key="NSNextResponder" ref="284012109"/>
+								<int key="NSvFlags">2304</int>
+								<array class="NSMutableArray" key="NSSubviews">
+									<object class="NSTableView" id="98686">
+										<reference key="NSNextResponder" ref="388988368"/>
+										<int key="NSvFlags">256</int>
+										<array class="NSMutableArray" key="NSSubviews"/>
+										<string key="NSFrameSize">{244, 139}</string>
+										<reference key="NSSuperview" ref="388988368"/>
+										<reference key="NSWindow"/>
+										<reference key="NSNextKeyView" ref="584956175"/>
+										<string key="NSReuseIdentifierKey">_NS:13</string>
+										<bool key="NSEnabled">YES</bool>
+										<bool key="NSAllowsLogicalLayoutDirection">NO</bool>
+										<bool key="NSControlAllowsExpansionToolTips">YES</bool>
+										<object class="NSTableHeaderView" key="NSHeaderView" id="767991715">
+											<reference key="NSNextResponder" ref="245742333"/>
+											<int key="NSvFlags">256</int>
+											<string key="NSFrameSize">{244, 17}</string>
+											<reference key="NSSuperview" ref="245742333"/>
+											<reference key="NSWindow"/>
+											<reference key="NSNextKeyView" ref="388988368"/>
+											<string key="NSReuseIdentifierKey">_NS:16</string>
+											<reference key="NSTableView" ref="98686"/>
+										</object>
+										<object class="_NSCornerView" key="NSCornerView">
+											<nil key="NSNextResponder"/>
+											<int key="NSvFlags">-2147483392</int>
+											<string key="NSFrame">{{224, 0}, {16, 17}}</string>
+											<reference key="NSNextKeyView" ref="388988368"/>
+											<string key="NSReuseIdentifierKey">_NS:19</string>
+										</object>
+										<array class="NSMutableArray" key="NSTableColumns">
+											<object class="NSTableColumn" id="734755654">
+												<double key="NSWidth">241</double>
+												<double key="NSMinWidth">40</double>
+												<double key="NSMaxWidth">1000</double>
+												<object class="NSTableHeaderCell" key="NSHeaderCell">
+													<int key="NSCellFlags">75497536</int>
+													<int key="NSCellFlags2">2048</int>
+													<string key="NSContents">Heartrate sensors</string>
+													<object class="NSFont" key="NSSupport">
+														<string key="NSName">LucidaGrande</string>
+														<double key="NSSize">11</double>
+														<int key="NSfFlags">3100</int>
+													</object>
+													<object class="NSColor" key="NSBackgroundColor">
+														<int key="NSColorSpace">3</int>
+														<bytes key="NSWhite">MC4zMzMzMzI5ODU2AA</bytes>
+													</object>
+													<object class="NSColor" key="NSTextColor">
+														<int key="NSColorSpace">6</int>
+														<string key="NSCatalogName">System</string>
+														<string key="NSColorName">headerTextColor</string>
+														<object class="NSColor" key="NSColor" id="983792185">
+															<int key="NSColorSpace">3</int>
+															<bytes key="NSWhite">MAA</bytes>
+														</object>
+													</object>
+												</object>
+												<object class="NSTextFieldCell" key="NSDataCell" id="550285729">
+													<int key="NSCellFlags">337641536</int>
+													<int key="NSCellFlags2">2048</int>
+													<string key="NSContents">Text Cell</string>
+													<object class="NSFont" key="NSSupport" id="253540020">
+														<string key="NSName">LucidaGrande</string>
+														<double key="NSSize">13</double>
+														<int key="NSfFlags">1044</int>
+													</object>
+													<reference key="NSControlView" ref="98686"/>
+													<object class="NSColor" key="NSBackgroundColor" id="721429111">
+														<int key="NSColorSpace">6</int>
+														<string key="NSCatalogName">System</string>
+														<string key="NSColorName">controlBackgroundColor</string>
+														<object class="NSColor" key="NSColor" id="870056675">
+															<int key="NSColorSpace">3</int>
+															<bytes key="NSWhite">MC42NjY2NjY2NjY3AA</bytes>
+														</object>
+													</object>
+													<object class="NSColor" key="NSTextColor" id="797292300">
+														<int key="NSColorSpace">6</int>
+														<string key="NSCatalogName">System</string>
+														<string key="NSColorName">controlTextColor</string>
+														<reference key="NSColor" ref="983792185"/>
+													</object>
+												</object>
+												<int key="NSResizingMask">3</int>
+												<bool key="NSIsResizeable">YES</bool>
+												<bool key="NSIsEditable">YES</bool>
+												<reference key="NSTableView" ref="98686"/>
+											</object>
+										</array>
+										<double key="NSIntercellSpacingWidth">3</double>
+										<double key="NSIntercellSpacingHeight">2</double>
+										<object class="NSColor" key="NSBackgroundColor">
+											<int key="NSColorSpace">3</int>
+											<bytes key="NSWhite">MQA</bytes>
+										</object>
+										<object class="NSColor" key="NSGridColor">
+											<int key="NSColorSpace">6</int>
+											<string key="NSCatalogName">System</string>
+											<string key="NSColorName">gridColor</string>
+											<object class="NSColor" key="NSColor">
+												<int key="NSColorSpace">3</int>
+												<bytes key="NSWhite">MC41AA</bytes>
+											</object>
+										</object>
+										<double key="NSRowHeight">17</double>
+										<int key="NSTvFlags">373293056</int>
+										<reference key="NSDelegate"/>
+										<reference key="NSDataSource"/>
+										<int key="NSColumnAutoresizingStyle">4</int>
+										<int key="NSDraggingSourceMaskForLocal">15</int>
+										<int key="NSDraggingSourceMaskForNonLocal">0</int>
+										<bool key="NSAllowsTypeSelect">YES</bool>
+										<int key="NSTableViewDraggingDestinationStyle">0</int>
+										<int key="NSTableViewGroupRowStyle">1</int>
+									</object>
+								</array>
+								<string key="NSFrame">{{1, 17}, {244, 139}}</string>
+								<reference key="NSSuperview" ref="284012109"/>
+								<reference key="NSWindow"/>
+								<reference key="NSNextKeyView" ref="98686"/>
+								<string key="NSReuseIdentifierKey">_NS:11</string>
+								<reference key="NSDocView" ref="98686"/>
+								<reference key="NSBGColor" ref="721429111"/>
+								<int key="NScvFlags">4</int>
+							</object>
+							<object class="NSScroller" id="455443854">
+								<reference key="NSNextResponder" ref="284012109"/>
+								<int key="NSvFlags">-2147483392</int>
+								<string key="NSFrame">{{224, 17}, {15, 102}}</string>
+								<reference key="NSSuperview" ref="284012109"/>
+								<reference key="NSWindow"/>
+								<reference key="NSNextKeyView" ref="316900889"/>
+								<string key="NSReuseIdentifierKey">_NS:58</string>
+								<bool key="NSAllowsLogicalLayoutDirection">NO</bool>
+								<reference key="NSTarget" ref="284012109"/>
+								<string key="NSAction">_doScroller:</string>
+								<double key="NSCurValue">37</double>
+								<double key="NSPercent">0.1947367936372757</double>
+							</object>
+							<object class="NSScroller" id="316900889">
+								<reference key="NSNextResponder" ref="284012109"/>
+								<int key="NSvFlags">-2147483392</int>
+								<string key="NSFrame">{{1, 118}, {244, 16}}</string>
+								<reference key="NSSuperview" ref="284012109"/>
+								<reference key="NSWindow"/>
+								<reference key="NSNextKeyView"/>
+								<string key="NSReuseIdentifierKey">_NS:60</string>
+								<bool key="NSAllowsLogicalLayoutDirection">NO</bool>
+								<int key="NSsFlags">1</int>
+								<reference key="NSTarget" ref="284012109"/>
+								<string key="NSAction">_doScroller:</string>
+								<double key="NSPercent">0.99591836734693873</double>
+							</object>
+							<object class="NSClipView" id="245742333">
+								<reference key="NSNextResponder" ref="284012109"/>
+								<int key="NSvFlags">2304</int>
+								<array class="NSMutableArray" key="NSSubviews">
+									<reference ref="767991715"/>
+								</array>
+								<string key="NSFrame">{{1, 0}, {244, 17}}</string>
+								<reference key="NSSuperview" ref="284012109"/>
+								<reference key="NSWindow"/>
+								<reference key="NSNextKeyView" ref="767991715"/>
+								<string key="NSReuseIdentifierKey">_NS:15</string>
+								<reference key="NSDocView" ref="767991715"/>
+								<reference key="NSBGColor" ref="721429111"/>
+								<int key="NScvFlags">4</int>
+							</object>
+						</array>
+						<string key="NSFrame">{{20, 259}, {246, 157}}</string>
+						<reference key="NSSuperview" ref="507049193"/>
+						<reference key="NSWindow"/>
+						<reference key="NSNextKeyView" ref="245742333"/>
+						<string key="NSReuseIdentifierKey">_NS:9</string>
+						<int key="NSsFlags">133682</int>
+						<reference key="NSVScroller" ref="455443854"/>
+						<reference key="NSHScroller" ref="316900889"/>
+						<reference key="NSContentView" ref="388988368"/>
+						<reference key="NSHeaderClipView" ref="245742333"/>
+						<bytes key="NSScrollAmts">QSAAAEEgAABBmAAAQZgAAA</bytes>
+						<double key="NSMinMagnification">0.25</double>
+						<double key="NSMaxMagnification">4</double>
+						<double key="NSMagnification">1</double>
+					</object>
+				</array>
+				<string key="NSFrameSize">{286, 436}</string>
+				<reference key="NSSuperview"/>
+				<reference key="NSWindow"/>
+				<reference key="NSNextKeyView" ref="284012109"/>
+				<string key="NSReuseIdentifierKey">_NS:9</string>
+				<string key="NSClassName">NSView</string>
+			</object>
+		</array>
+		<object class="IBObjectContainer" key="IBDocument.Objects">
+			<array class="NSMutableArray" key="connectionRecords">
+				<object class="IBConnectionRecord">
+					<object class="IBOutletConnection" key="connection">
+						<string key="label">view</string>
+						<reference key="source" ref="1001"/>
+						<reference key="destination" ref="507049193"/>
+					</object>
+					<int key="connectionID">32</int>
+				</object>
+				<object class="IBConnectionRecord">
+					<object class="IBOutletConnection" key="connection">
+						<string key="label">textField</string>
+						<object class="NSTableCellView" key="source" id="584956175">
+							<nil key="NSNextResponder"/>
+							<int key="NSvFlags">274</int>
+							<array class="NSMutableArray" key="NSSubviews">
+								<object class="NSTextField" id="220722449">
+									<reference key="NSNextResponder" ref="584956175"/>
+									<int key="NSvFlags">266</int>
+									<string key="NSFrameSize">{241, 17}</string>
+									<reference key="NSSuperview" ref="584956175"/>
+									<reference key="NSNextKeyView" ref="455443854"/>
+									<string key="NSAntiCompressionPriority">{250, 750}</string>
+									<bool key="NSEnabled">YES</bool>
+									<object class="NSTextFieldCell" key="NSCell" id="1067657403">
+										<int key="NSCellFlags">67108928</int>
+										<int key="NSCellFlags2">272631808</int>
+										<string key="NSContents">Table View Cell</string>
+										<reference key="NSSupport" ref="253540020"/>
+										<reference key="NSControlView" ref="220722449"/>
+										<object class="NSColor" key="NSBackgroundColor">
+											<int key="NSColorSpace">6</int>
+											<string key="NSCatalogName">System</string>
+											<string key="NSColorName">controlColor</string>
+											<reference key="NSColor" ref="870056675"/>
+										</object>
+										<reference key="NSTextColor" ref="797292300"/>
+									</object>
+									<bool key="NSAllowsLogicalLayoutDirection">NO</bool>
+								</object>
+							</array>
+							<string key="NSFrame">{{1, 1}, {241, 17}}</string>
+							<reference key="NSNextKeyView" ref="220722449"/>
+						</object>
+						<reference key="destination" ref="220722449"/>
+					</object>
+					<int key="connectionID">19</int>
+				</object>
+			</array>
+			<object class="IBMutableOrderedSet" key="objectRecords">
+				<array key="orderedObjects">
+					<object class="IBObjectRecord">
+						<int key="objectID">0</int>
+						<array key="object" id="0"/>
+						<reference key="children" ref="1000"/>
+						<nil key="parent"/>
+					</object>
+					<object class="IBObjectRecord">
+						<int key="objectID">-2</int>
+						<reference key="object" ref="1001"/>
+						<reference key="parent" ref="0"/>
+						<string key="objectName">File's Owner</string>
+					</object>
+					<object class="IBObjectRecord">
+						<int key="objectID">-1</int>
+						<reference key="object" ref="1003"/>
+						<reference key="parent" ref="0"/>
+						<string key="objectName">First Responder</string>
+					</object>
+					<object class="IBObjectRecord">
+						<int key="objectID">-3</int>
+						<reference key="object" ref="1004"/>
+						<reference key="parent" ref="0"/>
+						<string key="objectName">Application</string>
+					</object>
+					<object class="IBObjectRecord">
+						<int key="objectID">1</int>
+						<reference key="object" ref="507049193"/>
+						<array class="NSMutableArray" key="children">
+							<object class="IBNSLayoutConstraint" id="848385289">
+								<reference key="firstItem" ref="507049193"/>
+								<int key="firstAttribute">4</int>
+								<int key="relation">0</int>
+								<reference key="secondItem" ref="284012109"/>
+								<int key="secondAttribute">4</int>
+								<float key="multiplier">1</float>
+								<object class="IBLayoutConstant" key="constant">
+									<double key="value">259</double>
+								</object>
+								<float key="priority">1000</float>
+								<reference key="containingView" ref="507049193"/>
+								<int key="scoringType">3</int>
+								<float key="scoringTypeFloat">9</float>
+								<int key="contentType">3</int>
+							</object>
+							<object class="IBNSLayoutConstraint" id="110858348">
+								<reference key="firstItem" ref="284012109"/>
+								<int key="firstAttribute">3</int>
+								<int key="relation">0</int>
+								<reference key="secondItem" ref="507049193"/>
+								<int key="secondAttribute">3</int>
+								<float key="multiplier">1</float>
+								<object class="IBNSLayoutSymbolicConstant" key="constant">
+									<double key="value">20</double>
+								</object>
+								<float key="priority">1000</float>
+								<reference key="containingView" ref="507049193"/>
+								<int key="scoringType">8</int>
+								<float key="scoringTypeFloat">29</float>
+								<int key="contentType">3</int>
+							</object>
+							<object class="IBNSLayoutConstraint" id="261915838">
+								<reference key="firstItem" ref="507049193"/>
+								<int key="firstAttribute">6</int>
+								<int key="relation">0</int>
+								<reference key="secondItem" ref="284012109"/>
+								<int key="secondAttribute">6</int>
+								<float key="multiplier">1</float>
+								<object class="IBNSLayoutSymbolicConstant" key="constant">
+									<double key="value">20</double>
+								</object>
+								<float key="priority">1000</float>
+								<reference key="containingView" ref="507049193"/>
+								<int key="scoringType">8</int>
+								<float key="scoringTypeFloat">29</float>
+								<int key="contentType">3</int>
+							</object>
+							<object class="IBNSLayoutConstraint" id="626427501">
+								<reference key="firstItem" ref="284012109"/>
+								<int key="firstAttribute">5</int>
+								<int key="relation">0</int>
+								<reference key="secondItem" ref="507049193"/>
+								<int key="secondAttribute">5</int>
+								<float key="multiplier">1</float>
+								<object class="IBNSLayoutSymbolicConstant" key="constant">
+									<double key="value">20</double>
+								</object>
+								<float key="priority">1000</float>
+								<reference key="containingView" ref="507049193"/>
+								<int key="scoringType">8</int>
+								<float key="scoringTypeFloat">29</float>
+								<int key="contentType">3</int>
+							</object>
+							<reference ref="284012109"/>
+						</array>
+						<reference key="parent" ref="0"/>
+					</object>
+					<object class="IBObjectRecord">
+						<int key="objectID">2</int>
+						<reference key="object" ref="284012109"/>
+						<array class="NSMutableArray" key="children">
+							<reference ref="98686"/>
+							<reference ref="316900889"/>
+							<reference ref="767991715"/>
+							<reference ref="455443854"/>
+						</array>
+						<reference key="parent" ref="507049193"/>
+					</object>
+					<object class="IBObjectRecord">
+						<int key="objectID">3</int>
+						<reference key="object" ref="98686"/>
+						<array class="NSMutableArray" key="children">
+							<reference ref="734755654"/>
+						</array>
+						<reference key="parent" ref="284012109"/>
+					</object>
+					<object class="IBObjectRecord">
+						<int key="objectID">4</int>
+						<reference key="object" ref="316900889"/>
+						<reference key="parent" ref="284012109"/>
+					</object>
+					<object class="IBObjectRecord">
+						<int key="objectID">5</int>
+						<reference key="object" ref="767991715"/>
+						<reference key="parent" ref="284012109"/>
+					</object>
+					<object class="IBObjectRecord">
+						<int key="objectID">6</int>
+						<reference key="object" ref="455443854"/>
+						<reference key="parent" ref="284012109"/>
+					</object>
+					<object class="IBObjectRecord">
+						<int key="objectID">7</int>
+						<reference key="object" ref="734755654"/>
+						<array class="NSMutableArray" key="children">
+							<reference ref="550285729"/>
+							<reference ref="584956175"/>
+						</array>
+						<reference key="parent" ref="98686"/>
+					</object>
+					<object class="IBObjectRecord">
+						<int key="objectID">10</int>
+						<reference key="object" ref="550285729"/>
+						<reference key="parent" ref="734755654"/>
+					</object>
+					<object class="IBObjectRecord">
+						<int key="objectID">12</int>
+						<reference key="object" ref="626427501"/>
+						<reference key="parent" ref="507049193"/>
+					</object>
+					<object class="IBObjectRecord">
+						<int key="objectID">15</int>
+						<reference key="object" ref="261915838"/>
+						<reference key="parent" ref="507049193"/>
+					</object>
+					<object class="IBObjectRecord">
+						<int key="objectID">16</int>
+						<reference key="object" ref="584956175"/>
+						<array class="NSMutableArray" key="children">
+							<reference ref="220722449"/>
+							<object class="IBNSLayoutConstraint" id="265317742">
+								<reference key="firstItem" ref="220722449"/>
+								<int key="firstAttribute">10</int>
+								<int key="relation">0</int>
+								<reference key="secondItem" ref="584956175"/>
+								<int key="secondAttribute">10</int>
+								<float key="multiplier">1</float>
+								<object class="IBLayoutConstant" key="constant">
+									<double key="value">0.0</double>
+								</object>
+								<float key="priority">1000</float>
+								<reference key="containingView" ref="584956175"/>
+								<int key="scoringType">5</int>
+								<float key="scoringTypeFloat">22</float>
+								<int key="contentType">2</int>
+							</object>
+							<object class="IBNSLayoutConstraint" id="598452804">
+								<reference key="firstItem" ref="220722449"/>
+								<int key="firstAttribute">5</int>
+								<int key="relation">0</int>
+								<reference key="secondItem" ref="584956175"/>
+								<int key="secondAttribute">5</int>
+								<float key="multiplier">1</float>
+								<object class="IBLayoutConstant" key="constant">
+									<double key="value">3</double>
+								</object>
+								<float key="priority">1000</float>
+								<reference key="containingView" ref="584956175"/>
+								<int key="scoringType">8</int>
+								<float key="scoringTypeFloat">29</float>
+								<int key="contentType">3</int>
+							</object>
+							<object class="IBNSLayoutConstraint" id="732967190">
+								<reference key="firstItem" ref="584956175"/>
+								<int key="firstAttribute">6</int>
+								<int key="relation">0</int>
+								<reference key="secondItem" ref="220722449"/>
+								<int key="secondAttribute">6</int>
+								<float key="multiplier">1</float>
+								<object class="IBLayoutConstant" key="constant">
+									<double key="value">3</double>
+								</object>
+								<float key="priority">1000</float>
+								<reference key="containingView" ref="584956175"/>
+								<int key="scoringType">8</int>
+								<float key="scoringTypeFloat">29</float>
+								<int key="contentType">3</int>
+							</object>
+						</array>
+						<reference key="parent" ref="734755654"/>
+					</object>
+					<object class="IBObjectRecord">
+						<int key="objectID">17</int>
+						<reference key="object" ref="220722449"/>
+						<array class="NSMutableArray" key="children">
+							<reference ref="1067657403"/>
+						</array>
+						<reference key="parent" ref="584956175"/>
+					</object>
+					<object class="IBObjectRecord">
+						<int key="objectID">18</int>
+						<reference key="object" ref="1067657403"/>
+						<reference key="parent" ref="220722449"/>
+					</object>
+					<object class="IBObjectRecord">
+						<int key="objectID">24</int>
+						<reference key="object" ref="732967190"/>
+						<reference key="parent" ref="584956175"/>
+					</object>
+					<object class="IBObjectRecord">
+						<int key="objectID">25</int>
+						<reference key="object" ref="598452804"/>
+						<reference key="parent" ref="584956175"/>
+					</object>
+					<object class="IBObjectRecord">
+						<int key="objectID">26</int>
+						<reference key="object" ref="265317742"/>
+						<reference key="parent" ref="584956175"/>
+					</object>
+					<object class="IBObjectRecord">
+						<int key="objectID">30</int>
+						<reference key="object" ref="110858348"/>
+						<reference key="parent" ref="507049193"/>
+					</object>
+					<object class="IBObjectRecord">
+						<int key="objectID">33</int>
+						<reference key="object" ref="848385289"/>
+						<reference key="parent" ref="507049193"/>
+					</object>
+				</array>
+			</object>
+			<dictionary class="NSMutableDictionary" key="flattenedProperties">
+				<string key="-1.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+				<string key="-2.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+				<string key="-3.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+				<array class="NSMutableArray" key="1.IBNSViewMetadataConstraints">
+					<reference ref="626427501"/>
+					<reference ref="261915838"/>
+					<reference ref="110858348"/>
+					<reference ref="848385289"/>
+				</array>
+				<string key="1.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+				<string key="10.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+				<string key="12.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+				<string key="15.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+				<array key="16.IBNSViewMetadataConstraints">
+					<reference ref="732967190"/>
+					<reference ref="598452804"/>
+					<reference ref="265317742"/>
+				</array>
+				<string key="16.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+				<boolean value="NO" key="17.IBNSViewMetadataTranslatesAutoresizingMaskIntoConstraints"/>
+				<string key="17.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+				<string key="18.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+				<boolean value="NO" key="2.IBNSViewMetadataTranslatesAutoresizingMaskIntoConstraints"/>
+				<string key="2.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+				<string key="24.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+				<string key="25.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+				<string key="26.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+				<string key="3.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+				<boolean value="YES" key="3.ibExternalAutomaticallyCalculatesRowSizeFromViewHeight"/>
+				<string key="30.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+				<string key="33.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+				<string key="4.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+				<string key="5.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+				<string key="6.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+				<string key="7.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+				<boolean value="YES" key="7.isInViewBasedMode"/>
+				<array key="7.prototypeCellViews">
+					<reference ref="584956175"/>
+				</array>
+			</dictionary>
+			<dictionary class="NSMutableDictionary" key="unlocalizedProperties"/>
+			<nil key="activeLocalization"/>
+			<dictionary class="NSMutableDictionary" key="localizations"/>
+			<nil key="sourceID"/>
+			<int key="maxID">33</int>
+		</object>
+		<object class="IBClassDescriber" key="IBDocument.Classes">
+			<array class="NSMutableArray" key="referencedPartialClassDescriptions">
+				<object class="IBPartialClassDescription">
+					<string key="className">NSLayoutConstraint</string>
+					<string key="superclassName">NSObject</string>
+					<object class="IBClassDescriptionSource" key="sourceIdentifier">
+						<string key="majorKey">IBProjectSource</string>
+						<string key="minorKey">./Classes/NSLayoutConstraint.h</string>
+					</object>
+				</object>
+				<object class="IBPartialClassDescription">
+					<string key="className">QCPlugInViewController</string>
+					<string key="superclassName">NSViewController</string>
+					<object class="IBClassDescriptionSource" key="sourceIdentifier">
+						<string key="majorKey">IBProjectSource</string>
+						<string key="minorKey">./Classes/QCPlugInViewController.h</string>
+					</object>
+				</object>
+			</array>
+		</object>
+		<int key="IBDocument.localizationMode">0</int>
+		<string key="IBDocument.TargetRuntimeIdentifier">IBCocoaFramework</string>
+		<bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
+		<int key="IBDocument.defaultPropertyAccessControl">3</int>
+		<bool key="IBDocument.UseAutolayout">YES</bool>
+	</data>
+</archive>