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:
TerminalTimeouts.connectionTimeoutTerminalTimeouts.powerTimeoutTerminalTimeouts.protocolTimeoutTerminalTimeouts.apduTimeoutTerminalTimeouts.controlTimeout
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.
// ...
}
}