Compare commits

...

1 commit

Author SHA1 Message Date
Trev_Knows
391c4268b6 Overwrite
Puppet Module Demo
2020-01-31 17:04:32 -05:00
645 changed files with 52279 additions and 40212 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View file

@ -4,6 +4,8 @@
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>BP Simulated</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
@ -13,13 +15,19 @@
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>1</string>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>This app needs access to Bluetooth to connect to Circuit Playground Bluefruit devices</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>This app needs access to Bluetooth to connect to Circuit Playground Bluefruit devices</string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
@ -41,5 +49,7 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>

File diff suppressed because it is too large Load diff

View file

@ -2,6 +2,6 @@
<Workspace
version = "1.0">
<FileRef
location = "self:CPX+BLE.xcodeproj">
location = "self:CPX.xcodeproj">
</FileRef>
</Workspace>

View file

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1130"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "A9A08B3623A523C900498069"
BuildableName = "BluefruitPlayground-SimulateBluetooth.app"
BlueprintName = "BluefruitPlayground-SimulateBluetooth"
ReferencedContainer = "container:BluefruitPlayground.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "A9A08B3623A523C900498069"
BuildableName = "BluefruitPlayground-SimulateBluetooth.app"
BlueprintName = "BluefruitPlayground-SimulateBluetooth"
ReferencedContainer = "container:BluefruitPlayground.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "A9A08B3623A523C900498069"
BuildableName = "BluefruitPlayground-SimulateBluetooth.app"
BlueprintName = "BluefruitPlayground-SimulateBluetooth"
ReferencedContainer = "container:BluefruitPlayground.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View file

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1120"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "A92B9F08234DE4A2002374F0"
BuildableName = "Bluefruit Playground.app"
BlueprintName = "BluefruitPlayground"
ReferencedContainer = "container:BluefruitPlayground.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "A9A08B2B23A5238000498069"
BuildableName = "BluefruitPlaygroundUITests.xctest"
BlueprintName = "BluefruitPlaygroundUITests"
ReferencedContainer = "container:BluefruitPlayground.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "A92B9F08234DE4A2002374F0"
BuildableName = "Bluefruit Playground.app"
BlueprintName = "BluefruitPlayground"
ReferencedContainer = "container:BluefruitPlayground.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "A92B9F08234DE4A2002374F0"
BuildableName = "Bluefruit Playground.app"
BlueprintName = "BluefruitPlayground"
ReferencedContainer = "container:BluefruitPlayground.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View file

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1130"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "A9A08B2B23A5238000498069"
BuildableName = "BluefruitPlaygroundUITests.xctest"
BlueprintName = "BluefruitPlaygroundUITests"
ReferencedContainer = "container:BluefruitPlayground.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "A9A08B2B23A5238000498069"
BuildableName = "BluefruitPlaygroundUITests.xctest"
BlueprintName = "BluefruitPlaygroundUITests"
ReferencedContainer = "container:BluefruitPlayground.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "A9A08B3623A523C900498069"
BuildableName = "BluefruitPlayground-SimulateBluetooth.app"
BlueprintName = "BluefruitPlayground-SimulateBluetooth"
ReferencedContainer = "container:BluefruitPlayground.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>BluefruitPlayground SimulateBluetooth.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>1</integer>
</dict>
<key>BluefruitPlayground.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
</dict>
<key>BluefruitPlaygroundUITests.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>2</integer>
</dict>
</dict>
</dict>
</plist>

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>BluefruitPlayground SimulateBluetooth.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>1</integer>
</dict>
<key>BluefruitPlayground.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
</dict>
<key>BluefruitPlaygroundUITests.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>2</integer>
</dict>
</dict>
</dict>
</plist>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:BluefruitPlayground.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View file

@ -2,13 +2,7 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>CPX+BLE.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
</dict>
</dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View file

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Bucket
type = "1"
uuid = "3E1BB423-CDC0-4093-86D2-3CCAD71E4319"
type = "0"
version = "2.0">
</Bucket>

BIN
BluefruitPlayground/.DS_Store vendored Normal file

Binary file not shown.

View file

