ACS Smart Card I/O iOS Framework

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

BLE Usage Description

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

<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 the ACSSmartCardIO module. You can get an instance of the Bluetooth Terminal Manager and the Terminal Factory from the BluetoothSmartCard.shared object. To receive events from the 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 the 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 the 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 the BluetoothTerminalManagerDelegate.bluetoothTerminalManager(_:didDiscover:) method from your delegate object to return the 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 the BluetoothTerminalManager.stopScan() method 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 the BluetoothTerminalManager.setMasterKey(terminal:masterKey:) method 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 the CardTerminal.connect(protocolString:) method 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 the Card.disconnect(reset:) method 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 the Card.basicChannel() method or Card.openLogicalChannel() method from the Card object. After a CardChannel object is returned, you can transmit APDUs using the CardChannel.transmit(apdu:) method, the CardChannel.transmit(buffer:) method or the CardChannel.transmit(data:) method from the 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 the TransmitOptions class.

TransmitOptions.t0GetResponse = false
TransmitOptions.t1GetResponse = false

Transmitting Control Commands

If the card is connected successfully, you can call the Card.transmitControlCommand(controlCode:command:) method 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 the BluetoothTerminalManager.timeouts(terminal:) method from the BluetoothTerminalManager object to return the 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 the BluetoothTerminalManager.batteryStatus(terminal:timeout:) method 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 the BluetoothTerminalManager.batteryLevel(terminal:timeout:) method 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 the BluetoothTerminalManager.deviceInfo(terminal:type:timeout:) method 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 the CardTerminal.connect(protocolString:) method from the CardTerminal object. To terminate the Bluetooth connection manually, you must call the BluetoothTerminalManager.disconnect(terminal:) method from the BluetoothTerminalManager object.

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

The Bluetooth Terminal Manager will call BluetoothTerminalManagerDelegate.bluetoothTerminalManager(_:didDisconnect:error:) method from your delegate object to return the CardTerminal object and the Error object if any card terminal is disconnected.

// MARK: - BluetoothTerminalManagerDelegate
extension ViewController: BluetoothTerminalManagerDelegate {

    ...

    func bluetoothTerminalManager(_ manager: BluetoothTerminalManager,
                                  didDisconnect terminal: CardTerminal,
                                  error: Error?) {

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