CoreBlouetooth issues

Anyone have problems connecting with Swift 3 ios 10 beta 4 with a bluetooth device?


I get strange messages like "Writing is not permitted." and "API MISUSE: <private> can only accept commands while in the connected state"


when i try to connect.

I have just changes the Nessesary changes for my project to bould with swift 3 ( no connection logic changed )

when i run it on a ios 9.3 device it works perfectlly but when i build and run it on ios 10 it disconnects me


becouse i know the uuid i done use CBCentralPeripheral to first search and then connect but use this method insted "mCentral.connect(mPeripheral, options: nil)"

If i run that one, it does not call the "centralManagerDidUpdateState" method but just call the "func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {" method straigt away. the reson why i mention it is becouse on the web i have read that to be abel to connect you have too get the "centralManagerDidUpdateState" callback fist.


This is the entire log i get whn i connect and fail.


"Application Domain path = [file:/

CentralManager#retrievePeripheralsWithIdentifiers 4D8D0346-C58A-B372-421C-E2E9F5B58291

CentralManager#retrieveConnectedPeripheralsWithServices

CentralManager#centralManagerDidUpdateState: Bluetooth powered on.

state.description = %@ Connecting

Bluetooth Status: Turned On

warning: could not load any Objective-C class information from the dyld shared cache. This will significantly reduce the quality of type information available.

Error changing notification state: Writing is not permitted.

2016-08-05 11:53:55.525868 VSC[3524:804307] [CoreBluetooth] API MISUSE: <private> can only accept commands while in the connected state

The specified device has disconnected from us.

"

Regards

Adam

Replies

Can you post your actual code?


It looks like you may be trying to subscribe to change notifications before the peripheral is connected.

absolutly.

You can see that i have a timer that dilays a new reconnect on a disconnect. and we sends a /r/n directlly after we connect to the device. thats a workaround to know when the message is encrypted on the other side due to some api bbugs on the bluetooth hardware side.

keep in mind that this code work good on a ios 9.x device so that must be some logic in the new api that has been changed.


/
/
/
/
/
/
/ 
import Foundation
import CoreBluetooth
/ Services & Characteristics UUIDs */
let BLEServiceUUID = CBUUID(string: "2456E1B9-26E2-8F83-E744-F34F01E9D701")
let PositionCharUUID = CBUUID(string: "2456E1B9-26E2-8F83-E744-F34F01E9D703")
let    RX_VSC_DATA_MESSAGE_KEY_CONNSTATE    =   "E"          /
let    RX_VSC_DATA_MESSAGE_KEY_STATE        =   "F"          / 
let TX_VSC_STATUS_TAKE_OWNERSHIP_KEY        =   "#TAKE\r\n"  /
let TX_VSC_STATUS_SEND_INIT_MESSAGE         =   "\r\n"       / 
/
internal let VSC_DATA_MASK_STATE_ICSCON_BLOCK:      Int    = 0x000003
internal let VSC_DATA_MASK_STATE_ICSCON_FAILED:     Int    = 0x000100
internal let VSC_DATA_MASK_STATE_ICSCON_ERRCODE:    Int    = 0x7F0000
internal let VSC_DATA_SHIFT_STATE_ICSCON_ERRCODE:   Int    = 16
internal let VSC_DATA_ICS_ERR_CODE_ISC_SW_TOO_OLD:  Int    =  3; /
internal let VSC_DATA_ICS_ERR_CODE_ISC_SERIAL_WAS_ZERO: Int = 11; / 
private let ABORTTIME: Double = 1.5*60 / 
/*
PBLEWheelChairSocket connects to a wheelchair. Is responsible to handle connection/dosconnection, communication states
and receivement of messages from the wheelchair.
*/
public class PBLEWheelChairSocket: NSObject, CBPeripheralDelegate, CBCentralManagerDelegate, MessageParserDelegate {
   
    /
    private var mPeripheral: CBPeripheral!
    private var mCentral: CBCentralManager!
    private var mCharacteristic: CBCharacteristic?
    private var mMessageParser: MessageParser?
    private var mConnectionState: ConnectionState
    private var tryTakeDate = Date()
    private var mInitMsgTimer: Timer?
    private var mReconnectTimer: Timer?
    private weak var mInterpretor: IMessageInterpretor?
    public  weak var delegate: PBLEWheelchairSocketDelegate?
    private var mInternalConnectionState: InternalConnectionState = .pble_DISCONNECTED
    private let WheelchairExtraInfoFilter: HysteresObjectFilter = HysteresObjectFilter()
   
    /*
     Name of the peripheral.
     */
    public var Name:String {
        get {
            return mPeripheral.name!
        }
    }
   
