Data to Int / String in swift3 - ios

I'm working with BLE device. Device is sending values in specific UUID.
Here is my code for converting Data to Int or String for display.
...
else if characteristic.uuid == CBUUID(string: UUID_CHANGEIN_BATTERY_LEVEL) {
// let battery = UInt32(bigEndian: ((characteristic.value?.withUnsafeBytes { $0.pointee }) ?? 0))
let array : [UInt8] = [0, 0, 0]
var data = Data(bytes: array)
if characteristic.value != nil {
data.append(characteristic.value!)
}
let battery = UInt32(bigEndian: ((data.withUnsafeBytes { $0.pointee }) ?? 0))
print("battery level changed\nNew level is \(getInt(fromData: characteristic.value!, start: 0))!")
// lblBattery.text = (String(data: characteristic.value!, encoding: String.Encoding.utf8) ?? "0") + "%"
lblBattery.text = "\(getInt(fromData: characteristic.value!, start: 0))%"
}
...
func getInt(fromData data: Data, start: Int) -> Int32 {
let intBits = data.withUnsafeBytes({(bytePointer: UnsafePointer<UInt8>) -> Int32 in
bytePointer.advanced(by: start).withMemoryRebound(to: Int32.self, capacity: 4) { pointer in
return pointer.pointee
}
})
return Int32(bigEndian: intBits)
// return Int32(littleEndian: intBits)
}
I've checked in BLE Scanner iOS app, that is displaying 0x2b01 as value and it also display 43 as a battery %.
I wrote 3-4 kind of code as you see above.
But none of them are giving me 43 as a value.
Please help me find where I've done something wrong or which I'm missing to meet my requirement.

swift 3
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
let value = [UInt8](characteristic.value!)
print("Battery ", value[0], "%")
}

Related

Unwrapping optional value (returned by data.withUnsafeBytes(_:)) sometimes does not work with guard let

I have issue with guard let statement, which behaves strange. Whole code is below. Else block of statement guard let data = readData, let size = sizeOfData else ... in method readActivity(subdata: Data) is wrongly executed even thoug readData and sizeOfData are not nil.
Code
import Foundation
enum ActivityDataReaderError: Error {
case activityIsReadingOtherCentral
case bluetooth(Error?)
case staleData
}
protocol ActivityDataReaderDelegate: class {
func didReadActivity(data: Data)
func didFailToReadActivity(error: ActivityDataReaderError)
}
final class ActivityDataReader {
private var sizeOfData: Int?
private var isOtherDeviceReading: Bool {
// 0xFFFF
return sizeOfData == 65535
}
private var readData: Data?
var isEmpty: Bool {
return sizeOfData == nil
}
weak var delegate: ActivityDataReaderDelegate?
static func timestampValue(_ timestamp: UInt32) -> Data {
var value = timestamp
return Data(buffer: UnsafeBufferPointer(start: &value, count: 1))
}
func reset() {
readData = nil
sizeOfData = nil
NSLog("reset() -- \(Thread.current)")
}
func readActivity(data: Data?, error: Error? = nil) {
guard let data = data else {
delegate?.didFailToReadActivity(error: .bluetooth(error))
return
}
let isFirstChunk = readData == nil
if isFirstChunk {
let sizeData = data.subdata(in: 0..<2)
sizeOfData = sizeData.withUnsafeBytes { $0.pointee }
guard !isOtherDeviceReading else {
delegate?.didFailToReadActivity(error: .activityIsReadingOtherCentral)
return
}
NSLog(String("readActivity() Size of data: \(String(describing: sizeOfData))"))
let subdata = data.subdata(in: 2..<data.count)
readActivity(subdata: subdata)
} else {
readActivity(subdata: data)
}
}
private func readActivity(subdata: Data) {
if let lastReadData = readData {
readData = lastReadData + subdata
} else {
readData = subdata
}
guard let data = readData, let size = sizeOfData else {
NSLog("WTF? data:\(String(describing: readData)), "
+ "sizeOfData: \(String(describing: sizeOfData)), "
+ "thread: \(Thread.current)")
assertionFailure("WTF")
return
}
NSLog("subdata: \(String(describing: subdata)), "
+ "totalReadBytes: \(data.count), "
+ "size: \(size)")
if data.count == size {
delegate?.didReadActivity(data: data)
reset()
}
}
}
Test
Test which sometimes passes and sometimes crashes because of assertionFailure("WTF").
class ActivityDataServiceReaderTests: XCTestCase {
var service: ActivityDataReader?
override func setUp() {
super.setUp()
service = ActivityDataReader()
}
override func tearDown() {
service = nil
super.tearDown()
}
func testBufferIsNotEmpty() {
NSLog("testBufferIsNotEmpty thread: \(Thread.current)")
guard let service = service else { fatalError() }
let firstDataBytes = [UInt8.min]
let data1 = Data(bytes: [7, 0] + firstDataBytes)
service.readActivity(data: data1)
XCTAssertFalse(service.isEmpty)
service.reset()
XCTAssertTrue(service.isEmpty)
}
}
Log of console in case of crash
2018-10-25 14:53:30.033573+0200 GuardBug[84042:11188210] WTF? data:Optional(1 bytes), sizeOfData: Optional(7), thread: <NSThread: 0x600003399d00>{number = 1, name = main}
Environment
Xcode10
swift 4.1 with legacy build system
swift 4.2
In my opinion, there is no possible way to execute code in else block in guard let else block of method readActivity(subdata: Data). Everything is running on main thread. Am I misssing something? How is possible sometimes test passes and sometimes crasshes?
Thank you for any help.
Edit:
More narrow problem of guard let + data.withUnsafeBytes:
func testGuardLet() {
let data = Data(bytes: [7, 0, UInt8.min])
let sizeData = data.subdata(in: 0 ..< 2)
let size: Int? = sizeData.withUnsafeBytes { $0.pointee }
guard let unwrappedSize = size else {
NSLog("failure: \(size)")
XCTFail()
return
}
NSLog("success: \(unwrappedSize)")
}
Log:
2018-10-25 16:32:19.497540+0200 GuardBug[90576:11351167] failure: Optional(7)
Thanks to help at: https://forums.swift.org/t/unwrapping-value-with-guard-let-sometimes-does-not-work-with-result-from-data-withunsafebytes-0-pointee/17357 problem was with the line:
let size: Int? = sizeData.withUnsafeBytes { $0.pointee }
Where read data was downcasted to Optional Int (8 bytes long) but sizeData it self was just 2 bytes long. I have no idea how is possible it sometimes worked but solution -- which seems to work properly -- is to use method withUnsafeBytes in fallowing way:
let size = sizeData.withUnsafeBytes { (pointer: UnsafePointer<UInt16>) in pointer.pointee }
Returned value is not optional and has the proper type UInt16 (2 bytes long).
If you check Documentation, there is a warning:
Warning The byte pointer argument should not be stored and used
outside of the lifetime of the call to the closure.
Seems like You should deal with the size inside the closure body
func testGuardLet() {
let data = Data(bytes: [7, 0, UInt8.min])
var sizeData = data.subdata(in: 0 ..< 2)
withUnsafeBytes(of: &sizeData) { bytes in
print(bytes.count)
for byte in bytes {
print(byte)
}
}
let bytes = withUnsafeBytes(of: &sizeData) { bytes in
return bytes // BUGS ☠️☠️☠️
}
}

