Initial import: app build 7

This commit is contained in:
Antonio 2019-12-17 11:43:56 +01:00
parent 28157189a8
commit b9760b163f
553 changed files with 51402 additions and 0 deletions

14
.gitignore vendored Normal file
View file

@ -0,0 +1,14 @@
## User settings
xcuserdata/
# fastlane specific
**/fastlane/report.xml
# deliver temporary files
**/fastlane/Preview.html
# snapshot generated screenshots
**/fastlane/screenshots
# scan temporary files
**/fastlane/test_output

View file

@ -0,0 +1,55 @@
<?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>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>BP Simulated</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<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>
<string>Main</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UIStatusBarStyle</key>
<string>UIStatusBarStyleLightContent</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<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

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:CPX.xcodeproj">
</FileRef>
</Workspace>

View file

@ -0,0 +1,8 @@
<?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>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

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,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

@ -0,0 +1,8 @@
<?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>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

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,48 @@
//
// 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 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

@ -0,0 +1,71 @@
//
// AppDelegate.swift
// BluefruitPlayground
//
// Created by Antonio García on 09/10/2019.
// Copyright © 2019 Adafruit. All rights reserved.
//
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
startup()
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(_ application: UIApplication) {
// 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

@ -0,0 +1,116 @@
{
"images" : [
{
"size" : "20x20",
"idiom": "iphone",
"filename" : "BluefruitPlayground-Icon-20@2x.png",
"scale": "2x"
},
{
"size" : "20x20",
"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"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "BluefruitPlayground-Icon-29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "BluefruitPlayground-Icon-29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "BluefruitPlayground-Icon-40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "BluefruitPlayground-Icon-40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "BluefruitPlayground-Icon-60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "BluefruitPlayground-Icon-60@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "BluefruitPlayground-Icon-29.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "BluefruitPlayground-Icon-29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "BluefruitPlayground-Icon-40.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "BluefruitPlayground-Icon-40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "BluefruitPlayground-Icon-76.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "BluefruitPlayground-Icon-76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "BluefruitPlayground-Icon-83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "BluefruitPlayground-Icon-1024.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View file

@ -0,0 +1,6 @@
{
"info" : {
"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.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 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.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 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

View file

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

View file

@ -0,0 +1,20 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
},
"colors" : [
{
"idiom" : "universal",
"color" : {
"color-space" : "srgb",
"components" : {
"red" : "0x00",
"alpha" : "1.000",
"blue" : "0xB1",
"green" : "0x7C"
}
}
}
]
}

View file

@ -0,0 +1,20 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
},
"colors" : [
{
"idiom" : "universal",
"color" : {
"color-space" : "display-p3",
"components" : {
"red" : "0x00",
"alpha" : "1.000",
"blue" : "0xE1",
"green" : "0x9E"
}
}
}
]
}

View file

@ -0,0 +1,20 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
},
"colors" : [
{
"idiom" : "universal",
"color" : {
"color-space" : "srgb",
"components" : {
"red" : "1.000",
"alpha" : "0.150",
"blue" : "1.000",
"green" : "1.000"
}
}
}
]
}

View file

@ -0,0 +1,20 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
},
"colors" : [
{
"idiom" : "universal",
"color" : {
"color-space" : "display-p3",
"components" : {
"red" : "0.138",
"alpha" : "1.000",
"blue" : "0.726",
"green" : "0.218"
}
}
}
]
}

View file

@ -0,0 +1,20 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
},
"colors" : [
{
"idiom" : "universal",
"color" : {
"color-space" : "srgb",
"components" : {
"red" : "0.000",
"alpha" : "1.000",
"blue" : "0.882",
"green" : "0.620"
}
}
}
]
}

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