    /
     Unique identifier for the peripheral.
     */
    public var identifier: UUID {
        get {
            return mPeripheral.identifier
        }
    }
   
    public var connectionState: ConnectionState {
        get {
            return mConnectionState
        }
    }
   
    /
   
    init( peripheral: CBPeripheral, central: CBCentralManager) {
        mPeripheral = peripheral
        mCentral  =  central
        mConnectionState = ConnectionState.bt_NOT_CONNECTED
        super.init()
        mMessageParser = MessageParser(with: self)
        mCharacteristic = nil
        mInterpretor = nil
    }
    private var mUUID: UUID?
    public init( uuid: UUID )throws {
        mConnectionState = ConnectionState.bt_NOT_CONNECTED
        super.init()
        let centralQueue = DispatchQueue(label: "com.permobil.PBLEDiscoveryQueue")
        mCentral = CBCentralManager(delegate: self, queue: centralQueue)
        mUUID = uuid
        let peripherals = mCentral.retrievePeripherals(withIdentifiers: [uuid])
       
        if( peripherals.count <= 0){
            throw NSError(domain: NSCocoaErrorDomain, code: 200, userInfo: nil)
        }
       
        mPeripheral = peripherals[0];
       
        mMessageParser = MessageParser(with: self)
        mCharacteristic = nil
        mInterpretor = nil
    }
   
    deinit{
        print("deinit \(self.Name)")
        mPeripheral = nil
        mCentral = nil
        mCharacteristic = nil
        mMessageParser = nil
        mInterpretor = nil
        if mInitMsgTimer != nil {
            mInitMsgTimer?.invalidate()
            mInitMsgTimer = nil
        }
       
        if mReconnectTimer != nil {
            mReconnectTimer?.invalidate()
            mReconnectTimer = nil
        }
    }
   
    /
   
    public func centralManagerDidUpdateState(_ central: CBCentralManager) {
        switch (central.state) {
        case CBManagerState.poweredOff:
            /
            /
            break;
        case CBManagerState.unauthorized:
            /
            break
           
        case CBManagerState.unknown:
            /
            break
           
        case CBManagerState.poweredOn:
           
            break;
           
        case CBManagerState.resetting:
            /
            /
            break;
        case CBManagerState.unsupported:
            break
        }
    }
   
