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:
TerminalTimeouts.connectionTimeout
TerminalTimeouts.powerTimeout
TerminalTimeouts.protocolTimeout
TerminalTimeouts.apduTimeout
TerminalTimeouts.controlTimeout
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)
}