diff --git a/README.md b/README.md index 5c8eb77..2467787 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# ![Blue Falcon](bluefalcon.png) Blue-Falcon [![Build Status](https://api.travis-ci.com/Reedyuk/blue-falcon.svg?branch=master)](https://api.travis-ci.com/Reedyuk/blue-falcon) [![Kotlin](https://img.shields.io/badge/kotlin-1.3.71-blue.svg)](http://kotlinlang.org) +# ![Blue Falcon](bluefalcon.png) Blue-Falcon [![Build Status](https://api.travis-ci.com/Reedyuk/blue-falcon.svg?branch=master)](https://api.travis-ci.com/Reedyuk/blue-falcon) [![Kotlin](https://img.shields.io/badge/kotlin-1.3.71-blue.svg)](http://kotlinlang.org) ![badge][badge-android] ![badge][badge-native] ![badge][badge-mac] -A Bluetooth "Cross Platform" Kotlin Multiplatform library for iOS and Android. +A Bluetooth "Cross Platform" Kotlin Multiplatform library for iOS, Android and MacOS. Bluetooth in general has the same functionality for all platforms, e.g. connect to device, fetch services, fetch characteristics. @@ -10,13 +10,9 @@ The idea is to have a common api for using bluetooth as the principle of bluetoo What this library isn't? It is not a cross platform library, this is a multiplatform library. The difference? each platform is compiled down to the native code, so when you use the library in iOS, you are consuming an obj-c library and same principle for Android. -## Known issues: - -On the android example we have an issue where the characteristic which was originally stored has changed and we are holding onto an old version. - ## Basic Usage -### iOS +### iOS & MacOS Create an instance of BlueFalcon and then call the scan method. @@ -87,7 +83,7 @@ This repo contains examples for kotlin MP, ios and android in the examples folde Open the kotlin MP example directory in InteliJ and then run the install targets. -### iOS +### iOS & MacOS To run the iOS example, simply perform a pod install and run from xcode. @@ -106,3 +102,9 @@ For a **bug, feature request, or cool idea**, please [file a Github issue](https ### Two big little things Keep in mind that Blue-Falcon is maintained by volunteers. Please be patient if you don’t immediately get an answer to your question; we all have jobs, families, obligations, and lives beyond this project. + + +[badge-android]: http://img.shields.io/badge/platform-android-brightgreen.svg?style=flat +[badge-native]: http://img.shields.io/badge/platform-native-lightgrey.svg?style=flat +[badge-js]: http://img.shields.io/badge/platform-js-yellow.svg?style=flat +[badge-mac]: http://img.shields.io/badge/platform-macos-lightgrey.svg?style=flat diff --git a/examples/macOS/Blue-Falcon.xcodeproj/project.pbxproj b/examples/macOS/Blue-Falcon.xcodeproj/project.pbxproj new file mode 100644 index 0000000..35bdb89 --- /dev/null +++ b/examples/macOS/Blue-Falcon.xcodeproj/project.pbxproj @@ -0,0 +1,514 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 493702C42452366900CAE3CF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 493702C32452366900CAE3CF /* AppDelegate.swift */; }; + 49DEE382245239B00070FA09 /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49DEE37A245239AF0070FA09 /* Strings.swift */; }; + 49DEE3C324523BAA0070FA09 /* BluetoothConnectionState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49DEE3A924523BAA0070FA09 /* BluetoothConnectionState.swift */; }; + 49DEE3C424523BAA0070FA09 /* Descriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49DEE3AA24523BAA0070FA09 /* Descriptor.swift */; }; + 49DEE3C524523BAA0070FA09 /* BluetoothScanningState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49DEE3AB24523BAA0070FA09 /* BluetoothScanningState.swift */; }; + 49DEE3C624523BAA0070FA09 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 49DEE3AC24523BAA0070FA09 /* Assets.xcassets */; }; + 49DEE3C824523BAA0070FA09 /* BluetoothService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49DEE3B024523BAA0070FA09 /* BluetoothService.swift */; }; + 49DEE3C924523BAA0070FA09 /* DeviceCharacteristicCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49DEE3B224523BAA0070FA09 /* DeviceCharacteristicCell.swift */; }; + 49DEE3CA24523BAA0070FA09 /* DevicesViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49DEE3B424523BAA0070FA09 /* DevicesViewCell.swift */; }; + 49DEE3CB24523BAA0070FA09 /* ActivityIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49DEE3B524523BAA0070FA09 /* ActivityIndicator.swift */; }; + 49DEE3CC24523BAA0070FA09 /* DeviceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49DEE3B624523BAA0070FA09 /* DeviceView.swift */; }; + 49DEE3CD24523BAA0070FA09 /* DevicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49DEE3B724523BAA0070FA09 /* DevicesView.swift */; }; + 49DEE3CE24523BAA0070FA09 /* DeviceServiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49DEE3B824523BAA0070FA09 /* DeviceServiceView.swift */; }; + 49DEE3D024523BAA0070FA09 /* DeviceServiceCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49DEE3BD24523BAA0070FA09 /* DeviceServiceCellViewModel.swift */; }; + 49DEE3D124523BAA0070FA09 /* DeviceCharacteristicCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49DEE3BE24523BAA0070FA09 /* DeviceCharacteristicCellViewModel.swift */; }; + 49DEE3D224523BAA0070FA09 /* DeviceServiceViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49DEE3BF24523BAA0070FA09 /* DeviceServiceViewModel.swift */; }; + 49DEE3D324523BAA0070FA09 /* DevicesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49DEE3C024523BAA0070FA09 /* DevicesViewModel.swift */; }; + 49DEE3D424523BAA0070FA09 /* DevicesCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49DEE3C124523BAA0070FA09 /* DevicesCellViewModel.swift */; }; + 49DEE3D524523BAA0070FA09 /* DeviceViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49DEE3C224523BAA0070FA09 /* DeviceViewModel.swift */; }; + 49DEE3DA245241440070FA09 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 49DEE3D9245241440070FA09 /* Preview Assets.xcassets */; }; + 49DEE3DD24535DF60070FA09 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 49DEE3DB24535DF60070FA09 /* Main.storyboard */; }; + 6C7E01C2F62343FA39A1FA51 /* Pods_Blue_Falcon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 35C3E03E3C2E84AB6D47622C /* Pods_Blue_Falcon.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 35C3E03E3C2E84AB6D47622C /* Pods_Blue_Falcon.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Blue_Falcon.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 48647C8E9CB6F44A7C3208D5 /* Pods-Blue-Falcon.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Blue-Falcon.debug.xcconfig"; path = "Target Support Files/Pods-Blue-Falcon/Pods-Blue-Falcon.debug.xcconfig"; sourceTree = ""; }; + 493702C02452366900CAE3CF /* Blue-Falcon.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Blue-Falcon.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 493702C32452366900CAE3CF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 493702CF2452366A00CAE3CF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 493702D02452366A00CAE3CF /* Blue_Falcon.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Blue_Falcon.entitlements; sourceTree = ""; }; + 49DEE37A245239AF0070FA09 /* Strings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = ""; }; + 49DEE3A924523BAA0070FA09 /* BluetoothConnectionState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BluetoothConnectionState.swift; sourceTree = ""; }; + 49DEE3AA24523BAA0070FA09 /* Descriptor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Descriptor.swift; sourceTree = ""; }; + 49DEE3AB24523BAA0070FA09 /* BluetoothScanningState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BluetoothScanningState.swift; sourceTree = ""; }; + 49DEE3AC24523BAA0070FA09 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 49DEE3B024523BAA0070FA09 /* BluetoothService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BluetoothService.swift; sourceTree = ""; }; + 49DEE3B224523BAA0070FA09 /* DeviceCharacteristicCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceCharacteristicCell.swift; sourceTree = ""; }; + 49DEE3B424523BAA0070FA09 /* DevicesViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DevicesViewCell.swift; sourceTree = ""; }; + 49DEE3B524523BAA0070FA09 /* ActivityIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityIndicator.swift; sourceTree = ""; }; + 49DEE3B624523BAA0070FA09 /* DeviceView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceView.swift; sourceTree = ""; }; + 49DEE3B724523BAA0070FA09 /* DevicesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DevicesView.swift; sourceTree = ""; }; + 49DEE3B824523BAA0070FA09 /* DeviceServiceView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceServiceView.swift; sourceTree = ""; }; + 49DEE3BD24523BAA0070FA09 /* DeviceServiceCellViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceServiceCellViewModel.swift; sourceTree = ""; }; + 49DEE3BE24523BAA0070FA09 /* DeviceCharacteristicCellViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceCharacteristicCellViewModel.swift; sourceTree = ""; }; + 49DEE3BF24523BAA0070FA09 /* DeviceServiceViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceServiceViewModel.swift; sourceTree = ""; }; + 49DEE3C024523BAA0070FA09 /* DevicesViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DevicesViewModel.swift; sourceTree = ""; }; + 49DEE3C124523BAA0070FA09 /* DevicesCellViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DevicesCellViewModel.swift; sourceTree = ""; }; + 49DEE3C224523BAA0070FA09 /* DeviceViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceViewModel.swift; sourceTree = ""; }; + 49DEE3D9245241440070FA09 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 49DEE3DC24535DF60070FA09 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + EE00871EDF110355EF35AAE6 /* Pods-Blue-Falcon.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Blue-Falcon.release.xcconfig"; path = "Target Support Files/Pods-Blue-Falcon/Pods-Blue-Falcon.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 493702BD2452366900CAE3CF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 6C7E01C2F62343FA39A1FA51 /* Pods_Blue_Falcon.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 02718DCDF7E44F3FEE1214C1 /* Pods */ = { + isa = PBXGroup; + children = ( + 48647C8E9CB6F44A7C3208D5 /* Pods-Blue-Falcon.debug.xcconfig */, + EE00871EDF110355EF35AAE6 /* Pods-Blue-Falcon.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + 382A400FB3F941F4A7D908A2 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 35C3E03E3C2E84AB6D47622C /* Pods_Blue_Falcon.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 493702B72452366900CAE3CF = { + isa = PBXGroup; + children = ( + 493702C22452366900CAE3CF /* Blue-Falcon */, + 493702C12452366900CAE3CF /* Products */, + 02718DCDF7E44F3FEE1214C1 /* Pods */, + 382A400FB3F941F4A7D908A2 /* Frameworks */, + ); + sourceTree = ""; + }; + 493702C12452366900CAE3CF /* Products */ = { + isa = PBXGroup; + children = ( + 493702C02452366900CAE3CF /* Blue-Falcon.app */, + ); + name = Products; + sourceTree = ""; + }; + 493702C22452366900CAE3CF /* Blue-Falcon */ = { + isa = PBXGroup; + children = ( + 49DEE3D8245241440070FA09 /* Preview Content */, + 49DEE3AC24523BAA0070FA09 /* Assets.xcassets */, + 49DEE3AD24523BAA0070FA09 /* Controllers */, + 49DEE3A824523BAA0070FA09 /* Models */, + 49DEE3AF24523BAA0070FA09 /* Services */, + 49DEE3BC24523BAA0070FA09 /* ViewModels */, + 49DEE3B124523BAA0070FA09 /* Views */, + 49DEE37A245239AF0070FA09 /* Strings.swift */, + 493702C32452366900CAE3CF /* AppDelegate.swift */, + 49DEE3DB24535DF60070FA09 /* Main.storyboard */, + 493702CF2452366A00CAE3CF /* Info.plist */, + 493702D02452366A00CAE3CF /* Blue_Falcon.entitlements */, + ); + path = "Blue-Falcon"; + sourceTree = ""; + }; + 49DEE3A824523BAA0070FA09 /* Models */ = { + isa = PBXGroup; + children = ( + 49DEE3A924523BAA0070FA09 /* BluetoothConnectionState.swift */, + 49DEE3AA24523BAA0070FA09 /* Descriptor.swift */, + 49DEE3AB24523BAA0070FA09 /* BluetoothScanningState.swift */, + ); + path = Models; + sourceTree = ""; + }; + 49DEE3AD24523BAA0070FA09 /* Controllers */ = { + isa = PBXGroup; + children = ( + ); + path = Controllers; + sourceTree = ""; + }; + 49DEE3AF24523BAA0070FA09 /* Services */ = { + isa = PBXGroup; + children = ( + 49DEE3B024523BAA0070FA09 /* BluetoothService.swift */, + ); + path = Services; + sourceTree = ""; + }; + 49DEE3B124523BAA0070FA09 /* Views */ = { + isa = PBXGroup; + children = ( + 49DEE3B224523BAA0070FA09 /* DeviceCharacteristicCell.swift */, + 49DEE3B324523BAA0070FA09 /* Cells */, + 49DEE3B524523BAA0070FA09 /* ActivityIndicator.swift */, + 49DEE3B624523BAA0070FA09 /* DeviceView.swift */, + 49DEE3B724523BAA0070FA09 /* DevicesView.swift */, + 49DEE3B824523BAA0070FA09 /* DeviceServiceView.swift */, + ); + path = Views; + sourceTree = ""; + }; + 49DEE3B324523BAA0070FA09 /* Cells */ = { + isa = PBXGroup; + children = ( + 49DEE3B424523BAA0070FA09 /* DevicesViewCell.swift */, + ); + path = Cells; + sourceTree = ""; + }; + 49DEE3BC24523BAA0070FA09 /* ViewModels */ = { + isa = PBXGroup; + children = ( + 49DEE3BD24523BAA0070FA09 /* DeviceServiceCellViewModel.swift */, + 49DEE3BE24523BAA0070FA09 /* DeviceCharacteristicCellViewModel.swift */, + 49DEE3BF24523BAA0070FA09 /* DeviceServiceViewModel.swift */, + 49DEE3C024523BAA0070FA09 /* DevicesViewModel.swift */, + 49DEE3C124523BAA0070FA09 /* DevicesCellViewModel.swift */, + 49DEE3C224523BAA0070FA09 /* DeviceViewModel.swift */, + ); + path = ViewModels; + sourceTree = ""; + }; + 49DEE3D8245241440070FA09 /* Preview Content */ = { + isa = PBXGroup; + children = ( + 49DEE3D9245241440070FA09 /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 493702BF2452366900CAE3CF /* Blue-Falcon */ = { + isa = PBXNativeTarget; + buildConfigurationList = 493702D32452366A00CAE3CF /* Build configuration list for PBXNativeTarget "Blue-Falcon" */; + buildPhases = ( + 6938266890308D78F1793FBF /* [CP] Check Pods Manifest.lock */, + 493702BC2452366900CAE3CF /* Sources */, + 493702BD2452366900CAE3CF /* Frameworks */, + 493702BE2452366900CAE3CF /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Blue-Falcon"; + productName = "Blue-Falcon"; + productReference = 493702C02452366900CAE3CF /* Blue-Falcon.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 493702B82452366900CAE3CF /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1140; + LastUpgradeCheck = 1140; + ORGANIZATIONNAME = "Blue-Falcon"; + TargetAttributes = { + 493702BF2452366900CAE3CF = { + CreatedOnToolsVersion = 11.4.1; + }; + }; + }; + buildConfigurationList = 493702BB2452366900CAE3CF /* Build configuration list for PBXProject "Blue-Falcon" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 493702B72452366900CAE3CF; + productRefGroup = 493702C12452366900CAE3CF /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 493702BF2452366900CAE3CF /* Blue-Falcon */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 493702BE2452366900CAE3CF /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 49DEE3DD24535DF60070FA09 /* Main.storyboard in Resources */, + 49DEE3DA245241440070FA09 /* Preview Assets.xcassets in Resources */, + 49DEE3C624523BAA0070FA09 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 6938266890308D78F1793FBF /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Blue-Falcon-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 493702BC2452366900CAE3CF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 49DEE3D024523BAA0070FA09 /* DeviceServiceCellViewModel.swift in Sources */, + 49DEE3D324523BAA0070FA09 /* DevicesViewModel.swift in Sources */, + 49DEE3C324523BAA0070FA09 /* BluetoothConnectionState.swift in Sources */, + 49DEE3C924523BAA0070FA09 /* DeviceCharacteristicCell.swift in Sources */, + 49DEE3D224523BAA0070FA09 /* DeviceServiceViewModel.swift in Sources */, + 49DEE3CA24523BAA0070FA09 /* DevicesViewCell.swift in Sources */, + 49DEE3C824523BAA0070FA09 /* BluetoothService.swift in Sources */, + 49DEE3D424523BAA0070FA09 /* DevicesCellViewModel.swift in Sources */, + 49DEE382245239B00070FA09 /* Strings.swift in Sources */, + 49DEE3CD24523BAA0070FA09 /* DevicesView.swift in Sources */, + 49DEE3D524523BAA0070FA09 /* DeviceViewModel.swift in Sources */, + 49DEE3CC24523BAA0070FA09 /* DeviceView.swift in Sources */, + 49DEE3CB24523BAA0070FA09 /* ActivityIndicator.swift in Sources */, + 49DEE3C424523BAA0070FA09 /* Descriptor.swift in Sources */, + 49DEE3CE24523BAA0070FA09 /* DeviceServiceView.swift in Sources */, + 493702C42452366900CAE3CF /* AppDelegate.swift in Sources */, + 49DEE3C524523BAA0070FA09 /* BluetoothScanningState.swift in Sources */, + 49DEE3D124523BAA0070FA09 /* DeviceCharacteristicCellViewModel.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 49DEE3DB24535DF60070FA09 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 49DEE3DC24535DF60070FA09 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 493702D12452366A00CAE3CF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 493702D22452366A00CAE3CF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 493702D42452366A00CAE3CF /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 48647C8E9CB6F44A7C3208D5 /* Pods-Blue-Falcon.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = "Blue-Falcon/Blue_Falcon.entitlements"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_ASSET_PATHS = "Blue-Falcon/Preview\\ Content"; + DEVELOPMENT_TEAM = 269C5Q4423; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = NO; + INFOPLIST_FILE = "Blue-Falcon/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.15; + PRODUCT_BUNDLE_IDENTIFIER = "dev.bluefalcon.Blue-Falcon"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 493702D52452366A00CAE3CF /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = EE00871EDF110355EF35AAE6 /* Pods-Blue-Falcon.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = "Blue-Falcon/Blue_Falcon.entitlements"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_ASSET_PATHS = "Blue-Falcon/Preview\\ Content"; + DEVELOPMENT_TEAM = 269C5Q4423; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = NO; + INFOPLIST_FILE = "Blue-Falcon/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.15; + PRODUCT_BUNDLE_IDENTIFIER = "dev.bluefalcon.Blue-Falcon"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 493702BB2452366900CAE3CF /* Build configuration list for PBXProject "Blue-Falcon" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 493702D12452366A00CAE3CF /* Debug */, + 493702D22452366A00CAE3CF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 493702D32452366A00CAE3CF /* Build configuration list for PBXNativeTarget "Blue-Falcon" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 493702D42452366A00CAE3CF /* Debug */, + 493702D52452366A00CAE3CF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 493702B82452366900CAE3CF /* Project object */; +} diff --git a/examples/macOS/Blue-Falcon.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples/macOS/Blue-Falcon.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..2e60eaa --- /dev/null +++ b/examples/macOS/Blue-Falcon.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples/macOS/Blue-Falcon.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/examples/macOS/Blue-Falcon.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/examples/macOS/Blue-Falcon.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/examples/macOS/Blue-Falcon.xcworkspace/contents.xcworkspacedata b/examples/macOS/Blue-Falcon.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..6804777 --- /dev/null +++ b/examples/macOS/Blue-Falcon.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/examples/macOS/Blue-Falcon.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/examples/macOS/Blue-Falcon.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/examples/macOS/Blue-Falcon.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/examples/macOS/Blue-Falcon/AppDelegate.swift b/examples/macOS/Blue-Falcon/AppDelegate.swift new file mode 100644 index 0000000..503378c --- /dev/null +++ b/examples/macOS/Blue-Falcon/AppDelegate.swift @@ -0,0 +1,40 @@ +// +// AppDelegate.swift +// Blue-Falcon +// +// Created by Andrew Reed on 23/04/2020. +// Copyright © 2020 Blue-Falcon. All rights reserved. +// + +import Cocoa +import SwiftUI + +@NSApplicationMain +class AppDelegate: NSObject, NSApplicationDelegate { + + var window: NSWindow! + static let instance = (NSApplication.shared.delegate as? AppDelegate)! + let bluetoothService = BluetoothService() + + func applicationDidFinishLaunching(_ aNotification: Notification) { + // Create the SwiftUI view that provides the window contents. + let contentView = DevicesView() + + // Create the window and set the content view. + window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 480, height: 300), + styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView], + backing: .buffered, defer: false) + window.center() + window.setFrameAutosaveName("Main Window") + window.contentView = NSHostingView(rootView: contentView) + window.makeKeyAndOrderFront(nil) + } + + func applicationWillTerminate(_ aNotification: Notification) { + // Insert code here to tear down your application + } + + +} + diff --git a/examples/macOS/Blue-Falcon/Assets.xcassets/AppIcon.appiconset/Contents.json b/examples/macOS/Blue-Falcon/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d8db8d6 --- /dev/null +++ b/examples/macOS/Blue-Falcon/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "83.5x83.5", + "scale" : "2x" + }, + { + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/macOS/Blue-Falcon/Assets.xcassets/Contents.json b/examples/macOS/Blue-Falcon/Assets.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/examples/macOS/Blue-Falcon/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/macOS/Blue-Falcon/Base.lproj/Main.storyboard b/examples/macOS/Blue-Falcon/Base.lproj/Main.storyboard new file mode 100644 index 0000000..d935902 --- /dev/null +++ b/examples/macOS/Blue-Falcon/Base.lproj/Main.storyboard @@ -0,0 +1,683 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/macOS/Blue-Falcon/Blue_Falcon.entitlements b/examples/macOS/Blue-Falcon/Blue_Falcon.entitlements new file mode 100644 index 0000000..e9da8e1 --- /dev/null +++ b/examples/macOS/Blue-Falcon/Blue_Falcon.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.device.bluetooth + + com.apple.security.files.user-selected.read-only + + + diff --git a/examples/macOS/Blue-Falcon/Info.plist b/examples/macOS/Blue-Falcon/Info.plist new file mode 100644 index 0000000..3dc9828 --- /dev/null +++ b/examples/macOS/Blue-Falcon/Info.plist @@ -0,0 +1,40 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + Copyright © 2020 Blue-Falcon. All rights reserved. + NSMainStoryboardFile + Main + NSPrincipalClass + NSApplication + NSServices + + + + NSSupportsAutomaticTermination + + NSSupportsSuddenTermination + + + diff --git a/examples/macOS/Blue-Falcon/Models/BluetoothConnectionState.swift b/examples/macOS/Blue-Falcon/Models/BluetoothConnectionState.swift new file mode 100644 index 0000000..41986dc --- /dev/null +++ b/examples/macOS/Blue-Falcon/Models/BluetoothConnectionState.swift @@ -0,0 +1,23 @@ +// +// BluetoothConnectionState.swift +// Blue-Falcon +// +// Created by Andrew Reed on 01/09/2019. +// Copyright © 2019 Andrew Reed. All rights reserved. +// + +import CoreBluetooth + +extension CBPeripheralState { + + func displayText() -> String { + switch self { + case .connected: return "Connected" + case .connecting: return "Connecting..." + case .disconnected: return "Disconnected" + case .disconnecting: return "Disconnecting" + @unknown default: fatalError("Unexpected blueooth state") + } + } + +} diff --git a/examples/macOS/Blue-Falcon/Models/BluetoothScanningState.swift b/examples/macOS/Blue-Falcon/Models/BluetoothScanningState.swift new file mode 100644 index 0000000..f76ddfa --- /dev/null +++ b/examples/macOS/Blue-Falcon/Models/BluetoothScanningState.swift @@ -0,0 +1,16 @@ +// +// BluetoothScanningState.swift +// Blue-Falcon +// +// Created by Andrew Reed on 04/09/2019. +// Copyright © 2019 Andrew Reed. All rights reserved. +// + +import Foundation + +enum BluetoothScanningState: String { + + case scanning = "Scanning" + case notScanning = "Not Scanning" + +} diff --git a/examples/macOS/Blue-Falcon/Models/Descriptor.swift b/examples/macOS/Blue-Falcon/Models/Descriptor.swift new file mode 100644 index 0000000..2fa29ec --- /dev/null +++ b/examples/macOS/Blue-Falcon/Models/Descriptor.swift @@ -0,0 +1,14 @@ +// +// Descriptor.swift +// Blue-Falcon +// +// Created by Andrew Reed on 02/04/2020. +// Copyright © 2020 Andrew Reed. All rights reserved. +// + +import Foundation + +struct Descriptor { + let id = UUID() + let data: String +} diff --git a/examples/macOS/Blue-Falcon/Preview Content/Preview Assets.xcassets/Contents.json b/examples/macOS/Blue-Falcon/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/examples/macOS/Blue-Falcon/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/macOS/Blue-Falcon/Services/BluetoothService.swift b/examples/macOS/Blue-Falcon/Services/BluetoothService.swift new file mode 100644 index 0000000..54d4b2c --- /dev/null +++ b/examples/macOS/Blue-Falcon/Services/BluetoothService.swift @@ -0,0 +1,186 @@ +// +// BluetoothService.swift +// Blue-Falcon +// +// Created by Andrew Reed on 04/09/2019. +// Copyright © 2019 Andrew Reed. All rights reserved. +// + +import Foundation +import library +import CoreBluetooth +import Cocoa + +class BluetoothService { + + private let blueFalcon = BlueFalcon(context: NSView(), serviceUUID: nil) + private var devices: [BluetoothPeripheral] = [] + var detectedDeviceDelegates: [BluetoothServiceDetectedDeviceDelegate] = [] + var connectedDeviceDelegates: [(UUID, BluetoothServiceConnectedDeviceDelegate)] = [] + var characteristicDelegates: [(CBUUID, BluetoothServiceCharacteristicDelegate)] = [] + + //create some sort of notification queue which waits x seconds before refreshing. + + init() { + blueFalcon.delegates.add(self) + } + + func scan() throws { + try blueFalcon.scan() + } + + func connect(bluetoothPeripheral: BluetoothPeripheral) { + blueFalcon.connect(bluetoothPeripheral: bluetoothPeripheral) + } + + func notifyCharacteristic( + bluetoothPeripheral: BluetoothPeripheral, + bluetoothCharacteristic: CBCharacteristic, + notify: Bool + ) { + blueFalcon.notifyCharacteristic( + bluetoothPeripheral: bluetoothPeripheral, + bluetoothCharacteristic: BluetoothCharacteristic(characteristic: bluetoothCharacteristic), + notify: notify + ) + } + + func readCharacteristic( + bluetoothPeripheral: BluetoothPeripheral, + bluetoothCharacteristic: CBCharacteristic + ) { + blueFalcon.readCharacteristic( + bluetoothPeripheral: bluetoothPeripheral, + bluetoothCharacteristic: BluetoothCharacteristic(characteristic: bluetoothCharacteristic) + ) + } + + func writeCharacteristic( + bluetoothPeripheral: BluetoothPeripheral, + bluetoothCharacteristic: CBCharacteristic, + value: String + ) { + blueFalcon.writeCharacteristic( + bluetoothPeripheral: bluetoothPeripheral, + bluetoothCharacteristic: BluetoothCharacteristic(characteristic: bluetoothCharacteristic), + value: value + ) + } + + func readDescriptor( + bluetoothPeripheral: BluetoothPeripheral, + bluetoothCharacteristic: CBCharacteristic, + bluetoothDescriptor: CBDescriptor + ) { + blueFalcon.readDescriptor( + bluetoothPeripheral: bluetoothPeripheral, + bluetoothCharacteristic: BluetoothCharacteristic(characteristic: bluetoothCharacteristic), + bluetoothCharacteristicDescriptor: bluetoothDescriptor + ) + } + + func removeDetectedDeviceDelegate(delegate: BluetoothServiceDetectedDeviceDelegate) { + for (index, storedDelegate) in AppDelegate.instance.bluetoothService.detectedDeviceDelegates.enumerated() { + if delegate === storedDelegate { + AppDelegate.instance.bluetoothService.detectedDeviceDelegates.remove(at: index) + } + } + } + + func removeConnectedDeviceDelegate(delegate: BluetoothServiceConnectedDeviceDelegate) { + for (index, storedDelegate) in AppDelegate.instance.bluetoothService.connectedDeviceDelegates.enumerated() { + if delegate === storedDelegate.1 { + AppDelegate.instance.bluetoothService.connectedDeviceDelegates.remove(at: index) + } + } + } + + func removeCharacteristicDelegate(delegate: BluetoothServiceCharacteristicDelegate) { + for (index, storedDelegate) in AppDelegate.instance.bluetoothService.characteristicDelegates.enumerated() { + if delegate === storedDelegate.1 { + AppDelegate.instance.bluetoothService.characteristicDelegates.remove(at: index) + } + } + } + +} + +extension BluetoothService: BlueFalconDelegate { + + func didReadDescriptor(bluetoothPeripheral: BluetoothPeripheral, bluetoothCharacteristicDescriptor: CBDescriptor) { + print("BT Service didReadDescriptor -> \(bluetoothCharacteristicDescriptor.value)") + } + + func didRssiUpdate(bluetoothPeripheral: BluetoothPeripheral) { + } + + func didDiscoverDevice(bluetoothPeripheral: BluetoothPeripheral) { + guard (devices.first { + $0.bluetoothDevice.identifier == bluetoothPeripheral.bluetoothDevice.identifier + } == nil) else { return } + devices.append(bluetoothPeripheral) + detectedDeviceDelegates.forEach { delegate in + delegate.discoveredDevice(devices: devices) + } + } + + func didConnect(bluetoothPeripheral: BluetoothPeripheral) { + bluetoothServiceConnectedDeviceDelegates(bluetoothPeripheralId: bluetoothPeripheral.bluetoothDevice.identifier) + .forEach { bluetoothServiceConnectedDeviceDelegate in + bluetoothServiceConnectedDeviceDelegate.connectedDevice() + } + } + + func didDisconnect(bluetoothPeripheral: BluetoothPeripheral) { + + } + + func didDiscoverServices(bluetoothPeripheral: BluetoothPeripheral) { + bluetoothServiceConnectedDeviceDelegates(bluetoothPeripheralId: bluetoothPeripheral.bluetoothDevice.identifier) + .forEach { bluetoothServiceConnectedDeviceDelegate in + bluetoothServiceConnectedDeviceDelegate.discoveredServices() + } + } + + func didDiscoverCharacteristics(bluetoothPeripheral: BluetoothPeripheral) { + } + + func didCharacteristcValueChanged( + bluetoothPeripheral: BluetoothPeripheral, + bluetoothCharacteristic: BluetoothCharacteristic + ) { + bluetoothServiceCharacteristicDelegates(bluetoothCharacteristicId: bluetoothCharacteristic.characteristic.uuid) + .forEach { bluetoothServiceCharacteristicDelegate in + bluetoothServiceCharacteristicDelegate.characteristcValueChanged() + } + } + + func didUpdateMTU(bluetoothPeripheral: BluetoothPeripheral) {} + + private func bluetoothServiceConnectedDeviceDelegates(bluetoothPeripheralId: UUID) -> [BluetoothServiceConnectedDeviceDelegate] { + return connectedDeviceDelegates.compactMap { connectedDeviceDelegateTuple -> BluetoothServiceConnectedDeviceDelegate? in + connectedDeviceDelegateTuple.0 == bluetoothPeripheralId ? connectedDeviceDelegateTuple.1 : nil + } + } + + private func bluetoothServiceCharacteristicDelegates(bluetoothCharacteristicId: CBUUID) -> [BluetoothServiceCharacteristicDelegate] { + return characteristicDelegates.compactMap { characteristicDelegateTuple -> BluetoothServiceCharacteristicDelegate? in + characteristicDelegateTuple.0 == bluetoothCharacteristicId ? characteristicDelegateTuple.1 : nil + } + } + +} + + +protocol BluetoothServiceDetectedDeviceDelegate: class { + func discoveredDevice(devices: [BluetoothPeripheral]) +} + +protocol BluetoothServiceConnectedDeviceDelegate: class { + func connectedDevice() + func discoveredServices() +} + +protocol BluetoothServiceCharacteristicDelegate: class { + func characteristcValueChanged() +} diff --git a/examples/macOS/Blue-Falcon/Strings.swift b/examples/macOS/Blue-Falcon/Strings.swift new file mode 100644 index 0000000..d93cffb --- /dev/null +++ b/examples/macOS/Blue-Falcon/Strings.swift @@ -0,0 +1,20 @@ +// +// Strings.swift +// Blue-Falcon +// +// Created by Andrew Reed on 01/09/2019. +// Copyright © 2019 Andrew Reed. All rights reserved. +// + +import Foundation + +struct Strings { + struct Errors { + struct Bluetooth { + static let permission = "Please accept the bluetooth permission for the app" + static let notEnabled = "Bluetooth is not enabled for this device" + static let unsupported = "Bluetooth is not supported for this device" + static let resetting = "Bluetooth adapter is currently resetting" + } + } +} diff --git a/examples/macOS/Blue-Falcon/ViewModels/DeviceCharacteristicCellViewModel.swift b/examples/macOS/Blue-Falcon/ViewModels/DeviceCharacteristicCellViewModel.swift new file mode 100644 index 0000000..eb07def --- /dev/null +++ b/examples/macOS/Blue-Falcon/ViewModels/DeviceCharacteristicCellViewModel.swift @@ -0,0 +1,96 @@ +// +// DeviceCharacteristicCellViewModel.swift +// Blue-Falcon +// +// Created by Andrew Reed on 24/08/2019. +// Copyright © 2019 Andrew Reed. All rights reserved. +// + +import Foundation +import CoreBluetooth +import library + +class DeviceCharacteristicCellViewModel: Identifiable, ObservableObject { + + var id: CBUUID + let name: String + let characteristic: CBCharacteristic + let descriptors: [CBDescriptor] + var descriptorData: [Descriptor] { + descriptors.compactMap { + guard let data = $0.value as? Data else { return nil } + let dataString = String(decoding: data, as: UTF8.self) + return Descriptor(data: dataString) + } + } + let device: BluetoothPeripheral + @Published var notify: Bool = false + @Published var characterisicValue: String? = nil + @Published var reading: Bool = false + + init(id: CBUUID, characteristic: CBCharacteristic, device: BluetoothPeripheral) { + self.id = id + self.name = characteristic.uuid.description + self.characteristic = characteristic + self.device = device + self.descriptors = characteristic.descriptors ?? [] + } + + func onAppear() { + AppDelegate.instance.bluetoothService.characteristicDelegates.append((characteristic.uuid, self)) + } + + func onDisapear() { + AppDelegate.instance.bluetoothService.removeCharacteristicDelegate(delegate: self) + } + + //consider moving to the characteristic cell view model. + func readCharacteristicTapped(_ characteristic: CBCharacteristic) { + reading = true + AppDelegate.instance.bluetoothService.readCharacteristic( + bluetoothPeripheral: device, + bluetoothCharacteristic: characteristic + ) + } + + func notifyCharacteristicTapped(_ characteristic: CBCharacteristic) { + notify = !notify + AppDelegate.instance.bluetoothService.notifyCharacteristic( + bluetoothPeripheral: device, + bluetoothCharacteristic: characteristic, + notify: notify + ) + } + + func writeCharacteristicTapped(_ characteristic: CBCharacteristic) { + /*let alert = UIAlertController( + title: "Characteristic Write", + message: "Please enter a value to write to the characteristic", + preferredStyle: .alert + ) + alert.addTextField { textfield in + textfield.placeholder = "Enter value" + } + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { _ in + guard let input = alert.textFields?.first?.text else { return } + AppDelegate.instance.bluetoothService.writeCharacteristic( + bluetoothPeripheral: self.device, + bluetoothCharacteristic: characteristic, + value: input + ) + })) + alert.addAction(UIAlertAction(title: "Cancel", style: .cancel)) + SceneDelegate.instance.window?.rootViewController?.present(alert, animated: true)*/ + } +} + +extension DeviceCharacteristicCellViewModel: BluetoothServiceCharacteristicDelegate { + + func characteristcValueChanged() { + reading = false + guard let characteristicData = characteristic.value else { return } + characterisicValue = String(decoding: characteristicData, as: UTF8.self) + } + +} + diff --git a/examples/macOS/Blue-Falcon/ViewModels/DeviceServiceCellViewModel.swift b/examples/macOS/Blue-Falcon/ViewModels/DeviceServiceCellViewModel.swift new file mode 100644 index 0000000..cff167e --- /dev/null +++ b/examples/macOS/Blue-Falcon/ViewModels/DeviceServiceCellViewModel.swift @@ -0,0 +1,15 @@ +// +// DeviceServiceCellViewModel.swift +// Blue-Falcon +// +// Created by Andrew Reed on 24/08/2019. +// Copyright © 2019 Andrew Reed. All rights reserved. +// + +import Foundation +import CoreBluetooth + +struct DeviceServiceCellViewModel: Identifiable { + var id: CBUUID + let service: CBService +} diff --git a/examples/macOS/Blue-Falcon/ViewModels/DeviceServiceViewModel.swift b/examples/macOS/Blue-Falcon/ViewModels/DeviceServiceViewModel.swift new file mode 100644 index 0000000..6172bee --- /dev/null +++ b/examples/macOS/Blue-Falcon/ViewModels/DeviceServiceViewModel.swift @@ -0,0 +1,38 @@ +// +// DeviceServiceViewModel.swift +// Blue-Falcon +// +// Created by Andrew Reed on 24/08/2019. +// Copyright © 2019 Andrew Reed. All rights reserved. +// + +import SwiftUI +import library +import Combine +import CoreBluetooth + +class DeviceServiceViewModel: ObservableObject { + + let service: CBService + let device: BluetoothPeripheral + var characteristics: [CBCharacteristic] = [] + @Published var deviceCharacteristicCellViewModels: [DeviceCharacteristicCellViewModel] = [] + + init(service: CBService, device: BluetoothPeripheral) { + self.service = service + self.device = device + self.characteristics = service.characteristics ?? [] + createViewModelsFromCharacteristics() + } + + private func createViewModelsFromCharacteristics() { + deviceCharacteristicCellViewModels = characteristics.map { characteristic -> DeviceCharacteristicCellViewModel in + DeviceCharacteristicCellViewModel( + id: characteristic.uuid, + characteristic: characteristic, + device: device + ) + } + } + +} diff --git a/examples/macOS/Blue-Falcon/ViewModels/DeviceViewModel.swift b/examples/macOS/Blue-Falcon/ViewModels/DeviceViewModel.swift new file mode 100644 index 0000000..b4d825c --- /dev/null +++ b/examples/macOS/Blue-Falcon/ViewModels/DeviceViewModel.swift @@ -0,0 +1,57 @@ +// +// DeviceViewModel.swift +// Blue-Falcon +// +// Created by Andrew Reed on 24/08/2019. +// Copyright © 2019 Andrew Reed. All rights reserved. +// + +import Foundation +import library +import CoreBluetooth +import Combine + +class DeviceViewModel: ObservableObject { + + @Published var state: CBPeripheralState = .connecting + @Published var deviceServiceCellViewModels: [DeviceServiceCellViewModel] = [] + + let device: BluetoothPeripheral + let title: String + + init(device: BluetoothPeripheral) { + self.device = device + title = "\(device.bluetoothDevice.identifier.uuidString) \(device.name ?? "")" + } + + func onAppear() { + AppDelegate.instance.bluetoothService.connectedDeviceDelegates.append((device.bluetoothDevice.identifier, self)) + connect() + } + + func onDisapear() { + AppDelegate.instance.bluetoothService.removeConnectedDeviceDelegate(delegate: self) + } + + func connect() { + AppDelegate.instance.bluetoothService.connect(bluetoothPeripheral: device) + } + +} + +extension DeviceViewModel: BluetoothServiceConnectedDeviceDelegate { + + func connectedDevice() { + state = .connected + } + + func discoveredServices() { + guard let services = device.bluetoothDevice.services else { return } + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { + self.deviceServiceCellViewModels = services.map({ service -> DeviceServiceCellViewModel in + DeviceServiceCellViewModel(id: service.uuid, service: service) + }) + } + } + +} diff --git a/examples/macOS/Blue-Falcon/ViewModels/DevicesCellViewModel.swift b/examples/macOS/Blue-Falcon/ViewModels/DevicesCellViewModel.swift new file mode 100644 index 0000000..c83556b --- /dev/null +++ b/examples/macOS/Blue-Falcon/ViewModels/DevicesCellViewModel.swift @@ -0,0 +1,17 @@ +// +// DevicesCellViewModel.swift +// Blue-Falcon +// +// Created by Andrew Reed on 24/08/2019. +// Copyright © 2019 Andrew Reed. All rights reserved. +// + +import Foundation +import CoreBluetooth +import library + +struct DevicesCellViewModel: Identifiable { + var id: String + let name: String + let device: BluetoothPeripheral +} diff --git a/examples/macOS/Blue-Falcon/ViewModels/DevicesViewModel.swift b/examples/macOS/Blue-Falcon/ViewModels/DevicesViewModel.swift new file mode 100644 index 0000000..77075fb --- /dev/null +++ b/examples/macOS/Blue-Falcon/ViewModels/DevicesViewModel.swift @@ -0,0 +1,50 @@ +// +// DevicesViewModel.swift +// Blue-Falcon +// +// Created by Andrew Reed on 24/08/2019. +// Copyright © 2019 Andrew Reed. All rights reserved. +// + +import Foundation +import library +import CoreBluetooth +import Combine + +class DevicesViewModel: ObservableObject { + + @Published var devicesViewModels: [DevicesCellViewModel] = [] + @Published var status: BluetoothScanningState = .notScanning + + func onAppear() { + AppDelegate.instance.bluetoothService.detectedDeviceDelegates.append(self) + } + + func onDisapear() { + AppDelegate.instance.bluetoothService.removeDetectedDeviceDelegate(delegate: self) + } + + func scan() throws { + try AppDelegate.instance.bluetoothService.scan() + status = .scanning + } + +} + +extension DevicesViewModel: BluetoothServiceDetectedDeviceDelegate { + + func discoveredDevice(devices: [BluetoothPeripheral]) { + devicesViewModels = devices.map { device -> DevicesCellViewModel in + var deviceName = "" + if let name = device.name { + deviceName += " \(name)" + } + return DevicesCellViewModel( + id: device.bluetoothDevice.identifier.uuidString, + name: deviceName, + device: device + ) + } + } + +} diff --git a/examples/macOS/Blue-Falcon/Views/ActivityIndicator.swift b/examples/macOS/Blue-Falcon/Views/ActivityIndicator.swift new file mode 100644 index 0000000..41aa4a1 --- /dev/null +++ b/examples/macOS/Blue-Falcon/Views/ActivityIndicator.swift @@ -0,0 +1,25 @@ +// +// ActivityIndicator.swift +// Blue-Falcon +// +// Created by Andrew Reed on 01/09/2019. +// Copyright © 2019 Andrew Reed. All rights reserved. +// +import SwiftUI +import Cocoa + +struct ActivityIndicator: NSViewRepresentable { + func makeNSView(context: Context) -> NSProgressIndicator { + let indicator = NSProgressIndicator() + indicator.style = .spinning + return indicator + } + + func updateNSView(_ nsView: NSProgressIndicator, context: Context) { + isAnimating ? nsView.startAnimation(nil) : nsView.stopAnimation(nil) + } + + typealias NSViewType = NSProgressIndicator + + @Binding var isAnimating: Bool +} diff --git a/examples/macOS/Blue-Falcon/Views/Cells/DevicesViewCell.swift b/examples/macOS/Blue-Falcon/Views/Cells/DevicesViewCell.swift new file mode 100644 index 0000000..1954df4 --- /dev/null +++ b/examples/macOS/Blue-Falcon/Views/Cells/DevicesViewCell.swift @@ -0,0 +1,31 @@ +// +// DevicesViewCell.swift +// Blue-Falcon +// +// Created by Andrew Reed on 31/08/2019. +// Copyright © 2019 Andrew Reed. All rights reserved. +// + +import SwiftUI +import library +import Combine + +struct DevicesViewCell : View { + + let name: String + let deviceId: String + + var body: some View { + VStack(alignment: .leading) { + if !name.isEmpty { + HStack { + Text("Name:") + .bold() + Text(name) + .italic() + }.padding(EdgeInsets(top: 5, leading: 0, bottom: 0, trailing: 0)) + } + Text(deviceId) + } + } +} diff --git a/examples/macOS/Blue-Falcon/Views/DeviceCharacteristicCell.swift b/examples/macOS/Blue-Falcon/Views/DeviceCharacteristicCell.swift new file mode 100644 index 0000000..346baf0 --- /dev/null +++ b/examples/macOS/Blue-Falcon/Views/DeviceCharacteristicCell.swift @@ -0,0 +1,85 @@ +// +// DeviceCharacteristicCell.swift +// Blue-Falcon +// +// Created by Andrew Reed on 28/08/2019. +// Copyright © 2019 Andrew Reed. All rights reserved. +// + +import Foundation +import SwiftUI + +struct DeviceCharacteristicCell: View { + + @ObservedObject private var viewModel: DeviceCharacteristicCellViewModel + + init(viewModel: DeviceCharacteristicCellViewModel) { + self.viewModel = viewModel + } + + var body: some View { + VStack(alignment: HorizontalAlignment.leading, spacing: 10) { + VStack(alignment: HorizontalAlignment.leading, spacing: 10) { + HStack { + Text("ID: ").bold() + Text(viewModel.name) + } + if viewModel.reading { + HStack { + Text("Reading...") + //ActivityIndicator(isAnimating: .constant(true), style: .medium) + } + } + if viewModel.characterisicValue != nil { + HStack { + Text("Value: ").bold() + Text(viewModel.characterisicValue!) + .lineLimit(nil) + .padding() + } + } + } + HStack(alignment: .center) { + Spacer() + Group { + Button(action: { + self.viewModel.readCharacteristicTapped(self.viewModel.characteristic) + }) { + Text("Read") + } + } + Group { + Button(action: { + self.viewModel.notifyCharacteristicTapped(self.viewModel.characteristic) + }) { + Text("Notify \(viewModel.notify.description)") + } + } + Group { + Button(action: { + self.viewModel.writeCharacteristicTapped(self.viewModel.characteristic) + }) { + Text("Write") + } + } + Spacer() + }.padding() + if !self.viewModel.descriptors.isEmpty { + VStack(alignment: HorizontalAlignment.leading, spacing: 10) { + HStack { + Text("Descriptors: ").bold() + ForEach(self.viewModel.descriptorData, id: \.id) { descriptor in + Text("\(descriptor.id) \(descriptor.data)") + } + } + }.padding() + } + } + .onAppear { + self.viewModel.onAppear() + } + .onDisappear { + self.viewModel.onDisapear() + } + } +} diff --git a/examples/macOS/Blue-Falcon/Views/DeviceServiceView.swift b/examples/macOS/Blue-Falcon/Views/DeviceServiceView.swift new file mode 100644 index 0000000..d62c0c8 --- /dev/null +++ b/examples/macOS/Blue-Falcon/Views/DeviceServiceView.swift @@ -0,0 +1,38 @@ +// +// DeviceServiceView.swift +// Blue-Falcon +// +// Created by Andrew Reed on 24/08/2019. +// Copyright © 2019 Andrew Reed. All rights reserved. +// + +import SwiftUI +import library +import Combine +import CoreBluetooth + +struct DeviceServiceView : View { + + @ObservedObject var deviceServiceViewModel: DeviceServiceViewModel + + var body: some View { + VStack(alignment: .leading) { + Text(deviceServiceViewModel.characteristics.isEmpty ? "" : "Characteristics") + .bold() + .padding(10) + //.navigationBarTitle(Text(deviceServiceViewModel.service.uuid.uuidString)) + List(deviceServiceViewModel.deviceCharacteristicCellViewModels) { viewModel in + DeviceCharacteristicCell(viewModel: viewModel) + } + } + } + +} + +#if DEBUG +/*struct DeviceServiceView_Previews : PreviewProvider { + static var previews: some View { + //DeviceServiceView() + } +}*/ +#endif diff --git a/examples/macOS/Blue-Falcon/Views/DeviceView.swift b/examples/macOS/Blue-Falcon/Views/DeviceView.swift new file mode 100644 index 0000000..f508973 --- /dev/null +++ b/examples/macOS/Blue-Falcon/Views/DeviceView.swift @@ -0,0 +1,61 @@ +// +// DeviceView.swift +// Blue-Falcon +// +// Created by Andrew Reed on 24/08/2019. +// Copyright © 2019 Andrew Reed. All rights reserved. +// + +import SwiftUI +import library +import Combine +import CoreBluetooth + +struct DeviceView : View { + + @ObservedObject var deviceViewModel: DeviceViewModel + + var body: some View { + NavigationView { + VStack(alignment: HorizontalAlignment.leading, spacing: 10) { + HStack { + Text(deviceViewModel.state.displayText()) + if deviceViewModel.state == CBPeripheralState.connecting { + ActivityIndicator(isAnimating: .constant(true)) + } + }.padding(10) + Text(deviceViewModel.deviceServiceCellViewModels.isEmpty ? "" : "Services") + .bold() + .padding(10) + List(deviceViewModel.deviceServiceCellViewModels) { viewModel in + NavigationLink( + destination: DeviceServiceView( + deviceServiceViewModel: DeviceServiceViewModel( + service: viewModel.service, + device: self.deviceViewModel.device + ) + ) + ) { + Text(viewModel.id.uuidString) + } + } + } + } + //.navigationBarTitle(deviceViewModel.title) + .onAppear { + self.deviceViewModel.onAppear() + } + .onDisappear { + self.deviceViewModel.onDisapear() + } + } + +} + +#if DEBUG +/*struct DeviceView_Previews : PreviewProvider { + static var previews: some View { + //DeviceView() + } +}*/ +#endif diff --git a/examples/macOS/Blue-Falcon/Views/DevicesView.swift b/examples/macOS/Blue-Falcon/Views/DevicesView.swift new file mode 100644 index 0000000..12c00e2 --- /dev/null +++ b/examples/macOS/Blue-Falcon/Views/DevicesView.swift @@ -0,0 +1,94 @@ +// +// DevicesView.swift +// Blue-Falcon +// +// Created by Andrew Reed on 16/08/2019. +// Copyright © 2019 Andrew Reed. All rights reserved. +// + +import SwiftUI +import library +import Combine + +struct DevicesView : View { + + @ObservedObject var viewModel = DevicesViewModel() + + var body: some View { + NavigationView { + VStack(alignment: .leading) { + HStack { + Text("Bluetooth Status:") + Text(viewModel.status == .scanning ? "Scanning" : "Not Scanning") + if viewModel.status == .scanning { + ActivityIndicator(isAnimating: .constant(true)) + } + }.padding() + List(viewModel.devicesViewModels) { deviceViewModel in + NavigationLink( + destination: DeviceView( + deviceViewModel: DeviceViewModel( + device: deviceViewModel.device + ) + ) + ) { + DevicesViewCell( + name: deviceViewModel.name, + deviceId: deviceViewModel.id + ) + } + } + //.navigationBarTitle(Text("Blue Falcon Devices")) + } + .onAppear { + self.viewModel.onAppear() + self.scan() + } + .onDisappear { + self.viewModel.onDisapear() + } + } + } + + //the old way would be to perform this in the uiview controller. + private func scan() { + do { + try viewModel.scan() + } catch { + let error = error as NSError + switch error.userInfo["KotlinException"] { + case is BluetoothPermissionException: + showError(message: Strings.Errors.Bluetooth.permission) + case is BluetoothNotEnabledException: + showError(message: Strings.Errors.Bluetooth.notEnabled) + case is BluetoothUnsupportedException: + showError(message: Strings.Errors.Bluetooth.unsupported) + case is BluetoothResettingException: + showError(message: Strings.Errors.Bluetooth.resetting) + default: + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + self.scan() + } + } + } + } + + private func showError(message: String) { +// let alert = UIAlertController( +// title: "Bluetooth Error", +// message: message, +// preferredStyle: .alert +// ) +// alert.addAction(UIAlertAction(title: "OK", style: .default)) +// SceneDelegate.instance.window?.rootViewController?.present(alert, animated: true) + } + +} + +#if DEBUG +struct DevicesView_Previews : PreviewProvider { + static var previews: some View { + DevicesView() + } +} +#endif diff --git a/examples/macOS/Podfile b/examples/macOS/Podfile new file mode 100755 index 0000000..d2b62c9 --- /dev/null +++ b/examples/macOS/Podfile @@ -0,0 +1,16 @@ +source 'https://github.com/CocoaPods/Specs.git' + +platform :ios, '13.0' +use_frameworks! +inhibit_all_warnings! + +def required_pods + + pod 'library', :path => '../../library' + + +end + +target "Blue-Falcon" do + required_pods +end diff --git a/examples/macOS/Podfile.lock b/examples/macOS/Podfile.lock new file mode 100644 index 0000000..ce028ad --- /dev/null +++ b/examples/macOS/Podfile.lock @@ -0,0 +1,16 @@ +PODS: + - library (0.6.0) + +DEPENDENCIES: + - library (from `../../library`) + +EXTERNAL SOURCES: + library: + :path: "../../library" + +SPEC CHECKSUMS: + library: 2d73fa9daeefcbc684728dbe764d3b396b433a3c + +PODFILE CHECKSUM: 2ccc63dfdfcd667b82d0a91dccf64f28b91bff7f + +COCOAPODS: 1.9.1 diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 2d0c86f..3bac163 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -71,6 +71,7 @@ kotlin { iosArm64() iosX64() + macosX64() sourceSets { val commonMain by getting { diff --git a/library/src/macosX64Main/kotlin/dev/bluefalcon/ApplicationContext.kt b/library/src/macosX64Main/kotlin/dev/bluefalcon/ApplicationContext.kt new file mode 100644 index 0000000..cbd6d80 --- /dev/null +++ b/library/src/macosX64Main/kotlin/dev/bluefalcon/ApplicationContext.kt @@ -0,0 +1,5 @@ +package dev.bluefalcon + +import platform.AppKit.NSView + +actual typealias ApplicationContext = NSView \ No newline at end of file diff --git a/library/src/macosX64Main/kotlin/dev/bluefalcon/BlueFalcon.kt b/library/src/macosX64Main/kotlin/dev/bluefalcon/BlueFalcon.kt new file mode 100644 index 0000000..90d89d3 --- /dev/null +++ b/library/src/macosX64Main/kotlin/dev/bluefalcon/BlueFalcon.kt @@ -0,0 +1,209 @@ +package dev.bluefalcon + +import platform.CoreBluetooth.* +import platform.Foundation.* +import platform.darwin.NSObject + +actual class BlueFalcon actual constructor( + private val context: ApplicationContext, + private val serviceUUID: String? +) { + actual val delegates: MutableSet = mutableSetOf() + + private val centralManager: CBCentralManager + private val bluetoothPeripheralManager = BluetoothPeripheralManager() + private val peripheralDelegate = PeripheralDelegate() + actual var isScanning: Boolean = false + + init { + centralManager = CBCentralManager(bluetoothPeripheralManager, null) + } + + actual fun connect(bluetoothPeripheral: BluetoothPeripheral) { + centralManager.connectPeripheral(bluetoothPeripheral.bluetoothDevice, null) + } + + actual fun disconnect(bluetoothPeripheral: BluetoothPeripheral) { + centralManager.cancelPeripheralConnection(bluetoothPeripheral.bluetoothDevice) + } + + @Throws + actual fun scan() { + isScanning = true + when(centralManager.state) { + CBManagerStateUnknown -> throw BluetoothUnknownException() + CBManagerStateResetting -> throw BluetoothResettingException() + CBManagerStateUnsupported -> throw BluetoothUnsupportedException() + CBManagerStateUnauthorized -> throw BluetoothPermissionException() + CBManagerStatePoweredOff -> throw BluetoothNotEnabledException() + CBManagerStatePoweredOn -> { + if (serviceUUID != null) { + centralManager.scanForPeripheralsWithServices(listOf(serviceUUID), null) + } else { + centralManager.scanForPeripheralsWithServices(null, null) + } + } + } + } + + actual fun stopScanning() { + isScanning = false + centralManager.stopScan() + } + + actual fun readCharacteristic( + bluetoothPeripheral: BluetoothPeripheral, + bluetoothCharacteristic: BluetoothCharacteristic + ) { + bluetoothPeripheral.bluetoothDevice.readValueForCharacteristic(bluetoothCharacteristic.characteristic) + } + + actual fun notifyCharacteristic( + bluetoothPeripheral: BluetoothPeripheral, + bluetoothCharacteristic: BluetoothCharacteristic, + notify: Boolean + ) { + bluetoothPeripheral.bluetoothDevice.setNotifyValue(notify, bluetoothCharacteristic.characteristic) + } + + actual fun writeCharacteristic( + bluetoothPeripheral: BluetoothPeripheral, + bluetoothCharacteristic: BluetoothCharacteristic, + value: String + ) { + val formattedString = NSString.create(string = value) + formattedString.dataUsingEncoding(NSUTF8StringEncoding)?.let { + bluetoothPeripheral.bluetoothDevice.writeValue( + it, + bluetoothCharacteristic.characteristic, + CBCharacteristicWriteWithResponse + ) + } + } + + actual fun readDescriptor( + bluetoothPeripheral: BluetoothPeripheral, + bluetoothCharacteristic: BluetoothCharacteristic, + bluetoothCharacteristicDescriptor: BluetoothCharacteristicDescriptor + ) { + bluetoothPeripheral.bluetoothDevice.discoverDescriptorsForCharacteristic(bluetoothCharacteristic.characteristic) + } + + actual fun changeMTU(bluetoothPeripheral: BluetoothPeripheral, mtuSize: Int) { + println("Change MTU size called but not needed.") + delegates.forEach { + it.didUpdateMTU(bluetoothPeripheral) + } + } + + inner class BluetoothPeripheralManager: NSObject(), CBCentralManagerDelegateProtocol { + override fun centralManagerDidUpdateState(central: CBCentralManager) { + when (central.state) { + CBManagerStateUnknown -> log("State 0 is .unknown") + CBManagerStateResetting -> log("State 1 is .resetting") + CBManagerStateUnsupported -> log("State 2 is .unsupported") + CBManagerStateUnauthorized -> log("State 3 is .unauthorised") + CBManagerStatePoweredOff -> log("State 4 is .poweredOff") + CBManagerStatePoweredOn -> log("State 5 is .poweredOn") + else -> log("State ${central.state.toInt()}") + } + } + + override fun centralManager( + central: CBCentralManager, + didDiscoverPeripheral: CBPeripheral, + advertisementData: Map, + RSSI: NSNumber + ) { + if (isScanning) { + log("Discovered device ${didDiscoverPeripheral.name}") + val device = BluetoothPeripheral(didDiscoverPeripheral, rssiValue = RSSI.floatValue) + delegates.forEach { + it.didDiscoverDevice(device) + } + } + } + + override fun centralManager(central: CBCentralManager, didConnectPeripheral: CBPeripheral) { + log("DidConnectPeripheral ${didConnectPeripheral.name}") + val device = BluetoothPeripheral(didConnectPeripheral, rssiValue = null) + delegates.forEach { + it.didConnect(device) + } + didConnectPeripheral.delegate = peripheralDelegate + didConnectPeripheral.discoverServices(null) + } + + override fun centralManager(central: CBCentralManager, didDisconnectPeripheral: CBPeripheral, error: NSError?) { + log("DidDisconnectPeripheral ${didDisconnectPeripheral.name}") + val device = BluetoothPeripheral(didDisconnectPeripheral, rssiValue = null) + delegates.forEach { + it.didDisconnect(device) + } + } + + } + + inner class PeripheralDelegate: NSObject(), CBPeripheralDelegateProtocol { + + override fun peripheral( + peripheral: CBPeripheral, + didDiscoverServices: NSError? + ) { + if (didDiscoverServices != null) { + println("Error with service discovery ${didDiscoverServices}") + } else { + val device = BluetoothPeripheral(peripheral, rssiValue = null) + delegates.forEach { + it.didDiscoverServices(device) + } + peripheral.services + ?.mapNotNull { it as? CBService } + ?.forEach { + peripheral.discoverCharacteristics(null, it) + } + } + } + + override fun peripheral( + peripheral: CBPeripheral, + didDiscoverCharacteristicsForService: CBService, + error: NSError? + ) { + if (error != null) { + println("Error with characteristic discovery ${didDiscoverCharacteristicsForService}") + } + val device = BluetoothPeripheral(peripheral, rssiValue = null) + delegates.forEach { + it.didDiscoverCharacteristics(device) + } + BluetoothService(didDiscoverCharacteristicsForService).characteristics.forEach { + peripheral.discoverDescriptorsForCharacteristic(it.characteristic) + } + } + + override fun peripheral( + peripheral: CBPeripheral, + didUpdateValueForCharacteristic: CBCharacteristic, + error: NSError? + ) { + if (error != null) { + println("Error with characteristic update ${error}") + } + println("didUpdateValueForCharacteristic") + val device = BluetoothPeripheral(peripheral, rssiValue = null) + val characteristic = BluetoothCharacteristic(didUpdateValueForCharacteristic) + delegates.forEach { + it.didCharacteristcValueChanged( + device, + characteristic + ) + } + } + + override fun peripheral(peripheral: CBPeripheral, didUpdateValueForDescriptor: CBDescriptor, error: NSError?) { + println("didUpdateValueForDescriptor ${didUpdateValueForDescriptor.value}") + } + } + +} diff --git a/library/src/macosX64Main/kotlin/dev/bluefalcon/BluetoothCharacteristic.kt b/library/src/macosX64Main/kotlin/dev/bluefalcon/BluetoothCharacteristic.kt new file mode 100644 index 0000000..a48e792 --- /dev/null +++ b/library/src/macosX64Main/kotlin/dev/bluefalcon/BluetoothCharacteristic.kt @@ -0,0 +1,15 @@ +package dev.bluefalcon + +import platform.CoreBluetooth.CBCharacteristic +import platform.CoreBluetooth.CBDescriptor + +actual class BluetoothCharacteristic(val characteristic: CBCharacteristic) { + actual val name: String? + get() = characteristic.UUID.description + actual val value: String? + get() = characteristic.value?.string() + actual val descriptors: List + get() = characteristic.descriptors as List +} + +actual typealias BluetoothCharacteristicDescriptor = CBDescriptor \ No newline at end of file diff --git a/library/src/macosX64Main/kotlin/dev/bluefalcon/BluetoothPeripheral.kt b/library/src/macosX64Main/kotlin/dev/bluefalcon/BluetoothPeripheral.kt new file mode 100644 index 0000000..2c2267c --- /dev/null +++ b/library/src/macosX64Main/kotlin/dev/bluefalcon/BluetoothPeripheral.kt @@ -0,0 +1,16 @@ +package dev.bluefalcon + +import platform.CoreBluetooth.CBPeripheral +import platform.CoreBluetooth.CBService + +actual class BluetoothPeripheral(val bluetoothDevice: CBPeripheral, val rssiValue: Float?) { + actual val name: String? = bluetoothDevice.name + actual var rssi: Float? = rssiValue + actual val services: List + get() = bluetoothDevice.services?.map { + BluetoothService(it as CBService) + } ?: emptyList() + actual val uuid: String + get() = bluetoothDevice.identifier.UUIDString + +} \ No newline at end of file diff --git a/library/src/macosX64Main/kotlin/dev/bluefalcon/BluetoothService.kt b/library/src/macosX64Main/kotlin/dev/bluefalcon/BluetoothService.kt new file mode 100644 index 0000000..53f6520 --- /dev/null +++ b/library/src/macosX64Main/kotlin/dev/bluefalcon/BluetoothService.kt @@ -0,0 +1,13 @@ +package dev.bluefalcon + +import platform.CoreBluetooth.CBCharacteristic +import platform.CoreBluetooth.CBService + +actual class BluetoothService(val service: CBService) { + actual val name: String? + get() = service.UUID.UUIDString + actual val characteristics: List + get() = service.characteristics?.map { + BluetoothCharacteristic(it as CBCharacteristic) + } ?: emptyList() +} \ No newline at end of file diff --git a/library/src/macosX64Main/kotlin/dev/bluefalcon/Log.kt b/library/src/macosX64Main/kotlin/dev/bluefalcon/Log.kt new file mode 100644 index 0000000..3a9cd55 --- /dev/null +++ b/library/src/macosX64Main/kotlin/dev/bluefalcon/Log.kt @@ -0,0 +1,3 @@ +package dev.bluefalcon + +actual fun log(message: String) = println(message) \ No newline at end of file diff --git a/library/src/macosX64Main/kotlin/dev/bluefalcon/NSData.kt b/library/src/macosX64Main/kotlin/dev/bluefalcon/NSData.kt new file mode 100644 index 0000000..ac3de91 --- /dev/null +++ b/library/src/macosX64Main/kotlin/dev/bluefalcon/NSData.kt @@ -0,0 +1,7 @@ +package dev.bluefalcon + +import platform.Foundation.* + +fun NSData.string(): String? { + return NSString.create(this, NSUTF8StringEncoding) as String? +} \ No newline at end of file