NSOutputStream blocking, HasBytesAvailable event was not triggered - ios

I am trying to create custom framework that will deal with ExternalAccessory.framework to do read/write operation with the connected accessory.
I can able to create a session and open the device to do read/write operations.
Problem i am facing was, when i try to write data using NSOutputStream.write from the application, data successfully received by the accessory but after that UI was not responding and data returned byte accessory was not received by the app(HasBytesAvailable was not called)
Here my UIViewController.swift
let sessionHandler = SessionHandler().sharedController()
in ViewDidLoad()
let runLoop = NSRunLoop.currentRunLoop()
sessionHandler.openDeviceWithProtocolString(runLoop)
#IBAction calling sessionHandler._WriteData()
This SessionHandler class was in inside my custom SDK (custom framework)
Here my SessionHandler Class
import UIKit
import ExternalAccessory
public class SessionHandler: NSObject, NSStreamDelegate {
var readData: NSMutableData?
var writeData: NSMutableData?
var _session: EASession?
public func sharedController() -> SessionHandler{
var sessionController: SessionHandler?
if sessionController == nil {
sessionController = SessionHandler()
}
return sessionController!
}
func getConnectedAccessoris() -> Array<EAAccessory>{
let accessories : Array<EAAccessory> = EAAccessoryManager.sharedAccessoryManager().connectedAccessories
return accessories
}
public func openDeviceWithProtocolString(_runLoop: NSRunLoop){
print("Inside openDeviceWithProtocolString")
let _accessories = getConnectedAccessoris()
var _accessory: EAAccessory?
for acsy in _accessories {
if acsy.protocolStrings.contains("my.protocol.string") {
_accessory = acsy
}
}
if _accessory != nil {
_session = EASession(accessory: _accessory!, forProtocol: "my.protocol.string")
print("EASession create :: \(_session)")
if _session != nil {
_session?.inputStream?.delegate = self
_session?.inputStream?.scheduleInRunLoop(_runLoop, forMode: NSDefaultRunLoopMode)
_session?.inputStream?.open()
print("Input stream Opened")
_session?.outputStream?.delegate = self
_session?.outputStream?.scheduleInRunLoop(_runLoop, forMode: NSDefaultRunLoopMode)
_session?.outputStream?.open()
print("Output Stream Opened")
}else {
print("SessionHandler : session nil")
}
}
}
public func _readData() {
print("Trying to read data")
let INPUT_BUFFER_SIZE = 65536
let buf = UnsafeMutablePointer<UInt8>.alloc(INPUT_BUFFER_SIZE)
while ((_session?.inputStream?.hasBytesAvailable) != nil) {
let bytesRead = _session?.inputStream?.read(buf, maxLength: INPUT_BUFFER_SIZE)
if readData == nil {
readData = NSMutableData()
}
readData?.appendBytes(buf, length: bytesRead!)
}
if readData != nil {
let data: NSData = readData!
let count = data.length / sizeof(UInt8)
// create an array of Uint8
var array = [UInt8](count: count, repeatedValue: 0)
// copy bytes into array
data.getBytes(&array, length:count * sizeof(UInt8))
print("Data Received :: \(array)")
validateData(array)
}
}
public func _writeData() {
while _session?.outputStream?.hasSpaceAvailable != nil && writeData?.length > 0 {
print("Writting bytes :: \(writeData?.bytes)")
let bytesWritten = _session?.outputStream?.write(UnsafePointer<UInt8>((writeData?.bytes)!), maxLength: (writeData?.length)!)
print("written bytes : \(bytesWritten)")
if bytesWritten == -1 {
//Write error
print("written error")
break
}else if bytesWritten > 0 {
print("Written success")
validateData(writeData)
writeData?.replaceBytesInRange(NSMakeRange(0, bytesWritten!), withBytes: nil, length: 0)
}
}
}
func stream(aStream: NSStream, handleEvent eventCode: NSStreamEvent) {
switch (eventCode) {
case NSStreamEvent.None:
print("NSStream None")
break
case NSStreamEvent.OpenCompleted:
print("Open Completed")
break
case NSStreamEvent.HasBytesAvailable:
print("Has Bytes Available")
_readData()
break
case NSStreamEvent.HasSpaceAvailable:
print("Hase space Available")
_writeData()
break
case NSStreamEvent.ErrorOccurred:
print("Error occurred")
break
case NSStreamEvent.EndEncountered:
print("End Encountered")
break
default:
print("No stream event")
break
}
}
}
Thanks in Advance..

