Add support for watchOS and tvOS platforms.

This commit is contained in:
Zsombor Szabo 2020-01-22 15:10:56 +02:00
parent a94107f1a2
commit e8a17a238d
12 changed files with 368 additions and 138 deletions

View File

@ -7,7 +7,7 @@ import PackageDescription
let package = Package(
name: "BerkananSDK",
platforms: [
.iOS(.v9), .macOS(.v10_13),
.iOS(.v9), .macOS(.v10_13), .watchOS(.v2), .tvOS(.v9)
],
products: [
.library(
@ -20,9 +20,12 @@ let package = Package(
.package(url: "https://github.com/zssz/BerkananCompression", from: "1.0.0")
],
targets: [
.target(
name: "CBerkananSDK"),
.target(
name: "BerkananSDK",
dependencies: [
"CBerkananSDK",
"SwiftProtobuf",
"BerkananFoundation",
"BerkananCompression"

View File

@ -70,14 +70,14 @@ public class BerkananBluetoothService: NSObject {
/// Combine version of the `berkananBluetoothService(_:didDiscover:)` delegate method.
///
/// Note: Events are not delivered on the main thread.
@available(OSX 10.15, iOS 13.0, *)
@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
lazy public private(set) var discoverServiceSubject =
PassthroughSubject<BerkananBluetoothService, Never>()
/// Combine version of the `berkananBluetoothService(_:didReceive:)` delegate method.
///
/// Note: Events are not delivered on the main thread.
@available(OSX 10.15, iOS 13.0, *)
@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
lazy public private(set) var receiveMessageSubject =
PassthroughSubject<Message, Never>()
@ -213,7 +213,7 @@ public class BerkananBluetoothService: NSObject {
self.init(rawValue: cbPeripheralManagerAuthorizationStatus.rawValue)
}
@available(iOS 13.0, OSX 10.15, *)
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public init?(cbManagerAuthorization: CBManagerAuthorization) {
self.init(rawValue: cbManagerAuthorization.rawValue)
}

View File

@ -7,10 +7,13 @@
import Foundation
import CoreBluetooth
#if canImport(UIKit)
#if canImport(UIKit) && !os(watchOS)
import UIKit.UIApplication
#endif
import os.log
#if os(watchOS) || os(tvOS)
import CBerkananSDK
#endif
extension TimeInterval {
@ -24,7 +27,7 @@ class BluetoothController: NSObject {
public let label = UUID().uuidString
@available(OSX 10.12, iOS 10.0, *)
@available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *)
lazy private var log = OSLog(subsystem: label, category: "BerkananSDK")
lazy private var dispatchQueue: DispatchQueue =
@ -52,13 +55,19 @@ class BluetoothController: NSObject {
private var discoveryTimeoutTimersForPeripheralIdentifiers =
[UUID : Timer]()
private var connectingTimeoutTimersForPeripheralIdentifiers =
[UUID : Timer]()
@objc dynamic private var connectedPeripherals = Set<CBPeripheral>()
private var connectionStateObservationsForPeripheralIdentifiers =
[UUID : NSKeyValueObservation]()
#if os(watchOS)
private static let maxNumberOfConcurrentPeripheralConnections = 1
#else
private static let maxNumberOfConcurrentPeripheralConnections = 5
#endif
private var messagesForPeripherals =
[CBPeripheral : [Message]]()
@ -103,14 +112,15 @@ class BluetoothController: NSObject {
}
// macCatalyst apps do not need background tasks.
#if canImport(UIKit) && !targetEnvironment(macCatalyst)
// watchOS apps do not have background tasks.
#if canImport(UIKit) && !targetEnvironment(macCatalyst) && !os(watchOS)
private var connectedPeripheralsObservation: NSKeyValueObservation?
private var backgroundTaskIdentifier: UIBackgroundTaskIdentifier?
private func beginBackgroundTaskIfNeeded() {
guard self.backgroundTaskIdentifier == nil else { return }
self.backgroundTaskIdentifier = UIApplication.shared.beginBackgroundTask {
if #available(OSX 10.12, iOS 10.0, *) {
if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
os_log("Did expire background task", log: self.log)
}
self.endBackgroundTaskIfNeeded()
@ -129,7 +139,8 @@ class BluetoothController: NSObject {
super.init()
try self.setConfiguration(configuration)
// macCatalyst apps do not need background support.
#if canImport(UIKit) && !targetEnvironment(macCatalyst)
// watchOS apps do not have background tasks.
#if canImport(UIKit) && !targetEnvironment(macCatalyst) && !os(watchOS)
self.connectedPeripheralsObservation =
self.observe(\.connectedPeripherals) { [weak self] (_,_) in
guard let self = self else { return }
@ -157,7 +168,7 @@ class BluetoothController: NSObject {
}
deinit {
#if canImport(UIKit) && !targetEnvironment(macCatalyst)
#if canImport(UIKit) && !targetEnvironment(macCatalyst) && !os(watchOS)
let notificationCenter = NotificationCenter.default
notificationCenter.removeObserver(
self,
@ -177,7 +188,7 @@ class BluetoothController: NSObject {
@objc func applicationDidEnterBackgroundNotification(
_ notification: Notification
) {
self.dispatchQueue.sync { [weak self] in
self.dispatchQueue.async { [weak self] in
guard let self = self else { return }
self.discoveryTimeoutTimersForPeripheralIdentifiers.values.forEach {
$0.invalidate()
@ -189,7 +200,7 @@ class BluetoothController: NSObject {
@objc func applicationWillEnterForegroundNotification(
_ notification: Notification
) {
self.dispatchQueue.sync { [weak self] in
self.dispatchQueue.async { [weak self] in
guard let self = self else { return }
self.discoveredPeripherals.forEach {
self.setupDiscoveryTimeoutTimer(for: $0)
@ -215,16 +226,26 @@ class BluetoothController: NSObject {
queue: self.dispatchQueue,
options: nil
)
#if os(watchOS) || os(tvOS)
self.peripheralManager = CBPeripheralManager(
berkananSDKWith: self,
queue: self.dispatchQueue,
options: [
CBPeripheralManagerOptionRestoreIdentifierKey:
"chat.berkanan.sdk.peripheral." +
self.configuration.identifier.foundationValue()!.uuidString
])
#else
self.peripheralManager = CBPeripheralManager(
delegate: self,
queue: self.dispatchQueue,
options: [
CBPeripheralManagerOptionRestoreIdentifierKey:
"chat.berkanan.sdk.peripheral." +
self.configuration.identifier.foundationValue()!.uuidString
]
)
if #available(OSX 10.12, iOS 10.0, *) {
CBPeripheralManagerOptionRestoreIdentifierKey:
"chat.berkanan.sdk.peripheral." +
self.configuration.identifier.foundationValue()!.uuidString
])
#endif
if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
os_log(
"Service (%@) started",
log: self.log,
@ -241,14 +262,11 @@ class BluetoothController: NSObject {
$0.invalidate()
}
self.discoveryTimeoutTimersForPeripheralIdentifiers.removeAll()
self.connectingTimeoutTimersForPeripheralIdentifiers.values.forEach {
$0.invalidate()
}
self.connectingTimeoutTimersForPeripheralIdentifiers.removeAll()
self.discoveredPeripherals.forEach { peripheral in
DispatchQueue.main.async {
NSObject.cancelPreviousPerformRequests(
withTarget: self,
selector: #selector(self.connectingTimeout(for:)),
object: peripheral
)
}
peripheral.delegate = nil
self.cancelConnectionIfNeeded(for: peripheral)
}
@ -271,7 +289,7 @@ class BluetoothController: NSObject {
self.peripheralManager?.removeAllServices()
self.peripheralManager?.delegate = nil
self.peripheralManager = nil
if #available(OSX 10.12, iOS 10.0, *) {
if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
os_log(
"Service (%@) stopped",
log: self.log,
@ -302,7 +320,7 @@ class BluetoothController: NSObject {
guard let messageUUID = message.identifier.foundationValue() else {
return
}
if #available(OSX 10.12, iOS 10.0, *) {
if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
os_log(
"Enqueued message (uuid=%@ payload='%@')",
log: self.log,
@ -330,7 +348,7 @@ class BluetoothController: NSObject {
}
self.connectPeripheralsIfNeeded()
}
#if canImport(UIKit)
#if canImport(UIKit) && !os(watchOS)
DispatchQueue.main.async {
isInBackground = (UIApplication.shared.applicationState == .background)
self.dispatchQueue.async {
@ -379,7 +397,7 @@ class BluetoothController: NSObject {
if peripheral.state != .connected {
if peripheral.state != .connecting {
self.centralManager?.connect(peripheral, options: nil)
if #available(OSX 10.12, iOS 10.0, *) {
if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
os_log(
"Central manager connecting peripheral (uuid=%@ name='%@')",
log: self.log,
@ -387,13 +405,7 @@ class BluetoothController: NSObject {
peripheral.name ?? ""
)
}
DispatchQueue.main.async {
self.perform(
#selector(self.connectingTimeout(for:)),
with: peripheral,
afterDelay: TimeInterval.peripheralConnectingTimeout
)
}
self.setupConnectingTimeoutTimer(for: peripheral)
}
}
else {
@ -401,11 +413,78 @@ class BluetoothController: NSObject {
}
}
@objc private func connectingTimeout(for peripheral: CBPeripheral) {
private func addToSeenMessageUUIDsIfNeeded(uuid: UUID) {
if !self.seenMessageUUIDs.contains(uuid) {
if self.seenMessageUUIDs.count >=
BluetoothController.seenMessageUUIDsCacheLimit {
self.seenMessageUUIDs.removeObject(at: 0)
}
self.seenMessageUUIDs.add(uuid)
}
}
private func setupDiscoveryTimeoutTimer(for peripheral: CBPeripheral) {
let timer = Timer.init(
timeInterval: .peripheralDiscoveryTimeout,
target: self,
selector: #selector(_discoveryTimeoutTimerFired(timer:)),
userInfo: ["peripheral" : peripheral],
repeats: false
)
timer.tolerance = 0.5
RunLoop.main.add(timer, forMode: .common)
self.discoveryTimeoutTimersForPeripheralIdentifiers[peripheral.identifier]?
.invalidate()
self.discoveryTimeoutTimersForPeripheralIdentifiers[peripheral.identifier] =
timer
}
private func setupConnectingTimeoutTimer(for peripheral: CBPeripheral) {
let timer = Timer.init(
timeInterval: .peripheralConnectingTimeout,
target: self,
selector: #selector(_connectingTimeoutTimerFired(timer:)),
userInfo: ["peripheral" : peripheral],
repeats: false
)
timer.tolerance = 0.5
RunLoop.main.add(timer, forMode: .common)
self.connectingTimeoutTimersForPeripheralIdentifiers[
peripheral.identifier]?.invalidate()
self.connectingTimeoutTimersForPeripheralIdentifiers[
peripheral.identifier] = timer
}
@objc private func _discoveryTimeoutTimerFired(timer: Timer) {
let userInfo = timer.userInfo
self.dispatchQueue.async { [weak self] in
guard let self = self else { return }
guard let userInfo = userInfo as? [AnyHashable : Any],
let peripheral = userInfo["peripheral"] as? CBPeripheral else {
return
}
if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
os_log(
"Discovering did time out for peripheral (uuid=%@ name='%@')",
log: self.log,
peripheral.identifier.description,
peripheral.name ?? ""
)
}
self.flushPeripheral(peripheral)
}
}
@objc private func _connectingTimeoutTimerFired(timer: Timer) {
let userInfo = timer.userInfo
self.dispatchQueue.async { [weak self] in
guard let self = self else { return }
guard let userInfo = userInfo as? [AnyHashable : Any],
let peripheral = userInfo["peripheral"] as? CBPeripheral else {
return
}
if peripheral.state != .connected {
if #available(OSX 10.12, iOS 10.0, *) {
if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
os_log(
"Connecting did time out for peripheral (uuid=%@ name='%@')",
log: self.log,
@ -429,52 +508,6 @@ class BluetoothController: NSObject {
}
}
private func addToSeenMessageUUIDsIfNeeded(uuid: UUID) {
if !self.seenMessageUUIDs.contains(uuid) {
if self.seenMessageUUIDs.count >=
BluetoothController.seenMessageUUIDsCacheLimit {
self.seenMessageUUIDs.removeObject(at: 0)
}
self.seenMessageUUIDs.add(uuid)
}
}
private func setupDiscoveryTimeoutTimer(for peripheral: CBPeripheral) {
let timer = Timer.init(
timeInterval: .peripheralDiscoveryTimeout,
target: self,
selector: #selector(_timeoutTimerFired(timer:)),
userInfo: ["peripheral" : peripheral],
repeats: false
)
timer.tolerance = 0.5
RunLoop.main.add(timer, forMode: .common)
self.discoveryTimeoutTimersForPeripheralIdentifiers[peripheral.identifier]?
.invalidate()
self.discoveryTimeoutTimersForPeripheralIdentifiers[peripheral.identifier] =
timer
}
@objc private func _timeoutTimerFired(timer: Timer) {
let userInfo = timer.userInfo
self.dispatchQueue.async { [weak self] in
guard let self = self else { return }
guard let userInfo = userInfo as? [AnyHashable : Any],
let peripheral = userInfo["peripheral"] as? CBPeripheral else {
return
}
if #available(OSX 10.12, iOS 10.0, *) {
os_log(
"Discovering did time out for peripheral (uuid=%@ name='%@')",
log: self.log,
peripheral.identifier.description,
peripheral.name ?? ""
)
}
self.flushPeripheral(peripheral)
}
}
private func flushPeripheral(_ peripheral: CBPeripheral) {
peripheral.delegate = nil
self.servicesOfPeripherals[peripheral]?.forEach {
@ -499,11 +532,16 @@ class BluetoothController: NSObject {
}
private func cancelConnectionIfNeeded(for peripheral: CBPeripheral) {
self.connectingTimeoutTimersForPeripheralIdentifiers[
peripheral.identifier]?.invalidate()
self.connectingTimeoutTimersForPeripheralIdentifiers[
peripheral.identifier] = nil
guard peripheral.state != .disconnected else {
return
}
centralManager?.cancelPeripheralConnection(peripheral)
if #available(OSX 10.12, iOS 10.0, *) {
if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
os_log(
"Central manager cancelled peripheral (uuid=%@ name='%@') connection",
log: self.log,
@ -517,7 +555,7 @@ class BluetoothController: NSObject {
extension BluetoothController: CBCentralManagerDelegate {
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if #available(OSX 10.12, iOS 10.0, *) {
if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
os_log(
"Central manager did update state=%@",
log: self.log,
@ -544,7 +582,7 @@ extension BluetoothController: CBCentralManagerDelegate {
NSNumber(booleanLiteral: true)]
)
#if targetEnvironment(macCatalyst)
if #available(OSX 10.12, iOS 10.0, *) {
if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
os_log(
"Central manager scanning for peripherals with services=%@",
log: self.log,
@ -552,7 +590,7 @@ extension BluetoothController: CBCentralManagerDelegate {
)
}
#else
if #available(OSX 10.12, iOS 10.0, *) {
if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
os_log(
"Central manager scanning for peripherals with services=%@",
log: self.log,
@ -572,7 +610,7 @@ extension BluetoothController: CBCentralManagerDelegate {
rssi RSSI: NSNumber
) {
if !self.discoveredPeripherals.contains(peripheral) {
if #available(OSX 10.12, iOS 10.0, *) {
if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
os_log(
"Central manager did discover new peripheral (uuid=%@ name='%@') RSSI=%d",
log: self.log,
@ -584,8 +622,9 @@ extension BluetoothController: CBCentralManagerDelegate {
self.connectionStateObservationsForPeripheralIdentifiers[
peripheral.identifier
] = peripheral.observe(\.state) { [weak self] (peripheral, _) in
] = peripheral.observe(\.state) { [weak self] (peripheral, change) in
guard let self = self else { return }
guard change.newValue != change.oldValue else { return }
if peripheral.state == .disconnected {
self.connectedPeripherals.remove(peripheral)
self.messagesForPeripherals.removeValue(forKey: peripheral)
@ -611,7 +650,7 @@ extension BluetoothController: CBCentralManagerDelegate {
}
}
self.connectPeripheralsIfNeeded()
#if canImport(UIKit)
#if canImport(UIKit) && !os(watchOS)
DispatchQueue.main.async {
guard UIApplication.shared.applicationState != .background else {
return
@ -629,7 +668,7 @@ extension BluetoothController: CBCentralManagerDelegate {
_ central: CBCentralManager,
didConnect peripheral: CBPeripheral
) {
if #available(OSX 10.12, iOS 10.0, *) {
if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
os_log(
"Central manager did connect peripheral (uuid=%@ name='%@')",
log: self.log,
@ -637,13 +676,10 @@ extension BluetoothController: CBCentralManagerDelegate {
peripheral.name ?? ""
)
}
DispatchQueue.main.async {
NSObject.cancelPreviousPerformRequests(
withTarget: self,
selector: #selector(self.connectingTimeout(for:)),
object: peripheral
)
}
self.connectingTimeoutTimersForPeripheralIdentifiers[
peripheral.identifier]?.invalidate()
self.connectingTimeoutTimersForPeripheralIdentifiers[
peripheral.identifier] = nil
self._centralManager(central, didConnect: peripheral)
}
@ -657,7 +693,7 @@ extension BluetoothController: CBCentralManagerDelegate {
CBUUID(string: BluetoothService.UUIDPeripheralServiceString)
]
peripheral.discoverServices(services)
if #available(OSX 10.12, iOS 10.0, *) {
if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
os_log(
"Peripheral (uuid=%@ name='%@') discovering services=%@",
log: self.log,
@ -676,7 +712,7 @@ extension BluetoothController: CBCentralManagerDelegate {
didFailToConnect peripheral: CBPeripheral,
error: Error?
) {
if #available(OSX 10.12, iOS 10.0, *) {
if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
os_log(
"Central manager did fail to connect peripheral (uuid=%@ name='%@') error=%@",
log: self.log,
@ -695,7 +731,7 @@ extension BluetoothController: CBCentralManagerDelegate {
error: Error?
) {
if let error = error {
if #available(OSX 10.12, iOS 10.0, *) {
if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
os_log(
"Central manager did disconnect peripheral (uuid=%@ name='%@') error=%@",
log: self.log,
@ -707,7 +743,7 @@ extension BluetoothController: CBCentralManagerDelegate {
}
}
else {
if #available(OSX 10.12, iOS 10.0, *) {
if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
os_log(
"Central manager did disconnect peripheral (uuid=%@ name='%@')",
log: self.log,
@ -716,6 +752,7 @@ extension BluetoothController: CBCentralManagerDelegate {
)
}
}
self.cancelConnectionIfNeeded(for: peripheral)
}
}
@ -726,7 +763,7 @@ extension BluetoothController: CBPeripheralDelegate {
didDiscoverServices error: Error?
) {
if let error = error {
if #available(OSX 10.12, iOS 10.0, *) {
if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
os_log(
"Peripheral (uuid=%@ name='%@') did discover services error=%@",
log: self.log,
@ -738,7 +775,7 @@ extension BluetoothController: CBPeripheralDelegate {
}
}
else {
if #available(OSX 10.12, iOS 10.0, *) {
if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
os_log(
"Peripheral (uuid=%@ name='%@') did discover services",
log: self.log,
@ -773,7 +810,7 @@ extension BluetoothController: CBPeripheralDelegate {
CBUUID(string: BluetoothService.UUIDConfigurationCharacteristicString)
]
peripheral.discoverCharacteristics(characteristics, for: service)
if #available(OSX 10.12, iOS 10.0, *) {
if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
os_log(
"Peripheral (uuid=%@ name='%@') discovering characteristics=%@ for service=%@",
log: self.log,
@ -793,7 +830,7 @@ extension BluetoothController: CBPeripheralDelegate {
error: Error?
) {
if let error = error {
if #available(OSX 10.12, iOS 10.0, *) {
if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
os_log(
"Peripheral (uuid=%@ name='%@') did discover characteristics for service=%@ error=%@",
log: self.log,
@ -806,7 +843,7 @@ extension BluetoothController: CBPeripheralDelegate {
}
}
else {
if #available(OSX 10.12, iOS 10.0, *) {
if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
os_log(
"Peripheral (uuid=%@ name='%@') did discover characteristics for service=%@",
log: self.log,
@ -851,12 +888,12 @@ extension BluetoothController: CBPeripheralDelegate {
self.messagesForPeripherals.removeValue(forKey: peripheral)
self.dispatchQueue.asyncAfter(
deadline: .now() + TimeInterval.peripheralConnectionTimeout
deadline: .now() + .peripheralConnectionTimeout
) { [weak self] in
guard let self = self else { return }
guard self.hasMessagesEnqueued(for: peripheral) ||
self.shouldReadConfigurations(from: peripheral) else {
if #available(OSX 10.12, iOS 10.0, *) {
if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
os_log(
"Connection did time out for peripheral (uuid=%@ name='%@')",
log: self.log,
@ -889,7 +926,7 @@ extension BluetoothController: CBPeripheralDelegate {
)
}), self.shouldReadConfigurations(from: peripheral) {
peripheral.readValue(for: configurationCharacteristic)
if #available(OSX 10.12, iOS 10.0, *) {
if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
os_log(
"Peripheral (uuid=%@ name='%@') reading value for characteristic=%@ for service=%@",
log: self.log,
@ -927,7 +964,7 @@ extension BluetoothController: CBPeripheralDelegate {
for: messageCharacteristic,
type: .withResponse
)
if #available(OSX 10.12, iOS 10.0, *) {
if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
os_log(
"Peripheral (uuid=%@ name='%@') writing value=%{iec-bytes}d for characteristic=%@ for service=%@",
log: self.log,
@ -952,7 +989,7 @@ extension BluetoothController: CBPeripheralDelegate {
error: Error?
) {
if let error = error {
if #available(OSX 10.12, iOS 10.0, *) {
if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
os_log(
"Peripheral (uuid=%@ name='%@') did write value for characteristic=%@ for service=%@ error=%@",
log: self.log,
@ -966,7 +1003,7 @@ extension BluetoothController: CBPeripheralDelegate {
}
}
else {
if #available(OSX 10.12, iOS 10.0, *) {
if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
os_log(
"Peripheral (uuid=%@ name='%@') did write value for characteristic=%@ for service=%@",
log: self.log,
@ -985,7 +1022,7 @@ extension BluetoothController: CBPeripheralDelegate {
error: Error?
) {
if let error = error {
if #available(OSX 10.12, iOS 10.0, *) {
if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
os_log(
"Peripheral (uuid=%@ name='%@') did update value for characteristic=%@ for service=%@ error=%@",
log: self.log,
@ -999,7 +1036,7 @@ extension BluetoothController: CBPeripheralDelegate {
}
}
else {
if #available(OSX 10.12, iOS 10.0, *) {
if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
os_log(
"Peripheral (uuid=%@ name='%@') did update value=%{iec-bytes}d for characteristic=%@ for service=%@",
log: self.log,
@ -1025,7 +1062,7 @@ extension BluetoothController: CBPeripheralDelegate {
services.insert(remoteService)
self.servicesOfPeripherals[peripheral] = services
#if canImport(Combine)
if #available(OSX 10.15, iOS 13.0, *) {
if #available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) {
self.service?.discoverServiceSubject.send(remoteService)
}
#endif
@ -1036,7 +1073,7 @@ extension BluetoothController: CBPeripheralDelegate {
}
}
catch {
if #available(OSX 10.12, iOS 10.0, *) {
if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
os_log(
"Processing value failed=%@",
log: self.log,
@ -1052,7 +1089,7 @@ extension BluetoothController: CBPeripheralDelegate {
_ peripheral: CBPeripheral,
didModifyServices invalidatedServices: [CBService]
) {
if #available(OSX 10.12, iOS 10.0, *) {
if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
os_log(
"Peripheral (uuid=%@ name='%@') did modify services=%@",
log: self.log,
@ -1069,7 +1106,7 @@ extension BluetoothController: CBPeripheralManagerDelegate {
_ peripheral: CBPeripheralManager,
willRestoreState dict: [String : Any]
) {
if #available(OSX 10.12, iOS 10.0, *) {
if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
os_log(
"Peripheral manager will restore state",
log: self.log
@ -1078,7 +1115,7 @@ extension BluetoothController: CBPeripheralManagerDelegate {
}
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
if #available(OSX 10.12, iOS 10.0, *) {
if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
os_log(
"Peripheral manager did update state=%@",
log: self.log,
@ -1089,13 +1126,13 @@ extension BluetoothController: CBPeripheralManagerDelegate {
}
func _peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
if #available(OSX 10.15, macCatalyst 13.1, iOS 13.1, *) {
if #available(OSX 10.15, macCatalyst 13.1, iOS 13.1, tvOS 13.0, watchOS 6.0, *) {
self.service?.bluetoothAuthorization =
BluetoothAuthorization(
cbManagerAuthorization: CBManager.authorization
) ?? .notDetermined
}
else if #available(OSX 10.15, macCatalyst 13.0, iOS 13.0, *) {
else if #available(OSX 10.15, macCatalyst 13.0, iOS 13.0, tvOS 13.0, watchOS 6.0, *) {
self.service?.bluetoothAuthorization =
BluetoothAuthorization(
cbManagerAuthorization: peripheral.authorization
@ -1121,7 +1158,7 @@ extension BluetoothController: CBPeripheralManagerDelegate {
value: try! self.configuration.pdu()
)
]
if #available(OSX 10.12, iOS 10.0, *) {
if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
os_log(
"Peripheral manager adding service=%@",
log: self.log,
@ -1140,7 +1177,7 @@ extension BluetoothController: CBPeripheralManagerDelegate {
error: Error?
) {
if let error = error {
if #available(OSX 10.12, iOS 10.0, *) {
if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
os_log(
"Peripheral manager did add service=%@ error=%@",
log: self.log,
@ -1151,7 +1188,7 @@ extension BluetoothController: CBPeripheralManagerDelegate {
}
}
else {
if #available(OSX 10.12, iOS 10.0, *) {
if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
os_log(
"Peripheral manager did add service=%@",
log: self.log,
@ -1167,7 +1204,7 @@ extension BluetoothController: CBPeripheralManagerDelegate {
[CBAdvertisementDataServiceUUIDsKey :
[CBUUID(string: BluetoothService.UUIDPeripheralServiceString)]]
)
if #available(OSX 10.12, iOS 10.0, *) {
if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
os_log(
"Peripheral manager starting advertising",
log: self.log
@ -1180,7 +1217,7 @@ extension BluetoothController: CBPeripheralManagerDelegate {
error: Error?
) {
if let error = error {
if #available(OSX 10.12, iOS 10.0, *) {
if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
os_log(
"Peripheral manager did start advertising error=%@",
log: self.log,
@ -1190,7 +1227,7 @@ extension BluetoothController: CBPeripheralManagerDelegate {
}
}
else {
if #available(OSX 10.12, iOS 10.0, *) {
if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
os_log(
"Peripheral manager did start advertising",
log: self.log
@ -1203,7 +1240,7 @@ extension BluetoothController: CBPeripheralManagerDelegate {
_ peripheral: CBPeripheralManager,
didReceiveWrite requests: [CBATTRequest]
) {
if #available(OSX 10.12, iOS 10.0, *) {
if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
os_log(
"Peripheral manager did receive write requests=%@",
log: self.log,
@ -1246,7 +1283,7 @@ extension BluetoothController: CBPeripheralManagerDelegate {
result = error.code
}
peripheral.respond(to: request, withResult: result)
if #available(OSX 10.12, iOS 10.0, *) {
if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
os_log(
"Peripheral manager did respond to request=%@ with result=%d",
log: self.log,
@ -1259,7 +1296,7 @@ extension BluetoothController: CBPeripheralManagerDelegate {
}
if let request = requests.first {
peripheral.respond(to: request, withResult: .success)
if #available(OSX 10.12, iOS 10.0, *) {
if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
os_log(
"Peripheral manager did respond to request=%@ with result=%d",
log: self.log,
@ -1278,7 +1315,7 @@ extension BluetoothController: CBPeripheralManagerDelegate {
if !self.seenMessageUUIDs.contains(messageUUID) {
self.addToSeenMessageUUIDsIfNeeded(uuid: messageUUID)
#if canImport(Combine)
if #available(OSX 10.15, iOS 13.0, *) {
if #available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) {
self.service?.receiveMessageSubject.send($0)
}
#endif

View File

@ -7,6 +7,9 @@
import Foundation
import CoreBluetooth
#if os(watchOS) || os(tvOS)
import CBerkananSDK
#endif
extension BluetoothService {
@ -15,34 +18,61 @@ extension BluetoothService {
/// The peripheral service to be added to the local GATT database.
public static var peripheralService: CBMutableService {
#if os(watchOS) || os(tvOS)
let service = CBMutableService(
berkananSDKWithType: CBUUID(string: UUIDPeripheralServiceString),
primary: true
)
#else
let service = CBMutableService(
type: CBUUID(string: UUIDPeripheralServiceString),
primary: true
)
)
#endif
return service
}
/// The characteristic used for receiving messages.
public static var messageCharacteristic: CBMutableCharacteristic {
CBMutableCharacteristic(
#if os(watchOS) || os(tvOS)
return CBMutableCharacteristic(
berkananSDKWithType: CBUUID(string: UUIDMessageCharacteristicString),
properties: [.write],
value: nil,
permissions: [.writeable]
)
#else
return CBMutableCharacteristic(
type: CBUUID(string: UUIDMessageCharacteristicString),
properties: [.write],
value: nil,
permissions: [.writeable]
)
#endif
}
/// The characteristic used for sending configuration.
public static func configurationCharacteristic(
value: Data
) -> CBMutableCharacteristic {
#if os(watchOS) || os(tvOS)
return CBMutableCharacteristic(
type: CBUUID(
berkananSDKWithType: CBUUID(
string: BluetoothService.UUIDConfigurationCharacteristicString
),
properties: [.read],
value: value,
permissions: [.readable]
)
#else
return CBMutableCharacteristic(
type: CBUUID(
string: BluetoothService.UUIDConfigurationCharacteristicString
),
properties: [.read],
value: value,
permissions: [.readable]
)
#endif
}
}

View File

@ -6,7 +6,7 @@
//
import Foundation
#if canImport(UIKit)
#if canImport(UIKit) && !os(watchOS)
import UIKit.UIDevice
#endif
@ -18,7 +18,7 @@ extension User {
/// and `UIDevice.current.name` for `name`.
public static var current: User = {
return .with {
#if canImport(UIKit)
#if canImport(UIKit) && !os(watchOS)
$0.identifier =
UIDevice.current.identifierForVendor?.protobufValue() ?? PBUUID.random()
$0.name = UIDevice.current.name

View File

@ -0,0 +1,32 @@
//
// Copyright © 2019 IZE Ltd. and the project authors
// Licensed under MIT License
//
// See LICENSE.txt for license information.
//
#import "CBMutableCharacteristic+Init.h"
#include <objc/message.h>
@implementation CBMutableCharacteristic (Init)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-designated-initializers"
- (nonnull instancetype)initBerkananSDKWithType:(nonnull CBUUID *)UUID
properties:(CBCharacteristicProperties)properties
value:(nullable NSData *)value
permissions:(CBAttributePermissions)permissions {
NSString *selectorString = @"initWithType:properties:value:permissions:";
SEL selector = NSSelectorFromString(selectorString);
if (![self respondsToSelector:selector]) {
return self;
}
id (*action)(id, SEL, CBUUID *, CBCharacteristicProperties, NSData *, CBAttributePermissions) = (id (*)(id, SEL, CBUUID *, CBCharacteristicProperties, NSData *, CBAttributePermissions)) objc_msgSend;
action(self, selector, UUID, properties, value, permissions);
return self;
}
#pragma clang diagnostic pop
@end

View File

@ -0,0 +1,31 @@
//
// Copyright © 2019 IZE Ltd. and the project authors
// Licensed under MIT License
//
// See LICENSE.txt for license information.
//
#import "CBMutableService+Init.h"
#include <objc/message.h>
@implementation CBMutableService (Init)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-designated-initializers"
- (instancetype)initBerkananSDKWithType:(nonnull CBUUID *)UUID
primary:(BOOL)isPrimary {
NSString *selectorString = @"initWithType:primary:";
SEL selector = NSSelectorFromString(selectorString);
if (![self respondsToSelector:selector]) {
return self;
}
id (*action)(id, SEL, CBUUID *, BOOL) =
(id (*)(id, SEL, CBUUID *, BOOL)) objc_msgSend;
action(self, selector, UUID, isPrimary);
return self;
}
#pragma clang diagnostic pop
@end

View File

@ -0,0 +1,31 @@
//
// Copyright © 2019 IZE Ltd. and the project authors
// Licensed under MIT License
//
// See LICENSE.txt for license information.
//
#import "CBPeripheralManager+Init.h"
#include <objc/message.h>
@implementation CBPeripheralManager (Init)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-designated-initializers"
- (nonnull instancetype)initBerkananSDKWithDelegate:(nullable id<CBPeripheralManagerDelegate>)delegate
queue:(nullable dispatch_queue_t)queue
options:(nullable NSDictionary<NSString *, id> *)options {
NSString *selectorString = @"initWithDelegate:queue:options:";
SEL selector = NSSelectorFromString(selectorString);
if (![self respondsToSelector:selector]) {
return self;
}
id (*action)(id, SEL, id<CBPeripheralManagerDelegate>, dispatch_queue_t, NSDictionary<NSString *, id> *) = (id (*)(id, SEL, id<CBPeripheralManagerDelegate>, dispatch_queue_t, NSDictionary<NSString *, id> *)) objc_msgSend;
action(self, selector, delegate, queue, options);
return self;
}
#pragma clang diagnostic pop
@end

View File

@ -0,0 +1,17 @@
//
// Copyright © 2019 IZE Ltd. and the project authors
// Licensed under MIT License
//
// See LICENSE.txt for license information.
//
@import CoreBluetooth.CBCharacteristic;
@interface CBMutableCharacteristic (Init)
- (nonnull instancetype)initBerkananSDKWithType:(nonnull CBUUID *)UUID
properties:(CBCharacteristicProperties)properties
value:(nullable NSData *)value
permissions:(CBAttributePermissions)permissions;
@end

View File

@ -0,0 +1,15 @@
//
// Copyright © 2019 IZE Ltd. and the project authors
// Licensed under MIT License
//
// See LICENSE.txt for license information.
//
@import CoreBluetooth.CBService;
@interface CBMutableService (Init)
- (nonnull instancetype)initBerkananSDKWithType:(nonnull CBUUID *)UUID
primary:(BOOL)isPrimary;
@end

View File

@ -0,0 +1,16 @@
//
// Copyright © 2019 IZE Ltd. and the project authors
// Licensed under MIT License
//
// See LICENSE.txt for license information.
//
@import CoreBluetooth.CBPeripheralManager;
@interface CBPeripheralManager (Init)
- (nonnull instancetype)initBerkananSDKWithDelegate:(nullable id<CBPeripheralManagerDelegate>)delegate
queue:(nullable dispatch_queue_t)queue
options:(nullable NSDictionary<NSString *, id> *)options;
@end

View File

@ -6,10 +6,28 @@
//
import XCTest
import CoreBluetooth
@testable import BerkananSDK
final class BerkananSDKTests: XCTestCase {
func testCoreBluetoothInit() {
let service = BluetoothService.peripheralService
XCTAssertEqual(CBUUID(string: BluetoothService.UUIDPeripheralServiceString), service.uuid)
XCTAssertEqual(true, service.isPrimary)
let messageCharacteristic = BluetoothService.messageCharacteristic
XCTAssertEqual(CBUUID(string: BluetoothService.UUIDMessageCharacteristicString), messageCharacteristic.uuid)
XCTAssertEqual([.write], messageCharacteristic.properties)
XCTAssertEqual(nil, messageCharacteristic.value)
XCTAssertEqual([.writeable], messageCharacteristic.permissions)
let value = "Hello, World".data(using: .utf8)!
let configurationCharacteristic = BluetoothService.configurationCharacteristic(value: value)
XCTAssertEqual(CBUUID(string: BluetoothService.UUIDConfigurationCharacteristicString), configurationCharacteristic.uuid)
XCTAssertEqual([.read], configurationCharacteristic.properties)
XCTAssertEqual(value, configurationCharacteristic.value)
XCTAssertEqual([.readable], configurationCharacteristic.permissions)
}
func testMessagePDU() {
_testMessagePDU(
with: "Hello, World!",