    public func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
        print(NSError.description())
        /
    }
   
    /
    public func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        if (peripheral != self.mPeripheral) {
            /
            return
        }
       
        mPeripheral.delegate = self
        mPeripheral.discoverServices(nil)
    }
   
    public func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
        if error != nil {
            disconnect(false)
            let nsError = error as? NSError
            print(error!.localizedDescription)
            if nsError!.code == CBError.peripheralDisconnected.rawValue && mInternalConnectionState != .pble_DISCONNECTING{
                let tick: TimeInterval = Double(Int.random(500..<1200))/1000
                if !Thread.isMainThread {
                    DispatchQueue.main.async(execute: {
                        if self.mReconnectTimer == nil {
                            self.mReconnectTimer = Timer.scheduledTimer(timeInterval: tick, target: self, selector: #selector(PBLEWheelChairSocket.onReconnectTimerElapsed), userInfo: nil, repeats: false)
                        }
                    })
                }
                else {
                    if self.mReconnectTimer == nil {
                        self.mReconnectTimer = Timer.scheduledTimer(timeInterval: tick, target: self, selector: #selector(PBLEWheelChairSocket.onReconnectTimerElapsed), userInfo: nil, repeats: false)
                    }
                }
            }
            else {
                disconnect(true)
                mInternalConnectionState = .pble_DISCONNECTED
            }
        }
        else {
            disconnect(true)
            mInternalConnectionState = .pble_DISCONNECTED
        }
    }
   
    public func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        if (peripheral != self.mPeripheral) {
            /
            return
        }
       
        if (error != nil) {
            disconnect(true)
            return
        }
       
        if ((peripheral.services == nil) || (peripheral.services!.count == 0)) {
            disconnect(true)
            return
        }
       
        for service in peripheral.services! {
            if service.uuid == BLEServiceUUID {
                peripheral.discoverCharacteristics(nil, for: service )
            }
        }
    }
   
    public func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        if (peripheral != self.mPeripheral) {
            /
            return
        }
       
        if (error != nil) {
            disconnect(true)
            return
        }
       
        var found = false
        for characteristic in service.characteristics! {
            if characteristic.uuid == PositionCharUUID {
                self.mCharacteristic = (characteristic)
                peripheral.setNotifyValue(true, for: characteristic )
                found = true
               
                break;
            }
        }
       
        if !found {
            disconnect(true)
            return
        }
    }
   
    /
    public func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
        if !Thread.isMainThread {
            DispatchQueue.main.async(execute: {
                if self.mInitMsgTimer == nil {
                    self.mInitMsgTimer = Timer.scheduledTimer(timeInterval: 2, target: self, selector: #selector(PBLEWheelChairSocket.onTimerTimeElapsed), userInfo: nil, repeats: true)
                }
            })
        }else {
            if self.mInitMsgTimer == nil {
                self.mInitMsgTimer = Timer.scheduledTimer(timeInterval: 2, target: self, selector: #selector(PBLEWheelChairSocket.onTimerTimeElapsed), userInfo: nil, repeats: true)
            }
        }
       
        if error != nil {
            print("Error changing notification state: \(error!.localizedDescription)")
            if error!.localizedDescription == "Encryption is insufficient." {
                print(error?.localizedDescription)
                print(mPeripheral.state)
                setState(.bt_DECLINE)
            }
        }
       
        if (characteristic.isNotifying) {
            setState(.bt_CONNECTED)
        }
    }
   
    public func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor descriptor: CBDescriptor, error: Error?) {
        if error != nil{
            print(error?.localizedDescription)
        }
        else{
            if let data: Data = descriptor.value as? Data{
                mMessageParser!.dataHasArrived(data)
            }
        }
    }
    public func peripheralDidUpdateName(_ peripheral: CBPeripheral){
        if(delegate != nil){
            delegate?.onNameChanged!(peripheral.identifier, newName: peripheral.name!)
        }
    }
   
    /
   
    public class func createInstance(_ identifier: UUID) -> PBLEWheelChairSocket? {
        let centralQueue = DispatchQueue(label: "com.permobil.PBLEDiscoveryQueue")
        let central  =  CBCentralManager(delegate: nil, queue: centralQueue)
       
        let foundperipherals = central.retrievePeripherals(withIdentifiers: [identifier])
        if foundperipherals.count <= 0
        {
            return nil
        }
       
        return PBLEWheelChairSocket(peripheral: foundperipherals[0], central: central)
    }
   
    /*
     Connects to the wheelchair.
     */
    public func connect() {
        mInternalConnectionState = .pble_CONNECTING
        mManualDisConnect = false
        connect(true)
    }
   
    public func connect(_ sendStateChange: Bool) -> Bool {
       
       
        if mPeripheral.state == CBPeripheralState.connecting ||
            mPeripheral.state == CBPeripheralState.connected ||
            mPeripheral.state == CBPeripheralState.disconnecting{
            return false
        }
       
       
        / 
        mCentral.connect(mPeripheral, options: nil)
       
       
        if sendStateChange {
            setState(.bt_CONNECTING)
        }
       
        return true;
    }
    var mManualDisConnect = false
    /*
     Disconnects from the wheelchair.
     */
    public func disconnect() {
        mInternalConnectionState = .pble_DISCONNECTING
        mManualDisConnect = true
        disconnect(true)
    }
   
    internal func disconnect(_ sendStateChange: Bool) {
       
        if mInitMsgTimer != nil {
            mInitMsgTimer?.invalidate()
            mInitMsgTimer = nil
        }
       
        if mReconnectTimer != nil {
            mReconnectTimer?.invalidate()
            mReconnectTimer = nil
        }
       
        mPeripheral.delegate = nil
        if mCharacteristic != nil  {
            mPeripheral.setNotifyValue(false, for: mCharacteristic!)
            mCharacteristic = nil
        }
       
        mCentral.cancelPeripheralConnection(mPeripheral)
        mCentral.delegate = nil
        mCharacteristic = nil
        if sendStateChange {
            setState(.bt_NOT_CONNECTED)
        }
    }
   
    public func sendCommand(_ cmd: BaseCommansMessage){
        if let interpretor = cmd.getIterpretor() {
            mInterpretor = interpretor
        }
        send(cmd.getFormatedMessage())
    }
   
    /
   
    func onNewMessage(_ message: MessageBase){
        mInitMsgTimer?.invalidate()
        mInitMsgTimer = nil
       
        if( message.MessageType == BleMessageType.CmdResponseMsg){
            let action = Action()
            mInterpretor?.interprate(message, action: action)
            if action.Interrupted {
                mInterpretor = nil
            }
            return
        }
       
        /
        if let value = message.getValue(RX_VSC_DATA_MESSAGE_KEY_CONNSTATE){
            switch(value){
            case 0:
                setState(.bt_NOT_OWNER_CANT_GAIN)
            case 1:
                let currentTime = Date()
                if currentTime.isGreaterThanDate(tryTakeDate.addSeconds(1)) {
                    takeOwnership()
                    print(" \(currentTime) Taking")
                    tryTakeDate = currentTime
                }
               
                setState(.bt_NOT_OWNER_WAITING)
            case 2:
                setState(.bt_NOT_OWNER_WAITING)
               
            default:
                break
            }
        }
            /
        else
        {
            mInternalConnectionState = .pble_CONNECTED
            if( !checkForErrorState(message) ) {
                setState(.bt_CONNECTED_SLOT1)
            }
           
            let extraInfo = WheelChairExtraInfo(messageToCopy: message)
            if WheelchairExtraInfoFilter.filter(extraInfo) {
                delegate?.onNewWheelChairExtraInfoMessage(extraInfo)
            }
           
            let wheelChairInfo = WheelChairInfo(messageToCopy: message)
            delegate?.onNewWheelChairInfoMessage(wheelChairInfo)
           
           
        }
    }
   
    /
    /
    /
    private func checkForErrorState(_ values: MessageBase) -> Bool{
       
        let state = values.getValue(RX_VSC_DATA_MESSAGE_KEY_STATE)
        if  state == nil {
            return false
        }
       
        if ((state! & (VSC_DATA_MASK_STATE_ICSCON_BLOCK | VSC_DATA_MASK_STATE_ICSCON_FAILED)) > 0) {
            if ((state! & VSC_DATA_MASK_STATE_ICSCON_FAILED) > 0){
                let errCode = (Int)((state! & VSC_DATA_MASK_STATE_ICSCON_ERRCODE) >> VSC_DATA_SHIFT_STATE_ICSCON_ERRCODE);
               
                if (errCode == VSC_DATA_ICS_ERR_CODE_ISC_SW_TOO_OLD) {
                    setState(.bt_ICS_SW_TOO_OLD)
                    return true
                }
                else if (errCode == VSC_DATA_ICS_ERR_CODE_ISC_SERIAL_WAS_ZERO) {
                    setState(.bt_ICS_SERIAL_ZERO)
                    return true
                }
                else
                {
                    /
                }
            }
            setState(.bt_ICS_FAILED)
            return true
        }
        return false
    }
   
    private func setState(_ newState: ConnectionState){
        if mConnectionState == newState {
            return
        }
       
        mConnectionState = newState
        delegate?.onStateChanged(mConnectionState)
    }
   
    /*
     Sends a message to try gain slot 1 privileges and be able to get data.
     */
    private func takeOwnership(){
        if self.mCharacteristic == nil {
            return
        }
       
        /
        let positionValue = TX_VSC_STATUS_TAKE_OWNERSHIP_KEY
        let data: Data = positionValue.data(using: String.Encoding.utf8)!
        self.mPeripheral?.writeValue(data, for: self.mCharacteristic!, type: CBCharacteristicWriteType.withResponse)
    }
   
   
    /*
     Send a init message to the wheelchair.
     */
    private func sendInitMessage() {
        if self.mCharacteristic == nil {
            return
        }
       
        let message = TX_VSC_STATUS_SEND_INIT_MESSAGE
        send(message)
    }
   
    /*
     Sends a string message to the wheelchair.
     */
    private func send(_ strData: String) {
        let data: Data = strData.data(using: String.Encoding.utf8)!
        send(data)
    }
   
    private func send(_ data: Data) {
        self.mPeripheral?.writeValue(data, for: self.mCharacteristic!, type: CBCharacteristicWriteType.withResponse)
    }
   
    func createChacksum(_ data: Data ) -> String {
        var sum : Int = 0;
        let n = data.count
       
        for i in 0 ..< n {
           
            var array = [UInt8](repeating: 0, count: 1)
            (data as NSData).getBytes(&array, range: NSMakeRange(i, 1))
            sum += Int(array[0])
        }
       
        sum &= 0xFF;
       
        return String(format:"%2X", sum)
    }
   
    /*
     Timer for sending init message to the Wheelchair.
     */
    func onTimerTimeElapsed() {
        print("\(self.Name) Send Key")
        sendInitMessage()
    }
   
    func onReconnectTimerElapsed() {
        if !mManualDisConnect {
            connect(false)
        }
        mReconnectTimer!.invalidate()
        mReconnectTimer = nil
    }
}
@objc
public enum InternalConnectionState: Int {
    case pble_DISCONNECTED,
    pble_DISCONNECTING,
    pble_CONNECTING,
    pble_CONNECTED
}
@objc
public protocol PBLEWheelchairSocketDelegate : class {
    func onStateChanged(_ state: ConnectionState)
    func onNewWheelChairInfoMessage(_ message: WheelChairInfo)
    func onNewWheelChairExtraInfoMessage(_ message: WheelChairExtraInfo)
    @objc optional func onNameChanged(_ chairId:UUID, newName: String)
}