forked from flipperdevices/Flipper-iOS-App
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request flipperdevices#5 from evnik/InitialBluetooth
Initial support for Bluetooth (devices scanning). First set of changes for flipperdevices#7
- Loading branch information
Showing
21 changed files
with
841 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,7 @@ | ||
FilterTargets = ["CoreTests"] | ||
FilterPaths = ["Core/View"] | ||
FilterPaths = [ | ||
"Core/Container/ObservableResolver.swift", | ||
"Core/Service/Platform", | ||
"Core/View" | ||
] | ||
ZeroWarnOnly = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
// | ||
// Container.swift | ||
// FlipperZero | ||
// | ||
// Created by Eugene Berdnikov on 8/23/20. | ||
// | ||
|
||
// TODO: Replace with well-known DI container or extend to support resolving with dependencies | ||
class Container: Resolver { | ||
private struct Key: Hashable { | ||
private let type: Any.Type | ||
|
||
init(_ type: Any.Type) { | ||
self.type = type | ||
} | ||
|
||
func hash(into hasher: inout Hasher) { | ||
ObjectIdentifier(self.type).hash(into: &hasher) | ||
} | ||
|
||
static func == (lhs: Container.Key, rhs: Container.Key) -> Bool { | ||
lhs.type == rhs.type | ||
} | ||
} | ||
|
||
private var factories = [Key: ServiceFactory]() | ||
|
||
func register<Service>(_ builder: @escaping () -> Service, as type: Service.Type, isSingleton: Bool = false) { | ||
self.factories[Key(type)] = isSingleton ? SingletonFactory(builder) : SingleUseFactory(builder) | ||
} | ||
|
||
func resolve<Service>(_ type: Service.Type) -> Service { | ||
guard let factory = self.factories[Key(type)] else { | ||
fatalError("Factory service for [\(type)] is not registered") | ||
} | ||
|
||
guard let service = factory.create() as? Service else { | ||
fatalError("Service created by factory resolved for [\(type)] cannot be casted") | ||
} | ||
|
||
return service | ||
} | ||
} | ||
|
||
extension Container { | ||
func register<Service>(instance: Service) { | ||
self.register(instance: instance, as: Service.self) | ||
} | ||
|
||
func register<Service>(instance: Service, as type: Service.Type) { | ||
self.register({ instance }, as: type, isSingleton: true) | ||
} | ||
|
||
func register<Service>(_ builder: @escaping () -> Service, isSingleton: Bool = false) { | ||
self.register(builder, as: Service.self, isSingleton: isSingleton) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
// | ||
// ObservableResolver.swift | ||
// FlipperZero | ||
// | ||
// Created by Eugene Berdnikov on 8/23/20. | ||
// | ||
|
||
import Combine | ||
|
||
public class ObservableResolver: Resolver, ObservableObject { | ||
private let container: Container | ||
|
||
public init() { | ||
self.container = Container() | ||
self.container.register(BluetoothService.init, as: BluetoothConnector.self, isSingleton: true) | ||
} | ||
|
||
public func resolve<Service>(_ type: Service.Type) -> Service { | ||
self.container.resolve(type) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
// | ||
// Resolver.swift | ||
// FlipperZero | ||
// | ||
// Created by Eugene Berdnikov on 8/23/20. | ||
// | ||
|
||
public protocol Resolver { | ||
func resolve<Service>(_ type: Service.Type) -> Service | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
// | ||
// ServiceFactory.swift | ||
// FlipperZero | ||
// | ||
// Created by Eugene Berdnikov on 9/3/20. | ||
// | ||
|
||
import Foundation | ||
|
||
protocol ServiceFactory { | ||
func create() -> Any | ||
} | ||
|
||
class SingletonFactory: ServiceFactory { | ||
private let builder: () -> Any | ||
|
||
init(_ builder: @escaping () -> Any) { | ||
self.builder = builder | ||
} | ||
|
||
private lazy var value: Any = { | ||
self.builder() | ||
}() | ||
|
||
func create() -> Any { | ||
self.value | ||
} | ||
} | ||
|
||
class SingleUseFactory: ServiceFactory { | ||
private let builder: () -> Any | ||
|
||
init(_ builder: @escaping () -> Any) { | ||
self.builder = builder | ||
} | ||
|
||
func create() -> Any { | ||
self.builder() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// | ||
// BluetoothStatus.swift | ||
// FlipperZero | ||
// | ||
// Created by Eugene Berdnikov on 8/22/20. | ||
// | ||
|
||
enum BluetoothStatus: Equatable { | ||
enum NotReadyReason: String { | ||
case poweredOff | ||
case preparing | ||
case unauthorized | ||
case unsupported | ||
} | ||
|
||
case ready | ||
case notReady(NotReadyReason) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// | ||
// Peripheral.swift | ||
// FlipperZero | ||
// | ||
// Created by Eugene Berdnikov on 8/22/20. | ||
// | ||
|
||
struct Peripheral: EquatableById, Identifiable { | ||
let id: UUID | ||
let name: String | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
// | ||
// EquatableById.swift | ||
// FlipperZero | ||
// | ||
// Created by Eugene Berdnikov on 8/22/20. | ||
// | ||
|
||
protocol EquatableById: Equatable { | ||
} | ||
|
||
extension EquatableById where Self: Identifiable { | ||
static func == (lhs: Self, rhs: Self) -> Bool { | ||
lhs.id == rhs.id | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// | ||
// BluetoothConnector.swift | ||
// FlipperZero | ||
// | ||
// Created by Eugene Berdnikov on 8/22/20. | ||
// | ||
|
||
protocol BluetoothConnector { | ||
var peripherals: SafePublisher<[Peripheral]> { get } | ||
var status: SafePublisher<BluetoothStatus> { get } | ||
|
||
func startScanForPeripherals() | ||
func stopScanForPeripherals() | ||
} |
100 changes: 100 additions & 0 deletions
100
FlipperZero/Core/Service/Platform/BluetoothService.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
// | ||
// BluetoothService.swift | ||
// FlipperZero | ||
// | ||
// Created by Eugene Berdnikov on 8/22/20. | ||
// | ||
|
||
import CoreBluetooth | ||
|
||
class BluetoothService: NSObject, BluetoothConnector { | ||
private let manager: CBCentralManager | ||
private let peripheralsSubject = SafeSubject([Peripheral]()) | ||
private let statusSubject = SafeSubject(BluetoothStatus.notReady(.preparing)) | ||
|
||
var peripherals: SafePublisher<[Peripheral]> { | ||
self.peripheralsSubject.eraseToAnyPublisher() | ||
} | ||
|
||
private var peripheralsMap = [UUID: CBPeripheral]() { | ||
didSet { | ||
self.peripheralsSubject.value = | ||
self.peripheralsMap.values.compactMap(Peripheral.init).sorted { $0.name < $1.name } | ||
} | ||
} | ||
|
||
var status: SafePublisher<BluetoothStatus> { | ||
self.statusSubject.eraseToAnyPublisher() | ||
} | ||
|
||
override init() { | ||
self.manager = CBCentralManager() | ||
super.init() | ||
self.manager.delegate = self | ||
} | ||
|
||
func startScanForPeripherals() { | ||
if self.statusSubject.value == .ready { | ||
// TODO: Provide CBUUID relevant to Flipper devices | ||
self.manager.scanForPeripherals(withServices: nil) | ||
} | ||
} | ||
|
||
func stopScanForPeripherals() { | ||
if self.manager.isScanning { | ||
self.peripheralsMap.removeAll() | ||
self.manager.stopScan() | ||
} | ||
} | ||
} | ||
|
||
extension BluetoothService: CBCentralManagerDelegate { | ||
func centralManagerDidUpdateState(_ manager: CBCentralManager) { | ||
let status = BluetoothStatus(manager.state) | ||
self.statusSubject.value = status | ||
if status != .ready { | ||
self.peripheralsMap.removeAll() | ||
} | ||
} | ||
|
||
func centralManager( | ||
_: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi: NSNumber | ||
) { | ||
if self.peripheralsMap[peripheral.identifier] == nil, | ||
let isConnectable = advertisementData[CBAdvertisementDataIsConnectable] as? Bool, | ||
isConnectable { | ||
|
||
self.peripheralsMap[peripheral.identifier] = peripheral | ||
} | ||
} | ||
} | ||
|
||
fileprivate extension BluetoothStatus { | ||
init(_ source: CBManagerState) { | ||
switch source { | ||
case .resetting, .unknown: | ||
self = .notReady(.preparing) | ||
case .unsupported: | ||
self = .notReady(.unsupported) | ||
case .unauthorized: | ||
self = .notReady(.unauthorized) | ||
case .poweredOff: | ||
self = .notReady(.poweredOff) | ||
case .poweredOn: | ||
self = .ready | ||
@unknown default: | ||
self = .notReady(.unsupported) | ||
} | ||
} | ||
} | ||
|
||
fileprivate extension Peripheral { | ||
init?(_ source: CBPeripheral) { | ||
guard let name = source.name else { | ||
return nil | ||
} | ||
|
||
self.id = source.identifier | ||
self.name = name | ||
} | ||
} |
Oops, something went wrong.