How to extract SFLOAT value from Bluetooth LE data for 1822 PulseOximeter using CoreBluetooth

I'm working on an iOS app that is to read pulse oximeter data from a Bluetooth LE enabled device, using CoreBluetooth on iOS 11.4 in Swift 4.1.
I've got the CBCentralManager searching for peripherals, I find the CBPeripheral I am interested in, I verify that it has the 0x1822 Pulse Oximeter Service, as described by Bluetooth SIG here. (You may need to register with Bluetooth SIG to access that link. It's free but takes a day or two.)
After that, I connect to it, then I discover services:
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
peripheral.discoverServices(nil)
}
Then in my peripheral:didDiscoverServices I discover GATT characteristics:
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?){
for service in peripheral.services ?? [] {
if service.uuid.uuidString == "1822" {
peripheral.discoverCharacteristics(nil, for: service)
}
}
}
From that I see the the following characteristics (CBCharacteristic.uuid) available: 0x2A5F, 0x2A5E, 0x2a60, and 0x2A52. I then subscribe to updates for 0x2A5F, which is PLX Continuous Measurement, which is described here:
if service.uuid.uuidString == "1822" && characteristic.uuid.uuidString == "2A5F" {
// pulseox continuous
print("[SUBSCRIBING TO UPDATES FOR SERVICE 1822 'PulseOx' for Characteristic 2A5F 'PLX Continuous']")
peripheral.setNotifyValue(true, for: characteristic)
}
I then begin to receive back 20-byte packets in my peripheral:didUpdateValueFor method:
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
if characteristic.service.uuid.uuidString == "1822" && characteristic.uuid.uuidString == "2A5F" {
if let data = characteristic.value {
var values = [UInt8](repeating:0, count:data.count)
data.copyBytes(to: &values, count: data.count)
}
}
}
From the reference doc you can see the first byte is a bunch of bitfields describing which optional values are included in the packet. The next 2 bytes are an SFLOAT value for the SpO2PR-Normal - SpO2 (oxygenation) reading, and the following 2 bytes are another SFLOAT value for the
SpO2PR-Normal - PR (pulse rate) value.
Bluetooth SIG lists an SFLOAT as an IEEE-11073 16-bit SFLOAT here. IEEE's document on IEEE-11073 is not publicly listed, and is available for purchase, but I'd prefer to avoid that.
Any idea how to decode? I found another question on Stack Overflow referencing the normal 32-bit Float, but that question was for a different type of Float, and its answer was not applicable.
Here it is:
Xcode 9.4.1 or Xcode 10.0 beta 3
iOS 11.4.1 or iOS 12.0 beta 3
Swift 4.1.2 or Swift 4.2
func floatFromTwosComplementUInt16(_ value: UInt16, havingBitsInValueIncludingSign bitsInValueIncludingSign: Int) -> Float {
// calculate a signed float from a two's complement signed value
// represented in the lowest n ("bitsInValueIncludingSign") bits
// of the UInt16 value
let signMask: UInt16 = UInt16(0x1) << (bitsInValueIncludingSign - 1)
let signMultiplier: Float = (value & signMask == 0) ? 1.0 : -1.0
var valuePart = value
if signMultiplier < 0 {
// Undo two's complement if it's negative
var valueMask = UInt16(1)
for _ in 0 ..< bitsInValueIncludingSign - 2 {
valueMask = valueMask << 1
valueMask += 1
}
valuePart = ((~value) & valueMask) &+ 1
}
let floatValue = Float(valuePart) * signMultiplier
return floatValue
}
func extractSFloat(values: [UInt8], startingIndex index: Int) -> Float {
// IEEE-11073 16-bit SFLOAT -> Float
let full = UInt16(values[index+1]) * 256 + UInt16(values[index])
// Check special values defined by SFLOAT first
if full == 0x07FF {
return Float.nan
} else if full == 0x800 {
return Float.nan // This is really NRes, "Not at this Resolution"
} else if full == 0x7FE {
return Float.infinity
} else if full == 0x0802 {
return -Float.infinity // This is really negative infinity
} else if full == 0x801 {
return Float.nan // This is really RESERVED FOR FUTURE USE
}
// Get exponent (high 4 bits)
let expo = (full & 0xF000) >> 12
let expoFloat = floatFromTwosComplementUInt16(expo, havingBitsInValueIncludingSign: 4)
// Get mantissa (low 12 bits)
let mantissa = full & 0x0FFF
let mantissaFloat = floatFromTwosComplementUInt16(mantissa, havingBitsInValueIncludingSign: 12)
// Put it together
let finalValue = mantissaFloat * pow(10.0, expoFloat)
return finalValue
}
The extraSFloat method takes a Uint8 array and an index into that array to indicate where the SFLOAT is. For example, if the array was two bytes (just the two bytes of the SFLOAT), then you would say:
let floatValue = extractSFloat(values: array, startingIndex: 0)
I did it this way because when working with Bluetooth data I always ended up with an array of UInt8 values containing the data I needed to decode.
This is how I do it in Swift 5 (XCode 12.4)
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
guard characteristic.service.uuid == CBUUID(string: "1822"),
characteristic.uuid == CBUUID(string: "2A5F"),
let data = characteristic.value else {
return
}
let numberOfBytes = data.count
var byteArray = [UInt8](repeating: 0, count: numberOfBytes)
(data as NSData).getBytes(&byteArray, length: numberOfBytes)
logger.debug("Data: \(byteArray)")
let oxygenation = byteArray[1]
let heartRate = byteArray[3]
}
Taken from my tutorial "Reverse Engineering Bluetooth Devices - An Introduction to CoreBluetooth"

