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