From 7892a1a2155aeb9885cbda220808739c2b5fd8e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Lenart?= Date: Wed, 24 Feb 2016 12:43:52 +0100 Subject: [PATCH] Initial commit --- .gitignore | 4 +- Example/Podfile | 22 +- .../RxBluetoothKit.xcodeproj/project.pbxproj | 63 +- .../contents.xcworkspacedata | 10 + Example/RxBluetoothKit/ViewController.swift | 61 +- .../Tests/BluetoothManagerSpec+Scanning.swift | 239 +++++ Example/Tests/BluetoothManagerSpec.swift | 483 ++++++++++ Example/Tests/Fakes/FakeCentralManager.swift | 54 ++ Example/Tests/Fakes/FakeCharacteristic.swift | 28 + Example/Tests/Fakes/FakeDescriptor.swift | 9 + Example/Tests/Fakes/FakePeripheral.swift | 92 ++ Example/Tests/Fakes/FakeService.swift | 20 + .../PeripheralSpec+Characteristics.swift | 880 ++++++++++++++++++ Example/Tests/PeripheralSpec+Services.swift | 371 ++++++++ Example/Tests/Shared/Utilities.swift | 119 +++ Example/Tests/Tests.swift | 50 - .../BluetoothManager/BluetoothManager.swift | 255 +++++ .../Internal/ScanOperation.swift | 19 + .../BluetoothManager/RxCBCentralManager.swift | 81 ++ .../RxCentralManagerType.swift | 31 + .../Characteristic/Characteristic.swift | 57 ++ .../Characteristic/RxCBCharacteristic.swift | 31 + .../Characteristic/RxCharacteristicType.swift | 37 + Pod/Classes/Descriptor/Descriptor.swift | 26 + Pod/Classes/Descriptor/RxCBDescriptor.swift | 25 + Pod/Classes/Descriptor/RxDescriptorType.swift | 20 + .../Peripheral/AdvertisementData.swift | 43 + Pod/Classes/Peripheral/Peripheral.swift | 325 +++++++ Pod/Classes/Peripheral/RxCBPeripheral.swift | 183 ++++ Pod/Classes/Peripheral/RxPeripheralType.swift | 51 + .../Peripheral/ScannedPeripheral.swift | 25 + Pod/Classes/ReplaceMe.swift | 0 Pod/Classes/Service/RxCBService.swift | 34 + Pod/Classes/Service/RxServiceType.swift | 35 + Pod/Classes/Service/Service.swift | 48 + Pod/Classes/Shared/BluetoothError.swift | 127 +++ Pod/Classes/Shared/Boxes.swift | 27 + Pod/Classes/Shared/CollectionUtils.swift | 18 + Pod/Classes/Shared/Unimplemented.swift | 21 + RxBluetoothKit.podspec | 2 +- 40 files changed, 3965 insertions(+), 61 deletions(-) create mode 100644 Example/RxBluetoothKit.xcworkspace/contents.xcworkspacedata create mode 100644 Example/Tests/BluetoothManagerSpec+Scanning.swift create mode 100644 Example/Tests/BluetoothManagerSpec.swift create mode 100644 Example/Tests/Fakes/FakeCentralManager.swift create mode 100644 Example/Tests/Fakes/FakeCharacteristic.swift create mode 100644 Example/Tests/Fakes/FakeDescriptor.swift create mode 100644 Example/Tests/Fakes/FakePeripheral.swift create mode 100644 Example/Tests/Fakes/FakeService.swift create mode 100644 Example/Tests/PeripheralSpec+Characteristics.swift create mode 100644 Example/Tests/PeripheralSpec+Services.swift create mode 100644 Example/Tests/Shared/Utilities.swift delete mode 100644 Example/Tests/Tests.swift create mode 100644 Pod/Classes/BluetoothManager/BluetoothManager.swift create mode 100644 Pod/Classes/BluetoothManager/Internal/ScanOperation.swift create mode 100644 Pod/Classes/BluetoothManager/RxCBCentralManager.swift create mode 100644 Pod/Classes/BluetoothManager/RxCentralManagerType.swift create mode 100644 Pod/Classes/Characteristic/Characteristic.swift create mode 100644 Pod/Classes/Characteristic/RxCBCharacteristic.swift create mode 100644 Pod/Classes/Characteristic/RxCharacteristicType.swift create mode 100644 Pod/Classes/Descriptor/Descriptor.swift create mode 100644 Pod/Classes/Descriptor/RxCBDescriptor.swift create mode 100644 Pod/Classes/Descriptor/RxDescriptorType.swift create mode 100644 Pod/Classes/Peripheral/AdvertisementData.swift create mode 100644 Pod/Classes/Peripheral/Peripheral.swift create mode 100644 Pod/Classes/Peripheral/RxCBPeripheral.swift create mode 100644 Pod/Classes/Peripheral/RxPeripheralType.swift create mode 100644 Pod/Classes/Peripheral/ScannedPeripheral.swift delete mode 100644 Pod/Classes/ReplaceMe.swift create mode 100644 Pod/Classes/Service/RxCBService.swift create mode 100644 Pod/Classes/Service/RxServiceType.swift create mode 100644 Pod/Classes/Service/Service.swift create mode 100644 Pod/Classes/Shared/BluetoothError.swift create mode 100644 Pod/Classes/Shared/Boxes.swift create mode 100644 Pod/Classes/Shared/CollectionUtils.swift create mode 100644 Pod/Classes/Shared/Unimplemented.swift diff --git a/.gitignore b/.gitignore index f067d676..515d5a33 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,6 @@ Carthage # Note: if you ignore the Pods directory, make sure to uncomment # `pod install` in .travis.yml # -# Pods/ +Pods/ +.idea +Podfile.lock diff --git a/Example/Podfile b/Example/Podfile index d6f3eff6..ccf04a9a 100644 --- a/Example/Podfile +++ b/Example/Podfile @@ -1,13 +1,27 @@ source 'https://github.com/CocoaPods/Specs.git' use_frameworks! -target 'RxBluetoothKit_Example', :exclusive => true do +target 'RxBluetoothKit_Example' do + pod 'RxSwift', '~> 2.0' pod 'RxBluetoothKit', :path => '../' end -target 'RxBluetoothKit_Tests', :exclusive => true do +target 'RxBluetoothKit_Tests' do pod 'RxBluetoothKit', :path => '../' - pod 'Quick', '~> 0.8.0' - pod 'Nimble', '3.0.0' + pod 'RxSwift', '~> 2.0' + pod 'RxTests', '~> 2.0' + pod 'Quick', '~> 0.9.1' + pod 'Nimble', '3.1.0' +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + if target.name == "RxTests" + puts "Setting bitcode" + target.build_configurations.each do |config| + config.build_settings['ENABLE_BITCODE'] = 'NO' + end + end + end end diff --git a/Example/RxBluetoothKit.xcodeproj/project.pbxproj b/Example/RxBluetoothKit.xcodeproj/project.pbxproj index 705916f7..4ec9002f 100644 --- a/Example/RxBluetoothKit.xcodeproj/project.pbxproj +++ b/Example/RxBluetoothKit.xcodeproj/project.pbxproj @@ -8,13 +8,22 @@ /* Begin PBXBuildFile section */ 031FE2B248FDF03BE182C365 /* Pods_RxBluetoothKit_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C3B93FA4703CCAA337FB22E /* Pods_RxBluetoothKit_Example.framework */; }; + 262D60041C87163E0018834F /* PeripheralSpec+Services.swift in Sources */ = {isa = PBXBuildFile; fileRef = 262D60031C87163E0018834F /* PeripheralSpec+Services.swift */; }; + 267E905B1C7E09FC0022BBF5 /* FakePeripheral.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26F930301C7DFFF600CCAC5E /* FakePeripheral.swift */; }; + 26B6649A1C7F0D4000B839B4 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26B664991C7F0D4000B839B4 /* Utilities.swift */; }; + 26B664A31C7F5C2F00B839B4 /* FakeDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26B664A11C7F5C2B00B839B4 /* FakeDescriptor.swift */; }; + 26B664A41C7F5C3300B839B4 /* FakeCharacteristic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26B6649F1C7F5C2200B839B4 /* FakeCharacteristic.swift */; }; + 26B664A51C7F5C3600B839B4 /* FakeService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26B6649D1C7F5C1700B839B4 /* FakeService.swift */; }; + 26DD50401C85AA7100D47B37 /* BluetoothManagerSpec+Scanning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26DD503F1C85AA7100D47B37 /* BluetoothManagerSpec+Scanning.swift */; }; + 26F9302C1C7DFAE600CCAC5E /* PeripheralSpec+Characteristics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26F9302A1C7DFADA00CCAC5E /* PeripheralSpec+Characteristics.swift */; }; + 26F9302F1C7DFAF000CCAC5E /* BluetoothManagerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26F9302D1C7DFAEE00CCAC5E /* BluetoothManagerSpec.swift */; }; 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD51AFB9204008FA782 /* AppDelegate.swift */; }; 607FACD81AFB9204008FA782 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD71AFB9204008FA782 /* ViewController.swift */; }; 607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 607FACD91AFB9204008FA782 /* Main.storyboard */; }; 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDC1AFB9204008FA782 /* Images.xcassets */; }; 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */; }; - 607FACEC1AFB9204008FA782 /* Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACEB1AFB9204008FA782 /* Tests.swift */; }; 72B1A0EDA344062F5F4794AD /* Pods_RxBluetoothKit_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1CE9373E3F0A9E06F4BBC24D /* Pods_RxBluetoothKit_Tests.framework */; }; + D7F8043B1C7DFD0700BDA198 /* FakeCentralManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7F8043A1C7DFD0700BDA198 /* FakeCentralManager.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -29,6 +38,15 @@ /* Begin PBXFileReference section */ 1CE9373E3F0A9E06F4BBC24D /* Pods_RxBluetoothKit_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RxBluetoothKit_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 262D60031C87163E0018834F /* PeripheralSpec+Services.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PeripheralSpec+Services.swift"; sourceTree = ""; }; + 26B664991C7F0D4000B839B4 /* Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = ""; }; + 26B6649D1C7F5C1700B839B4 /* FakeService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FakeService.swift; sourceTree = ""; }; + 26B6649F1C7F5C2200B839B4 /* FakeCharacteristic.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FakeCharacteristic.swift; sourceTree = ""; }; + 26B664A11C7F5C2B00B839B4 /* FakeDescriptor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FakeDescriptor.swift; sourceTree = ""; }; + 26DD503F1C85AA7100D47B37 /* BluetoothManagerSpec+Scanning.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BluetoothManagerSpec+Scanning.swift"; sourceTree = ""; }; + 26F9302A1C7DFADA00CCAC5E /* PeripheralSpec+Characteristics.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PeripheralSpec+Characteristics.swift"; sourceTree = ""; }; + 26F9302D1C7DFAEE00CCAC5E /* BluetoothManagerSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BluetoothManagerSpec.swift; sourceTree = ""; }; + 26F930301C7DFFF600CCAC5E /* FakePeripheral.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FakePeripheral.swift; path = Tests/Fakes/FakePeripheral.swift; sourceTree = SOURCE_ROOT; }; 347C5E26C8326B9D808F1095 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 5C1D4A0EA46D75905230CEC3 /* Pods-RxBluetoothKit_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RxBluetoothKit_Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-RxBluetoothKit_Example/Pods-RxBluetoothKit_Example.debug.xcconfig"; sourceTree = ""; }; 5C3B93FA4703CCAA337FB22E /* Pods_RxBluetoothKit_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RxBluetoothKit_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -41,10 +59,10 @@ 607FACDF1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 607FACE51AFB9204008FA782 /* RxBluetoothKit_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RxBluetoothKit_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 607FACEA1AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 607FACEB1AFB9204008FA782 /* Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests.swift; sourceTree = ""; }; 7718924577072E7313522770 /* Pods-RxBluetoothKit_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RxBluetoothKit_Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-RxBluetoothKit_Example/Pods-RxBluetoothKit_Example.release.xcconfig"; sourceTree = ""; }; C37C289CD8239251A18D6D90 /* Pods-RxBluetoothKit_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RxBluetoothKit_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-RxBluetoothKit_Tests/Pods-RxBluetoothKit_Tests.debug.xcconfig"; sourceTree = ""; }; D69E263E7DA204DABD9084C4 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; + D7F8043A1C7DFD0700BDA198 /* FakeCentralManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FakeCentralManager.swift; sourceTree = ""; }; F13742F6B8F4630D234879E1 /* Pods-RxBluetoothKit_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RxBluetoothKit_Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-RxBluetoothKit_Tests/Pods-RxBluetoothKit_Tests.release.xcconfig"; sourceTree = ""; }; F1755B6F4B4F1691B0F27175 /* RxBluetoothKit.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = RxBluetoothKit.podspec; path = ../RxBluetoothKit.podspec; sourceTree = ""; }; /* End PBXFileReference section */ @@ -69,6 +87,14 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 26B664981C7F0D1400B839B4 /* Shared */ = { + isa = PBXGroup; + children = ( + 26B664991C7F0D4000B839B4 /* Utilities.swift */, + ); + path = Shared; + sourceTree = ""; + }; 4AB0C3008E27C14BB533E7D6 /* Pods */ = { isa = PBXGroup; children = ( @@ -126,8 +152,13 @@ 607FACE81AFB9204008FA782 /* Tests */ = { isa = PBXGroup; children = ( - 607FACEB1AFB9204008FA782 /* Tests.swift */, + 26B664981C7F0D1400B839B4 /* Shared */, + D7F804371C7DFC4D00BDA198 /* Fakes */, 607FACE91AFB9204008FA782 /* Supporting Files */, + 26F9302A1C7DFADA00CCAC5E /* PeripheralSpec+Characteristics.swift */, + 26F9302D1C7DFAEE00CCAC5E /* BluetoothManagerSpec.swift */, + 26DD503F1C85AA7100D47B37 /* BluetoothManagerSpec+Scanning.swift */, + 262D60031C87163E0018834F /* PeripheralSpec+Services.swift */, ); path = Tests; sourceTree = ""; @@ -150,6 +181,18 @@ name = "Podspec Metadata"; sourceTree = ""; }; + D7F804371C7DFC4D00BDA198 /* Fakes */ = { + isa = PBXGroup; + children = ( + D7F8043A1C7DFD0700BDA198 /* FakeCentralManager.swift */, + 26F930301C7DFFF600CCAC5E /* FakePeripheral.swift */, + 26B6649D1C7F5C1700B839B4 /* FakeService.swift */, + 26B6649F1C7F5C2200B839B4 /* FakeCharacteristic.swift */, + 26B664A11C7F5C2B00B839B4 /* FakeDescriptor.swift */, + ); + path = Fakes; + sourceTree = ""; + }; D88C4B1E3CE04CC57D0A9CCD /* Frameworks */ = { isa = PBXGroup; children = ( @@ -368,7 +411,16 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 607FACEC1AFB9204008FA782 /* Tests.swift in Sources */, + 26B6649A1C7F0D4000B839B4 /* Utilities.swift in Sources */, + 26F9302F1C7DFAF000CCAC5E /* BluetoothManagerSpec.swift in Sources */, + 26DD50401C85AA7100D47B37 /* BluetoothManagerSpec+Scanning.swift in Sources */, + 267E905B1C7E09FC0022BBF5 /* FakePeripheral.swift in Sources */, + 26B664A41C7F5C3300B839B4 /* FakeCharacteristic.swift in Sources */, + 26F9302C1C7DFAE600CCAC5E /* PeripheralSpec+Characteristics.swift in Sources */, + 26B664A31C7F5C2F00B839B4 /* FakeDescriptor.swift in Sources */, + 262D60041C87163E0018834F /* PeripheralSpec+Services.swift in Sources */, + 26B664A51C7F5C3600B839B4 /* FakeService.swift in Sources */, + D7F8043B1C7DFD0700BDA198 /* FakeCentralManager.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -422,6 +474,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; @@ -467,8 +520,10 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; diff --git a/Example/RxBluetoothKit.xcworkspace/contents.xcworkspacedata b/Example/RxBluetoothKit.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..39435b8e --- /dev/null +++ b/Example/RxBluetoothKit.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/Example/RxBluetoothKit/ViewController.swift b/Example/RxBluetoothKit/ViewController.swift index 38600c47..b374961c 100644 --- a/Example/RxBluetoothKit/ViewController.swift +++ b/Example/RxBluetoothKit/ViewController.swift @@ -7,12 +7,71 @@ // import UIKit +import RxSwift +import CoreBluetooth +import RxBluetoothKit class ViewController: UIViewController { + + var disposeBag = DisposeBag() + var manager : BluetoothManager! + + override func viewDidLoad() { super.viewDidLoad() - // Do any additional setup after loading the view, typically from a nib. +// manager = BluetoothManager(centralManager: RxCBCentralManager(queue: dispatch_get_main_queue())) + +// let serviceUUID = CBUUID(string: "181d") +// let characteristicUUID = CBUUID(string: "2a9d") + +// manager.scanForDevices([]) +// .delaySubscription(3, scheduler: ConcurrentMainScheduler.instance) +// .filter { scannedDevice in +// return scannedDevice.advertisementData.localName == "StandUp_Mat" +// } +// .take(1) +// .flatMap { scannedDevice -> Observable in +// let device = scannedDevice.device +// return device.connect() +// } +// .flatMap { connectedDevice -> Observable in +// XCGLogger.info("Connected!") +// return connectedDevice.discoverServices([]) +// } +// .filter { +// XCGLogger.info("Found service: \($0.uuid)") +// return $0.uuid == serviceUUID +// } +// .take(1) +// .flatMap { service -> Observable in +// XCGLogger.info("Discovers characteristic for service: \(service.uuid)") +// return service.discoverCharacteristics([characteristicUUID]) +// } +// .flatMap { service -> Observable in +// XCGLogger.info( "Scanned characteristics: \(service.service.characteristics)") +// return service.characteristics.toObservable() +// } +// .flatMap { characteristic -> Observable in +// XCGLogger.info("Setting notify for characteristic: \(characteristic.uuid.UUIDString)") +// return characteristic.setNotifyValue(true) +// } +// .flatMap { characteristic -> Observable in +// XCGLogger.info("Observing characteristic: \(characteristic.uuid.UUIDString)") +// return characteristic.monitorValueUpdate() +// } +// .subscribe({ (event) -> Void in +// switch event { +// case .Next(let characteristic): +// XCGLogger.info("Characteristic: \(characteristic.uuid) value: \(characteristic.value)") +// case .Completed: +// XCGLogger.info("Completed") +// case .Error(let err): +// XCGLogger.info("Error: \(err)") +// } +// }) +// .addDisposableTo(disposeBag) + } override func didReceiveMemoryWarning() { diff --git a/Example/Tests/BluetoothManagerSpec+Scanning.swift b/Example/Tests/BluetoothManagerSpec+Scanning.swift new file mode 100644 index 00000000..de66462f --- /dev/null +++ b/Example/Tests/BluetoothManagerSpec+Scanning.swift @@ -0,0 +1,239 @@ +// +// BluetoothScanningSpec.swift +// RxBluetoothKit +// +// Created by Kacper Harasim on 01.03.2016. +// Copyright © 2016 CocoaPods. All rights reserved. +// +import Quick +import Nimble + +import CoreBluetooth +import RxBluetoothKit +import RxTests +import RxSwift + +class BluetoothManagerSpecScanning : QuickSpec { + + override func spec() { + + var manager: BluetoothManager! + var fakeCentralManager: FakeCentralManager! + var testScheduler : TestScheduler! + var fakePeripheral: FakePeripheral! + + beforeEach { + fakePeripheral = FakePeripheral() + fakeCentralManager = FakeCentralManager() + manager = BluetoothManager(centralManager: fakeCentralManager) + testScheduler = TestScheduler(initialClock: 0, resolution: 1.0, simulateProcessingDelay: false) + } + + describe("scanning devices test") { + var scanObservers : [ScheduledObservable]! + + var scanCallObserver : TestableObserver<([CBUUID]?, [String:AnyObject]?)>! + var stopScanCallObserver : TestableObserver<()>! + + beforeEach { + scanCallObserver = testScheduler.createObserver(([CBUUID]?, [String:AnyObject]?)) + fakeCentralManager.scanForPeripheralsWithServicesTO = scanCallObserver + + stopScanCallObserver = testScheduler.createObserver(Void) + fakeCentralManager.stopScanTO = stopScanCallObserver + + scanObservers = [testScheduler.scheduleObservable { manager.scanForPeripherals([]) }] + } + + context("before scanning") { + + it("contains valid time for test scheduler") { + expect(testScheduler.clock).to(equal(0)) + } + + it("didn't call scan function for any observer") { + for observer in scanObservers { + expect(observer.events).to(beEmpty()) + } + } + } + + for (cberror, bleerror) in BluetoothError.invalidStateErrors { + context("when bluetooth manager has state: \(bleerror) and user is subscribed for scanning") { + beforeEach { + fakeCentralManager.state = cberror + testScheduler.advanceTo(scanObservers[0].subscribeTime) + } + + it("should return only an error") { + expect(scanObservers[0].events.count).to(equal(1)) + expectError(scanObservers[0].events[0].value, errorType: bleerror) + } + } + + context("when bluetooth manager has state: \(bleerror)") { + let firstScanTime = 550 + let errorPropagationTime = 600 + + beforeEach { + fakeCentralManager.state = .PoweredOn + let errors : [Recorded>] = [Recorded(time: errorPropagationTime, event: .Next(cberror))] + testScheduler.scheduleAt(errorPropagationTime - 1, action: {fakeCentralManager.state = cberror}) + fakeCentralManager.rx_didUpdateState = testScheduler.createHotObservable(errors).asObservable() + fakeCentralManager.rx_didDiscoverPeripheral = testScheduler.createHotObservable( + [Recorded(time: firstScanTime, event: .Next(FakePeripheral() as RxPeripheralType, [String:AnyObject](), NSNumber(double: 0)))]).asObservable() + } + + context("when first device is scanned") { + beforeEach { + testScheduler.advanceTo(firstScanTime) + } + + it ("it should find one scanned device") { + expect(scanObservers[0].events.count).to(equal(1)) + expect(scanObservers[0].events[0].value.isStopEvent).to(beFalse()) + } + } + + context("when error is propagated") { + beforeEach { + testScheduler.advanceTo(errorPropagationTime) + } + + it ("it should find one scanned device and emit error") { + expect(scanObservers[0].events.count).to(equal(2)) + expect(scanObservers[0].events[0].value.isStopEvent).to(beFalse()) + expectError(scanObservers[0].events[1].value, errorType: bleerror) + } + } + } + } + + context("when bluetooth manager is powered on and there are 3 devices to be scanned") { + var recordsTime : [Int]! + var recordsRSSI : [Double]! + + func expectRecordsFor(records: [Recorded>], rssis: [Double], file: String = __FILE__, line: UInt = __LINE__) { + expect(records.count, file: file, line: line).to(equal(rssis.count)) + for (i, record) in records.enumerate() { + expect(record.value.isStopEvent).to(beFalse()) + let args : ScannedPeripheral = record.value.element! + expect(args.RSSI).to(equal(rssis[i])) + } + } + + beforeEach { + fakeCentralManager.state = .PoweredOn + var scans : [Recorded>] = [] + + recordsRSSI = [] + recordsTime = [] + + for i in 0..<3 { + let time = 450 * (i+1) + let rssi = Double(i) * 10 + recordsTime.append(time) + recordsRSSI.append(rssi) + scans.append(Recorded(time: time, event: .Next(FakePeripheral() as RxPeripheralType, + [String:AnyObject](), + NSNumber(double: rssi)))) + } + + let scansObservable = testScheduler.createHotObservable(scans) + fakeCentralManager.rx_didDiscoverPeripheral = scansObservable.asObservable() + } + + context("before user is subscribed for scanning") { + beforeEach { + testScheduler.advanceTo(scanObservers[0].time.before.subscribeTime) + } + it("should not call scan function") { + expect(scanCallObserver.events.count).to(equal(0)) + } + } + + context("after first scanned device") { + beforeEach { + testScheduler.advanceTo(recordsTime[0]) + } + it("should call scan function once") { + expect(scanCallObserver.events.count).to(equal(1)) + } + it("should have only one scanned device registered") { + expectRecordsFor(scanObservers[0].events, rssis: [recordsRSSI[0]]) + } + } + context("after all detected scanned devices") { + beforeEach { + testScheduler.advanceTo(recordsTime.last!) + } + it("should call scan function once") { + expect(scanCallObserver.events.count).to(equal(1)) + } + it("should register all scanned devices detected before disposal and don't complete it's stream ") { + expectRecordsFor(scanObservers[0].events, rssis: [recordsRSSI[0], recordsRSSI[1]]) + } + } + + context("when there are two simultaneous users of bluetooth manager") { + + beforeEach { + let times = ObservableScheduleTimes(createTime: 150, subscribeTime: 600, disposeTime: 1400) + scanObservers.append(testScheduler.scheduleObservable(times, create: {manager.scanForPeripherals([])})) + } + + context("and only first user is subscribed and one peripheral was discovered") { + beforeEach { + testScheduler.advanceTo(recordsTime[0]) + } + it("should call scan function once only") { + expect(scanCallObserver.events.count).to(equal(1)) + } + it("should contain one event for first observer") { + expectRecordsFor(scanObservers[0].events, rssis: [recordsRSSI[0]]) + } + it("shoudn't contain any event for second observer") { + expect(scanObservers[1].events).to(beEmpty()) + } + } + + context("when two users are subscribed and two devices are discovered") { + beforeEach { + testScheduler.advanceTo(recordsTime[1]) + } + it("should call scan function once only") { + expect(scanCallObserver.events.count).to(equal(1)) + } + it("should emit two events for first user") { + expectRecordsFor(scanObservers[0].events, rssis: [recordsRSSI[0], recordsRSSI[1]]) + } + it("should emit one event for second user") { + expectRecordsFor(scanObservers[1].events, rssis: [recordsRSSI[1]]) + } + } + + context("when first user is unsubsribed and last scan is delivered") { + beforeEach { + testScheduler.advanceTo(recordsTime[2]) + } + it("shoudn't call stop scan function") { + expect(stopScanCallObserver.events).to(beEmpty()) + } + it("should emit two events for third user") { + expectRecordsFor(scanObservers[1].events, rssis: [recordsRSSI[1], recordsRSSI[2]]) + } + } + + context("when all users are unsubscribed") { + beforeEach { + testScheduler.advanceTo(scanObservers[1].time.after.disposeTime) + } + it("shoud call stop scan function") { + expect(stopScanCallObserver.events.count).to(equal(1)) + } + } + } + } + } + } +} \ No newline at end of file diff --git a/Example/Tests/BluetoothManagerSpec.swift b/Example/Tests/BluetoothManagerSpec.swift new file mode 100644 index 00000000..5d5f8dc6 --- /dev/null +++ b/Example/Tests/BluetoothManagerSpec.swift @@ -0,0 +1,483 @@ +// +// BluetoothManagerSpec.swift +// RxBluetoothKit +// +// Created by Kacper Harasim on 24.02.2016. +// + +import Quick +import Nimble +import CoreBluetooth +@testable +import RxBluetoothKit +import RxTests +import RxSwift + +//TODO: Add testable import + +class BluetoothManagerSpec : QuickSpec { + override func spec() { + + var manager: BluetoothManager! + var fakeCentralManager: FakeCentralManager! + var testScheduler : TestScheduler! + var fakePeripheral: FakePeripheral! + let statesWithErrors = BluetoothError.invalidStateErrors + + var nextTime: Int! + var errorTime: Int! + beforeEach { + fakePeripheral = FakePeripheral() + fakeCentralManager = FakeCentralManager() + manager = BluetoothManager(centralManager: fakeCentralManager) + testScheduler = TestScheduler(initialClock: 0, resolution: 1.0, simulateProcessingDelay: false) + nextTime = 230 + errorTime = 240 + } + + describe("retrieving peripherals") { + + var peripheralsObserver : ScheduledObservable<[Peripheral]>! + context("via identifiers") { + var uuids: [NSUUID]! + var retrieveWithIdentifiersCallObserver : TestableObserver<[NSUUID]>! + + beforeEach { + uuids = [NSUUID(), NSUUID()] + fakeCentralManager.retrievePeripheralsWithIdentifiersTO = testScheduler.createObserver([NSUUID]) + retrieveWithIdentifiersCallObserver = fakeCentralManager.retrievePeripheralsWithIdentifiersTO + peripheralsObserver = testScheduler.scheduleObservable { manager.retrievePeripheralsWithIdentifiers(uuids) } + fakeCentralManager.state = .PoweredOn + } + context("before subscription") { + it("should not call retrieve method") { + expect(retrieveWithIdentifiersCallObserver.events.count).to(equal(0)) + } + } + context("after subscription") { + beforeEach { + let peripherals: [Recorded>] = [Recorded(time: nextTime, event: .Next([fakePeripheral]))] + fakeCentralManager.retrievePeripheralsWithIdentifiersResult = testScheduler.createHotObservable(peripherals).asObservable() + testScheduler.advanceTo(250) + } + it("should call retrieve method on central manager") { + expect(retrieveWithIdentifiersCallObserver.events.count).to(equal(1)) + } + it("should call it with proper identifiers") { + expect(retrieveWithIdentifiersCallObserver.events[0].value.element! == uuids) + } + it("should receive event in return") { + expect(peripheralsObserver.events.count).to(equal(1)) + } + it("should retrieve next with peripherals table") { + expect(peripheralsObserver.events[0].value.element).toNot(beNil()) + } + it("should retrieve next with exactly one peripheral in table") { + expect(peripheralsObserver.events[0].value.element!.count).to(equal(1)) + } + it("should get proper peripheral") { + expect(peripheralsObserver.events[0].value.element![0].peripheral == fakePeripheral) + } + } + describe("error propagation in wrong bluetooth state") { + var state: CBCentralManagerState! + var error: BluetoothError! + for i in statesWithErrors { + beforeEach { + let (s, e) = i + state = s + error = e + } + context("before subscribe") { + it("should not call before subscribe") { + expect(retrieveWithIdentifiersCallObserver.events.count).to(equal(0)) + } + } + context("after subscribe and getting wrong state on start") { + beforeEach { + fakeCentralManager.state = state + testScheduler.advanceTo(250) + } + it("should get error") { + expect(peripheralsObserver.events.count > 0) + } + it("should get proper error") { + expectError(peripheralsObserver.events[0].value, errorType: error) + } + } + context("after subscribe and getting wrong state after function is called") { + beforeEach { + fakeCentralManager.state = .PoweredOn + let scans: [Recorded>] = [Recorded(time: errorTime, event: .Next(state))] + fakeCentralManager.rx_didUpdateState = testScheduler.createHotObservable(scans).asObservable() + testScheduler.advanceTo(250) + } + it("should call on central manager") { + expect(retrieveWithIdentifiersCallObserver.events.count == 1) + } + + it("should get error") { + expect(peripheralsObserver.events.count > 0) + } + it("should get proper error") { + expectError(peripheralsObserver.events[0].value, errorType: error) + } + } + } + } + } + context("via services uuids") { + var retrieveWithServicesCallObserver : TestableObserver<[CBUUID]>! + var cbuuids: [CBUUID]! + beforeEach { + cbuuids = [CBUUID()] + fakeCentralManager.retrieveConnectedPeripheralsWithServicesResult = Observable.just([fakePeripheral]) + fakeCentralManager.retrieveConnectedPeripheralsWithServicesTO = testScheduler.createObserver([CBUUID]) + retrieveWithServicesCallObserver = fakeCentralManager.retrieveConnectedPeripheralsWithServicesTO + peripheralsObserver = testScheduler.scheduleObservable { manager.retrieveConnectedPeripheralsWithServices(cbuuids)} + fakeCentralManager.state = .PoweredOn + } + context("before subscription") { + it("should not call retrieve method") { + expect(retrieveWithServicesCallObserver.events.count).to(equal(0)) + } + } + context("after subscription") { + beforeEach { + fakeCentralManager.retrievePeripheralsWithIdentifiersResult = Observable.just([fakePeripheral]) + testScheduler.advanceTo(250) + } + it("should call retrieve method on central manager") { + expect(retrieveWithServicesCallObserver.events.count).to(equal(1)) + } + it("should call with proper identifiers") { + expect(retrieveWithServicesCallObserver.events[0].value.element! == cbuuids) + } + it("should receive event") { + expect(peripheralsObserver.events.count).to(equal(1)) + } + it("should retrieve peripherals table") { + expect(peripheralsObserver.events[0].value.element).toNot(beNil()) + } + it("should retrieve exactly one peripheral in table") { + expect(peripheralsObserver.events[0].value.element!.count).to(equal(1)) + } + it("should retrieve given peripheral") { + expect(peripheralsObserver.events[0].value.element![0].peripheral == fakePeripheral) + } + } + describe("error propagation") { + var state: CBCentralManagerState! + var error: BluetoothError! + for i in statesWithErrors { + beforeEach { + let (s, e) = i + state = s + error = e + } + beforeEach { + } + context("before subscribe") { + it("should not call before subscribe") { + expect(retrieveWithServicesCallObserver.events.count).to(equal(0)) + } + } + context("after subscribe and getting wrong state on start") { + beforeEach { + fakeCentralManager.state = state + testScheduler.advanceTo(250) + } + it("should get error") { + expect(peripheralsObserver.events.count > 0) + } + it("should return p error") { + expectError(peripheralsObserver.events[0].value, errorType: error) + } + } + context("after subscribe and getting wrong state after function is called") { + + beforeEach { + fakeCentralManager.state = .PoweredOn + let scans: [Recorded>] = [Recorded(time: 240, event: .Next(state))] + fakeCentralManager.rx_didUpdateState = testScheduler.createHotObservable(scans).asObservable() + testScheduler.advanceTo(250) + } + it("should call method on central manager once") { + expect(retrieveWithServicesCallObserver.events.count == 1) + } + it("should get two events - peripheral and error") { + expect(peripheralsObserver.events.count == 2) + } + it("should return proper error") { + expectError(peripheralsObserver.events[1].value, errorType: error) + } + } + } + } + } + } + + describe("interaction with device") { + + var peripheral: Peripheral! + beforeEach { + peripheral = Peripheral(manager: manager, peripheral: fakePeripheral) + + } + + describe("Error propagation") { + + var state: CBCentralManagerState! + var error: BluetoothError! + + var peripheralObserver: ScheduledObservable! + var cancelConnectionObserver: TestableObserver! + + context("connecting while bluetooth dissalows connection") { + var connectObserver: TestableObserver<(RxPeripheralType, [String: AnyObject]?)>! + for i in statesWithErrors { + beforeEach { + fakeCentralManager.connectPeripheralOptionsTO = testScheduler.createObserver((RxPeripheralType, [String: AnyObject]?)) + connectObserver = fakeCentralManager.connectPeripheralOptionsTO + peripheralObserver = testScheduler.scheduleObservable { + manager.connectToPeripheral(peripheral) + } + let (s, e) = i + state = s + error = e + fakeCentralManager.state = state + } + context("before subscribe") { + it("should not call before subscribe") { + expect(connectObserver.events.count).to(equal(0)) + } + } + context("after subscribe and getting wrong state on start") { + beforeEach { + testScheduler.advanceTo(250) + } + it("should get event error") { + expect(peripheralObserver.events.count > 0) + } + it("should return proper error") { + expectError(peripheralObserver.events[0].value, errorType: error) + } + } + context("After subscribe and getting wrong state after function is called") { + + beforeEach { + fakeCentralManager.state = .PoweredOn + let scans: [Recorded>] = [Recorded(time: 240, event: .Next(state))] + fakeCentralManager.rx_didUpdateState = testScheduler.createHotObservable(scans).asObservable() + testScheduler.advanceTo(250) + } + it("Should call connect on central manager") { + expect(connectObserver.events.count == 1) + } + + it("should get events") { + expect(peripheralObserver.events.count > 0) + } + it("should return proper error after peripheral") { + expectError(peripheralObserver.events[1].value, errorType: error) + } + } + } + } + + context("disconnecting while bluetooth disallows") { + for i in statesWithErrors { + beforeEach { + fakeCentralManager.cancelPeripheralConnectionTO = testScheduler.createObserver(RxPeripheralType) + cancelConnectionObserver = fakeCentralManager.cancelPeripheralConnectionTO + peripheralObserver = testScheduler.scheduleObservable { + manager.cancelConnectionToPeripheral(peripheral) + } + let (s, e) = i + state = s + error = e + fakeCentralManager.state = state + } + context("before subscribe") { + it("should not call before subscribe") { + expect(cancelConnectionObserver.events.count).to(equal(0)) + } + } + context("after subscribe and getting wrong state on start") { + beforeEach { + testScheduler.advanceTo(250) + } + it("should return proper error") { + expectError(peripheralObserver.events[0].value, errorType: error) + } + } + context("After subscribe and getting wrong state after function is called") { + + beforeEach { + fakeCentralManager.state = .PoweredOn + let scans: [Recorded>] = [Recorded(time: 240, event: .Next(state))] + fakeCentralManager.rx_didUpdateState = testScheduler.createHotObservable(scans).asObservable() + testScheduler.advanceTo(250) + } + it("Should call connect on central manager") { + expect(cancelConnectionObserver.events.count == 1) + } + it("should return proper error") { + expectError(peripheralObserver.events[0].value, errorType: error) + } + } + + } + } + } + + describe("connecting to device with powered on BT ") { + var connectionTime : Int! + var peripheralObserver: ScheduledObservable! + var connectObserver: TestableObserver<(RxPeripheralType, [String: AnyObject]?)>! + + beforeEach { + fakeCentralManager.rx_didUpdateState = Observable.just(.PoweredOn) + fakeCentralManager.connectPeripheralOptionsTO = testScheduler.createObserver((RxPeripheralType, [String: AnyObject]?)) + connectObserver = fakeCentralManager.connectPeripheralOptionsTO + peripheralObserver = testScheduler.scheduleObservable { + manager.connectToPeripheral(peripheral) + } + fakeCentralManager.state = .PoweredOn + fakePeripheral.state = .Disconnected + connectionTime = peripheralObserver.time.after.subscribeTime + } + context("before subscribe") { + it("should not call connect before subscribe") { + expect(connectObserver.events.count).to(equal(0)) + } + } + context("after subscribe with connection success") { + beforeEach { + testScheduler.scheduleAt(connectionTime, action: {fakePeripheral.state = CBPeripheralState.Connected}) + fakeCentralManager.rx_didConnectPeripheral = + testScheduler.createHotObservable([Recorded(time: connectionTime, event: .Next(peripheral.peripheral))]).asObservable() + + testScheduler.advanceTo(connectionTime + 1) + } + + //Common to both success and fail end... + it("should call connect") { + expect(connectObserver.events.count).to(equal(1)) + } + it("Should call connect to proper peripheral") { + let (peripheralToConnect, _) = connectObserver.events[0].value.element! + expect(peripheralToConnect == peripheral.peripheral) + } + + + describe("connected peripheral") { + var peripheralConnected: Peripheral? + + beforeEach { + if let p = peripheralObserver.events.first?.value.element { + peripheralConnected = p + } + } + it("should return peripheral") { + expect(peripheralConnected).toNot(beNil()) + } + it("should return peripheral to which we're trying to connect to") { + expect(peripheralConnected!.peripheral == peripheral.peripheral) + } + + } + } + context("after subscribe with connection failed") { + beforeEach { + fakeCentralManager.rx_didConnectPeripheral = + testScheduler.createHotObservable([Recorded(time: connectionTime, event: .Next(peripheral.peripheral))]).asObservable() + fakeCentralManager.rx_didFailToConnectPeripheral = Observable.just((peripheral.peripheral, NSError(domain: "Error", code: 200, userInfo: nil))) + testScheduler.advanceTo(250) + } + + it("should call connect") { + expect(connectObserver.events.count).to(equal(1)) + } + it("should call connect with proper peripheral") { + let (peripheralToConnect, _) = connectObserver.events[0].value.element! + expect(peripheralToConnect == peripheral.peripheral) + } + + describe("error returned") { + it("should return event") { + expect(peripheralObserver.events.count).to(beGreaterThan(0)) + } + + it("Should return coneection failed error") { + expectError(peripheralObserver.events[0].value, errorType: BluetoothError.PeripheralConnectionFailed(peripheral, nil)) + } + } + } + } + describe("disconnecting from device with powered ON BT.") { + var peripheralObserver: ScheduledObservable! + var disconnectObserver: TestableObserver! + + + beforeEach { + fakeCentralManager.rx_didUpdateState = Observable.just(.PoweredOn) + fakeCentralManager.cancelPeripheralConnectionTO = testScheduler.createObserver(RxPeripheralType) + disconnectObserver = fakeCentralManager.cancelPeripheralConnectionTO + peripheralObserver = testScheduler.scheduleObservable { + manager.cancelConnectionToPeripheral(peripheral) + } + fakeCentralManager.state = .PoweredOn + } + context("Before subscribe") { + it("should not call disconnect before subscribe") { + expect(disconnectObserver.events.count).to(equal(0)) + } + } + context("after subscribe with disconnection success") { + beforeEach { + fakeCentralManager.rx_didDisconnectPeripheral = Observable.just((peripheral.peripheral, nil)) + testScheduler.advanceTo(250) + } + it("should call disconnect with proper peripheral") { + expect(disconnectObserver.events.count).to(equal(1)) + let peripheralToDisconnect = disconnectObserver.events[0].value.element! + expect(peripheralToDisconnect == peripheral.peripheral) + } + describe("disconnected peripheral") { + var peripheralDisconnected: Peripheral? + beforeEach { + if let p = peripheralObserver.events.first?.value.element { + peripheralDisconnected = p + } + } + it("should return peripheral") { + expect(peripheralDisconnected).toNot(beNil()) + } + it("should return peripheral from which we're trying to disconnect") { + expect(peripheralDisconnected!.peripheral == peripheral.peripheral) + } + } + } + context("when peripheral is disconnected with an error (disconnection executed by system)") { + beforeEach { + fakeCentralManager.rx_didDisconnectPeripheral = Observable.just((peripheral.peripheral, NSError(domain: "error", code: 200, userInfo: nil))) + testScheduler.advanceTo(peripheralObserver.time.after.subscribeTime) + } + it("should call disconnect with proper peripheral") { + expect(disconnectObserver.events.count).to(equal(1)) + let peripheralToDisconnect = disconnectObserver.events[0].value.element! + expect(peripheralToDisconnect == peripheral.peripheral) + } + it("Should return an peripheral event with completed stream") { + expect(peripheralObserver.events.count).to(equal(2)) + expect(peripheralObserver.events[0].value.element == peripheral) + expect(peripheralObserver.events[1].value == Event.Completed) + } + } + } + } + } +} + + diff --git a/Example/Tests/Fakes/FakeCentralManager.swift b/Example/Tests/Fakes/FakeCentralManager.swift new file mode 100644 index 00000000..a59e2603 --- /dev/null +++ b/Example/Tests/Fakes/FakeCentralManager.swift @@ -0,0 +1,54 @@ +// +// FakeCentralManager.swift +// RxBluetoothKit +// +// Created by Przemysław Lenart on 24/02/16. +// + +import Foundation + +import RxBluetoothKit +import RxSwift +import RxTests +import CoreBluetooth + +class FakeCentralManager: RxCentralManagerType { + + var rx_didUpdateState: Observable = Observable.never() + var rx_willRestoreState: Observable<[String : AnyObject]> = Observable.never() + var rx_didDiscoverPeripheral: Observable<(RxPeripheralType, [String : AnyObject], NSNumber)> = Observable.never() + var rx_didConnectPeripheral: Observable = Observable.never() + var rx_didFailToConnectPeripheral: Observable<(RxPeripheralType, NSError?)> = Observable.never() + var rx_didDisconnectPeripheral: Observable<(RxPeripheralType, NSError?)> = Observable.never() + + var state: CBCentralManagerState = CBCentralManagerState.PoweredOn + + var scanForPeripheralsWithServicesTO : TestableObserver<([CBUUID]?, [String:AnyObject]?)>? + func scanForPeripheralsWithServices(serviceUUIDs: [CBUUID]?, options: [String : AnyObject]?) { + scanForPeripheralsWithServicesTO?.onNext((serviceUUIDs, options)) + } + var connectPeripheralOptionsTO : TestableObserver<(RxPeripheralType, [String: AnyObject]?)>? + func connectPeripheral(peripheral: RxPeripheralType, options: [String : AnyObject]?) { + connectPeripheralOptionsTO?.onNext((peripheral, options)) + } + var cancelPeripheralConnectionTO : TestableObserver? + func cancelPeripheralConnection(peripheral: RxPeripheralType) { + cancelPeripheralConnectionTO?.onNext(peripheral) + } + var stopScanTO : TestableObserver<()>? + func stopScan() { + stopScanTO?.onNext(()) + } + var retrieveConnectedPeripheralsWithServicesTO : TestableObserver<[CBUUID]>? + var retrieveConnectedPeripheralsWithServicesResult : Observable<[RxPeripheralType]> = .never() + func retrieveConnectedPeripheralsWithServices(serviceUUIDs: [CBUUID]) -> Observable<[RxPeripheralType]> { + retrieveConnectedPeripheralsWithServicesTO?.onNext(serviceUUIDs) + return retrieveConnectedPeripheralsWithServicesResult + } + var retrievePeripheralsWithIdentifiersTO : TestableObserver<[NSUUID]>? + var retrievePeripheralsWithIdentifiersResult : Observable<[RxPeripheralType]> = .never() + func retrievePeripheralsWithIdentifiers(identifiers: [NSUUID]) -> Observable<[RxPeripheralType]> { + retrievePeripheralsWithIdentifiersTO?.onNext(identifiers) + return retrievePeripheralsWithIdentifiersResult + } +} \ No newline at end of file diff --git a/Example/Tests/Fakes/FakeCharacteristic.swift b/Example/Tests/Fakes/FakeCharacteristic.swift new file mode 100644 index 00000000..b31e3f9b --- /dev/null +++ b/Example/Tests/Fakes/FakeCharacteristic.swift @@ -0,0 +1,28 @@ +// +// FakeCharacteristic.swift +// RxBluetoothKit +// +// Created by Kacper Harasim on 25.02.2016. +// Copyright © 2016 CocoaPods. All rights reserved. +// + +import Foundation +import RxBluetoothKit +import CoreBluetooth + +class FakeCharacteristic: RxCharacteristicType { + + var uuid: CBUUID = CBUUID() + var value: NSData? = nil + var isNotifying: Bool = false + var properties: CBCharacteristicProperties = .Notify + + var descriptors: [RxDescriptorType]? = nil + + let service: RxServiceType + + init(service: RxServiceType) { + self.service = service + } + +} \ No newline at end of file diff --git a/Example/Tests/Fakes/FakeDescriptor.swift b/Example/Tests/Fakes/FakeDescriptor.swift new file mode 100644 index 00000000..7440ecad --- /dev/null +++ b/Example/Tests/Fakes/FakeDescriptor.swift @@ -0,0 +1,9 @@ +// +// FakeDescriptor.swift +// RxBluetoothKit +// +// Created by Kacper Harasim on 25.02.2016. +// Copyright © 2016 CocoaPods. All rights reserved. +// + +import Foundation diff --git a/Example/Tests/Fakes/FakePeripheral.swift b/Example/Tests/Fakes/FakePeripheral.swift new file mode 100644 index 00000000..0b0d2c99 --- /dev/null +++ b/Example/Tests/Fakes/FakePeripheral.swift @@ -0,0 +1,92 @@ +// +// FakePeripheral.swift +// RxBluetoothKit +// +// Created by Kacper Harasim on 24.02.2016. +// + + + +import Foundation +import RxSwift +import CoreBluetooth +import RxTests +import RxBluetoothKit +class FakePeripheral: RxPeripheralType { + + + var name: String? = nil + var state: CBPeripheralState = CBPeripheralState.Connected + var services: [RxServiceType]? = nil + var identifier: NSUUID = NSUUID() + + var RSSI: NSNumber? = nil + + + var rx_didUpdateName: Observable = .never() + var rx_didModifyServices: Observable<[RxServiceType]> = .never() + var rx_didReadRSSI: Observable<(NSNumber, NSError?)> = .never() + var rx_didDiscoverServices: Observable<([RxServiceType]?, NSError?)> = .never() + var rx_didDiscoverIncludedServicesForService: Observable<(RxServiceType, NSError?)> = .never() + var rx_didDiscoverCharacteristicsForService: Observable<(RxServiceType, NSError?)> = .never() + var rx_didUpdateValueForCharacteristic: Observable<(RxCharacteristicType, NSError?)> = .never() + var rx_didWriteValueForCharacteristic: Observable<(RxCharacteristicType, NSError?)> = .never() + var rx_didUpdateNotificationStateForCharacteristic: Observable<(RxCharacteristicType, NSError?)> = .never() + var rx_didDiscoverDescriptorsForCharacteristic: Observable<(RxCharacteristicType, NSError?)> = .never() + var rx_didUpdateValueForDescriptor: Observable<(RxDescriptorType, NSError?)> = .never() + var rx_didWriteValueForDescriptor: Observable<(RxDescriptorType, NSError?)> = .never() + + + + var discoverServicesTO: TestableObserver<[CBUUID]?>? + func discoverServices(serviceUUIDs: [CBUUID]?) { + discoverServicesTO?.onNext(serviceUUIDs) + } + + var discoverCharacteristicsTO: TestableObserver<([CBUUID]?, RxServiceType)>? + func discoverCharacteristics(characteristicUUIDs: [CBUUID]?, forService: RxServiceType) { + discoverCharacteristicsTO?.onNext((characteristicUUIDs, forService)) + } + + var discoverIncludedServicesTO: TestableObserver<([CBUUID]?, RxServiceType)>? + func discoverIncludedServices(includedServiceUUIDs: [CBUUID]?, forService service: RxServiceType) { + discoverIncludedServicesTO?.onNext((includedServiceUUIDs, service)) + } + + var readValueForCharacteristicTO: TestableObserver? + func readValueForCharacteristic(characteristic: RxCharacteristicType) { + readValueForCharacteristicTO?.onNext(characteristic) + } + + var writeValueForCharacteristicTypeTO: TestableObserver<(NSData, RxCharacteristicType, CBCharacteristicWriteType)>? + func writeValue(data: NSData, forCharacteristic characteristic: RxCharacteristicType, type: CBCharacteristicWriteType) { + writeValueForCharacteristicTypeTO?.onNext((data, characteristic, type)) + } + + var setNotifyValueForCharacteristicTO: TestableObserver<(Bool, RxCharacteristicType)>? + func setNotifyValue(enabled: Bool, forCharacteristic characteristic: RxCharacteristicType) { + setNotifyValueForCharacteristicTO?.onNext((enabled, characteristic)) + } + + var discoverDescriptorsForCharacteristicTO: TestableObserver? + func discoverDescriptorsForCharacteristic(characteristic: RxCharacteristicType) { + discoverDescriptorsForCharacteristicTO?.onNext(characteristic) + } + + var readValueForDescriptorTO: TestableObserver? + func readValueForDescriptor(descriptor: RxDescriptorType) { + readValueForDescriptorTO?.onNext(descriptor) + } + + var writeValueForDescriptorTO: TestableObserver<(NSData, RxDescriptorType)>? + func writeValue(data: NSData, forDescriptor descriptor: RxDescriptorType) { + writeValueForDescriptorTO?.onNext((data, descriptor)) + } + var readRSSITO: TestableObserver? + func readRSSI() { + readRSSITO?.onNext() + } + + + +} diff --git a/Example/Tests/Fakes/FakeService.swift b/Example/Tests/Fakes/FakeService.swift new file mode 100644 index 00000000..57046a85 --- /dev/null +++ b/Example/Tests/Fakes/FakeService.swift @@ -0,0 +1,20 @@ +// +// FakeService.swift +// RxBluetoothKit +// +// Created by Kacper Harasim on 25.02.2016. +// Copyright © 2016 CocoaPods. All rights reserved. +// + +import Foundation +import RxBluetoothKit +import CoreBluetooth + +class FakeService: RxServiceType { + + var uuid: CBUUID = CBUUID() + + var characteristics: [RxCharacteristicType]? = nil + var includedServices: [RxServiceType]? = nil + var isPrimary: Bool = false +} \ No newline at end of file diff --git a/Example/Tests/PeripheralSpec+Characteristics.swift b/Example/Tests/PeripheralSpec+Characteristics.swift new file mode 100644 index 00000000..ac926c89 --- /dev/null +++ b/Example/Tests/PeripheralSpec+Characteristics.swift @@ -0,0 +1,880 @@ +//// +//// PeripheralSpec.swift +//// RxBluetoothKit +//// +//// Created by Kacper Harasim on 24.02.2016. +//// +// +import Quick +import Nimble +@testable +import RxBluetoothKit +import RxTests +import RxSwift +import CoreBluetooth + + +class PeripheralSpecCharacteristics : QuickSpec { + + //Propagating errors too... + override func spec() { + + var manager: BluetoothManager! + var fakeCentralManager: FakeCentralManager! + var testScheduler : TestScheduler! + var fakePeripheral: FakePeripheral! + var peripheral: Peripheral! + var fakeService: FakeService! + var service: Service! + var fakeCharacteristic: FakeCharacteristic! + var characteristic: Characteristic! + let statesWithErrors = BluetoothError.invalidStateErrors + + var eventTime: Int! + var errorTime: Int! + beforeEach { + testScheduler = TestScheduler(initialClock: 0) + + fakePeripheral = FakePeripheral() + fakeCentralManager = FakeCentralManager() + manager = BluetoothManager(centralManager: fakeCentralManager) + peripheral = Peripheral(manager: manager, peripheral: fakePeripheral) + fakeService = FakeService() + service = Service(peripheral: peripheral, service: fakeService) + fakeCharacteristic = FakeCharacteristic(service: fakeService) + characteristic = Characteristic(characteristic: fakeCharacteristic, service: service) + eventTime = 230 + errorTime = 240 + } + + + describe("characteristic") { + var identifiers: [CBUUID]! + beforeEach { + identifiers = [CBUUID()] + } + describe("discover") { + var characteristicsDiscoverObserver: ScheduledObservable<[Characteristic]>! + var discoverCharacteristicsMethodObserver: TestableObserver<([CBUUID]?, RxServiceType)>! + beforeEach { + fakePeripheral.discoverCharacteristicsTO = testScheduler.createObserver(([CBUUID]?, RxServiceType)) + discoverCharacteristicsMethodObserver = fakePeripheral.discoverCharacteristicsTO + characteristicsDiscoverObserver = testScheduler.scheduleObservable { + return peripheral.discoverCharacteristics(identifiers, service: service) + } + } + context("before subscribe to discover") { + it("should not call discover before subscribe") { + expect(discoverCharacteristicsMethodObserver.events.count).to(equal(0)) + } + } + context("after subscribe to discover with success characteristic discovery ") { + var fakeChars: [FakeCharacteristic]! + beforeEach { + fakeChars = [FakeCharacteristic(service: fakeService)] + fakeService.characteristics = fakeChars.map { $0 as RxCharacteristicType } + + + let event: Event<(RxServiceType, NSError?)> = Event.Next(fakeService as RxServiceType, nil) + let service: [Recorded>] = [Recorded(time: eventTime, event: event)] + fakePeripheral.rx_didDiscoverCharacteristicsForService = testScheduler.createHotObservable(service).asObservable() + + testScheduler.advanceTo(250) + } + + it("should call discover") { + expect(discoverCharacteristicsMethodObserver.events.count).to(equal(1)) + } + + it("should call discover with proper uuids") { + expect(discoverCharacteristicsMethodObserver.events[0].value.element!.0).to(equal(identifiers)) + } + it("should call discover characteristic for proper service") { + expect(discoverCharacteristicsMethodObserver.events[0].value.element!.1 == fakeService) + } + describe("discovered characteristic") { + var characteristicsDiscovered: [Characteristic]? + + beforeEach { + if let c = characteristicsDiscoverObserver.events.first?.value.element { + characteristicsDiscovered = c + } + } + it("should return characteristics") { + expect(characteristicsDiscovered).toNot(beNil()) + } + it("should return proper characteristics") { + expect(characteristicsDiscovered!.map{ $0.characteristic } == fakeService.characteristics!) + } + } + } + context("after subscribe with failed discovery") { + beforeEach { + let tuple: (RxServiceType, NSError?) = (fakeService as RxServiceType, NSError(domain: "ERROR", code: 200, userInfo: nil)) + let event: Event<(RxServiceType, NSError?)> = .Next(tuple) + let service: [Recorded>] = [Recorded(time: eventTime, event: event)] + fakePeripheral.rx_didDiscoverCharacteristicsForService = testScheduler.createHotObservable(service).asObservable() + testScheduler.advanceTo(250) + } + describe("error returned") { + it("should return event") { + expect(characteristicsDiscoverObserver.events.count).to(equal(1)) + } + it("Should return coneection failed error") { + expectError(characteristicsDiscoverObserver.events[0].value, errorType: BluetoothError.CharacteristicsDiscoveryFailed(service, nil)) + } + } + } + context("error propagation ble wrong state") { + var state: CBCentralManagerState! + var error: BluetoothError! + for i in statesWithErrors { + beforeEach { + let (s, e) = i + state = s + error = e + } + + context("before subscribe") { + it("should not call discover before subscribe") { + expect(discoverCharacteristicsMethodObserver.events.count).to(equal(0)) + } + } + context("after subscribe and getting wrong state on start") { + beforeEach { + fakeCentralManager.rx_didUpdateState = Observable.just(state) + testScheduler.advanceTo(250) + } + it("should get event error") { + expect(characteristicsDiscoverObserver.events.count > 0) + } + it("should return proper error") { + expectError(characteristicsDiscoverObserver.events[0].value, errorType: error) + } + } + + context("after subscribe and getting wrong state after function is called") { + beforeEach { + fakeCentralManager.state = .PoweredOn + let scans: [Recorded>] = [Recorded(time: errorTime, event: .Next(state))] + fakeCentralManager.rx_didUpdateState = testScheduler.createHotObservable(scans).asObservable() + testScheduler.advanceTo(250) + } + it("Should call connect on central manager") { + expect(discoverCharacteristicsMethodObserver.events.count == 1) + } + beforeEach { + fakeCentralManager.rx_didUpdateState = Observable.just(state) + } + it("should get event error") { + expect(characteristicsDiscoverObserver.events.count > 0) + } + it("should return proper error") { + expectError(characteristicsDiscoverObserver.events[0].value, errorType: error) + } + } + } + } + context("error propagation device while device disconnects at some time") { + context("before subscribe") { + it("should not call before subscribe") { + expect(discoverCharacteristicsMethodObserver.events.count).to(equal(0)) + } + } + context("after subscribe and getting wrong state on start by event") { + beforeEach { + fakePeripheral.state = .Disconnected + testScheduler.advanceTo(250) + } + it("should get event error") { + expect(characteristicsDiscoverObserver.events.count > 0) + } + it("should return proper error") { + expectError(characteristicsDiscoverObserver.events[0].value, errorType: BluetoothError.PeripheralDisconnected(peripheral, nil)) + } + } + context("after subscribe and getting wrong state after function is called") { + beforeEach { + //State is good on start. + fakeCentralManager.state = .PoweredOn + fakePeripheral.state = .Connected + //Different types of errors :: + let event: Event<(RxPeripheralType, NSError?)> = Event.Next(fakePeripheral as RxPeripheralType, nil) + let scans: [Recorded>] = [Recorded(time: errorTime, event: event)] + fakeCentralManager.rx_didDisconnectPeripheral = testScheduler.createHotObservable(scans).asObservable() + testScheduler.advanceTo(250) + } + it("should call discover characteristics method") { + expect(discoverCharacteristicsMethodObserver.events.count == 1) + } + context("getting wrong state in the middle of discover") { + it("should get event error") { + expect(characteristicsDiscoverObserver.events.count > 0) + } + it("should return proper error") { + expectError(characteristicsDiscoverObserver.events[0].value, errorType: BluetoothError.PeripheralDisconnected(peripheral, nil)) + } + } + } + } + } + + describe("writing to characteristic") { + + var data: NSData! + let writeType: CBCharacteristicWriteType = CBCharacteristicWriteType.WithResponse + var characteristicObserver: ScheduledObservable! + var writeValueForCharacteristicMethodObserver: TestableObserver<(NSData, RxCharacteristicType, CBCharacteristicWriteType)>! + + beforeEach { + fakePeripheral.writeValueForCharacteristicTypeTO = testScheduler.createObserver((NSData, RxCharacteristicType, CBCharacteristicWriteType)) + writeValueForCharacteristicMethodObserver = fakePeripheral.writeValueForCharacteristicTypeTO + data = "A".dataUsingEncoding(NSUTF8StringEncoding) + characteristicObserver = testScheduler.scheduleObservable { + return peripheral.writeValue(data, forCharacteristic: characteristic, type: writeType) + } + } + + context("before subscribe") { + it("should not call write before subscribe") { + expect(writeValueForCharacteristicMethodObserver.events.count).to(equal(0)) + } + } + context("after subscribe with success write") { + beforeEach { + + let disconnect: [Recorded>] = [Recorded(time: errorTime, event: .Next(fakePeripheral as RxPeripheralType, nil))] + fakeCentralManager.rx_didDisconnectPeripheral = testScheduler.createHotObservable(disconnect).asObservable() + + let write: [Recorded>] = [Recorded(time: eventTime, event: .Next(fakeCharacteristic as RxCharacteristicType, nil))] + fakePeripheral.rx_didWriteValueForCharacteristic = testScheduler.createHotObservable(write).asObservable() + testScheduler.advanceTo(250) + } + + it("should call write") { + expect(writeValueForCharacteristicMethodObserver.events.count).to(equal(1)) + } + it("should call write with proper data") { + expect(writeValueForCharacteristicMethodObserver.events[0].value.element!.0).to(equal(data)) + } + it("should call write to proper characteristic") { + expect(writeValueForCharacteristicMethodObserver.events[0].value.element!.1 == fakeCharacteristic) + } + it("should call write with proper write type") { + expect(writeValueForCharacteristicMethodObserver.events[0].value.element!.2 == writeType) + + } + + describe("result of call") { + var characteristicWrittenTo: Characteristic? + + beforeEach { + if let c = characteristicObserver.events.first?.value.element { + characteristicWrittenTo = c + } + } + it("should return characteristic") { + expect(characteristicWrittenTo).toNot(beNil()) + } + it("should return proper characteristic") { + expect(characteristicWrittenTo!.characteristic == fakeCharacteristic) + } + } + } + context("after subscribe with failed write") { + + beforeEach { + let tuple: (RxCharacteristicType, NSError?) = (fakeCharacteristic as RxCharacteristicType, NSError(domain: "ERROR", code: 200, userInfo: nil)) + let write: [Recorded>] = [Recorded(time: eventTime, event: .Next(tuple))] + fakePeripheral.rx_didWriteValueForCharacteristic = testScheduler.createHotObservable(write).asObservable() + testScheduler.advanceTo(250) + } + describe("error returned") { + it("should return event") { + expect(characteristicObserver.events.count).to(equal(1)) + } + it("Should return coneection failed error") { + expectError(characteristicObserver.events[0].value, errorType: BluetoothError.CharacteristicWriteFailed(characteristic, nil)) + } + } + } + context("error propagation ble wrong state") { + var state: CBCentralManagerState! + var error: BluetoothError! + for i in statesWithErrors { + beforeEach { + let (s, e) = i + state = s + error = e + } + + context("before subscribe") { + it("should not call before subscribe") { + expect(writeValueForCharacteristicMethodObserver.events.count).to(equal(0)) + } + } + context("after subscribe and getting wrong state on start") { + beforeEach { + fakeCentralManager.rx_didUpdateState = Observable.just(state) + testScheduler.advanceTo(250) + } + it("should get event error") { + expect(characteristicObserver.events.count > 0) + } + it("should return proper error") { + expectError(characteristicObserver.events[0].value, errorType: error) + } + } + + context("After subscribe and getting wrong state after function is called") { + beforeEach { + fakeCentralManager.state = .PoweredOn + let scans: [Recorded>] = [Recorded(time: 240, event: .Next(state))] + fakeCentralManager.rx_didUpdateState = testScheduler.createHotObservable(scans).asObservable() + testScheduler.advanceTo(250) + } + it("Should call connect on central manager") { + expect(writeValueForCharacteristicMethodObserver.events.count == 1) + } + beforeEach { + fakeCentralManager.rx_didUpdateState = Observable.just(state) + } + it("should get event error") { + expect(characteristicObserver.events.count > 0) + } + it("should return proper error") { + expectError(characteristicObserver.events[0].value, errorType: error) + } + } + } + } + context("error propagation device disconnect") { + context("before subscribe") { + it("should not call before subscribe") { + expect(writeValueForCharacteristicMethodObserver.events.count).to(equal(0)) + } + } + + context("after subscribe and getting wrong state on start by event") { + beforeEach { + fakePeripheral.state = .Disconnected + testScheduler.advanceTo(250) + } + it("should get event error") { + expect(characteristicObserver.events.count > 0) + } + it("should return proper error") { + expectError(characteristicObserver.events[0].value, errorType: BluetoothError.PeripheralDisconnected(peripheral, nil)) + } + } + context("After subscribe and getting wrong state after function is called") { + beforeEach { + //State is good on start. + fakeCentralManager.state = .PoweredOn + fakePeripheral.state = .Connected + //Different types of errors :: + let event: Event<(RxPeripheralType, NSError?)> = Event.Next(fakePeripheral as RxPeripheralType, nil) + let scans: [Recorded>] = [Recorded(time: 240, event: event)] + fakeCentralManager.rx_didDisconnectPeripheral = testScheduler.createHotObservable(scans).asObservable() + testScheduler.advanceTo(250) + } + it("Should call discover services") { + expect(writeValueForCharacteristicMethodObserver.events.count == 1) + } + context("getting wrong state in the middle of discover") { + beforeEach { + fakeCentralManager.rx_didDisconnectPeripheral = Observable.just((fakePeripheral, nil)) + } + it("should get event error") { + expect(characteristicObserver.events.count > 0) + } + it("should return proper error") { + expectError(characteristicObserver.events[0].value, errorType: BluetoothError.PeripheralDisconnected(peripheral, nil)) + } + } + } + } + } + + describe("reading") { + var characteristicObserver: ScheduledObservable! + var readValueForCharacteristicMethodObserver: TestableObserver<(RxCharacteristicType)>! + + beforeEach { + fakePeripheral.readValueForCharacteristicTO = testScheduler.createObserver(RxCharacteristicType) + readValueForCharacteristicMethodObserver = fakePeripheral.readValueForCharacteristicTO + characteristicObserver = testScheduler.scheduleObservable { + return peripheral.readValueForCharacteristic(characteristic) + } + } + + context("before subscribe") { + it("should not call write before subscribe") { + expect(readValueForCharacteristicMethodObserver.events.count).to(equal(0)) + } + } + context("after subscribe with success read") { + beforeEach { + fakePeripheral.rx_didUpdateValueForCharacteristic = Observable.just((fakeCharacteristic, nil)) + testScheduler.advanceTo(250) + } + + it("should call read") { + expect(readValueForCharacteristicMethodObserver.events.count).to(equal(1)) + } + it("should call read with proper characteristic") { + expect(readValueForCharacteristicMethodObserver.events[0].value.element! == fakeCharacteristic) + } + + describe("result of call") { + var characteristicToRead: Characteristic? + + beforeEach { + if let c = characteristicObserver.events.first?.value.element { + characteristicToRead = c + } + } + it("should return characteristic") { + expect(characteristicToRead).toNot(beNil()) + } + it("should return proper characteristic") { + expect(characteristicToRead!.characteristic == fakeCharacteristic) + } + } + } + context("after subscribe with failed read") { + beforeEach { + fakePeripheral.rx_didUpdateValueForCharacteristic = Observable.just((characteristic.characteristic, NSError(domain: "ERROR", code: 200, userInfo: nil))) + testScheduler.advanceTo(250) + } + describe("error returned") { + it("should return event") { + expect(characteristicObserver.events.count).to(equal(1)) + } + it("Should return coneection failed error") { + expectError(characteristicObserver.events[0].value, errorType: BluetoothError.CharacteristicReadFailed(characteristic, nil)) + } + } + } + context("error propagation ble wrong state") { + var state: CBCentralManagerState! + var error: BluetoothError! + for i in statesWithErrors { + beforeEach { + let (s, e) = i + state = s + error = e + } + + context("before subscribe") { + it("should not call before subscribe") { + expect(readValueForCharacteristicMethodObserver.events.count).to(equal(0)) + } + } + context("after subscribe and getting wrong state on start") { + beforeEach { + fakeCentralManager.rx_didUpdateState = Observable.just(state) + testScheduler.advanceTo(250) + } + it("should get event error") { + expect(characteristicObserver.events.count > 0) + } + it("should return proper error") { + expectError(characteristicObserver.events[0].value, errorType: error) + } + } + + context("After subscribe and getting wrong state after function is called") { + beforeEach { + fakeCentralManager.state = .PoweredOn + let scans: [Recorded>] = [Recorded(time: 240, event: .Next(state))] + fakeCentralManager.rx_didUpdateState = testScheduler.createHotObservable(scans).asObservable() + testScheduler.advanceTo(250) + } + it("Should call connect on central manager") { + expect(readValueForCharacteristicMethodObserver.events.count == 1) + } + beforeEach { + fakeCentralManager.rx_didUpdateState = Observable.just(state) + } + it("should get event error") { + expect(characteristicObserver.events.count > 0) + } + it("should return proper error") { + expectError(characteristicObserver.events[0].value, errorType: error) + } + } + } + } + context("error propagation device disconnect") { + context("before subscribe") { + it("should not call before subscribe") { + expect(readValueForCharacteristicMethodObserver.events.count).to(equal(0)) + } + } + + context("after subscribe and getting wrong state on start by event") { + beforeEach { + fakePeripheral.state = .Disconnected + testScheduler.advanceTo(250) + } + it("should get event error") { + expect(characteristicObserver.events.count > 0) + } + it("should return proper error") { + expectError(characteristicObserver.events[0].value, errorType: BluetoothError.PeripheralDisconnected(peripheral, nil)) + } + } + context("After subscribe and getting wrong state after function is called") { + beforeEach { + //State is good on start. + fakeCentralManager.state = .PoweredOn + fakePeripheral.state = .Connected + //Different types of errors :: + let event: Event<(RxPeripheralType, NSError?)> = Event.Next(fakePeripheral as RxPeripheralType, nil) + let scans: [Recorded>] = [Recorded(time: 240, event: event)] + fakeCentralManager.rx_didDisconnectPeripheral = testScheduler.createHotObservable(scans).asObservable() + testScheduler.advanceTo(250) + } + it("Should call discover services") { + expect(readValueForCharacteristicMethodObserver.events.count == 1) + } + context("getting wrong state in the middle of discover") { + beforeEach { + fakeCentralManager.rx_didDisconnectPeripheral = Observable.just((fakePeripheral, nil)) + } + it("should get event error") { + expect(characteristicObserver.events.count > 0) + } + it("should return proper error") { + expectError(characteristicObserver.events[0].value, errorType: BluetoothError.PeripheralDisconnected(peripheral, nil)) + } + } + } + } + } + describe("set notify") { + + var characteristicObserver: ScheduledObservable! + var setNotifyCharacteristicMethodObserver: TestableObserver<(Bool, RxCharacteristicType)>! + beforeEach { + + fakePeripheral.setNotifyValueForCharacteristicTO = testScheduler.createObserver((Bool, RxCharacteristicType)) + setNotifyCharacteristicMethodObserver = fakePeripheral.setNotifyValueForCharacteristicTO + characteristicObserver = testScheduler.scheduleObservable { + //TODO: Think about checking enabled value - it could be self test check? + return peripheral.setNotifyValue(true, forCharacteristic: characteristic) + } + } + + context("before subscribe") { + it("should not set any value before subscribe") { + expect(setNotifyCharacteristicMethodObserver.events.count).to(equal(0)) + } + } + context("after subscribe with success read") { + beforeEach { + //TODO: Think about checking enabled value - it could be self test check? + fakePeripheral.rx_didUpdateNotificationStateForCharacteristic = Observable.just((fakeCharacteristic, nil)) + testScheduler.advanceTo(250) + } + + it("should call read") { + expect(setNotifyCharacteristicMethodObserver.events.count).to(equal(1)) + } + it("should call read with proper characteristic") { + expect(setNotifyCharacteristicMethodObserver.events[0].value.element!.1 == fakeCharacteristic) + } + + describe("result of call") { + var characteristicToRead: Characteristic? + + beforeEach { + if let c = characteristicObserver.events.first?.value.element { + characteristicToRead = c + } + } + it("should return characteristic") { + expect(characteristicToRead).toNot(beNil()) + } + it("should return proper characteristic") { + expect(characteristicToRead!.characteristic == fakeCharacteristic) + } + } + } + context("after subscribe with failed read") { + beforeEach { + fakePeripheral.rx_didUpdateNotificationStateForCharacteristic = Observable.just((characteristic.characteristic, NSError(domain: "ERROR", code: 200, userInfo: nil))) + testScheduler.advanceTo(250) + } + describe("error returned") { + it("should return event") { + expect(characteristicObserver.events.count).to(equal(1)) + } + it("Should return coneection failed error") { + expectError(characteristicObserver.events[0].value, errorType: BluetoothError.CharacteristicNotifyChangeFailed(characteristic, nil)) + } + } + } + context("error propagation ble wrong state") { + var state: CBCentralManagerState! + var error: BluetoothError! + for i in statesWithErrors { + beforeEach { + let (s, e) = i + state = s + error = e + } + + context("before subscribe") { + it("should not call before subscribe") { + expect(setNotifyCharacteristicMethodObserver.events.count).to(equal(0)) + } + } + context("after subscribe and getting wrong state on start") { + beforeEach { + fakeCentralManager.rx_didUpdateState = Observable.just(state) + testScheduler.advanceTo(250) + } + it("should get event error") { + expect(characteristicObserver.events.count > 0) + } + it("should return proper error") { + expectError(characteristicObserver.events[0].value, errorType: error) + } + } + + context("After subscribe and getting wrong state after function is called") { + beforeEach { + fakeCentralManager.state = .PoweredOn + let scans: [Recorded>] = [Recorded(time: 240, event: .Next(state))] + fakeCentralManager.rx_didUpdateState = testScheduler.createHotObservable(scans).asObservable() + testScheduler.advanceTo(250) + } + it("Should call connect on central manager") { + expect(setNotifyCharacteristicMethodObserver.events.count == 1) + } + beforeEach { + fakeCentralManager.rx_didUpdateState = Observable.just(state) + } + it("should get event error") { + expect(characteristicObserver.events.count > 0) + } + it("should return proper error") { + expectError(characteristicObserver.events[0].value, errorType: error) + } + } + } + } + context("error propagation device disconnect") { + context("before subscribe") { + it("should not call before subscribe") { + expect(setNotifyCharacteristicMethodObserver.events.count).to(equal(0)) + } + } + + context("after subscribe and getting wrong state on start by event") { + beforeEach { + fakePeripheral.state = .Disconnected + testScheduler.advanceTo(250) + } + it("should get event error") { + expect(characteristicObserver.events.count > 0) + } + it("should return proper error") { + expectError(characteristicObserver.events[0].value, errorType: BluetoothError.PeripheralDisconnected(peripheral, nil)) + } + } + context("After subscribe and getting wrong state after function is called") { + beforeEach { + //State is good on start. + fakeCentralManager.state = .PoweredOn + fakePeripheral.state = .Connected + //Different types of errors :: + let event: Event<(RxPeripheralType, NSError?)> = Event.Next(fakePeripheral as RxPeripheralType, nil) + let scans: [Recorded>] = [Recorded(time: 240, event: event)] + fakeCentralManager.rx_didDisconnectPeripheral = testScheduler.createHotObservable(scans).asObservable() + testScheduler.advanceTo(250) + } + it("Should call discover services") { + expect(setNotifyCharacteristicMethodObserver.events.count == 1) + } + context("getting wrong state in the middle of discover") { + beforeEach { + fakeCentralManager.rx_didDisconnectPeripheral = Observable.just((fakePeripheral, nil)) + } + it("should get event error") { + expect(characteristicObserver.events.count > 0) + } + it("should return proper error") { + expectError(characteristicObserver.events[0].value, errorType: BluetoothError.PeripheralDisconnected(peripheral, nil)) + } + } + } + } + + } + describe("monitor updates") { + + var characteristicObserver: ScheduledObservable! + var setNotifyCharacteristicMethodObserver: TestableObserver<(Bool, RxCharacteristicType)>! + beforeEach { + + fakePeripheral.setNotifyValueForCharacteristicTO = testScheduler.createObserver((Bool, RxCharacteristicType)) + setNotifyCharacteristicMethodObserver = fakePeripheral.setNotifyValueForCharacteristicTO + characteristicObserver = testScheduler.scheduleObservable { + //TODO: Think about checking enabled value - it could be self test check? + return peripheral.setNotifyValue(true, forCharacteristic: characteristic) + } + } + + context("before subscribe") { + it("should not set any value before subscribe") { + expect(setNotifyCharacteristicMethodObserver.events.count).to(equal(0)) + } + } + context("after subscribe with success read") { + beforeEach { + //TODO: Think about checking enabled value - it could be self test check? + fakePeripheral.rx_didUpdateNotificationStateForCharacteristic = Observable.just((fakeCharacteristic, nil)) + testScheduler.advanceTo(250) + } + + it("should call read") { + expect(setNotifyCharacteristicMethodObserver.events.count).to(equal(1)) + } + it("should call read with proper characteristic") { + expect(setNotifyCharacteristicMethodObserver.events[0].value.element!.1 == fakeCharacteristic) + } + + describe("result of call") { + var characteristicToRead: Characteristic? + + beforeEach { + if let c = characteristicObserver.events.first?.value.element { + characteristicToRead = c + } + } + it("should return characteristic") { + expect(characteristicToRead).toNot(beNil()) + } + it("should return proper characteristic") { + expect(characteristicToRead!.characteristic == fakeCharacteristic) + } + } + } + context("after subscribe with failed read") { + beforeEach { + fakePeripheral.rx_didUpdateNotificationStateForCharacteristic = Observable.just((characteristic.characteristic, NSError(domain: "ERROR", code: 200, userInfo: nil))) + testScheduler.advanceTo(250) + } + describe("error returned") { + it("should return event") { + expect(characteristicObserver.events.count).to(equal(1)) + } + it("Should return coneection failed error") { + expectError(characteristicObserver.events[0].value, errorType: BluetoothError.CharacteristicNotifyChangeFailed(characteristic, nil)) + } + } + } + context("error propagation ble wrong state") { + var state: CBCentralManagerState! + var error: BluetoothError! + for i in statesWithErrors { + beforeEach { + let (s, e) = i + state = s + error = e + } + + context("before subscribe") { + it("should not call before subscribe") { + expect(setNotifyCharacteristicMethodObserver.events.count).to(equal(0)) + } + } + context("after subscribe and getting wrong state on start") { + beforeEach { + fakeCentralManager.rx_didUpdateState = Observable.just(state) + testScheduler.advanceTo(250) + } + it("should get event error") { + expect(characteristicObserver.events.count > 0) + } + it("should return proper error") { + expectError(characteristicObserver.events[0].value, errorType: error) + } + } + + context("After subscribe and getting wrong state after function is called") { + beforeEach { + fakeCentralManager.state = .PoweredOn + let scans: [Recorded>] = [Recorded(time: 240, event: .Next(state))] + fakeCentralManager.rx_didUpdateState = testScheduler.createHotObservable(scans).asObservable() + testScheduler.advanceTo(250) + } + it("Should call connect on central manager") { + expect(setNotifyCharacteristicMethodObserver.events.count == 1) + } + beforeEach { + fakeCentralManager.rx_didUpdateState = Observable.just(state) + } + it("should get event error") { + expect(characteristicObserver.events.count > 0) + } + it("should return proper error") { + expectError(characteristicObserver.events[0].value, errorType: error) + } + } + } + } + context("error propagation device disconnect") { + context("before subscribe") { + it("should not call before subscribe") { + expect(setNotifyCharacteristicMethodObserver.events.count).to(equal(0)) + } + } + + context("after subscribe and getting wrong state on start by event") { + beforeEach { + fakePeripheral.state = .Disconnected + testScheduler.advanceTo(250) + } + it("should get event error") { + expect(characteristicObserver.events.count > 0) + } + it("should return proper error") { + expectError(characteristicObserver.events[0].value, errorType: BluetoothError.PeripheralDisconnected(peripheral, nil)) + } + } + context("After subscribe and getting wrong state after function is called") { + beforeEach { + //State is good on start. + fakeCentralManager.state = .PoweredOn + fakePeripheral.state = .Connected + //Different types of errors :: + let event: Event<(RxPeripheralType, NSError?)> = Event.Next(fakePeripheral as RxPeripheralType, nil) + let scans: [Recorded>] = [Recorded(time: 240, event: event)] + fakeCentralManager.rx_didDisconnectPeripheral = testScheduler.createHotObservable(scans).asObservable() + testScheduler.advanceTo(250) + } + it("Should call discover services") { + expect(setNotifyCharacteristicMethodObserver.events.count == 1) + } + context("getting wrong state in the middle of discover") { + beforeEach { + fakeCentralManager.rx_didDisconnectPeripheral = Observable.just((fakePeripheral, nil)) + } + it("should get event error") { + expect(characteristicObserver.events.count > 0) + } + it("should return proper error") { + expectError(characteristicObserver.events[0].value, errorType: BluetoothError.PeripheralDisconnected(peripheral, nil)) + } + } + } + } + + } + } + } +} diff --git a/Example/Tests/PeripheralSpec+Services.swift b/Example/Tests/PeripheralSpec+Services.swift new file mode 100644 index 00000000..b94e70d2 --- /dev/null +++ b/Example/Tests/PeripheralSpec+Services.swift @@ -0,0 +1,371 @@ +// +// PeripheralSpec+Services.swift +// RxBluetoothKit +// +// Created by Kacper Harasim on 02.03.2016. +// Copyright © 2016 CocoaPods. All rights reserved. +// + +import Quick +import Nimble +@testable +import RxBluetoothKit +import RxTests +import RxSwift +import CoreBluetooth + + +class PeripheralSpecServices : QuickSpec { + + override func spec() { + + var manager: BluetoothManager! + var fakeCentralManager: FakeCentralManager! + var testScheduler : TestScheduler! + var fakePeripheral: FakePeripheral! + var peripheral: Peripheral! + var fakeService: FakeService! + + let statesWithErrors = BluetoothError.invalidStateErrors + + beforeEach { + testScheduler = TestScheduler(initialClock: 0) + + fakePeripheral = FakePeripheral() + fakeCentralManager = FakeCentralManager() + manager = BluetoothManager(centralManager: fakeCentralManager) + peripheral = Peripheral(manager: manager, peripheral: fakePeripheral) + fakeService = FakeService() + } + + describe("services") { + + var servicesObserver: ScheduledObservable<[Service]>! + var cbuuids: [CBUUID]! + + beforeEach { + cbuuids = [CBUUID()] + } + + describe("discover services") { + var discoverServicesMethodObserver: TestableObserver<[CBUUID]?>! + + beforeEach { + fakePeripheral.discoverServicesTO = testScheduler.createObserver([CBUUID]?) + discoverServicesMethodObserver = fakePeripheral.discoverServicesTO + servicesObserver = testScheduler.scheduleObservable { + peripheral.discoverServices(cbuuids) + } + } + + context("before subscribe") { + it("should not call discover before subscribe") { + expect(discoverServicesMethodObserver.events.count).to(equal(0)) + } + } + context("after subscribe with success discovery ") { + beforeEach { + fakePeripheral.rx_didDiscoverServices = Observable.just(([fakeService], nil)) + testScheduler.advanceTo(250) + } + + it("should call discover") { + expect(discoverServicesMethodObserver.events.count).to(equal(1)) + } + + it("should call discover with proper uuids") { + expect(discoverServicesMethodObserver.events[0].value.element!).to(equal(cbuuids)) + } + describe("discovered service") { + var servicesDiscovered: [Service]? + + beforeEach { + if let s = servicesObserver.events.first?.value.element { + servicesDiscovered = s + } + } + it("should return service") { + expect(servicesDiscovered).toNot(beNil()) + } + it("should return proper service") { + expect(servicesDiscovered!.map { $0.service } == [fakeService]) + } + } + } + context("after subscribe with failed discovery") { + + beforeEach { + fakePeripheral.rx_didDiscoverServices = Observable.just((nil, NSError(domain: "ERROR", code: 200, userInfo: nil))) + testScheduler.advanceTo(250) + } + describe("error returned") { + it("should return event") { + expect(servicesObserver.events.count).to(equal(1)) + } + it("Should return coneection failed error") { + expectError(servicesObserver.events[0].value, errorType: BluetoothError.ServicesDiscoveryFailed(peripheral, nil)) + } + } + } + context("error propagation ble wrong state") { + var state: CBCentralManagerState! + var error: BluetoothError! + for i in statesWithErrors { + beforeEach { + let (s, e) = i + state = s + error = e + } + + context("before subscribe") { + it("should not call before subscribe") { + expect(discoverServicesMethodObserver.events.count).to(equal(0)) + } + } + context("after subscribe and getting wrong state on start") { + beforeEach { + fakeCentralManager.rx_didUpdateState = Observable.just(state) + testScheduler.advanceTo(250) + } + it("should get event error") { + expect(servicesObserver.events.count > 0) + } + it("should return proper error") { + expectError(servicesObserver.events[0].value, errorType: error) + } + } + + context("After subscribe and getting wrong state after function is called") { + beforeEach { + fakeCentralManager.state = .PoweredOn + let scans: [Recorded>] = [Recorded(time: 240, event: .Next(state))] + fakeCentralManager.rx_didUpdateState = testScheduler.createHotObservable(scans).asObservable() + testScheduler.advanceTo(250) + } + it("Should call connect on central manager") { + expect(discoverServicesMethodObserver.events.count == 1) + } + beforeEach { + fakeCentralManager.rx_didUpdateState = Observable.just(state) + } + it("should get event error") { + expect(servicesObserver.events.count > 0) + } + it("should return proper error") { + expectError(servicesObserver.events[0].value, errorType: error) + } + } + } + } + context("error propagation device disconnect") { + context("before subscribe") { + it("should not call before subscribe") { + expect(discoverServicesMethodObserver.events.count).to(equal(0)) + } + } + + context("after subscribe and getting wrong state on start by event") { + beforeEach { + fakePeripheral.state = .Disconnected + testScheduler.advanceTo(250) + } + it("should get event error") { + expect(servicesObserver.events.count > 0) + } + it("should return proper error") { + expectError(servicesObserver.events[0].value, errorType: BluetoothError.PeripheralDisconnected(peripheral, nil)) + } + } + context("After subscribe and getting wrong state after function is called") { + beforeEach { + //State is good on start. + fakeCentralManager.state = .PoweredOn + fakePeripheral.state = .Connected + //Different types of errors :: + let event: Event<(RxPeripheralType, NSError?)> = Event.Next(fakePeripheral as RxPeripheralType, nil) + let scans: [Recorded>] = [Recorded(time: 240, event: event)] + fakeCentralManager.rx_didDisconnectPeripheral = testScheduler.createHotObservable(scans).asObservable() + testScheduler.advanceTo(250) + } + it("Should call discover") { + expect(discoverServicesMethodObserver.events.count == 1) + } + context("getting wrong state in the middle") { + beforeEach { + fakeCentralManager.rx_didDisconnectPeripheral = Observable.just((fakePeripheral, nil)) + } + it("should get event error") { + expect(servicesObserver.events.count > 0) + } + it("should return proper error") { + expectError(servicesObserver.events[0].value, errorType: BluetoothError.PeripheralDisconnected(peripheral, nil)) + } + } + } + } + } + + describe("discover included services") { + + var service: Service! + var discoverIncludedServicesMethodObserver: TestableObserver<([CBUUID]?, RxServiceType)>! + beforeEach { + fakePeripheral.discoverIncludedServicesTO = testScheduler.createObserver(([CBUUID]?, RxServiceType)) + discoverIncludedServicesMethodObserver = fakePeripheral.discoverIncludedServicesTO + service = Service(peripheral: peripheral, service: fakeService) + servicesObserver = testScheduler.scheduleObservable { + peripheral.discoverIncludedServices(cbuuids, forService: service) + } + } + it("should not call discover before subscribe") { + expect(servicesObserver.events.count).to(equal(0)) + } + context("after subscribe with success discovery") { + var includedServices: [FakeService]! + beforeEach { + includedServices = [FakeService()] + fakeService.includedServices = includedServices.map { $0 as RxServiceType } + let event: Event<(RxServiceType, NSError?)> = Event.Next(fakeService as RxServiceType, nil) + let services: [Recorded>] = [Recorded(time: 230, event: event)] + fakePeripheral.rx_didDiscoverIncludedServicesForService = testScheduler.createHotObservable(services).asObservable() + testScheduler.advanceTo(250) + } + it("should call discover") { + expect(discoverIncludedServicesMethodObserver.events.count).to(equal(1)) + } + it("should call discover with proper uuids") { + expect(discoverIncludedServicesMethodObserver.events[0].value.element!.0).to(equal(cbuuids)) + } + it("should call discover included with proper service") { + expect(discoverIncludedServicesMethodObserver.events[0].value.element!.1 == service.service) + } + describe("discovered service") { + var servicesDiscovered: [Service]? + + beforeEach { + if let s = servicesObserver.events.first?.value.element { + servicesDiscovered = s + } + } + it("should return service") { + expect(servicesDiscovered).toNot(beNil()) + } + it("should return proper service") { + expect(servicesDiscovered!.map{$0.service } == includedServices) + } + } + } + context("after subscribe with failed discovery") { + beforeEach { + fakePeripheral.rx_didDiscoverIncludedServicesForService = Observable.just((fakeService, NSError(domain: "ERROR", code: 200, userInfo: nil))) + testScheduler.advanceTo(250) + } + describe("error returned") { + it("should return event") { + expect(servicesObserver.events.count).to(equal(1)) + } + it("Should return services discovery failed error") { + expectError(servicesObserver.events[0].value, errorType: BluetoothError.IncludedServicesDiscoveryFailed(peripheral, nil)) + } + } + } + context("error propagation ble wrong state") { + var state: CBCentralManagerState! + var error: BluetoothError! + for i in statesWithErrors { + beforeEach { + let (s, e) = i + state = s + error = e + } + + context("before subscribe") { + it("should not call before subscribe") { + expect(discoverIncludedServicesMethodObserver.events.count).to(equal(0)) + } + } + context("after subscribe and getting wrong state on start") { + beforeEach { + fakeCentralManager.rx_didUpdateState = Observable.just(state) + testScheduler.advanceTo(250) + } + it("should get event error") { + expect(servicesObserver.events.count > 0) + } + it("should return proper error") { + expectError(servicesObserver.events[0].value, errorType: error) + } + } + + context("After subscribe and getting wrong state after function is called") { + beforeEach { + fakeCentralManager.state = .PoweredOn + let scans: [Recorded>] = [Recorded(time: 240, event: .Next(state))] + fakeCentralManager.rx_didUpdateState = testScheduler.createHotObservable(scans).asObservable() + testScheduler.advanceTo(250) + } + it("Should call connect on central manager") { + expect(discoverIncludedServicesMethodObserver.events.count == 1) + } + beforeEach { + fakeCentralManager.rx_didUpdateState = Observable.just(state) + } + it("should get event error") { + expect(servicesObserver.events.count > 0) + } + it("should return proper error") { + expectError(servicesObserver.events[0].value, errorType: error) + } + } + } + } + context("error propagation device disconnect") { + context("before subscribe") { + it("should not call before subscribe") { + expect(discoverIncludedServicesMethodObserver.events.count).to(equal(0)) + } + } + + context("after subscribe and getting wrong state on start by event") { + beforeEach { + fakePeripheral.state = .Disconnected + testScheduler.advanceTo(250) + } + it("should get event error") { + expect(servicesObserver.events.count > 0) + } + it("should return proper error") { + expectError(servicesObserver.events[0].value, errorType: BluetoothError.PeripheralDisconnected(peripheral, nil)) + } + } + context("After subscribe and getting wrong state after function is called") { + beforeEach { + //State is good on start. + fakeCentralManager.state = .PoweredOn + fakePeripheral.state = .Connected + //Different types of errors :: + let event: Event<(RxPeripheralType, NSError?)> = Event.Next(fakePeripheral as RxPeripheralType, nil) + let scans: [Recorded>] = [Recorded(time: 240, event: event)] + fakeCentralManager.rx_didDisconnectPeripheral = testScheduler.createHotObservable(scans).asObservable() + testScheduler.advanceTo(250) + } + it("Should call discover services") { + expect(discoverIncludedServicesMethodObserver.events.count == 1) + } + context("getting wrong state in the middle of discover") { + beforeEach { + fakeCentralManager.rx_didDisconnectPeripheral = Observable.just((fakePeripheral, nil)) + } + it("should get event error") { + expect(servicesObserver.events.count > 0) + } + it("should return proper error") { + expectError(servicesObserver.events[0].value, errorType: BluetoothError.PeripheralDisconnected(peripheral, nil)) + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/Example/Tests/Shared/Utilities.swift b/Example/Tests/Shared/Utilities.swift new file mode 100644 index 00000000..639caa24 --- /dev/null +++ b/Example/Tests/Shared/Utilities.swift @@ -0,0 +1,119 @@ +// +// HelperFunctions.swift +// RxBluetoothKit +// +// Created by Kacper Harasim on 25.02.2016. +// Copyright © 2016 CocoaPods. All rights reserved. +// + +import Foundation +import Quick +import Nimble +import RxTests +import RxSwift +import CoreBluetooth +import RxBluetoothKit + +//Helps +final class Box { + let value: T + + init(value: T) { + self.value = value + } +} + +func expectError(event: Event, errorType: ErrorType, file: String = __FILE__, line: UInt = __LINE__) { + expect(event.isStopEvent, file: file, line: line).to(beTrue()) + expect(event.error, file: file, line: line).toNot(beNil()) + expect(event.error is ErrorType, file: file, line: line).to(beTrue()) + expect(event.error as? ErrorType, file: file, line: line).to(equal(errorType)) +} + +extension TestScheduler { + func scheduleObservable(time: ObservableScheduleTimes = ObservableScheduleTimes(), create: () -> Observable) -> ScheduledObservable { + var source : Observable? = nil + var subscription : Disposable? = nil + let observer = createObserver(Element) + + self.scheduleAbsoluteVirtual((), time: time.createTime) { + source = create() + return NopDisposable.instance + } + + self.scheduleAbsoluteVirtual((), time: time.subscribeTime) { + subscription = source!.subscribe(observer) + return NopDisposable.instance + } + + self.scheduleAbsoluteVirtual((), time: time.disposeTime) { + subscription!.dispose() + return NopDisposable.instance + } + + return ScheduledObservable(observer: observer, time: time) + } +} + +struct ScheduledObservable { + let observer : TestableObserver + let time : ObservableScheduleTimes + + var createTime : Int { + return time.createTime + } + + var subscribeTime : Int { + return time.subscribeTime + } + + var disposeTime : Int { + return time.disposeTime + } + + var events : [Recorded>] { + return observer.events + } +} + +struct ObservableScheduleTimes { + let createTime : Int + let subscribeTime : Int + let disposeTime : Int + + init(createTime: Int, subscribeTime: Int, disposeTime: Int) { + self.createTime = createTime + self.subscribeTime = subscribeTime + self.disposeTime = disposeTime + } + + init() { + self.createTime = TestScheduler.Defaults.created + self.subscribeTime = TestScheduler.Defaults.subscribed + self.disposeTime = TestScheduler.Defaults.disposed + } +} + +extension ObservableScheduleTimes { + var before : ObservableScheduleTimes { + return ObservableScheduleTimes(createTime: createTime - 1, + subscribeTime: subscribeTime - 1, + disposeTime: disposeTime - 1) + } + + var after : ObservableScheduleTimes { + return ObservableScheduleTimes(createTime: createTime + 1, + subscribeTime: subscribeTime + 1, + disposeTime: disposeTime + 1) + } +} + +extension BluetoothError { + static var invalidStateErrors : [(CBCentralManagerState, BluetoothError)] { + return [(.PoweredOff, .BluetoothPoweredOff), + (.Resetting, .BluetoothResetting), + (.Unauthorized, .BluetoothUnauthorized), + (.Unknown, .BluetoothInUnknownState), + (.Unsupported, .BluetoothUnsupported)] + } +} diff --git a/Example/Tests/Tests.swift b/Example/Tests/Tests.swift deleted file mode 100644 index 4ace94e0..00000000 --- a/Example/Tests/Tests.swift +++ /dev/null @@ -1,50 +0,0 @@ -// https://github.com/Quick/Quick - -import Quick -import Nimble -import RxBluetoothKit - -class TableOfContentsSpec: QuickSpec { - override func spec() { - describe("these will fail") { - - it("can do maths") { - expect(1) == 2 - } - - it("can read") { - expect("number") == "string" - } - - it("will eventually fail") { - expect("time").toEventually( equal("done") ) - } - - context("these will pass") { - - it("can do maths") { - expect(23) == 23 - } - - it("can read") { - expect("🐮") == "🐮" - } - - it("will eventually pass") { - var time = "passing" - - dispatch_async(dispatch_get_main_queue()) { - time = "done" - } - - waitUntil { done in - NSThread.sleepForTimeInterval(0.5) - expect(time) == "done" - - done() - } - } - } - } - } -} diff --git a/Pod/Classes/BluetoothManager/BluetoothManager.swift b/Pod/Classes/BluetoothManager/BluetoothManager.swift new file mode 100644 index 00000000..a8171db1 --- /dev/null +++ b/Pod/Classes/BluetoothManager/BluetoothManager.swift @@ -0,0 +1,255 @@ +// +// BluetoothManager.swift +// Pods +// +// Created by Przemysław Lenart on 24/02/16. +// +// + +import Foundation +import RxSwift +import CoreBluetooth + + +public class BluetoothManager { + + /// Implementation of Central Manager + private let centralManager : RxCentralManagerType + + // TODO: To be completed: seealso: scanForPeripherals + /// Scheduler on which all serialized operations should be performed + private let queueScheduler : SchedulerType + + /// Internal structures lock + private let lock = NSLock() + + // TODO: To be completed: seealso: scanForPeripherals + /// Queue of scan operations to be completed + private var scanQueue: [ScanOperation] = [] + + private let disposeBag = DisposeBag() + + //MARK: Public interfaces + + /** + Create new Bluetooth Manager with specified implemention of Bluetooth Central Manager which will execute operations + by default on main thread. + + - Parameter centralManager: implementation of Central Manager + - Parameter queueScheduler: scheduler on which serialized work will be executed + */ + public init(centralManager: RxCentralManagerType, queueScheduler: SchedulerType = ConcurrentMainScheduler.instance) { + self.centralManager = centralManager + self.queueScheduler = queueScheduler + } + + /** + Create new Bluetooth Manager with default implmentation of Core Bluetooth Central Manager which callbacks are + handled on main thread. + */ + convenience public init() { + self.init(centralManager: RxCBCentralManager(queue: dispatch_get_main_queue())) + } + + // TODO: Currently simulaneous scanning is not working properly. + // - add tests for concurrent scanning of two users + // - add tests for scanning which has to be queued + /** + Starts BLE scan for peripherals with given service UUIDs. When scan with the same + set of UUIDs is in progress you will bind to it. Otherwise new scan will be queued. + + - Parameter serviceUUIDs: services of peripherals to search for + - Returns: Observable :stream of scanned peripherals. + */ + public func scanForPeripherals(serviceUUIDs: [CBUUID], options: [String : AnyObject]? = nil) -> Observable { + + return Observable.deferred { + let observable : Observable = { Void -> Observable in + // If it's possible use existing scan - take if from the queue + self.lock.lock(); defer { self.lock.unlock() } + if let elem = self.scanQueue.findElement ({ Set(serviceUUIDs).isSubsetOf($0.UUIDs) }) { + return elem.observable + } + + let operationBox = MutableBox>() + + // Create new scan which will be processed in a queue + let operation = Observable.create({ (element: AnyObserver) -> Disposable in + do { self.lock.lock(); defer { self.lock.unlock() } + self.scanQueue.append(ScanOperation(UUIDs: serviceUUIDs, observable: operationBox.value!)) + } + // Start scanning for devices + self.centralManager.scanForPeripheralsWithServices(serviceUUIDs.isEmpty ? nil : Array(serviceUUIDs), options: options) + + // Observable which will emit next element, when peripheral is discovered. + self.centralManager.rx_didDiscoverPeripheral + .map({ (peripheral, advertisment, rssi) -> ScannedPeripheral in + let peripheral = Peripheral(manager: self, peripheral: peripheral) + let advertismentData = AdvertisementData(advertisementData: advertisment) + return ScannedPeripheral(peripheral: peripheral, advertisementData: advertismentData, RSSI: rssi) + }) + .subscribe(element) + return AnonymousDisposable { + //When disposed, stop all scans, and remove scanning operation from queue + self.centralManager.stopScan() + do { self.lock.lock(); defer { self.lock.unlock() } + if let index = self.scanQueue.indexOf({ $0.UUIDs == serviceUUIDs }) { + self.scanQueue.removeAtIndex(index) + } + } + } + + }) + .subscribeOn(self.queueScheduler) + .publish() + .refCount() + + operationBox.value = operation + return operation + }() + // Allow scanning as long as bluetooth is powered on + return self.ensureState(.PoweredOn, observable: observable) + } + } + + /** + Returns current state of BLE Central Manager. + - Returns: Current state of BLE Central Manager. + */ + public var state : CBCentralManagerState { + return centralManager.state + } + + /** + Starts observing state changes for BLE. It starts emitting current state first. + - Returns: Stream of BLE states + */ + public func monitorState() -> Observable { + return centralManager.rx_didUpdateState.startWith(centralManager.state) + } + + // TODO: Consider adding monitorStateChange() without emitting current state. @maciek + + /** + Establishes connection with BLE Peripheral + + - Parameter device: Device to connect to + - Returns: Observation which emits next event after connection is established + */ + public func connectToPeripheral(peripheral: Peripheral, options: [String:AnyObject]? = nil) -> Observable { + + //TODO: Is it possible to connect simultaneously to two devices. If not we should consider + // doing this call in serialized queue. + let success = centralManager.rx_didConnectPeripheral + .filter { $0 == peripheral.peripheral } + .map { _ in return peripheral } + + let error = centralManager.rx_didFailToConnectPeripheral + .filter { $0.0 == peripheral.peripheral } + .flatMap { (peripheral, error) -> Observable in + Observable.error(BluetoothError.PeripheralConnectionFailed(Peripheral(manager: self, peripheral: peripheral), error)) + } + //Defer any action to moment of subscription + let observable = Observable.deferred { + if let error = BluetoothError.errorFromState(self.state) { + return Observable.error(error) + } + guard !peripheral.isConnected else { return Observable.just(peripheral) } + self.centralManager.connectPeripheral(peripheral.peripheral, options: options) + return success.amb(error) + } + + return ensureState(.PoweredOn, observable: observable) + } + + /** + Cancels an active or pending local connection to a peripheral. + - Parameter device: The peripheral to which the central manager is either trying to connect or has already connected. + - Returns: Observation which emits next event when peripheral canceled connection + */ + public func cancelConnectionToPeripheral(peripheral: Peripheral) -> Observable { + + let observable = Observable.deferred { + //TODO: What if not connected? leave it to the OS? + self.centralManager.cancelPeripheralConnection(peripheral.peripheral) + return self.monitorPeripheralDisconnection(peripheral) + } + return ensureState(.PoweredOn, observable: observable) + } + + /** + Returns observable list of the peripherals containing any of the specified services currently connected to the system + - Parameter serviceUUIDs: A list of service UUIDs + - Returns: Observation which emits next when peripherals are retrieved. Peripheral to be included in this event has to be connected to the system and has to contain any of the services specified in the serviceUUID parameter. + */ + public func retrieveConnectedPeripheralsWithServices(serviceUUIDs: [CBUUID]) -> Observable<[Peripheral]> { + let observable = Observable<[Peripheral]>.deferred { + return self.centralManager.retrieveConnectedPeripheralsWithServices(serviceUUIDs).map { (peripheralTable: [RxPeripheralType]) -> + [Peripheral] in peripheralTable.map {Peripheral(manager: self, peripheral: $0) } + } + } + return ensureState(.PoweredOn, observable: observable) + } + + /** + Returns observable list of known peripherals by their identifiers + - Parameter identifiers: List of peripheral identifiers from which CBPeripheral objects can be retrieved + - Returns: Observation which emits next when peripherals are retrieved + */ + public func retrievePeripheralsWithIdentifiers(identifiers: [NSUUID]) -> Observable<[Peripheral]> { + let observable = Observable<[Peripheral]>.deferred { + return self.centralManager.retrievePeripheralsWithIdentifiers(identifiers).map { (peripheralTable: [RxPeripheralType]) -> + [Peripheral] in peripheralTable.map {Peripheral(manager: self, peripheral: $0) } + } + } + return ensureState(.PoweredOn, observable: observable) + } + + ///MARK: Internal functions + + /** + Ensure that state is preserved. It there is other state present error will be merged into stream. + - Parameter state: Central Manager State which should be ensured + - Parameter observable: Observable into which potential errors should be merged + */ + func ensureState(state: CBCentralManagerState, observable: Observable) -> Observable { + + let statesObservable = monitorState() + .filter { $0 != state && BluetoothError.errorFromState($0) != nil } + .map { state -> T in throw BluetoothError.errorFromState(state)! } + + return Observable.of(statesObservable, observable).merge() + } + + /** + This function injects emits errors when peripheral is in disconnected state. + - Parameter peripheral: Peripheral for which errors should be emitted when disconnected + - Returns: Stream of disconnection errors + */ + func ensurePeripheralIsConnected(peripheral: Peripheral) -> Observable { + return Observable.deferred { + if !peripheral.isConnected { + return Observable.error(BluetoothError.PeripheralDisconnected(peripheral, nil)) + } + return self.centralManager.rx_didDisconnectPeripheral + .filter { $0.0 == peripheral.peripheral } + .flatMap { (_, error) -> Observable in + return Observable.error(BluetoothError.PeripheralDisconnected(peripheral, error)) + } + } + } + + /** + Observe peripheral disconnection event + - Parameter peripheral: Peripheral which disconnection events should be observed + - Returns: Observation which emits next events when peripheral was disconnected + */ + func monitorPeripheralDisconnection(peripheral: Peripheral) -> Observable { + return centralManager + .rx_didDisconnectPeripheral + .filter { $0.0 == peripheral.peripheral } + .flatMap { (_, error) -> Observable in + return Observable.just(peripheral) + } + } +} \ No newline at end of file diff --git a/Pod/Classes/BluetoothManager/Internal/ScanOperation.swift b/Pod/Classes/BluetoothManager/Internal/ScanOperation.swift new file mode 100644 index 00000000..1891ac07 --- /dev/null +++ b/Pod/Classes/BluetoothManager/Internal/ScanOperation.swift @@ -0,0 +1,19 @@ +// +// ScanOperation.swift +// Pods +// +// Created by Przemysław Lenart on 26/02/16. +// +// + + +import Foundation +import CoreBluetooth +import RxSwift + +struct ScanOperation { + + let UUIDs: [CBUUID] + let observable: Observable + +} \ No newline at end of file diff --git a/Pod/Classes/BluetoothManager/RxCBCentralManager.swift b/Pod/Classes/BluetoothManager/RxCBCentralManager.swift new file mode 100644 index 00000000..1b95c73d --- /dev/null +++ b/Pod/Classes/BluetoothManager/RxCBCentralManager.swift @@ -0,0 +1,81 @@ +// +// RxCBCentralManager.swift +// Pods +// +// Created by Przemysław Lenart on 24/02/16. +// +// + +import Foundation +import RxSwift +import CoreBluetooth + +public class RxCBCentralManager : RxCentralManagerType { + private let centralManager : CBCentralManager + private let internalDelegate = InternalDelegate() + + public init(queue: dispatch_queue_t) { + centralManager = CBCentralManager(delegate: internalDelegate, queue: queue) + } + + @objc private class InternalDelegate: NSObject, CBCentralManagerDelegate { + let didUpdateStateSubject = PublishSubject() + let willRestoreStateSubject = PublishSubject<[String:AnyObject]>() + let didDiscoverPeripheralSubject = PublishSubject<(RxPeripheralType, [String:AnyObject], NSNumber)>() + let didConnectPerihperalSubject = PublishSubject() + let didFailToConnectPeripheralSubject = PublishSubject<(RxPeripheralType, NSError?)>() + let didDisconnectPeripheral = PublishSubject<(RxPeripheralType, NSError?)>() + + @objc func centralManagerDidUpdateState(central: CBCentralManager) { + didUpdateStateSubject.onNext(central.state) + } + + @objc func centralManager(central: CBCentralManager, willRestoreState dict: [String : AnyObject]) { + willRestoreStateSubject.onNext(dict) + } + + @objc func centralManager(central: CBCentralManager, didDiscoverPeripheral peripheral: CBPeripheral, advertisementData: [String : AnyObject], RSSI: NSNumber) { + didDiscoverPeripheralSubject.onNext((RxCBPeripheral(peripheral: peripheral), advertisementData, RSSI)) + } + + @objc func centralManager(central: CBCentralManager, didConnectPeripheral peripheral: CBPeripheral) { + didConnectPerihperalSubject.onNext(RxCBPeripheral(peripheral: peripheral)) + } + + @objc func centralManager(central: CBCentralManager, didFailToConnectPeripheral peripheral: CBPeripheral, error: NSError?) { + didFailToConnectPeripheralSubject.onNext((RxCBPeripheral(peripheral: peripheral), error)) + } + + @objc func centralManager(central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: NSError?) { + didDisconnectPeripheral.onNext((RxCBPeripheral(peripheral: peripheral), error)) + } + } + + public var rx_didUpdateState: Observable { return internalDelegate.didUpdateStateSubject } + public var rx_willRestoreState: Observable<[String:AnyObject]> { return internalDelegate.willRestoreStateSubject } + public var rx_didDiscoverPeripheral: Observable<(RxPeripheralType, [String : AnyObject], NSNumber)> { return internalDelegate.didDiscoverPeripheralSubject } + public var rx_didConnectPeripheral: Observable { return internalDelegate.didConnectPerihperalSubject } + public var rx_didFailToConnectPeripheral: Observable<(RxPeripheralType, NSError?)> { return internalDelegate.didFailToConnectPeripheralSubject } + public var rx_didDisconnectPeripheral: Observable<(RxPeripheralType, NSError?)> { return internalDelegate.didDisconnectPeripheral } + + public var state: CBCentralManagerState { return centralManager.state } + + public func scanForPeripheralsWithServices(serviceUUIDs: [CBUUID]?, options: [String : AnyObject]?) { + return centralManager.scanForPeripheralsWithServices(serviceUUIDs, options: options) + } + public func connectPeripheral(peripheral: RxPeripheralType, options: [String : AnyObject]?) { + return centralManager.connectPeripheral((peripheral as! RxCBPeripheral).peripheral, options: options) + } + public func cancelPeripheralConnection(peripheral: RxPeripheralType) { + return centralManager.cancelPeripheralConnection((peripheral as! RxCBPeripheral).peripheral) + } + public func stopScan() { + return centralManager.stopScan() + } + public func retrieveConnectedPeripheralsWithServices(serviceUUIDs: [CBUUID]) -> Observable<[RxPeripheralType]> { + return Observable.just( centralManager.retrieveConnectedPeripheralsWithServices(serviceUUIDs).map { RxCBPeripheral(peripheral: $0) }) + } + public func retrievePeripheralsWithIdentifiers(identifiers: [NSUUID]) -> Observable<[RxPeripheralType]> { + return Observable.just(centralManager.retrievePeripheralsWithIdentifiers(identifiers).map { RxCBPeripheral(peripheral: $0) }) + } +} \ No newline at end of file diff --git a/Pod/Classes/BluetoothManager/RxCentralManagerType.swift b/Pod/Classes/BluetoothManager/RxCentralManagerType.swift new file mode 100644 index 00000000..c2d4427a --- /dev/null +++ b/Pod/Classes/BluetoothManager/RxCentralManagerType.swift @@ -0,0 +1,31 @@ +// +// RxCentralManagerType.swift +// Pods +// +// Created by Przemysław Lenart on 24/02/16. +// +// + +import Foundation +import RxSwift +import CoreBluetooth + +public protocol RxCentralManagerType { + + var rx_didUpdateState: Observable { get } + var rx_willRestoreState: Observable<[String : AnyObject]> { get } + var rx_didDiscoverPeripheral: Observable<(RxPeripheralType, [String : AnyObject], NSNumber)> { get } + var rx_didConnectPeripheral: Observable { get } + var rx_didFailToConnectPeripheral: Observable<(RxPeripheralType, NSError?)> { get } + var rx_didDisconnectPeripheral: Observable<(RxPeripheralType, NSError?)> { get } + + var state: CBCentralManagerState { get } + + func scanForPeripheralsWithServices(serviceUUIDs: [CBUUID]?, options: [String : AnyObject]?) + func connectPeripheral(peripheral: RxPeripheralType, options: [String : AnyObject]?) + func cancelPeripheralConnection(peripheral: RxPeripheralType) + func stopScan() + func retrieveConnectedPeripheralsWithServices(serviceUUIDs: [CBUUID]) -> Observable<[RxPeripheralType]> + func retrievePeripheralsWithIdentifiers(identifiers: [NSUUID]) -> Observable<[RxPeripheralType]> + +} \ No newline at end of file diff --git a/Pod/Classes/Characteristic/Characteristic.swift b/Pod/Classes/Characteristic/Characteristic.swift new file mode 100644 index 00000000..2311b4b0 --- /dev/null +++ b/Pod/Classes/Characteristic/Characteristic.swift @@ -0,0 +1,57 @@ +// +// Characteristic.swift +// Pods +// +// Created by Kacper Harasim on 24.02.2016. +// +// + +import Foundation +import RxSwift +import CoreBluetooth + +public class Characteristic { + let characteristic: RxCharacteristicType + public let service: Service + public var value: NSData? { + return characteristic.value + } + public var uuid: CBUUID { + return characteristic.uuid + } + public var isNotifying: Bool { + return characteristic.isNotifying + } + public var properties: CBCharacteristicProperties { + return characteristic.properties + } + public var descriptors: [Descriptor]? { + return characteristic.descriptors?.map { Descriptor(descriptor: $0, characteristic: self) } + } + + init(characteristic: RxCharacteristicType, service: Service) { + self.characteristic = characteristic + self.service = service + } + public func monitorWrite() -> Observable{ + return Observable.never() + } + public func writeValue(data: NSData,type: CBCharacteristicWriteType) -> Observable { + return Observable.never() + } + func setNotifyValue(enabled: Bool) -> Observable{ + return Observable.never() + } + + //MARK: Reading characteristic values + func monitorValueUpdate() -> Observable { + return Observable.never() + } + func readValue() -> Observable{ + return Observable.never() + } +} +extension Characteristic: Equatable {} +public func ==(lhs: Characteristic, rhs: Characteristic) -> Bool { + return lhs.characteristic == rhs.characteristic +} \ No newline at end of file diff --git a/Pod/Classes/Characteristic/RxCBCharacteristic.swift b/Pod/Classes/Characteristic/RxCBCharacteristic.swift new file mode 100644 index 00000000..446121c4 --- /dev/null +++ b/Pod/Classes/Characteristic/RxCBCharacteristic.swift @@ -0,0 +1,31 @@ +// +// RxCBCharacteristic.swift +// Pods +// +// Created by Kacper Harasim on 24.02.2016. +// +// + +import Foundation +import CoreBluetooth +import RxSwift + +class RxCBCharacteristic: RxCharacteristicType { + + let characteristic: CBCharacteristic + + init(characteristic: CBCharacteristic) { + self.characteristic = characteristic + } + + var uuid: CBUUID { return characteristic.UUID } + var value: NSData? { return characteristic.value } + var isNotifying: Bool { return characteristic.isNotifying } + var properties: CBCharacteristicProperties { return characteristic.properties } + var descriptors: [RxDescriptorType]? { + guard let descriptors = characteristic.descriptors else { return nil } + return descriptors.map { RxCBDescriptor(descriptor: $0) } + } + var service: RxServiceType { return RxCBService(service: characteristic.service) } + +} \ No newline at end of file diff --git a/Pod/Classes/Characteristic/RxCharacteristicType.swift b/Pod/Classes/Characteristic/RxCharacteristicType.swift new file mode 100644 index 00000000..ed8e7091 --- /dev/null +++ b/Pod/Classes/Characteristic/RxCharacteristicType.swift @@ -0,0 +1,37 @@ +// +// RxCharacteristicType.swift +// Pods +// +// Created by Przemysław Lenart on 24/02/16. +// +// + +import Foundation +import CoreBluetooth +public protocol RxCharacteristicType { + + var uuid: CBUUID { get } + var value: NSData? { get } + var isNotifying: Bool { get } + var properties: CBCharacteristicProperties { get } + var descriptors: [RxDescriptorType]? { get } + var service: RxServiceType { get } + +} + +public func ==(lhs: RxCharacteristicType, rhs: RxCharacteristicType) -> Bool { + return lhs.uuid == rhs.uuid +} + +func ==(lhs: [RxCharacteristicType], rhs: [RxCharacteristicType]) -> Bool +{ + guard lhs.count == rhs.count else { return false } + var i1 = lhs.generate() + var i2 = rhs.generate() + var isEqual = true + while let e1 = i1.next(), e2 = i2.next() where isEqual + { + isEqual = e1 == e2 + } + return isEqual +} \ No newline at end of file diff --git a/Pod/Classes/Descriptor/Descriptor.swift b/Pod/Classes/Descriptor/Descriptor.swift new file mode 100644 index 00000000..88d32d0f --- /dev/null +++ b/Pod/Classes/Descriptor/Descriptor.swift @@ -0,0 +1,26 @@ +// +// Descriptor.swift +// Pods +// +// Created by Kacper Harasim on 24.02.2016. +// +// + +import Foundation +import CoreBluetooth + +public class Descriptor { + + let descriptor: RxDescriptorType + + public let characteristic: Characteristic + + public var UUID: CBUUID { return descriptor.UUID } + public var value: AnyObject? { return descriptor.value } + + init(descriptor: RxDescriptorType, characteristic: Characteristic) { + self.descriptor = descriptor + self.characteristic = characteristic + } + +} \ No newline at end of file diff --git a/Pod/Classes/Descriptor/RxCBDescriptor.swift b/Pod/Classes/Descriptor/RxCBDescriptor.swift new file mode 100644 index 00000000..1a4cfa2b --- /dev/null +++ b/Pod/Classes/Descriptor/RxCBDescriptor.swift @@ -0,0 +1,25 @@ +// +// RxCBDescriptor.swift +// Pods +// +// Created by Kacper Harasim on 24.02.2016. +// +// + +import Foundation +import CoreBluetooth + + +class RxCBDescriptor: RxDescriptorType { + + let descriptor: CBDescriptor + + init(descriptor: CBDescriptor) { + self.descriptor = descriptor + } + + var UUID: CBUUID { return descriptor.UUID } + var characteristic: RxCharacteristicType { return RxCBCharacteristic(characteristic: descriptor.characteristic) } + var value: AnyObject? { return descriptor.value } + +} \ No newline at end of file diff --git a/Pod/Classes/Descriptor/RxDescriptorType.swift b/Pod/Classes/Descriptor/RxDescriptorType.swift new file mode 100644 index 00000000..11451003 --- /dev/null +++ b/Pod/Classes/Descriptor/RxDescriptorType.swift @@ -0,0 +1,20 @@ +// +// RxDescriptorType.swift +// Pods +// +// Created by Przemysław Lenart on 24/02/16. +// +// + +import Foundation +import CoreBluetooth + + +public protocol RxDescriptorType { + + + var UUID: CBUUID { get } + var characteristic: RxCharacteristicType { get } + var value: AnyObject? { get } + +} \ No newline at end of file diff --git a/Pod/Classes/Peripheral/AdvertisementData.swift b/Pod/Classes/Peripheral/AdvertisementData.swift new file mode 100644 index 00000000..9b4a0a2e --- /dev/null +++ b/Pod/Classes/Peripheral/AdvertisementData.swift @@ -0,0 +1,43 @@ +// +// AdvertisementData.swift +// Pods +// +// Created by Kacper Harasim on 24.02.2016. +// +// + +import Foundation +import CoreBluetooth + + +public struct AdvertisementData { + private let advertisementData : [String : AnyObject] + + public init(advertisementData: [String : AnyObject]) { + self.advertisementData = advertisementData + } + public var localName : String? { + return advertisementData[CBAdvertisementDataLocalNameKey] as? String + } + public var manufacturerData : NSData? { + return advertisementData[CBAdvertisementDataManufacturerDataKey] as? NSData + } + public var serviceData : [CBUUID: NSData]? { + return advertisementData[CBAdvertisementDataServiceDataKey] as? [CBUUID: NSData] + } + public var serviceUUIDs : [CBUUID]? { + return advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID] + } + public var overflowServiceUUIDs : [CBUUID]? { + return advertisementData[CBAdvertisementDataOverflowServiceUUIDsKey] as? [CBUUID] + } + public var txPowerLevel : NSNumber? { + return advertisementData[CBAdvertisementDataTxPowerLevelKey] as? NSNumber + } + public var isConnectable : Bool? { + return advertisementData[CBAdvertisementDataIsConnectable] as? Bool + } + public var solicitedServiceUUIDs : [CBUUID]? { + return advertisementData[CBAdvertisementDataSolicitedServiceUUIDsKey] as? [CBUUID] + } +} \ No newline at end of file diff --git a/Pod/Classes/Peripheral/Peripheral.swift b/Pod/Classes/Peripheral/Peripheral.swift new file mode 100644 index 00000000..e354f9b0 --- /dev/null +++ b/Pod/Classes/Peripheral/Peripheral.swift @@ -0,0 +1,325 @@ +// +// Peripheral.swift +// Pods +// +// Created by Przemysław Lenart on 24/02/16. +// +// + +import Foundation +import RxSwift +import CoreBluetooth + + +public class Peripheral { + + + /// Implementation of peripheral + let peripheral: RxPeripheralType + + + var isConnected: Bool { + return peripheral.state == .Connected + } + + // Current state of peripheral + public var state: CBPeripheralState { + return peripheral.state + } + + /// Name of a peripheral + public var name: String? { return peripheral.name } + + // Peripheral identifier + public var identifier: NSUUID { return peripheral.identifier } + + // Currently hold services + public var services: [Service]? { + return peripheral.services?.map { Service(peripheral: self, service: $0) } + } + + let manager: BluetoothManager + + init(manager: BluetoothManager, peripheral: RxPeripheralType) { + self.manager = manager + self.peripheral = peripheral + } + + /** + Establishes connection with BLE Peripheral + - Parameter options: Connection options + - Returns: Observation which emits next event after connection is established + */ + public func connect(options: [String : AnyObject]? = nil) -> Observable { + return manager.connectToPeripheral(self, options: options) + } + + /** + Connects to BLE Peripheral + - Returns: Observation which emits next event after peripheral is disconnected + */ + public func cancelConnection() -> Observable { + return manager.cancelConnectionToPeripheral(self) + } + + /** + Triggers services discovery. + - Parameter identifiers: Identifiers of wanted services + - Returns: Array of discovered services + */ + public func discoverServices(identifiers: [CBUUID]) -> Observable<[Service]> { + let observable = peripheral.rx_didDiscoverServices + .flatMap({ (services, error) -> Observable<[Service]> in + if let discoveredServices = services { + let mapped = discoveredServices + .filter { identifiers.isEmpty || identifiers.contains($0.uuid) } + .map { Service(peripheral: self, service: $0) as Service } + return Observable.just(mapped) + } + return Observable.error(BluetoothError.ServicesDiscoveryFailed(self, error)) + }) + return Observable.deferred { + self.peripheral.discoverServices(identifiers.isEmpty ? nil : identifiers) + return self.ensureValidPeripheralState(observable) + } + } + + /** + Triggers included services discovery. + - Parameter identifiers: Array of services identifiers + - Parameter service: The service whose included services you want to discover. + - Returns: Returns array of included discovered services. + */ + public func discoverIncludedServices(includedServiceUUIDs: [CBUUID], forService service: Service) -> Observable<[Service]> { + let observable = peripheral.rx_didDiscoverIncludedServicesForService + .flatMap({ (service, error) -> Observable<[Service]> in + if let includedServices = service.includedServices where error == nil { + let services = includedServices + .filter{ includedServiceUUIDs.isEmpty || includedServiceUUIDs.contains($0.uuid) } + .map {Service(peripheral: self, service: $0)} + return Observable.just(services) + } + return Observable.error(BluetoothError.IncludedServicesDiscoveryFailed(self, error)) + }) + return Observable.deferred { + self.peripheral.discoverIncludedServices(includedServiceUUIDs.isEmpty ? nil : includedServiceUUIDs, forService: service.service) + return self.ensureValidPeripheralState(observable) + } + } + + //MARK: Characteristics + + /** + Triggers characteristics discovery for specified service. + - Parameter identifiers: Identifiers of wanted characteristics + - Returns: Stream of characteristics + */ + public func discoverCharacteristics(identifiers: [CBUUID], service: Service) -> Observable<[Characteristic]> { + let observable = peripheral.rx_didDiscoverCharacteristicsForService + .flatMap { (cbService, error) -> Observable<[Characteristic]> in + if let characteristics = cbService.characteristics where error == nil { + let filtered = characteristics + .filter { identifiers.isEmpty || identifiers.contains($0.uuid) } + .map { Characteristic(characteristic: $0, service: service) } + return Observable.just(filtered) + } + return Observable.error(BluetoothError.CharacteristicsDiscoveryFailed(service, error)) + } + return Observable.deferred { + self.peripheral.discoverCharacteristics(identifiers.isEmpty ? nil : identifiers, forService: service.service) + return self.ensureValidPeripheralState(observable) + } + } + + /** + It connects to events of writes for characteristics. + - Parameter characteristic: Characteristic to connect + - Returns: Stream of characteristic, for which value write was detected + */ + public func monitorWriteForCharacteristic(characteristic: Characteristic) -> Observable { + return peripheral.rx_didWriteValueForCharacteristic + .filter { return $0.0 == characteristic.characteristic } + .flatMap { (rxCharacteristic, error) -> Observable in + if let error = error { + return Observable.error(BluetoothError.CharacteristicWriteFailed(characteristic, error)) + } + return Observable.just(characteristic) + } + } + + /** + Writes given data to characteristic + - Parameter characteristic: Characteristic to connect + - Returns: Stream of characteristic, for which value write was detected + */ + public func writeValue(data: NSData, forCharacteristic characteristic: Characteristic, type: CBCharacteristicWriteType) -> Observable { + return Observable.deferred { + //TODO: Check state before call? + self.peripheral.writeValue(data, forCharacteristic: characteristic.characteristic, type: type) + return self.ensureValidPeripheralState(self.monitorWriteForCharacteristic(characteristic)) + } + } + + /** + It connects to events of value updates for characteristics. + - Parameter characteristic: Characteristic to connect + - Returns: Stream of characteristic, for which value change was detected + */ + public func monitorValueUpdateForCharacteristic(characteristic: Characteristic) -> Observable { + let observable = peripheral.rx_didUpdateValueForCharacteristic + .filter { $0.0 == characteristic.characteristic } + .flatMap { (rxCharacteristic, error) -> Observable in + if let error = error { + return Observable.error(BluetoothError.CharacteristicReadFailed(characteristic, error)) + } + return Observable.just(characteristic) + } + return self.ensureValidPeripheralState(observable) + } + + + /** + Reads data from characteristic. + - Parameter characteristic: Characteristic to read value from + - Returns: Stream of characteristic, for which value write was detected + */ + public func readValueForCharacteristic(characteristic: Characteristic) -> Observable { + return Observable.deferred { + // TODO: check state before call? + self.peripheral.readValueForCharacteristic(characteristic.characteristic) + return self.monitorValueUpdateForCharacteristic(characteristic) + } + } + + //MARK: Descriptors + + /** + Triggers descriptors discovery for characteristics + - Parameter characteristic: Characteristic for which descriptors will be discovered + - Returns: Array of descriptors + */ + public func discoverDescriptorsForCharacteristic(characteristic: Characteristic) -> Observable<[Descriptor]> { + return Observable.unimplemented() + } + + + /** + It connects to events of writes for descriptor. + - Parameter characteristic: Descriptor to connect + - Returns: Stream of characteristic, for which value write was detected + */ + public func monitorWriteForDescriptor(descriptor: Descriptor) -> Observable { + return Observable.unimplemented() + } + + /** + Writes given data to descriptor + - Parameter data: Characteristic to connect + - Parameter descriptor: descriptor to write value to + - Returns: Stream of descriptor + */ + public func writeValue(data: NSData, forDescriptor descriptor: Descriptor) -> Observable { + return Observable.unimplemented() + } + + /** + It connects to events of value updates for descriptor. + - Parameter characteristic: Descriptor to connect + - Returns: Stream of characteristic, for which value change was detected + */ + public func monitorValueUpdateForDescriptor(descriptor: Descriptor) -> Observable { + return Observable.unimplemented() + } + + /** + Reads data from given descriptor. + - Parameter characteristic: Descriptor to read value from + - Returns: Stream of characteristic, for which value write was detected + */ + public func readValueForDescriptor(descriptor: Descriptor) -> Observable { + return Observable.unimplemented() + } + + /** + detectErrorsObservable: Function that merges given observable with error streams. Helps propagate errors with connection, while calling another functions + - Parameter observable: observation to be transformed + - Returns: Observable :transformed input observation + */ + func ensureValidPeripheralState(observable: Observable) -> Observable { + return Observable.deferred { + if !self.isConnected { + return Observable.error(BluetoothError.PeripheralDisconnected(self, nil)) + } + return Observable.of(self.manager.ensurePeripheralIsConnected(self), + self.manager.ensureState(.PoweredOn, observable: observable)).merge() + } + } + + /** + Changes state of characteristic notify mode + - Parameter enabled: state to set + - Parameter characteristic: Characteristic to change state + - Returns: Stream of characteristic, for which value was updated + */ + public func setNotifyValue(enabled: Bool, forCharacteristic characteristic: Characteristic) -> Observable { + let observable = peripheral.rx_didUpdateNotificationStateForCharacteristic + .filter { $0.0 == characteristic.characteristic } + .flatMap { (rxCharacteristic, error) -> Observable in + if let error = error { + return Observable.error(BluetoothError.CharacteristicNotifyChangeFailed(characteristic, error)) + } + return Observable.just(characteristic) + } + return Observable.deferred { + //TODO: Check state before call? + self.peripheral.setNotifyValue(enabled, forCharacteristic: characteristic.characteristic) + return self.ensureValidPeripheralState(observable).take(1) + } + } + + /** + Triggers read RSSI from peripheral + - Returns: Peripheral which value is up-to date and current RSSI. + */ + public func readRSSI() -> Observable<(Peripheral, NSNumber)> { + let observable = peripheral.rx_didReadRSSI + .flatMap { (rssi, error) -> Observable<(Peripheral, NSNumber)> in + if error != nil { + return Observable.error(BluetoothError.PeripheralRSSIReadFailed(self, error)) + } + return Observable.just(self, rssi) + } + return Observable.deferred { + self.peripheral.readRSSI() + return self.ensureValidPeripheralState(observable) + } + } + + /** + Connects to name update events + - Returns: Peripheral which name was updated, along with updated name. + */ + public func monitorUpdateName() -> Observable<(Peripheral, String?)> { + return peripheral.rx_didUpdateName + .map { return (self, $0) } + } + + /** + Connects to services modification events + - Returns: stream of arrays services that have been modificated + */ + public func monitorServicesModification() -> Observable<(Peripheral, [Service])> { + let observable = peripheral.rx_didModifyServices + .map { + $0.map { Service(peripheral: self, service: $0) } + } + .map { (self, $0) } + return ensureValidPeripheralState(observable) + } + +} + +extension Peripheral: Equatable {} +public func ==(lhs: Peripheral, rhs: Peripheral) -> Bool { + return lhs.peripheral == rhs.peripheral +} diff --git a/Pod/Classes/Peripheral/RxCBPeripheral.swift b/Pod/Classes/Peripheral/RxCBPeripheral.swift new file mode 100644 index 00000000..91e7ef1e --- /dev/null +++ b/Pod/Classes/Peripheral/RxCBPeripheral.swift @@ -0,0 +1,183 @@ +// +// RxCBPeripheral.swift +// Pods +// +// Created by Kacper Harasim on 24.02.2016. +// +// + +import Foundation +import CoreBluetooth +import RxSwift + +public class RxCBPeripheral: RxPeripheralType { + + let peripheral: CBPeripheral + + private let internalDelegate = InternalPeripheralDelegate() + init(peripheral: CBPeripheral) { + self.peripheral = peripheral + peripheral.delegate = internalDelegate + } + + + + + public var identifier: NSUUID { return peripheral.identifier } + public var name: String? { return peripheral.name } + + public var state: CBPeripheralState { return peripheral.state } + public var services: [RxServiceType]? { + guard let services = peripheral.services else { return nil } + return services.map {RxCBService(service: $0) } + } + + + + public var rx_didUpdateName: Observable { + return internalDelegate.peripheralDidUpdateNameSubject + } + + public var rx_didModifyServices: Observable<([RxServiceType])> { + return internalDelegate.peripheralDidModifyServicesSubject + } + public var rx_didReadRSSI: Observable<(NSNumber, NSError?)> { + return internalDelegate.peripheralDidReadRSSISubject + } + + public var rx_didDiscoverServices: Observable<([RxServiceType]?, NSError?)> { + return internalDelegate.peripheralDidDiscoverServicesSubject + } + + public var rx_didDiscoverIncludedServicesForService: Observable<(RxServiceType, NSError?)> { + return internalDelegate.peripheralDidDiscoverIncludedServicesForServiceSubject + } + + public var rx_didDiscoverCharacteristicsForService: Observable<(RxServiceType, NSError?)> { + return internalDelegate.peripheralDidDiscoverCharacteristicsForServiceSubject + } + public var rx_didUpdateValueForCharacteristic: Observable<(RxCharacteristicType, NSError?)> { + return internalDelegate.peripheralDidUpdateValueForCharacteristicSubject + } + + public var rx_didWriteValueForCharacteristic: Observable<(RxCharacteristicType, NSError?)> { + return internalDelegate.peripheralDidWriteValueForCharacteristicSubject + } + public var rx_didUpdateNotificationStateForCharacteristic: Observable<(RxCharacteristicType, NSError?)> { + return internalDelegate.peripheralDidUpdateNotificationStateForCharacteristicSubject + } + public var rx_didDiscoverDescriptorsForCharacteristic: Observable<(RxCharacteristicType, NSError?)> { + return internalDelegate.peripheralDidDiscoverDescriptorsForCharacteristicSubject + } + public var rx_didUpdateValueForDescriptor: Observable<(RxDescriptorType, NSError?)> { + return internalDelegate.peripheralDidUpdateValueForDescriptorSubject + } + public var rx_didWriteValueForDescriptor: Observable<(RxDescriptorType, NSError?)> { + return internalDelegate.peripheralDidWriteValueForDescriptorSubject + } + + public func discoverServices(serviceUUIDs: [CBUUID]?) { + peripheral.discoverServices(serviceUUIDs) + } + public func discoverCharacteristics(characteristicUUIDs: [CBUUID]?, forService service: RxServiceType) { + peripheral.discoverCharacteristics(characteristicUUIDs, forService: (service as! RxCBService).service) + } + public func discoverIncludedServices(includedServiceUUIDs: [CBUUID]?, forService service: RxServiceType) { + peripheral.discoverIncludedServices(includedServiceUUIDs, forService: (service as! RxCBService).service) + } + public func readValueForCharacteristic(characteristic: RxCharacteristicType) { + peripheral.readValueForCharacteristic((characteristic as! RxCBCharacteristic).characteristic) + } + public func writeValue(data: NSData, forCharacteristic characteristic: RxCharacteristicType, type: CBCharacteristicWriteType) { + peripheral.writeValue(data, forCharacteristic: (characteristic as! RxCBCharacteristic).characteristic, type: type) + } + + public func setNotifyValue(enabled: Bool, forCharacteristic characteristic: RxCharacteristicType) { + peripheral.setNotifyValue(enabled, forCharacteristic: (characteristic as! RxCBCharacteristic).characteristic) + } + public func discoverDescriptorsForCharacteristic(characteristic: RxCharacteristicType) { + peripheral.discoverDescriptorsForCharacteristic((characteristic as! RxCBCharacteristic).characteristic) + } + public func readValueForDescriptor(descriptor: RxDescriptorType) { + peripheral.readValueForDescriptor((descriptor as! RxCBDescriptor).descriptor) + } + public func writeValue(data: NSData, forDescriptor descriptor: RxDescriptorType) { + peripheral.writeValue(data, forDescriptor: (descriptor as! RxCBDescriptor).descriptor) + } + public func readRSSI() { + peripheral.readRSSI() + } + + + + private class InternalPeripheralDelegate: NSObject, CBPeripheralDelegate { + let peripheralDidUpdateNameSubject = PublishSubject() + let peripheralDidModifyServicesSubject = PublishSubject<([RxServiceType])>() + let peripheralDidReadRSSISubject = PublishSubject<(NSNumber, NSError?)>() + let peripheralDidDiscoverServicesSubject = PublishSubject<([RxServiceType]?, NSError?)>() + let peripheralDidDiscoverIncludedServicesForServiceSubject = PublishSubject<(RxServiceType, NSError? )>() + let peripheralDidDiscoverCharacteristicsForServiceSubject = PublishSubject<(RxServiceType, NSError?)>() + let peripheralDidUpdateValueForCharacteristicSubject = PublishSubject<(RxCharacteristicType, NSError?)>() + let peripheralDidWriteValueForCharacteristicSubject = PublishSubject<(RxCharacteristicType, NSError?)>() + let peripheralDidUpdateNotificationStateForCharacteristicSubject = PublishSubject<(RxCharacteristicType, NSError?)>() + let peripheralDidDiscoverDescriptorsForCharacteristicSubject = PublishSubject<(RxCharacteristicType, NSError?)>() + let peripheralDidUpdateValueForDescriptorSubject = PublishSubject<(RxDescriptorType, NSError?)>() + let peripheralDidWriteValueForDescriptorSubject = PublishSubject<(RxDescriptorType, NSError?)>() + + + @objc func peripheralDidUpdateName(peripheral: CBPeripheral) { + peripheralDidUpdateNameSubject.onNext(peripheral.name) + } + + @objc func peripheral(peripheral: CBPeripheral, didModifyServices invalidatedServices: [CBService]) { + peripheralDidModifyServicesSubject.onNext(invalidatedServices.map { RxCBService(service: $0) }) + } + + + @objc func peripheral(peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: NSError?) { + peripheralDidReadRSSISubject.onNext((RSSI, error)) + } + + @objc func peripheral(peripheral: CBPeripheral, didDiscoverServices error: NSError?) { + peripheralDidDiscoverServicesSubject.onNext((peripheral.services?.map{ RxCBService(service: $0) }, error)) + } + + @objc func peripheral(peripheral: CBPeripheral, didDiscoverIncludedServicesForService service: CBService, error: NSError?) { + peripheralDidDiscoverIncludedServicesForServiceSubject.onNext((RxCBService(service: service), error )) + } + + @objc func peripheral(peripheral: CBPeripheral, didDiscoverCharacteristicsForService service: CBService, error: NSError?) { + peripheralDidDiscoverCharacteristicsForServiceSubject.onNext((RxCBService(service: service), error)) + } + + @objc func peripheral(peripheral: CBPeripheral, didUpdateValueForCharacteristic characteristic: CBCharacteristic, error: NSError?) { + peripheralDidUpdateValueForCharacteristicSubject.onNext((RxCBCharacteristic(characteristic: characteristic), error)) + } + + @objc func peripheral(peripheral: CBPeripheral, didWriteValueForCharacteristic characteristic: CBCharacteristic, error: NSError?) { + peripheralDidWriteValueForCharacteristicSubject.onNext((RxCBCharacteristic(characteristic: characteristic), error)) + } + + @objc func peripheral(peripheral: CBPeripheral, didUpdateNotificationStateForCharacteristic characteristic: CBCharacteristic, error: NSError?) { + peripheralDidUpdateNotificationStateForCharacteristicSubject.onNext((RxCBCharacteristic(characteristic: characteristic), error)) + } + + @objc func peripheral(peripheral: CBPeripheral, didDiscoverDescriptorsForCharacteristic characteristic: CBCharacteristic, error: NSError?) { + peripheralDidDiscoverDescriptorsForCharacteristicSubject.onNext((RxCBCharacteristic(characteristic: characteristic), error)) + } + + @objc func peripheral(peripheral: CBPeripheral, didUpdateValueForDescriptor descriptor: CBDescriptor, error: NSError?) { + peripheralDidUpdateValueForDescriptorSubject.onNext((RxCBDescriptor(descriptor: descriptor), error)) + } + @objc func peripheral(peripheral: CBPeripheral, didWriteValueForDescriptor descriptor: CBDescriptor, error: NSError?) { + peripheralDidWriteValueForDescriptorSubject.onNext((RxCBDescriptor(descriptor: descriptor), error)) + + } + + } +} + + + + + diff --git a/Pod/Classes/Peripheral/RxPeripheralType.swift b/Pod/Classes/Peripheral/RxPeripheralType.swift new file mode 100644 index 00000000..f427a61a --- /dev/null +++ b/Pod/Classes/Peripheral/RxPeripheralType.swift @@ -0,0 +1,51 @@ +// +// RxPeripheralType.swift +// Pods +// +// Created by Przemysław Lenart on 24/02/16. +// +// + +import Foundation +import RxSwift +import CoreBluetooth + +public protocol RxPeripheralType { + + var name: String? { get } + var identifier: NSUUID { get } + var state: CBPeripheralState { get } + var services: [RxServiceType]? { get } + + var rx_didUpdateName: Observable { get } + var rx_didModifyServices: Observable<([RxServiceType])>{ get } + var rx_didReadRSSI: Observable<(NSNumber, NSError?)> { get } + var rx_didDiscoverServices: Observable<([RxServiceType]?, NSError?)> { get } + var rx_didDiscoverIncludedServicesForService: Observable<(RxServiceType, NSError?)> { get } + var rx_didDiscoverCharacteristicsForService: Observable<(RxServiceType, NSError?)> { get } + var rx_didUpdateValueForCharacteristic: Observable<(RxCharacteristicType, NSError?)> { get } + var rx_didWriteValueForCharacteristic: Observable<(RxCharacteristicType, NSError?)> { get } + var rx_didUpdateNotificationStateForCharacteristic: Observable<(RxCharacteristicType, NSError?)> { get } + var rx_didDiscoverDescriptorsForCharacteristic: Observable<(RxCharacteristicType, NSError?)> { get } + var rx_didUpdateValueForDescriptor: Observable<(RxDescriptorType, NSError?)> { get } + var rx_didWriteValueForDescriptor: Observable<(RxDescriptorType, NSError?)> { get } + + + func discoverServices(serviceUUIDs: [CBUUID]?) + func discoverCharacteristics(characteristicUUIDs: [CBUUID]?, forService: RxServiceType) + func discoverIncludedServices(includedServiceUUIDs: [CBUUID]?, forService service: RxServiceType) + func readValueForCharacteristic(characteristic: RxCharacteristicType) + func writeValue(data: NSData, forCharacteristic characteristic: RxCharacteristicType, type: CBCharacteristicWriteType) + func setNotifyValue(enabled: Bool, forCharacteristic characteristic: RxCharacteristicType) + func discoverDescriptorsForCharacteristic(characteristic: RxCharacteristicType) + func readValueForDescriptor(descriptor: RxDescriptorType) + func writeValue(data: NSData, forDescriptor descriptor: RxDescriptorType) + + func readRSSI() + +} + + +public func ==(lhs: RxPeripheralType, rhs: RxPeripheralType) -> Bool { + return lhs.identifier == rhs.identifier +} diff --git a/Pod/Classes/Peripheral/ScannedPeripheral.swift b/Pod/Classes/Peripheral/ScannedPeripheral.swift new file mode 100644 index 00000000..392bfaf8 --- /dev/null +++ b/Pod/Classes/Peripheral/ScannedPeripheral.swift @@ -0,0 +1,25 @@ +// +// ScannedPeripheral.swift +// Pods +// +// Created by Kacper Harasim on 24.02.2016. +// +// + +import Foundation +import CoreBluetooth + +public class ScannedPeripheral { + + + + public let peripheral: Peripheral + public let advertisementData: AdvertisementData + public let RSSI: NSNumber + + public init(peripheral: Peripheral, advertisementData: AdvertisementData, RSSI: NSNumber) { + self.peripheral = peripheral + self.advertisementData = advertisementData + self.RSSI = RSSI + } +} diff --git a/Pod/Classes/ReplaceMe.swift b/Pod/Classes/ReplaceMe.swift deleted file mode 100644 index e69de29b..00000000 diff --git a/Pod/Classes/Service/RxCBService.swift b/Pod/Classes/Service/RxCBService.swift new file mode 100644 index 00000000..b0c4c44a --- /dev/null +++ b/Pod/Classes/Service/RxCBService.swift @@ -0,0 +1,34 @@ +// +// RxCBService.swift +// Pods +// +// Created by Kacper Harasim on 24.02.2016. +// +// + +import Foundation +import CoreBluetooth +import RxSwift + +class RxCBService: RxServiceType { + + let service: CBService + init(service: CBService) { + self.service = service + } + + var uuid: CBUUID { return service.UUID } + var characteristics: [RxCharacteristicType]? { + guard let chars = service.characteristics else { return nil } + return chars.map { RxCBCharacteristic(characteristic: $0) } + } + var includedServices: [RxServiceType]? { + guard let services = service.includedServices else { return nil } + return services.map { RxCBService(service: $0) } + } + var isPrimary: Bool { + return service.isPrimary + } + +} + diff --git a/Pod/Classes/Service/RxServiceType.swift b/Pod/Classes/Service/RxServiceType.swift new file mode 100644 index 00000000..31c9e0e6 --- /dev/null +++ b/Pod/Classes/Service/RxServiceType.swift @@ -0,0 +1,35 @@ +// +// RxServiceType.swift +// Pods +// +// Created by Przemysław Lenart on 24/02/16. +// +// + +import Foundation +import CoreBluetooth + +public protocol RxServiceType { + + var uuid: CBUUID { get } + var characteristics: [RxCharacteristicType]? { get } + var includedServices: [RxServiceType]? { get } + var isPrimary: Bool { get } +} + +public func ==(lhs: RxServiceType, rhs: RxServiceType) -> Bool { + return lhs.uuid == rhs.uuid +} + +func ==(lhs: [RxServiceType], rhs: [RxServiceType]) -> Bool +{ + guard lhs.count == rhs.count else { return false } + var i1 = lhs.generate() + var i2 = rhs.generate() + var isEqual = true + while let e1 = i1.next(), e2 = i2.next() where isEqual + { + isEqual = e1 == e2 + } + return isEqual +} \ No newline at end of file diff --git a/Pod/Classes/Service/Service.swift b/Pod/Classes/Service/Service.swift new file mode 100644 index 00000000..d46dc921 --- /dev/null +++ b/Pod/Classes/Service/Service.swift @@ -0,0 +1,48 @@ +// +// Service.swift +// Pods +// +// Created by Kacper Harasim on 24.02.2016. +// +// + +import Foundation + + +import Foundation +import CoreBluetooth +import RxSwift +public class Service { + let service: RxServiceType + public let peripheral: Peripheral + + public var isPrimary: Bool { + return service.isPrimary + } + + public var uuid: CBUUID { + return service.uuid + } + + public var includedServices: [Service]? { + return service.includedServices?.map { Service(peripheral: peripheral, service: $0) } ?? nil + } + + public var characteristics: [Characteristic]? { + return service.characteristics?.map { Characteristic(characteristic: $0, service: self) } ?? nil + } + + public init(peripheral: Peripheral, service: RxServiceType) { + self.service = service + self.peripheral = peripheral + } + + public func discoverCharacteristics(identifiers: [CBUUID]) -> Observable<[Characteristic]> { + return peripheral.discoverCharacteristics(identifiers, service: self) + } +} + +extension Service: Equatable {} +public func ==(lhs: Service, rhs: Service) -> Bool { + return lhs.service == rhs.service +} diff --git a/Pod/Classes/Shared/BluetoothError.swift b/Pod/Classes/Shared/BluetoothError.swift new file mode 100644 index 00000000..3b04a67b --- /dev/null +++ b/Pod/Classes/Shared/BluetoothError.swift @@ -0,0 +1,127 @@ +// +// BluetoothError.swift +// Pods +// +// Created by Przemysław Lenart on 04/03/16. +// +// + +import Foundation +import CoreBluetooth + +public enum BluetoothError : ErrorType { + // States + case BluetoothUnsupported + case BluetoothUnauthorized + case BluetoothPoweredOff + case BluetoothInUnknownState + case BluetoothResetting + + // Peripheral + case PeripheralConnectionFailed(Peripheral, NSError?) + case PeripheralDisconnected(Peripheral, NSError?) + case PeripheralRSSIReadFailed(Peripheral, NSError?) + + // Services + case ServicesDiscoveryFailed(Peripheral, NSError?) + case IncludedServicesDiscoveryFailed(Peripheral, NSError?) + + // Characteristics + case CharacteristicsDiscoveryFailed(Service, NSError?) + case CharacteristicWriteFailed(Characteristic, NSError?) + case CharacteristicReadFailed(Characteristic, NSError?) + case CharacteristicNotifyChangeFailed(Characteristic, NSError?) + + // Descriptors + case DescriptorsDiscoveryFailed(Characteristic, NSError?) +} + +extension BluetoothError : CustomStringConvertible { + public var description: String { + switch self { + case .BluetoothUnsupported: + return "Bluetooth is unsupported" + case .BluetoothUnauthorized: + return "Bluetooth is unauthorized" + case .BluetoothPoweredOff: + return "Bluetooth is powered off" + case .BluetoothInUnknownState: + return "Bluetooth is in unknown state" + case .BluetoothResetting: + return "Bluetooth is resetting" + + case .PeripheralConnectionFailed(_, let err): + return "Connection error has occured: \(err?.description ?? "-")" + case .PeripheralDisconnected(_, let err): + return "Connection error has occured: \(err?.description ?? "-")" + case .PeripheralRSSIReadFailed(_, let err): + return "RSSI read failed : \(err?.description ?? "-")" + + case .ServicesDiscoveryFailed(_, let err): + return "Services discovery error has occured: \(err?.description ?? "-")" + case .IncludedServicesDiscoveryFailed(_, let err): + return "Included services discovery error has occured: \(err?.description ?? "-")" + + case .CharacteristicsDiscoveryFailed(_, let err): + return "Characteristics discovery error has occured: \(err?.description ?? "-")" + case .CharacteristicWriteFailed(_, let err): + return "Characteristic write error has occured: \(err?.description ?? "-")" + case .CharacteristicReadFailed(_, let err): + return "Characteristic read error has occured: \(err?.description ?? "-")" + case .CharacteristicNotifyChangeFailed(_, let err): + return "Characteristic notify change error has occured: \(err?.description ?? "-")" + + case .DescriptorsDiscoveryFailed(_, let err): + return "Descriptor discovery error has occured: \(err?.description ?? "-")" + } + + } +} +extension BluetoothError { + static func errorFromState(state: CBCentralManagerState) -> BluetoothError? { + switch state { + case .Unsupported: + return .BluetoothUnsupported + case .Unauthorized: + return .BluetoothUnauthorized + case .PoweredOff: + return .BluetoothPoweredOff + case .Unknown: + return .BluetoothInUnknownState + case .Resetting: + return .BluetoothResetting + default: + return nil + } + } +} + + + + +extension BluetoothError: Equatable {} +public func ==(lhs: BluetoothError, rhs: BluetoothError) -> Bool { + switch (lhs, rhs) { + case (.BluetoothUnsupported, .BluetoothUnsupported): return true + case (.BluetoothUnauthorized, .BluetoothUnauthorized): return true + case (.BluetoothPoweredOff, .BluetoothPoweredOff): return true + case (.BluetoothInUnknownState, .BluetoothInUnknownState): return true + case (.BluetoothResetting, .BluetoothResetting): return true + + case (.ServicesDiscoveryFailed(let l, _), .ServicesDiscoveryFailed(let r, _)): return l == r + case (.IncludedServicesDiscoveryFailed(let l, _), .IncludedServicesDiscoveryFailed(let r, _)): return l == r + + case (.PeripheralConnectionFailed(let l, _), .PeripheralConnectionFailed(let r, _)): return l == r + case (.PeripheralDisconnected(let l, _), .PeripheralDisconnected(let r, _)): return l == r + case (.PeripheralRSSIReadFailed(let l, _), .PeripheralRSSIReadFailed(let r, _)): return l == r + + case (.CharacteristicsDiscoveryFailed(let l, _), .CharacteristicsDiscoveryFailed(let r, _)): return l == r + case (.CharacteristicWriteFailed(let l, _), .CharacteristicWriteFailed(let r, _)): return l == r + case (.CharacteristicReadFailed(let l, _), .CharacteristicReadFailed(let r, _)): return l == r + case (.CharacteristicNotifyChangeFailed(let l, _), .CharacteristicNotifyChangeFailed(let r, _)): return l == r + + case (.DescriptorsDiscoveryFailed(let l, _), .DescriptorsDiscoveryFailed(let r, _)): return l == r + + default: return false + } +} diff --git a/Pod/Classes/Shared/Boxes.swift b/Pod/Classes/Shared/Boxes.swift new file mode 100644 index 00000000..b76892d7 --- /dev/null +++ b/Pod/Classes/Shared/Boxes.swift @@ -0,0 +1,27 @@ +// +// Boxes.swift +// Pods +// +// Created by Przemysław Lenart on 26/02/16. +// +// + +import Foundation + + +class MutableBox : CustomDebugStringConvertible { + var value : T? + + init() {} + init(value: T) { + self.value = value + } +} + +extension MutableBox { + var debugDescription: String { + get { + return "MutatingBox(\(self.value))" + } + } +} \ No newline at end of file diff --git a/Pod/Classes/Shared/CollectionUtils.swift b/Pod/Classes/Shared/CollectionUtils.swift new file mode 100644 index 00000000..48e47f30 --- /dev/null +++ b/Pod/Classes/Shared/CollectionUtils.swift @@ -0,0 +1,18 @@ +// +// CollectionUtils.swift +// Pods +// +// Created by Przemysław Lenart on 26/02/16. +// +// + +import Foundation + +extension SequenceType { + func findElement(@noescape match: Generator.Element -> Bool) -> Generator.Element? { + for elem in self where match(elem) { + return elem + } + return nil + } +} \ No newline at end of file diff --git a/Pod/Classes/Shared/Unimplemented.swift b/Pod/Classes/Shared/Unimplemented.swift new file mode 100644 index 00000000..9ccffb8b --- /dev/null +++ b/Pod/Classes/Shared/Unimplemented.swift @@ -0,0 +1,21 @@ +// +// UnimplementedError.swift +// Pods +// +// Created by Przemysław Lenart on 04/03/16. +// +// + +import Foundation +import RxSwift + +func unimplementedFunction(file: String = __FILE__, function: String = __FUNCTION__, line: Int = __LINE__) { + fatalError("Unimplemented function \(function) in \(file):\(line)") +} + +extension Observable { + static func unimplemented(file: String = __FILE__, function: String = __FUNCTION__, line: Int = __LINE__) -> Observable { + unimplementedFunction(file, function: function, line: line) + return Observable.empty() + } +} \ No newline at end of file diff --git a/RxBluetoothKit.podspec b/RxBluetoothKit.podspec index 1a881300..78e26537 100644 --- a/RxBluetoothKit.podspec +++ b/RxBluetoothKit.podspec @@ -36,5 +36,5 @@ Pod::Spec.new do |s| # s.public_header_files = 'Pod/Classes/**/*.h' # s.frameworks = 'UIKit', 'MapKit' - # s.dependency 'AFNetworking', '~> 2.3' + s.dependency 'RxSwift', '~> 2.0' end