can't get returned values in a structure in swift 3

I'm having this code:
//
// Measurement.swift
// BicycleSpeed
import Foundation
import CoreBluetooth
// CSC Measurement
// https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.csc_measurement.xml
//
// Flags : 1 byte. Bit 0: Wheel. Bit 1: Crank
// Cumulative Wheel revolutions: 4 bytes uint32
// Last wheel event time: 2 bytes. uint16 (1/1024s)
// Cumulative Crank revolutions: 2 bytes uint16
// Last cranck event time: 2 bytes. uint16 (1/1024s)
struct Measurement : CustomDebugStringConvertible {
let hasWheel:Bool
let hasCrank:Bool
let cumulativeWheel:UInt32
let lastWheelEventTime:TimeInterval
let cumulativeCrank:UInt16
let lastCrankEventTime:TimeInterval
let wheelSize:UInt32
init(data:Data, wheelSize:UInt32) {
self.wheelSize = wheelSize
// Flags
var flags:UInt8=0
(data as NSData).getBytes(&flags, range: NSRange(location: 0, length: 1))
hasWheel = ((flags & BTConstants.WheelFlagMask) > 0)
hasCrank = ((flags & BTConstants.CrankFlagMask) > 0)
var wheel:UInt32=0
var wheelTime:UInt16=0
var crank:UInt16=0
var crankTime:UInt16=0
var currentOffset = 1
var length = 0
if ( hasWheel ) {
length = MemoryLayout<UInt32>.size
(data as NSData).getBytes(&wheel, range: NSRange(location: currentOffset, length: length))
currentOffset += length
length = MemoryLayout<UInt16>.size
(data as NSData).getBytes(&wheelTime, range: NSRange(location: currentOffset, length: length))
currentOffset += length
}
if ( hasCrank ) {
length = MemoryLayout<UInt16>.size
(data as NSData).getBytes(&crank, range: NSRange(location: currentOffset, length: length))
currentOffset += length
length = MemoryLayout<UInt16>.size
(data as NSData).getBytes(&crankTime, range: NSRange(location: currentOffset, length: length))
currentOffset += length
}
cumulativeWheel = CFSwapInt32LittleToHost(wheel)
lastWheelEventTime = TimeInterval( Double(CFSwapInt16LittleToHost(wheelTime))/BTConstants.TimeScale)
cumulativeCrank = CFSwapInt16LittleToHost(crank)
lastCrankEventTime = TimeInterval( Double(CFSwapInt16LittleToHost(crankTime))/BTConstants.TimeScale)
}
func timeIntervalForCurrentSample( _ current:TimeInterval, previous:TimeInterval ) -> TimeInterval {
var timeDiff:TimeInterval = 0
if( current >= previous ) {
timeDiff = current - previous
}
else {
// passed the maximum value
timeDiff = ( TimeInterval((Double( UINT16_MAX) / BTConstants.TimeScale)) - previous) + current
}
return timeDiff
}
func valueDiffForCurrentSample<T:UnsignedInteger>( _ current:T, previous:T , max:T) -> T {
var diff:T = 0
if ( current >= previous ) {
diff = current - previous
}
else {
diff = ( max - previous ) + current
}
return diff
}
func valuesForPreviousMeasurement( _ previousSample:Measurement? ) -> ( cadenceinRPM:Double?, distanceinMeters:Double?, speedInMetersPerSecond:Double?)? {
var distance:Double?, cadence:Double?, speed:Double?
guard let previousSample = previousSample else {
return nil
}
if ( hasWheel && previousSample.hasWheel ) {
let wheelTimeDiff = timeIntervalForCurrentSample(lastWheelEventTime, previous: previousSample.lastWheelEventTime)
let valueDiff = valueDiffForCurrentSample(cumulativeWheel, previous: previousSample.cumulativeWheel, max: UInt32.max)
distance = Double( valueDiff * wheelSize) / 1000.0 // distance in meters
if distance != nil && wheelTimeDiff > 0 {
speed = (wheelTimeDiff == 0 ) ? 0 : distance! / wheelTimeDiff // m/s
}
}
if( hasCrank && previousSample.hasCrank ) {
let crankDiffTime = timeIntervalForCurrentSample(lastCrankEventTime, previous: previousSample.lastCrankEventTime)
let valueDiff = Double(valueDiffForCurrentSample(cumulativeCrank, previous: previousSample.cumulativeCrank, max: UInt16.max))
cadence = (crankDiffTime == 0) ? 0 : Double(60.0 * valueDiff / crankDiffTime) // RPM
}
print( "Cadence: \(String(describing: cadence)) RPM. Distance: \(String(describing: distance)) meters. Speed: \(String(describing: speed)) Km/h" )
return ( cadenceinRPM:cadence, distanceinMeters:distance, speedInMetersPerSecond:speed)
}
var debugDescription:String {
get {
return "Wheel Revs: \(cumulativeWheel). Last wheel event time: \(lastWheelEventTime). Crank Revs: \(cumulativeCrank). Last Crank event time: \(lastCrankEventTime)"
}
}
var myMeasurement = ((Measurement?) -> (cadenceinRPM: Double?, DistanceinMeters: Double?, speedinMetersPerSecond: Double?)).self
struct dataVariables {
static var mySpeed = myMeasurement.speedInMetersPerSecond
static var myCadence : Double?
static var miDistance : Double?
static var myLastWheelEventTime : Double? = Measurement.valuesForPreviousMeasurement(lastWheelEventTime)
static var myLastCrankEventTime : Double? = Measurement.valuesForPreviousMeasurement(lastCrankEventTime)
}
}
}
and I'm trying to assign to static variables inside the struct dataVariables the returned values cadenceinRPM,distanceinMeters,speedinMetersPerSecond
as well as lastWheelEventTime and lastCrankEventTime, so I can access them in another class, but I'm having the following errors:
on var mySpeed : Instance member'myMeasurement' cannot be used on type 'Measurement'
on var myLastWheelEventTime and var myLastCrankEventTime : Instance member 'valuesForPreviousMeasurement' cannot be used on type 'Measurement' ; did you mean to use a value of this type instead?
how can a reference to those returned values than?
Can somebody explain the error? I searched for other similar questions but I'm not getting to a solution yet
I have tried to change var myVariable to
var myMeasurement = Measurement.self
but the errors stayed the same.
and this other code
//
// CadenceSensor.swift
import Foundation
import CoreBluetooth
/*
// Bluetooth "Cycling Speed and Cadence"
https://developer.bluetooth.org/gatt/services/Pages/ServiceViewer.aspx?u=org.bluetooth.service.cycling_speed_and_cadence.xml
Service Cycling Speed and Cadence. Characteristic [2A5B] // Measurement
Service Cycling Speed and Cadence. Characteristic [2A5C] // Supported Features
Service Cycling Speed and Cadence. Characteristic [2A5D] // Sensor location
Service Cycling Speed and Cadence. Characteristic [2A55] // Control Point
*/
public struct BTConstants {
static let CadenceService = "1816"
static let CSCMeasurementUUID = "2a5b"
static let CSCFeatureUUID = "2a5c"
static let SensorLocationUUID = "2a5d"
static let ControlPointUUID = "2a55"
static let WheelFlagMask:UInt8 = 0b01
static let CrankFlagMask:UInt8 = 0b10
static let DefaultWheelSize:UInt32 = UInt32(myVariables.circonferenzaRuota!) // In millimiters. 700x30 (by default my bike's wheels) :)
static let TimeScale = 1024.0
}
protocol CadenceSensorDelegate {
func errorDiscoveringSensorInformation(_ error:NSError)
func sensorReady()
func sensorUpdatedValues( speedInMetersPerSecond speed:Double?, cadenceInRpm cadence:Double?, distanceInMeters distance:Double? )
}
class CadenceSensor: NSObject {
let peripheral:CBPeripheral
var sensorDelegate:CadenceSensorDelegate?
var measurementCharasteristic:CBCharacteristic?
var lastMeasurement:Measurement?
let wheelCircunference:UInt32
init(peripheral:CBPeripheral , wheel:UInt32=BTConstants.DefaultWheelSize) {
self.peripheral = peripheral
wheelCircunference = wheel
}
func start() {
self.peripheral.discoverServices(nil)
self.peripheral.delegate = self
}
func stop() {
if let measurementCharasteristic = measurementCharasteristic {
peripheral.setNotifyValue(false, for: measurementCharasteristic)
}
}
func handleValueData( _ data:Data ) {
let measurement = Measurement(data: data, wheelSize: wheelCircunference)
print("\(measurement)")
let values = measurement.valuesForPreviousMeasurement(lastMeasurement)
lastMeasurement = measurement
sensorDelegate?.sensorUpdatedValues(speedInMetersPerSecond: values?.speedInMetersPerSecond, cadenceInRpm: values?.cadenceinRPM, distanceInMeters: values?.distanceinMeters)
}
}
extension CadenceSensor : CBPeripheralDelegate {
func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
guard error == nil else {
sensorDelegate?.errorDiscoveringSensorInformation(NSError(domain: CBErrorDomain, code: 0, userInfo: [NSLocalizedDescriptionKey:NSLocalizedString("Error receiving measurements updates", comment:"")]))
return
}
print("notification status changed for [\(characteristic.uuid)]...")
}
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
print("Updated [\(characteristic.uuid)]...")
guard error == nil , let data = characteristic.value else {
return
}
handleValueData(data)
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
guard error == nil else {
sensorDelegate?.errorDiscoveringSensorInformation(error! as NSError)
return
}
// Find the cadence service
guard let cadenceService = peripheral.services?.filter({ (service) -> Bool in
return service.uuid == CBUUID(string: BTConstants.CadenceService)
}).first else {
sensorDelegate?.errorDiscoveringSensorInformation(NSError(domain: CBErrorDomain, code: NSNotFound, userInfo: [NSLocalizedDescriptionKey:NSLocalizedString("Cadence service not found for this peripheral", comment:"")]))
return
}
// Discover the cadence service characteristics
peripheral.discoverCharacteristics(nil, for:cadenceService )
print("Cadence service discovered")
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
guard let characteristics = service.characteristics else {
sensorDelegate?.errorDiscoveringSensorInformation(NSError(domain: CBErrorDomain, code: NSNotFound, userInfo: [NSLocalizedDescriptionKey:NSLocalizedString("No characteristics found for the cadence service", comment:"")]))
return
}
print("Received characteristics");
// Enable notifications for the measurement characteristic
for characteristic in characteristics {
print("Service \(service.uuid). Characteristic [\(characteristic.uuid)]")
if characteristic.uuid == CBUUID(string: BTConstants.CSCMeasurementUUID) {
print("Found measurement characteristic. Subscribing...")
peripheral.setNotifyValue(true, for: characteristic)
measurementCharasteristic = characteristic
}
}
sensorDelegate?.sensorReady()
}
}
and this other code
//
// MainViewController.swift
// BicycleSpeed
import UIKit
import CoreBluetooth
class MainViewController: UIViewController {
struct Constants {
static let ScanSegue = "ScanSegue"
static let SensorUserDefaultsKey = "lastsensorused"
}
var bluetoothManager:BluetoothManager!
var sensor:CadenceSensor?
weak var scanViewController:ScanViewController?
var infoViewController:InfoTableViewController?
var accumulatedDistance:Double?
lazy var distanceFormatter:LengthFormatter = {
let formatter = LengthFormatter()
formatter.numberFormatter.maximumFractionDigits = 1
return formatter
}()
//#IBOutlet var labelBTStatus:UILabel!
#IBOutlet var scanItem:UIBarButtonItem!
#IBOutlet weak var idLabel: UILabel!
override func viewDidLoad() {
bluetoothManager = BluetoothManager()
bluetoothManager.bluetoothDelegate = self
scanItem.isEnabled = false
}
deinit {
disconnectSensor()
}
#IBAction func unwindSegue( _ segue:UIStoryboardSegue ) {
bluetoothManager.stopScan()
guard let sensor = (segue as? ScanUnwindSegue)?.sensor else {
return
}
print("Need to connect to sensor \(sensor.peripheral.identifier)")
connectToSensor(sensor)
}
func disconnectSensor( ) {
if sensor != nil {
bluetoothManager.disconnectSensor(sensor!)
sensor = nil
}
accumulatedDistance = nil
}
func connectToSensor(_ sensor:CadenceSensor) {
self.sensor = sensor
bluetoothManager.connectToSensor(sensor)
// Save the sensor ID
UserDefaults.standard.set(sensor.peripheral.identifier.uuidString, forKey: Constants.SensorUserDefaultsKey)
UserDefaults.standard.synchronize()
}
// TODO: REconnect. Try this every X seconds
func checkPreviousSensor() {
guard let sensorID = UserDefaults.standard.object(forKey: Constants.SensorUserDefaultsKey) as? String else {
return
}
guard let sensor = bluetoothManager.retrieveSensorWithIdentifier(sensorID) else {
return
}
self.sensor = sensor
connectToSensor(sensor)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let infoVC = segue.destination as? InfoTableViewController {
infoViewController = infoVC
}
if segue.identifier == Constants.ScanSegue {
// Scan segue
bluetoothManager.startScan()
scanViewController = (segue.destination as? UINavigationController)?.viewControllers.first as? ScanViewController
}
}
}
extension MainViewController : CadenceSensorDelegate {
func errorDiscoveringSensorInformation(_ error: NSError) {
print("An error ocurred disconvering the sensor services/characteristics: \(error)")
}
func sensorReady() {
print("Sensor ready to go...")
accumulatedDistance = 0.0
}
func updateSensorInfo() {
let name = sensor?.peripheral.name ?? ""
let uuid = sensor?.peripheral.identifier.uuidString ?? ""
OperationQueue.main.addOperation { () -> Void in
self.infoViewController?.showDeviceName(name , uuid:uuid )
}
}
func sensorUpdatedValues( speedInMetersPerSecond speed:Double?, cadenceInRpm cadence:Double?, distanceInMeters distance:Double? ) {
accumulatedDistance? += distance ?? 0
let distanceText = (accumulatedDistance != nil && accumulatedDistance! >= 1.0) ? distanceFormatter.string(fromMeters: accumulatedDistance!) : "N/A"
let speedText = (speed != nil) ? distanceFormatter.string(fromValue: speed!*3.6, unit: .kilometer) + NSLocalizedString("/h", comment:"(km) Per hour") : "N/A"
let cadenceText = (cadence != nil) ? String(format: "%.2f %#", cadence!, NSLocalizedString("RPM", comment:"Revs per minute") ) : "N/A"
OperationQueue.main.addOperation { () -> Void in
self.infoViewController?.showMeasurementWithSpeed(speedText , cadence: cadenceText, distance: distanceText )
}
}
}
extension MainViewController : BluetoothManagerDelegate {
func stateChanged(_ state: CBCentralManagerState) {
print("State Changed: \(state)")
var enabled = false
var title = ""
switch state {
case .poweredOn:
title = "Bluetooth ON"
enabled = true
// When the bluetooth changes to ON, try to reconnect to the previous sensor
checkPreviousSensor()
case .resetting:
title = "Reseeting"
case .poweredOff:
title = "Bluetooth Off"
case .unauthorized:
title = "Bluetooth not authorized"
case .unknown:
title = "Unknown"
case .unsupported:
title = "Bluetooth not supported"
}
infoViewController?.showBluetoothStatusText( title )
scanItem.isEnabled = enabled
}
func sensorConnection( _ sensor:CadenceSensor, error:NSError?) {
print("")
guard error == nil else {
self.sensor = nil
print("Error connecting to sensor: \(sensor.peripheral.identifier)")
updateSensorInfo()
accumulatedDistance = nil
return
}
self.sensor = sensor
self.sensor?.sensorDelegate = self
print("Sensor connected. \(String(describing: sensor.peripheral.name)). [\(sensor.peripheral.identifier)]")
updateSensorInfo()
sensor.start()
}
func sensorDisconnected( _ sensor:CadenceSensor, error:NSError?) {
print("Sensor disconnected")
self.sensor = nil
}
func sensorDiscovered( _ sensor:CadenceSensor ) {
scanViewController?.addSensor(sensor)
}
}
in MainViewController.swift, there is this func sensorUpdatedValues that converts the three values that I want, to strings and initialize the the func showMeasurementWithSpeed ins the InfoTableViewController.swift .
Could I just return the three values inside function sensorUpdateValues instead to be able to store them into new variables?
The better way to solve this is by passing on the sensor object to the InfoTableViewController in the prepare(forSegue:) and then inside InfoTableViewControlleryou can call sensor.lastMeasurement.speedInMetersPerSecond or any other var that is inside there. Since the class is passed on by reference it will retain the data even when you transition to a new ViewController.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let infoVC = segue.destination as? InfoTableViewController {
infoVC.sensor = self.sensor
}
if segue.identifier == Constants.ScanSegue {
// Scan segue
bluetoothManager.startScan()
scanViewController = (segue.destination as? UINavigationController)?.viewControllers.first as? ScanViewController
}
}
Then of course you can do whatever you want with this data in the new VC( assign the values to labels or whatnot)
I believe it's because you have that func declared as an instance level function rather than a class/struct level function. You should be able to simply add the "static" keyword to make it accessible for the way you are using it in your sample code. i.e. "static func valuesForPreviousMeasurement ..."
** UPDATE - Added a simple example to show the difference between class and instance functions.
// This is an instance function being used. It's called such because
// you need an actual object instance in order to call the func.
var myCar: Car = Car()
myCar.startEngine()
// This is a class level function being used. It's called such because
// you don't actually need an object instance: It's simply part of the class.
Car.PriceForModel("HondaCivic")
Finally Solved .. it was easier than thought.. as I was understanding the three values I'm interested in were passed some how to the infoTableViewController I wanted to get rid of. And they were passed, already converted to String with the function sensorUpdatedValues inside MainView controller
func sensorUpdatedValues( speedInMetersPerSecond speed:Double?, cadenceInRpm cadence:Double?, distanceInMeters distance:Double? ) {
accumulatedDistance? += distance ?? 0
let distanceText = (accumulatedDistance != nil && accumulatedDistance! >= 1.0) ? distanceFormatter.string(fromMeters: accumulatedDistance!) : "N/A"
let speedText = (speed != nil) ? distanceFormatter.string(fromValue: speed!*3.6, unit: .kilometer) + NSLocalizedString("/h", comment:"(km) Per hour") : "N/A"
let cadenceText = (cadence != nil) ? String(format: "%.2f %#", cadence!, NSLocalizedString("RPM", comment:"Revs per minute") ) : "N/A"
OperationQueue.main.addOperation { () -> Void in
self.infoViewController?.showMeasurementWithSpeed(speedText , cadence: cadenceText, distance: distanceText )
}
}
so I traced down the function inside my InfospeedoViewController ( as xCode was asking for it because it was present in InfoTableViewController and rerouting to InfoSpeedoViewController made it necessary) and inside that function's body I made the connection of the values to the labels.
func showMeasurementWithSpeed( _ speed:String, cadence:String, distance:String ) {
speedDisplayLabel.text = speed
cadenceDisplayLabel.text = cadence
distanceDisplayLabel.text = distance
// showDetailText(speed, atSection: Constants.MeasurementsSection, row:Constants.SpeedRow)
// showDetailText(cadence, atSection: Constants.MeasurementsSection, row:Constants.CadenceRow)
// showDetailText(distance, atSection: Constants.MeasurementsSection, row:Constants.DistanceRow)
}
the commented out parts were the old InfoTableViewController indication to fill the cells..
Many thanks for your help. I learned a few things and consolidated others. I guess we went the hard way trying to catch this values in the wrong place, but I guess that just because I had all the project's file printed that I could trace the data flow easier. Not fully understanding the code because of my low knowledge of swift made it a bit more difficoult, but I had this feeling the this solution was logic as often the simple way is the best way, an often one just can't see it.
thanks again
I have two more values I want to get. A new adventure begins ..should I post another question or continue this one?

