ACS Smart Card I/O iOS Framework

This framework provides classes and interfaces for communicating with ACS Bluetooth readers on iOS 9.0 or above.

BLE Usage Description

Your app must add the following keys to Info.plist in order to access Bluetooth peripheral.

<key>NSBluetoothAlwaysUsageDescription</key>
<string>$(PRODUCT_NAME) would like to use Bluetooth to connect ACS readers.</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>$(PRODUCT_NAME) would like to use Bluetooth to connect ACS readers.</string>

Setting Up BLE

Before connecting Bluetooth card terminals, your app must import ACSSmartCardIO module. You can get an instance of Bluetooth Terminal Manager and Terminal Factory from BluetoothSmartCard.shared object. To receive events from BluetoothTerminalManager object, your app must assign a delegate object to it.

...

import SmartCardIO
import ACSSmartCardIO

...

class ViewController: UIViewController {

    ...

    let manager = BluetoothSmartCard.shared.manager
    let factory = BluetoothSmartCard.shared.factory

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        ...

        // Set the delegate.
        manager.delegate = self

        ...
    }

    ...
}

...

Your app must check the state from CentralManager object and show the message if Bluetooth is not supported or other reasons that your app cannot use Bluetooth on your iOS device.

// MARK: - BluetoothTerminalManagerDelegate
extension ViewController: BluetoothTerminalManagerDelegate {

    func bluetoothTerminalManagerDidUpdateState(_ manager: BluetoothTerminalManager) {

        var message = ""

        switch manager.centralManager.state {

        case .unknown, .resetting:
            message = "The update is being started. Please wait until Bluetooth is ready."

        case .unsupported:
            message = "This device does not support Bluetooth low energy."

        case .unauthorized:
            message = "This app is not authorized to use Bluetooth low energy."

        case .poweredOff:
            if !firstRun {
                message = "You must turn on Bluetooth in Settings in order to use the reader."
            }

        default:
            break
        }

        if !message.isEmpty {

            // TODO: Show the message.
            // ...
        }

        firstRun = false
    }

    ...
}

Finding BLE Card Terminals

To find BLE card terminals, you must use BluetoothTerminalManager.startScan(terminalType:) method from your BluetoothTerminalManager object.

...

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

    if let identifier = segue.identifier {
        switch identifier {

        case "ScanTerminals":

            ...

            // Start the scan.
            manager.startScan(terminalType: .amr220c)

            ...

        default:
            break
        }
    }
}

...

The Bluetooth Terminal Manager will call BluetoothTerminalManagerDelegate.bluetoothTerminalManager(_:didDiscover:) method from your delegate object to return CardTerminal object if any card terminal is detected.

// MARK: - BluetoothTerminalManagerDelegate
extension ViewController: BluetoothTerminalManagerDelegate {

    ...

    func bluetoothTerminalManager(_ manager: BluetoothTerminalManager, didDiscover terminal: CardTerminal) {

        // TODO: Process the card terminal here.
        // ...
    }
}

Because continuing to scan drains the battery, you must call BluetoothTerminalManager.stopScan() after your card terminal was found.

...

@IBAction func unwindToMain(segue: UIStoryboardSegue) {

    if let identifier = segue.identifier {
        switch identifier {

        case "ReturnTerminal":

            ...

            // Stop the scan.
            manager.stopScan()

            ...

        ...

        default:
            break
        }
    }
}

...

Authenticating a BLE Card Terminal

Normally, the BLE card terminal will use the default key in authentication. If you have modified the master key, you must call BluetoothTerminalManager.setMasterKey(terminal:masterKey:) before connecting to a card.

let newKey = [
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08,
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08
]

manager.setMasterKey(terminal: terminal, masterKey: newKey)

Connecting to a Card

To connect a card, you must call CardTerminal.connect(protocolString:) from your CardTerminal object to return a Card object. The available protocols are T=0, T=1, T=0 or T=1 and direct.