sessionHandler.openDeviceWithProtocolString(runLoop)
Isn't it supposed to be called on another thread?

I'm sure this is too late, but while ((_session?.inputStream?.hasBytesAvailable) != nil) looks like an infinite loop to me, because true/false will always be != nil.

Related

CFStreamCreatePairWithSocketToHost - How to handle the network disconnect?

We have created socket for streaming using the API "CFStreamCreatePairWithSocketToHost".
func openStream() {
if let aUrl = url {
var readStream: Unmanaged<CFReadStream>?
var writeStream: Unmanaged<CFWriteStream>?
CFStreamCreatePairWithSocketToHost(
kCFAllocatorDefault,
aUrl as CFString,
port!,
&readStream,
&writeStream)
iStream = readStream!.takeRetainedValue()
oStream = writeStream!.takeRetainedValue()
if ssl == true {
let dict = [
kCFStreamSSLValidatesCertificateChain: kCFBooleanFalse, // allow self-signed certificate
kCFStreamSSLLevel: kCFStreamSSLValidatesCertificateChain // don't understand, why there isn't a constant for version 1.2
] as CFDictionary
let sslSetRead = CFReadStreamSetProperty(iStream, CFStreamPropertyKey(kCFStreamPropertySSLSettings), dict)
let sslSetWrite = CFWriteStreamSetProperty(oStream, CFStreamPropertyKey(kCFStreamPropertySSLSettings), dict)
if sslSetRead == false || sslSetWrite == false {
//print("SSL Configuration Failed")
}
}
commonStr = ""
open(iStream)
open(oStream)
print("Open Stream")
}
}
Used the below delegate for getting events.
func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
// print("Stream : \(aStream.debugDescription) Event : \(eventCode.rawValue)")
switch eventCode {
case .endEncountered, .errorOccurred:
streamError()
break
case .hasBytesAvailable:
if let iStream = iStream {
if aStream == iStream {
//read data
let bufferSize = 1024
let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: bufferSize)
var len: Int
while iStream.hasBytesAvailable {
len = iStream.read(buffer, maxLength: bufferSize)
if len > 0 {
let output = Data.init(bytes: buffer, count: len)
bufferTokenize(output: output)
}else {
break
}
}
buffer.deallocate()
}
}
break
case .hasSpaceAvailable:
if aStream == oStream! {
streamReady()
}
break
case .openCompleted:
if aStream == oStream! {
if let del = delegate {
del.connectionCompleted()
}
}
break
default:
break
}
}
Other Supporting functions are
func streamError() {
stopStream()
NSObject.cancelPreviousPerformRequests(withTarget:self)
perform(#selector(callReconnect), with: nil, afterDelay: 2)
}
func open(_ stream: Stream?) {
if let aStream = stream {
aStream.delegate = self
aStream.schedule(in: RunLoop.main, forMode: .common)
aStream.open()
}
}
func stopStream() {
print("Stop Stream")
close(iStream)
close(oStream)
}
func close(_ stream: Stream?) {
if nil != stream {
stream?.close()
stream?.remove(from: RunLoop.main, forMode: .common)
}
}
func resetStream() {
stopStream()
openStream()
}
It is working fine for below steps
Create connection - Open stream
get "openCompleted" event - then Create request
get "hasSpaceAvailable" event - then Send Request
get "hasBytesAvailable" event - then Handle Data
Disconnect connection from server for testing purpose
get "endEncountered" event - then Close the connection and Recreate the connection from step 1
But the below scenario not working properly
Follow the steps 1 - 4 then
Disconnect Internet connection for testing purpose - the event "hasBytesAvailable" stopped
Again reconnect the Internet connection within 30 seconds - now we get "hasBytesAvailable" event
But the reconnect time extent to 5 minute - We don't have any event, and don't get "endEncountered" or "errorOccurred" also. Then how to move to next stop.
Please Help, Thanks
Regards,
Elamvazhuthi K

How to convert UnsafeMutablePointer<UInt8> to UIImage in Swift

I have been trying to send image data from android and receive at iOS using Socket,But the problem is that I am not able to show Image from data which is received from stream.
Here is the complete code:-
Setting Up the network communication
var readStream: Unmanaged<CFReadStream>?
var writeStream: Unmanaged<CFWriteStream>?
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault,
ip as CFString,
3003,
&readStream,
&writeStream)
inputStream = readStream!.takeRetainedValue()
outputStream = writeStream!.takeRetainedValue()
inputStream.delegate = self
// outputStream.delegate = self
inputStream.schedule(in: .current, forMode: RunLoop.Mode.common)
outputStream.schedule(in: .current, forMode: RunLoop.Mode.common)
inputStream.open()
outputStream.open()
Receive data through the delegates as shown below:-
extension ChatRoom: StreamDelegate {
func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
switch eventCode {
case Stream.Event.hasBytesAvailable:
print("new message received")
readAvailableBytes(stream: aStream as! InputStream)
case Stream.Event.endEncountered:
print("end received")
stopStreamSession()
case Stream.Event.errorOccurred:
print("error occurred")
case Stream.Event.hasSpaceAvailable:
print("has space available")
case Stream.Event.openCompleted:
print("Stream opened")
default:
print("some other event...")
break
}
}
private func readAvailableBytes(stream: InputStream) {
let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: maxReadLength)
while stream.hasBytesAvailable {
let numberOfBytesRead = inputStream.read(buffer, maxLength: maxReadLength)
if numberOfBytesRead < 0 {
if let _ = stream.streamError {
break
}
}
NSLog("number of bytes read = %i", numberOfBytesRead)
if numberOfBytesRead > 0 {
dataOfImage.append(buffer, count: numberOfBytesRead)
}
}
}
}
I have been trying to show image from data received using UIImage(data:dataOfImage), But it is showing nil , when I inspect the dataOfImage it consists of all the data received from stream.
Can anyone suggest a way to get the image out of UnsafeMutablePointer<UInt8> Data.
Also if there is any better approach for data transfer through sockets, Please do Suggest..
Rather than allocate memory declare a buffer variable with the capacity of maxReadLength.
And stream(_:handle:) is called multiple times. You have to check the result of the read function
private var dataOfImage = Data()
private func readAvailableBytes(stream: InputStream) {
var buffer = [UInt8](repeating: 0, count: maxReadLength)
let bytesRead = inputStream.read(&buffer, maxLength: maxReadLength)
switch bytesRead {
case 0: // success, end of file
case -1: // error
default: // append data
dataOfImage.append(contentsOf: buffer)
}
NSLog("number of bytes read = %i", bytesRead)
}

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 ☠️☠️☠️
}
}

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?