@ -0,0 +1,369 @@
//
// BleManager.swift
// BleManager
//
// Created by Antonio García on 13/10/2016.
// Copyright © 2016 Adafruit. All rights reserved.
//
import Foundation
import CoreBluetooth
#if COMMANDLINE
#else
import MSWeakTimer
#endif
class BleManager: NSObject {
// Configuration
private static let kStopScanningWhenConnectingToPeripheral = false
private static let kAlwaysAllowDuplicateKeys = true
// Singleton
static let shared = BleManager()
// Ble
var centralManager: CBCentralManager?
private var centralManagerPoweredOnSemaphore = DispatchSemaphore(value: 1)
// Scanning
var isScanning = false
private var isScanningWaitingToStart = false
private var scanningServicesFilter: [CBUUID]?
internal var peripheralsFound = [UUID: BlePeripheral]()
private var peripheralsFoundLock = NSLock()
// Connecting
private var connectionTimeoutTimers = [UUID: MSWeakTimer]()
// Notifications
enum NotificationUserInfoKey: String {
case uuid = "uuid"
}
override init() {
super.init()
centralManagerPoweredOnSemaphore.wait()
centralManager = CBCentralManager(delegate: self, queue: DispatchQueue.global(qos: .background), options: [:])
// centralManager = CBCentralManager(delegate: self, queue: DispatchQueue.main, options: [:])
}
deinit {
scanningServicesFilter?.removeAll()
peripheralsFound.removeAll()
}
public var state: CBManagerState {
return centralManager?.state ?? .unknown
}
func restoreCentralManager() {
DLog("Restoring central manager")
/*
guard centralManager?.delegate !== self else {
DLog("No need to restore it. It it still ours")
return
}*/
// Restore peripherals status
peripheralsFoundLock.lock()
for (_, blePeripheral) in peripheralsFound {
blePeripheral.peripheral.delegate = nil
}
let knownIdentifiers = Array(peripheralsFound.keys)
let knownPeripherals = centralManager?.retrievePeripherals(withIdentifiers: knownIdentifiers)
peripheralsFound.removeAll()
if let knownPeripherals = knownPeripherals {
for peripheral in knownPeripherals {
DLog("Adding prediscovered peripheral: \(peripheral.name ?? peripheral.identifier.uuidString)")
discovered(peripheral: peripheral)
}
}
peripheralsFoundLock.unlock()
// Restore central manager delegate if was changed
centralManager?.delegate = self
if isScanning {
startScan()
}
}
// MARK: - Scan
func startScan(withServices services: [CBUUID]? = nil) {
centralManagerPoweredOnSemaphore.wait()
centralManagerPoweredOnSemaphore.signal()
isScanningWaitingToStart = true
guard let centralManager = centralManager, centralManager.state != .poweredOff && centralManager.state != .unauthorized && centralManager.state != .unsupported else {
DLog("startScan failed because central manager is not ready")
return
}
scanningServicesFilter = services
guard centralManager.state == .poweredOn else {
DLog("startScan failed because central manager is not powered on")
return
}
// DLog("start scan")
isScanning = true
NotificationCenter.default.post(name: .didStartScanning, object: nil)
let options = BleManager.kAlwaysAllowDuplicateKeys ? [CBCentralManagerScanOptionAllowDuplicatesKey: true] : nil
centralManager.scanForPeripherals(withServices: services, options: options)
isScanningWaitingToStart = false
}
func stopScan() {
// DLog("stop scan")
centralManager?.stopScan()
isScanning = false
isScanningWaitingToStart = false
NotificationCenter.default.post(name: .didStopScanning, object: nil)
}
func peripherals() -> [BlePeripheral] {
peripheralsFoundLock.lock(); defer { peripheralsFoundLock.unlock() }
return Array(peripheralsFound.values)
}
func connectedPeripherals() -> [BlePeripheral] {
return peripherals().filter {$0.state == .connected}
}
func connectingPeripherals() -> [BlePeripheral] {
return peripherals().filter {$0.state == .connecting}
}
func connectedOrConnectingPeripherals() -> [BlePeripheral] {
return peripherals().filter {$0.state == .connected || $0.state == .connecting}
}
func refreshPeripherals() {
stopScan()
peripheralsFoundLock.lock()
// Don't remove connnected or connecting peripherals
for (identifier, peripheral) in peripheralsFound {
if peripheral.state != .connected && peripheral.state != .connecting {
peripheralsFound.removeValue(forKey: identifier)
}
}
peripheralsFoundLock.unlock()
//
NotificationCenter.default.post(name: .didUnDiscoverPeripheral, object: nil)
startScan(withServices: scanningServicesFilter)
}
// MARK: - Connection Management
func connect(to peripheral: BlePeripheral, timeout: TimeInterval? = nil, shouldNotifyOnConnection: Bool = false, shouldNotifyOnDisconnection: Bool = false, shouldNotifyOnNotification: Bool = false) {
centralManagerPoweredOnSemaphore.wait()
centralManagerPoweredOnSemaphore.signal()
// Stop scanning when connecting to a peripheral
if BleManager.kStopScanningWhenConnectingToPeripheral {
stopScan()
}
// Connect
NotificationCenter.default.post(name: .willConnectToPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
//DLog("connect")
var options: [String: Bool]?
#if os(OSX)
#else
if shouldNotifyOnConnection || shouldNotifyOnDisconnection || shouldNotifyOnNotification {
options = [CBConnectPeripheralOptionNotifyOnConnectionKey: shouldNotifyOnConnection, CBConnectPeripheralOptionNotifyOnDisconnectionKey: shouldNotifyOnDisconnection, CBConnectPeripheralOptionNotifyOnNotificationKey: shouldNotifyOnNotification]
}
#endif
if let timeout = timeout {
connectionTimeoutTimers[peripheral.identifier] = MSWeakTimer.scheduledTimer(withTimeInterval: timeout, target: self, selector: #selector(connectionTimeoutFired), userInfo: peripheral.identifier, repeats: false, dispatchQueue: .global(qos: .background))
}
centralManager?.connect(peripheral.peripheral, options: options)
}
@objc private func connectionTimeoutFired(timer: MSWeakTimer) {
let peripheralIdentifier = timer.userInfo() as! UUID
DLog("connection timeout fired: \(peripheralIdentifier)")
connectionTimeoutTimers[peripheralIdentifier] = nil
NotificationCenter.default.post(name: .willDisconnectFromPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheralIdentifier])
if let blePeripheral = peripheralsFound[peripheralIdentifier] {
centralManager?.cancelPeripheralConnection(blePeripheral.peripheral)
} else {
DLog("simulate disconnection")
// The blePeripheral is available on peripheralsFound, so simulate the disconnection
NotificationCenter.default.post(name: .didDisconnectFromPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheralIdentifier])
}
}
func disconnect(from peripheral: BlePeripheral) {
DLog("disconnect")
NotificationCenter.default.post(name: .willDisconnectFromPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
centralManager?.cancelPeripheralConnection(peripheral.peripheral)
}
func reconnecToPeripherals(withIdentifiers identifiers: [UUID], withServices services: [CBUUID], timeout: Double? = nil) -> Bool {
var reconnecting = false
let knownPeripherals = centralManager?.retrievePeripherals(withIdentifiers: identifiers)
if let peripherals = knownPeripherals?.filter({identifiers.contains($0.identifier)}), !peripherals.isEmpty {
for peripheral in peripherals {
discovered(peripheral: peripheral)
if let blePeripheral = peripheralsFound[peripheral.identifier] {
connect(to: blePeripheral, timeout: timeout)
reconnecting = true
}
}
} else {
let connectedPeripherals = centralManager?.retrieveConnectedPeripherals(withServices: services)
if let peripherals = connectedPeripherals?.filter({identifiers.contains($0.identifier)}), !peripherals.isEmpty {
for peripheral in peripherals {
discovered(peripheral: peripheral)
if let blePeripheral = peripheralsFound[peripheral.identifier] {
connect(to: blePeripheral, timeout: timeout)
reconnecting = true
}
}
}
}
return reconnecting
}
private func discovered(peripheral: CBPeripheral, advertisementData: [String: Any]? = nil, rssi: Int? = nil) {
peripheralsFoundLock.lock(); defer { peripheralsFoundLock.unlock() }
if let existingPeripheral = peripheralsFound[peripheral.identifier] {
existingPeripheral.lastSeenTime = CFAbsoluteTimeGetCurrent()
if let rssi = rssi, rssi != 127 { // only update rssi value if is defined ( 127 means undefined )
existingPeripheral.rssi = rssi
}
if let advertisementData = advertisementData {
for (key, value) in advertisementData {
existingPeripheral.advertisement.advertisementData.updateValue(value, forKey: key)
}
}
peripheralsFound[peripheral.identifier] = existingPeripheral
} else { // New peripheral found
let blePeripheral = BlePeripheral(peripheral: peripheral, advertisementData: advertisementData, rssi: rssi)
peripheralsFound[peripheral.identifier] = blePeripheral
}
}
// MARK: - Notifications
func peripheral(from notification: Notification) -> BlePeripheral? {
guard let uuid = notification.userInfo?[NotificationUserInfoKey.uuid.rawValue] as? UUID else { return nil }
return peripheral(with: uuid)
}
func peripheral(with uuid: UUID) -> BlePeripheral? {
return peripheralsFound[uuid]
}
}
// MARK: - CBCentralManagerDelegate
extension BleManager: CBCentralManagerDelegate {
func centralManagerDidUpdateState(_ central: CBCentralManager) {
DLog("centralManagerDidUpdateState: \(central.state.rawValue)")
// Unlock state lock if we have a known state
if central.state == .poweredOn || central.state == .poweredOff || central.state == .unsupported || central.state == .unauthorized {
centralManagerPoweredOnSemaphore.signal()
}
// Scanning
if central.state == .poweredOn {
if isScanningWaitingToStart {
startScan(withServices: scanningServicesFilter) // Continue scanning now that bluetooth is back
}
} else {
if isScanning {
isScanningWaitingToStart = true
}
isScanning = false
}
NotificationCenter.default.post(name: .didUpdateBleState, object: nil)
}
/*
func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) {
}*/
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
// DLog("didDiscover: \(peripheral.name ?? peripheral.identifier.uuidString)")
let rssi = RSSI.intValue
DispatchQueue.main.async { // This Fixes iOS12 race condition on cached filtered peripherals. TODO: investigate
self.discovered(peripheral: peripheral, advertisementData: advertisementData, rssi: rssi)
NotificationCenter.default.post(name: .didDiscoverPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
}
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
DLog("didConnect: \(peripheral.identifier)")
// Remove connection timeout if exists
if let timer = connectionTimeoutTimers[peripheral.identifier] {
timer.invalidate()
connectionTimeoutTimers[peripheral.identifier] = nil
}
// Send notification
NotificationCenter.default.post(name: .didConnectToPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
}
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
DLog("didFailToConnect")
NotificationCenter.default.post(name: .didDisconnectFromPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
}
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
DLog("didDisconnectPeripheral")
// Clean
peripheralsFound[peripheral.identifier]?.reset()
// Notify
NotificationCenter.default.post(name: .didDisconnectFromPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
// Remove from peripheral list (after sending notification so the receiving objects can query about the peripheral before being removed)
peripheralsFoundLock.lock()
peripheralsFound.removeValue(forKey: peripheral.identifier)
peripheralsFoundLock.unlock()
}
}
// MARK: - Custom Notifications
extension Notification.Name {
private static let kPrefix = Bundle.main.bundleIdentifier!
static let didUpdateBleState = Notification.Name(kPrefix+".didUpdateBleState")
static let didStartScanning = Notification.Name(kPrefix+".didStartScanning")
static let didStopScanning = Notification.Name(kPrefix+".didStopScanning")
static let didDiscoverPeripheral = Notification.Name(kPrefix+".didDiscoverPeripheral")
static let didUnDiscoverPeripheral = Notification.Name(kPrefix+".didUnDiscoverPeripheral")
static let willConnectToPeripheral = Notification.Name(kPrefix+".willConnectToPeripheral")
static let didConnectToPeripheral = Notification.Name(kPrefix+".didConnectToPeripheral")
static let willDisconnectFromPeripheral = Notification.Name(kPrefix+".willDisconnectFromPeripheral")
static let didDisconnectFromPeripheral = Notification.Name(kPrefix+".didDisconnectFromPeripheral")
}

View file

@ -0,0 +1,76 @@
//
// BlePeripheral+Battery.swift
// Bluefruit
//
// Created by Antonio García on 22/06/2017.
// Copyright © 2017 Adafruit. All rights reserved.
//
import Foundation
import CoreBluetooth
extension BlePeripheral {
// Costants
static let kBatteryServiceUUID = CBUUID(string: "180F")
static let kBatteryCharacteristicUUID = CBUUID(string: "2A19")
// MARK: - Actions
func readBatteryLevel(handler: @escaping ((Int, Error?) -> Void)) {
self.characteristic(uuid: BlePeripheral.kBatteryCharacteristicUUID, serviceUuid: BlePeripheral.kBatteryServiceUUID) { (characteristic, error) in
guard error == nil, let characteristic = characteristic else { DLog("Error reading battery characteristic: \(error?.localizedDescription ?? "")"); return }
self.readCharacteristic(characteristic) { (result, error) in
guard error == nil, let data = result as? Data, data.count >= 1 else {
DLog("Error reading battery level: \(error?.localizedDescription ?? "")")
handler(-1, error)
return
}
let level = Int(data[0]) // from 0 to 100
handler(level, nil)
}
}
}
func startReadingBatteryLevel(handler: @escaping ((Int) -> Void)) {
self.characteristic(uuid: BlePeripheral.kBatteryCharacteristicUUID, serviceUuid: BlePeripheral.kBatteryServiceUUID) { (characteristic, error) in
guard error == nil, let characteristic = characteristic else { DLog("Error starting read for battery characteristic: \(error?.localizedDescription ?? "")"); return }
// Read current value
self.readCharacteristic(characteristic) { (result, error) in
guard error == nil, let data = result as? Data, data.count >= 1 else { DLog("Error reading battery level: \(error?.localizedDescription ?? "")"); return }
let level = Int(data[0]) // from 0 to 100
handler(level)
}
// Enable notifications to receive value changes
self.enableNotify(for: characteristic, handler: { error in
guard error == nil else { DLog("Error receiving notify for battery level"); return }
guard let data = characteristic.value, data.count >= 1 else { DLog("Invalid data receiving notify for battery level"); return }
let level = Int(data[0]) // from 0 to 100
handler(level)
})
}
}
func stopReadingBatteryLevel() {
self.characteristic(uuid: BlePeripheral.kBatteryCharacteristicUUID, serviceUuid: BlePeripheral.kBatteryServiceUUID) { (characteristic, error) in
guard error == nil, let characteristic = characteristic else { DLog("Error stopping read for battery characteristic: \(error?.localizedDescription ?? "")"); return }
self.disableNotify(for: characteristic)
}
}
// MARK: - Utils
func isBatteryAdvertised() -> Bool {
return advertisement.services?.contains(BlePeripheral.kBatteryServiceUUID) ?? false
}
func hasBattery() -> Bool {
return peripheral.services?.first(where: {$0.uuid == BlePeripheral.kBatteryServiceUUID}) != nil
}
}

View file

@ -0,0 +1,333 @@
//
// BlePeripheral+Uart.swift
// Calibration
//
// Created by Antonio García on 19/10/2016.
// Copyright © 2016 Adafruit. All rights reserved.
//
import Foundation
import CoreBluetooth
extension BlePeripheral {
// Config
private static let kDebugLog = false
// Costants
static let kUartServiceUUID = CBUUID(string: "6e400001-b5a3-f393-e0a9-e50e24dcca9e")
static let kUartTxCharacteristicUUID = CBUUID(string: "6e400002-b5a3-f393-e0a9-e50e24dcca9e")
static let kUartRxCharacteristicUUID = CBUUID(string: "6e400003-b5a3-f393-e0a9-e50e24dcca9e")
//private static let kUartTxMaxBytes = 20
static let kUartReplyDefaultTimeout = 2.0 // seconds
// MARK: - Custom properties
private struct CustomPropertiesKeys {
static var uartRxCharacteristic: CBCharacteristic?
static var uartTxCharacteristic: CBCharacteristic?
static var uartTxCharacteristicWriteType: CBCharacteristicWriteType?
static var sendSequentiallyCancelled: Bool = false
}
private var uartRxCharacteristic: CBCharacteristic? {
get {
return objc_getAssociatedObject(self, &CustomPropertiesKeys.uartRxCharacteristic) as! CBCharacteristic?
}
set {
objc_setAssociatedObject(self, &CustomPropertiesKeys.uartRxCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
private var uartTxCharacteristic: CBCharacteristic? {
get {
return objc_getAssociatedObject(self, &CustomPropertiesKeys.uartTxCharacteristic) as! CBCharacteristic?
}
set {
objc_setAssociatedObject(self, &CustomPropertiesKeys.uartTxCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
private var uartTxCharacteristicWriteType: CBCharacteristicWriteType? {
get {
return objc_getAssociatedObject(self, &CustomPropertiesKeys.uartTxCharacteristicWriteType) as! CBCharacteristicWriteType?
}
set {
objc_setAssociatedObject(self, &CustomPropertiesKeys.uartTxCharacteristicWriteType, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
private var isSendSequentiallyCancelled: Bool {
get {
return objc_getAssociatedObject(self, &CustomPropertiesKeys.sendSequentiallyCancelled) as! Bool
}
set {
objc_setAssociatedObject(self, &CustomPropertiesKeys.sendSequentiallyCancelled, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
// MARK: - Errors
enum PeripheralUartError: Error {
case invalidCharacteristic
case enableNotifyFailed
}
// MARK: - Initialization
func uartEnable(uartRxHandler: ((Data?, UUID, Error?) -> Void)?, completion: ((Error?) -> Void)?) {
// Get uart communications characteristic
characteristic(uuid: BlePeripheral.kUartTxCharacteristicUUID, serviceUuid: BlePeripheral.kUartServiceUUID) { [unowned self] (characteristic, error) in
guard let characteristic = characteristic, error == nil else {
completion?(error != nil ? error : PeripheralUartError.invalidCharacteristic)
return
}
self.uartTxCharacteristic = characteristic
self.uartTxCharacteristicWriteType = characteristic.properties.contains(.writeWithoutResponse) ? .withoutResponse:.withResponse
//self.uartTxCharacteristicWriteType = .withResponse // Debug: force withResponse
self.characteristic(uuid: BlePeripheral.kUartRxCharacteristicUUID, serviceUuid: BlePeripheral.kUartServiceUUID) { [unowned self] (characteristic, error) in
guard let characteristic = characteristic, error == nil else {
completion?(error != nil ? error : PeripheralUartError.invalidCharacteristic)
return
}
// Get characteristic info
self.uartRxCharacteristic = characteristic
// Prepare notification handler
let notifyHandler: ((Error?) -> Void)? = { [unowned self] error in
let value = characteristic.value
if let value = value, BlePeripheral.kDebugLog == true, error == nil {
UartLogManager.log(data: value, type: .uartRx)
}
uartRxHandler?(value, self.identifier, error)
}
// Enable notifications
if !characteristic.isNotifying {
self.enableNotify(for: characteristic, handler: notifyHandler, completion: { error in
completion?(error != nil ? error : (characteristic.isNotifying ? nil : PeripheralUartError.enableNotifyFailed))
})
} else {
self.updateNotifyHandler(for: characteristic, handler: notifyHandler)
completion?(nil)
}
}
}
}
func isUartEnabled() -> Bool {
return uartRxCharacteristic != nil && uartTxCharacteristic != nil && uartTxCharacteristicWriteType != nil && uartRxCharacteristic!.isNotifying
}
func uartDisable() {
// Clear all Uart specific data
defer {
uartRxCharacteristic = nil
uartTxCharacteristic = nil
uartTxCharacteristicWriteType = nil
}
// Disable notify
guard let characteristic = uartRxCharacteristic, characteristic.isNotifying else { return }
disableNotify(for: characteristic)
}
// MARK: - Send
func uartSend(data: Data?, progress: ((Float)->Void)? = nil, completion: ((Error?) -> Void)? = nil) {
guard let data = data else { completion?(nil); return }
guard let uartTxCharacteristic = uartTxCharacteristic, let uartTxCharacteristicWriteType = uartTxCharacteristicWriteType else {
DLog("Command Error: characteristic no longer valid")
completion?(PeripheralUartError.invalidCharacteristic)
return
}
// Split data in kUartTxMaxBytes bytes packets
var offset = 0
var writtenSize = 0
//let maxPacketSize = peripheral.maximumWriteValueLength(for: uartTxCharacteristicWriteType)
let maxPacketSize = peripheral.maximumWriteValueLength(for: .withoutResponse) // Use .withoutResponse event if sending .withResponse or didWriteValueFor is not called when using a larger packet
repeat {
let packetSize = min(data.count-offset, maxPacketSize)
let packet = data.subdata(in: offset..<offset+packetSize)
let writeStartingOffset = offset
self.write(data: packet, for: uartTxCharacteristic, type: uartTxCharacteristicWriteType) { error in
if let error = error {
DLog("write packet at offset: \(writeStartingOffset) error: \(error)")
} else {
//DLog("tx offset:\(offset): \(hexDescription(data: packet))")
//DLog("uart tx write (hex): \(hexDescription(data: packet))")
// DLog("uart tx write (dec): \(decimalDescription(data: packet))")
// DLog("uart tx write (utf8): \(String(data: packet, encoding: .utf8) ?? "<invalid>")")
writtenSize += packetSize
if BlePeripheral.kDebugLog {
UartLogManager.log(data: packet, type: .uartTx)
}
}
if writtenSize >= data.count {
progress?(1)
completion?(error)
}
else {
progress?(Float(writtenSize) / Float(data.count))
}
}
offset += packetSize
} while offset < data.count
}
func uartSendEachPacketSequentially(data: Data?, withResponseEveryPacketCount: Int, progress: ((Float)->Void)? = nil, completion: ((Error?) -> Void)? = nil) {
guard let data = data else { completion?(nil); return }
guard let uartTxCharacteristic = uartTxCharacteristic else {//}, let uartTxCharacteristicWriteType = uartTxCharacteristicWriteType else {
DLog("Command Error: characteristic no longer valid")
completion?(PeripheralUartError.invalidCharacteristic)
return
}
isSendSequentiallyCancelled = false
uartSentPacket(data: data, offset: 0, uartTxCharacteristic: uartTxCharacteristic, withResponseEveryPacketCount: withResponseEveryPacketCount, numPacketsRemainingForDelay: withResponseEveryPacketCount, progress: progress, completion: completion)
}
func uartCancelOngoingSendPacketSequentiallyInMainThread() {
isSendSequentiallyCancelled = true
}
private func uartSentPacket(data: Data, offset: Int, uartTxCharacteristic: CBCharacteristic, withResponseEveryPacketCount: Int, numPacketsRemainingForDelay: Int, progress: ((Float)->Void)? = nil, completion: ((Error?) -> Void)? = nil) {
//let maxPacketSize = peripheral.maximumWriteValueLength(for: uartTxCharacteristicWriteType)
let maxPacketSize = peripheral.maximumWriteValueLength(for: .withoutResponse) // Use .withoutResponse event if sending .withResponse or didWriteValueFor is not called when using a larger packet
let packetSize = min(data.count-offset, maxPacketSize)
let packet = data.subdata(in: offset..<offset+packetSize)
let writeStartingOffset = offset
let uartTxCharacteristicWriteType: CBCharacteristicWriteType = numPacketsRemainingForDelay <= 0 ? .withResponse : .withoutResponse // Send a packet .withResponse to force wait until receive response and avoid dropping packets if the peripheral is not processing them fast enough
self.write(data: packet, for: uartTxCharacteristic, type: uartTxCharacteristicWriteType) { error in
var writtenSize = writeStartingOffset
if let error = error {
DLog("write packet at offset: \(writeStartingOffset) error: \(error)")
} else {
DLog("uart tx \(uartTxCharacteristicWriteType == .withResponse ? "withResponse":"withoutResponse") offset: \(writeStartingOffset): \(HexUtils.hexDescription(data: packet))")
writtenSize += packet.count
if BlePeripheral.kDebugLog {
UartLogManager.log(data: packet, type: .uartTx)
}
if !self.isSendSequentiallyCancelled && writtenSize < data.count {
DispatchQueue.main.async { // Send in main thread to avoid commandqueue function nesting limit if there is a lot of data to send
self.uartSentPacket(data: data, offset: writtenSize, uartTxCharacteristic: uartTxCharacteristic, withResponseEveryPacketCount: withResponseEveryPacketCount, numPacketsRemainingForDelay: numPacketsRemainingForDelay <= 0 ? withResponseEveryPacketCount : numPacketsRemainingForDelay-1, progress: progress, completion: completion)
}
}
}
// Call completion handlers in main thread
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
if self.isSendSequentiallyCancelled {
completion?(nil)
}
else if writtenSize >= data.count {
progress?(1)
completion?(error)
}
else {
progress?(Float(writtenSize) / Float(data.count))
}
}
}
}
func uartSendAndWaitReply(data: Data?, writeProgress: ((Float)->Void)? = nil, writeCompletion: ((Error?) -> Void)? = nil, readTimeout: Double? = BlePeripheral.kUartReplyDefaultTimeout, readCompletion: @escaping CapturedReadCompletionHandler) {
guard let data = data else {
if let writeCompletion = writeCompletion {
writeCompletion(nil)
} else {
// If no writeCompletion defined, move the error result to the readCompletion
readCompletion(nil, nil)
}
return
}
guard let uartTxCharacteristic = uartTxCharacteristic, /*let uartTxCharacteristicWriteType = uartTxCharacteristicWriteType, */let uartRxCharacteristic = uartRxCharacteristic else {
DLog("Command Error: characteristic no longer valid")
if let writeCompletion = writeCompletion {
writeCompletion(PeripheralUartError.invalidCharacteristic)
} else {
// If no writeCompletion defined, move the error result to the readCompletion
readCompletion(nil, PeripheralUartError.invalidCharacteristic)
}
return
}
// Split data in kUartTxMaxBytes bytes packets
var offset = 0
var writtenSize = 0
//let maxPacketSize = peripheral.maximumWriteValueLength(for: .withResponse)
let maxPacketSize = peripheral.maximumWriteValueLength(for: .withoutResponse) // Use .withoutResponse event if sending .withResponse or didWriteValueFor is not called when using a larger packet
repeat {
let packetSize = min(data.count-offset, maxPacketSize)
let packet = data.subdata(in: offset..<offset+packetSize)
offset += packetSize
writeAndCaptureNotify(data: packet, for: uartTxCharacteristic, writeCompletion: { error in
if let error = error {
DLog("write packet at offset: \(offset) error: \(error)")
} else {
DLog("uart tx writeAndWait (hex): \(HexUtils.hexDescription(data: packet))")
// DLog("uart tx writeAndWait (dec): \(decimalDescription(data: packet))")
// DLog("uart tx writeAndWait (utf8): \(String(data: packet, encoding: .utf8) ?? "<invalid>")")
writtenSize += packetSize
}
if writtenSize >= data.count {
writeProgress?(1)
writeCompletion?(error)
}
else {
writeProgress?(Float(writtenSize) / Float(data.count))
}
}, readCharacteristic: uartRxCharacteristic, readTimeout: readTimeout, readCompletion: readCompletion)
} while offset < data.count
}
// MARK: - Utils
func isUartAdvertised() -> Bool {
return advertisement.services?.contains(BlePeripheral.kUartServiceUUID) ?? false
}
func hasUart() -> Bool {
return peripheral.services?.first(where: {$0.uuid == BlePeripheral.kUartServiceUUID}) != nil
}
}
// MARK: - Data + CRC
extension Data {
mutating func appendCrc() {
var dataBytes = [UInt8](repeating: 0, count: count)
copyBytes(to: &dataBytes, count: count)
var crc: UInt8 = 0
for i in dataBytes { //add all bytes
crc = crc &+ i
}
crc = ~crc //invert
append(&crc, count: 1)
}
}

View file

@ -0,0 +1,627 @@
//
// BlePeripheral.swift
// NewtManager
//
// Created by Antonio García on 12/09/2016.
// Copyright © 2016 Adafruit. All rights reserved.
//
import Foundation
import CoreBluetooth
#if COMMANDLINE
#else
import MSWeakTimer
#endif
// TODO: Modernize completion blocks to use Swift.Result
class BlePeripheral: NSObject {
// Config
private static var kProfileCharacteristicUpdates = true
// Notifications
enum NotificationUserInfoKey: String {
case uuid = "uuid"
case name = "name"
case invalidatedServices = "invalidatedServices"
}
enum PeripheralError: Error {
case timeout
}
// Data
var peripheral: CBPeripheral
var rssi: Int? // rssi only is updated when a non undefined value is received from CoreBluetooth. Note: this is slighty different to the CoreBluetooth implementation, because it will not be updated with undefined values
var lastSeenTime: CFAbsoluteTime
var identifier: UUID {
return peripheral.identifier
}
var name: String? {
return peripheral.name
}
var state: CBPeripheralState {
return peripheral.state
}
struct Advertisement {
var advertisementData: [String: Any]
init(advertisementData: [String: Any]?) {
self.advertisementData = advertisementData ?? [String: Any]()
}
// Advertisement data formatted
var localName: String? {
return advertisementData[CBAdvertisementDataLocalNameKey] as? String
}
var manufacturerData: Data? {
return advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data
}
var manufacturerHexDescription: String? {
guard let manufacturerData = manufacturerData else { return nil }
return HexUtils.hexDescription(data: manufacturerData)
// return String(data: manufacturerData, encoding: .utf8)
}
var manufacturerIdentifier: Data? {
guard let manufacturerData = manufacturerData, manufacturerData.count >= 2 else { return nil }
let manufacturerIdentifierData = manufacturerData[0..<2]
return manufacturerIdentifierData
}
var services: [CBUUID]? {
return advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID]
}
var servicesOverflow: [CBUUID]? {
return advertisementData[CBAdvertisementDataOverflowServiceUUIDsKey] as? [CBUUID]
}
var servicesSolicited: [CBUUID]? {
return advertisementData[CBAdvertisementDataSolicitedServiceUUIDsKey] as? [CBUUID]
}
var serviceData: [CBUUID: Data]? {
return advertisementData[CBAdvertisementDataServiceDataKey] as? [CBUUID: Data]
}
var txPower: Int? {
let number = advertisementData[CBAdvertisementDataTxPowerLevelKey] as? NSNumber
return number?.intValue
}
var isConnectable: Bool? {
let connectableNumber = advertisementData[CBAdvertisementDataIsConnectable] as? NSNumber
return connectableNumber?.boolValue
}
}
var advertisement: Advertisement
typealias CapturedReadCompletionHandler = ((_ value: Any?, _ error: Error?) -> Void)
private class CaptureReadHandler {
var identifier: String
var result: CapturedReadCompletionHandler
var timeoutTimer: MSWeakTimer?
var timeoutAction: ((String)->())?
var isNotifyOmitted: Bool
init(identifier: String, result: @escaping CapturedReadCompletionHandler, timeout: Double?, timeoutAction:((String)->())?, isNotifyOmitted: Bool = false) {
self.identifier = identifier
self.result = result
self.isNotifyOmitted = isNotifyOmitted
if let timeout = timeout {
self.timeoutAction = timeoutAction
timeoutTimer = MSWeakTimer.scheduledTimer(withTimeInterval: timeout, target: self, selector: #selector(timerFired), userInfo: nil, repeats: false, dispatchQueue: .global(qos: .background))
}
}
@objc private func timerFired() {
timeoutTimer?.invalidate()
timeoutTimer = nil
result(nil, PeripheralError.timeout)
timeoutAction?(identifier)
}
}
private func timeOutRemoveCaptureHandler(identifier: String) { // Default behaviour for a capture handler timeout
guard captureReadHandlers.count > 0, let index = captureReadHandlers.firstIndex(where: {$0.identifier == identifier}) else { return }
// DLog("captureReadHandlers index: \(index) / \(captureReadHandlers.count)")
// Remove capture handler
captureReadHandlers.remove(at: index)
finishedExecutingCommand(error: PeripheralError.timeout)
}
// Internal data
private var notifyHandlers = [String: ((Error?) -> Void)]() // Nofify handlers for each service-characteristic
private var captureReadHandlers = [CaptureReadHandler]()
private var commandQueue = CommandQueue<BleCommand>()
// Profiling
//private var profileStartTime: CFTimeInterval = 0
// MARK: - Init
init(peripheral: CBPeripheral, advertisementData: [String: Any]?, rssi: Int?) {
self.peripheral = peripheral
self.advertisement = Advertisement(advertisementData: advertisementData)
self.rssi = rssi
self.lastSeenTime = CFAbsoluteTimeGetCurrent()
super.init()
self.peripheral.delegate = self
// DLog("create peripheral: \(peripheral.name ?? peripheral.identifier.uuidString)")
commandQueue.executeHandler = executeCommand
}
deinit {
//DLog("peripheral deinit")
}
func reset() {
rssi = nil
notifyHandlers.removeAll()
captureReadHandlers.removeAll()
commandQueue.first()?.isCancelled = true // Stop current command if is processing
commandQueue.removeAll()
}
// MARK: - Discover
func discover(serviceUuids: [CBUUID]?, completion: ((Error?) -> Void)?) {
let command = BleCommand(type: .discoverService, parameters: serviceUuids, completion: completion)
commandQueue.append(command)
}
func discover(characteristicUuids: [CBUUID]?, service: CBService, completion: ((Error?) -> Void)?) {
let command = BleCommand(type: .discoverCharacteristic, parameters: [characteristicUuids as Any, service], completion: completion)
commandQueue.append(command)
}
func discover(characteristicUuids: [CBUUID]?, serviceUuid: CBUUID, completion: ((Error?) -> Void)?) {
// Discover service
discover(serviceUuids: [serviceUuid]) { [unowned self] error in
guard error == nil else {
completion?(error)
return
}
guard let service = self.peripheral.services?.first(where: {$0.uuid == serviceUuid}) else {
completion?(BleCommand.CommandError.invalidService)
return
}
// Discover characteristic
self.discover(characteristicUuids: characteristicUuids, service: service, completion: completion)
}
}
func discoverDescriptors(characteristic: CBCharacteristic, completion: ((Error?) -> Void)?) {
let command = BleCommand(type: .discoverDescriptor, parameters: [characteristic], completion: completion)
commandQueue.append(command)
}
// MARK: - Service
func discoveredService(uuid: CBUUID) -> CBService? {
let service = peripheral.services?.first(where: {$0.uuid == uuid})
return service
}
func service(uuid: CBUUID, completion: ((CBService?, Error?) -> Void)?) {
if let discoveredService = discoveredService(uuid: uuid) { // Service was already discovered
completion?(discoveredService, nil)
} else {
discover(serviceUuids: [uuid], completion: { [unowned self] (error) in // Discover service
var discoveredService: CBService?
if error == nil {
discoveredService = self.discoveredService(uuid: uuid)
}
completion?(discoveredService, error)
})
}
}
// MARK: - Characteristic
func discoveredCharacteristic(uuid: CBUUID, service: CBService) -> CBCharacteristic? {
let characteristic = service.characteristics?.first(where: {$0.uuid == uuid})
return characteristic
}
func characteristic(uuid: CBUUID, service: CBService, completion: ((CBCharacteristic?, Error?) -> Void)?) {
if let discoveredCharacteristic = discoveredCharacteristic(uuid: uuid, service: service) { // Characteristic was already discovered
completion?(discoveredCharacteristic, nil)
} else {
discover(characteristicUuids: [uuid], service: service, completion: { [unowned self] (error) in // Discover characteristic
var discoveredCharacteristic: CBCharacteristic?
if error == nil {
discoveredCharacteristic = self.discoveredCharacteristic(uuid: uuid, service: service)
}
completion?(discoveredCharacteristic, error)
})
}
}
func characteristic(uuid: CBUUID, serviceUuid: CBUUID, completion: ((CBCharacteristic?, Error?) -> Void)?) {
if let discoveredService = discoveredService(uuid: serviceUuid) { // Service was already discovered
characteristic(uuid: uuid, service: discoveredService, completion: completion)
} else { // Discover service
service(uuid: serviceUuid) { (service, error) in
if let service = service, error == nil { // Discover characteristic
self.characteristic(uuid: uuid, service: service, completion: completion)
} else {
completion?(nil, error != nil ? error: BleCommand.CommandError.invalidService)
}
}
}
}
func enableNotify(for characteristic: CBCharacteristic, handler: ((Error?) -> Void)?, completion: ((Error?) -> Void)? = nil) {
let command = BleCommand(type: .setNotify, parameters: [characteristic, true, handler as Any], completion: completion)
commandQueue.append(command)
}
func disableNotify(for characteristic: CBCharacteristic, completion: ((Error?) -> Void)? = nil) {
let command = BleCommand(type: .setNotify, parameters: [characteristic, false], completion: completion)
commandQueue.append(command)
}
func updateNotifyHandler(for characteristic: CBCharacteristic, handler: ((Error?) -> Void)? = nil) {
let identifier = handlerIdentifier(from: characteristic)
if notifyHandlers[identifier] == nil {
DLog("Warning: trying to update inexistent notifyHandler")
}
notifyHandlers[identifier] = handler
}
func readCharacteristic(_ characteristic: CBCharacteristic, completion readCompletion: @escaping CapturedReadCompletionHandler) {
let command = BleCommand(type: .readCharacteristic, parameters: [characteristic, readCompletion as Any], completion: nil)
commandQueue.append(command)
}
func write(data: Data, for characteristic: CBCharacteristic, type: CBCharacteristicWriteType, completion: ((Error?) -> Void)? = nil) {
let command = BleCommand(type: .writeCharacteristic, parameters: [characteristic, type, data], completion: completion)
commandQueue.append(command)
}
func writeAndCaptureNotify(data: Data, for characteristic: CBCharacteristic, writeCompletion: ((Error?) -> Void)? = nil, readCharacteristic: CBCharacteristic, readTimeout: Double? = nil, readCompletion: CapturedReadCompletionHandler? = nil) {
let type: CBCharacteristicWriteType = .withResponse // Force write with response
let command = BleCommand(type: .writeCharacteristicAndWaitNofity, parameters: [characteristic, type, data, readCharacteristic, readCompletion as Any, readTimeout as Any], timeout: readTimeout, completion: writeCompletion)
commandQueue.append(command)
}
// MARK: - Descriptors
func readDescriptor(_ descriptor: CBDescriptor, completion readCompletion: @escaping CapturedReadCompletionHandler) {
let command = BleCommand(type: .readDescriptor, parameters: [descriptor, readCompletion as Any], completion: nil)
commandQueue.append(command)
}
// MARK: - Rssi
func readRssi() {
peripheral.readRSSI()
}
// MARK: - Command Queue
private class BleCommand: Equatable {
enum CommandType {
case discoverService
case discoverCharacteristic
case discoverDescriptor
case setNotify
case readCharacteristic
case writeCharacteristic
case writeCharacteristicAndWaitNofity
case readDescriptor
}
enum CommandError: Error {
case invalidService
}
var type: CommandType
var parameters: [Any]?
var completion: ((Error?) -> Void)?
var isCancelled = false
init(type: CommandType, parameters: [Any]?, timeout: Double? = nil, completion: ((Error?) -> Void)?) {
self.type = type
self.parameters = parameters
self.completion = completion
}
func completion(withError error: Error?) {
completion?(error)
}
static func == (left: BleCommand, right: BleCommand) -> Bool {
return left.type == right.type
}
}
private func executeCommand(command: BleCommand) {
switch command.type {
case .discoverService:
discoverService(with: command)
case .discoverCharacteristic:
discoverCharacteristic(with: command)
case .discoverDescriptor:
discoverDescriptor(with: command)
case .setNotify:
setNotify(with: command)
case .readCharacteristic:
readCharacteristic(with: command)
case .writeCharacteristic, .writeCharacteristicAndWaitNofity:
write(with: command)
case .readDescriptor:
readDescriptor(with: command)
}
}
private func handlerIdentifier(from characteristic: CBCharacteristic) -> String {
return "\(characteristic.service.uuid.uuidString)-\(characteristic.uuid.uuidString)"
}
private func handlerIdentifier(from descriptor: CBDescriptor) -> String {
return "\(descriptor.characteristic.service.uuid.uuidString)-\(descriptor.characteristic.uuid.uuidString)-\(descriptor.uuid.uuidString)"
}
private func finishedExecutingCommand(error: Error?) {
//DLog("finishedExecutingCommand")
// Result Callback
if let command = commandQueue.first(), !command.isCancelled {
command.completion(withError: error)
}
commandQueue.executeNext()
}
// MARK: - Commands
private func discoverService(with command: BleCommand) {
var serviceUuids = command.parameters as? [CBUUID]
let discoverAll = serviceUuids == nil
// Remove services already discovered from the query
if let services = peripheral.services, let serviceUuidsToDiscover = serviceUuids {
for (i, serviceUuid) in serviceUuidsToDiscover.enumerated().reversed() {
if services.contains(where: {$0.uuid == serviceUuid}) {
serviceUuids!.remove(at: i)
}
}
}
// Discover remaining uuids
if discoverAll || (serviceUuids != nil && serviceUuids!.count > 0) {
peripheral.discoverServices(serviceUuids)
} else {
// Everthing was already discovered
finishedExecutingCommand(error: nil)
}
}
private func discoverCharacteristic(with command: BleCommand) {
var characteristicUuids = command.parameters![0] as? [CBUUID]
let discoverAll = characteristicUuids == nil
let service = command.parameters![1] as! CBService
// Remove services already discovered from the query
if let characteristics = service.characteristics, let characteristicUuidsToDiscover = characteristicUuids {
for (i, characteristicUuid) in characteristicUuidsToDiscover.enumerated().reversed() {
if characteristics.contains(where: {$0.uuid == characteristicUuid}) {
characteristicUuids!.remove(at: i)
}
}
}
// Discover remaining uuids
if discoverAll || (characteristicUuids != nil && characteristicUuids!.count > 0) {
//DLog("discover \(characteristicUuids == nil ? "all": String(characteristicUuids!.count)) characteristics for \(service.uuid.uuidString)")
peripheral.discoverCharacteristics(characteristicUuids, for: service)
} else {
// Everthing was already discovered
finishedExecutingCommand(error: nil)
}
}
private func discoverDescriptor(with command: BleCommand) {
let characteristic = command.parameters![0] as! CBCharacteristic
peripheral.discoverDescriptors(for: characteristic)
}
private func setNotify(with command: BleCommand) {
let characteristic = command.parameters![0] as! CBCharacteristic
let enabled = command.parameters![1] as! Bool
let identifier = handlerIdentifier(from: characteristic)
if enabled {
let handler = command.parameters![2] as? ((Error?) -> Void)
notifyHandlers[identifier] = handler
} else {
notifyHandlers.removeValue(forKey: identifier)
}
peripheral.setNotifyValue(enabled, for: characteristic)
}
private func readCharacteristic(with command: BleCommand) {
let characteristic = command.parameters!.first as! CBCharacteristic
let completion = command.parameters![1] as! CapturedReadCompletionHandler
let identifier = handlerIdentifier(from: characteristic)
let captureReadHandler = CaptureReadHandler(identifier: identifier, result: completion, timeout: nil, timeoutAction: timeOutRemoveCaptureHandler)
captureReadHandlers.append(captureReadHandler)
peripheral.readValue(for: characteristic)
}
private func write(with command: BleCommand) {
let characteristic = command.parameters![0] as! CBCharacteristic
let writeType = command.parameters![1] as! CBCharacteristicWriteType
let data = command.parameters![2] as! Data
peripheral.writeValue(data, for: characteristic, type: writeType)
if writeType == .withoutResponse {
if !command.isCancelled, command.type == .writeCharacteristicAndWaitNofity {
let readCharacteristic = command.parameters![3] as! CBCharacteristic
let readCompletion = command.parameters![4] as! CapturedReadCompletionHandler
let timeout = command.parameters![5] as? Double
let identifier = handlerIdentifier(from: readCharacteristic)
let captureReadHandler = CaptureReadHandler(identifier: identifier, result: readCompletion, timeout: timeout, timeoutAction: timeOutRemoveCaptureHandler)
captureReadHandlers.append(captureReadHandler)
}
finishedExecutingCommand(error: nil)
}
}
private func readDescriptor(with command: BleCommand) {
let descriptor = command.parameters!.first as! CBDescriptor
let completion = command.parameters![1] as! CapturedReadCompletionHandler
let identifier = handlerIdentifier(from: descriptor)
let captureReadHandler = CaptureReadHandler(identifier: identifier, result: completion, timeout: nil, timeoutAction: timeOutRemoveCaptureHandler)
captureReadHandlers.append(captureReadHandler)
peripheral.readValue(for: descriptor)
}
}
// MARK: - CBPeripheralDelegate
extension BlePeripheral: CBPeripheralDelegate {
func peripheralDidUpdateName(_ peripheral: CBPeripheral) {
DLog("peripheralDidUpdateName: \(name ?? "{ No Name }")")
NotificationCenter.default.post(name: .peripheralDidUpdateName, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier, NotificationUserInfoKey.name.rawValue: name as Any])
}
func peripheral(_ peripheral: CBPeripheral, didModifyServices invalidatedServices: [CBService]) {
DLog("didModifyServices")
NotificationCenter.default.post(name: .peripheralDidModifyServices, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier, NotificationUserInfoKey.invalidatedServices.rawValue: invalidatedServices])
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
DLog("didDiscoverServices for: \(peripheral.name ?? peripheral.identifier.uuidString)")
finishedExecutingCommand(error: error)
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
DLog("didDiscoverCharacteristicsFor: \(service.uuid.uuidString)")
finishedExecutingCommand(error: error)
}
func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
finishedExecutingCommand(error: error)
}
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
let identifier = handlerIdentifier(from: characteristic)
/*
if (BlePeripheral.kProfileCharacteristicUpdates) {
let currentTime = CACurrentMediaTime()
let elapsedTime = currentTime - profileStartTime
DLog("elapsed: \(String(format: "%.1f", elapsedTime * 1000))")
profileStartTime = currentTime
}
*/
//DLog("didUpdateValueFor \(characteristic.uuid.uuidString): \(String(data: characteristic.value ?? Data(), encoding: .utf8) ?? "<invalid>")")
// Check if waiting to capture this read
var isNotifyOmitted = false
var hasCaptureHandler = false
if captureReadHandlers.count > 0, let index = captureReadHandlers.firstIndex(where: {$0.identifier == identifier}) {
hasCaptureHandler = true
// DLog("captureReadHandlers index: \(index) / \(captureReadHandlers.count)")
// Remove capture handler
let captureReadHandler = captureReadHandlers.remove(at: index)
// DLog("captureReadHandlers postRemove count: \(captureReadHandlers.count)")
// Cancel timeout timer
captureReadHandler.timeoutTimer?.invalidate()
captureReadHandler.timeoutTimer = nil
// Send result
let value = characteristic.value
// DLog("updated value: \(String(data: value!, encoding: .utf8)!)")
captureReadHandler.result(value, error)
isNotifyOmitted = captureReadHandler.isNotifyOmitted
}
// Notify
if !isNotifyOmitted {
if let notifyHandler = notifyHandlers[identifier] {
//let currentTime = CACurrentMediaTime()
notifyHandler(error)
//DLog("elapsed: \(String(format: "%.1f", (CACurrentMediaTime() - currentTime) * 1000))")
}
}
if hasCaptureHandler {
finishedExecutingCommand(error: error)
}
}
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
if let command = commandQueue.first(), !command.isCancelled, command.type == .writeCharacteristicAndWaitNofity {
let characteristic = command.parameters![3] as! CBCharacteristic
let readCompletion = command.parameters![4] as! CapturedReadCompletionHandler
let timeout = command.parameters![5] as? Double
let identifier = handlerIdentifier(from: characteristic)
//DLog("read timeout started")
let captureReadHandler = CaptureReadHandler(identifier: identifier, result: readCompletion, timeout: timeout, timeoutAction: timeOutRemoveCaptureHandler)
captureReadHandlers.append(captureReadHandler)
} else {
finishedExecutingCommand(error: error)
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverDescriptorsFor characteristic: CBCharacteristic, error: Error?) {
finishedExecutingCommand(error: error)
}
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor descriptor: CBDescriptor, error: Error?) {
let identifier = handlerIdentifier(from: descriptor)
if captureReadHandlers.count > 0, let index = captureReadHandlers.firstIndex(where: {$0.identifier == identifier}) {
// Remove capture handler
let captureReadHandler = captureReadHandlers.remove(at: index)
// Send result
let value = descriptor.value
captureReadHandler.result(value, error)
finishedExecutingCommand(error: error)
}
}
func peripheral(_ peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: Error?) {
guard error == nil else { DLog("didReadRSSI error: \(error!.localizedDescription)"); return }
let rssi = RSSI.intValue
if rssi != 127 { // only update rssi value if is defined ( 127 means undefined )
self.rssi = rssi
}
NotificationCenter.default.post(name: .peripheralDidUpdateRssi, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
}
}
// MARK: - Custom Notifications
extension Notification.Name {
private static let kPrefix = Bundle.main.bundleIdentifier!
static let peripheralDidUpdateName = Notification.Name(kPrefix+".peripheralDidUpdateName")
static let peripheralDidModifyServices = Notification.Name(kPrefix+".peripheralDidModifyServices")
static let peripheralDidUpdateRssi = Notification.Name(kPrefix+".peripheralDidUpdateRssi")
}

View file

@ -0,0 +1,27 @@
//
// BleUUIDNames.swift
// Bluefruit Connect
//
// Created by Antonio García on 15/02/16.
// Copyright © 2016 Adafruit. All rights reserved.
//
import Foundation
class BleUUIDNames {
// Manager
static let shared = BleUUIDNames()
// Data
private var gattUUIds: [String:String]?
init() {
// Read known UUIDs
let path = Bundle.main.path(forResource: "GattUUIDs", ofType: "plist")!
gattUUIds = NSDictionary(contentsOfFile: path) as? [String: String]
}
func nameForUUID(_ uuidString: String) -> String? {
return gattUUIds?[uuidString]
}
}

View file

@ -0,0 +1,143 @@
//
// UartDataManager.swift
// Calibration
//
// Created by Antonio García on 20/10/2016.
// Copyright © 2016 Adafruit. All rights reserved.
//
import Foundation
protocol UartDataManagerDelegate: class {
func onUartRx(data: Data, peripheralIdentifier: UUID) // data contents depends on the isRxCacheEnabled flag
}
// Basic Uart Management. Use it to cache all data received and help parsing it
class UartDataManager {
// Params
var isEnabled: Bool {
didSet {
if isEnabled != oldValue {
registerNotifications(enabled: isEnabled)
}
}
}
var isRxCacheEnabled: Bool { // If cache is enabled, onUartRx sends the cachedData. Cache can be cleared using removeRxCacheFirst or clearRxCache. If not enabled, onUartRx sends only the latest data received
didSet {
if !isRxCacheEnabled {
DLog("Clearing all rx caches")
rxDatas.removeAll()
}
}
}
weak var delegate: UartDataManagerDelegate?
// Data
private var rxDatas = [UUID: Data]()
private var rxDataSemaphore = DispatchSemaphore(value: 1)
init(delegate: UartDataManagerDelegate?, isRxCacheEnabled: Bool) {
self.delegate = delegate
self.isRxCacheEnabled = isRxCacheEnabled
isEnabled = true
}
deinit {
isEnabled = false
}
// MARK: - BLE Notifications
private weak var didConnectToPeripheralObserver: NSObjectProtocol?
private weak var didDisconnectFromPeripheralObserver: NSObjectProtocol?
private func registerNotifications(enabled: Bool) {
let notificationCenter = NotificationCenter.default
if enabled {
didConnectToPeripheralObserver = notificationCenter.addObserver(forName: .didConnectToPeripheral, object: nil, queue: .main, using: {[weak self] notification in self?.didConnectToPeripheral(notification: notification)})
didDisconnectFromPeripheralObserver = notificationCenter.addObserver(forName: .didDisconnectFromPeripheral, object: nil, queue: .main, using: {[weak self] notification in self?.didDisconnectFromPeripheral(notification: notification)})
} else {
if let didConnectToPeripheralObserver = didConnectToPeripheralObserver {notificationCenter.removeObserver(didConnectToPeripheralObserver)}
if let didDisconnectFromPeripheralObserver = didDisconnectFromPeripheralObserver {notificationCenter.removeObserver(didDisconnectFromPeripheralObserver)}
}
}
private func didConnectToPeripheral(notification: Notification) {
guard let identifier = notification.userInfo?[BleManager.NotificationUserInfoKey.uuid.rawValue] as? UUID else { return }
clearRxCache(peripheralIdentifier: identifier)
}
private func didDisconnectFromPeripheral(notification: Notification) {
guard let identifier = notification.userInfo?[BleManager.NotificationUserInfoKey.uuid.rawValue] as? UUID else { return }
// Clean data on disconnect
rxDatas[identifier] = nil
rxDataSemaphore.signal() // Force signal if was waiting
}
// MARK: - Send data
func send(blePeripheral: BlePeripheral, data: Data?, completion: ((Error?) -> Void)? = nil) {
blePeripheral.uartSend(data: data, completion: completion)
}
// MARK: - Received data
func rxDataReceived(data: Data?, peripheralIdentifier identifier: UUID, error: Error?) {
guard error == nil else { DLog("rxDataReceived error: \(error!)"); return }
guard let data = data else { return }
// Pre-create rxData entry if needed
if isRxCacheEnabled && rxDatas[identifier] == nil {
rxDatas[identifier] = Data()
}
if isRxCacheEnabled {
rxDataSemaphore.wait() // don't append more data, till the delegate has finished processing it
rxDatas[identifier]!.append(data)
// Send data to delegate
delegate?.onUartRx(data: rxDatas[identifier]!, peripheralIdentifier: identifier)
//DLog("cachedRxData: \(cachedRxData.count)")
rxDataSemaphore.signal()
} else {
delegate?.onUartRx(data: data, peripheralIdentifier: identifier)
}
}
func clearRxCache(peripheralIdentifier identifier: UUID) {
guard rxDatas[identifier] != nil else { return }
rxDatas[identifier]!.removeAll()
}
func removeRxCacheFirst(n: Int, peripheralIdentifier identifier: UUID) {
// Note: this is usually called from onUartRx delegates, so don't use rxDataSemaphore because it is already being used by the onUartRX caller
guard let rxData = rxDatas[identifier] else { return }
//DLog("remove \(n) items")
//DLog("pre remove: \(hexDescription(data: rxData))")
if n < rxData.count {
rxDatas[identifier]!.removeFirst(n)
} else {
clearRxCache(peripheralIdentifier: identifier)
}
//DLog("post remove: \(hexDescription(data: rxDatas[identifier]!))")
}
func flushRxCache(peripheralIdentifier identifier: UUID) {
guard let rxData = rxDatas[identifier] else { return }
if rxData.count > 0 {
rxDataSemaphore.wait()
delegate?.onUartRx(data: rxData, peripheralIdentifier: identifier)
rxDataSemaphore.signal()
}
}
}

View file

@ -0,0 +1,55 @@
//
// UartLogManager.swift
// Calibration
//
// Created by Antonio García on 24/10/2016.
// Copyright © 2016 Adafruit. All rights reserved.
//
import Foundation
class UartLogManager {
private static var kIsEnabled = false
enum LogType {
case info
case uartTx
case uartRx
var description: String {
switch self {
case .info: return ""
case .uartTx: return "sent"
case .uartRx: return "received"
}
}
}
struct LogItem {
var type = LogType.info
var data: Data
}
static var logItems = [LogItem]()
static func log(data: Data, type: LogType) {
if UartLogManager.kIsEnabled {
let item = LogItem(type: type, data: data)
UartLogManager.logItems.append(item)
}
}
static func log(message: String, type: LogType = .info) {
if UartLogManager.kIsEnabled {
if let data = message.data(using: .utf8) {
let item = LogItem(type: type, data: data)
UartLogManager.logItems.append(item)
}
}
}
static func clearLog() {
UartLogManager.logItems.removeAll()
}
}

View file

@ -0,0 +1,99 @@
//
// UartPacketManager.swift
// Bluefruit
//
// Created by Antonio on 01/02/2017.
// Copyright © 2017 Adafruit. All rights reserved.
//
import Foundation
class UartPacketManager: UartPacketManagerBase {
override init(delegate: UartPacketManagerDelegate?, isPacketCacheEnabled: Bool, isMqttEnabled: Bool) {
super.init(delegate: delegate, isPacketCacheEnabled: isPacketCacheEnabled, isMqttEnabled: isMqttEnabled)
registerNotifications(enabled: true)
}
deinit {
registerNotifications(enabled: false)
}
// MARK: - BLE Notifications
private weak var didConnectToPeripheralObserver: NSObjectProtocol?
private func registerNotifications(enabled: Bool) {
let notificationCenter = NotificationCenter.default
if enabled {
didConnectToPeripheralObserver = notificationCenter.addObserver(forName: .didConnectToPeripheral, object: nil, queue: .main) {[weak self] _ in self?.clearPacketsCache()}
} else {
if let didConnectToPeripheralObserver = didConnectToPeripheralObserver {notificationCenter.removeObserver(didConnectToPeripheralObserver)}
}
}
// MARK: - Send data
func send(blePeripheral: BlePeripheral, data: Data?, progress: ((Float)->Void)? = nil, completion: ((Error?) -> Void)? = nil) {
sentBytes += Int64(data?.count ?? 0)
blePeripheral.uartSend(data: data, progress: progress, completion: completion)
}
func sendEachPacketSequentially(blePeripheral: BlePeripheral, data: Data?, withResponseEveryPacketCount: Int, progress: ((Float)->Void)? = nil, completion: ((Error?) -> Void)? = nil) {
sentBytes += Int64(data?.count ?? 0)
blePeripheral.uartSendEachPacketSequentially(data: data, withResponseEveryPacketCount: withResponseEveryPacketCount, progress: progress, completion: completion)
}
func cancelOngoingSendPacketSequentiallyInMainThread(blePeripheral: BlePeripheral) {
blePeripheral.uartCancelOngoingSendPacketSequentiallyInMainThread()
}
func sendAndWaitReply(blePeripheral: BlePeripheral, data: Data?, writeProgress: ((Float)->Void)? = nil, writeCompletion: ((Error?) -> Void)? = nil, readTimeout: Double? = BlePeripheral.kUartReplyDefaultTimeout, readCompletion: @escaping BlePeripheral.CapturedReadCompletionHandler) {
sentBytes += Int64(data?.count ?? 0)
blePeripheral.uartSendAndWaitReply(data: data, writeProgress: writeProgress, writeCompletion: writeCompletion, readTimeout: readTimeout, readCompletion: readCompletion)
}
func send(blePeripheral: BlePeripheral, text: String, wasReceivedFromMqtt: Bool = false) {
#if MQTT_ENABLED
if isMqttEnabled {
// Mqtt publish to TX
let mqttSettings = MqttSettings.shared
if mqttSettings.isPublishEnabled {
if let topic = mqttSettings.getPublishTopic(index: MqttSettings.PublishFeed.tx.rawValue) {
let qos = mqttSettings.getPublishQos(index: MqttSettings.PublishFeed.tx.rawValue)
MqttManager.shared.publish(message: text, topic: topic, qos: qos)
}
}
}
#endif
// Create data and send to Uart
if let data = text.data(using: .utf8) {
let uartPacket = UartPacket(peripheralId: blePeripheral.identifier, mode: .tx, data: data)
// Add Packet
packetsSemaphore.wait()
packets.append(uartPacket)
packetsSemaphore.signal()
DispatchQueue.main.async {
self.delegate?.onUartPacket(uartPacket)
}
#if MQTT_ENABLED
let shouldBeSent = !wasReceivedFromMqtt || (isMqttEnabled && MqttSettings.shared.subscribeBehaviour == .transmit)
#else
let shouldBeSent = true
#endif
if shouldBeSent {
send(blePeripheral: blePeripheral, data: data)
}
}
}
// MARK: - Force reset
func reset(blePeripheral: BlePeripheral) {
blePeripheral.reset()
}
}

View file

@ -0,0 +1,108 @@
//
// BlePeripheral+CPBAccelerometer.swift
// BluefruitPlayground
//
// Created by Antonio García on 25/11/2019.
// Copyright © 2019 Adafruit. All rights reserved.
//
import Foundation
import CoreBluetooth
extension BlePeripheral {
// Constants
static let kCPBAccelerometerServiceUUID = CBUUID(string: "ADAF0200-C332-42A8-93BD-25E905756CB8")
private static let kCPBAccelerometerCharacteristicUUID = CBUUID(string: "ADAF0201-C332-42A8-93BD-25E905756CB8")
static let kCPBAcceleromterDefaultPeriod: TimeInterval = 0.1
struct AccelerometerValue {
var x: Float
var y: Float
var z: Float
}
// MARK: - Custom properties
private struct CustomPropertiesKeys {
static var cpbAccelerometerCharacteristic: CBCharacteristic?
}
private var cpbAccelerometerCharacteristic: CBCharacteristic? {
get {
return objc_getAssociatedObject(self, &CustomPropertiesKeys.cpbAccelerometerCharacteristic) as! CBCharacteristic?
}
set {
objc_setAssociatedObject(self, &CustomPropertiesKeys.cpbAccelerometerCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
// MARK: - Actions
func cpbAccelerometerEnable(responseHandler: @escaping(Result<(AccelerometerValue, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
self.cpbServiceEnable(serviceUuid: BlePeripheral.kCPBAccelerometerServiceUUID, mainCharacteristicUuid: BlePeripheral.kCPBAccelerometerCharacteristicUUID, timePeriod: BlePeripheral.kCPBAcceleromterDefaultPeriod, responseHandler: { response in
switch response {
case let .success((data, uuid)):
if let acceleration = self.cpbAccelerometerDataToFloatVector(data) {
responseHandler(.success((acceleration, uuid)))
}
else {
responseHandler(.failure(PeripheralCPBError.invalidResponseData))
}
case let .failure(error):
responseHandler(.failure(error))
}
}) { result in
switch result {
case let .success((version, characteristic)):
guard version == 1 else {
DLog("Warning: cpbAccelerometerEnable unknown version: \(version)")
completion?(.failure(PeripheralCPBError.unknownVersion))
return
}
self.cpbAccelerometerCharacteristic = characteristic
completion?(.success(()))
case let .failure(error):
self.cpbAccelerometerCharacteristic = nil
completion?(.failure(error))
}
}
}
func isCpbAccelerometerEnabled() -> Bool {
return cpbAccelerometerCharacteristic != nil && cpbAccelerometerCharacteristic!.isNotifying
}
func cpbAccelerometerDisable() {
// Clear all specific data
defer {
cpbAccelerometerCharacteristic = nil
}
// Disable notify
guard let characteristic = cpbAccelerometerCharacteristic, characteristic.isNotifying else { return }
disableNotify(for: characteristic)
}
func cpbAccelerometerLastValue() -> AccelerometerValue? {
guard let data = cpbAccelerometerCharacteristic?.value else { return nil }
return cpbAccelerometerDataToFloatVector(data)
}
// MARK: - Utils
private func cpbAccelerometerDataToFloatVector(_ data: Data) -> AccelerometerValue? {
let unitSize = MemoryLayout<Float32>.stride
var bytes = [Float32](repeating: 0, count: data.count / unitSize)
(data as NSData).getBytes(&bytes, length: data.count * unitSize)
guard bytes.count >= 3 else { return nil }
return AccelerometerValue(x: bytes[0], y: bytes[1], z: bytes[2])
}
}

View file

@ -0,0 +1,133 @@
//
// BlePeripheral+CPBButtons.swift
// BluefruitPlayground
//
// Created by Antonio García on 15/11/2019.
// Copyright © 2019 Adafruit. All rights reserved.
//
import Foundation
import CoreBluetooth
extension BlePeripheral {
// Constants
static let kCPBButtonsServiceUUID = CBUUID(string: "ADAF0600-C332-42A8-93BD-25E905756CB8")
private static let kCPBButtonsCharacteristicUUID = CBUUID(string: "ADAF0601-C332-42A8-93BD-25E905756CB8")
enum SlideSwitchState: Int32 {
case right = 0
case left = 1
}
enum ButtonState: Int32 {
case released = 0
case pressed = 1
}
struct ButtonsState {
var slideSwitch: SlideSwitchState
var buttonA: ButtonState
var buttonB: ButtonState
}
// MARK: - Custom properties
private struct CustomPropertiesKeys {
static var cpbButtonsCharacteristic: CBCharacteristic?
}
private var cpbButtonsCharacteristic: CBCharacteristic? {
get {
return objc_getAssociatedObject(self, &CustomPropertiesKeys.cpbButtonsCharacteristic) as! CBCharacteristic?
}
set {
objc_setAssociatedObject(self, &CustomPropertiesKeys.cpbButtonsCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
// MARK: - Actions
func cpbButtonsEnable(responseHandler: @escaping(Result<(ButtonsState, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
self.cpbServiceEnable(serviceUuid: BlePeripheral.kCPBButtonsServiceUUID, mainCharacteristicUuid: BlePeripheral.kCPBButtonsCharacteristicUUID, timePeriod: 0, responseHandler: { response in
switch response {
case let .success((data, uuid)):
let state = self.cpbButtonsDataToStateMask(data)
responseHandler(.success((state, uuid)))
case let .failure(error):
responseHandler(.failure(error))
}
}) { result in
switch result {
case let .success((version, characteristic)):
guard version == 1 else {
DLog("Warning: cpbButtonsEnable unknown version: \(version)")
completion?(.failure(PeripheralCPBError.unknownVersion))
return
}
self.cpbButtonsCharacteristic = characteristic
completion?(.success(()))
case let .failure(error):
self.cpbButtonsCharacteristic = nil
completion?(.failure(error))
}
}
}
func isCpbButtonsEnabled() -> Bool {
return cpbButtonsCharacteristic != nil && cpbButtonsCharacteristic!.isNotifying
}
func cpbButtonsDisable() {
// Clear all specific data
defer {
cpbButtonsCharacteristic = nil
}
// Disable notify
guard let characteristic = cpbButtonsCharacteristic, characteristic.isNotifying else { return }
disableNotify(for: characteristic)
}
func cpbButtonsReadState(completion: @escaping(Result<(ButtonsState, UUID), Error>) -> Void) {
guard let cpbButtonsCharacteristic = cpbButtonsCharacteristic else {
completion(.failure(PeripheralCPBError.invalidCharacteristic))
return
}
self.readCharacteristic(cpbButtonsCharacteristic) { [weak self] (data, error) in
guard let self = self else { return }
guard error == nil, let data = data as? Data else {
completion(.failure(error ?? PeripheralCPBError.invalidResponseData))
return
}
let state = self.cpbButtonsDataToStateMask(data)
completion(.success((state, self.identifier)))
}
}
func cpbButtonsLastValue() -> ButtonsState? {
guard let data = cpbButtonsCharacteristic?.value else { return nil }
return cpbButtonsDataToStateMask(data)
}
// MARK: - Utils
private func cpbButtonsDataToStateMask(_ data: Data) -> ButtonsState {
let stateMask = data.toInt32From32Bits()
let slideSwitchBit = stateMask & 0b1
let slideSwitchState = SlideSwitchState(rawValue: slideSwitchBit)!
let buttonABit = ( stateMask >> 1 ) & 0b1
let buttonAState = ButtonState(rawValue: buttonABit)!
let buttonBBit = ( stateMask >> 2 ) & 0b1
let buttonBState = ButtonState(rawValue: buttonBBit)!
return ButtonsState(slideSwitch: slideSwitchState , buttonA: buttonAState, buttonB: buttonBState)
}
}

View file

@ -0,0 +1,190 @@
//
// BlePeripehral+CPBCommon.swift
// BluefruitPlayground
//
// Created by Antonio García on 13/11/2019.
// Copyright © 2019 Adafruit. All rights reserved.
//
import Foundation
import CoreBluetooth
extension BlePeripheral {
// Costants
private static let kCPBMeasurementPeriodCharacteristicUUID = CBUUID(string: "ADAF0001-C332-42A8-93BD-25E905756CB8")
private static let kCPBMeasurementVersionCharacteristicUUID = CBUUID(string: "ADAF0002-C332-42A8-93BD-25E905756CB8")
private static let kCPBDefaultVersionValue = 1 // Used as default version value if version characteristic cannot be read
// MARK: - Errors
enum PeripheralCPBError: Error {
case invalidCharacteristic
case enableNotifyFailed
case unknownVersion
case invalidResponseData
}
// MARK: - Custom properties
/*
private struct CustomPropertiesKeys {
static var cpbMeasurementPeriodCharacteristic: CBCharacteristic?
//static var cpbVersionCharacteristic: CBCharacteristic?
}
private var cpbMeasurementPeriodCharacteristic: CBCharacteristic? {
get {
return objc_getAssociatedObject(self, &CustomPropertiesKeys.cpbMeasurementPeriodCharacteristic) as! CBCharacteristic?
}
set {
objc_setAssociatedObject(self, &CustomPropertiesKeys.cpbMeasurementPeriodCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
private var cpbVersionCharacteristic: CBCharacteristic? {
get {
return objc_getAssociatedObject(self, &CustomPropertiesKeys.cpbVersionCharacteristic) as! CBCharacteristic?
}
set {
objc_setAssociatedObject(self, &CustomPropertiesKeys.cpbVersionCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}*/
// MARK: - Service Actions
func cpbServiceEnable(serviceUuid: CBUUID, mainCharacteristicUuid: CBUUID, completion: ((Result<(Int, CBCharacteristic), Error>) -> Void)?) {
self.characteristic(uuid: mainCharacteristicUuid, serviceUuid: serviceUuid) { [unowned self] (characteristic, error) in
guard let characteristic = characteristic, error == nil else {
completion?(.failure(error ?? PeripheralCPBError.invalidCharacteristic))
return
}
// Check version
self.cpbVersion(serviceUuid: serviceUuid) { version in
completion?(.success((version, characteristic)))
}
}
}
func cpbServiceEnable(serviceUuid: CBUUID, mainCharacteristicUuid: CBUUID, timePeriod: TimeInterval?, responseHandler: @escaping(Result<(Data, UUID), Error>) -> Void, completion: ((Result<(Int, CBCharacteristic), Error>) -> Void)?) {
self.characteristic(uuid: mainCharacteristicUuid, serviceUuid: serviceUuid) { [unowned self] (characteristic, error) in
guard let characteristic = characteristic, error == nil else {
completion?(.failure(error ?? PeripheralCPBError.invalidCharacteristic))
return
}
// Check version
self.cpbVersion(serviceUuid: serviceUuid) { version in
// Prepare notification handler
let notifyHandler: ((Error?) -> Void)? = { [unowned self] error in
guard error == nil else {
responseHandler(.failure(error!))
return
}
if let data = characteristic.value {
responseHandler(.success((data, self.identifier)))
}
}
// Refresh period handler
let enableNotificationsHandler = {
// Enable notifications
if !characteristic.isNotifying {
self.enableNotify(for: characteristic, handler: notifyHandler, completion: { error in
guard error == nil else {
completion?(.failure(error!))
return
}
guard characteristic.isNotifying else {
completion?(.failure(PeripheralCPBError.enableNotifyFailed))
return
}
completion?(.success((version, characteristic)))
})
} else {
self.updateNotifyHandler(for: characteristic, handler: notifyHandler)
completion?(.success((version, characteristic)))
}
}
// Set timePeriod if not nil
if let timePeriod = timePeriod {
self.cpbSetPeriod(timePeriod, serviceUuid: serviceUuid) { result in
if Config.isDebugEnabled {
// Check period
self.cpbPeriod(serviceUuid: serviceUuid) { period in
DLog("service period: \(String(describing: period))")
}
}
enableNotificationsHandler()
}
}
else {
enableNotificationsHandler()
}
}
}
}
func cpbVersion(serviceUuid: CBUUID, completion: @escaping(Int) -> Void) {
self.characteristic(uuid: BlePeripheral.kCPBMeasurementVersionCharacteristicUUID, serviceUuid: serviceUuid) { (characteristic, error) in
guard error == nil, let characteristic = characteristic, let data = characteristic.value else {
completion(BlePeripheral.kCPBDefaultVersionValue)
return
}
let version = data.toIntFrom32Bits()
completion(version)
}
}
func cpbPeriod(serviceUuid: CBUUID, completion: @escaping(TimeInterval?) -> Void) {
self.characteristic(uuid: BlePeripheral.kCPBMeasurementPeriodCharacteristicUUID, serviceUuid: serviceUuid) { (characteristic, error) in
guard error == nil, let characteristic = characteristic else {
completion(nil)
return
}
self.readCharacteristic(characteristic) { (data, error) in
guard error == nil, let data = data as? Data else {
completion(nil)
return
}
let period = TimeInterval(data.toIntFrom32Bits()) / 1000.0
completion(period)
}
}
}
func cpbSetPeriod(_ period: TimeInterval, serviceUuid: CBUUID, completion: ((Result<Void, Error>) -> Void)?) {
self.characteristic(uuid: BlePeripheral.kCPBMeasurementPeriodCharacteristicUUID, serviceUuid: serviceUuid) { (characteristic, error) in
guard error == nil, let characteristic = characteristic else {
DLog("Error: cpbSetPeriod: \(String(describing: error))")
return
}
let periodMillis = Int32(period * 1000)
let data = periodMillis.littleEndian.data
self.write(data: data, for: characteristic, type: .withResponse) { error in
guard error == nil else {
DLog("Error: cpbSetPeriod \(error!)")
completion?(.failure(error!))
return
}
completion?(.success(()))
}
}
}
}

View file

@ -0,0 +1,89 @@
//
// BlePeripheral+CPBLight.swift
// BluefruitPlayground
//
// Created by Antonio García on 13/11/2019.
// Copyright © 2019 Adafruit. All rights reserved.
//
import Foundation
import CoreBluetooth
extension BlePeripheral {
// Constants
static let kCPBLightServiceUUID = CBUUID(string: "ADAF0300-C332-42A8-93BD-25E905756CB8")
private static let kCPBLightCharacteristicUUID = CBUUID(string: "ADAF0301-C332-42A8-93BD-25E905756CB8")
static let kCPBLightDefaultPeriod: TimeInterval = 0.1
// MARK: - Custom properties
private struct CustomPropertiesKeys {
static var cpbLightCharacteristic: CBCharacteristic?
}
private var cpbLightCharacteristic: CBCharacteristic? {
get {
return objc_getAssociatedObject(self, &CustomPropertiesKeys.cpbLightCharacteristic) as! CBCharacteristic?
}
set {
objc_setAssociatedObject(self, &CustomPropertiesKeys.cpbLightCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
// MARK: - Actions
func cpbLightEnable(responseHandler: @escaping(Result<(Float, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
self.cpbServiceEnable(serviceUuid: BlePeripheral.kCPBLightServiceUUID, mainCharacteristicUuid: BlePeripheral.kCPBLightCharacteristicUUID, timePeriod: BlePeripheral.kCPBLightDefaultPeriod, responseHandler: { response in
switch response {
case let .success((data, uuid)):
let light = self.cpbLightDataToFloat(data)
responseHandler(.success((light, uuid)))
case let .failure(error):
responseHandler(.failure(error))
}
}) { result in
switch result {
case let .success((version, characteristic)):
guard version == 1 else {
DLog("Warning: cpbLightEnable unknown version: \(version)")
completion?(.failure(PeripheralCPBError.unknownVersion))
return
}
self.cpbLightCharacteristic = characteristic
completion?(.success(()))
case let .failure(error):
self.cpbLightCharacteristic = nil
completion?(.failure(error))
}
}
}
func isCpbLightEnabled() -> Bool {
return cpbLightCharacteristic != nil && cpbLightCharacteristic!.isNotifying
}
func cpbLightDisable() {
// Clear all specific data
defer {
cpbLightCharacteristic = nil
}
// Disable notify
guard let characteristic = cpbLightCharacteristic, characteristic.isNotifying else { return }
disableNotify(for: characteristic)
}
func cpbLightLastValue() -> Float? {
guard let data = cpbLightCharacteristic?.value else { return nil }
return cpbLightDataToFloat(data)
}
// MARK: - Utils
private func cpbLightDataToFloat(_ data: Data) -> Float {
return data.toFloatFrom32Bits()
}
}

View file

@ -0,0 +1,200 @@
//
// BlePeripheral+CPBPixels.swift
// BluefruitPlayground
//
// Created by Antonio García on 25/11/2019.
// Copyright © 2019 Adafruit. All rights reserved.
//
import UIKit
import CoreBluetooth
extension BlePeripheral {
// Config
private static let kPixelsServiceNumberOfBitsPerPixel = 3
private static let kPixelsServiceNumPixels = 10
// Constants
static let kCPBPixelsServiceUUID = CBUUID(string: "ADAF0900-C332-42A8-93BD-25E905756CB8")
private static let kCPBPixelsDataCharacteristicUUID = CBUUID(string: "ADAF0903-C332-42A8-93BD-25E905756CB8")
// MARK: - Custom properties
private struct CustomPropertiesKeys {
static var cpbPixelsDataCharacteristic: CBCharacteristic?
static var cpbPixelsDataValue: Data?
}
private var cpbPixelsDataCharacteristic: CBCharacteristic? {
get {
return objc_getAssociatedObject(self, &CustomPropertiesKeys.cpbPixelsDataCharacteristic) as! CBCharacteristic?
}
set {
objc_setAssociatedObject(self, &CustomPropertiesKeys.cpbPixelsDataCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
private var cpbPixelsDataValue: Data {
get {
if let data = objc_getAssociatedObject(self, &CustomPropertiesKeys.cpbPixelsDataValue) as! Data? {
return data
}
else { // Initial value
return Data(repeating: 0, count: BlePeripheral.kPixelsServiceNumPixels * BlePeripheral.kPixelsServiceNumberOfBitsPerPixel)
}
}
set {
objc_setAssociatedObject(self, &CustomPropertiesKeys.cpbPixelsDataValue, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
// MARK: - Actions
func cpbPixelsEnable(completion: ((Result<Void, Error>) -> Void)?) {
self.cpbServiceEnable(serviceUuid: BlePeripheral.kCPBPixelsServiceUUID, mainCharacteristicUuid: BlePeripheral.kCPBPixelsDataCharacteristicUUID) { result in
switch result {
case let .success((version, characteristic)):
guard version == 1 else {
DLog("Warning: cpbPixelsEnable unknown version: \(version)")
completion?(.failure(PeripheralCPBError.unknownVersion))
return
}
self.cpbPixelsDataCharacteristic = characteristic
completion?(.success(()))
case let .failure(error):
self.cpbPixelsDataCharacteristic = nil
completion?(.failure(error))
}
}
}
func isCpbPixelsEnabled() -> Bool {
return cpbPixelsDataCharacteristic != nil
}
func cpbPixelsDisable() {
// Clear all specific data
cpbPixelsDataCharacteristic = nil
}
func cpbNumPixels() -> Int {
return BlePeripheral.kPixelsServiceNumPixels
}
func cpbPixelSetAllPixelsColor(_ color: UIColor) {
let colors = [UIColor](repeating: color, count: BlePeripheral.kPixelsServiceNumPixels)
cpbPixelsWriteData(offset: 0, colors: colors)
}
func cpbPixelSetPixelColor(index: Int, color: UIColor) {
let offset = UInt16(index * BlePeripheral.kPixelsServiceNumberOfBitsPerPixel)
cpbPixelsWriteData(offset: offset, colors: [color])
}
func cpbPixelSetColor(index: UInt, color: UIColor, pixelMask: [Bool]) {
guard let pixelData = pixelDataFromColorMask(color: color, pixelMask: pixelMask) else {
DLog("Error neopixelSetColor invalid color data")
return
}
let offset = UInt16(index * UInt(BlePeripheral.kPixelsServiceNumberOfBitsPerPixel))
cpbPixelsWriteData(offset: offset, pixelData: pixelData)
}
// MARK: - Low level actions
func cpbPixelsWriteData(offset: UInt16, colors: [UIColor]) {
let pixelData = BlePeripheral.pixelDataFromColors(colors)
cpbPixelsWriteData(offset: offset, pixelData: pixelData)
}
func cpbPixelsWriteData(offset: UInt16, pixelData: Data) {
guard let cpbPixelsDataCharacteristic = cpbPixelsDataCharacteristic else { return }
enum Flags: UInt8 {
case save = 0
case flush = 1
}
let flags = Flags.flush
let data = offset.littleEndian.data + flags.rawValue.littleEndian.data + pixelData
// self.write(data: data, for: cpbPixelsDataCharacteristic, type: .withResponse)
self.write(data: data, for: cpbPixelsDataCharacteristic, type: .withResponse) { [unowned self] error in
guard error == nil else { DLog("Error cpbPixelsWriteData: \(error!)"); return }
self.cpbPixelsDataValue = pixelData
}
}
// MARK: - Utils
private func pixelDataFromColorMask(color: UIColor, pixelMask: [Bool]) -> Data? {
let colorData = BlePeripheral.pixelDataFromColor(color)
var pixelData = Data()
for (i, mask) in pixelMask.enumerated() {
if mask { // overwrite color
pixelData += colorData
}
else { // use current color
let existingColorData: Data
let byteOffset = i * BlePeripheral.kPixelsServiceNumberOfBitsPerPixel
DLog("cpbPixelsDataValue.count: \(cpbPixelsDataValue.count) ")
if byteOffset < cpbPixelsDataValue.count {
existingColorData = Data(cpbPixelsDataValue[byteOffset..<(byteOffset + BlePeripheral.kPixelsServiceNumberOfBitsPerPixel)])
}
else {
existingColorData = Data(repeating: 0, count: BlePeripheral.kPixelsServiceNumberOfBitsPerPixel)
}
pixelData += existingColorData
}
}
return pixelData
}
private static func pixelDataFromColors(_ colors: [UIColor]) -> Data {
var pixelData = Data()
for color in colors {
pixelData += pixelDataFromColor(color)
}
return pixelData
}
static func pixelDataFromColor(_ color: UIColor) -> Data {
let bytes = pixelUInt8FromColor(color)
return bytes.data
}
static func pixelUInt8FromColor(_ color: UIColor) -> [UInt8] {
var pixelBytes: [UInt8]? = nil
let cgColor = color.cgColor
let numComponents = cgColor.numberOfComponents
if let components = cgColor.components {
if numComponents == 2 {
let white = UInt8(components[0] * 255)
//let alpha = UInt8(components[1] * 255)
pixelBytes = [white, white, white]
}
else if numComponents == 4 {
let r = UInt8(components[0] * 255)
let g = UInt8(components[1] * 255)
let b = UInt8(components[2] * 255)
//let alpha = UInt8(components[3] * 255)
pixelBytes = [g, r, b]
}
else {
DLog("Error converting color (number of components is: \(numComponents))")
}
}
return pixelBytes ?? [UInt8](repeating: 0, count: BlePeripheral.kPixelsServiceNumberOfBitsPerPixel)
}
}

View file

@ -0,0 +1,87 @@
//
// BlePeripheral+CPBTemperature.swift
// BluefruitPlayground
//
// Created by Antonio García on 13/11/2019.
// Copyright © 2019 Adafruit. All rights reserved.
//
import Foundation
import CoreBluetooth
extension BlePeripheral {
// Constants
static let kCPBTemperatureServiceUUID = CBUUID(string: "ADAF0100-C332-42A8-93BD-25E905756CB8")
private static let kCPBTemperatureCharacteristicUUID = CBUUID(string: "ADAF0101-C332-42A8-93BD-25E905756CB8")
// MARK: - Custom properties
private struct CustomPropertiesKeys {
static var cpbTemperatureCharacteristic: CBCharacteristic?
}
private var cpbTemperatureCharacteristic: CBCharacteristic? {
get {
return objc_getAssociatedObject(self, &CustomPropertiesKeys.cpbTemperatureCharacteristic) as! CBCharacteristic?
}
set {
objc_setAssociatedObject(self, &CustomPropertiesKeys.cpbTemperatureCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
// MARK: - Actions
func cpbTemperatureEnable(responseHandler: @escaping(Result<(Float, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
self.cpbServiceEnable(serviceUuid: BlePeripheral.kCPBTemperatureServiceUUID, mainCharacteristicUuid: BlePeripheral.kCPBTemperatureCharacteristicUUID, timePeriod: 0.5, responseHandler: { response in
switch response {
case let .success((data, uuid)):
let temperature = self.cpbTemperatureDataToFloat(data)
responseHandler(.success((temperature, uuid)))
case let .failure(error):
responseHandler(.failure(error))
}
}) { result in
switch result {
case let .success((version, characteristic)):
guard version == 1 else {
DLog("Warning: cpbTemperatureEnable unknown version: \(version)")
completion?(.failure(PeripheralCPBError.unknownVersion))
return
}
self.cpbTemperatureCharacteristic = characteristic
completion?(.success(()))
case let .failure(error):
self.cpbTemperatureCharacteristic = nil
completion?(.failure(error))
}
}
}
func isCpbTemperatureEnabled() -> Bool {
return cpbTemperatureCharacteristic != nil && cpbTemperatureCharacteristic!.isNotifying
}
func cpbTemperatureDisable() {
// Clear all specific data
defer {
cpbTemperatureCharacteristic = nil
}
// Disable notify
guard let characteristic = cpbTemperatureCharacteristic, characteristic.isNotifying else { return }
disableNotify(for: characteristic)
}
func cpbTemperatureLastValue() -> Float? {
guard let data = cpbTemperatureCharacteristic?.value else { return nil }
return cpbTemperatureDataToFloat(data)
}
// MARK: - Utils
private func cpbTemperatureDataToFloat(_ data: Data) -> Float {
return data.toFloatFrom32Bits()
}
}

View file

@ -0,0 +1,69 @@
//
// BlePeripheral+CPBToneGenerator.swift
// BluefruitPlayground
//
// Created by Antonio García on 18/11/2019.
// Copyright © 2019 Adafruit. All rights reserved.
//
import Foundation
import CoreBluetooth
extension BlePeripheral {
// Constants
static let kCPBToneGeneratorServiceUUID = CBUUID(string: "ADAF0C00-C332-42A8-93BD-25E905756CB8")
private static let kCPBToneGeneratorCharacteristicUUID = CBUUID(string: "ADAF0C01-C332-42A8-93BD-25E905756CB8")
// MARK: - Custom properties
private struct CustomPropertiesKeys {
static var cpbToneGeneratorCharacteristic: CBCharacteristic?
}
private var cpbToneGeneratorCharacteristic: CBCharacteristic? {
get {
return objc_getAssociatedObject(self, &CustomPropertiesKeys.cpbToneGeneratorCharacteristic) as! CBCharacteristic?
}
set {
objc_setAssociatedObject(self, &CustomPropertiesKeys.cpbToneGeneratorCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
// MARK: - Actions
func cpbToneGeneratorEnable(completion: ((Result<Void, Error>) -> Void)?) {
self.cpbServiceEnable(serviceUuid: BlePeripheral.kCPBToneGeneratorServiceUUID, mainCharacteristicUuid: BlePeripheral.kCPBToneGeneratorCharacteristicUUID) { result in
switch result {
case let .success((version, characteristic)):
guard version == 1 else {
DLog("Warning: cpbToneGeneratorEnable unknown version: \(version)")
completion?(.failure(PeripheralCPBError.unknownVersion))
return
}
self.cpbToneGeneratorCharacteristic = characteristic
completion?(.success(()))
case let .failure(error):
self.cpbToneGeneratorCharacteristic = nil
completion?(.failure(error))
}
}
}
func isCpbToneGeneratorEnabled() -> Bool {
return cpbToneGeneratorCharacteristic != nil
}
func cpbToneGeneratorDisable() {
// Clear all specific data
cpbToneGeneratorCharacteristic = nil
}
func cpbToneGeneratorStartPlaying(frequency: UInt16, duration: UInt32 = 0) { // Duration 0 means non-stop
guard let cpbToneGeneratorCharacteristic = cpbToneGeneratorCharacteristic else { return }
let data = frequency.littleEndian.data + duration.littleEndian.data
self.write(data: data, for: cpbToneGeneratorCharacteristic, type: .withResponse)
//DLog("tone: \(frequency)")
}
}

View file

@ -0,0 +1,23 @@
//
// BlePeripheral+ManufacturerAdafruit.swift
// BluefruitPlayground
//
// Created by Antonio García on 10/12/2019.
// Copyright © 2019 Adafruit. All rights reserved.
//
import Foundation
import CoreBluetooth
extension BlePeripheral {
// Constants
private static let kManufacturerAdafruitIdentifier: [UInt8] = [0x22, 0x08]
func isManufacturerAdafruit() -> Bool {
guard let manufacturerIdentifier = advertisement.manufacturerIdentifier else { return false }
let manufacturerIdentifierBytes = [UInt8](manufacturerIdentifier)
//DLog("\(name) manufacturer: \(advertisement.manufacturerString)")
return manufacturerIdentifierBytes == BlePeripheral.kManufacturerAdafruitIdentifier
}
}

View file

@ -0,0 +1,43 @@
//
// BleManagerSimulated.swift
// BluefruitPlayground
//
// Created by Antonio García on 14/12/2019.
// Copyright © 2019 Adafruit. All rights reserved.
//
import Foundation
import CoreBluetooth
class BleManagerSimulated: BleManager {
// Singleton
static let simulated = BleManagerSimulated()
// MARK: - Lifecycle
override init() {
}
override func startScan(withServices services: [CBUUID]? = nil) {
isScanning = true
// Add simulated peripheral
let simulatedBlePeripheral = BlePeripheralSimulated()
peripheralsFound[simulatedBlePeripheral.identifier] = simulatedBlePeripheral
NotificationCenter.default.post(name: .didDiscoverPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: simulatedBlePeripheral.identifier])
}
override func stopScan() {
}
override func connect(to peripheral: BlePeripheral, timeout: TimeInterval? = nil, shouldNotifyOnConnection: Bool = false, shouldNotifyOnDisconnection: Bool = false, shouldNotifyOnNotification: Bool = false) {
// Send notification
NotificationCenter.default.post(name: .didConnectToPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
}
override func reconnecToPeripherals(withIdentifiers identifiers: [UUID], withServices services: [CBUUID], timeout: Double? = nil) -> Bool {
return false
}
}

View file

@ -0,0 +1,56 @@
//
// BlePeripheral+CPBButtons.swift
// BluefruitPlayground
//
// Created by Antonio García on 15/11/2019.
// Copyright © 2019 Adafruit. All rights reserved.
//
import Foundation
import CoreBluetooth
extension BlePeripheral {
// Constants
enum SlideSwitchState: Int32 {
case right = 0
case left = 1
}
enum ButtonState: Int32 {
case released = 0
case pressed = 1
}
struct ButtonsState {
var slideSwitch: SlideSwitchState
var buttonA: ButtonState
var buttonB: ButtonState
}
// MARK: - Actions
func cpbButtonsEnable(responseHandler: @escaping(Result<(ButtonsState, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
completion?(.success(()))
}
func isCpbButtonsEnabled() -> Bool {
return true
}
func cpbButtonsDisable() {
}
func cpbButtonsReadState(completion: @escaping(Result<(ButtonsState, UUID), Error>) -> Void) {
guard let state = cpbButtonsLastValue() else {
completion(.failure(PeripheralCPBError.invalidResponseData))
return
}
completion(.success((state, self.identifier)))
}
func cpbButtonsLastValue() -> ButtonsState? {
return ButtonsState(slideSwitch: .left, buttonA: .pressed, buttonB: .released)
}
}

View file

@ -0,0 +1,29 @@
//
// BlePeripheral+CPBLight.swift
// BluefruitPlayground
//
// Created by Antonio García on 13/11/2019.
// Copyright © 2019 Adafruit. All rights reserved.
//
import Foundation
import CoreBluetooth
extension BlePeripheral {
// MARK: - Actions
func cpbLightEnable(responseHandler: @escaping(Result<(Float, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
completion?(.success(()))
}
func isCpbLightEnabled() -> Bool {
return true
}
func cpbLightDisable() {
}
func cpbLightLastValue() -> Float? {
return 523
}
}

View file

@ -0,0 +1,53 @@
//
// BlePeripheral+CPBTemperature.swift
// BluefruitPlayground
//
// Created by Antonio García on 13/11/2019.
// Copyright © 2019 Adafruit. All rights reserved.
//
import Foundation
import CoreBluetooth
extension BlePeripheral {
// MARK: - Custom properties
private struct CustomPropertiesKeys {
static var cpbTemperatureResponseDataTimer: Timer?
}
private var cpbTemperatureResponseDataTimer: Timer? {
get {
return objc_getAssociatedObject(self, &CustomPropertiesKeys.cpbTemperatureResponseDataTimer) as! Timer?
}
set {
objc_setAssociatedObject(self, &CustomPropertiesKeys.cpbTemperatureResponseDataTimer, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
// MARK: - Actions
func cpbTemperatureEnable(responseHandler: @escaping(Result<(Float, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
cpbTemperatureResponseDataTimer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { [weak self] timer in
guard let self = self else { return }
guard let temperature = self.cpbTemperatureLastValue() else { return }
responseHandler(.success((temperature, self.identifier)))
}
completion?(.success(()))
}
func isCpbTemperatureEnabled() -> Bool {
return true
}
func cpbTemperatureDisable() {
cpbTemperatureResponseDataTimer?.invalidate()
cpbTemperatureResponseDataTimer = nil
}
func cpbTemperatureLastValue() -> Float? {
let temperature = Float.random(in: 18.5 ..< 19.5)
return temperature
}
}

View file

@ -0,0 +1,50 @@
//
// BlePeripheralSimulated.swift
// BluefruitPlayground
//
// Created by Antonio García on 14/12/2019.
// Copyright © 2019 Adafruit. All rights reserved.
//
import Foundation
import CoreBluetooth
class BlePeripheralSimulated: BlePeripheral {
// Constants
private static let kSimulatedUUID = UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!
// Data
override var identifier: UUID {
return BlePeripheralSimulated.kSimulatedUUID
}
override var name: String? {
return "Simulated Peripheral"
}
override var state: CBPeripheralState {
return .connected
}
// MARK: - Lifecycle
init() {
// Mocking CBPeripheral: https://forums.developer.apple.com/thread/29851
guard let peripheral = ObjectBuilder.createInstance(ofClass: "CBPeripheral") as? CBPeripheral else {
assertionFailure("Unable to mock CBPeripheral")
let nilPeripheral: CBPeripheral! = nil // Just to avoid a compiling error. This will never be executed
super.init(peripheral: nilPeripheral, advertisementData: nil, rssi: nil)
return
}
peripheral.addObserver(peripheral, forKeyPath: "delegate", options: .new, context: nil)
let manufacturerDataBytes: [UInt8] = [0x22, 0x08, 0x04, 0x01, 0x00, 0x45, 0x80] // Adafruit CPB
let advertisementData = [CBAdvertisementDataManufacturerDataKey: Data(manufacturerDataBytes)]
super.init(peripheral: peripheral, advertisementData: advertisementData, rssi: 20)
}
// MARK: - Discover
override func discover(serviceUuids: [CBUUID]?, completion: ((Error?) -> Void)?) {
completion?(nil)
}
}

View file

@ -0,0 +1,20 @@
//
// ObjectBuilder.h
// BluefruitPlayground
//
// Created by Antonio García on 16/12/2019.
// Copyright © 2019 Adafruit. All rights reserved.
//
// from: https://forums.developer.apple.com/thread/29851
#ifndef ObjectBuilder_h
#define ObjectBuilder_h
#import <Foundation/Foundation.h>
@interface ObjectBuilder: NSObject
+ (id)createInstanceOfClass:(NSString *)name;
@end
#endif /* ObjectBuilder_h */

View file

@ -0,0 +1,17 @@
//
// ObjectBuilder.m
// BluefruitPlayground
//
// Created by Antonio García on 16/12/2019.
// Copyright © 2019 Adafruit. All rights reserved.
//
#import "ObjectBuilder.h"
@implementation ObjectBuilder
+ (id)createInstanceOfClass:(NSString *)name {
return [[NSClassFromString(name) alloc] init];
}
@end

View file

@ -0,0 +1,105 @@
//
// UartPacketManagerBase.swift
// Bluefruit
//
// Created by Antonio García on 05/08/2017.
// Copyright © 2017 Adafruit. All rights reserved.
//
import Foundation
protocol UartPacketManagerDelegate: class {
func onUartPacket(_ packet: UartPacket)
}
struct UartPacket { // A packet of data received or sent
var timestamp: CFAbsoluteTime
enum TransferMode {
case tx
case rx
}
var mode: TransferMode
var data: Data
var peripheralId: UUID?
init(peripheralId: UUID?, timestamp: CFAbsoluteTime? = nil, mode: TransferMode, data: Data) {
self.peripheralId = peripheralId
self.timestamp = timestamp ?? CFAbsoluteTimeGetCurrent()
self.mode = mode
self.data = data
}
}
class UartPacketManagerBase {
// Data
internal weak var delegate: UartPacketManagerDelegate?
internal var packets = [UartPacket]()
internal var packetsSemaphore = DispatchSemaphore(value: 1)
internal var isMqttEnabled: Bool
internal var isPacketCacheEnabled: Bool
var receivedBytes: Int64 = 0
var sentBytes: Int64 = 0
init(delegate: UartPacketManagerDelegate?, isPacketCacheEnabled: Bool, isMqttEnabled: Bool) {
self.isPacketCacheEnabled = isPacketCacheEnabled
self.isMqttEnabled = isMqttEnabled
self.delegate = delegate
}
// MARK: - Received data
func rxPacketReceived(data: Data?, peripheralIdentifier: UUID?, error: Error?) {
guard error == nil else { DLog("uartRxPacketReceived error: \(error!)"); return }
guard let data = data else { return }
let uartPacket = UartPacket(peripheralId: peripheralIdentifier, mode: .rx, data: data)
// Mqtt publish to RX. TODO: Remove the dependency with MqttSettings and pass parameters
#if MQTT_ENABLED
if isMqttEnabled {
let mqttSettings = MqttSettings.shared
if mqttSettings.isPublishEnabled {
if let message = String(data: uartPacket.data, encoding: .utf8) {
if let topic = mqttSettings.getPublishTopic(index: MqttSettings.PublishFeed.rx.rawValue) {
let qos = mqttSettings.getPublishQos(index: MqttSettings.PublishFeed.rx.rawValue)
MqttManager.shared.publish(message: message, topic: topic, qos: qos)
}
}
}
}
#endif
packetsSemaphore.wait() // don't append more data, till the delegate has finished processing it
receivedBytes += Int64(data.count)
if isPacketCacheEnabled {
packets.append(uartPacket)
}
// Send data to delegate
DispatchQueue.main.async {
self.delegate?.onUartPacket(uartPacket)
}
//DLog("packetsData: \(packetsData.count)")
packetsSemaphore.signal()
}
func clearPacketsCache() {
packets.removeAll()
}
func packetsCache() -> [UartPacket] {
return packets
}
// MARK: - Counters
func resetCounters() {
receivedBytes = 0
sentBytes = 0
}
}

View file

@ -0,0 +1,102 @@
//
// ElementQueue.swift
// Bluefruit
//
// Created by Antonio García on 17/10/2016.
// Copyright © 2016 Adafruit. All rights reserved.
//
import Foundation
// Command array, executed sequencially
class CommandQueue<Element> {
var executeHandler: ((_ command: Element) -> Void)?
private var queueLock = NSLock()
/*
private var queue = [Element]() {
didSet {
queueLock.lock()
var shouldExecute = false
// Start executing the first command (if it was not already executing)
let nextElement = queue.first
if oldValue.isEmpty, nextElement != nil {
shouldExecute = true
}
DLog("queue size: \(queue.count)")
queueLock.unlock()
if shouldExecute {
self.executeHandler?(nextElement!)
}
}
}
func first() -> Element? {
queueLock.lock(); defer { queueLock.unlock() }
return queue.first
}
func append(_ element: Element) {
queue.append(element)
}
func next() {
guard !queue.isEmpty else { return }
// Delete finished command and trigger next execution if needed
queue.removeFirst()
if let nextElement = queue.first {
executeHandler?(nextElement)
}
}
func removeAll() {
DLog("queue removeAll")
queue.removeAll()
}
*/
private var queue = [Element]()
func first() -> Element? {
queueLock.lock(); defer { queueLock.unlock() }
//DLog("queue: \(queue) first: \(queue.first)")
return queue.first
}
func executeNext() {
queueLock.lock()
guard !queue.isEmpty else { queueLock.unlock(); return }
//DLog("queue remove finished: \(queue.first)")
// Delete finished command and trigger next execution if needed
queue.removeFirst()
let nextElement = queue.first
queueLock.unlock()
if let nextElement = nextElement {
//DLog("execute next")
executeHandler?(nextElement)
}
}
func append(_ element: Element) {
queueLock.lock()
let shouldExecute = queue.isEmpty
queue.append(element)
queueLock.unlock()
//DLog("queue: \(queue) append: \(element). total: \(queue.count)")
if shouldExecute {
executeHandler?(element)
}
}
func removeAll() {
// DLog("queue removeAll: \(queue.count)")
queue.removeAll()
}
}

View file

@ -0,0 +1,26 @@
//
// Data+LittleEndianTypes.swift
// BluefruitPlayground
//
// Created by Antonio García on 13/11/2019.
// Copyright © 2019 Adafruit. All rights reserved.
//
import Foundation
extension Data {
func toFloatFrom32Bits() -> Float {
return Float(bitPattern: UInt32(littleEndian: self.withUnsafeBytes { $0.load(as: UInt32.self) }))
}
func toIntFrom32Bits() -> Int {
return Int(Int32(bitPattern: UInt32(littleEndian: self.withUnsafeBytes { $0.load(as: UInt32.self) })))
}
func toInt32From32Bits() -> Int32 {
return Int32(bitPattern: UInt32(littleEndian: self.withUnsafeBytes { $0.load(as: UInt32.self) }))
}
}

View file

@ -0,0 +1,17 @@
//
// Data+ScanValues.swift
// Bluefruit
//
// Created by Antonio García on 17/11/2016.
// Copyright © 2016 Adafruit. All rights reserved.
//
import Foundation
// MARK: - Data Scan
extension Data {
func scanValue<T>(start: Int, length: Int) -> T {
let subdata = self.subdata(in: start..<start+length)
return subdata.withUnsafeBytes { $0.load(as: T.self) }
}
}

View file

@ -0,0 +1,23 @@
//
// HexUtils.swift
// Bluefruit
//
// Created by Antonio García on 15/10/16.
// Copyright © 2015 Adafruit. All rights reserved.
//
import Foundation
struct HexUtils {
static func hexDescription(data: Data, prefix: String = "", postfix: String = " ") -> String {
return data.reduce("") {$0 + String(format: "%@%02X%@", prefix, $1, postfix)}
}
static func hexDescription(bytes: [UInt8], prefix: String = "", postfix: String = " ") -> String {
return bytes.reduce("") {$0 + String(format: "%@%02X%@", prefix, $1, postfix)}
}
static func decimalDescription(data: Data, prefix: String = "", postfix: String = " ") -> String {
return data.reduce("") {$0 + String(format: "%@%ld%@", prefix, $1, postfix)}
}
}

View file

@ -0,0 +1,45 @@
//
// Int+ToByteArray.swift
// Bluefruit
//
// Created by Antonio García on 11/06/2019.
// Copyright © 2019 Adafruit. All rights reserved.
//
import Foundation
// From https://stackoverflow.com/questions/29970204/split-uint32-into-uint8-in-swift
protocol UIntToBytesConvertable {
var toBytes: [UInt8] { get }
}
extension UIntToBytesConvertable {
fileprivate func toByteArr<T: FixedWidthInteger>(endian: T, count: Int) -> [UInt8] {
var _endian = endian
let bytePtr = withUnsafePointer(to: &_endian) {
$0.withMemoryRebound(to: UInt8.self, capacity: count) {
UnsafeBufferPointer(start: $0, count: count)
}
}
return [UInt8](bytePtr)
}
}
extension UInt16: UIntToBytesConvertable {
var toBytes: [UInt8] {
return toByteArr(endian: self.littleEndian, count: MemoryLayout<UInt16>.size)
}
}
extension UInt32: UIntToBytesConvertable {
var toBytes: [UInt8] {
return toByteArr(endian: self.littleEndian, count: MemoryLayout<UInt32>.size)
}
}
extension UInt64: UIntToBytesConvertable {
var toBytes: [UInt8] {
return toByteArr(endian: self.littleEndian, count: MemoryLayout<UInt64>.size)
}
}

View file

@ -0,0 +1,17 @@
//
// LogHelper.swift
// BluefruitPlayground
//
// Created by Antonio García on 10/10/2019.
// Copyright © 2019 Adafruit. All rights reserved.
//
import Foundation
// Note: check that Build Settings -> Project -> Active Compilation Conditions -> Debug, has DEBUG
func DLog(_ message: String, function: String = #function) {
if _isDebugAssertConfiguration() {
NSLog("%@, %@", function, message)
}
}

View file

@ -0,0 +1,22 @@
//
// TouchReleaseRectangularPaletteControl.swift
// BluefruitPlayground
//
// Created by Antonio García on 16/12/2019.
// Copyright © 2019 Adafruit. All rights reserved.
//
import UIKit
import FlexColorPicker
class TouchReleaseRectangularPaletteControl: RectangularPaletteControl {
/*
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
}
*/
}

View file

@ -0,0 +1,55 @@
//
// Types+Data.swift
// BluefruitPlayground
//
// Created by Antonio García on 13/11/2019.
// Copyright © 2019 Adafruit. All rights reserved.
//
import Foundation
// from: https://stackoverflow.com/questions/38023838/round-trip-swift-number-types-to-from-data
protocol DataConvertible {
init?(data: Data)
var data: Data { get }
}
extension DataConvertible where Self: ExpressibleByIntegerLiteral {
init?(data: Data) {
var value: Self = 0
guard data.count == MemoryLayout.size(ofValue: value) else { return nil }
_ = withUnsafeMutableBytes(of: &value, { data.copyBytes(to: $0)} )
self = value
}
var data: Data {
return withUnsafeBytes(of: self) { Data($0) }
}
}
// Declare conformance to all types which can safely be converted to Data and back
extension Int: DataConvertible { }
extension UInt8: DataConvertible { }
extension Int16: DataConvertible { }
extension UInt16: DataConvertible { }
extension Int32: DataConvertible { }
extension UInt32: DataConvertible { }
extension Float: DataConvertible { }
extension Double: DataConvertible { }
// Convert from [UInt8] to Data and from Data to [UInt8]
// from: https://stackoverflow.com/questions/31821709/nsdata-to-uint8-in-swift/31821838
extension Data {
var bytes: [UInt8] {
return [UInt8](self)
}
}
extension Array where Element == UInt8 {
var data: Data {
return Data(self)
}
}

View file

@ -1,9 +1,9 @@
//
// AppDelegate.swift
// CPX+BLE
// BluefruitPlayground
//
// Created by Trevor B on 8/14/19.
// Copyright © 2019 Adafruit Industries LLC. All rights reserved.
// Created by Antonio García on 09/10/2019.
// Copyright © 2019 Adafruit. All rights reserved.
//
import UIKit
@ -13,25 +13,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
self.window = UIWindow(frame: UIScreen.main.bounds)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
var vc: UIViewController?
if (UserDefaults.standard.value(forKey: "name") as? String) == nil {
vc = storyboard.instantiateViewController(withIdentifier: "rootPage")
} else {
vc = storyboard.instantiateInitialViewController()!
}
self.window?.rootViewController = vc
self.window?.makeKeyAndVisible()
startup()
return true
}
@ -57,6 +42,30 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
// MARK: - Startup
private func startup() {
// Settings
Settings.registerDefaults()
// UI
UINavigationBar.appearance().prefersLargeTitles = ConfigUI.prefersLargeTitles
// Navigation bar: add background when large title is used
if #available(iOS 13.0, *) {
let appearance = UINavigationBarAppearance()
appearance.backgroundColor = UIColor(named: "main")
appearance.titleTextAttributes = [.foregroundColor: UIColor.white]
appearance.largeTitleTextAttributes = [.foregroundColor: UIColor.white]
UINavigationBar.appearance().tintColor = .white
UINavigationBar.appearance().standardAppearance = appearance
UINavigationBar.appearance().compactAppearance = appearance
UINavigationBar.appearance().scrollEdgeAppearance = appearance
} else {
UINavigationBar.appearance().tintColor = .white
UINavigationBar.appearance().barTintColor = UIColor(named: "main")
UINavigationBar.appearance().isTranslucent = false
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9 KiB

View file

@ -1,93 +1,111 @@
{
"images" : [
{
"idiom" : "iphone",
{
"size" : "20x20",
"scale" : "2x"
"idiom": "iphone",
"filename" : "BluefruitPlayground-Icon-20@2x.png",
"scale": "2x"
},
{
"idiom" : "iphone",
{
"size" : "20x20",
"scale" : "3x"
"idiom": "iphone",
"filename" : "BluefruitPlayground-Icon-20@3x.png",
"scale": "3x"
},
{
"size" : "20x20",
"idiom": "ipad",
"filename" : "BluefruitPlayground-Icon-20.png",
"scale": "1x"
},
{
"size" : "20x20",
"idiom": "ipad",
"filename" : "BluefruitPlayground-Icon-20@2x.png",
"scale": "2x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"idiom" : "iphone",
"filename" : "BluefruitPlayground-Icon-29@2x.png",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"idiom" : "iphone",
"filename" : "BluefruitPlayground-Icon-29@3x.png",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"idiom" : "iphone",
"filename" : "BluefruitPlayground-Icon-40@2x.png",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"idiom" : "iphone",
"filename" : "BluefruitPlayground-Icon-40@3x.png",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"idiom" : "iphone",
"filename" : "BluefruitPlayground-Icon-60@2x.png",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"idiom" : "iphone",
"filename" : "BluefruitPlayground-Icon-60@3x.png",
"scale" : "3x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"idiom" : "ipad",
"filename" : "BluefruitPlayground-Icon-29.png",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"idiom" : "ipad",
"filename" : "BluefruitPlayground-Icon-29@2x.png",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"idiom" : "ipad",
"filename" : "BluefruitPlayground-Icon-40.png",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"idiom" : "ipad",
"filename" : "BluefruitPlayground-Icon-40@2x.png",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"idiom" : "ipad",
"filename" : "BluefruitPlayground-Icon-76.png",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"idiom" : "ipad",
"filename" : "BluefruitPlayground-Icon-76@2x.png",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "BluefruitPlayground-Icon-83.5@2x.png",
"scale" : "2x"
},
{
"idiom" : "ios-marketing",
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "BluefruitPlayground-Icon-1024.png",
"scale" : "1x"
}
],
@ -95,4 +113,4 @@
"version" : 1,
"author" : "xcode"
}
}
}

View file

@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View file

@ -0,0 +1,25 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "info_adafruit_logo.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "info_adafruit_logo@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View file

@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View file

@ -0,0 +1,26 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "status_a.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "status_a@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "status_a@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -0,0 +1,26 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "status_b.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "status_b@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "status_b@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -0,0 +1,26 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "status_left.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "status_left@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "status_left@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -0,0 +1,26 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "status_right.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "status_right@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "status_right@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View file

@ -0,0 +1,26 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "circuit_all.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "circuit_all@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "circuit_all@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 929 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

View file

@ -0,0 +1,26 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "circuit_clear.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "circuit_clear@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "circuit_clear@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 932 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View file

@ -0,0 +1,26 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "circuit_reset.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "circuit_reset@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "circuit_reset@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Some files were not shown because too many files have changed in this diff Show more