// Protocol:
// "T=0"    - T=0
// "T=1"    - T=1
// "*"      - T=0 or T=1
// "direct" - Direct mode
do {
    Card card = try terminal.connect(protocolString: "*")
} catch {
    print(error.localizedDescription)
}

Disconnecting from the Card

After using the card, you can disconnect it by calling Card.disconnect(reset:) from the Card object. If you want to reset the card after disconnecting, you can pass true to the parameter.

do {
    try card.disconnect(reset: false)
} catch {
    print(error.localizedDescription)
}

Transmitting APDUs

If the card is connected successfully, you must open a channel to transmit APDUs. You can call either Card.basicChannel() or Card.openLogicalChannel() from the Card object. After a CardChannel object is returned, you can transmit APDUs using CardChannel.transmit(apdu:), CardChannel.transmit(buffer:) or CardChannel.transmit(data:) from CardChannel object.

let command: [UInt8] = [ 0x00, 0x84, 0x00, 0x00, 0x08 ]

do {

    let card = try terminal.connect(protocolString: "*")
    let channel = try card.basicChannel()
    let commandAPDU = try CommandAPDU(apdu: command)
    let responseAPDU = try channel.transmit(apdu: commandAPDU)

} catch {

    print(error.localizedDescription)
}

These methods issue a GET RESPONSE command using SW2 as the Le field if 61 XX is received and reissue the command using SW2 as the Le field if 6C XX is received. To modify the default behaviour, you can set the option by calling methods from TransmitOptions class.

TransmitOptions.t0GetResponse = false
TransmitOptions.t1GetResponse = false

Transmitting Control Commands

If the card is connected successfully, you can call Card.transmitControlCommand(controlCode:command:) from the Card object to transmit control commands.

let controlCode = BluetoothTerminalManager.ioctlEscape

do {

    let card = try terminal.connect(protocolString: "direct")
    let response = try card.transmitControlCommand(controlCode: controlCode,
                                                   command: command)

} catch {

    print(error.localizedDescription)
}

Modifying the Timeout Settings

To get the timeout settings of the card terminal, you must call BluetoothTerminalManager.timeouts(terminal:) from the BluetoothTerminalManager object to return TerminalTimeouts object.

You can modify the timeout from the following properties:

do {

    let timeouts = try manager.timeouts(terminal: terminal)
    timeouts.apduTimeout = 20000

} catch {

    print(error.localizedDescription)
}

Getting the Battery Status

You can call BluetoothTerminalManager.batteryStatus(terminal:timeout:) from the BluetoothTerminalManager object to return the battery status. If the card terminal does not support the battery status, it will return BluetoothTerminalManager.BatteryStatus.notSupported.

do {
    let batteryStatus = try manager.batteryStatus(terminal: terminal,
                                                  timeout: 10000)
} catch {
    print(error.localizedDescription)
}

Getting the Battery Level

You can call BluetoothTerminalManager.batteryLevel(terminal:timeout:) from the BluetoothTerminalManager object to return the battery level. If the card terminal does not support the battery level, it will return -1.

do {
    let batteryLevel = manager.batteryLevel(terminal: terminal,
                                            timeout: 10000)
} catch {
    print(error.localizedDescription)
}

Getting the Device Information

You can call BluetoothTerminalManager.deviceInfo(terminal:type:timeout:) from the BluetoothTerminalManager object to return the device information. If the card terminal does not support the device information, it will return nil.

let type = BluetoothTerminalManager.DeviceInfoType.modelNumberString

do {
    let deviceInfo = manager.deviceInfo(terminal: terminal,
                                        type: type,
                                        timeout: 10000)
} catch {
    print(error.localizedDescription)
}

Disconnecting from BLE Card Terminal

The library connects BLE card terminal automatically when calling CardTerminal.connect(protocolString:) from the CardTerminal object. To terminate the Bluetooth connection manually, you must call BluetoothTerminalManager.disconnect(terminal:) from BluetoothTerminalManager object.

do {
    try manager.disconnect(terminal: terminal)
} catch {
    print(error.localizedDescription)
}