Compare commits
80 commits
puppetModu
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
05e43d9a74 | ||
|
|
5e5995eb5d | ||
|
|
78a3e93679 | ||
|
|
47ffad0c0d | ||
|
|
82a6a37a9b | ||
|
|
4c924c2605 | ||
|
|
d290b65d91 | ||
|
|
f17d2017ce | ||
|
|
ad8e7ffade | ||
|
|
251bd8ec31 | ||
|
|
4d268e4f4f | ||
|
|
e2bd12b539 | ||
|
|
969e1eb7c0 | ||
|
|
d22f07df7d | ||
|
|
60c0ae8c6b | ||
|
|
263c1163e9 | ||
|
|
c089e932da | ||
|
|
5125b08706 | ||
|
|
5f22e72211 | ||
|
|
542f5ca8c8 | ||
|
|
0d33821c88 | ||
|
|
76ac60c734 | ||
|
|
5f9bfb3ee9 | ||
|
|
ec13361e4f | ||
|
|
7c7ae8578f | ||
|
|
3fdf7a15fe | ||
|
|
cd834da0a1 | ||
|
|
d487170a96 | ||
|
|
d706ae85fd | ||
|
|
0d7c091fb6 | ||
|
|
5e9eeabe5e | ||
|
|
d6269f59be | ||
|
|
13a4c4ff3d | ||
|
|
49b2508da4 | ||
|
|
44e4654301 | ||
|
|
b5394dab0a | ||
|
|
36872363d8 | ||
|
|
cdf816f848 | ||
|
|
380891dceb | ||
|
|
2090698fc4 | ||
|
|
91d5322795 | ||
|
|
019befae61 | ||
|
|
c02a36c976 | ||
|
|
3feac2a2e5 | ||
|
|
a61def2b4c | ||
|
|
1c0cb8235e | ||
|
|
c1a88e264e | ||
|
|
a1cc6d6306 | ||
|
|
5244715073 | ||
|
|
19768e4c52 | ||
|
|
d1e72eb47e | ||
|
|
90da0e9896 | ||
|
|
519022fcf5 | ||
|
|
0b7259834e | ||
|
|
3ba4fa2d9d | ||
|
|
213910c54a | ||
|
|
b020a9ee43 | ||
|
|
4eec8bb5b7 | ||
|
|
a44b33e66f | ||
|
|
031bab1241 | ||
|
|
f48758ed77 | ||
|
|
b96425596a | ||
|
|
148a33a5d8 | ||
|
|
e326b0b9d5 | ||
|
|
a220f3dec4 | ||
|
|
e4f4c8bcd1 | ||
|
|
a105506ba6 | ||
|
|
170809c2f8 | ||
|
|
f75db839ff | ||
|
|
0bf2400b88 | ||
|
|
1c98c92bac | ||
|
|
379d61a3b1 | ||
|
|
f5af18568e | ||
|
|
f56cfee413 | ||
|
|
3933990111 | ||
|
|
eb4182c30e | ||
|
|
1dab5a618e | ||
|
|
eacad0dc42 | ||
|
|
462ea1c289 | ||
|
|
9c8d6f2e00 |
BIN
.DS_Store
vendored
26
.github/workflows/build.yml
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
name: Build App
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
jobs:
|
||||
build-stable:
|
||||
name: Build Bluefruit in stable Xcode
|
||||
runs-on: macos-13
|
||||
strategy:
|
||||
matrix:
|
||||
xcode: ['15.0.1']
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Select ${{ matrix.xcode }}
|
||||
run: |
|
||||
sudo xcode-select -s /Applications/Xcode_$XCODE_VERSION.app
|
||||
xcode-select -p
|
||||
env:
|
||||
XCODE_VERSION: ${{ matrix.xcode }}
|
||||
|
||||
- name: Build
|
||||
run: xcodebuild -scheme BluefruitPlayground -workspace BluefruitPlayground.xcworkspace -destination "generic/platform=iOS" -configuration Release build CODE_SIGNING_ALLOWED=NO
|
||||
|
|
@ -28,6 +28,8 @@
|
|||
<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>NSCameraUsageDescription</key>
|
||||
<string>Puppet module requires access to the camera</string>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1130"
|
||||
LastUpgradeVersion = "1500"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1120"
|
||||
LastUpgradeVersion = "1500"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1130"
|
||||
LastUpgradeVersion = "1500"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
|
|
|||
BIN
BluefruitPlayground/.DS_Store
vendored
|
|
@ -0,0 +1,97 @@
|
|||
//
|
||||
// BlePeripheral+AdafruitAccelerometer.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 kAdafruitAccelerometerServiceUUID = CBUUID(string: "ADAF0200-C332-42A8-93BD-25E905756CB8")
|
||||
private static let kAdafruitAccelerometerCharacteristicUUID = CBUUID(string: "ADAF0201-C332-42A8-93BD-25E905756CB8")
|
||||
private static let kAdafruitAccelerometerVersion = 1
|
||||
|
||||
// Structs
|
||||
/// Acceleration in m/s²
|
||||
struct AccelerometerValue {
|
||||
var x: Float
|
||||
var y: Float
|
||||
var z: Float
|
||||
}
|
||||
|
||||
// MARK: - Custom properties
|
||||
private struct CustomPropertiesKeys {
|
||||
static var adafruitAccelerometerCharacteristic: CBCharacteristic?
|
||||
}
|
||||
|
||||
private var adafruitAccelerometerCharacteristic: CBCharacteristic? {
|
||||
get {
|
||||
return objc_getAssociatedObject(self, &CustomPropertiesKeys.adafruitAccelerometerCharacteristic) as? CBCharacteristic
|
||||
}
|
||||
set {
|
||||
objc_setAssociatedObject(self, &CustomPropertiesKeys.adafruitAccelerometerCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
func adafruitAccelerometerEnable(responseHandler: @escaping(Result<(AccelerometerValue, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
||||
|
||||
self.adafruitServiceEnableIfVersion(version: BlePeripheral.kAdafruitAccelerometerVersion, serviceUuid: BlePeripheral.kAdafruitAccelerometerServiceUUID, mainCharacteristicUuid: BlePeripheral.kAdafruitAccelerometerCharacteristicUUID, timePeriod: BlePeripheral.kAdafruitSensorDefaultPeriod, responseHandler: { response in
|
||||
|
||||
switch response {
|
||||
case let .success((data, uuid)):
|
||||
if let value = self.adafruitAccelerometerDataToAcceleromterValue(data) {
|
||||
responseHandler(.success((value, uuid)))
|
||||
} else {
|
||||
responseHandler(.failure(PeripheralAdafruitError.invalidResponseData))
|
||||
}
|
||||
case let .failure(error):
|
||||
responseHandler(.failure(error))
|
||||
}
|
||||
|
||||
}, completion: { result in
|
||||
switch result {
|
||||
case let .success(characteristic):
|
||||
self.adafruitAccelerometerCharacteristic = characteristic
|
||||
completion?(.success(()))
|
||||
|
||||
case let .failure(error):
|
||||
self.adafruitAccelerometerCharacteristic = nil
|
||||
completion?(.failure(error))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func adafruitAccelerometerIsEnabled() -> Bool {
|
||||
return adafruitAccelerometerCharacteristic != nil && adafruitAccelerometerCharacteristic!.isNotifying
|
||||
}
|
||||
|
||||
func adafruitAccelerometerDisable() {
|
||||
// Clear all specific data
|
||||
defer {
|
||||
adafruitAccelerometerCharacteristic = nil
|
||||
}
|
||||
|
||||
// Disable notify
|
||||
guard let characteristic = adafruitAccelerometerCharacteristic, characteristic.isNotifying else { return }
|
||||
disableNotify(for: characteristic)
|
||||
}
|
||||
|
||||
func adafruitAccelerometerLastValue() -> AccelerometerValue? {
|
||||
guard let data = adafruitAccelerometerCharacteristic?.value else { return nil }
|
||||
return adafruitAccelerometerDataToAcceleromterValue(data)
|
||||
}
|
||||
|
||||
// MARK: - Utils
|
||||
private func adafruitAccelerometerDataToAcceleromterValue(_ data: Data) -> AccelerometerValue? {
|
||||
|
||||
guard let bytes = adafruitDataToFloatArray(data) else { return nil }
|
||||
guard bytes.count >= 3 else { return nil }
|
||||
return AccelerometerValue(x: bytes[0], y: bytes[1], z: bytes[2])
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
//
|
||||
// BlePeripheral+AdafruitBarometricPressure.swift
|
||||
// BluefruitPlayground
|
||||
//
|
||||
// Created by Antonio García on 06/03/2020.
|
||||
// Copyright © 2019 Adafruit. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreBluetooth
|
||||
|
||||
extension BlePeripheral {
|
||||
// Constants
|
||||
static let kAdafruitBarometricPressureServiceUUID = CBUUID(string: "ADAF0800-C332-42A8-93BD-25E905756CB8")
|
||||
private static let kAdafruitBarometricPressureCharacteristicUUID = CBUUID(string: "ADAF0801-C332-42A8-93BD-25E905756CB8")
|
||||
private static let kAdafruitBarometricPressureVersion = 1
|
||||
|
||||
// MARK: - Custom properties
|
||||
private struct CustomPropertiesKeys {
|
||||
static var adafruitBarometricPressureCharacteristic: CBCharacteristic?
|
||||
}
|
||||
|
||||
private var adafruitBarometricPressureCharacteristic: CBCharacteristic? {
|
||||
get {
|
||||
return objc_getAssociatedObject(self, &CustomPropertiesKeys.adafruitBarometricPressureCharacteristic) as? CBCharacteristic
|
||||
}
|
||||
set {
|
||||
objc_setAssociatedObject(self, &CustomPropertiesKeys.adafruitBarometricPressureCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
func adafruitBarometricPressureEnable(responseHandler: @escaping(Result<(Float, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
||||
|
||||
self.adafruitServiceEnableIfVersion(version: BlePeripheral.kAdafruitBarometricPressureVersion, serviceUuid: BlePeripheral.kAdafruitBarometricPressureServiceUUID, mainCharacteristicUuid: BlePeripheral.kAdafruitBarometricPressureCharacteristicUUID, timePeriod: BlePeripheral.kAdafruitSensorDefaultPeriod, responseHandler: { response in
|
||||
|
||||
switch response {
|
||||
case let .success((data, uuid)):
|
||||
let value = self.adafruitBarometricPressureDataToFloat(data)
|
||||
responseHandler(.success((value, uuid)))
|
||||
case let .failure(error):
|
||||
responseHandler(.failure(error))
|
||||
}
|
||||
|
||||
}, completion: { result in
|
||||
switch result {
|
||||
case let .success(characteristic):
|
||||
self.adafruitBarometricPressureCharacteristic = characteristic
|
||||
completion?(.success(()))
|
||||
|
||||
case let .failure(error):
|
||||
self.adafruitBarometricPressureCharacteristic = nil
|
||||
completion?(.failure(error))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func adafruitBarometricPressureIsEnabled() -> Bool {
|
||||
return adafruitBarometricPressureCharacteristic != nil && adafruitBarometricPressureCharacteristic!.isNotifying
|
||||
}
|
||||
|
||||
func adafruitBarometricPressureDisable() {
|
||||
// Clear all specific data
|
||||
defer {
|
||||
adafruitBarometricPressureCharacteristic = nil
|
||||
}
|
||||
|
||||
// Disable notify
|
||||
guard let characteristic = adafruitBarometricPressureCharacteristic, characteristic.isNotifying else { return }
|
||||
disableNotify(for: characteristic)
|
||||
}
|
||||
|
||||
func adafruitBarometricPressureLastValue() -> Float? {
|
||||
guard let data = adafruitBarometricPressureCharacteristic?.value else { return nil }
|
||||
return adafruitBarometricPressureDataToFloat(data)
|
||||
}
|
||||
|
||||
// MARK: - Utils
|
||||
private func adafruitBarometricPressureDataToFloat(_ data: Data) -> Float {
|
||||
return data.toFloatFrom32Bits()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
//
|
||||
// BlePeripheral+AdafruitButtons.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 kAdafruitButtonsServiceUUID = CBUUID(string: "ADAF0600-C332-42A8-93BD-25E905756CB8")
|
||||
private static let kAdafruitButtonsCharacteristicUUID = CBUUID(string: "ADAF0601-C332-42A8-93BD-25E905756CB8")
|
||||
private static let kAdafruitButtonsVersion = 1
|
||||
|
||||
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 adafruitButtonsCharacteristic: CBCharacteristic?
|
||||
}
|
||||
|
||||
private var adafruitButtonsCharacteristic: CBCharacteristic? {
|
||||
get {
|
||||
return objc_getAssociatedObject(self, &CustomPropertiesKeys.adafruitButtonsCharacteristic) as? CBCharacteristic
|
||||
}
|
||||
set {
|
||||
objc_setAssociatedObject(self, &CustomPropertiesKeys.adafruitButtonsCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
func adafruitButtonsEnable(responseHandler: @escaping(Result<(ButtonsState, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
||||
|
||||
let timePeriod: TimeInterval = 0 // 0 means that the responseHandler will be called only when there is a change
|
||||
self.adafruitServiceEnableIfVersion(version: BlePeripheral.kAdafruitButtonsVersion, serviceUuid: BlePeripheral.kAdafruitButtonsServiceUUID, mainCharacteristicUuid: BlePeripheral.kAdafruitButtonsCharacteristicUUID, timePeriod: timePeriod, responseHandler: { response in
|
||||
|
||||
switch response {
|
||||
case let .success((data, uuid)):
|
||||
let state = self.adafruitButtonsDataToStateMask(data)
|
||||
responseHandler(.success((state, uuid)))
|
||||
case let .failure(error):
|
||||
responseHandler(.failure(error))
|
||||
}
|
||||
|
||||
}, completion: { result in
|
||||
switch result {
|
||||
case let .success(characteristic):
|
||||
self.adafruitButtonsCharacteristic = characteristic
|
||||
|
||||
if timePeriod == 0 { // Read initial state if the timePeriod is 0 (update only when changed)
|
||||
self.adafruitButtonsReadState { response in
|
||||
switch response {
|
||||
case .success:
|
||||
completion?(.success(()))
|
||||
case .failure(let error):
|
||||
DLog("Error receiving initial button state data: \(error)")
|
||||
completion?(.failure(error))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
completion?(.success(()))
|
||||
}
|
||||
|
||||
case let .failure(error):
|
||||
self.adafruitButtonsCharacteristic = nil
|
||||
completion?(.failure(error))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func adafruitButtonsIsEnabled() -> Bool {
|
||||
return adafruitButtonsCharacteristic != nil && adafruitButtonsCharacteristic!.isNotifying
|
||||
}
|
||||
|
||||
func adafruitButtonsDisable() {
|
||||
// Clear all specific data
|
||||
defer {
|
||||
adafruitButtonsCharacteristic = nil
|
||||
}
|
||||
|
||||
// Disable notify
|
||||
guard let characteristic = adafruitButtonsCharacteristic, characteristic.isNotifying else { return }
|
||||
disableNotify(for: characteristic)
|
||||
}
|
||||
|
||||
func adafruitButtonsReadState(completion: @escaping(Result<(ButtonsState, UUID), Error>) -> Void) {
|
||||
guard let adafruitButtonsCharacteristic = adafruitButtonsCharacteristic else {
|
||||
completion(.failure(PeripheralAdafruitError.invalidCharacteristic))
|
||||
return
|
||||
}
|
||||
|
||||
self.readCharacteristic(adafruitButtonsCharacteristic) { [weak self] (data, error) in
|
||||
guard let self = self else { return }
|
||||
|
||||
guard error == nil, let data = data as? Data else {
|
||||
completion(.failure(error ?? PeripheralAdafruitError.invalidResponseData))
|
||||
return
|
||||
}
|
||||
|
||||
let state = self.adafruitButtonsDataToStateMask(data)
|
||||
completion(.success((state, self.identifier)))
|
||||
}
|
||||
}
|
||||
|
||||
func adafruitButtonsLastValue() -> ButtonsState? {
|
||||
guard let data = adafruitButtonsCharacteristic?.value else { return nil }
|
||||
return adafruitButtonsDataToStateMask(data)
|
||||
}
|
||||
|
||||
// MARK: - Utils
|
||||
private func adafruitButtonsDataToStateMask(_ 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
//
|
||||
// BlePeripheral+AdafruitColorSensor.swift
|
||||
// BluefruitPlayground
|
||||
//
|
||||
// Created by Antonio García on 06/03/2020.
|
||||
// Copyright © 2019 Adafruit. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import CoreBluetooth
|
||||
|
||||
extension BlePeripheral {
|
||||
// Constants
|
||||
static let kAdafruitColorSensorServiceUUID = CBUUID(string: "ADAF0A00-C332-42A8-93BD-25E905756CB8")
|
||||
private static let kAdafruitColorSensorCharacteristicUUID = CBUUID(string: "ADAF0A01-C332-42A8-93BD-25E905756CB8")
|
||||
private static let kAdafruitColorSensorVersion = 1
|
||||
|
||||
// MARK: - Custom properties
|
||||
private struct CustomPropertiesKeys {
|
||||
static var adafruitColorSensorCharacteristic: CBCharacteristic?
|
||||
}
|
||||
|
||||
private var adafruitColorSensorCharacteristic: CBCharacteristic? {
|
||||
get {
|
||||
return objc_getAssociatedObject(self, &CustomPropertiesKeys.adafruitColorSensorCharacteristic) as? CBCharacteristic
|
||||
}
|
||||
set {
|
||||
objc_setAssociatedObject(self, &CustomPropertiesKeys.adafruitColorSensorCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
func adafruitColorSensorEnable(responseHandler: @escaping(Result<(UIColor, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
||||
|
||||
self.adafruitServiceEnableIfVersion(version: BlePeripheral.kAdafruitColorSensorVersion, serviceUuid: BlePeripheral.kAdafruitColorSensorServiceUUID, mainCharacteristicUuid: BlePeripheral.kAdafruitColorSensorCharacteristicUUID, timePeriod: BlePeripheral.kAdafruitSensorDefaultPeriod, responseHandler: { response in
|
||||
|
||||
switch response {
|
||||
case let .success((data, uuid)):
|
||||
if let value = self.adafruitColorSensorDataToColor(data) {
|
||||
responseHandler(.success((value, uuid)))
|
||||
}
|
||||
else {
|
||||
responseHandler(.failure(PeripheralAdafruitError.invalidResponseData))
|
||||
}
|
||||
case let .failure(error):
|
||||
responseHandler(.failure(error))
|
||||
}
|
||||
|
||||
}, completion: { result in
|
||||
switch result {
|
||||
case let .success(characteristic):
|
||||
self.adafruitColorSensorCharacteristic = characteristic
|
||||
completion?(.success(()))
|
||||
|
||||
case let .failure(error):
|
||||
self.adafruitColorSensorCharacteristic = nil
|
||||
completion?(.failure(error))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func adafruitColorSensorIsEnabled() -> Bool {
|
||||
return adafruitColorSensorCharacteristic != nil && adafruitColorSensorCharacteristic!.isNotifying
|
||||
}
|
||||
|
||||
func adafruitColorSensorDisable() {
|
||||
// Clear all specific data
|
||||
defer {
|
||||
adafruitColorSensorCharacteristic = nil
|
||||
}
|
||||
|
||||
// Disable notify
|
||||
guard let characteristic = adafruitColorSensorCharacteristic, characteristic.isNotifying else { return }
|
||||
disableNotify(for: characteristic)
|
||||
}
|
||||
|
||||
func adafruitColorSensorLastValue() -> UIColor? {
|
||||
guard let data = adafruitColorSensorCharacteristic?.value else { return nil }
|
||||
return adafruitColorSensorDataToColor(data)
|
||||
}
|
||||
|
||||
// MARK: - Utils
|
||||
private func adafruitColorSensorDataToColor(_ data: Data) -> UIColor? {
|
||||
guard let components = adafruitDataToUInt16Array(data) else { return nil }
|
||||
guard components.count >= 3 else { return nil }
|
||||
return UIColor(red: CGFloat(components[0])/CGFloat(UInt16.max), green: CGFloat(components[1])/CGFloat(UInt16.max), blue: CGFloat(components[2])/CGFloat(UInt16.max), alpha: 1)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,275 @@
|
|||
//
|
||||
// BlePeripehral+AdafruitCommon.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 kAdafruitMeasurementPeriodCharacteristicUUID = CBUUID(string: "ADAF0001-C332-42A8-93BD-25E905756CB8")
|
||||
private static let kAdafruitServiceVersionCharacteristicUUID = CBUUID(string: "ADAF0002-C332-42A8-93BD-25E905756CB8")
|
||||
|
||||
private static let kAdafruitDefaultVersionValue = 1 // Used as default version value if version characteristic cannot be read
|
||||
|
||||
static let kAdafruitSensorDefaultPeriod: TimeInterval = 0.2
|
||||
|
||||
|
||||
// MARK: - Errors
|
||||
enum PeripheralAdafruitError: Error {
|
||||
case invalidCharacteristic
|
||||
case enableNotifyFailed
|
||||
case disableNotifyFailed
|
||||
case unknownVersion
|
||||
case invalidResponseData
|
||||
}
|
||||
|
||||
// MARK: - Service Actions
|
||||
func adafruitServiceEnable(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 ?? PeripheralAdafruitError.invalidCharacteristic))
|
||||
return
|
||||
}
|
||||
|
||||
// Check version
|
||||
self.adafruitVersion(serviceUuid: serviceUuid) { version in
|
||||
completion?(.success((version, characteristic)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func adafruitServiceEnableIfVersion(version expectedVersion: Int, serviceUuid: CBUUID, mainCharacteristicUuid: CBUUID, completion: ((Result<CBCharacteristic, Error>) -> Void)?) {
|
||||
|
||||
self.adafruitServiceEnable(serviceUuid: serviceUuid, mainCharacteristicUuid: mainCharacteristicUuid) { [weak self] result in
|
||||
self?.checkVersionResult(expectedVersion: expectedVersion, result: result, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
- parameters:
|
||||
- timePeriod: seconds between measurements. -1 to disable measurements
|
||||
|
||||
*/
|
||||
func adafruitServiceEnableIfVersion(version expectedVersion: Int, serviceUuid: CBUUID, mainCharacteristicUuid: CBUUID, timePeriod: TimeInterval?, responseHandler: @escaping(Result<(Data, UUID), Error>) -> Void, completion: ((Result<CBCharacteristic, Error>) -> Void)?) {
|
||||
|
||||
adafruitServiceEnableIfVersion(version: expectedVersion, serviceUuid: serviceUuid, mainCharacteristicUuid: mainCharacteristicUuid) { [weak self] result in
|
||||
|
||||
switch result {
|
||||
case let .success(characteristic): // Version supported
|
||||
self?.adafruitServiceSetRepeatingResponse(characteristic: characteristic, timePeriod: timePeriod, responseHandler: responseHandler, completion: { result in
|
||||
|
||||
completion?(.success(characteristic))
|
||||
})
|
||||
|
||||
case let .failure(error): // Unsupported version (or error)
|
||||
completion?(.failure(error))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private func adafruitServiceSetRepeatingResponse(characteristic: CBCharacteristic, timePeriod: TimeInterval?, responseHandler: @escaping(Result<(Data, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
||||
|
||||
// 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(PeripheralAdafruitError.enableNotifyFailed))
|
||||
return
|
||||
}
|
||||
|
||||
completion?(.success(()))
|
||||
|
||||
})
|
||||
} else {
|
||||
self.updateNotifyHandler(for: characteristic, handler: notifyHandler)
|
||||
completion?(.success(()))
|
||||
}
|
||||
}
|
||||
|
||||
// Time period
|
||||
if let timePeriod = timePeriod, let serviceUuid = characteristic.service?.uuid { // Set timePeriod if not nil
|
||||
|
||||
self.adafruitSetPeriod(timePeriod, serviceUuid: serviceUuid) { _ in
|
||||
|
||||
if Config.isDebugEnabled {
|
||||
// Check period
|
||||
self.adafruitPeriod(serviceUuid: serviceUuid) { period in
|
||||
guard period != nil else { DLog("Error setting service period"); return }
|
||||
//DLog("service period: \(period!)")
|
||||
}
|
||||
}
|
||||
|
||||
enableNotificationsHandler()
|
||||
}
|
||||
} else { // Use default timePeriod
|
||||
enableNotificationsHandler()
|
||||
}
|
||||
}
|
||||
|
||||
private func checkVersionResult(expectedVersion: Int, result: Result<(Int, CBCharacteristic), Error>, completion: ((Result<CBCharacteristic, Error>) -> Void)?) {
|
||||
switch result {
|
||||
case let .success((version, characteristic)):
|
||||
guard version == expectedVersion else {
|
||||
DLog("Warning: adafruitServiceEnableIfVersion unknown version: \(version). Expected: \(expectedVersion)")
|
||||
completion?(.failure(PeripheralAdafruitError.unknownVersion))
|
||||
return
|
||||
}
|
||||
|
||||
completion?(.success(characteristic))
|
||||
case let .failure(error):
|
||||
completion?(.failure(error))
|
||||
}
|
||||
}
|
||||
|
||||
func adafruitServiceDisable(serviceUuid: CBUUID, mainCharacteristicUuid: CBUUID, completion: ((Result<Void, Error>) -> Void)?) {
|
||||
self.characteristic(uuid: mainCharacteristicUuid, serviceUuid: serviceUuid) { [weak self] (characteristic, error) in
|
||||
guard let characteristic = characteristic, error == nil else {
|
||||
completion?(.failure(error ?? PeripheralAdafruitError.invalidCharacteristic))
|
||||
return
|
||||
}
|
||||
|
||||
let kDisablePeriod: TimeInterval = -1 // -1 means taht the updates will be disabled
|
||||
self?.adafruitSetPeriod(kDisablePeriod, serviceUuid: serviceUuid) { [weak self] result in
|
||||
// Disable notifications
|
||||
if characteristic.isNotifying {
|
||||
self?.disableNotify(for: characteristic) { error in
|
||||
guard error == nil else {
|
||||
completion?(.failure(error!))
|
||||
return
|
||||
}
|
||||
guard !characteristic.isNotifying else {
|
||||
completion?(.failure(PeripheralAdafruitError.disableNotifyFailed))
|
||||
return
|
||||
}
|
||||
|
||||
completion?(.success(()))
|
||||
}
|
||||
}
|
||||
else {
|
||||
completion?(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func adafruitVersion(serviceUuid: CBUUID, completion: @escaping(Int) -> Void) {
|
||||
self.characteristic(uuid: BlePeripheral.kAdafruitServiceVersionCharacteristicUUID, serviceUuid: serviceUuid) { [weak self] (characteristic, error) in
|
||||
|
||||
// Check if version characteristic exists or return default value
|
||||
guard error == nil, let characteristic = characteristic else {
|
||||
completion(BlePeripheral.kAdafruitDefaultVersionValue)
|
||||
return
|
||||
}
|
||||
|
||||
// Read the version
|
||||
self?.readCharacteristic(characteristic) { (result, error) in
|
||||
guard error == nil, let data = result as? Data, data.count >= 4 else {
|
||||
completion(BlePeripheral.kAdafruitDefaultVersionValue)
|
||||
return
|
||||
}
|
||||
|
||||
let version = data.toIntFrom32Bits()
|
||||
completion(version)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func adafruitPeriod(serviceUuid: CBUUID, completion: @escaping(TimeInterval?) -> Void) {
|
||||
self.characteristic(uuid: BlePeripheral.kAdafruitMeasurementPeriodCharacteristicUUID, 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Set measurement period
|
||||
|
||||
- parameters:
|
||||
- period: seconds between measurements. -1 to disable measurements
|
||||
|
||||
*/
|
||||
func adafruitSetPeriod(_ period: TimeInterval, serviceUuid: CBUUID, completion: ((Result<Void, Error>) -> Void)?) {
|
||||
|
||||
self.characteristic(uuid: BlePeripheral.kAdafruitMeasurementPeriodCharacteristicUUID, serviceUuid: serviceUuid) { (characteristic, error) in
|
||||
|
||||
guard error == nil, let characteristic = characteristic else {
|
||||
DLog("Error: adafruitSetPeriod: \(String(describing: error))")
|
||||
return
|
||||
}
|
||||
|
||||
let periodMillis = period == -1 ? -1 : Int32(period * 1000) // -1 means disable measurements. It is a special value
|
||||
let data = periodMillis.littleEndian.data
|
||||
self.write(data: data, for: characteristic, type: .withResponse) { error in
|
||||
guard error == nil else {
|
||||
DLog("Error: adafruitSetPeriod \(error!)")
|
||||
completion?(.failure(error!))
|
||||
return
|
||||
}
|
||||
|
||||
completion?(.success(()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Utils
|
||||
func adafruitDataToFloatArray(_ data: Data) -> [Float]? {
|
||||
let unitSize = MemoryLayout<Float32>.stride
|
||||
var bytes = [Float32](repeating: 0, count: data.count / unitSize)
|
||||
(data as NSData).getBytes(&bytes, length: data.count * unitSize)
|
||||
|
||||
return bytes
|
||||
}
|
||||
|
||||
func adafruitDataToUInt16Array(_ data: Data) -> [UInt16]? {
|
||||
let unitSize = MemoryLayout<UInt16>.stride
|
||||
var words = [UInt16](repeating: 0, count: data.count / unitSize)
|
||||
(data as NSData).getBytes(&words, length: data.count * unitSize)
|
||||
return words
|
||||
}
|
||||
|
||||
func adafruitDataToInt16Array(_ data: Data) -> [Int16]? {
|
||||
let unitSize = MemoryLayout<Int16>.stride
|
||||
var words = [Int16](repeating: 0, count: data.count / unitSize)
|
||||
(data as NSData).getBytes(&words, length: data.count * unitSize)
|
||||
return words
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
//
|
||||
// BlePeripheral+AdafruitGyroscope.swift
|
||||
// BluefruitPlayground
|
||||
//
|
||||
// Created by Antonio García on 06/03/2020.
|
||||
// Copyright © 2019 Adafruit. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
import CoreBluetooth
|
||||
|
||||
extension BlePeripheral {
|
||||
// Constants
|
||||
static let kAdafruitGyroscopeServiceUUID = CBUUID(string: "ADAF0400-C332-42A8-93BD-25E905756CB8")
|
||||
private static let kAdafruitGyroscopeCharacteristicUUID = CBUUID(string: "ADAF0401-C332-42A8-93BD-25E905756CB8")
|
||||
private static let kAdafruitGyroscopeVersion = 1
|
||||
|
||||
// Structs
|
||||
/// Values in rad/s
|
||||
struct GyroscopeValue {
|
||||
var x: Float
|
||||
var y: Float
|
||||
var z: Float
|
||||
}
|
||||
|
||||
// MARK: - Custom properties
|
||||
private struct CustomPropertiesKeys {
|
||||
static var adafruitGyroscopeCharacteristic: CBCharacteristic?
|
||||
}
|
||||
|
||||
private var adafruitGyroscopeCharacteristic: CBCharacteristic? {
|
||||
get {
|
||||
return objc_getAssociatedObject(self, &CustomPropertiesKeys.adafruitGyroscopeCharacteristic) as? CBCharacteristic
|
||||
}
|
||||
set {
|
||||
objc_setAssociatedObject(self, &CustomPropertiesKeys.adafruitGyroscopeCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
func adafruitGyroscopeEnable(responseHandler: @escaping(Result<(GyroscopeValue, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
||||
|
||||
self.adafruitServiceEnableIfVersion(version: BlePeripheral.kAdafruitGyroscopeVersion, serviceUuid: BlePeripheral.kAdafruitGyroscopeServiceUUID, mainCharacteristicUuid: BlePeripheral.kAdafruitGyroscopeCharacteristicUUID, timePeriod: BlePeripheral.kAdafruitSensorDefaultPeriod, responseHandler: { response in
|
||||
|
||||
switch response {
|
||||
case let .success((data, uuid)):
|
||||
if let value = self.adafruitGyroscopeDataToGyroscopeValue(data) {
|
||||
responseHandler(.success((value, uuid)))
|
||||
} else {
|
||||
responseHandler(.failure(PeripheralAdafruitError.invalidResponseData))
|
||||
}
|
||||
case let .failure(error):
|
||||
responseHandler(.failure(error))
|
||||
}
|
||||
|
||||
}, completion: { result in
|
||||
switch result {
|
||||
case let .success(characteristic):
|
||||
self.adafruitGyroscopeCharacteristic = characteristic
|
||||
completion?(.success(()))
|
||||
|
||||
case let .failure(error):
|
||||
self.adafruitGyroscopeCharacteristic = nil
|
||||
completion?(.failure(error))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func adafruitGyroscopeIsEnabled() -> Bool {
|
||||
return adafruitGyroscopeCharacteristic != nil && adafruitGyroscopeCharacteristic!.isNotifying
|
||||
}
|
||||
|
||||
func adafruitGyroscopeDisable() {
|
||||
// Clear all specific data
|
||||
defer {
|
||||
adafruitGyroscopeCharacteristic = nil
|
||||
}
|
||||
|
||||
// Disable notify
|
||||
guard let characteristic = adafruitGyroscopeCharacteristic, characteristic.isNotifying else { return }
|
||||
disableNotify(for: characteristic)
|
||||
}
|
||||
|
||||
func adafruitGyroscopeLastValue() -> GyroscopeValue? {
|
||||
guard let data = adafruitGyroscopeCharacteristic?.value else { return nil }
|
||||
return adafruitGyroscopeDataToGyroscopeValue(data)
|
||||
}
|
||||
|
||||
// MARK: - Utils
|
||||
private func adafruitGyroscopeDataToGyroscopeValue(_ data: Data) -> GyroscopeValue? {
|
||||
guard let bytes = adafruitDataToFloatArray(data) else { return nil }
|
||||
guard bytes.count >= 3 else { return nil }
|
||||
return GyroscopeValue(x: bytes[0], y: bytes[1], z: bytes[2])
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
//
|
||||
// BlePeripheral+AdafruitHumidity.swift
|
||||
// BluefruitPlayground
|
||||
//
|
||||
// Created by Antonio García on 06/03/2020.
|
||||
// Copyright © 2019 Adafruit. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreBluetooth
|
||||
|
||||
extension BlePeripheral {
|
||||
// Constants
|
||||
static let kAdafruitHumidityServiceUUID = CBUUID(string: "ADAF0700-C332-42A8-93BD-25E905756CB8")
|
||||
private static let kAdafruitHumidityCharacteristicUUID = CBUUID(string: "ADAF0701-C332-42A8-93BD-25E905756CB8")
|
||||
private static let kAdafruitHumidityVersion = 1
|
||||
|
||||
// MARK: - Custom properties
|
||||
private struct CustomPropertiesKeys {
|
||||
static var adafruitHumidityCharacteristic: CBCharacteristic?
|
||||
}
|
||||
|
||||
private var adafruitHumidityCharacteristic: CBCharacteristic? {
|
||||
get {
|
||||
return objc_getAssociatedObject(self, &CustomPropertiesKeys.adafruitHumidityCharacteristic) as? CBCharacteristic
|
||||
}
|
||||
set {
|
||||
objc_setAssociatedObject(self, &CustomPropertiesKeys.adafruitHumidityCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
func adafruitHumidityEnable(responseHandler: @escaping(Result<(Float, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
||||
|
||||
self.adafruitServiceEnableIfVersion(version: BlePeripheral.kAdafruitHumidityVersion, serviceUuid: BlePeripheral.kAdafruitHumidityServiceUUID, mainCharacteristicUuid: BlePeripheral.kAdafruitHumidityCharacteristicUUID, timePeriod: BlePeripheral.kAdafruitSensorDefaultPeriod, responseHandler: { response in
|
||||
|
||||
switch response {
|
||||
case let .success((data, uuid)):
|
||||
let value = self.adafruitHumidityDataToFloat(data)
|
||||
responseHandler(.success((value, uuid)))
|
||||
case let .failure(error):
|
||||
responseHandler(.failure(error))
|
||||
}
|
||||
|
||||
}, completion: { result in
|
||||
switch result {
|
||||
case let .success(characteristic):
|
||||
self.adafruitHumidityCharacteristic = characteristic
|
||||
completion?(.success(()))
|
||||
|
||||
case let .failure(error):
|
||||
self.adafruitHumidityCharacteristic = nil
|
||||
completion?(.failure(error))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func adafruitHumidityIsEnabled() -> Bool {
|
||||
return adafruitHumidityCharacteristic != nil && adafruitHumidityCharacteristic!.isNotifying
|
||||
}
|
||||
|
||||
func adafruitHumidityDisable() {
|
||||
// Clear all specific data
|
||||
defer {
|
||||
adafruitHumidityCharacteristic = nil
|
||||
}
|
||||
|
||||
// Disable notify
|
||||
guard let characteristic = adafruitHumidityCharacteristic, characteristic.isNotifying else { return }
|
||||
disableNotify(for: characteristic)
|
||||
}
|
||||
|
||||
func adafruitHumidityLastValue() -> Float? {
|
||||
guard let data = adafruitHumidityCharacteristic?.value else { return nil }
|
||||
return adafruitHumidityDataToFloat(data)
|
||||
}
|
||||
|
||||
// MARK: - Utils
|
||||
private func adafruitHumidityDataToFloat(_ data: Data) -> Float {
|
||||
return data.toFloatFrom32Bits()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
//
|
||||
// BlePeripheral+AdafruitLight.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 kAdafruitLightServiceUUID = CBUUID(string: "ADAF0300-C332-42A8-93BD-25E905756CB8")
|
||||
private static let kAdafruitLightCharacteristicUUID = CBUUID(string: "ADAF0301-C332-42A8-93BD-25E905756CB8")
|
||||
private static let kAdafruitLightVersion = 1
|
||||
|
||||
|
||||
// MARK: - Custom properties
|
||||
private struct CustomPropertiesKeys {
|
||||
static var adafruitLightCharacteristic: CBCharacteristic?
|
||||
}
|
||||
|
||||
private var adafruitLightCharacteristic: CBCharacteristic? {
|
||||
get {
|
||||
return objc_getAssociatedObject(self, &CustomPropertiesKeys.adafruitLightCharacteristic) as? CBCharacteristic
|
||||
}
|
||||
set {
|
||||
objc_setAssociatedObject(self, &CustomPropertiesKeys.adafruitLightCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
func adafruitLightEnable(responseHandler: @escaping(Result<(Float, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
||||
|
||||
self.adafruitServiceEnableIfVersion(version: BlePeripheral.kAdafruitLightVersion, serviceUuid: BlePeripheral.kAdafruitLightServiceUUID, mainCharacteristicUuid: BlePeripheral.kAdafruitLightCharacteristicUUID, timePeriod: BlePeripheral.kAdafruitSensorDefaultPeriod, responseHandler: { response in
|
||||
|
||||
switch response {
|
||||
case let .success((data, uuid)):
|
||||
let value = self.adafruitLightDataToFloat(data)
|
||||
responseHandler(.success((value, uuid)))
|
||||
case let .failure(error):
|
||||
responseHandler(.failure(error))
|
||||
}
|
||||
|
||||
}, completion: { result in
|
||||
switch result {
|
||||
case let .success(characteristic):
|
||||
self.adafruitLightCharacteristic = characteristic
|
||||
completion?(.success(()))
|
||||
|
||||
case let .failure(error):
|
||||
self.adafruitLightCharacteristic = nil
|
||||
completion?(.failure(error))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func adafruitLightIsEnabled() -> Bool {
|
||||
return adafruitLightCharacteristic != nil && adafruitLightCharacteristic!.isNotifying
|
||||
}
|
||||
|
||||
func adafruitLightDisable() {
|
||||
// Clear all specific data
|
||||
defer {
|
||||
adafruitLightCharacteristic = nil
|
||||
}
|
||||
|
||||
// Disable notify
|
||||
guard let characteristic = adafruitLightCharacteristic, characteristic.isNotifying else { return }
|
||||
disableNotify(for: characteristic)
|
||||
}
|
||||
|
||||
func adafruitLightLastValue() -> Float? {
|
||||
guard let data = adafruitLightCharacteristic?.value else { return nil }
|
||||
return adafruitLightDataToFloat(data)
|
||||
}
|
||||
|
||||
// MARK: - Utils
|
||||
private func adafruitLightDataToFloat(_ data: Data) -> Float {
|
||||
return data.toFloatFrom32Bits()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
//
|
||||
// BlePeripheral+AdafruitMagnetometer.swift
|
||||
// BluefruitPlayground
|
||||
//
|
||||
// Created by Antonio García on 06/03/2020.
|
||||
// Copyright © 2019 Adafruit. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
import CoreBluetooth
|
||||
|
||||
extension BlePeripheral {
|
||||
// Constants
|
||||
static let kAdafruitMagnetometerServiceUUID = CBUUID(string: "ADAF0500-C332-42A8-93BD-25E905756CB8")
|
||||
private static let kAdafruitMagnetometerCharacteristicUUID = CBUUID(string: "ADAF0501-C332-42A8-93BD-25E905756CB8")
|
||||
private static let kAdafruitMagnetometerVersion = 1
|
||||
|
||||
// Structs
|
||||
/// Values in microTesla (μT)
|
||||
struct MagnetometerValue {
|
||||
var x: Float
|
||||
var y: Float
|
||||
var z: Float
|
||||
}
|
||||
|
||||
// MARK: - Custom properties
|
||||
private struct CustomPropertiesKeys {
|
||||
static var adafruitMagnetometerCharacteristic: CBCharacteristic?
|
||||
}
|
||||
|
||||
private var adafruitMagnetometerCharacteristic: CBCharacteristic? {
|
||||
get {
|
||||
return objc_getAssociatedObject(self, &CustomPropertiesKeys.adafruitMagnetometerCharacteristic) as? CBCharacteristic
|
||||
}
|
||||
set {
|
||||
objc_setAssociatedObject(self, &CustomPropertiesKeys.adafruitMagnetometerCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
func adafruitMagnetometerEnable(responseHandler: @escaping(Result<(MagnetometerValue, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
||||
|
||||
self.adafruitServiceEnableIfVersion(version: BlePeripheral.kAdafruitMagnetometerVersion, serviceUuid: BlePeripheral.kAdafruitMagnetometerServiceUUID, mainCharacteristicUuid: BlePeripheral.kAdafruitMagnetometerCharacteristicUUID, timePeriod: BlePeripheral.kAdafruitSensorDefaultPeriod, responseHandler: { response in
|
||||
|
||||
switch response {
|
||||
case let .success((data, uuid)):
|
||||
if let value = self.adafruitMagnetometerDataToMagnetometerValue(data) {
|
||||
responseHandler(.success((value, uuid)))
|
||||
} else {
|
||||
responseHandler(.failure(PeripheralAdafruitError.invalidResponseData))
|
||||
}
|
||||
case let .failure(error):
|
||||
responseHandler(.failure(error))
|
||||
}
|
||||
|
||||
}, completion: { result in
|
||||
switch result {
|
||||
case let .success(characteristic):
|
||||
self.adafruitMagnetometerCharacteristic = characteristic
|
||||
completion?(.success(()))
|
||||
|
||||
case let .failure(error):
|
||||
self.adafruitMagnetometerCharacteristic = nil
|
||||
completion?(.failure(error))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func adafruitMagnetometerIsEnabled() -> Bool {
|
||||
return adafruitMagnetometerCharacteristic != nil && adafruitMagnetometerCharacteristic!.isNotifying
|
||||
}
|
||||
|
||||
func adafruitMagnetometerDisable() {
|
||||
// Clear all specific data
|
||||
defer {
|
||||
adafruitMagnetometerCharacteristic = nil
|
||||
}
|
||||
|
||||
// Disable notify
|
||||
guard let characteristic = adafruitMagnetometerCharacteristic, characteristic.isNotifying else { return }
|
||||
disableNotify(for: characteristic)
|
||||
}
|
||||
|
||||
func adafruitMagnetometerLastValue() -> MagnetometerValue? {
|
||||
guard let data = adafruitMagnetometerCharacteristic?.value else { return nil }
|
||||
return adafruitMagnetometerDataToMagnetometerValue(data)
|
||||
}
|
||||
|
||||
// MARK: - Utils
|
||||
private func adafruitMagnetometerDataToMagnetometerValue(_ data: Data) -> MagnetometerValue? {
|
||||
guard let bytes = adafruitDataToFloatArray(data) else { return nil }
|
||||
guard bytes.count >= 3 else { return nil }
|
||||
return MagnetometerValue(x: bytes[0], y: bytes[1], z: bytes[2])
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,187 @@
|
|||
//
|
||||
// BlePeripheral+AdafruitNeoPixels.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 kAdafruitNeoPixelsServiceNumberOfBitsPerPixel = 3
|
||||
private static let kAdafruitNeoPixelsVersion = 1
|
||||
|
||||
// Constants
|
||||
static let kAdafruitNeoPixelsServiceUUID = CBUUID(string: "ADAF0900-C332-42A8-93BD-25E905756CB8")
|
||||
private static let kAdafruitNeoPixelsDataCharacteristicUUID = CBUUID(string: "ADAF0903-C332-42A8-93BD-25E905756CB8")
|
||||
|
||||
// MARK: - Custom properties
|
||||
private struct CustomPropertiesKeys {
|
||||
static var adafruitNeoPixelsDataCharacteristic: CBCharacteristic?
|
||||
static var adafruitNeoPixelsDataValue: Data?
|
||||
}
|
||||
|
||||
private var adafruitNeoPixelsDataCharacteristic: CBCharacteristic? {
|
||||
get {
|
||||
return objc_getAssociatedObject(self, &CustomPropertiesKeys.adafruitNeoPixelsDataCharacteristic) as? CBCharacteristic
|
||||
}
|
||||
set {
|
||||
objc_setAssociatedObject(self, &CustomPropertiesKeys.adafruitNeoPixelsDataCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
}
|
||||
|
||||
private var adafruitNeoPixelsDataValue: Data {
|
||||
get {
|
||||
if let data = objc_getAssociatedObject(self, &CustomPropertiesKeys.adafruitNeoPixelsDataValue) as? Data {
|
||||
return data
|
||||
} else { // Initial value
|
||||
return Data(repeating: 0, count: adafruitNeoPixelsCount * BlePeripheral.kAdafruitNeoPixelsServiceNumberOfBitsPerPixel)
|
||||
}
|
||||
}
|
||||
set {
|
||||
objc_setAssociatedObject(self, &CustomPropertiesKeys.adafruitNeoPixelsDataValue, newValue, .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
func adafruitNeoPixelsEnable(completion: ((Result<Void, Error>) -> Void)?) {
|
||||
|
||||
self.adafruitServiceEnableIfVersion(version: BlePeripheral.kAdafruitNeoPixelsVersion, serviceUuid: BlePeripheral.kAdafruitNeoPixelsServiceUUID, mainCharacteristicUuid: BlePeripheral.kAdafruitNeoPixelsDataCharacteristicUUID) { result in
|
||||
switch result {
|
||||
case let .success(characteristic):
|
||||
self.adafruitNeoPixelsDataCharacteristic = characteristic
|
||||
completion?(.success(()))
|
||||
|
||||
case let .failure(error):
|
||||
self.adafruitNeoPixelsDataCharacteristic = nil
|
||||
completion?(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var adafruitNeoPixelsCount: Int {
|
||||
return self.adafruitManufacturerData()?.boardModel?.neoPixelsCount ?? 0
|
||||
}
|
||||
|
||||
func adafruitNeoPixelsIsEnabled() -> Bool {
|
||||
return adafruitNeoPixelsDataCharacteristic != nil
|
||||
}
|
||||
|
||||
func adafruitNeoPixelsDisable() {
|
||||
// Clear all specific data
|
||||
adafruitNeoPixelsDataCharacteristic = nil
|
||||
}
|
||||
|
||||
func adafruitNeoPixelSetAllPixelsColor(_ color: UIColor) {
|
||||
let colors = [UIColor](repeating: color, count: adafruitNeoPixelsCount)
|
||||
adafruitNeoPixelsWriteData(offset: 0, colors: colors)
|
||||
}
|
||||
|
||||
func adafruitNeoPixelSetPixelColor(index: Int, color: UIColor) {
|
||||
let offset = UInt16(index * BlePeripheral.kAdafruitNeoPixelsServiceNumberOfBitsPerPixel)
|
||||
adafruitNeoPixelsWriteData(offset: offset, colors: [color])
|
||||
}
|
||||
|
||||
func adafruitNeoPixelSetColor(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.kAdafruitNeoPixelsServiceNumberOfBitsPerPixel))
|
||||
adafruitNeoPixelsWriteData(offset: offset, pixelData: pixelData)
|
||||
}
|
||||
|
||||
// MARK: - Low level actions
|
||||
func adafruitNeoPixelsWriteData(offset: UInt16, colors: [UIColor]) {
|
||||
let pixelData = BlePeripheral.pixelDataFromColors(colors)
|
||||
adafruitNeoPixelsWriteData(offset: offset, pixelData: pixelData)
|
||||
}
|
||||
|
||||
func adafruitNeoPixelsWriteData(offset: UInt16, pixelData: Data) {
|
||||
guard let adafruitNeoPixelsDataCharacteristic = adafruitNeoPixelsDataCharacteristic 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: adafruitNeoPixelsDataCharacteristic, type: .withResponse) { [unowned self] error in
|
||||
guard error == nil else { DLog("Error adafruitNeoPixelsWriteData: \(error!)"); return }
|
||||
|
||||
self.adafruitNeoPixelsDataValue = 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.kAdafruitNeoPixelsServiceNumberOfBitsPerPixel
|
||||
DLog("adafruitNeoPixelsDataValue.count: \(adafruitNeoPixelsDataValue.count) ")
|
||||
if byteOffset < adafruitNeoPixelsDataValue.count {
|
||||
existingColorData = Data(adafruitNeoPixelsDataValue[byteOffset..<(byteOffset + BlePeripheral.kAdafruitNeoPixelsServiceNumberOfBitsPerPixel)])
|
||||
} else {
|
||||
existingColorData = Data(repeating: 0, count: BlePeripheral.kAdafruitNeoPixelsServiceNumberOfBitsPerPixel)
|
||||
}
|
||||
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]?
|
||||
|
||||
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.kAdafruitNeoPixelsServiceNumberOfBitsPerPixel)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
//
|
||||
// BlePeripheral+AdafruitQuaternion.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 kAdafruitQuaternionServiceUUID = CBUUID(string: "ADAF0D00-C332-42A8-93BD-25E905756CB8")
|
||||
private static let kAdafruitQuaternionCharacteristicUUID = CBUUID(string: "ADAF0D01-C332-42A8-93BD-25E905756CB8")
|
||||
private static let kAdafruitQuaternionCalibrationInCharacteristicUUID = CBUUID(string: "ADAFD002-C332-42A8-93BD-25E905756CB8")
|
||||
private static let kAdafruitQuaternionCalibrationOutCharacteristicUUID = CBUUID(string: "ADAFD003-C332-42A8-93BD-25E905756CB8")
|
||||
private static let kAdafruitQuaternionVersion = 1
|
||||
|
||||
// Structs
|
||||
struct QuaternionValue {
|
||||
var x: Float
|
||||
var y: Float
|
||||
var z: Float
|
||||
var w: Float
|
||||
}
|
||||
|
||||
// MARK: - Custom properties
|
||||
private struct CustomPropertiesKeys {
|
||||
static var adafruitQuaternionCharacteristic: CBCharacteristic?
|
||||
}
|
||||
|
||||
private var adafruitQuaternionCharacteristic: CBCharacteristic? {
|
||||
get {
|
||||
return objc_getAssociatedObject(self, &CustomPropertiesKeys.adafruitQuaternionCharacteristic) as? CBCharacteristic
|
||||
}
|
||||
set {
|
||||
objc_setAssociatedObject(self, &CustomPropertiesKeys.adafruitQuaternionCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
func adafruitQuaternionEnable(responseHandler: @escaping(Result<(QuaternionValue, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
||||
|
||||
self.adafruitServiceEnableIfVersion(version: BlePeripheral.kAdafruitQuaternionVersion, serviceUuid: BlePeripheral.kAdafruitQuaternionServiceUUID, mainCharacteristicUuid: BlePeripheral.kAdafruitQuaternionCharacteristicUUID, timePeriod: BlePeripheral.kAdafruitSensorDefaultPeriod, responseHandler: { response in
|
||||
|
||||
switch response {
|
||||
case let .success((data, uuid)):
|
||||
if let value = self.adafruitQuaternionDataToQuaternionValue(data) {
|
||||
responseHandler(.success((value, uuid)))
|
||||
} else {
|
||||
responseHandler(.failure(PeripheralAdafruitError.invalidResponseData))
|
||||
}
|
||||
case let .failure(error):
|
||||
responseHandler(.failure(error))
|
||||
}
|
||||
|
||||
}, completion: { result in
|
||||
switch result {
|
||||
case let .success(characteristic):
|
||||
self.adafruitQuaternionCharacteristic = characteristic
|
||||
completion?(.success(()))
|
||||
|
||||
case let .failure(error):
|
||||
self.adafruitQuaternionCharacteristic = nil
|
||||
completion?(.failure(error))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func adafruitQuaternionIsEnabled() -> Bool {
|
||||
return adafruitQuaternionCharacteristic != nil && adafruitQuaternionCharacteristic!.isNotifying
|
||||
}
|
||||
|
||||
func adafruitQuaternionDisable() {
|
||||
// Clear all specific data
|
||||
defer {
|
||||
adafruitQuaternionCharacteristic = nil
|
||||
}
|
||||
|
||||
// Disable notify
|
||||
guard let characteristic = adafruitQuaternionCharacteristic, characteristic.isNotifying else { return }
|
||||
disableNotify(for: characteristic)
|
||||
}
|
||||
|
||||
func adafruitQuaternionLastValue() -> QuaternionValue? {
|
||||
guard let data = adafruitQuaternionCharacteristic?.value else { return nil }
|
||||
return adafruitQuaternionDataToQuaternionValue(data)
|
||||
}
|
||||
|
||||
// MARK: - Utils
|
||||
private func adafruitQuaternionDataToQuaternionValue(_ data: Data) -> QuaternionValue? {
|
||||
|
||||
guard let bytes = adafruitDataToFloatArray(data) else { return nil }
|
||||
guard bytes.count >= 4 else { return nil }
|
||||
//return QuaternionValue(x: bytes[0], y: bytes[1], z: bytes[2], w: bytes[3])
|
||||
return QuaternionValue(x: bytes[1], y: bytes[2], z: bytes[3], w: bytes[0])
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
//
|
||||
// BlePeripheral+AdafruitSoundSensor.swift
|
||||
// BluefruitPlayground
|
||||
//
|
||||
// Created by Antonio García on 09/03/2020.
|
||||
// Copyright © 2019 Adafruit. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import CoreBluetooth
|
||||
|
||||
extension BlePeripheral {
|
||||
// Constants
|
||||
static let kAdafruitSoundSensorServiceUUID = CBUUID(string: "ADAF0B00-C332-42A8-93BD-25E905756CB8")
|
||||
private static let kAdafruitSoundSamplesCharacteristicUUID = CBUUID(string: "ADAF0B01-C332-42A8-93BD-25E905756CB8")
|
||||
private static let kAdafruitSoundNumberOfChannelsCharacteristicUUID = CBUUID(string: "ADAF0B02-C332-42A8-93BD-25E905756CB8")
|
||||
private static let kAdafruitSoundSensorVersion = 1
|
||||
|
||||
static let kAdafruitSoundSensorMaxAmplitude = 32768 // Int16 range is from -32768 to 32767
|
||||
|
||||
// MARK: - Custom properties
|
||||
private struct CustomPropertiesKeys {
|
||||
static var adafruitSoundCharacteristic: CBCharacteristic?
|
||||
static var adafruitSoundNumChannels: Int = 0
|
||||
}
|
||||
|
||||
private var adafruitSoundCharacteristic: CBCharacteristic? {
|
||||
get {
|
||||
return objc_getAssociatedObject(self, &CustomPropertiesKeys.adafruitSoundCharacteristic) as? CBCharacteristic
|
||||
}
|
||||
set {
|
||||
objc_setAssociatedObject(self, &CustomPropertiesKeys.adafruitSoundCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
}
|
||||
|
||||
private var adafruitSoundNumChannels: Int {
|
||||
get {
|
||||
return objc_getAssociatedObject(self, &CustomPropertiesKeys.adafruitSoundNumChannels) as? Int ?? 0
|
||||
}
|
||||
set {
|
||||
objc_setAssociatedObject(self, &CustomPropertiesKeys.adafruitSoundNumChannels, newValue, .OBJC_ASSOCIATION_ASSIGN)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
func adafruitSoundEnable(responseHandler: @escaping(Result<([Double], UUID), Error>) -> Void, completion: ((Result<Int, Error>) -> Void)?) {
|
||||
|
||||
self.adafruitServiceEnableIfVersion(version: BlePeripheral.kAdafruitSoundSensorVersion, serviceUuid: BlePeripheral.kAdafruitSoundSensorServiceUUID, mainCharacteristicUuid: BlePeripheral.kAdafruitSoundSamplesCharacteristicUUID, timePeriod: BlePeripheral.kAdafruitSensorDefaultPeriod, responseHandler: { [weak self] response in
|
||||
|
||||
guard self?.adafruitSoundNumChannels ?? 0 > 0 else { return } // Ignore received data until sound channels are defined
|
||||
// TODO: read sound channels BEFORE enabling notify
|
||||
|
||||
switch response {
|
||||
case let .success((data, uuid)):
|
||||
if let value = self?.adafruitSoundDataToAmplitudePerChannel(data) {
|
||||
responseHandler(.success((value, uuid)))
|
||||
}
|
||||
else {
|
||||
responseHandler(.failure(PeripheralAdafruitError.invalidResponseData))
|
||||
}
|
||||
|
||||
case let .failure(error):
|
||||
responseHandler(.failure(error))
|
||||
}
|
||||
|
||||
}, completion: { result in
|
||||
switch result {
|
||||
case let .success(characteristic):
|
||||
self.adafruitSoundCharacteristic = characteristic
|
||||
|
||||
// Read number of channels
|
||||
self.characteristic(uuid: BlePeripheral.kAdafruitSoundNumberOfChannelsCharacteristicUUID, serviceUuid: BlePeripheral.kAdafruitSoundSensorServiceUUID) { [weak self] (characteristic, error) in
|
||||
|
||||
guard error == nil, let characteristic = characteristic else {
|
||||
self?.adafruitSoundDisable() // Error, disable sound // TODO: dont enable until checks have been performed
|
||||
completion?(.failure(PeripheralAdafruitError.invalidCharacteristic))
|
||||
return
|
||||
}
|
||||
|
||||
self?.readCharacteristic(characteristic) { (result, error) in
|
||||
guard error == nil, let data = result as? Data, data.count >= 1 else {
|
||||
DLog("Error reading numChannels: \(error?.localizedDescription ?? "")")
|
||||
completion?(.failure(PeripheralAdafruitError.invalidResponseData))
|
||||
return
|
||||
}
|
||||
|
||||
let numChannels = Int(data[0]) // from 0 to 100
|
||||
self?.adafruitSoundNumChannels = numChannels
|
||||
completion?(.success(numChannels))
|
||||
}
|
||||
}
|
||||
|
||||
case let .failure(error):
|
||||
self.adafruitSoundCharacteristic = nil
|
||||
completion?(.failure(error))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func adafruitSoundIsEnabled() -> Bool {
|
||||
return adafruitSoundCharacteristic != nil && adafruitSoundCharacteristic!.isNotifying && adafruitSoundNumChannels > 0
|
||||
}
|
||||
|
||||
func adafruitSoundDisable() {
|
||||
// Clear all specific data
|
||||
defer {
|
||||
adafruitSoundCharacteristic = nil
|
||||
}
|
||||
|
||||
// Disable notify
|
||||
guard let characteristic = adafruitSoundCharacteristic, characteristic.isNotifying else { return }
|
||||
disableNotify(for: characteristic)
|
||||
}
|
||||
|
||||
func adafruitSoundLastAmplitudePerChannel() -> [Double]? { // Samples fo reach channel
|
||||
guard let data = adafruitSoundCharacteristic?.value else { return nil }
|
||||
return adafruitSoundDataToAmplitudePerChannel(data)
|
||||
}
|
||||
|
||||
// MARK: - Utils
|
||||
/**
|
||||
Convert raw data into the amplitude for each channel
|
||||
- returns: array with amplitude for each channel measured in decibel relative to full scale (dBFS)
|
||||
*/
|
||||
private func adafruitSoundDataToAmplitudePerChannel(_ data: Data) -> [Double]? {
|
||||
guard let samples = adafruitDataToInt16Array(data) else { return nil }
|
||||
let numChannels = adafruitSoundNumChannels
|
||||
guard numChannels > 0, samples.count >= numChannels else { return nil }
|
||||
|
||||
var samplesSumPerChannel = [Double](repeating: 0, count: numChannels)
|
||||
for (index, sample) in samples.enumerated() {
|
||||
let channelIndex = index % numChannels
|
||||
samplesSumPerChannel[channelIndex] += abs(Double(sample))
|
||||
}
|
||||
|
||||
let samplesPerChannel = samples.count / numChannels
|
||||
var amplitudePerChannel = [Double](repeating: 0, count: numChannels)
|
||||
for (index, samplesSum) in samplesSumPerChannel.enumerated() {
|
||||
let samplesAvg = samplesSum / Double(samplesPerChannel)
|
||||
|
||||
// Calculate amplitude
|
||||
// based on: https://devzone.nordicsemi.com/f/nordic-q-a/28248/get-amplitude-db-from-pdm/111560#111560
|
||||
let amplitude = 20 * log10(abs(samplesAvg) / Double(BlePeripheral.kAdafruitSoundSensorMaxAmplitude))
|
||||
|
||||
// Note:
|
||||
// The base 10 log of -1 is NaN.
|
||||
// The base 10 log of 0 is -Infinity.
|
||||
|
||||
amplitudePerChannel[index] = amplitude
|
||||
}
|
||||
|
||||
return amplitudePerChannel
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
//
|
||||
// AdafruitTemperature.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 kAdafruitTemperatureServiceUUID = CBUUID(string: "ADAF0100-C332-42A8-93BD-25E905756CB8")
|
||||
private static let kAdafruitTemperatureCharacteristicUUID = CBUUID(string: "ADAF0101-C332-42A8-93BD-25E905756CB8")
|
||||
private static let kAdafruitTemperatureVersion = 1
|
||||
|
||||
// MARK: - Custom properties
|
||||
private struct CustomPropertiesKeys {
|
||||
static var adafruitTemperatureCharacteristic: CBCharacteristic?
|
||||
}
|
||||
|
||||
private var adafruitTemperatureCharacteristic: CBCharacteristic? {
|
||||
get {
|
||||
return objc_getAssociatedObject(self, &CustomPropertiesKeys.adafruitTemperatureCharacteristic) as? CBCharacteristic
|
||||
}
|
||||
set {
|
||||
objc_setAssociatedObject(self, &CustomPropertiesKeys.adafruitTemperatureCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
func adafruitTemperatureEnable(responseHandler: @escaping(Result<(Float, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
||||
|
||||
self.adafruitServiceEnableIfVersion(version: BlePeripheral.kAdafruitTemperatureVersion, serviceUuid: BlePeripheral.kAdafruitTemperatureServiceUUID, mainCharacteristicUuid: BlePeripheral.kAdafruitTemperatureCharacteristicUUID, timePeriod: BlePeripheral.kAdafruitSensorDefaultPeriod, responseHandler: { response in
|
||||
|
||||
switch response {
|
||||
case let .success((data, uuid)):
|
||||
let temperature = self.adafruitTemperatureDataToFloat(data)
|
||||
responseHandler(.success((temperature, uuid)))
|
||||
case let .failure(error):
|
||||
responseHandler(.failure(error))
|
||||
}
|
||||
|
||||
}, completion: { result in
|
||||
switch result {
|
||||
case let .success(characteristic):
|
||||
self.adafruitTemperatureCharacteristic = characteristic
|
||||
completion?(.success(()))
|
||||
|
||||
case let .failure(error):
|
||||
self.adafruitTemperatureCharacteristic = nil
|
||||
completion?(.failure(error))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func adafruitTemperatureIsEnabled() -> Bool {
|
||||
return adafruitTemperatureCharacteristic != nil && adafruitTemperatureCharacteristic!.isNotifying
|
||||
}
|
||||
|
||||
func adafruitTemperatureDisable() {
|
||||
// Clear all specific data
|
||||
defer {
|
||||
adafruitTemperatureCharacteristic = nil
|
||||
}
|
||||
|
||||
// Disable notify
|
||||
guard let characteristic = adafruitTemperatureCharacteristic, characteristic.isNotifying else { return }
|
||||
disableNotify(for: characteristic)
|
||||
}
|
||||
|
||||
func adafruitTemperatureLastValue() -> Float? {
|
||||
guard let data = adafruitTemperatureCharacteristic?.value else { return nil }
|
||||
return adafruitTemperatureDataToFloat(data)
|
||||
}
|
||||
|
||||
// MARK: - Utils
|
||||
private func adafruitTemperatureDataToFloat(_ data: Data) -> Float {
|
||||
return data.toFloatFrom32Bits()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
//
|
||||
// BlePeripheral+AdafruitToneGenerator.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 kAdafruitToneGeneratorServiceUUID = CBUUID(string: "ADAF0C00-C332-42A8-93BD-25E905756CB8")
|
||||
private static let kAdafruitToneGeneratorCharacteristicUUID = CBUUID(string: "ADAF0C01-C332-42A8-93BD-25E905756CB8")
|
||||
private static let kAdafruitToneGeneratorVersion = 1
|
||||
|
||||
// MARK: - Custom properties
|
||||
private struct CustomPropertiesKeys {
|
||||
static var adafruitToneGeneratorCharacteristic: CBCharacteristic?
|
||||
}
|
||||
|
||||
private var adafruitToneGeneratorCharacteristic: CBCharacteristic? {
|
||||
get {
|
||||
return objc_getAssociatedObject(self, &CustomPropertiesKeys.adafruitToneGeneratorCharacteristic) as? CBCharacteristic
|
||||
}
|
||||
set {
|
||||
objc_setAssociatedObject(self, &CustomPropertiesKeys.adafruitToneGeneratorCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
func adafruitToneGeneratorEnable(completion: ((Result<Void, Error>) -> Void)?) {
|
||||
|
||||
self.adafruitServiceEnableIfVersion(version: BlePeripheral.kAdafruitToneGeneratorVersion, serviceUuid: BlePeripheral.kAdafruitToneGeneratorServiceUUID, mainCharacteristicUuid: BlePeripheral.kAdafruitToneGeneratorCharacteristicUUID) { result in
|
||||
switch result {
|
||||
case let .success(characteristic):
|
||||
self.adafruitToneGeneratorCharacteristic = characteristic
|
||||
completion?(.success(()))
|
||||
|
||||
case let .failure(error):
|
||||
self.adafruitToneGeneratorCharacteristic = nil
|
||||
completion?(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func adafruitToneGeneratorIsEnabled() -> Bool {
|
||||
return adafruitToneGeneratorCharacteristic != nil
|
||||
}
|
||||
|
||||
func adafruitToneGeneratorDisable() {
|
||||
// Clear all specific data
|
||||
adafruitToneGeneratorCharacteristic = nil
|
||||
}
|
||||
|
||||
func adafruitToneGeneratorStartPlaying(frequency: UInt16, duration: UInt32 = 0) { // Duration 0 means non-stop
|
||||
guard let adafruitToneGeneratorCharacteristic = adafruitToneGeneratorCharacteristic else { return }
|
||||
|
||||
let data = frequency.littleEndian.data + duration.littleEndian.data
|
||||
self.write(data: data, for: adafruitToneGeneratorCharacteristic, type: .withResponse)
|
||||
//DLog("tone: \(frequency)")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
//
|
||||
// BlePeripheral+ManufacturerAdafruit.swift
|
||||
// BluefruitPlayground
|
||||
//
|
||||
// Created by Antonio García on 10/12/2019.
|
||||
// Copyright © 2019 Adafruit. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import CoreBluetooth
|
||||
|
||||
extension BlePeripheral {
|
||||
// Constants
|
||||
internal static let kManufacturerAdafruitIdentifier: [UInt8] = [0x22, 0x08]
|
||||
|
||||
// MARK: - Check Manufacturer
|
||||
func isManufacturerAdafruit() -> Bool {
|
||||
guard let manufacturerIdentifier = advertisement.manufacturerIdentifier else { return false }
|
||||
|
||||
let manufacturerIdentifierBytes = [UInt8](manufacturerIdentifier)
|
||||
//DLog("\(name) manufacturer: \(advertisement.manufacturerString)")
|
||||
return manufacturerIdentifierBytes == BlePeripheral.kManufacturerAdafruitIdentifier
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Adafruit Specific Data
|
||||
struct AdafruitManufacturerData {
|
||||
// Types
|
||||
enum BoardModel: CaseIterable {
|
||||
case circuitPlaygroundBluefruit
|
||||
case clue_nRF52840
|
||||
case feather_nRF52840_express
|
||||
case feather_nRF52832
|
||||
|
||||
var identifier: [[UInt8]] { // Board identifiers used on the advertisement packet (USB PID)
|
||||
switch self {
|
||||
case .circuitPlaygroundBluefruit: return [[0x45, 0x80], [0x46, 0x80]]
|
||||
case .clue_nRF52840: return [[0x71, 0x80], [0x72, 0x80]]
|
||||
case .feather_nRF52840_express: return [[0x29, 0x80], [0x2A, 0x80]]
|
||||
case .feather_nRF52832: return [[0x60, 0xEA]]
|
||||
}
|
||||
}
|
||||
|
||||
var neoPixelsCount: Int {
|
||||
switch self {
|
||||
case .circuitPlaygroundBluefruit: return 10
|
||||
case .clue_nRF52840: return 1
|
||||
case .feather_nRF52840_express: return 0
|
||||
case .feather_nRF52832: return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Data
|
||||
var color: UIColor?
|
||||
var boardModel: BoardModel?
|
||||
|
||||
// Utils
|
||||
static func board(withBoardTypeData data: Data) -> BoardModel? {
|
||||
let bytes = [UInt8](data)
|
||||
|
||||
let board = BoardModel.allCases.first(where: {
|
||||
$0.identifier.contains(bytes)
|
||||
})
|
||||
|
||||
return board
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func adafruitManufacturerData() -> AdafruitManufacturerData? {
|
||||
guard let manufacturerData = advertisement.manufacturerData else { return nil }
|
||||
guard manufacturerData.count > 2 else { return nil } // It should have fields beyond the manufacturer identifier
|
||||
|
||||
var manufacturerFieldsData = Data(manufacturerData.dropFirst(2)) // Remove manufacturer identifier
|
||||
|
||||
var adafruitManufacturerData = AdafruitManufacturerData()
|
||||
|
||||
// Parse fields
|
||||
let kHeaderLength = 1 + 2 // 1 byte len + 2 bytes key
|
||||
while manufacturerFieldsData.count >= kHeaderLength {
|
||||
// Parse current field
|
||||
guard let fieldKey = Int16(data: manufacturerFieldsData[1...2]) else { return nil }
|
||||
let fieldDataLenght = Int(manufacturerFieldsData[0]) - kHeaderLength // don't count header
|
||||
let fieldData: Data
|
||||
if manufacturerFieldsData.count >= kHeaderLength + fieldDataLenght {
|
||||
fieldData = Data(manufacturerFieldsData[kHeaderLength...])
|
||||
} else {
|
||||
fieldData = Data()
|
||||
}
|
||||
|
||||
// Decode field
|
||||
switch fieldKey {
|
||||
case 0 where fieldData.count >= 3: // Color
|
||||
let r = fieldData[0]
|
||||
let g = fieldData[1]
|
||||
let b = fieldData[2]
|
||||
adafruitManufacturerData.color = UIColor(red: CGFloat(r)/255, green: CGFloat(g)/255, blue: CGFloat(b)/255, alpha: 1)
|
||||
|
||||
case 1 where fieldData.count >= 2: // Board type
|
||||
let boardTypeData = fieldData[0..<2]
|
||||
|
||||
if let board = AdafruitManufacturerData.board(withBoardTypeData: boardTypeData) {
|
||||
adafruitManufacturerData.boardModel = board
|
||||
}
|
||||
else {
|
||||
DLog("Warning: unknown board type found: \([UInt8](boardTypeData))")
|
||||
}
|
||||
|
||||
default:
|
||||
DLog("Error processing manufacturer data with key: \(fieldKey) len: \(fieldData.count) expectedLen: \(fieldDataLenght)")
|
||||
break
|
||||
}
|
||||
|
||||
// Remove processed field
|
||||
manufacturerFieldsData = Data(manufacturerFieldsData.dropFirst(3 + fieldDataLenght))
|
||||
}
|
||||
|
||||
return adafruitManufacturerData
|
||||
}
|
||||
}
|
||||
|
|
@ -8,43 +8,47 @@
|
|||
|
||||
import Foundation
|
||||
import CoreBluetooth
|
||||
import QuartzCore
|
||||
|
||||
#if COMMANDLINE
|
||||
#else
|
||||
import MSWeakTimer
|
||||
#endif
|
||||
|
||||
class BleManager: NSObject {
|
||||
public class BleManager: NSObject {
|
||||
// Configuration
|
||||
private static let kStopScanningWhenConnectingToPeripheral = false
|
||||
private static let kAlwaysAllowDuplicateKeys = true
|
||||
|
||||
// Singleton
|
||||
static let shared = BleManager()
|
||||
public static let shared = BleManager()
|
||||
|
||||
// Ble
|
||||
var centralManager: CBCentralManager?
|
||||
private var centralManagerPoweredOnSemaphore = DispatchSemaphore(value: 1)
|
||||
|
||||
// Scanning
|
||||
var isScanning = false
|
||||
public var isScanning: Bool {
|
||||
return scanningStartTime != nil
|
||||
}
|
||||
public var scanningElapsedTime: TimeInterval? {
|
||||
guard let scanningStartTime = scanningStartTime else { return nil }
|
||||
return CACurrentMediaTime() - scanningStartTime
|
||||
}
|
||||
private var isScanningWaitingToStart = false
|
||||
internal var scanningStartTime: TimeInterval? // Time when the scanning started. nil if stopped
|
||||
private var scanningServicesFilter: [CBUUID]?
|
||||
internal var peripheralsFound = [UUID: BlePeripheral]()
|
||||
private var peripheralsFoundLock = NSLock()
|
||||
private var peripheralsFoundFirstTime = [UUID: Date]() // Date that the perihperal was discovered for the first time. Useful for sorting
|
||||
internal var peripheralsFoundLock = NSLock()
|
||||
|
||||
// Connecting
|
||||
private var connectionTimeoutTimers = [UUID: MSWeakTimer]()
|
||||
private var connectionTimeoutTimers = [UUID: Foundation.Timer]()
|
||||
private var autoreconnectOnDisconnection = Set<UUID>() // List of peripheral IDs to automatically reconnect if disconnected
|
||||
|
||||
// Notifications
|
||||
enum NotificationUserInfoKey: String {
|
||||
public enum NotificationUserInfoKey: String {
|
||||
case uuid = "uuid"
|
||||
case error = "error"
|
||||
}
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
|
||||
centralManagerPoweredOnSemaphore.wait()
|
||||
centralManager = CBCentralManager(delegate: self, queue: DispatchQueue.global(qos: .background), options: [:])
|
||||
// centralManager = CBCentralManager(delegate: self, queue: DispatchQueue.main, options: [:])
|
||||
}
|
||||
|
|
@ -52,20 +56,16 @@ class BleManager: NSObject {
|
|||
deinit {
|
||||
scanningServicesFilter?.removeAll()
|
||||
peripheralsFound.removeAll()
|
||||
peripheralsFoundFirstTime.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()
|
||||
|
||||
|
|
@ -94,12 +94,9 @@ class BleManager: NSObject {
|
|||
startScan()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Scan
|
||||
func startScan(withServices services: [CBUUID]? = nil) {
|
||||
centralManagerPoweredOnSemaphore.wait()
|
||||
centralManagerPoweredOnSemaphore.signal()
|
||||
|
||||
// MARK: - Scan
|
||||
public func startScan(withServices services: [CBUUID]? = nil) {
|
||||
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")
|
||||
|
|
@ -114,7 +111,7 @@ class BleManager: NSObject {
|
|||
}
|
||||
|
||||
// DLog("start scan")
|
||||
isScanning = true
|
||||
scanningStartTime = CACurrentMediaTime()
|
||||
NotificationCenter.default.post(name: .didStartScanning, object: nil)
|
||||
|
||||
let options = BleManager.kAlwaysAllowDuplicateKeys ? [CBCentralManagerScanOptionAllowDuplicatesKey: true] : nil
|
||||
|
|
@ -122,32 +119,55 @@ class BleManager: NSObject {
|
|||
isScanningWaitingToStart = false
|
||||
}
|
||||
|
||||
func stopScan() {
|
||||
public func stopScan() {
|
||||
// DLog("stop scan")
|
||||
centralManager?.stopScan()
|
||||
isScanning = false
|
||||
scanningStartTime = nil
|
||||
isScanningWaitingToStart = false
|
||||
NotificationCenter.default.post(name: .didStopScanning, object: nil)
|
||||
}
|
||||
|
||||
func peripherals() -> [BlePeripheral] {
|
||||
|
||||
public func numPeripherals() -> Int {
|
||||
return peripheralsFound.count
|
||||
}
|
||||
|
||||
public func peripherals() -> [BlePeripheral] {
|
||||
peripheralsFoundLock.lock(); defer { peripheralsFoundLock.unlock() }
|
||||
return Array(peripheralsFound.values)
|
||||
}
|
||||
|
||||
func connectedPeripherals() -> [BlePeripheral] {
|
||||
public func peripheralsSortedByFirstDiscovery() -> [BlePeripheral] {
|
||||
let now = Date()
|
||||
var peripheralsList = peripherals()
|
||||
peripheralsList.sort { (p0, p1) -> Bool in
|
||||
peripheralsFoundFirstTime[p0.identifier] ?? now < peripheralsFoundFirstTime[p1.identifier] ?? now
|
||||
}
|
||||
|
||||
return peripheralsList
|
||||
}
|
||||
|
||||
public func peripheralsSortedByRSSI() -> [BlePeripheral] {
|
||||
var peripheralsList = peripherals()
|
||||
peripheralsList.sort { (p0, p1) -> Bool in
|
||||
return (p0.rssi ?? -127) > (p1.rssi ?? -127)
|
||||
}
|
||||
|
||||
return peripheralsList
|
||||
}
|
||||
|
||||
public func connectedPeripherals() -> [BlePeripheral] {
|
||||
return peripherals().filter {$0.state == .connected}
|
||||
}
|
||||
|
||||
func connectingPeripherals() -> [BlePeripheral] {
|
||||
public func connectingPeripherals() -> [BlePeripheral] {
|
||||
return peripherals().filter {$0.state == .connecting}
|
||||
}
|
||||
|
||||
func connectedOrConnectingPeripherals() -> [BlePeripheral] {
|
||||
|
||||
public func connectedOrConnectingPeripherals() -> [BlePeripheral] {
|
||||
return peripherals().filter {$0.state == .connected || $0.state == .connecting}
|
||||
}
|
||||
|
||||
func refreshPeripherals() {
|
||||
public func refreshPeripherals() {
|
||||
stopScan()
|
||||
|
||||
peripheralsFoundLock.lock()
|
||||
|
|
@ -155,6 +175,7 @@ class BleManager: NSObject {
|
|||
for (identifier, peripheral) in peripheralsFound {
|
||||
if peripheral.state != .connected && peripheral.state != .connecting {
|
||||
peripheralsFound.removeValue(forKey: identifier)
|
||||
peripheralsFoundFirstTime.removeValue(forKey: identifier)
|
||||
}
|
||||
}
|
||||
peripheralsFoundLock.unlock()
|
||||
|
|
@ -165,12 +186,14 @@ class BleManager: NSObject {
|
|||
}
|
||||
|
||||
// MARK: - Connection Management
|
||||
func connect(to peripheral: BlePeripheral, timeout: TimeInterval? = nil, shouldNotifyOnConnection: Bool = false, shouldNotifyOnDisconnection: Bool = false, shouldNotifyOnNotification: Bool = false) {
|
||||
public 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
|
||||
guard centralManager?.state == .poweredOn else {
|
||||
DLog("connect failed because central manager is not ready")
|
||||
return
|
||||
}
|
||||
|
||||
// Stop scanning when connecting to a peripheral
|
||||
if BleManager.kStopScanningWhenConnectingToPeripheral {
|
||||
stopScan()
|
||||
}
|
||||
|
|
@ -189,14 +212,14 @@ class BleManager: NSObject {
|
|||
#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))
|
||||
self.connectionTimeoutTimers[peripheral.identifier] = Timer.scheduledTimer(timeInterval: timeout, target: self, selector: #selector(self.connectionTimeoutFired), userInfo: peripheral.identifier, repeats: false)
|
||||
}
|
||||
centralManager?.connect(peripheral.peripheral, options: options)
|
||||
}
|
||||
|
||||
@objc private func connectionTimeoutFired(timer: MSWeakTimer) {
|
||||
let peripheralIdentifier = timer.userInfo() as! UUID
|
||||
DLog("connection timeout fired: \(peripheralIdentifier)")
|
||||
@objc private func connectionTimeoutFired(timer: Foundation.Timer) {
|
||||
let peripheralIdentifier = timer.userInfo as! UUID
|
||||
DLog("connection timeout for: \(peripheralIdentifier)")
|
||||
connectionTimeoutTimers[peripheralIdentifier] = nil
|
||||
|
||||
NotificationCenter.default.post(name: .willDisconnectFromPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheralIdentifier])
|
||||
|
|
@ -204,37 +227,57 @@ class BleManager: NSObject {
|
|||
if let blePeripheral = peripheralsFound[peripheralIdentifier] {
|
||||
centralManager?.cancelPeripheralConnection(blePeripheral.peripheral)
|
||||
} else {
|
||||
DLog("simulate disconnection")
|
||||
//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) {
|
||||
public func disconnect(from peripheral: BlePeripheral, waitForQueuedCommands: Bool = false) {
|
||||
guard let centralManager = centralManager else { return}
|
||||
|
||||
DLog("disconnect")
|
||||
DLog("disconnect: \(peripheral.identifier)")
|
||||
autoreconnectOnDisconnection.remove(peripheral.identifier) // Disable autoreconnection because user initiated the disconection
|
||||
|
||||
NotificationCenter.default.post(name: .willDisconnectFromPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
|
||||
centralManager?.cancelPeripheralConnection(peripheral.peripheral)
|
||||
|
||||
if waitForQueuedCommands {
|
||||
// Send the disconnection to the command queue, so all the previous command are executed before disconnecting
|
||||
peripheral.disconnect(centralManager: centralManager)
|
||||
} else {
|
||||
centralManager.cancelPeripheralConnection(peripheral.peripheral)
|
||||
}
|
||||
}
|
||||
|
||||
func discoverConnectedPeripherals(services: [CBUUID]) {
|
||||
guard let centralManager = centralManager else { return}
|
||||
|
||||
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
|
||||
let peripheralsWithServices = centralManager.retrieveConnectedPeripherals(withServices: services)
|
||||
if !peripheralsWithServices.isEmpty {
|
||||
let alreadyConnectingOrConnectedPeripheralsIds = BleManager.shared.connectedOrConnectingPeripherals().map{$0.identifier}
|
||||
for peripheral in peripheralsWithServices {
|
||||
if !alreadyConnectingOrConnectedPeripheralsIds.contains(peripheral.identifier) {
|
||||
DLog("Discovered peripheral with known service: \(peripheral.identifier)")
|
||||
let advertisementData = [CBAdvertisementDataServiceUUIDsKey: services]
|
||||
discovered(peripheral: peripheral, advertisementData: advertisementData )
|
||||
}
|
||||
}
|
||||
} 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)
|
||||
}
|
||||
}
|
||||
func reconnecToPeripherals(peripheralsData: [(identifier: UUID, advertisementData: [String: Any])], withServices services: [CBUUID], timeout: Double? = nil) -> Bool {
|
||||
|
||||
guard let centralManager = centralManager else { return false }
|
||||
var reconnecting = false
|
||||
|
||||
// Reconnect to a known identifier
|
||||
let identifiers = peripheralsData.map({$0.identifier})
|
||||
|
||||
if !identifiers.isEmpty {
|
||||
let peripheralsWithIdentifiers = centralManager.retrievePeripherals(withIdentifiers: identifiers)
|
||||
for peripheral in peripheralsWithIdentifiers {
|
||||
if let peripheralData = peripheralsData.first(where: {$0.identifier == peripheral.identifier}) {
|
||||
DLog("Try to connect to known peripheral: \(peripheral.identifier)")
|
||||
discovered(peripheral: peripheral, advertisementData: peripheralData.advertisementData)
|
||||
if let blePeripheral = peripheralsFound[peripheral.identifier] {
|
||||
connect(to: blePeripheral, timeout: timeout)
|
||||
reconnecting = true
|
||||
|
|
@ -242,17 +285,35 @@ class BleManager: NSObject {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Reconnect even if no identifier was saved if we are already connected to a device with the expected services
|
||||
let peripheralsWithServices = centralManager.retrieveConnectedPeripherals(withServices: services)
|
||||
if !peripheralsWithServices.isEmpty {
|
||||
let alreadyConnectingOrConnectedPeripheralsIds = BleManager.shared.connectedOrConnectingPeripherals().map{$0.identifier}
|
||||
for peripheral in peripheralsWithServices {
|
||||
if !alreadyConnectingOrConnectedPeripheralsIds.contains(peripheral.identifier) {
|
||||
if let peripheralData = peripheralsData.first(where: {$0.identifier == peripheral.identifier}) {
|
||||
DLog("Connect to peripheral with known service: \(peripheral.identifier)")
|
||||
discovered(peripheral: peripheral, advertisementData: peripheralData.advertisementData )
|
||||
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 )
|
||||
if let rssi = rssi, rssi != BlePeripheral.kUndefinedRssiValue { // only update rssi value if is defined ( 127 means undefined )
|
||||
existingPeripheral.rssi = rssi
|
||||
}
|
||||
|
||||
|
|
@ -265,30 +326,35 @@ class BleManager: NSObject {
|
|||
} else { // New peripheral found
|
||||
let blePeripheral = BlePeripheral(peripheral: peripheral, advertisementData: advertisementData, rssi: rssi)
|
||||
peripheralsFound[peripheral.identifier] = blePeripheral
|
||||
peripheralsFoundFirstTime[peripheral.identifier] = Date()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Notifications
|
||||
func peripheral(from notification: Notification) -> BlePeripheral? {
|
||||
guard let uuid = notification.userInfo?[NotificationUserInfoKey.uuid.rawValue] as? UUID else { return nil }
|
||||
public func peripheralUUID(from notification: Notification) -> UUID? {
|
||||
return notification.userInfo?[NotificationUserInfoKey.uuid.rawValue] as? UUID
|
||||
}
|
||||
|
||||
public func peripheral(from notification: Notification) -> BlePeripheral? {
|
||||
guard let uuid = peripheralUUID(from: notification) else { return nil }
|
||||
|
||||
return peripheral(with: uuid)
|
||||
}
|
||||
|
||||
func peripheral(with uuid: UUID) -> BlePeripheral? {
|
||||
public func error(from notification: Notification) -> Error? {
|
||||
return notification.userInfo?[NotificationUserInfoKey.error.rawValue] as? Error
|
||||
}
|
||||
|
||||
public func peripheral(with uuid: UUID) -> BlePeripheral? {
|
||||
return peripheralsFound[uuid]
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - CBCentralManagerDelegate
|
||||
extension BleManager: CBCentralManagerDelegate {
|
||||
func centralManagerDidUpdateState(_ central: CBCentralManager) {
|
||||
|
||||
public 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 {
|
||||
|
|
@ -299,71 +365,107 @@ extension BleManager: CBCentralManagerDelegate {
|
|||
if isScanning {
|
||||
isScanningWaitingToStart = true
|
||||
}
|
||||
isScanning = false
|
||||
scanningStartTime = nil
|
||||
|
||||
// Remove all peripherals found (Important because the BlePeripheral queues could contain old commands that were processing when the bluetooth state changed)
|
||||
peripheralsFound.removeAll()
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
NotificationCenter.default.post(name: .didUpdateBleState, object: nil)
|
||||
}
|
||||
|
||||
NotificationCenter.default.post(name: .didUpdateBleState, object: nil)
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) {
|
||||
func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) {
|
||||
|
||||
}*/
|
||||
|
||||
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
|
||||
public 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)")
|
||||
public func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
|
||||
DLog("didConnect: \(peripheral.name ?? peripheral.identifier.uuidString)")
|
||||
|
||||
// Remove connection timeout if exists
|
||||
if let timer = connectionTimeoutTimers[peripheral.identifier] {
|
||||
timer.invalidate()
|
||||
connectionTimeoutTimers[peripheral.identifier] = nil
|
||||
}
|
||||
|
||||
|
||||
// Set reconnection flag
|
||||
autoreconnectOnDisconnection.insert(peripheral.identifier)
|
||||
|
||||
// Send notification
|
||||
NotificationCenter.default.post(name: .didConnectToPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
|
||||
DispatchQueue.main.async {
|
||||
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")
|
||||
|
||||
|
||||
public func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
|
||||
DLog("didFailToConnect: \(peripheral.name ?? peripheral.identifier.uuidString). \(String(describing: error))")
|
||||
|
||||
// Clean
|
||||
peripheralsFound[peripheral.identifier]?.reset()
|
||||
|
||||
|
||||
// Notify
|
||||
NotificationCenter.default.post(name: .didDisconnectFromPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
|
||||
DispatchQueue.main.async {
|
||||
NotificationCenter.default.post(name: .didDisconnectFromPeripheral, object: nil, userInfo: [
|
||||
NotificationUserInfoKey.uuid.rawValue: peripheral.identifier,
|
||||
NotificationUserInfoKey.error.rawValue: error as Any
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
public func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
|
||||
|
||||
let peripheralIdentifier = peripheral.identifier
|
||||
DLog("didDisconnectPeripheral: \(peripheral.name ?? peripheralIdentifier.uuidString)")
|
||||
|
||||
// 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()
|
||||
// Clean
|
||||
peripheralsFound[peripheralIdentifier]?.reset()
|
||||
|
||||
DispatchQueue.main.async {
|
||||
// Try to reconnect automatically
|
||||
if self.autoreconnectOnDisconnection.contains(peripheralIdentifier),
|
||||
let blePeripheral = self.peripheralsFound[peripheralIdentifier] {
|
||||
self.autoreconnectOnDisconnection.remove(peripheral.identifier)
|
||||
|
||||
DLog("Trying to reconnect to peripheral: \(peripheral.name ?? peripheralIdentifier.uuidString)")
|
||||
NotificationCenter.default.post(name: .willReconnectToPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
|
||||
|
||||
self.connect(to: blePeripheral)
|
||||
}
|
||||
else {
|
||||
// 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)
|
||||
self.peripheralsFoundLock.lock()
|
||||
self.peripheralsFound.removeValue(forKey: peripheral.identifier)
|
||||
self.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")
|
||||
public static let didUpdateBleState = Notification.Name(kPrefix+".didUpdateBleState")
|
||||
public static let didStartScanning = Notification.Name(kPrefix+".didStartScanning")
|
||||
public static let didStopScanning = Notification.Name(kPrefix+".didStopScanning")
|
||||
public static let didDiscoverPeripheral = Notification.Name(kPrefix+".didDiscoverPeripheral")
|
||||
public static let didUnDiscoverPeripheral = Notification.Name(kPrefix+".didUnDiscoverPeripheral")
|
||||
public static let willConnectToPeripheral = Notification.Name(kPrefix+".willConnectToPeripheral")
|
||||
public static let didConnectToPeripheral = Notification.Name(kPrefix+".didConnectToPeripheral")
|
||||
public static let willDisconnectFromPeripheral = Notification.Name(kPrefix+".willDisconnectFromPeripheral")
|
||||
public static let didDisconnectFromPeripheral = Notification.Name(kPrefix+".didDisconnectFromPeripheral")
|
||||
public static let willReconnectToPeripheral = Notification.Name(kPrefix+".willReconnectToPeripheral")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,38 +19,38 @@ extension BlePeripheral {
|
|||
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)
|
||||
})
|
||||
|
|
@ -60,16 +60,16 @@ extension BlePeripheral {
|
|||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,10 +72,13 @@ extension BlePeripheral {
|
|||
}
|
||||
|
||||
// MARK: - Initialization
|
||||
func uartEnable(uartRxHandler: ((Data?, UUID, Error?) -> Void)?, completion: ((Error?) -> Void)?) {
|
||||
func uartEnable(uartServiceUuid: CBUUID = BlePeripheral.kUartServiceUUID,
|
||||
txCharacteristicUuid: CBUUID = BlePeripheral.kUartTxCharacteristicUUID,
|
||||
rxCharacteristicUuid: CBUUID = BlePeripheral.kUartRxCharacteristicUUID,
|
||||
uartRxHandler: ((Data?, UUID, Error?) -> Void)?, completion: ((Error?) -> Void)?) {
|
||||
|
||||
// Get uart communications characteristic
|
||||
characteristic(uuid: BlePeripheral.kUartTxCharacteristicUUID, serviceUuid: BlePeripheral.kUartServiceUUID) { [unowned self] (characteristic, error) in
|
||||
characteristic(uuid: txCharacteristicUuid, serviceUuid: uartServiceUuid) { [unowned self] (characteristic, error) in
|
||||
guard let characteristic = characteristic, error == nil else {
|
||||
completion?(error != nil ? error : PeripheralUartError.invalidCharacteristic)
|
||||
return
|
||||
|
|
@ -85,7 +88,7 @@ extension BlePeripheral {
|
|||
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
|
||||
self.characteristic(uuid: rxCharacteristicUuid, serviceUuid: uartServiceUuid) { [unowned self] (characteristic, error) in
|
||||
guard let characteristic = characteristic, error == nil else {
|
||||
completion?(error != nil ? error : PeripheralUartError.invalidCharacteristic)
|
||||
return
|
||||
|
|
@ -122,17 +125,17 @@ extension BlePeripheral {
|
|||
}
|
||||
|
||||
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) { [weak self] error in
|
||||
guard let self = self else { return }
|
||||
|
||||
disableNotify(for: characteristic)
|
||||
// Clear all Uart specific data
|
||||
self.uartRxCharacteristic = nil
|
||||
self.uartTxCharacteristic = nil
|
||||
self.uartTxCharacteristicWriteType = nil
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Send
|
||||
|
|
|
|||
|
|
@ -8,19 +8,18 @@
|
|||
|
||||
import Foundation
|
||||
import CoreBluetooth
|
||||
#if COMMANDLINE
|
||||
#else
|
||||
import MSWeakTimer
|
||||
#endif
|
||||
|
||||
// TODO: Modernize completion blocks to use Swift.Result
|
||||
|
||||
class BlePeripheral: NSObject {
|
||||
open class BlePeripheral: NSObject {
|
||||
// Config
|
||||
private static var kProfileCharacteristicUpdates = true
|
||||
|
||||
// Constants
|
||||
static var kUndefinedRssiValue = 127
|
||||
|
||||
// Notifications
|
||||
enum NotificationUserInfoKey: String {
|
||||
public enum NotificationUserInfoKey: String {
|
||||
case uuid = "uuid"
|
||||
case name = "name"
|
||||
case invalidatedServices = "invalidatedServices"
|
||||
|
|
@ -32,22 +31,48 @@ class BlePeripheral: NSObject {
|
|||
|
||||
// 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 {
|
||||
public static var rssiRunningAverageFactor: Double = 1 /// Global Parameter that affects all rssi measurements. 1 means don't use a running average. The closer to 0 the more resistant the value it is to change
|
||||
private var runningRssi: Int?
|
||||
public 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. If runningRssiFactorFactor == 1, the newer value replaces the old value and not average is calculated
|
||||
get {
|
||||
return runningRssi
|
||||
}
|
||||
set {
|
||||
guard newValue != BlePeripheral.kUndefinedRssiValue else { return } // Don't accept undefined values
|
||||
|
||||
// based on https://en.wikipedia.org/wiki/Exponential_smoothing
|
||||
if newValue == nil || runningRssi == nil || runningRssi == BlePeripheral.kUndefinedRssiValue {
|
||||
runningRssi = newValue
|
||||
} else {
|
||||
runningRssi = Int(BlePeripheral.rssiRunningAverageFactor * Double(newValue!) + (1-BlePeripheral.rssiRunningAverageFactor) * Double(runningRssi!))
|
||||
}
|
||||
}
|
||||
}
|
||||
public var lastSeenTime: CFAbsoluteTime
|
||||
|
||||
open var identifier: UUID {
|
||||
return peripheral.identifier
|
||||
}
|
||||
|
||||
var name: String? {
|
||||
open var name: String? {
|
||||
return peripheral.name
|
||||
}
|
||||
|
||||
var state: CBPeripheralState {
|
||||
return peripheral.state
|
||||
|
||||
public var debugName: String {
|
||||
return peripheral.name ?? peripheral.identifier.uuidString
|
||||
}
|
||||
|
||||
struct Advertisement {
|
||||
open var state: CBPeripheralState {
|
||||
return peripheral.state
|
||||
}
|
||||
|
||||
func maximumWriteValueLength(for: CBCharacteristicWriteType) -> Int {
|
||||
return peripheral.maximumWriteValueLength(for: .withoutResponse)
|
||||
}
|
||||
|
||||
public struct Advertisement {
|
||||
var advertisementData: [String: Any]
|
||||
|
||||
init(advertisementData: [String: Any]?) {
|
||||
|
|
@ -55,71 +80,73 @@ class BlePeripheral: NSObject {
|
|||
}
|
||||
|
||||
// Advertisement data formatted
|
||||
var localName: String? {
|
||||
public var localName: String? {
|
||||
return advertisementData[CBAdvertisementDataLocalNameKey] as? String
|
||||
}
|
||||
|
||||
var manufacturerData: Data? {
|
||||
|
||||
public var manufacturerData: Data? {
|
||||
return advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data
|
||||
}
|
||||
|
||||
var manufacturerHexDescription: String? {
|
||||
|
||||
public var manufacturerHexDescription: String? {
|
||||
guard let manufacturerData = manufacturerData else { return nil }
|
||||
return HexUtils.hexDescription(data: manufacturerData)
|
||||
// return String(data: manufacturerData, encoding: .utf8)
|
||||
}
|
||||
|
||||
var manufacturerIdentifier: Data? {
|
||||
|
||||
public var manufacturerIdentifier: Data? {
|
||||
guard let manufacturerData = manufacturerData, manufacturerData.count >= 2 else { return nil }
|
||||
let manufacturerIdentifierData = manufacturerData[0..<2]
|
||||
return manufacturerIdentifierData
|
||||
}
|
||||
|
||||
var services: [CBUUID]? {
|
||||
public var services: [CBUUID]? {
|
||||
return advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID]
|
||||
}
|
||||
|
||||
var servicesOverflow: [CBUUID]? {
|
||||
public var servicesOverflow: [CBUUID]? {
|
||||
return advertisementData[CBAdvertisementDataOverflowServiceUUIDsKey] as? [CBUUID]
|
||||
}
|
||||
|
||||
var servicesSolicited: [CBUUID]? {
|
||||
public var servicesSolicited: [CBUUID]? {
|
||||
return advertisementData[CBAdvertisementDataSolicitedServiceUUIDsKey] as? [CBUUID]
|
||||
}
|
||||
|
||||
var serviceData: [CBUUID: Data]? {
|
||||
|
||||
public var serviceData: [CBUUID: Data]? {
|
||||
return advertisementData[CBAdvertisementDataServiceDataKey] as? [CBUUID: Data]
|
||||
}
|
||||
|
||||
var txPower: Int? {
|
||||
public var txPower: Int? {
|
||||
let number = advertisementData[CBAdvertisementDataTxPowerLevelKey] as? NSNumber
|
||||
return number?.intValue
|
||||
}
|
||||
|
||||
var isConnectable: Bool? {
|
||||
public var isConnectable: Bool? {
|
||||
let connectableNumber = advertisementData[CBAdvertisementDataIsConnectable] as? NSNumber
|
||||
return connectableNumber?.boolValue
|
||||
}
|
||||
}
|
||||
var advertisement: Advertisement
|
||||
public 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 timeoutTimer: Foundation.Timer?
|
||||
var timeoutAction: ((String) -> Void)?
|
||||
var isNotifyOmitted: Bool
|
||||
|
||||
init(identifier: String, result: @escaping CapturedReadCompletionHandler, timeout: Double?, timeoutAction:((String)->())?, isNotifyOmitted: Bool = false) {
|
||||
init(identifier: String, result: @escaping CapturedReadCompletionHandler, timeout: Double?, timeoutAction: ((String) -> Void)?, 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))
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
self.timeoutTimer = Timer.scheduledTimer(timeInterval: timeout, target: self, selector: #selector(self.timerFired), userInfo: nil, repeats: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -134,12 +161,12 @@ class BlePeripheral: NSObject {
|
|||
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]()
|
||||
|
|
@ -149,13 +176,13 @@ class BlePeripheral: NSObject {
|
|||
//private var profileStartTime: CFTimeInterval = 0
|
||||
|
||||
// MARK: - Init
|
||||
init(peripheral: CBPeripheral, advertisementData: [String: Any]?, rssi: Int?) {
|
||||
public 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.rssi = rssi
|
||||
self.peripheral.delegate = self
|
||||
// DLog("create peripheral: \(peripheral.name ?? peripheral.identifier.uuidString)")
|
||||
commandQueue.executeHandler = executeCommand
|
||||
|
|
@ -207,6 +234,12 @@ class BlePeripheral: NSObject {
|
|||
commandQueue.append(command)
|
||||
}
|
||||
|
||||
// MARK: - Connection
|
||||
func disconnect(centralManager: CBCentralManager) {
|
||||
let command = BleCommand(type: .disconnect, parameters: [centralManager], completion: nil)
|
||||
commandQueue.append(command)
|
||||
}
|
||||
|
||||
// MARK: - Service
|
||||
func discoveredService(uuid: CBUUID) -> CBService? {
|
||||
let service = peripheral.services?.first(where: {$0.uuid == uuid})
|
||||
|
|
@ -248,7 +281,7 @@ class BlePeripheral: NSObject {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
|
|
@ -267,7 +300,7 @@ class BlePeripheral: NSObject {
|
|||
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)
|
||||
|
|
@ -302,14 +335,14 @@ class BlePeripheral: NSObject {
|
|||
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 {
|
||||
internal class BleCommand: Equatable {
|
||||
enum CommandType {
|
||||
case discoverService
|
||||
case discoverCharacteristic
|
||||
|
|
@ -319,6 +352,7 @@ class BlePeripheral: NSObject {
|
|||
case writeCharacteristic
|
||||
case writeCharacteristicAndWaitNofity
|
||||
case readDescriptor
|
||||
case disconnect
|
||||
}
|
||||
|
||||
enum CommandError: Error {
|
||||
|
|
@ -362,18 +396,22 @@ class BlePeripheral: NSObject {
|
|||
write(with: command)
|
||||
case .readDescriptor:
|
||||
readDescriptor(with: command)
|
||||
case .disconnect:
|
||||
disconnect(with: command)
|
||||
}
|
||||
}
|
||||
|
||||
private func handlerIdentifier(from characteristic: CBCharacteristic) -> String {
|
||||
return "\(characteristic.service.uuid.uuidString)-\(characteristic.uuid.uuidString)"
|
||||
guard let service = characteristic.service else { DLog("Error: handleIdentifier with nil characteritic service"); return "" }
|
||||
return "\(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)"
|
||||
guard let characteristic = descriptor.characteristic, let service = characteristic.service else { DLog("Error: handleIdentifier with nil descriptor service"); return "" }
|
||||
return "\(service.uuid.uuidString)-\(characteristic.uuid.uuidString)-\(descriptor.uuid.uuidString)"
|
||||
}
|
||||
|
||||
private func finishedExecutingCommand(error: Error?) {
|
||||
internal func finishedExecutingCommand(error: Error?) {
|
||||
//DLog("finishedExecutingCommand")
|
||||
|
||||
// Result Callback
|
||||
|
|
@ -401,7 +439,7 @@ class BlePeripheral: NSObject {
|
|||
if discoverAll || (serviceUuids != nil && serviceUuids!.count > 0) {
|
||||
peripheral.discoverServices(serviceUuids)
|
||||
} else {
|
||||
// Everthing was already discovered
|
||||
// Everything was already discovered
|
||||
finishedExecutingCommand(error: nil)
|
||||
}
|
||||
}
|
||||
|
|
@ -464,21 +502,32 @@ class BlePeripheral: NSObject {
|
|||
let writeType = command.parameters![1] as! CBCharacteristicWriteType
|
||||
let data = command.parameters![2] as! Data
|
||||
|
||||
peripheral.writeValue(data, for: characteristic, type: writeType)
|
||||
|
||||
if writeType == .withoutResponse {
|
||||
let mtu = maximumWriteValueLength(for: .withoutResponse)
|
||||
var offset = 0
|
||||
while offset < data.count {
|
||||
let chunkData = data.subdata(in: offset ..< min(offset + mtu, data.count))
|
||||
//DLog("blewrite offset: \(offset) / \(data.count), size: \(chunkData.count)")
|
||||
peripheral.writeValue(chunkData, for: characteristic, type: .withoutResponse)
|
||||
offset += chunkData.count
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
else {
|
||||
peripheral.writeValue(data, for: characteristic, type: writeType)
|
||||
}
|
||||
}
|
||||
|
||||
private func readDescriptor(with command: BleCommand) {
|
||||
|
|
@ -491,35 +540,41 @@ class BlePeripheral: NSObject {
|
|||
|
||||
peripheral.readValue(for: descriptor)
|
||||
}
|
||||
|
||||
internal func disconnect(with command: BleCommand) {
|
||||
let centralManager = command.parameters!.first as! CBCentralManager
|
||||
centralManager.cancelPeripheralConnection(self.peripheral)
|
||||
finishedExecutingCommand(error: nil)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - CBPeripheralDelegate
|
||||
extension BlePeripheral: CBPeripheralDelegate {
|
||||
func peripheralDidUpdateName(_ peripheral: CBPeripheral) {
|
||||
public 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]) {
|
||||
public 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)")
|
||||
public 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)")
|
||||
public 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?) {
|
||||
public func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
|
||||
finishedExecutingCommand(error: error)
|
||||
}
|
||||
|
||||
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
|
||||
public func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
|
||||
|
||||
let identifier = handlerIdentifier(from: characteristic)
|
||||
|
||||
|
|
@ -571,8 +626,8 @@ extension BlePeripheral: CBPeripheralDelegate {
|
|||
finishedExecutingCommand(error: error)
|
||||
}
|
||||
}
|
||||
|
||||
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
|
||||
|
||||
public 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
|
||||
|
|
@ -587,11 +642,11 @@ extension BlePeripheral: CBPeripheralDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
func peripheral(_ peripheral: CBPeripheral, didDiscoverDescriptorsFor characteristic: CBCharacteristic, error: Error?) {
|
||||
public func peripheral(_ peripheral: CBPeripheral, didDiscoverDescriptorsFor characteristic: CBCharacteristic, error: Error?) {
|
||||
finishedExecutingCommand(error: error)
|
||||
}
|
||||
|
||||
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor descriptor: CBDescriptor, error: Error?) {
|
||||
public 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}) {
|
||||
|
|
@ -605,15 +660,15 @@ extension BlePeripheral: CBPeripheralDelegate {
|
|||
finishedExecutingCommand(error: error)
|
||||
}
|
||||
}
|
||||
|
||||
func peripheral(_ peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: Error?) {
|
||||
|
||||
public 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 )
|
||||
if rssi != BlePeripheral.kUndefinedRssiValue { // 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])
|
||||
}
|
||||
}
|
||||
|
|
@ -621,7 +676,7 @@ extension BlePeripheral: CBPeripheralDelegate {
|
|||
// 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")
|
||||
public static let peripheralDidUpdateName = Notification.Name(kPrefix+".peripheralDidUpdateName")
|
||||
public static let peripheralDidModifyServices = Notification.Name(kPrefix+".peripheralDidModifyServices")
|
||||
public static let peripheralDidUpdateRssi = Notification.Name(kPrefix+".peripheralDidUpdateRssi")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
protocol UartDataManagerDelegate: class {
|
||||
protocol UartDataManagerDelegate: AnyObject {
|
||||
func onUartRx(data: Data, peripheralIdentifier: UUID) // data contents depends on the isRxCacheEnabled flag
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,10 @@ import Foundation
|
|||
|
||||
class UartPacketManager: UartPacketManagerBase {
|
||||
|
||||
// Params
|
||||
var isResetPacketsOnReconnectionEnabled = true
|
||||
|
||||
// MARK: - Lifecycle
|
||||
override init(delegate: UartPacketManagerDelegate?, isPacketCacheEnabled: Bool, isMqttEnabled: Bool) {
|
||||
super.init(delegate: delegate, isPacketCacheEnabled: isPacketCacheEnabled, isMqttEnabled: isMqttEnabled)
|
||||
|
||||
|
|
@ -25,14 +29,20 @@ class UartPacketManager: UartPacketManagerBase {
|
|||
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()}
|
||||
didConnectToPeripheralObserver = notificationCenter.addObserver(forName: .didConnectToPeripheral, object: nil, queue: .main) {
|
||||
[weak self] _ in
|
||||
guard let self = self else { return }
|
||||
if self.isResetPacketsOnReconnectionEnabled {
|
||||
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) {
|
||||
private func sendUart(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)
|
||||
}
|
||||
|
|
@ -51,13 +61,16 @@ class UartPacketManager: UartPacketManagerBase {
|
|||
blePeripheral.uartSendAndWaitReply(data: data, writeProgress: writeProgress, writeCompletion: writeCompletion, readTimeout: readTimeout, readCompletion: readCompletion)
|
||||
}
|
||||
|
||||
func send(blePeripheral: BlePeripheral, text: String, wasReceivedFromMqtt: Bool = false) {
|
||||
/*
|
||||
Send data to MQTT (if enabled) and also to UART (if MQTT configuration allows it)
|
||||
*/
|
||||
func send(blePeripheral: BlePeripheral, data: Data, wasReceivedFromMqtt: Bool = false) {
|
||||
|
||||
#if MQTT_ENABLED
|
||||
if isMqttEnabled {
|
||||
// Mqtt publish to TX
|
||||
let mqttSettings = MqttSettings.shared
|
||||
if mqttSettings.isPublishEnabled {
|
||||
if mqttSettings.isPublishEnabled, let text = String(data: data, encoding: .utf8) {
|
||||
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)
|
||||
|
|
@ -67,27 +80,25 @@ class UartPacketManager: UartPacketManagerBase {
|
|||
#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)
|
||||
}
|
||||
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 {
|
||||
sendUart(blePeripheral: blePeripheral, data: data)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,108 +0,0 @@
|
|||
//
|
||||
// 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])
|
||||
}
|
||||
}
|
||||
|
|
@ -1,133 +0,0 @@
|
|||
//
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,190 +0,0 @@
|
|||
//
|
||||
// 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(()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
//
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,200 +0,0 @@
|
|||
//
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
//
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
//
|
||||
// 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)")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
|
@ -6,38 +6,78 @@
|
|||
// Copyright © 2019 Adafruit. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import CoreBluetooth
|
||||
|
||||
class BleManagerSimulated: BleManager {
|
||||
|
||||
|
||||
// Singleton
|
||||
static let simulated = BleManagerSimulated()
|
||||
|
||||
// MARK: - Lifecycle
|
||||
override init() {
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Scanning
|
||||
override func startScan(withServices services: [CBUUID]? = nil) {
|
||||
isScanning = true
|
||||
scanningStartTime = CACurrentMediaTime()
|
||||
|
||||
// Add simulated peripherals
|
||||
let simulatedCPB = BlePeripheralSimulated(model: .circuitPlaygroundBluefruit)
|
||||
peripheralsFound[simulatedCPB.identifier] = simulatedCPB
|
||||
NotificationCenter.default.post(name: .didDiscoverPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: simulatedCPB.identifier])
|
||||
|
||||
let simulatedClue = BlePeripheralSimulated(model: .clue_nRF52840)
|
||||
peripheralsFound[simulatedClue.identifier] = simulatedClue
|
||||
NotificationCenter.default.post(name: .didDiscoverPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: simulatedClue.identifier])
|
||||
|
||||
|
||||
// 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() {
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Connect
|
||||
override func connect(to peripheral: BlePeripheral, timeout: TimeInterval? = nil, shouldNotifyOnConnection: Bool = false, shouldNotifyOnDisconnection: Bool = false, shouldNotifyOnNotification: Bool = false) {
|
||||
|
||||
|
||||
guard let blePeripheral = peripheral as? BlePeripheralSimulated else { return }
|
||||
blePeripheral.simulateConnect()
|
||||
|
||||
// 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 {
|
||||
|
||||
override func reconnecToPeripherals(peripheralsData: [(identifier: UUID, advertisementData: [String: Any])], withServices services: [CBUUID], timeout: Double? = nil) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// MARK: - Disconnect
|
||||
override func disconnect(from peripheral: BlePeripheral, waitForQueuedCommands: Bool = false) {
|
||||
guard let blePeripheral = peripheral as? BlePeripheralSimulated else { return }
|
||||
|
||||
DLog("disconnect")
|
||||
NotificationCenter.default.post(name: .willDisconnectFromPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
|
||||
|
||||
if waitForQueuedCommands {
|
||||
// Send the disconnection to the command queue, so all the previous command are executed before disconnecting
|
||||
if let centralManager = centralManager {
|
||||
blePeripheral.disconnect(centralManager: centralManager)
|
||||
}
|
||||
} else {
|
||||
didDisconnectPeripheral(blePeripheral: blePeripheral)
|
||||
}
|
||||
}
|
||||
|
||||
func didDisconnectPeripheral(blePeripheral: BlePeripheralSimulated) {
|
||||
DLog("didDisconnectPeripheral")
|
||||
|
||||
// Clean
|
||||
peripheralsFound[blePeripheral.identifier]?.reset()
|
||||
|
||||
// Notify
|
||||
NotificationCenter.default.post(name: .didDisconnectFromPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: blePeripheral.identifier])
|
||||
|
||||
// Don't remove the peripheral from the peripheral list (so we can select again the simulated peripheral)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
//
|
||||
// BlePeripheralSimulated+AdafruitAccelerometer.swift
|
||||
// BluefruitPlayground
|
||||
//
|
||||
// Created by Antonio García on 25/11/2019.
|
||||
// Copyright © 2019 Adafruit. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
import CoreBluetooth
|
||||
|
||||
extension BlePeripheral {
|
||||
// Structs
|
||||
struct AccelerometerValue {
|
||||
var x: Float
|
||||
var y: Float
|
||||
var z: Float
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
func adafruitAccelerometerEnable(responseHandler: @escaping(Result<(AccelerometerValue, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
||||
|
||||
completion?(.success(()))
|
||||
}
|
||||
|
||||
func adafruitAccelerometerIsEnabled() -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func adafruitAccelerometerDisable() {
|
||||
}
|
||||
|
||||
func adafruitAccelerometerLastValue() -> AccelerometerValue? {
|
||||
return AccelerometerValue(x: 0, y: 0, z: 0)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
//
|
||||
// BlePeripheral+AdafruitBarometricPressure.swift
|
||||
// BluefruitPlayground
|
||||
//
|
||||
// Created by Antonio García on 06/03/2020.
|
||||
// Copyright © 2019 Adafruit. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreBluetooth
|
||||
|
||||
extension BlePeripheral {
|
||||
// MARK: - Custom properties
|
||||
private struct CustomPropertiesKeys {
|
||||
static var adafruitBarometricPressureResponseDataTimer: Timer?
|
||||
}
|
||||
|
||||
private var adafruitBarometricPressureResponseDataTimer: Timer? {
|
||||
get {
|
||||
return objc_getAssociatedObject(self, &CustomPropertiesKeys.adafruitBarometricPressureResponseDataTimer) as! Timer?
|
||||
}
|
||||
set {
|
||||
objc_setAssociatedObject(self, &CustomPropertiesKeys.adafruitBarometricPressureResponseDataTimer, newValue, .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
func adafruitBarometricPressureEnable(responseHandler: @escaping(Result<(Float, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
||||
|
||||
adafruitBarometricPressureResponseDataTimer = Timer.scheduledTimer(withTimeInterval: BlePeripheral.kAdafruitSensorDefaultPeriod, repeats: true) { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
guard let value = self.adafruitBarometricPressureLastValue() else { return }
|
||||
responseHandler(.success((value, self.identifier)))
|
||||
}
|
||||
|
||||
completion?(.success(()))
|
||||
}
|
||||
|
||||
func adafruitBarometricPressureIsEnabled() -> Bool {
|
||||
return self.adafruitManufacturerData()?.boardModel == .clue_nRF52840
|
||||
}
|
||||
|
||||
func adafruitBarometricPressureDisable() {
|
||||
}
|
||||
|
||||
func adafruitBarometricPressureLastValue() -> Float? {
|
||||
return Float.random(in: 1190.0 ..< 1191.0)
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// BlePeripheral+CPBButtons.swift
|
||||
// BlePeripheralSimulated+AdafruitButtons.swift
|
||||
// BluefruitPlayground
|
||||
//
|
||||
// Created by Antonio García on 15/11/2019.
|
||||
|
|
@ -10,8 +10,6 @@ import Foundation
|
|||
import CoreBluetooth
|
||||
|
||||
extension BlePeripheral {
|
||||
// Constants
|
||||
|
||||
enum SlideSwitchState: Int32 {
|
||||
case right = 0
|
||||
case left = 1
|
||||
|
|
@ -28,28 +26,27 @@ extension BlePeripheral {
|
|||
var buttonB: ButtonState
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Actions
|
||||
func cpbButtonsEnable(responseHandler: @escaping(Result<(ButtonsState, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
||||
completion?(.success(()))
|
||||
func adafruitButtonsEnable(responseHandler: @escaping(Result<(ButtonsState, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
||||
completion?(.success(()))
|
||||
}
|
||||
|
||||
func isCpbButtonsEnabled() -> Bool {
|
||||
func adafruitButtonsIsEnabled() -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func cpbButtonsDisable() {
|
||||
func adafruitButtonsDisable() {
|
||||
}
|
||||
|
||||
func cpbButtonsReadState(completion: @escaping(Result<(ButtonsState, UUID), Error>) -> Void) {
|
||||
guard let state = cpbButtonsLastValue() else {
|
||||
completion(.failure(PeripheralCPBError.invalidResponseData))
|
||||
func adafruitButtonsReadState(completion: @escaping(Result<(ButtonsState, UUID), Error>) -> Void) {
|
||||
guard let state = adafruitButtonsLastValue() else {
|
||||
completion(.failure(PeripheralAdafruitError.invalidResponseData))
|
||||
return
|
||||
}
|
||||
completion(.success((state, self.identifier)))
|
||||
}
|
||||
|
||||
func cpbButtonsLastValue() -> ButtonsState? {
|
||||
func adafruitButtonsLastValue() -> ButtonsState? {
|
||||
return ButtonsState(slideSwitch: .left, buttonA: .pressed, buttonB: .released)
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
//
|
||||
// BlePeripheral+AdafruitHumidity.swift
|
||||
// BluefruitPlayground
|
||||
//
|
||||
// Created by Antonio García on 06/03/2020.
|
||||
// Copyright © 2019 Adafruit. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreBluetooth
|
||||
|
||||
extension BlePeripheral {
|
||||
// MARK: - Custom properties
|
||||
private struct CustomPropertiesKeys {
|
||||
static var adafruitHumidityResponseDataTimer: Timer?
|
||||
}
|
||||
|
||||
private var adafruitHumidityResponseDataTimer: Timer? {
|
||||
get {
|
||||
return objc_getAssociatedObject(self, &CustomPropertiesKeys.adafruitHumidityResponseDataTimer) as! Timer?
|
||||
}
|
||||
set {
|
||||
objc_setAssociatedObject(self, &CustomPropertiesKeys.adafruitHumidityResponseDataTimer, newValue, .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
func adafruitHumidityEnable(responseHandler: @escaping(Result<(Float, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
||||
|
||||
adafruitHumidityResponseDataTimer = Timer.scheduledTimer(withTimeInterval: BlePeripheral.kAdafruitSensorDefaultPeriod, repeats: true) { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
guard let value = self.adafruitHumidityLastValue() else { return }
|
||||
responseHandler(.success((value, self.identifier)))
|
||||
}
|
||||
|
||||
completion?(.success(()))
|
||||
}
|
||||
|
||||
func adafruitHumidityIsEnabled() -> Bool {
|
||||
return self.adafruitManufacturerData()?.boardModel == .clue_nRF52840
|
||||
}
|
||||
|
||||
func adafruitHumidityDisable() {
|
||||
}
|
||||
|
||||
func adafruitHumidityLastValue() -> Float? {
|
||||
return Float.random(in: 28.5 ..< 29.0)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
//
|
||||
// BlePeripheralSimulated+AdafruitLight.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 adafruitLightResponseDataTimer: Timer?
|
||||
}
|
||||
|
||||
private var adafruitLightResponseDataTimer: Timer? {
|
||||
get {
|
||||
return objc_getAssociatedObject(self, &CustomPropertiesKeys.adafruitLightResponseDataTimer) as! Timer?
|
||||
}
|
||||
set {
|
||||
objc_setAssociatedObject(self, &CustomPropertiesKeys.adafruitLightResponseDataTimer, newValue, .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
func adafruitLightEnable(responseHandler: @escaping(Result<(Float, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
||||
|
||||
adafruitLightResponseDataTimer = Timer.scheduledTimer(withTimeInterval: BlePeripheral.kAdafruitSensorDefaultPeriod, repeats: true) { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
guard let value = self.adafruitLightLastValue() else { return }
|
||||
responseHandler(.success((value, self.identifier)))
|
||||
}
|
||||
|
||||
completion?(.success(()))
|
||||
}
|
||||
|
||||
func adafruitLightIsEnabled() -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func adafruitLightDisable() {
|
||||
adafruitLightResponseDataTimer?.invalidate()
|
||||
adafruitLightResponseDataTimer = nil
|
||||
}
|
||||
|
||||
func adafruitLightLastValue() -> Float? {
|
||||
let temperature = Float.random(in: 300 ..< 400)
|
||||
return temperature
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
//
|
||||
// BlePeripheralSimulated+AdafruitNeoPixels.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 kAdafruitNeoPixelsServiceNumberOfBitsPerPixel = 3
|
||||
|
||||
// MARK: - Actions
|
||||
func adafruitNeoPixelsEnable(completion: ((Result<Void, Error>) -> Void)?) {
|
||||
completion?(.success(()))
|
||||
}
|
||||
|
||||
func adafruitNeoPixelsIsEnabled() -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func adafruitNeoPixelsDisable() {
|
||||
}
|
||||
|
||||
var adafruitNeoPixelsCount: Int {
|
||||
return self.adafruitManufacturerData()?.boardModel?.neoPixelsCount ?? 0
|
||||
}
|
||||
|
||||
func adafruitNeoPixelSetAllPixelsColor(_ color: UIColor) {
|
||||
}
|
||||
|
||||
func adafruitNeoPixelSetPixelColor(index: Int, color: UIColor) {
|
||||
}
|
||||
|
||||
func adafruitNeoPixelSetColor(index: UInt, color: UIColor, pixelMask: [Bool]) {
|
||||
}
|
||||
|
||||
// MARK: - Low level actions
|
||||
func adafruitNeoPixelsWriteData(offset: UInt16, pixelData: Data) {
|
||||
}
|
||||
|
||||
static func pixelDataFromColor(_ color: UIColor) -> Data {
|
||||
let bytes = pixelUInt8FromColor(color)
|
||||
return bytes.data
|
||||
}
|
||||
|
||||
static func pixelUInt8FromColor(_ color: UIColor) -> [UInt8] {
|
||||
return [UInt8]([0, 0, 0])
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
//
|
||||
// BlePeripheral+AdafruitQuaternion.swift
|
||||
// BluefruitPlayground
|
||||
//
|
||||
// Created by Antonio García on 25/11/2019.
|
||||
// Copyright © 2019 Adafruit. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
import CoreBluetooth
|
||||
|
||||
extension BlePeripheral {
|
||||
// Structs
|
||||
struct QuaternionValue {
|
||||
var x: Float
|
||||
var y: Float
|
||||
var z: Float
|
||||
var w: Float
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
func adafruitQuaternionEnable(responseHandler: @escaping(Result<(QuaternionValue, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
||||
|
||||
completion?(.success(()))
|
||||
}
|
||||
|
||||
func adafruitQuaternionIsEnabled() -> Bool {
|
||||
return self.adafruitManufacturerData()?.boardModel == .clue_nRF52840
|
||||
}
|
||||
|
||||
func adafruitQuaternionDisable() {
|
||||
}
|
||||
|
||||
func adafruitQuaternionLastValue() -> QuaternionValue? {
|
||||
return QuaternionValue(x: 0, y: 0, z: 0, w: 1)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
//
|
||||
// BlePeripheralSimulated+AdafruitTemperature.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 adafruitTemperatureResponseDataTimer: Timer?
|
||||
}
|
||||
|
||||
private var adafruitTemperatureResponseDataTimer: Timer? {
|
||||
get {
|
||||
return objc_getAssociatedObject(self, &CustomPropertiesKeys.adafruitTemperatureResponseDataTimer) as! Timer?
|
||||
}
|
||||
set {
|
||||
objc_setAssociatedObject(self, &CustomPropertiesKeys.adafruitTemperatureResponseDataTimer, newValue, .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
func adafruitTemperatureEnable(responseHandler: @escaping(Result<(Float, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
||||
|
||||
adafruitTemperatureResponseDataTimer = Timer.scheduledTimer(withTimeInterval: BlePeripheral.kAdafruitSensorDefaultPeriod, repeats: true) { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
guard let value = self.adafruitTemperatureLastValue() else { return }
|
||||
responseHandler(.success((value, self.identifier)))
|
||||
}
|
||||
|
||||
completion?(.success(()))
|
||||
}
|
||||
|
||||
func adafruitTemperatureIsEnabled() -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func adafruitTemperatureDisable() {
|
||||
adafruitTemperatureResponseDataTimer?.invalidate()
|
||||
adafruitTemperatureResponseDataTimer = nil
|
||||
}
|
||||
|
||||
func adafruitTemperatureLastValue() -> Float? {
|
||||
let temperature = Float.random(in: 18.5 ..< 19.5)
|
||||
return temperature
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
//
|
||||
// BlePeripheralSimulated+AdafruitToneGenerator.swift
|
||||
// BluefruitPlayground
|
||||
//
|
||||
// Created by Antonio García on 18/11/2019.
|
||||
// Copyright © 2019 Adafruit. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreBluetooth
|
||||
|
||||
extension BlePeripheral {
|
||||
// MARK: - Actions
|
||||
func adafruitToneGeneratorEnable(completion: ((Result<Void, Error>) -> Void)?) {
|
||||
completion?(.success(()))
|
||||
}
|
||||
|
||||
func adafruitToneGeneratorIsEnabled() -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func adafruitToneGeneratorDisable() {
|
||||
}
|
||||
|
||||
func adafruitToneGeneratorStartPlaying(frequency: UInt16, duration: UInt32 = 0) { // Duration 0 means non-stop
|
||||
}
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
//
|
||||
// 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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -10,24 +10,38 @@ import Foundation
|
|||
import CoreBluetooth
|
||||
|
||||
class BlePeripheralSimulated: BlePeripheral {
|
||||
// Constants
|
||||
private static let kSimulatedUUID = UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!
|
||||
|
||||
// Data
|
||||
private var simulatedIdentifier = UUID()
|
||||
override var identifier: UUID {
|
||||
return BlePeripheralSimulated.kSimulatedUUID
|
||||
return simulatedIdentifier
|
||||
}
|
||||
|
||||
|
||||
override var name: String? {
|
||||
return "Simulated Peripheral"
|
||||
let result: String
|
||||
switch model {
|
||||
case .circuitPlaygroundBluefruit:
|
||||
result = "CPB"
|
||||
case .clue_nRF52840:
|
||||
result = "CLUE"
|
||||
case .feather_nRF52832:
|
||||
result = "Feather"
|
||||
case .feather_nRF52840_express:
|
||||
result = "Feather Express"
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
private var simulatedState: CBPeripheralState = .disconnected
|
||||
override var state: CBPeripheralState {
|
||||
return .connected
|
||||
return simulatedState
|
||||
}
|
||||
|
||||
private var model: AdafruitManufacturerData.BoardModel
|
||||
|
||||
// MARK: - Lifecycle
|
||||
init() {
|
||||
init(model: AdafruitManufacturerData.BoardModel) {
|
||||
self.model = model
|
||||
|
||||
// Mocking CBPeripheral: https://forums.developer.apple.com/thread/29851
|
||||
guard let peripheral = ObjectBuilder.createInstance(ofClass: "CBPeripheral") as? CBPeripheral else {
|
||||
assertionFailure("Unable to mock CBPeripheral")
|
||||
|
|
@ -35,16 +49,34 @@ class BlePeripheralSimulated: BlePeripheral {
|
|||
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
|
||||
peripheral.addObserver(peripheral, forKeyPath: "delegate", options: .new, context: nil)
|
||||
|
||||
let adafruitManufacturerIdentifier = BlePeripheral.kManufacturerAdafruitIdentifier
|
||||
let boardId = model.identifier.first!
|
||||
let boardField: [UInt8] = [0x04, 0x01, 0x00] + boardId
|
||||
let manufacturerDataBytes: [UInt8] = adafruitManufacturerIdentifier + boardField
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Connect
|
||||
func simulateConnect() {
|
||||
simulatedState = .connected
|
||||
}
|
||||
|
||||
// MARK: - Disconnect
|
||||
override internal func disconnect(with command: BleCommand) {
|
||||
// Simulate disconnection
|
||||
simulatedState = .disconnected
|
||||
BleManagerSimulated.simulated.didDisconnectPeripheral(blePeripheral: self)
|
||||
|
||||
// Finished
|
||||
finishedExecutingCommand(error: nil)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
protocol UartPacketManagerDelegate: class {
|
||||
protocol UartPacketManagerDelegate: AnyObject {
|
||||
func onUartPacket(_ packet: UartPacket)
|
||||
}
|
||||
|
||||
|
|
@ -21,7 +21,7 @@ struct UartPacket { // A packet of data received or sent
|
|||
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()
|
||||
|
|
@ -30,34 +30,32 @@ struct UartPacket { // A packet of data received or sent
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
|
||||
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 {
|
||||
|
|
@ -72,31 +70,31 @@ class UartPacketManagerBase {
|
|||
}
|
||||
}
|
||||
#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
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ class CommandQueue<Element> {
|
|||
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()
|
||||
|
|
|
|||
|
|
@ -8,19 +8,17 @@
|
|||
|
||||
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) }))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,11 +12,11 @@ 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)}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ extension UInt16: UIntToBytesConvertable {
|
|||
|
||||
extension UInt32: UIntToBytesConvertable {
|
||||
var toBytes: [UInt8] {
|
||||
return toByteArr(endian: self.littleEndian, count: MemoryLayout<UInt32>.size)
|
||||
return toByteArr(endian: self.littleEndian, count: MemoryLayout<UInt32>.size)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
//
|
||||
// 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
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
|
|
@ -20,7 +20,7 @@ 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)} )
|
||||
_ = withUnsafeMutableBytes(of: &value, { data.copyBytes(to: $0)})
|
||||
self = value
|
||||
}
|
||||
|
||||
|
|
@ -39,7 +39,6 @@ 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 {
|
||||
|
|
|
|||
|
|
@ -15,8 +15,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
|
||||
|
||||
startup()
|
||||
|
||||
ScreenFlowManager.enableBleStateManagement()
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -40,6 +41,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
|
||||
func applicationWillTerminate(_ application: UIApplication) {
|
||||
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
|
||||
|
||||
ScreenFlowManager.disableBleStateManagement()
|
||||
}
|
||||
|
||||
// MARK: - Startup
|
||||
|
|
@ -49,7 +52,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
|
||||
// UI
|
||||
UINavigationBar.appearance().prefersLargeTitles = ConfigUI.prefersLargeTitles
|
||||
|
||||
|
||||
// Navigation bar: add background when large title is used
|
||||
if #available(iOS 13.0, *) {
|
||||
let appearance = UINavigationBarAppearance()
|
||||
|
|
@ -68,4 +71,3 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
23
BluefruitPlayground/Assets.xcassets/autoscan/scanning_background.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "scanning_background.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "scanning_background@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "scanning_background@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
BluefruitPlayground/Assets.xcassets/autoscan/scanning_background.imageset/scanning_background.png
vendored
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
BluefruitPlayground/Assets.xcassets/autoscan/scanning_background.imageset/scanning_background@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
BluefruitPlayground/Assets.xcassets/autoscan/scanning_background.imageset/scanning_background@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 87 KiB |
23
BluefruitPlayground/Assets.xcassets/bluetooth/bluetooth_status.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "bluetooth_status.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "bluetooth_status@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "bluetooth_status@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
BluefruitPlayground/Assets.xcassets/bluetooth/bluetooth_status.imageset/bluetooth_status.png
vendored
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
BluefruitPlayground/Assets.xcassets/bluetooth/bluetooth_status.imageset/bluetooth_status@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
BluefruitPlayground/Assets.xcassets/bluetooth/bluetooth_status.imageset/bluetooth_status@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 24 KiB |
23
BluefruitPlayground/Assets.xcassets/boards/board_clue_back.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "board_clue_back.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "board_clue_back@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "board_clue_back@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
BluefruitPlayground/Assets.xcassets/boards/board_clue_back.imageset/board_clue_back.png
vendored
Normal file
|
After Width: | Height: | Size: 205 KiB |
BIN
BluefruitPlayground/Assets.xcassets/boards/board_clue_back.imageset/board_clue_back@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 687 KiB |
BIN
BluefruitPlayground/Assets.xcassets/boards/board_clue_back.imageset/board_clue_back@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
23
BluefruitPlayground/Assets.xcassets/boards/board_clue_front.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "board_clue_front.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "board_clue_front@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "board_clue_front@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
BluefruitPlayground/Assets.xcassets/boards/board_clue_front.imageset/board_clue_front.png
vendored
Normal file
|
After Width: | Height: | Size: 165 KiB |
BIN
BluefruitPlayground/Assets.xcassets/boards/board_clue_front.imageset/board_clue_front@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 563 KiB |
BIN
BluefruitPlayground/Assets.xcassets/boards/board_clue_front.imageset/board_clue_front@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
|
|
@ -2,17 +2,17 @@
|
|||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "cpb_circuit.png",
|
||||
"filename" : "board_cpb.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "cpb_circuit@2x.png",
|
||||
"filename" : "board_cpb@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "cpb_circuit@3x.png",
|
||||
"filename" : "board_cpb@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
BIN
BluefruitPlayground/Assets.xcassets/boards/board_cpb.imageset/board_cpb.png
vendored
Normal file
|
After Width: | Height: | Size: 191 KiB |
BIN
BluefruitPlayground/Assets.xcassets/boards/board_cpb.imageset/board_cpb@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 466 KiB |
BIN
BluefruitPlayground/Assets.xcassets/boards/board_cpb.imageset/board_cpb@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 745 KiB |
|
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 996 B |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
|
|
@ -9,10 +9,10 @@
|
|||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"red" : "0.910",
|
||||
"red" : "0.000",
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.824",
|
||||
"green" : "0.486"
|
||||
"blue" : "0.000",
|
||||
"green" : "0.839"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,12 +7,12 @@
|
|||
{
|
||||
"idiom" : "universal",
|
||||
"color" : {
|
||||
"color-space" : "display-p3",
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"red" : "0.010",
|
||||
"red" : "0.949",
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.802",
|
||||
"green" : "0.219"
|
||||
"blue" : "0.961",
|
||||
"green" : "0.961"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
},
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"color" : {
|
||||
"color-space" : "display-p3",
|
||||
"components" : {
|
||||
"red" : "0.889",
|
||||
"alpha" : "0.800",
|
||||
"blue" : "0.360",
|
||||
"green" : "0.400"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
|
@ -9,10 +9,10 @@
|
|||
"color" : {
|
||||
"color-space" : "display-p3",
|
||||
"components" : {
|
||||
"red" : "0.910",
|
||||
"red" : "0.545",
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.374",
|
||||
"green" : "0.429"
|
||||
"blue" : "1.000",
|
||||
"green" : "0.169"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,8 +11,8 @@
|
|||
"components" : {
|
||||
"red" : "1.000",
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.486",
|
||||
"green" : "0.918"
|
||||
"blue" : "0.953",
|
||||
"green" : "0.212"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
},
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"red" : "0x00",
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x80",
|
||||
"green" : "0x80"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
},
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"red" : "0.988",
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.118",
|
||||
"green" : "1.000"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -9,10 +9,10 @@
|
|||
"color" : {
|
||||
"color-space" : "display-p3",
|
||||
"components" : {
|
||||
"red" : "0.404",
|
||||
"red" : "0.180",
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.692",
|
||||
"green" : "0.910"
|
||||
"blue" : "1.000",
|
||||
"green" : "0.282"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
},
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"red" : "0xFF",
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xB4",
|
||||
"green" : "0xE5"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -9,10 +9,10 @@
|
|||
"color" : {
|
||||
"color-space" : "display-p3",
|
||||
"components" : {
|
||||
"red" : "0.138",
|
||||
"red" : "0.244",
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.726",
|
||||
"green" : "0.218"
|
||||
"blue" : "0.965",
|
||||
"green" : "0.544"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
},
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"red" : "0x00",
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x00",
|
||||
"green" : "0x80"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
},
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"red" : "0xFF",
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x00",
|
||||
"green" : "0x00"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -9,10 +9,10 @@
|
|||
"color" : {
|
||||
"color-space" : "display-p3",
|
||||
"components" : {
|
||||
"red" : "0.564",
|
||||
"red" : "1.000",
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.910",
|
||||
"green" : "0.362"
|
||||
"blue" : "0.094",
|
||||
"green" : "0.341"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
},
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"color" : {
|
||||
"color-space" : "display-p3",
|
||||
"components" : {
|
||||
"red" : "0.275",
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.773",
|
||||
"green" : "1.000"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 151 KiB |