StreamDelegate stops to receive events after a amount of readings in swift

I have an iOS chat application that receives messages over a socket connection.
When user open the app after a long time and there are more than 50 unread messages, the server send over socket a message telling the number of unread messages, at this point the application show an alert with a progress bar and then the server send each one message.
So, the application get each one message on the StreamDelegate method stream(_ stream: Stream, handle eventCode: Stream.Event) and update the progress bar until the end of messages.
The problem is, when I have a large amount of unread messages (about 300+) at some point the StreamDelegate stops to receive the events with the messages, and none error messages is displayed.
I call the connect method on a global queue:
DispatchQueue.global().async {
self.connect(host, port: port)
}
This is my socket connect code:
fileprivate func connect(_ host: String, port: Int) {
postStatus(.connecting)
self.host = NSString(string: host)
self.port = UInt32(port)
self.log("connect to \(host):\(port)")
var readStream : Unmanaged<CFReadStream>?
var writeStream : Unmanaged<CFWriteStream>?
CFStreamCreatePairWithSocketToHost(nil, self.host, self.port, &readStream, &writeStream)
self.inOk = false
self.outOk = false
self.inputStream = readStream!.takeRetainedValue()
self.outputStream = writeStream!.takeRetainedValue()
self.inputStream.delegate = self
self.outputStream.delegate = self
let mainThread = Thread.isMainThread;
let loop = mainThread ? RunLoop.main : RunLoop.current
self.inputStream.schedule(in: loop, forMode: RunLoopMode.defaultRunLoopMode)
self.outputStream.schedule(in: loop, forMode: RunLoopMode.defaultRunLoopMode)
self.inputStream.open()
self.outputStream.open()
self.timer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(connectionTimeout), userInfo: nil, repeats: false)
if(!mainThread) {
loop.run()
}
}
In the StreamDelegate method stream(_ stream: Stream, handle eventCode: Stream.Event) I get the message event and process it on the method read(String)
case Stream.Event.hasBytesAvailable:
if let timer = timer {
timer.invalidate()
self.timer = nil
}
let json = ChatLibSwift.readMessage(self.inputStream)
do {
if StringUtils.isNotEmpty(json) {
try self.read(json)
}
} catch let ex as NSError {
LogUtils.log("ERROR: \(ex.description)")
}
break
case Stream.Event.hasSpaceAvailable:
break
The method that read each one message:
static func readMessage(_ inputStream: InputStream) -> String {
do {
var lenBytes = [UInt8](repeating: 0, count: 4)
inputStream.read(&lenBytes, maxLength: 4)
// header
let i32: Int = Int(UInt32.init(lenBytes[3]) | UInt32.init(lenBytes[2]) << 8 | UInt32.init(lenBytes[1]) << 16 | UInt32.init(lenBytes[0]) << 24 )
var msg = [UInt8](repeating: 0, count: (MemoryLayout<UInt8>.size * Int(i32)))
let bytesRead = inputStream.read(&msg, maxLength: Int(i32))
if bytesRead == -1 {
print("<< ChatLib ERROR -1")
return ""
}
let s = NSString(bytes: msg, length: bytesRead, encoding: String.Encoding.utf8.rawValue) as String?
if let s = s {
if bytesRead == Int(i32) {
return s
}
else {
print("Error: readMessage \(s)")
}
return s
}
return ""
} catch {
return ""
}
}
Anyone has idea how to solve it?
The main idea is to force schedule the reading of the stream after a successful read operation:
let _preallocatedBufferSize = 64 * 1024
var _preallocatedBuffer = [UInt8](repeating: 0, count: MemoryLayout<UInt8>.size * Int(_preallocatedBufferSize))
var message : ....
func readMessage(_ inputStream: InputStream) {
if !inputStream.hasBytesAvailable || message.isCompleted {
return
}
var theBuffer : UnsafeMutablePointer<UInt8>?
var theLength : Int = 0
// try to get buffer from the stream otherwise use the preallocated buffer
if !inputStream.getBuffer(&theBuffer, length:&theLength) || nil == theBuffer
{
memset(&_preallocatedBuffer, 0, _preallocatedBufferSize)
let theReadCount = inputStream.read(&_preallocatedBuffer, maxLength:_preallocatedBufferSize)
if theReadCount > 0 {
theBuffer = _preallocatedBuffer;
theLength = theReadCount;
} else {
theBuffer = nil;
theLength = 0;
}
}
if nil != theBuffer && theLength > 0 {
_message.appendData(theBuffer, length:theLength)
self.perform(#selector(readMessage), with:inputStream, afterDelay:0.0, inModes:[RunLoopMode.defaultRunLoopMode])
}
}

Resources