Bluetooth - data reading from custom controller returning same value

I am creating app which connects to custom controller and reads some data over bluetooth, should be pretty simple stuff.
App has address and data characteristics:
- I am writing addresses to address characteristics (that way I tell controller which values from given addresses I would like to have)
- Controller is updating data characteristics with values from given addresses
I am constantly updating address in time interval of 12 seconds to receive different values from different addresses.
App works and is reading data from controller. But after some time values on data address don't update and controller returns same values for different addresses.
We know controller works since Android version of the app has already been made and it works like a charm.
I am wondering if anyone has any idea what could be wrong? Is there a way bluetooth connection changes services / characteristics so I have to update them too?
EDIT
Wasn't able to add code before, so here is the main part:
To sum it up:
I read values manually by using .readValue and .writeWithResponse when writing.
internal func nextIteration() {
if _writeQueue.count > 0 {
self.writeData()
} else {
self.readData()
}
}
internal func writeData() {
let writeProperty = _writeQueue[0]
self.writeDataCharacteristics(writeProperty: writeProperty)
_writeQueue.remove(at: 0)
}
internal func readData() {
if _resetReadParameters || _currentPropertiesNumber != _selectedPropertiesNumber {
if _selectedPropertiesNumber >= _readQueue.count || _selectedPropertiesNumber < 0 {
_selectedPropertiesNumber = 0
}
_currentProperties = _readQueue[_selectedPropertiesNumber]
writeAddressCharacteristics()
_resetReadParameters = false
_currentPropertiesNumber = _selectedPropertiesNumber
return
}
readDataCharacteristics()
_selectedPropertiesNumber += 1
if _selectedPropertiesNumber >= _readQueue.count {
_selectedPropertiesNumber = 0
}
}
internal func writeAddressCharacteristics() {
if _currentProperties.count != Constants.maxProperties {
print("Not enough current properties.")
return
}
_addressBytes.removeAll()
for property in _currentProperties {
let a = property.address.rawValue
_addressBytes.append(UInt8(a & 0xFF))
_addressBytes.append(UInt8(a >> 8))
}
peripheral?.writeValue(Data(bytes: _addressBytes), for: _address, type: .withResponse)
}
internal func writeDataCharacteristics(writeProperty: Property) {
_writeBytes?.removeAll()
let d = UInt16(bitPattern: Int16(writeProperty.value!))
_writeBytes = [UInt8(d & 0xFF), UInt8(d >> 8), 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
peripheral?.writeValue(Data(bytes: _writeBytes!), for: _data, type: .withResponse)
}
internal func readDataCharacteristics() {
_communicatingData = true
peripheral?.readValue(for: _data)
}
internal func parseValueBroadcast(bytes: [UInt8], property: Property, byteIndex: Int) {
let value = Int(Int16(bitPattern: UInt16(bytes[byteIndex]) | UInt16(bytes[byteIndex + 1]) << 8))
property.value = value
_communicationDelegate?.didReceiveProperty(property: property)
}
internal func setWriteProperty(address: Address, value: Int) {
let newProperty = Property.get(address)
newProperty.value = value
_writeQueue.append(newProperty)
if _pausedReading {
nextIteration()
}
}
// MARK: CBPeripheralDelegate
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
if let writeError = error {
print("Error writing: \(writeError.localizedDescription)")
}
DispatchQueue.main.asyncAfter(deadline: .now() + 4, execute: {
self.nextIteration()
})
}
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
if let error = error {
print("READ ERROR:", error.localizedDescription)
return
}
guard let value = characteristic.value else {
print("READ ERROR: NO DATA")
return
}
let bytes = [UInt8](value)
if !_pausedReading {
var n = 0
for property in _currentProperties {
parseValueBroadcast(bytes: bytes, property: property, byteIndex: n)
n += 2
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + 4, execute: {
self.nextIteration()
})
}
}

