Compare commits

..

1 commit

Author SHA1 Message Date
Trev_Knows
f6d10619ff Version 1.1.0
- Puppet Module
2020-02-04 14:25:18 -05:00
383 changed files with 10134 additions and 18031 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View file

@ -1,26 +0,0 @@
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

View file

@ -28,8 +28,6 @@
<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>

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1500"
LastUpgradeVersion = "1130"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1500"
LastUpgradeVersion = "1120"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1500"
LastUpgradeVersion = "1130"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

BIN
BluefruitPlayground/.DS_Store vendored Normal file

Binary file not shown.

View file

@ -1,97 +0,0 @@
//
// 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])
}
}

View file

@ -1,82 +0,0 @@
//
// 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()
}
}

View file

@ -1,142 +0,0 @@
//
// 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)
}
}

View file

@ -1,88 +0,0 @@
//
// 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)
}
}

View file

@ -1,275 +0,0 @@
//
// 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
}
}

View file

@ -1,96 +0,0 @@
//
// 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])
}
}

View file

@ -1,82 +0,0 @@
//
// 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()
}
}

View file

@ -1,83 +0,0 @@
//
// 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()
}
}

View file

@ -1,96 +0,0 @@
//
// 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])
}
}

View file

@ -1,187 +0,0 @@
//
// 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)
}
}

View file

@ -1,100 +0,0 @@
//
// 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])
}
}

View file

@ -1,154 +0,0 @@
//
// 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
}
}

View file

@ -1,82 +0,0 @@
//
// 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()
}
}

View file

@ -1,64 +0,0 @@
//
// 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)")
}
}

View file

@ -1,121 +0,0 @@
//
// 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
}
}

View file

