Repository: michaeldorner/BeeTee Branch: master Commit: 34149af004ad Files: 30 Total size: 103.6 KB Directory structure: gitextract_giyuhi4h/ ├── .gitignore ├── .travis.yml ├── BeeTee/ │ ├── BeeTee-Bridging-Header.h │ ├── BeeTee.swift │ ├── BeeTeeDevice.swift │ ├── BeeTeeModel.swift │ ├── BluetoothDevice.h │ ├── BluetoothDeviceHandler.h │ ├── BluetoothDeviceHandler.m │ ├── BluetoothManager.h │ ├── BluetoothManagerHandler.h │ ├── BluetoothManagerHandler.m │ ├── DeviceDetailViewController.swift │ └── DeviceListingViewController.swift ├── BeeTee Demo/ │ ├── AppDelegate.swift │ ├── Assets.xcassets/ │ │ ├── AppIcon.appiconset/ │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── DeviceListing.imageset/ │ │ │ └── Contents.json │ │ └── MissionControl.imageset/ │ │ └── Contents.json │ ├── Base.lproj/ │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ └── MissionControlViewController.swift ├── BeeTee.xcodeproj/ │ ├── project.pbxproj │ └── project.xcworkspace/ │ └── contents.xcworkspacedata ├── BeeTeeTests/ │ ├── BeeTeeTests.swift │ └── Info.plist ├── LICENSE ├── README.md └── landingPage/ └── BeeTeeLayer.graphml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Xcode .DS_Store */build/* *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcuserdata profile *.moved-aside DerivedData .idea/ *.hmap *.xccheckout #CocoaPods Pods ================================================ FILE: .travis.yml ================================================ branches: only: - master language: objective-c osx_image: xcode10 script: - xcodebuild -project BeeTee.xcodeproj -target BeeTee -sdk iphonesimulator after_success: - bash <(curl -s https://codecov.io/bash) ================================================ FILE: BeeTee/BeeTee-Bridging-Header.h ================================================ /* This file is part of BeeTee Project. It is subject to the license terms in the LICENSE file found in the top-level directory of this distribution and at https://github.com/michaeldorner/BeeTee/blob/master/LICENSE. No part of BeeTee Project, including this file, may be copied, modified, propagated, or distributed except according to the terms contained in the LICENSE file. */ #import "BluetoothManagerHandler.h" #import "BluetoothDeviceHandler.h" ================================================ FILE: BeeTee/BeeTee.swift ================================================ /* This file is part of BeeTee Project. It is subject to the license terms in the LICENSE file found in the top-level directory of this distribution and at https://github.com/michaeldorner/BeeTee/blob/master/LICENSE. No part of BeeTee Project, including this file, may be copied, modified, propagated, or distributed except according to the terms contained in the LICENSE file. */ import Foundation public enum BeeTeeNotification: String { case PowerChanged = "BluetoothPowerChangedNotification" case AvailabilityChanged = "BluetoothAvailabilityChangedNotification" case DeviceDiscovered = "BluetoothDeviceDiscoveredNotification" case DeviceRemoved = "BluetoothDeviceRemovedNotification" case ConnectabilityChanged = "BluetoothConnectabilityChangedNotification" case DeviceUpdated = "BluetoothDeviceUpdatedNotification" case DiscoveryStateChanged = "BluetoothDiscoveryStateChangedNotification" case DeviceConnectSuccess = "BluetoothDeviceConnectSuccessNotification" case ConnectionStatusChanged = "BluetoothConnectionStatusChangedNotification" case DeviceDisconnectSuccess = "BluetoothDeviceDisconnectSuccessNotification" public static let allNotifications: [BeeTeeNotification] = [.PowerChanged, .AvailabilityChanged, .DeviceDiscovered, .DeviceRemoved, .ConnectabilityChanged, .DeviceUpdated, .DiscoveryStateChanged, .DeviceConnectSuccess, .ConnectionStatusChanged, .DeviceDisconnectSuccess] } public protocol BeeTeeDelegate { func receivedBeeTeeNotification(notification: BeeTeeNotification) } public class BeeTee { public var delegate: BeeTeeDelegate? = nil public var availableDevices: [BeeTeeDevice] { get { return Array(_availableDevices) } } private let bluetoothManagerHandler = BluetoothManagerHandler.sharedInstance()! private var _availableDevices = Set() private var tokenCache = [BeeTeeNotification: NSObjectProtocol]() public init() { for beeTeeNotification in BeeTeeNotification.allNotifications { print("Registered \(beeTeeNotification)") let notification = NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: beeTeeNotification.rawValue), object: nil, queue: OperationQueue.main) { [unowned self] (notification) in let beeTeeNotification = BeeTeeNotification.init(rawValue: notification.name.rawValue)! switch beeTeeNotification { case .DeviceDiscovered: let beeTeeDevice = self.extractBeeTeeDevice(from: notification) self._availableDevices.insert(beeTeeDevice) case .DeviceRemoved: let beeTeeDevice = self.extractBeeTeeDevice(from: notification) self._availableDevices.remove(beeTeeDevice) default: break } if (self.delegate != nil) { self.delegate?.receivedBeeTeeNotification(notification: beeTeeNotification) } } self.tokenCache[beeTeeNotification] = notification } } deinit { for key in tokenCache.keys { NotificationCenter.default.removeObserver(tokenCache[key]!) } } public func enableBluetooth() { bluetoothManagerHandler.enable() } public func disableBluetooth() { bluetoothManagerHandler.disable() } public func bluetoothIsEnabled() -> Bool { return bluetoothManagerHandler.enabled() } public func startScanForDevices() { bluetoothManagerHandler.startScan() } public func stopScan() { bluetoothManagerHandler.stopScan() resetAvailableDevices() } public func isScanning() -> Bool { return bluetoothManagerHandler.isScanning() } public static func debugLowLevel() { print("This is a dirty C hack and only for demonstration and deep debugging, but not for production.") // credits to http://stackoverflow.com/a/3738387/1864294 CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), nil, { (_, observer, name, _, _) in let n = name?.rawValue as! String if n.hasPrefix("B") { // notice only notification they are associated with the BluetoothManager.framework print("Received notification: \(name)") } }, nil, nil, .deliverImmediately) } private func resetAvailableDevices() { _availableDevices.removeAll() } private func extractBeeTeeDevice(from notification: Notification) -> BeeTeeDevice { let bluetoothDevice = BluetoothDeviceHandler(notification: notification)! let beeTeeDevice = BeeTeeDevice(name: bluetoothDevice.name, address: bluetoothDevice.address, majorClass: bluetoothDevice.majorClass, minorClass: bluetoothDevice.minorClass, type: bluetoothDevice.type, supportsBatteryLevel: bluetoothDevice.supportsBatteryLevel, detectingDate: Date()) return beeTeeDevice } } ================================================ FILE: BeeTee/BeeTeeDevice.swift ================================================ /* This file is part of BeeTee Project. It is subject to the license terms in the LICENSE file found in the top-level directory of this distribution and at https://github.com/michaeldorner/BeeTee/blob/master/LICENSE. No part of BeeTee Project, including this file, may be copied, modified, propagated, or distributed except according to the terms contained in the LICENSE file. */ import Foundation public class BeeTeeDevice: Hashable, CustomStringConvertible { let name: String let address: String let majorClass: UInt let minorCass: UInt let type: Int let supportsBatteryLevel: Bool let detectingDate: Date convenience init(notification: Notification) { let bluetoothDevice = BluetoothDeviceHandler(notification: notification)! self.init(name: bluetoothDevice.name, address: bluetoothDevice.address, majorClass: bluetoothDevice.majorClass, minorClass: bluetoothDevice.minorClass, type: bluetoothDevice.type, supportsBatteryLevel: bluetoothDevice.supportsBatteryLevel, detectingDate: Date()) } init(name: String, address: String, majorClass: UInt, minorClass: UInt, type: Int, supportsBatteryLevel: Bool, detectingDate: Date) { self.name = name self.address = address self.majorClass = majorClass self.minorCass = minorClass self.type = type self.supportsBatteryLevel = supportsBatteryLevel self.detectingDate = detectingDate } public var description: String { return "\(name) (\(address)) @ \(detectingDate))" } public var hashValue: Int { return address.hashValue } public static func ==(lhs: BeeTeeDevice, rhs: BeeTeeDevice) -> Bool { return lhs.address == rhs.address } } ================================================ FILE: BeeTee/BeeTeeModel.swift ================================================ /* This file is part of BeeTee Project. It is subject to the license terms in the LICENSE file found in the top-level directory of this distribution and at https://github.com/michaeldorner/BeeTee/blob/master/LICENSE. No part of BeeTee Project, including this file, may be copied, modified, propagated, or distributed except according to the terms contained in the LICENSE file. */ import Foundation class BeeTeeModel: BeeTeeDelegate { static let sharedInstance = BeeTeeModel() private let beeTee = BeeTee() private var subscribers = [BeeTeeDelegate]() public var availableDevices: [BeeTeeDevice] { get { return beeTee.availableDevices } } private init() { beeTee.delegate = self } func receivedBeeTeeNotification(notification: BeeTeeNotification) { for subscriber in subscribers { subscriber.receivedBeeTeeNotification(notification: notification) } print("BeeTeeNotification: " + String(describing: notification)) } public func turnBluetoothOn() { beeTee.enableBluetooth() print("Bluetooth turned on") } public func turnBluetoothOff() { beeTee.disableBluetooth() print("Bluetooth turned off") } public func bluetoothIsOn() -> Bool { return beeTee.bluetoothIsEnabled() } public func startScanForDevices() { beeTee.startScanForDevices() print("Bluetooth started scanning for new devices") } public func stopScan() { beeTee.stopScan() print("Bluetooth stopped scanning") } public func isScanning() -> Bool { return beeTee.isScanning() } public func subscribe(subscriber: BeeTeeDelegate) { subscribers.append(subscriber) } public func unsubscribe(subscriber: BeeTeeDelegate) { //todo: add remove } } ================================================ FILE: BeeTee/BluetoothDevice.h ================================================ /* This file is part of BeeTee Project. It is subject to the license terms in the LICENSE file found in the top-level directory of this distribution and at https://github.com/michaeldorner/BeeTee/blob/master/LICENSE. No part of BeeTee Project, including this file, may be copied, modified, propagated, or distributed except according to the terms contained in the LICENSE file. */ /* Generated by RuntimeBrowser Image: /System/Library/PrivateFrameworks/BluetoothManager.framework/BluetoothManager */ @interface BluetoothDevice : NSObject { NSString * _address; struct BTDeviceImpl { } * _device; NSString * _name; } - (void)_clearName; - (BOOL)_isNameCached; - (void)acceptSSP:(int)arg1; - (id)address; - (int)batteryLevel; - (BOOL)cloudPaired; - (int)compare:(id)arg1; - (void)connect; - (void)connectWithServices:(unsigned int)arg1; - (BOOL)connected; - (unsigned int)connectedServices; - (unsigned int)connectedServicesCount; - (id)copyWithZone:(struct _NSZone { }*)arg1; - (void)dealloc; - (id)description; - (struct BTDeviceImpl*)device; - (void)disconnect; - (unsigned int)doubleTapAction; - (void)endVoiceCommand; - (id)getServiceSetting:(unsigned int)arg1 key:(id)arg2; - (BOOL)inEarDetectEnabled; - (id)initWithDevice:(struct BTDeviceImpl { }*)arg1 address:(id)arg2; - (BOOL)isAccessory; - (BOOL)isAppleAudioDevice; - (BOOL)isServiceSupported:(unsigned int)arg1; - (BOOL)magicPaired; - (BOOL)magicPairedDeviceNameUpdated; - (unsigned int)majorClass; - (unsigned int)micMode; - (unsigned int)minorClass; - (id)name; - (BOOL)paired; - (unsigned int)productId; - (id)scoUID; - (void)setDevice:(struct BTDeviceImpl { }*)arg1; - (BOOL)setDoubleTapAction:(unsigned int)arg1; - (BOOL)setInEarDetectEnabled:(BOOL)arg1; - (BOOL)setMicMode:(unsigned int)arg1; - (void)setPIN:(id)arg1; - (void)setServiceSetting:(unsigned int)arg1 key:(id)arg2 value:(id)arg3; - (void)setSyncGroup:(int)arg1 enabled:(BOOL)arg2; - (void)setSyncSettings:(struct { BOOL x1; BOOL x2; BOOL x3; BOOL x4; })arg1; - (BOOL)setUserName:(id)arg1; - (void)startVoiceCommand; - (BOOL)supportsBatteryLevel; - (id)syncGroups; - (struct { BOOL x1; BOOL x2; BOOL x3; BOOL x4; })syncSettings; - (int)type; - (void)unpair; - (unsigned int)vendorId; @end ================================================ FILE: BeeTee/BluetoothDeviceHandler.h ================================================ /* This file is part of BeeTee Project. It is subject to the license terms in the LICENSE file found in the top-level directory of this distribution and at https://github.com/michaeldorner/BeeTee/blob/master/LICENSE. No part of BeeTee Project, including this file, may be copied, modified, propagated, or distributed except according to the terms contained in the LICENSE file. */ #import @interface BluetoothDeviceHandler : NSObject @property (strong, nonatomic, readonly) NSString* name; @property (strong, nonatomic, readonly) NSString* address; @property (assign, nonatomic, readonly) NSUInteger majorClass; @property (assign, nonatomic, readonly) NSUInteger minorClass; @property (assign, nonatomic, readonly) NSInteger type; @property (assign, nonatomic, readonly) BOOL supportsBatteryLevel; - (instancetype)initWithNotification:(NSNotification*) notification; @end ================================================ FILE: BeeTee/BluetoothDeviceHandler.m ================================================ /* This file is part of BeeTee Project. It is subject to the license terms in the LICENSE file found in the top-level directory of this distribution and at https://github.com/michaeldorner/BeeTee/blob/master/LICENSE. No part of BeeTee Project, including this file, may be copied, modified, propagated, or distributed except according to the terms contained in the LICENSE file. */ #import "BluetoothDeviceHandler.h" #import "BluetoothDevice.h" @interface BluetoothDeviceHandler () @property (strong, nonatomic, readwrite) NSString* name; @property (strong, nonatomic, readwrite) NSString* address; @property (assign, nonatomic, readwrite) NSUInteger majorClass; @property (assign, nonatomic, readwrite) NSUInteger minorClass; @property (assign, nonatomic, readwrite) NSInteger type; @property (assign, nonatomic, readwrite) BOOL supportsBatteryLevel; @end @implementation BluetoothDeviceHandler - (instancetype)initWithNotification:(NSNotification*) notification { BluetoothDevice *bluetoothDevice = [notification object]; self = [super init]; if (self) { self.name = bluetoothDevice.name; self.address = bluetoothDevice.address; self.majorClass = bluetoothDevice.majorClass; self.minorClass = bluetoothDevice.minorClass; self.type = bluetoothDevice.type; self.supportsBatteryLevel = bluetoothDevice.supportsBatteryLevel; } return self; } @end ================================================ FILE: BeeTee/BluetoothManager.h ================================================ /* This file is part of BeeTee Project. It is subject to the license terms in the LICENSE file found in the top-level directory of this distribution and at https://github.com/michaeldorner/BeeTee/blob/master/LICENSE. No part of BeeTee Project, including this file, may be copied, modified, propagated, or distributed except according to the terms contained in the LICENSE file. */ /* Generated by RuntimeBrowser Image: /System/Library/PrivateFrameworks/BluetoothManager.framework/BluetoothManager */ @interface BluetoothManager : NSObject { struct BTAccessoryManagerImpl { } * _accessoryManager; BOOL _audioConnected; int _available; NSMutableDictionary * _btAddrDict; NSMutableDictionary * _btDeviceDict; struct BTDiscoveryAgentImpl { } * _discoveryAgent; struct BTLocalDeviceImpl { } * _localDevice; struct BTPairingAgentImpl { } * _pairingAgent; BOOL _scanningEnabled; BOOL _scanningInProgress; unsigned int _scanningServiceMask; struct BTSessionImpl { } * _session; } // Image: /System/Library/PrivateFrameworks/BluetoothManager.framework/BluetoothManager + (int)lastInitError; + (void)setSharedInstanceQueue:(id)arg1; + (id)sharedInstance; - (struct BTAccessoryManagerImpl*)_accessoryManager; - (void)_advertisingChanged; - (BOOL)_attach; - (void)_cleanup:(BOOL)arg1; - (void)_connectabilityChanged; - (void)_connectedStatusChanged; - (void)_discoveryStateChanged; - (void)_pairedStatusChanged; - (void)_postNotification:(id)arg1; - (void)_postNotificationWithArray:(id)arg1; - (void)_powerChanged; - (void)_removeDevice:(id)arg1; - (void)_restartScan; - (void)_scanForServices:(unsigned int)arg1 withMode:(int)arg2; - (void)_setScanState:(int)arg1; - (BOOL)_setup:(struct BTSessionImpl { }*)arg1; - (void)acceptSSP:(int)arg1 forDevice:(id)arg2; - (id)addDeviceIfNeeded:(struct BTDeviceImpl { }*)arg1; - (BOOL)audioConnected; - (BOOL)available; - (void)cancelPairing; - (void)connectDevice:(id)arg1; - (void)connectDevice:(id)arg1 withServices:(unsigned int)arg2; - (BOOL)connectable; - (BOOL)connected; - (id)connectedDevices; - (id)connectingDevices; - (void)dealloc; - (BOOL)devicePairingEnabled; - (BOOL)deviceScanningEnabled; - (BOOL)deviceScanningInProgress; - (void)disconnectDevice:(id)arg1; - (void)enableTestMode; - (BOOL)enabled; - (void)endVoiceCommand:(id)arg1; - (id)init; - (BOOL)isAnyoneAdvertising; - (BOOL)isAnyoneScanning; - (BOOL)isDiscoverable; - (BOOL)isServiceSupported:(unsigned int)arg1; - (id)localAddress; - (id)pairedDevices; - (void)postNotification:(id)arg1; - (void)postNotificationName:(id)arg1 object:(id)arg2; - (void)postNotificationName:(id)arg1 object:(id)arg2 error:(id)arg3; - (int)powerState; - (BOOL)powered; - (void)resetDeviceScanning; - (void)scanForConnectableDevices:(unsigned int)arg1; - (void)scanForServices:(unsigned int)arg1; - (void)setAudioConnected:(BOOL)arg1; - (void)setConnectable:(BOOL)arg1; - (void)setDevicePairingEnabled:(BOOL)arg1; - (void)setDeviceScanningEnabled:(BOOL)arg1; - (void)setDiscoverable:(BOOL)arg1; - (BOOL)setEnabled:(BOOL)arg1; - (void)setPincode:(id)arg1 forDevice:(id)arg2; - (BOOL)setPowered:(BOOL)arg1; - (void)showPowerPrompt; - (void)startVoiceCommand:(id)arg1; - (void)unpairDevice:(id)arg1; - (BOOL)wasDeviceDiscovered:(id)arg1; // Image: /System/Library/PrivateFrameworks/GameKitServices.framework/GameKitServices - (int)localDeviceSupportsService:(unsigned int)arg1; @end ================================================ FILE: BeeTee/BluetoothManagerHandler.h ================================================ /* This file is part of BeeTee Project. It is subject to the license terms in the LICENSE file found in the top-level directory of this distribution and at https://github.com/michaeldorner/BeeTee/blob/master/LICENSE. No part of BeeTee Project, including this file, may be copied, modified, propagated, or distributed except according to the terms contained in the LICENSE file. */ #import @interface BluetoothManagerHandler : NSObject + (BluetoothManagerHandler*) sharedInstance; - (bool) powered; - (void) setPower: (bool)powerStatus; - (void) startScan; - (void) stopScan; - (bool) isScanning; - (bool) enabled; - (void) disable; - (void) enable; @end ================================================ FILE: BeeTee/BluetoothManagerHandler.m ================================================ /* This file is part of BeeTee Project. It is subject to the license terms in the LICENSE file found in the top-level directory of this distribution and at https://github.com/michaeldorner/BeeTee/blob/master/LICENSE. No part of BeeTee Project, including this file, may be copied, modified, propagated, or distributed except according to the terms contained in the LICENSE file. */ #import "BluetoothManagerHandler.h" #import "BluetoothManager.h" static BluetoothManager *_bluetoothManager = nil; static BluetoothManagerHandler *_handler = nil; @implementation BluetoothManagerHandler + (BluetoothManagerHandler*) sharedInstance { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSBundle *b = [NSBundle bundleWithPath:@"/System/Library/PrivateFrameworks/BluetoothManager.framework"]; if (![b load]) { NSLog(@"Error"); // maybe throw an exception } else { _bluetoothManager = [NSClassFromString(@"BluetoothManager") valueForKey:@"sharedInstance"]; _handler = [[BluetoothManagerHandler alloc] init]; } }); return _handler; } - (bool) powered { return [_bluetoothManager powered]; } - (void) setPower: (bool)powerStatus { [_bluetoothManager setPowered:powerStatus]; } - (void) startScan { [_bluetoothManager setDeviceScanningEnabled: true]; [_bluetoothManager scanForServices: 0xFFFFFFFF]; } - (void) stopScan { [_bluetoothManager setDeviceScanningEnabled: false]; } - (bool)isScanning { return [_bluetoothManager deviceScanningEnabled]; } - (bool)enabled { return [_bluetoothManager enabled]; } - (void)disable { [_bluetoothManager setEnabled:false]; } - (void)enable { [_bluetoothManager setEnabled:true]; } @end ================================================ FILE: BeeTee/DeviceDetailViewController.swift ================================================ /* This file is part of BeeTee Project. It is subject to the license terms in the LICENSE file found in the top-level directory of this distribution and at https://github.com/michaeldorner/BeeTee/blob/master/LICENSE. No part of BeeTee Project, including this file, may be copied, modified, propagated, or distributed except according to the terms contained in the LICENSE file. */ import UIKit class DeviceDetailViewController: UITableViewController { var device: BeeTeeDevice? = nil let attributes = ["Name", "Address", "Major Class", "Minor Class", "Type", "Battery"] override func numberOfSections(in tableView: UITableView) -> Int { return 1 } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return attributes.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { var value: String = "" if (device != nil) { switch indexPath.row { case 0: value = device!.name case 1: value = device!.address case 2: value = String(device!.majorClass) case 3: value = String(device!.minorCass) case 4: value = String(device!.type) case 5: value = device!.supportsBatteryLevel ? "Yes" : "No" default: break } } let cell = tableView.dequeueReusableCell(withIdentifier: "DeviceDetailCellIdentifier")! cell.textLabel?.text = attributes[indexPath.row] cell.detailTextLabel?.text = value return cell } override func tableView(_ tableView: UITableView, shouldShowMenuForRowAt indexPath: IndexPath) -> Bool { return (tableView.cellForRow(at: indexPath)?.detailTextLabel?.text) != nil } override func tableView(_ tableView: UITableView, canPerformAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) -> Bool { return action == #selector(copy(_:)) } override func tableView(_ tableView: UITableView, performAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) { if action == #selector(copy(_:)) { let cell = tableView.cellForRow(at: indexPath) let pasteboard = UIPasteboard.general pasteboard.string = cell?.detailTextLabel?.text } } } ================================================ FILE: BeeTee/DeviceListingViewController.swift ================================================ /* This file is part of BeeTee Project. It is subject to the license terms in the LICENSE file found in the top-level directory of this distribution and at https://github.com/michaeldorner/BeeTee/blob/master/LICENSE. No part of BeeTee Project, including this file, may be copied, modified, propagated, or distributed except according to the terms contained in the LICENSE file. */ import UIKit class DeviceListingViewController: UITableViewController, BeeTeeDelegate { let beeTeeModel = BeeTeeModel.sharedInstance override func viewDidLoad() { super.viewDidLoad() beeTeeModel.subscribe(subscriber: self) } func receivedBeeTeeNotification(notification: BeeTeeNotification) { tableView.reloadData() } override func numberOfSections(in tableView: UITableView) -> Int { return 1 } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return beeTeeModel.availableDevices.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let beeTeeDevice = beeTeeModel.availableDevices[indexPath.row] let cell = tableView.dequeueReusableCell(withIdentifier: "DeviceCellIdentifier")! cell.textLabel?.text = beeTeeDevice.name cell.detailTextLabel?.text = beeTeeDevice.address return cell } override func prepare(for segue: UIStoryboardSegue, sender: Any?) { let indexPath = tableView.indexPath(for: sender as! UITableViewCell)! let selectedDevice = beeTeeModel.availableDevices[indexPath.row] let deviceDetailViewController: DeviceDetailViewController = segue.destination as! DeviceDetailViewController deviceDetailViewController.device = selectedDevice } } ================================================ FILE: BeeTee Demo/AppDelegate.swift ================================================ /* This file is part of BeeTee Project. It is subject to the license terms in the LICENSE file found in the top-level directory of this distribution and at https://github.com/michaeldorner/BeeTee/blob/master/LICENSE. No part of BeeTee Project, including this file, may be copied, modified, propagated, or distributed except according to the terms contained in the LICENSE file. */ import UIKit import CoreLocation @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. return true } func applicationWillResignActive(_ application: UIApplication) { // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. } func applicationDidEnterBackground(_ application: UIApplication) { // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. } func applicationWillEnterForeground(_ application: UIApplication) { // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. } func applicationDidBecomeActive(_ application: UIApplication) { // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. } func applicationWillTerminate(_ application: UIApplication) { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } } ================================================ FILE: BeeTee Demo/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "iphone", "size" : "20x20", "scale" : "2x" }, { "idiom" : "iphone", "size" : "20x20", "scale" : "3x" }, { "idiom" : "iphone", "size" : "29x29", "scale" : "2x" }, { "idiom" : "iphone", "size" : "29x29", "scale" : "3x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "2x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "3x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "2x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "3x" }, { "idiom" : "ipad", "size" : "20x20", "scale" : "1x" }, { "idiom" : "ipad", "size" : "20x20", "scale" : "2x" }, { "idiom" : "ipad", "size" : "29x29", "scale" : "1x" }, { "idiom" : "ipad", "size" : "29x29", "scale" : "2x" }, { "idiom" : "ipad", "size" : "40x40", "scale" : "1x" }, { "idiom" : "ipad", "size" : "40x40", "scale" : "2x" }, { "idiom" : "ipad", "size" : "76x76", "scale" : "1x" }, { "idiom" : "ipad", "size" : "76x76", "scale" : "2x" }, { "idiom" : "ipad", "size" : "83.5x83.5", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: BeeTee Demo/Assets.xcassets/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: BeeTee Demo/Assets.xcassets/DeviceListing.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "noun_18353-2.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "noun_18353-1.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "noun_18353.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: BeeTee Demo/Assets.xcassets/MissionControl.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "noun_655374_cc-2.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "noun_655374_cc-1.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "noun_655374_cc.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: BeeTee Demo/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: BeeTee Demo/Base.lproj/Main.storyboard ================================================ ================================================ FILE: BeeTee Demo/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString 3.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: BeeTee Demo/MissionControlViewController.swift ================================================ /* This file is part of BeeTee Project. It is subject to the license terms in the LICENSE file found in the top-level directory of this distribution and at https://github.com/michaeldorner/BeeTee/blob/master/LICENSE. No part of BeeTee Project, including this file, may be copied, modified, propagated, or distributed except according to the terms contained in the LICENSE file. */ import UIKit class MissionControlViewController: UITableViewController, BeeTeeDelegate { private let beeTeeModel = BeeTeeModel.sharedInstance @IBOutlet weak var bluetoothPowerSwitch: UISwitch! @IBOutlet weak var bluetoothScanSwitch: UISwitch! override func viewDidLoad() { super.viewDidLoad() beeTeeModel.subscribe(subscriber: self) bluetoothPowerSwitch.setOn(beeTeeModel.bluetoothIsOn(), animated: false) bluetoothScanSwitch.setOn(beeTeeModel.isScanning(), animated: false) } @IBAction func switchBluetoothPower() { if beeTeeModel.bluetoothIsOn() { beeTeeModel.turnBluetoothOff() } else { beeTeeModel.turnBluetoothOn() } } @IBAction func switchBluetoothScanStatus() { if beeTeeModel.isScanning() { beeTeeModel.stopScan() } else { beeTeeModel.startScanForDevices() } } func receivedBeeTeeNotification(notification: BeeTeeNotification) { tableView.reloadData() bluetoothPowerSwitch.setOn(beeTeeModel.bluetoothIsOn(), animated: true) bluetoothScanSwitch.setOn(beeTeeModel.isScanning(), animated: true) } } ================================================ FILE: BeeTee.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 5F0893721E17F82500197FAD /* MissionControlViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F0893711E17F82500197FAD /* MissionControlViewController.swift */; }; 5F0893761E181C7B00197FAD /* BeeTeeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F0893751E181C7B00197FAD /* BeeTeeModel.swift */; }; 5F08937D1E19021500197FAD /* DeviceListingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F08937C1E19021500197FAD /* DeviceListingViewController.swift */; }; 5F08937F1E19071800197FAD /* DeviceDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F08937E1E19071800197FAD /* DeviceDetailViewController.swift */; }; 5F1764F71E0EBFB0003F888A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F1764F61E0EBFB0003F888A /* AppDelegate.swift */; }; 5F1764F91E0EBFBA003F888A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5F1764F81E0EBFBA003F888A /* Assets.xcassets */; }; 5F1764FE1E0EBFC2003F888A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5F1764FA1E0EBFC2003F888A /* LaunchScreen.storyboard */; }; 5F1764FF1E0EBFC2003F888A /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5F1764FC1E0EBFC2003F888A /* Main.storyboard */; }; 5F2409071E0C0134004AF90F /* BeeTeeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F2409061E0C0134004AF90F /* BeeTeeTests.swift */; }; 5F9F63371E0EB9300014A043 /* BeeTee.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F9F632F1E0EB9300014A043 /* BeeTee.swift */; }; 5F9F63381E0EB9300014A043 /* BeeTeeDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F9F63301E0EB9300014A043 /* BeeTeeDevice.swift */; }; 5F9F63391E0EB9300014A043 /* BluetoothDeviceHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F9F63331E0EB9300014A043 /* BluetoothDeviceHandler.m */; }; 5F9F633A1E0EB9300014A043 /* BluetoothManagerHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F9F63361E0EB9300014A043 /* BluetoothManagerHandler.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 5F2409031E0C0134004AF90F /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 5F2408E61E0C0134004AF90F /* Project object */; proxyType = 1; remoteGlobalIDString = 5F2408ED1E0C0134004AF90F; remoteInfo = BeeTee; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ 5F0893711E17F82500197FAD /* MissionControlViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MissionControlViewController.swift; path = "BeeTee Demo/MissionControlViewController.swift"; sourceTree = SOURCE_ROOT; }; 5F0893751E181C7B00197FAD /* BeeTeeModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BeeTeeModel.swift; sourceTree = ""; }; 5F08937C1E19021500197FAD /* DeviceListingViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceListingViewController.swift; sourceTree = ""; }; 5F08937E1E19071800197FAD /* DeviceDetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceDetailViewController.swift; sourceTree = ""; }; 5F1764F61E0EBFB0003F888A /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = "BeeTee Demo/AppDelegate.swift"; sourceTree = SOURCE_ROOT; }; 5F1764F81E0EBFBA003F888A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = "BeeTee Demo/Assets.xcassets"; sourceTree = SOURCE_ROOT; }; 5F1764FB1E0EBFC2003F888A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = "BeeTee Demo/Base.lproj/LaunchScreen.storyboard"; sourceTree = SOURCE_ROOT; }; 5F1764FD1E0EBFC2003F888A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = "BeeTee Demo/Base.lproj/Main.storyboard"; sourceTree = SOURCE_ROOT; }; 5F1765001E0EBFD2003F888A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = "BeeTee Demo/Info.plist"; sourceTree = SOURCE_ROOT; }; 5F2408EE1E0C0134004AF90F /* BeeTee.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BeeTee.app; sourceTree = BUILT_PRODUCTS_DIR; }; 5F2409021E0C0134004AF90F /* BeeTeeTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BeeTeeTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 5F2409061E0C0134004AF90F /* BeeTeeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BeeTeeTests.swift; sourceTree = ""; }; 5F2409081E0C0134004AF90F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 5F9F632E1E0EB9300014A043 /* BeeTee-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "BeeTee-Bridging-Header.h"; path = "BeeTee/BeeTee-Bridging-Header.h"; sourceTree = ""; }; 5F9F632F1E0EB9300014A043 /* BeeTee.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BeeTee.swift; path = BeeTee/BeeTee.swift; sourceTree = ""; }; 5F9F63301E0EB9300014A043 /* BeeTeeDevice.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BeeTeeDevice.swift; path = BeeTee/BeeTeeDevice.swift; sourceTree = ""; }; 5F9F63311E0EB9300014A043 /* BluetoothDevice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BluetoothDevice.h; path = BeeTee/BluetoothDevice.h; sourceTree = ""; }; 5F9F63321E0EB9300014A043 /* BluetoothDeviceHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BluetoothDeviceHandler.h; path = BeeTee/BluetoothDeviceHandler.h; sourceTree = ""; }; 5F9F63331E0EB9300014A043 /* BluetoothDeviceHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BluetoothDeviceHandler.m; path = BeeTee/BluetoothDeviceHandler.m; sourceTree = ""; }; 5F9F63341E0EB9300014A043 /* BluetoothManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BluetoothManager.h; path = BeeTee/BluetoothManager.h; sourceTree = ""; }; 5F9F63351E0EB9300014A043 /* BluetoothManagerHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BluetoothManagerHandler.h; path = BeeTee/BluetoothManagerHandler.h; sourceTree = ""; }; 5F9F63361E0EB9300014A043 /* BluetoothManagerHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BluetoothManagerHandler.m; path = BeeTee/BluetoothManagerHandler.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 5F2408EB1E0C0134004AF90F /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 5F2408FF1E0C0134004AF90F /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 5F0893791E18F95B00197FAD /* View */ = { isa = PBXGroup; children = ( 5F1764F81E0EBFBA003F888A /* Assets.xcassets */, 5F1764FA1E0EBFC2003F888A /* LaunchScreen.storyboard */, 5F1764FC1E0EBFC2003F888A /* Main.storyboard */, ); name = View; sourceTree = ""; }; 5F08937A1E18F96900197FAD /* Controller */ = { isa = PBXGroup; children = ( 5F0893711E17F82500197FAD /* MissionControlViewController.swift */, 5F08937C1E19021500197FAD /* DeviceListingViewController.swift */, 5F08937E1E19071800197FAD /* DeviceDetailViewController.swift */, ); name = Controller; sourceTree = ""; }; 5F08937B1E18F97000197FAD /* Model */ = { isa = PBXGroup; children = ( 5F0893751E181C7B00197FAD /* BeeTeeModel.swift */, ); name = Model; sourceTree = ""; }; 5F2408E51E0C0134004AF90F = { isa = PBXGroup; children = ( 5F9F632D1E0EB60A0014A043 /* BeeTee */, 5F2408F01E0C0134004AF90F /* BeeTee Demo */, 5F2409051E0C0134004AF90F /* BeeTeeTests */, 5F2408EF1E0C0134004AF90F /* Products */, ); sourceTree = ""; }; 5F2408EF1E0C0134004AF90F /* Products */ = { isa = PBXGroup; children = ( 5F2408EE1E0C0134004AF90F /* BeeTee.app */, 5F2409021E0C0134004AF90F /* BeeTeeTests.xctest */, ); name = Products; sourceTree = ""; }; 5F2408F01E0C0134004AF90F /* BeeTee Demo */ = { isa = PBXGroup; children = ( 5F1765001E0EBFD2003F888A /* Info.plist */, 5F1764F61E0EBFB0003F888A /* AppDelegate.swift */, 5F0893791E18F95B00197FAD /* View */, 5F08937A1E18F96900197FAD /* Controller */, 5F08937B1E18F97000197FAD /* Model */, ); name = "BeeTee Demo"; path = BeeTee; sourceTree = ""; }; 5F2409051E0C0134004AF90F /* BeeTeeTests */ = { isa = PBXGroup; children = ( 5F2409061E0C0134004AF90F /* BeeTeeTests.swift */, 5F2409081E0C0134004AF90F /* Info.plist */, ); path = BeeTeeTests; sourceTree = ""; }; 5F9F63281E0D75EC0014A043 /* Objective-C */ = { isa = PBXGroup; children = ( 5F9F632E1E0EB9300014A043 /* BeeTee-Bridging-Header.h */, 5F9F63321E0EB9300014A043 /* BluetoothDeviceHandler.h */, 5F9F63331E0EB9300014A043 /* BluetoothDeviceHandler.m */, 5F9F63351E0EB9300014A043 /* BluetoothManagerHandler.h */, 5F9F63361E0EB9300014A043 /* BluetoothManagerHandler.m */, 5F9F63291E0D75F70014A043 /* Private Headers */, ); name = "Objective-C"; sourceTree = ""; }; 5F9F63291E0D75F70014A043 /* Private Headers */ = { isa = PBXGroup; children = ( 5F9F63311E0EB9300014A043 /* BluetoothDevice.h */, 5F9F63341E0EB9300014A043 /* BluetoothManager.h */, ); name = "Private Headers"; sourceTree = ""; }; 5F9F632D1E0EB60A0014A043 /* BeeTee */ = { isa = PBXGroup; children = ( 5F9F632F1E0EB9300014A043 /* BeeTee.swift */, 5F9F63301E0EB9300014A043 /* BeeTeeDevice.swift */, 5F9F63281E0D75EC0014A043 /* Objective-C */, ); name = BeeTee; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 5F2408ED1E0C0134004AF90F /* BeeTee */ = { isa = PBXNativeTarget; buildConfigurationList = 5F24090B1E0C0134004AF90F /* Build configuration list for PBXNativeTarget "BeeTee" */; buildPhases = ( 5F2408EA1E0C0134004AF90F /* Sources */, 5F2408EB1E0C0134004AF90F /* Frameworks */, 5F2408EC1E0C0134004AF90F /* Resources */, ); buildRules = ( ); dependencies = ( ); name = BeeTee; productName = BeeTee; productReference = 5F2408EE1E0C0134004AF90F /* BeeTee.app */; productType = "com.apple.product-type.application"; }; 5F2409011E0C0134004AF90F /* BeeTeeTests */ = { isa = PBXNativeTarget; buildConfigurationList = 5F24090E1E0C0134004AF90F /* Build configuration list for PBXNativeTarget "BeeTeeTests" */; buildPhases = ( 5F2408FE1E0C0134004AF90F /* Sources */, 5F2408FF1E0C0134004AF90F /* Frameworks */, 5F2409001E0C0134004AF90F /* Resources */, ); buildRules = ( ); dependencies = ( 5F2409041E0C0134004AF90F /* PBXTargetDependency */, ); name = BeeTeeTests; productName = BeeTeeTests; productReference = 5F2409021E0C0134004AF90F /* BeeTeeTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 5F2408E61E0C0134004AF90F /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0820; LastUpgradeCheck = 0820; ORGANIZATIONNAME = "Michael Dorner"; TargetAttributes = { 5F2408ED1E0C0134004AF90F = { CreatedOnToolsVersion = 8.2.1; LastSwiftMigration = 0820; ProvisioningStyle = Manual; }; 5F2409011E0C0134004AF90F = { CreatedOnToolsVersion = 8.2.1; ProvisioningStyle = Automatic; TestTargetID = 5F2408ED1E0C0134004AF90F; }; }; }; buildConfigurationList = 5F2408E91E0C0134004AF90F /* Build configuration list for PBXProject "BeeTee" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 5F2408E51E0C0134004AF90F; productRefGroup = 5F2408EF1E0C0134004AF90F /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 5F2408ED1E0C0134004AF90F /* BeeTee */, 5F2409011E0C0134004AF90F /* BeeTeeTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 5F2408EC1E0C0134004AF90F /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 5F1764F91E0EBFBA003F888A /* Assets.xcassets in Resources */, 5F1764FF1E0EBFC2003F888A /* Main.storyboard in Resources */, 5F1764FE1E0EBFC2003F888A /* LaunchScreen.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 5F2409001E0C0134004AF90F /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 5F2408EA1E0C0134004AF90F /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 5F9F633A1E0EB9300014A043 /* BluetoothManagerHandler.m in Sources */, 5F9F63371E0EB9300014A043 /* BeeTee.swift in Sources */, 5F1764F71E0EBFB0003F888A /* AppDelegate.swift in Sources */, 5F08937D1E19021500197FAD /* DeviceListingViewController.swift in Sources */, 5F9F63381E0EB9300014A043 /* BeeTeeDevice.swift in Sources */, 5F08937F1E19071800197FAD /* DeviceDetailViewController.swift in Sources */, 5F0893761E181C7B00197FAD /* BeeTeeModel.swift in Sources */, 5F9F63391E0EB9300014A043 /* BluetoothDeviceHandler.m in Sources */, 5F0893721E17F82500197FAD /* MissionControlViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 5F2408FE1E0C0134004AF90F /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 5F2409071E0C0134004AF90F /* BeeTeeTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 5F2409041E0C0134004AF90F /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 5F2408ED1E0C0134004AF90F /* BeeTee */; targetProxy = 5F2409031E0C0134004AF90F /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 5F1764FA1E0EBFC2003F888A /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 5F1764FB1E0EBFC2003F888A /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; 5F1764FC1E0EBFC2003F888A /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 5F1764FD1E0EBFC2003F888A /* Base */, ); name = Main.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 5F2409091E0C0134004AF90F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 10.2; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OBJC_BRIDGING_HEADER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 5F24090A1E0C0134004AF90F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 10.2; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OBJC_BRIDGING_HEADER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 5F24090C1E0C0134004AF90F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = "$(SRCROOT)/BeeTee Demo/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; OTHER_LDFLAGS = ( "-ObjC", "$(inherited)", ); PRODUCT_BUNDLE_IDENTIFIER = de.michaeldorner.BeeTee; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "BeeTee/BeeTee-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 3.0; }; name = Debug; }; 5F24090D1E0C0134004AF90F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = "$(SRCROOT)/BeeTee Demo/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; OTHER_LDFLAGS = ( "-ObjC", "$(inherited)", ); PRODUCT_BUNDLE_IDENTIFIER = de.michaeldorner.BeeTee; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "BeeTee/BeeTee-Bridging-Header.h"; SWIFT_VERSION = 3.0; }; name = Release; }; 5F24090F1E0C0134004AF90F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; INFOPLIST_FILE = BeeTeeTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = de.michaeldorner.BeeTeeTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 3.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/BeeTee.app/BeeTee"; }; name = Debug; }; 5F2409101E0C0134004AF90F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; INFOPLIST_FILE = BeeTeeTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = de.michaeldorner.BeeTeeTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 3.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/BeeTee.app/BeeTee"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 5F2408E91E0C0134004AF90F /* Build configuration list for PBXProject "BeeTee" */ = { isa = XCConfigurationList; buildConfigurations = ( 5F2409091E0C0134004AF90F /* Debug */, 5F24090A1E0C0134004AF90F /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 5F24090B1E0C0134004AF90F /* Build configuration list for PBXNativeTarget "BeeTee" */ = { isa = XCConfigurationList; buildConfigurations = ( 5F24090C1E0C0134004AF90F /* Debug */, 5F24090D1E0C0134004AF90F /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 5F24090E1E0C0134004AF90F /* Build configuration list for PBXNativeTarget "BeeTeeTests" */ = { isa = XCConfigurationList; buildConfigurations = ( 5F24090F1E0C0134004AF90F /* Debug */, 5F2409101E0C0134004AF90F /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 5F2408E61E0C0134004AF90F /* Project object */; } ================================================ FILE: BeeTee.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: BeeTeeTests/BeeTeeTests.swift ================================================ /* This file is part of BeeTee Project. It is subject to the license terms in the LICENSE file found in the top-level directory of this distribution and at https://github.com/michaeldorner/BeeTee/blob/master/LICENSE. No part of BeeTee Project, including this file, may be copied, modified, propagated, or distributed except according to the terms contained in the LICENSE file. */ import XCTest @testable import BeeTee class BeeTeeTests: XCTestCase { override func setUp() { super.setUp() let beeTee = BeeTee() for device in beeTee.availableDevices { print(device) } } override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. super.tearDown() } func testExample() { // This is an example of a functional test case. // Use XCTAssert and related functions to verify your tests produce the correct results. } func testPerformanceExample() { // This is an example of a performance test case. self.measure { // Put the code you want to measure the time of here. } } } ================================================ FILE: BeeTeeTests/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleVersion 1 ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2016 Michael Dorner Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in 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: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 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 AUTHORS 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 IN THE SOFTWARE. ================================================ FILE: README.md ================================================ [![Build Status](https://travis-ci.org/michaeldorner/BeeTee.svg?branch=master)](https://travis-ci.org/michaeldorner/BeeTee) [![codebeat badge](https://codebeat.co/badges/65bf4b44-cbbc-4807-a9e9-b3cd68c4378d)](https://codebeat.co/projects/github-com-michaeldorner-beetee) [![codecov](https://codecov.io/gh/michaeldorner/BeeTee/branch/master/graph/badge.svg)](https://codecov.io/gh/michaeldorner/BeeTee) [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE) # BeeTee > BeeTee is an easy to use Swift framework, that allows simple access to the Bluetooth classic in iOS for turning on/off and scanning for Bluetooth devices. Besides BeeTee demonstrates how to access the private `BluetoothManager.framework` in iOS. ## Table of Contents - [Limitations](#limitations) - [Installation](#installation) - [Usage](#usage) - [API](#api) - [Known Issues](#known-issues) - [Contributions](#contributions) - [Versions](#versions) - [License](#license) ## Limitations Based on the [AppStore guideline §2.5](https://developer.apple.com/appstore/resources/approval/guidelines.html) on private (undocumented) functions it is not possible to publish apps with the _BeeTee_ and `BluetoothManager.framework` in the AppStore. You need a valid membership of the [iOS Developer Program](https://developer.apple.com/programs/ios/), because the _BeeTee_ does not work in the iOS simulator. Connecting to devices is not possible in most cases and, therefore, not yet supported. There are currently no known limitations on iOS versions. ## Installation Copy all files in the _BeeTee_ folder to your project and done. That means there are 9 files to copy: * `BluetoothDevice.h` * `BluetoothManager.h` * `BluetoothDeviceHandler.h` * `BluetoothDeviceHandler.m` * `BluetoothManagerHandler.h` * `BluetoothManagerHandler.m` * `BeeTee-Bridge-Header.h` * `BeeTee.swift` * `BeeTeeDevice.swift` ## Usage Here is a small code snippet, how easily you can use _BeeTee_: class Demo: BeeTeeDelegate { let beeTee = BeeTee() init() { beeTee.delegate = self beeTee.enableBluetooth() beeTee.startScanForDevices() } func receivedBeeTeeNotification(notification: BeeTeeNotification) { switch notification { case .DeviceDiscovered: for device in beeTee.availableDevices { print(device) } default: print(notification) } } } ## API ### BeeTee The API is based on the other hardware managers, such as [`CLLocationManager`](https://developer.apple.com/reference/corelocation/cllocationmanager) or the underlaying `BluetoothManager.framework`. I focused on a clear distinction between the different layers, also by using different programming languages: ![Layer architecture of BeeTee](landingPage/BeeTeeLayer.png) #### `BeeTeeNotification` public enum BeeTeeNotification { case PowerChanged case AvailabilityChanged case DeviceDiscovered case DeviceRemoved case ConnectabilityChanged case DeviceUpdated case DiscoveryStateChanged case DeviceConnectSuccess case ConnectionStatusChanged case DeviceDisconnectSuccess static let allNotifications: [BeeTeeNotification] } So all known notification from `BluetoothManager.framework` are passed through (see next section). I used only `PowerChanged`, `DeviceDiscovered`, `DeviceRemoved` in my demo application. #### `BeeTeeDelegate` public protocol BeeTeeDelegate { func receivedBeeTeeNotification(notification: BeeTeeNotification) } #### `BeeTee` public class BeeTee { public var delegate: BeeTeeDelegate? public var availableDevices: [BeeTeeDevice] convenience init(delegate: BeeTeeDelegate) public func enableBluetooth() public func disableBluetooth() public func bluetoothIsEnabled() -> Bool public func startScanForDevices() public func stopScan() public func isScanning() -> Bool public static func debugLowLevel() // see section BluetoothManager.framework/Available Notification } ### `BluetoothManager.framework` If you want to dive deeper into `BluetoothManager.framework` this section is interesting for you. #### Available Notifications I found the following notification regarding Bluetooth BluetoothAvailabilityChangedNotification BluetoothDiscoveryStateChangedNotification BluetoothDeviceDiscoveredNotification BluetoothDeviceRemovedNotification BluetoothPowerChangedNotification BluetoothConnectabilityChangedNotification BluetoothDeviceUpdatedNotification BluetoothDeviceConnectSuccessNotification BluetoothConnectionStatusChangedNotification BluetoothDeviceDisconnectSuccessNotification Maybe the list is not complete. You can look for them yourself using CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), nil, { (_, observer, name, _, _) in let n = name?.rawValue as! String if n.hasPrefix("B") { // notice only notification they are associated with the BluetoothManager.framework print("Received notification: \(name)") } }, nil, nil, .deliverImmediately) or with in `BeeTee`: BeeTee.debugLowLevel() ## Known Issues * Actually I wanted to encapsulate BeeTee in a formal framework. But it seems that [Swift does not allow framework-internal (protected) Objective-C code](http://stackoverflow.com/questions/41303716/objective-c-code-swift-framework-internal). * Some notifications are sent multiple times ([issue](https://github.com/michaeldorner/BeeTee/issues/13)). I am not sure how to deal with it. If you have problems make this project running have a look at [Stackoverflow](http://stackoverflow.com/search?q=beetee). If you have other questions or suggestions, feel free to contact me here in GitHub or somehow else. :-) ## Contributions Help is welcome! If you do not know what to do, just pick one item and send me a pull request. - [ ] Fix issue with multiple notifications - [ ] Restructure BeeTee in a framework (`BeeTee.framework`, see [discussion on stackoverflow](http://stackoverflow.com/questions/41303716/objective-c-code-swift-framework-internal)) - [ ] Write test cases - [ ] Support Cocoapods - [ ] Improve documentation, especially inline documentation - [ ] Provide app icons - [x] Support Travis support ## Versions ### 3.0 * Rewritten in Swift 3 * New API * Clear separation of Objective-C and Swift code * Dynamically loading of `Bluetooth.framework` (so no more header and import trouble) * Released now under MIT license ### 2.0 * Wrapper classes `MDBluetoothManager` and `MDBluetoothDevice` introduced * Updated to ARC * Extented GUI ### 1.0 * Initial Commit as demo project for `BluetoothManager.framework`, Non-ARC ## License BeeTee is released under the MIT license. See [LICENSE](LICENSE) for more details. The list icon was created by Aya Sofya (thenounproject.com). ================================================ FILE: landingPage/BeeTeeLayer.graphml ================================================ BeeTee.swift BluetoothManager.h BluetoothDevice.h BluetoothManagerHandler.h BluetoothDeviceHandler.h BeeTeeDevice.swift public protected private BeeTee framework (Swift) Objective-C wrapper BluetoothManager.framework Description Framework Access Level Implementation