Decrypt Media Files in chunks and play via AVPlayer

I have a mp4 video file which i am encrypting to save and decrypting to play via AVPlayer. Using CRYPTOSWIFT Library for encrypting/decrypting
Its working fine when i am decrypting whole file at once but my file is quite big and taking 100% CPU usage and lot of memory. So, I need to decrypt encrypted file in chunks.
I tried to decrypt file in chunks but its not playing video as AVPlayer is not recognizing decrypted chunk data maybe data is not stored sequentially while encrypting file. I have tried chacha20, AES, AES.CTR & AES.CBC protocols to encrypt and decrypt files but to no avail.
extension PlayerController: AVAssetResourceLoaderDelegate {
func resourceLoader(resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool {
let request = loadingRequest.request
guard let path = request.URL?.path where request.URL?.scheme == Constants.customVideoScheme else { return true }
if let contentRequest = loadingRequest.contentInformationRequest {
do {
let fileAttributes = try NSFileManager.defaultManager().attributesOfItemAtPath(path)
if let fileSizeNumber = fileAttributes[NSFileSize] {
contentRequest.contentLength = fileSizeNumber.longLongValue
}
} catch { }
if fileHandle == nil {
fileHandle = NSFileHandle(forReadingAtPath: (request.URL?.path)!)!
}
contentRequest.contentType = "video/mp4"
contentRequest.byteRangeAccessSupported = true
}
if let data = decryptData(loadingRequest, path: path), dataRequest = loadingRequest.dataRequest {
dataRequest.respondWithData(data)
loadingRequest.finishLoading()
return true
}
return true
}
func decryptData(loadingRequest: AVAssetResourceLoadingRequest, path: String) -> NSData? {
print("Current OFFSET: \(loadingRequest.dataRequest?.currentOffset)")
print("requested OFFSET: \(loadingRequest.dataRequest?.requestedOffset)")
print("Current Length: \(loadingRequest.dataRequest?.requestedLength)")
if loadingRequest.contentInformationRequest != nil {
var data = fileHandle!.readDataOfLength((loadingRequest.dataRequest?.requestedLength)!)
fileHandle!.seekToFileOffset(0)
data = decodeVideoData(data)!
return data
} else {
fileHandle?.seekToFileOffset(UInt64((loadingRequest.dataRequest?.currentOffset)!))
let data = fileHandle!.readDataOfLength((loadingRequest.dataRequest?.requestedLength)!)
// let data = fileHandle!.readDataOfLength(length!) ** When I use this its not playing video but play fine when try with requestedLength **
return decodeVideoData(data)
}
}
}
Decode code to decode nsdata :
func decodeVideoData(data: NSData) -> NSData? {
if let cha = ChaCha20(key: Constants.Encryption.SecretKey, iv: Constants.Encryption.IvKey) {
let decrypted: NSData = try! data.decrypt(cha)
return decrypted
}
return nil
}
I need help regarding this issue, Kindly guide me to the right way to achieve this.
For in depth and a more complete CommonCrypto wrapper, check out my CommonCrypto wrapper. I've extracted bits and pieces for this answer.
First of all, we need to define some functions that will do the encryption/decryption. I'm assuming, for now, you use AES(256) CBC with PKCS#7 padding. Summarising the snippet below: we have an update function, that can be called repeatedly to consume the chunks. There's also a final function that will wrap up any left overs (usually deals with padding).
import CommonCrypto
import Foundation
enum CryptoError: Error {
case generic(CCCryptorStatus)
}
func getOutputLength(_ reference: CCCryptorRef?, inputLength: Int, final: Bool) -> Int {
CCCryptorGetOutputLength(reference, inputLength, final)
}
func update(_ reference: CCCryptorRef?, data: Data) throws -> Data {
var output = [UInt8](repeating: 0, count: getOutputLength(reference, inputLength: data.count, final: false))
let status = data.withUnsafeBytes { dataPointer -> CCCryptorStatus in
CCCryptorUpdate(reference, dataPointer.baseAddress, data.count, &output, output.count, nil)
}
guard status == kCCSuccess else {
throw CryptoError.generic(status)
}
return Data(output)
}
func final(_ reference: CCCryptorRef?) throws -> Data {
var output = [UInt8](repeating: 0, count: getOutputLength(reference, inputLength: 0, final: true))
var moved = 0
let status = CCCryptorFinal(reference, &output, output.count, &moved)
guard status == kCCSuccess else {
throw CryptoError.generic(status)
}
output.removeSubrange(moved...)
return Data(output)
}
Next up, for the purpose of demonstration, the encryption.
let key = Data(repeating: 0x0a, count: kCCKeySizeAES256)
let iv = Data(repeating: 0, count: kCCBlockSizeAES128)
let bigFile = (0 ..< 0xffff).map { _ in
return Data(repeating: UInt8.random(in: 0 ... UInt8.max), count: kCCBlockSizeAES128)
}.reduce(Data(), +)
var encryptor: CCCryptorRef?
CCCryptorCreate(CCOperation(kCCEncrypt), CCAlgorithm(kCCAlgorithmAES), CCOptions(kCCOptionPKCS7Padding), Array(key), key.count, Array(iv), &encryptor)
do {
let ciphertext = try update(encryptor, data: bigFile) + final(encryptor)
print(ciphertext) // 1048576 bytes
} catch {
print(error)
}
That appears to me as quite a large file. Now decrypting, would be done in a similar fashion.
var decryptor: CCCryptorRef?
CCCryptorCreate(CCOperation(kCCDecrypt), CCAlgorithm(kCCAlgorithmAES), CCOptions(kCCOptionPKCS7Padding), Array(key), key.count, Array(iv), &decryptor)
do {
var plaintext = Data()
for i in 0 ..< 0xffff {
plaintext += try update(decryptor, data: ciphertext[i * kCCBlockSizeAES128 ..< i * kCCBlockSizeAES128 + kCCBlockSizeAES128])
}
plaintext += try final(decryptor)
print(plaintext == bigFile, plaintext) // true 1048560 bytes
} catch {
print(error)
}
The encryptor can be altered for different modes and should also be released once it's done, and I'm not too sure how arbitrary output on the update function will behave, but this should be enough to give you an idea of how it can be done using CommonCrypto.

Resources