@ -8,47 +8,43 @@
import Foundation
import CoreBluetooth
import QuartzCore
public class BleManager: NSObject {
#if COMMANDLINE
#else
import MSWeakTimer
#endif
class BleManager: NSObject {
// Configuration
private static let kStopScanningWhenConnectingToPeripheral = false
private static let kAlwaysAllowDuplicateKeys = true
// Singleton
public static let shared = BleManager()
static let shared = BleManager()
// Ble
var centralManager: CBCentralManager?
private var centralManagerPoweredOnSemaphore = DispatchSemaphore(value: 1)
// Scanning
public var isScanning: Bool {
return scanningStartTime != nil
}
public var scanningElapsedTime: TimeInterval? {
guard let scanningStartTime = scanningStartTime else { return nil }
return CACurrentMediaTime() - scanningStartTime
}
var isScanning = false
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 peripheralsFoundFirstTime = [UUID: Date]() // Date that the perihperal was discovered for the first time. Useful for sorting
internal var peripheralsFoundLock = NSLock()
private var peripheralsFoundLock = NSLock()
// Connecting
private var connectionTimeoutTimers = [UUID: Foundation.Timer]()
private var autoreconnectOnDisconnection = Set<UUID>() // List of peripheral IDs to automatically reconnect if disconnected
private var connectionTimeoutTimers = [UUID: MSWeakTimer]()
// Notifications
public enum NotificationUserInfoKey: String {
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: [:])
}
@ -56,7 +52,6 @@ public class BleManager: NSObject {
deinit {
scanningServicesFilter?.removeAll()
peripheralsFound.removeAll()
peripheralsFoundFirstTime.removeAll()
}
public var state: CBManagerState {
@ -65,6 +60,11 @@ public class BleManager: NSObject {
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()
@ -96,7 +96,10 @@ public class BleManager: NSObject {
}
// MARK: - Scan
public func startScan(withServices services: [CBUUID]? = nil) {
func startScan(withServices services: [CBUUID]? = nil) {
centralManagerPoweredOnSemaphore.wait()
centralManagerPoweredOnSemaphore.signal()
isScanningWaitingToStart = true
guard let centralManager = centralManager, centralManager.state != .poweredOff && centralManager.state != .unauthorized && centralManager.state != .unsupported else {
DLog("startScan failed because central manager is not ready")
@ -111,7 +114,7 @@ public class BleManager: NSObject {
}
// DLog("start scan")
scanningStartTime = CACurrentMediaTime()
isScanning = true
NotificationCenter.default.post(name: .didStartScanning, object: nil)
let options = BleManager.kAlwaysAllowDuplicateKeys ? [CBCentralManagerScanOptionAllowDuplicatesKey: true] : nil
@ -119,55 +122,32 @@ public class BleManager: NSObject {
isScanningWaitingToStart = false
}
public func stopScan() {
func stopScan() {
// DLog("stop scan")
centralManager?.stopScan()
scanningStartTime = nil
isScanning = false
isScanningWaitingToStart = false
NotificationCenter.default.post(name: .didStopScanning, object: nil)
}
public func numPeripherals() -> Int {
return peripheralsFound.count
}
public func peripherals() -> [BlePeripheral] {
func peripherals() -> [BlePeripheral] {
peripheralsFoundLock.lock(); defer { peripheralsFoundLock.unlock() }
return Array(peripheralsFound.values)
}
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] {
func connectedPeripherals() -> [BlePeripheral] {
return peripherals().filter {$0.state == .connected}
}
public func connectingPeripherals() -> [BlePeripheral] {
func connectingPeripherals() -> [BlePeripheral] {
return peripherals().filter {$0.state == .connecting}
}
public func connectedOrConnectingPeripherals() -> [BlePeripheral] {
func connectedOrConnectingPeripherals() -> [BlePeripheral] {
return peripherals().filter {$0.state == .connected || $0.state == .connecting}
}
public func refreshPeripherals() {
func refreshPeripherals() {
stopScan()
peripheralsFoundLock.lock()
@ -175,7 +155,6 @@ public 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()
@ -186,12 +165,10 @@ public class BleManager: NSObject {
}
// MARK: - Connection Management
public func connect(to peripheral: BlePeripheral, timeout: TimeInterval? = nil, shouldNotifyOnConnection: Bool = false, shouldNotifyOnDisconnection: Bool = false, shouldNotifyOnNotification: Bool = false) {
func connect(to peripheral: BlePeripheral, timeout: TimeInterval? = nil, shouldNotifyOnConnection: Bool = false, shouldNotifyOnDisconnection: Bool = false, shouldNotifyOnNotification: Bool = false) {
guard centralManager?.state == .poweredOn else {
DLog("connect failed because central manager is not ready")
return
}
centralManagerPoweredOnSemaphore.wait()
centralManagerPoweredOnSemaphore.signal()
// Stop scanning when connecting to a peripheral
if BleManager.kStopScanningWhenConnectingToPeripheral {
@ -212,14 +189,14 @@ public class BleManager: NSObject {
#endif
if let timeout = timeout {
self.connectionTimeoutTimers[peripheral.identifier] = Timer.scheduledTimer(timeInterval: timeout, target: self, selector: #selector(self.connectionTimeoutFired), userInfo: peripheral.identifier, repeats: false)
connectionTimeoutTimers[peripheral.identifier] = MSWeakTimer.scheduledTimer(withTimeInterval: timeout, target: self, selector: #selector(connectionTimeoutFired), userInfo: peripheral.identifier, repeats: false, dispatchQueue: .global(qos: .background))
}
centralManager?.connect(peripheral.peripheral, options: options)
}
@objc private func connectionTimeoutFired(timer: Foundation.Timer) {
let peripheralIdentifier = timer.userInfo as! UUID
DLog("connection timeout for: \(peripheralIdentifier)")
@objc private func connectionTimeoutFired(timer: MSWeakTimer) {
let peripheralIdentifier = timer.userInfo() as! UUID
DLog("connection timeout fired: \(peripheralIdentifier)")
connectionTimeoutTimers[peripheralIdentifier] = nil
NotificationCenter.default.post(name: .willDisconnectFromPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheralIdentifier])
@ -227,57 +204,37 @@ public 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])
}
}
public func disconnect(from peripheral: BlePeripheral, waitForQueuedCommands: Bool = false) {
guard let centralManager = centralManager else { return}
DLog("disconnect: \(peripheral.identifier)")
autoreconnectOnDisconnection.remove(peripheral.identifier) // Disable autoreconnection because user initiated the disconection
func disconnect(from peripheral: BlePeripheral) {
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
peripheral.disconnect(centralManager: centralManager)
} else {
centralManager.cancelPeripheralConnection(peripheral.peripheral)
}
centralManager?.cancelPeripheralConnection(peripheral.peripheral)
}
func discoverConnectedPeripherals(services: [CBUUID]) {
guard let centralManager = centralManager else { return}
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 )
}
}
}
}
func reconnecToPeripherals(peripheralsData: [(identifier: UUID, advertisementData: [String: Any])], withServices services: [CBUUID], timeout: Double? = nil) -> Bool {
guard let centralManager = centralManager else { return false }
func reconnecToPeripherals(withIdentifiers identifiers: [UUID], withServices services: [CBUUID], timeout: Double? = nil) -> Bool {
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)
let knownPeripherals = centralManager?.retrievePeripherals(withIdentifiers: identifiers)
if let peripherals = knownPeripherals?.filter({identifiers.contains($0.identifier)}), !peripherals.isEmpty {
for peripheral in peripherals {
discovered(peripheral: peripheral)
if let blePeripheral = peripheralsFound[peripheral.identifier] {
connect(to: blePeripheral, timeout: timeout)
reconnecting = true
}
}
} else {
let connectedPeripherals = centralManager?.retrieveConnectedPeripherals(withServices: services)
if let peripherals = connectedPeripherals?.filter({identifiers.contains($0.identifier)}), !peripherals.isEmpty {
for peripheral in peripherals {
discovered(peripheral: peripheral)
if let blePeripheral = peripheralsFound[peripheral.identifier] {
connect(to: blePeripheral, timeout: timeout)
reconnecting = true
@ -286,24 +243,6 @@ public 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
}
@ -313,7 +252,7 @@ public class BleManager: NSObject {
if let existingPeripheral = peripheralsFound[peripheral.identifier] {
existingPeripheral.lastSeenTime = CFAbsoluteTimeGetCurrent()
if let rssi = rssi, rssi != BlePeripheral.kUndefinedRssiValue { // only update rssi value if is defined ( 127 means undefined )
if let rssi = rssi, rssi != 127 { // only update rssi value if is defined ( 127 means undefined )
existingPeripheral.rssi = rssi
}
@ -326,35 +265,30 @@ public 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
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 }
func peripheral(from notification: Notification) -> BlePeripheral? {
guard let uuid = notification.userInfo?[NotificationUserInfoKey.uuid.rawValue] as? UUID else { return nil }
return peripheral(with: uuid)
}
public func error(from notification: Notification) -> Error? {
return notification.userInfo?[NotificationUserInfoKey.error.rawValue] as? Error
}
public func peripheral(with uuid: UUID) -> BlePeripheral? {
func peripheral(with uuid: UUID) -> BlePeripheral? {
return peripheralsFound[uuid]
}
}
// MARK: - CBCentralManagerDelegate
extension BleManager: CBCentralManagerDelegate {
public func centralManagerDidUpdateState(_ central: CBCentralManager) {
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 {
@ -365,23 +299,18 @@ extension BleManager: CBCentralManagerDelegate {
if isScanning {
isScanningWaitingToStart = true
}
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()
isScanning = false
}
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]) {
}*/
public func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
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
@ -390,8 +319,8 @@ extension BleManager: CBCentralManagerDelegate {
}
}
public func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
DLog("didConnect: \(peripheral.name ?? peripheral.identifier.uuidString)")
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
DLog("didConnect: \(peripheral.identifier)")
// Remove connection timeout if exists
if let timer = connectionTimeoutTimers[peripheral.identifier] {
@ -399,73 +328,42 @@ extension BleManager: CBCentralManagerDelegate {
connectionTimeoutTimers[peripheral.identifier] = nil
}
// Set reconnection flag
autoreconnectOnDisconnection.insert(peripheral.identifier)
// Send notification
DispatchQueue.main.async {
NotificationCenter.default.post(name: .didConnectToPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
}
NotificationCenter.default.post(name: .didConnectToPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
}
public func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
DLog("didFailToConnect: \(peripheral.name ?? peripheral.identifier.uuidString). \(String(describing: error))")
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
DLog("didFailToConnect")
NotificationCenter.default.post(name: .didDisconnectFromPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
}
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
DLog("didDisconnectPeripheral")
// Clean
peripheralsFound[peripheral.identifier]?.reset()
// Notify
DispatchQueue.main.async {
NotificationCenter.default.post(name: .didDisconnectFromPeripheral, object: nil, userInfo: [
NotificationUserInfoKey.uuid.rawValue: peripheral.identifier,
NotificationUserInfoKey.error.rawValue: error as Any
])
}
}
NotificationCenter.default.post(name: .didDisconnectFromPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
public func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
let peripheralIdentifier = peripheral.identifier
DLog("didDisconnectPeripheral: \(peripheral.name ?? peripheralIdentifier.uuidString)")
// 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()
}
}
// Remove from peripheral list (after sending notification so the receiving objects can query about the peripheral before being removed)
peripheralsFoundLock.lock()
peripheralsFound.removeValue(forKey: peripheral.identifier)
peripheralsFoundLock.unlock()
}
}
// MARK: - Custom Notifications
extension Notification.Name {
private static let kPrefix = Bundle.main.bundleIdentifier!
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")
static let didUpdateBleState = Notification.Name(kPrefix+".didUpdateBleState")
static let didStartScanning = Notification.Name(kPrefix+".didStartScanning")
static let didStopScanning = Notification.Name(kPrefix+".didStopScanning")
static let didDiscoverPeripheral = Notification.Name(kPrefix+".didDiscoverPeripheral")
static let didUnDiscoverPeripheral = Notification.Name(kPrefix+".didUnDiscoverPeripheral")
static let willConnectToPeripheral = Notification.Name(kPrefix+".willConnectToPeripheral")
static let didConnectToPeripheral = Notification.Name(kPrefix+".didConnectToPeripheral")
static let willDisconnectFromPeripheral = Notification.Name(kPrefix+".willDisconnectFromPeripheral")
static let didDisconnectFromPeripheral = Notification.Name(kPrefix+".didDisconnectFromPeripheral")
}

View file

@ -72,13 +72,10 @@ extension BlePeripheral {
}
// MARK: - Initialization
func uartEnable(uartServiceUuid: CBUUID = BlePeripheral.kUartServiceUUID,
txCharacteristicUuid: CBUUID = BlePeripheral.kUartTxCharacteristicUUID,
rxCharacteristicUuid: CBUUID = BlePeripheral.kUartRxCharacteristicUUID,
uartRxHandler: ((Data?, UUID, Error?) -> Void)?, completion: ((Error?) -> Void)?) {
func uartEnable(uartRxHandler: ((Data?, UUID, Error?) -> Void)?, completion: ((Error?) -> Void)?) {
// Get uart communications characteristic
characteristic(uuid: txCharacteristicUuid, serviceUuid: uartServiceUuid) { [unowned self] (characteristic, error) in
characteristic(uuid: BlePeripheral.kUartTxCharacteristicUUID, serviceUuid: BlePeripheral.kUartServiceUUID) { [unowned self] (characteristic, error) in
guard let characteristic = characteristic, error == nil else {
completion?(error != nil ? error : PeripheralUartError.invalidCharacteristic)
return
@ -88,7 +85,7 @@ extension BlePeripheral {
self.uartTxCharacteristicWriteType = characteristic.properties.contains(.writeWithoutResponse) ? .withoutResponse:.withResponse
//self.uartTxCharacteristicWriteType = .withResponse // Debug: force withResponse
self.characteristic(uuid: rxCharacteristicUuid, serviceUuid: uartServiceUuid) { [unowned self] (characteristic, error) in
self.characteristic(uuid: BlePeripheral.kUartRxCharacteristicUUID, serviceUuid: BlePeripheral.kUartServiceUUID) { [unowned self] (characteristic, error) in
guard let characteristic = characteristic, error == nil else {
completion?(error != nil ? error : PeripheralUartError.invalidCharacteristic)
return
@ -125,17 +122,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 }
// Clear all Uart specific data
self.uartRxCharacteristic = nil
self.uartTxCharacteristic = nil
self.uartTxCharacteristicWriteType = nil
}
disableNotify(for: characteristic)
}
// MARK: - Send

View file

@ -8,18 +8,19 @@
import Foundation
import CoreBluetooth
#if COMMANDLINE
#else
import MSWeakTimer
#endif
// TODO: Modernize completion blocks to use Swift.Result
open class BlePeripheral: NSObject {
class BlePeripheral: NSObject {
// Config
private static var kProfileCharacteristicUpdates = true
// Constants
static var kUndefinedRssiValue = 127
// Notifications
public enum NotificationUserInfoKey: String {
enum NotificationUserInfoKey: String {
case uuid = "uuid"
case name = "name"
case invalidatedServices = "invalidatedServices"
@ -31,48 +32,22 @@ open 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
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 {
var identifier: UUID {
return peripheral.identifier
}
open var name: String? {
var name: String? {
return peripheral.name
}
public var debugName: String {
return peripheral.name ?? peripheral.identifier.uuidString
}
open var state: CBPeripheralState {
var state: CBPeripheralState {
return peripheral.state
}
func maximumWriteValueLength(for: CBCharacteristicWriteType) -> Int {
return peripheral.maximumWriteValueLength(for: .withoutResponse)
}
public struct Advertisement {
struct Advertisement {
var advertisementData: [String: Any]
init(advertisementData: [String: Any]?) {
@ -80,73 +55,71 @@ open class BlePeripheral: NSObject {
}
// Advertisement data formatted
public var localName: String? {
var localName: String? {
return advertisementData[CBAdvertisementDataLocalNameKey] as? String
}
public var manufacturerData: Data? {
var manufacturerData: Data? {
return advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data
}
public var manufacturerHexDescription: String? {
var manufacturerHexDescription: String? {
guard let manufacturerData = manufacturerData else { return nil }
return HexUtils.hexDescription(data: manufacturerData)
// return String(data: manufacturerData, encoding: .utf8)
}
public var manufacturerIdentifier: Data? {
var manufacturerIdentifier: Data? {
guard let manufacturerData = manufacturerData, manufacturerData.count >= 2 else { return nil }
let manufacturerIdentifierData = manufacturerData[0..<2]
return manufacturerIdentifierData
}
public var services: [CBUUID]? {
var services: [CBUUID]? {
return advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID]
}
public var servicesOverflow: [CBUUID]? {
var servicesOverflow: [CBUUID]? {
return advertisementData[CBAdvertisementDataOverflowServiceUUIDsKey] as? [CBUUID]
}
public var servicesSolicited: [CBUUID]? {
var servicesSolicited: [CBUUID]? {
return advertisementData[CBAdvertisementDataSolicitedServiceUUIDsKey] as? [CBUUID]
}
public var serviceData: [CBUUID: Data]? {
var serviceData: [CBUUID: Data]? {
return advertisementData[CBAdvertisementDataServiceDataKey] as? [CBUUID: Data]
}
public var txPower: Int? {
var txPower: Int? {
let number = advertisementData[CBAdvertisementDataTxPowerLevelKey] as? NSNumber
return number?.intValue
}
public var isConnectable: Bool? {
var isConnectable: Bool? {
let connectableNumber = advertisementData[CBAdvertisementDataIsConnectable] as? NSNumber
return connectableNumber?.boolValue
}
}
public var advertisement: Advertisement
var advertisement: Advertisement
typealias CapturedReadCompletionHandler = ((_ value: Any?, _ error: Error?) -> Void)
private class CaptureReadHandler {
var identifier: String
var result: CapturedReadCompletionHandler
var timeoutTimer: Foundation.Timer?
var timeoutAction: ((String) -> Void)?
var timeoutTimer: MSWeakTimer?
var timeoutAction: ((String)->())?
var isNotifyOmitted: Bool
init(identifier: String, result: @escaping CapturedReadCompletionHandler, timeout: Double?, timeoutAction: ((String) -> Void)?, isNotifyOmitted: Bool = false) {
init(identifier: String, result: @escaping CapturedReadCompletionHandler, timeout: Double?, timeoutAction:((String)->())?, isNotifyOmitted: Bool = false) {
self.identifier = identifier
self.result = result
self.isNotifyOmitted = isNotifyOmitted
if let timeout = timeout {
self.timeoutAction = timeoutAction
DispatchQueue.global(qos: .background).async {
self.timeoutTimer = Timer.scheduledTimer(timeInterval: timeout, target: self, selector: #selector(self.timerFired), userInfo: nil, repeats: false)
}
timeoutTimer = MSWeakTimer.scheduledTimer(withTimeInterval: timeout, target: self, selector: #selector(timerFired), userInfo: nil, repeats: false, dispatchQueue: .global(qos: .background))
}
}
@ -176,13 +149,13 @@ open class BlePeripheral: NSObject {
//private var profileStartTime: CFTimeInterval = 0
// MARK: - Init
public init(peripheral: CBPeripheral, advertisementData: [String: Any]?, rssi: Int?) {
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
@ -234,12 +207,6 @@ open 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})
@ -342,7 +309,7 @@ open class BlePeripheral: NSObject {
}
// MARK: - Command Queue
internal class BleCommand: Equatable {
private class BleCommand: Equatable {
enum CommandType {
case discoverService
case discoverCharacteristic
@ -352,7 +319,6 @@ open class BlePeripheral: NSObject {
case writeCharacteristic
case writeCharacteristicAndWaitNofity
case readDescriptor
case disconnect
}
enum CommandError: Error {
@ -396,22 +362,18 @@ open class BlePeripheral: NSObject {
write(with: command)
case .readDescriptor:
readDescriptor(with: command)
case .disconnect:
disconnect(with: command)
}
}
private func handlerIdentifier(from characteristic: CBCharacteristic) -> String {
guard let service = characteristic.service else { DLog("Error: handleIdentifier with nil characteritic service"); return "" }
return "\(service.uuid.uuidString)-\(characteristic.uuid.uuidString)"
return "\(characteristic.service.uuid.uuidString)-\(characteristic.uuid.uuidString)"
}
private func handlerIdentifier(from descriptor: CBDescriptor) -> String {
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)"
return "\(descriptor.characteristic.service.uuid.uuidString)-\(descriptor.characteristic.uuid.uuidString)-\(descriptor.uuid.uuidString)"
}
internal func finishedExecutingCommand(error: Error?) {
private func finishedExecutingCommand(error: Error?) {
//DLog("finishedExecutingCommand")
// Result Callback
@ -439,7 +401,7 @@ open class BlePeripheral: NSObject {
if discoverAll || (serviceUuids != nil && serviceUuids!.count > 0) {
peripheral.discoverServices(serviceUuids)
} else {
// Everything was already discovered
// Everthing was already discovered
finishedExecutingCommand(error: nil)
}
}
@ -502,17 +464,9 @@ open 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
@ -525,9 +479,6 @@ open class BlePeripheral: NSObject {
finishedExecutingCommand(error: nil)
}
else {
peripheral.writeValue(data, for: characteristic, type: writeType)
}
}
private func readDescriptor(with command: BleCommand) {
@ -540,41 +491,35 @@ open 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 {
public func peripheralDidUpdateName(_ peripheral: CBPeripheral) {
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])
}
public func peripheral(_ peripheral: CBPeripheral, didModifyServices invalidatedServices: [CBService]) {
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])
}
public func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
//DLog("didDiscoverServices for: \(peripheral.name ?? peripheral.identifier.uuidString)")
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
DLog("didDiscoverServices for: \(peripheral.name ?? peripheral.identifier.uuidString)")
finishedExecutingCommand(error: error)
}
public func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
///DLog("didDiscoverCharacteristicsFor: \(service.uuid.uuidString)")
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
DLog("didDiscoverCharacteristicsFor: \(service.uuid.uuidString)")
finishedExecutingCommand(error: error)
}
public func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
finishedExecutingCommand(error: error)
}
public func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
let identifier = handlerIdentifier(from: characteristic)
@ -627,7 +572,7 @@ extension BlePeripheral: CBPeripheralDelegate {
}
}
public func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
if let command = commandQueue.first(), !command.isCancelled, command.type == .writeCharacteristicAndWaitNofity {
let characteristic = command.parameters![3] as! CBCharacteristic
let readCompletion = command.parameters![4] as! CapturedReadCompletionHandler
@ -642,11 +587,11 @@ extension BlePeripheral: CBPeripheralDelegate {
}
}
public func peripheral(_ peripheral: CBPeripheral, didDiscoverDescriptorsFor characteristic: CBCharacteristic, error: Error?) {
func peripheral(_ peripheral: CBPeripheral, didDiscoverDescriptorsFor characteristic: CBCharacteristic, error: Error?) {
finishedExecutingCommand(error: error)
}
public func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor descriptor: CBDescriptor, error: Error?) {
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor descriptor: CBDescriptor, error: Error?) {
let identifier = handlerIdentifier(from: descriptor)
if captureReadHandlers.count > 0, let index = captureReadHandlers.firstIndex(where: {$0.identifier == identifier}) {
@ -661,11 +606,11 @@ extension BlePeripheral: CBPeripheralDelegate {
}
}
public func peripheral(_ peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: Error?) {
func peripheral(_ peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: Error?) {
guard error == nil else { DLog("didReadRSSI error: \(error!.localizedDescription)"); return }
let rssi = RSSI.intValue
if rssi != BlePeripheral.kUndefinedRssiValue { // only update rssi value if is defined ( 127 means undefined )
if rssi != 127 { // only update rssi value if is defined ( 127 means undefined )
self.rssi = rssi
}
@ -676,7 +621,7 @@ extension BlePeripheral: CBPeripheralDelegate {
// MARK: - Custom Notifications
extension Notification.Name {
private static let kPrefix = Bundle.main.bundleIdentifier!
public static let peripheralDidUpdateName = Notification.Name(kPrefix+".peripheralDidUpdateName")
public static let peripheralDidModifyServices = Notification.Name(kPrefix+".peripheralDidModifyServices")
public static let peripheralDidUpdateRssi = Notification.Name(kPrefix+".peripheralDidUpdateRssi")
static let peripheralDidUpdateName = Notification.Name(kPrefix+".peripheralDidUpdateName")
static let peripheralDidModifyServices = Notification.Name(kPrefix+".peripheralDidModifyServices")
static let peripheralDidUpdateRssi = Notification.Name(kPrefix+".peripheralDidUpdateRssi")
}

View file

@ -8,7 +8,7 @@
import Foundation
protocol UartDataManagerDelegate: AnyObject {
protocol UartDataManagerDelegate: class {
func onUartRx(data: Data, peripheralIdentifier: UUID) // data contents depends on the isRxCacheEnabled flag
}

View file

@ -10,10 +10,6 @@ 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)
@ -29,20 +25,14 @@ 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
guard let self = self else { return }
if self.isResetPacketsOnReconnectionEnabled {
self.clearPacketsCache()
}
}
didConnectToPeripheralObserver = notificationCenter.addObserver(forName: .didConnectToPeripheral, object: nil, queue: .main) {[weak self] _ in self?.clearPacketsCache()}
} else {
if let didConnectToPeripheralObserver = didConnectToPeripheralObserver {notificationCenter.removeObserver(didConnectToPeripheralObserver)}
}
}
// MARK: - Send data
private func sendUart(blePeripheral: BlePeripheral, data: Data?, progress: ((Float)->Void)? = nil, completion: ((Error?) -> Void)? = nil) {
func send(blePeripheral: BlePeripheral, data: Data?, progress: ((Float)->Void)? = nil, completion: ((Error?) -> Void)? = nil) {
sentBytes += Int64(data?.count ?? 0)
blePeripheral.uartSend(data: data, progress: progress, completion: completion)
}
@ -61,16 +51,13 @@ class UartPacketManager: UartPacketManagerBase {
blePeripheral.uartSendAndWaitReply(data: data, writeProgress: writeProgress, writeCompletion: writeCompletion, readTimeout: readTimeout, readCompletion: readCompletion)
}
/*
Send data to MQTT (if enabled) and also to UART (if MQTT configuration allows it)
*/
func send(blePeripheral: BlePeripheral, data: Data, wasReceivedFromMqtt: Bool = false) {
func send(blePeripheral: BlePeripheral, text: String, wasReceivedFromMqtt: Bool = false) {
#if MQTT_ENABLED
if isMqttEnabled {
// Mqtt publish to TX
let mqttSettings = MqttSettings.shared
if mqttSettings.isPublishEnabled, let text = String(data: data, encoding: .utf8) {
if mqttSettings.isPublishEnabled {
if let topic = mqttSettings.getPublishTopic(index: MqttSettings.PublishFeed.tx.rawValue) {
let qos = mqttSettings.getPublishQos(index: MqttSettings.PublishFeed.tx.rawValue)
MqttManager.shared.publish(message: text, topic: topic, qos: qos)
@ -80,25 +67,27 @@ class UartPacketManager: UartPacketManagerBase {
#endif
// Create data and send to Uart
let uartPacket = UartPacket(peripheralId: blePeripheral.identifier, mode: .tx, data: data)
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()
// Add Packet
packetsSemaphore.wait()
packets.append(uartPacket)
packetsSemaphore.signal()
DispatchQueue.main.async {
self.delegate?.onUartPacket(uartPacket)
}
DispatchQueue.main.async {
self.delegate?.onUartPacket(uartPacket)
}
#if MQTT_ENABLED
let shouldBeSent = !wasReceivedFromMqtt || (isMqttEnabled && MqttSettings.shared.subscribeBehaviour == .transmit)
#else
let shouldBeSent = true
#endif
#if MQTT_ENABLED
let shouldBeSent = !wasReceivedFromMqtt || (isMqttEnabled && MqttSettings.shared.subscribeBehaviour == .transmit)
#else
let shouldBeSent = true
#endif
if shouldBeSent {
sendUart(blePeripheral: blePeripheral, data: data)
if shouldBeSent {
send(blePeripheral: blePeripheral, data: data)
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -6,7 +6,7 @@
// Copyright © 2019 Adafruit. All rights reserved.
//
import UIKit
import Foundation
import CoreBluetooth
class BleManagerSimulated: BleManager {
@ -19,65 +19,25 @@ class BleManagerSimulated: BleManager {
}
// MARK: - Scanning
override func startScan(withServices services: [CBUUID]? = nil) {
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])
isScanning = true
// Add simulated peripheral
let simulatedBlePeripheral = BlePeripheralSimulated()
peripheralsFound[simulatedBlePeripheral.identifier] = simulatedBlePeripheral
NotificationCenter.default.post(name: .didDiscoverPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: simulatedBlePeripheral.identifier])
}
override func stopScan() {
}
// 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(peripheralsData: [(identifier: UUID, advertisementData: [String: Any])], withServices services: [CBUUID], timeout: Double? = nil) -> Bool {
override func reconnecToPeripherals(withIdentifiers identifiers: [UUID], 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)
}
}

View file

@ -1,37 +0,0 @@
//
// 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)
}
}

View file

@ -1,50 +0,0 @@
//
// 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)
}
}

View file

@ -1,49 +0,0 @@
//
// 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)
}
}

View file

@ -1,52 +0,0 @@
//
// 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
}
}

View file

@ -1,53 +0,0 @@
//
// 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])
}
}

View file

@ -1,39 +0,0 @@
//
// 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)
}
}

View file

@ -1,53 +0,0 @@
//
// 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
}
}

View file

@ -1,27 +0,0 @@
//
// 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
}
}

View file

@ -1,5 +1,5 @@
//
// BlePeripheralSimulated+AdafruitButtons.swift
// BlePeripheral+CPBButtons.swift
// BluefruitPlayground
//
// Created by Antonio García on 15/11/2019.
@ -10,6 +10,8 @@ import Foundation
import CoreBluetooth
extension BlePeripheral {
// Constants
enum SlideSwitchState: Int32 {
case right = 0
case left = 1
@ -26,27 +28,28 @@ extension BlePeripheral {
var buttonB: ButtonState
}
// MARK: - Actions
func adafruitButtonsEnable(responseHandler: @escaping(Result<(ButtonsState, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
completion?(.success(()))
func cpbButtonsEnable(responseHandler: @escaping(Result<(ButtonsState, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
completion?(.success(()))
}
func adafruitButtonsIsEnabled() -> Bool {
func isCpbButtonsEnabled() -> Bool {
return true
}
func adafruitButtonsDisable() {
func cpbButtonsDisable() {
}
func adafruitButtonsReadState(completion: @escaping(Result<(ButtonsState, UUID), Error>) -> Void) {
guard let state = adafruitButtonsLastValue() else {
completion(.failure(PeripheralAdafruitError.invalidResponseData))
func cpbButtonsReadState(completion: @escaping(Result<(ButtonsState, UUID), Error>) -> Void) {
guard let state = cpbButtonsLastValue() else {
completion(.failure(PeripheralCPBError.invalidResponseData))
return
}
completion(.success((state, self.identifier)))
}
func adafruitButtonsLastValue() -> ButtonsState? {
func cpbButtonsLastValue() -> ButtonsState? {
return ButtonsState(slideSwitch: .left, buttonA: .pressed, buttonB: .released)
}

View file

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

View file

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

View file

@ -10,38 +10,24 @@ 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 simulatedIdentifier
return BlePeripheralSimulated.kSimulatedUUID
}
override var name: String? {
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
return "Simulated Peripheral"
}
private var simulatedState: CBPeripheralState = .disconnected
override var state: CBPeripheralState {
return simulatedState
return .connected
}
private var model: AdafruitManufacturerData.BoardModel
// MARK: - Lifecycle
init(model: AdafruitManufacturerData.BoardModel) {
self.model = model
init() {
// Mocking CBPeripheral: https://forums.developer.apple.com/thread/29851
guard let peripheral = ObjectBuilder.createInstance(ofClass: "CBPeripheral") as? CBPeripheral else {
assertionFailure("Unable to mock CBPeripheral")
@ -49,13 +35,9 @@ class BlePeripheralSimulated: BlePeripheral {
super.init(peripheral: nilPeripheral, advertisementData: nil, rssi: nil)
return
}
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 manufacturerDataBytes: [UInt8] = [0x22, 0x08, 0x04, 0x01, 0x00, 0x45, 0x80] // Adafruit CPB
let advertisementData = [CBAdvertisementDataManufacturerDataKey: Data(manufacturerDataBytes)]
super.init(peripheral: peripheral, advertisementData: advertisementData, rssi: 20)
}
@ -64,19 +46,5 @@ class BlePeripheralSimulated: BlePeripheral {
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)
}
}

View file

@ -8,7 +8,7 @@
import Foundation
protocol UartPacketManagerDelegate: AnyObject {
protocol UartPacketManagerDelegate: class {
func onUartPacket(_ packet: UartPacket)
}
@ -30,6 +30,7 @@ struct UartPacket { // A packet of data received or sent
}
}
class UartPacketManagerBase {
// Data
@ -48,6 +49,7 @@ class UartPacketManagerBase {
self.delegate = delegate
}
// MARK: - Received data
func rxPacketReceived(data: Data?, peripheralIdentifier: UUID?, error: Error?) {

View file

@ -8,6 +8,7 @@
import Foundation
extension Data {
func toFloatFrom32Bits() -> Float {
return Float(bitPattern: UInt32(littleEndian: self.withUnsafeBytes { $0.load(as: UInt32.self) }))
@ -22,3 +23,4 @@ extension Data {
}
}

View file

@ -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)
}
}

View file

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

View file

@ -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,6 +39,7 @@ 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 {

View file

@ -15,9 +15,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
startup()
ScreenFlowManager.enableBleStateManagement()
startup()
return true
}
@ -41,8 +40,6 @@ 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
@ -71,3 +68,4 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
}
}
}

View file

@ -1,23 +0,0 @@
{
"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"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

View file

@ -1,23 +0,0 @@
{
"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"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

View file

@ -1,23 +0,0 @@
{
"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"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 687 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

View file

@ -1,23 +0,0 @@
{
"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"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 563 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

View file

@ -1,23 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "board_cpb.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "board_cpb@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "board_cpb@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 466 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 745 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 996 B

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -7,12 +7,12 @@
{
"idiom" : "universal",
"color" : {
"color-space" : "srgb",
"color-space" : "display-p3",
"components" : {
"red" : "0.949",
"red" : "0.010",
"alpha" : "1.000",
"blue" : "0.961",
"green" : "0.961"
"blue" : "0.802",
"green" : "0.219"
}
}
}

View file

@ -1,20 +0,0 @@
{
"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"
}
}
}
]
}

View file

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

View file

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

View file

@ -1,20 +0,0 @@
{
"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"
}
}
}
]
}

View file

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

View file

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

View file

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

View file

@ -1,20 +0,0 @@
{
"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"
}
}
}
]
}

View file

@ -2,17 +2,17 @@
"images" : [
{
"idiom" : "universal",
"filename" : "scan_cpb.png",
"filename" : "cpb_circuit.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "scan_cpb@2x.png",
"filename" : "cpb_circuit@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "scan_cpb@3x.png",
"filename" : "cpb_circuit@3x.png",
"scale" : "3x"
}
],

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 744 KiB

View file

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

View file

@ -1,23 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "humidity_fill.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "humidity_fill@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "humidity_fill@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

View file

@ -1,23 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "humidity_outline.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "humidity_outline@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "humidity_outline@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

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