Include the latest library version in your gradle.build
dependency block
latest
placeholder with the latest release versionimplementation "com.ingonoka:cba9-driver:<latest>"
The CBA9 is a banknote acceptor manufactured by Innovate Technology for the Asian market. The device comes with a technical handbook that gives details on power and data connection as well as hardware setup. The handbook also explains how to configure the device for the SSP™ protocol.
The CBA9 must be connected via an OTG cable to the Android device.
The problem is that most Android devices will not charge the device when in USB OTG mode. Usually, the provider of the Android device can change this default behaviour. The OTG cable will also likely have to be altered to get the power for charging from a separate charger.
Of course the BNA cannot get its power from the Android device and will also need a separate connection for power.
Before starting the USB device manager, which will create and attach a driver to a connected CBA, set the configuration that will be applied to newly created drivers.
The configuration is set by changing the stateLog
and cba9Props
properties of the Cba9Factory
.
This can be done also when the USB device manager is already running, but changes will only take effect when the CBA9 is disconnected and then reconnected again.
/**
* Example for changing state log for newly created driver to a Room
* based log
*/
Cba9Factory.stateLog = roomCba9StateLog(this)
/**
* Example for changing properties of CBA9 driver to limiting accepted
* banknotes to 20 and 100 Pesos
*/
Cba9Factory.cba9Props = Cba9Factory.cba9Props.copy(
acceptedDenominations = listOf(
Denomination(20, CountryCode.PHP),
Denomination(100, CountryCode.PHP)
)
)
/**
* Example for changing USB properties for newly created CBA9 driver
*/
Cba9Factory.cba9Props = Cba9Factory.cba9Props.copy(
usbProps = Cba9Factory.cba9Props.usbProps.copy(
timeoutReceive = 3.seconds
)
)
CBA9 drivers are instantiated by the USB device manager. Once the USB device manager has been started, it will attempt to create and attach a driver for a CBA9 that is already connected or that will be connected (plugged-in) later.
TIP The logcat
may show an error for an already connected device, but usually the second attempt to attach a driver will succeed.
val usbManager = UsbDeviceManager()
usbManager.start(this)
You need to collect the connectedDrivers
state flow to get the CBA9 driver. This state flow holds a list of connected drivers. A driver is added to the list of connected drivers for a newly connected CBA9, and removed when the CBA9 is disconnected.
The CBA0 driver manages a validator object in a state flow. When the driver is created it will first intsantiate a validator object and then use it to send a number of configuration commands to the CBA9. This will take a couple of seconds, which means that the application has to wait for the validator object to be published by the driver before it can communicate with the device (see Use the CBA Validator).
private fun monitorConnectedDrivers(usbManager: UsbDeviceManager, binding: ActivityMainBinding) =
lifecycleScope.launch {
usbManager.connectedDrivers.collect { drivers ->
if (drivers.isNotEmpty()) {
logger.info("List of connected drivers changed")
usbManager.getDriver(Cba9::class)
.onSuccess { /* New CBA9 connection */ }
.onFailure { /* Some other device was connected or CBA9 was lost */ }
}
}
}
The state
flow of the validator provides information about the current status, such as whether the validator is scanning/stacking/rejecting a banknote or whether a banknote is currently in escrow.
/**
* cba9 is the driver provided by the connected Drivers flow of the USB
* device manager. The `cba9.cba9validator` state floww will contain null at
* first. The `filterNotNull` will wait for the validator object to
* be published, before collecting its state.
*/
cba9.cba9Validator.filterNotNull().collect { iCba9Validator ->
iCba9Validator.state.collect { stateHolder ->
when (stateHolder.state) {
Cba9ValidatorState.UNDEFINED -> TODO()
Cba9ValidatorState.DISCONNECTED -> TODO()
Cba9ValidatorState.FAILURE -> TODO()
Cba9ValidatorState.INITIALIZING -> TODO()
Cba9ValidatorState.SCANNING -> TODO()
Cba9ValidatorState.NOTE_IN_ESCROW -> TODO()
Cba9ValidatorState.REJECTING -> TODO()
Cba9ValidatorState.STACKING -> TODO()
Cba9ValidatorState.STACKING_CREDITED -> TODO()
Cba9ValidatorState.READY -> TODO()
Cba9ValidatorState.UNSAFE_JAM -> TODO()
Cba9ValidatorState.DISABLED -> TODO()
Cba9ValidatorState.INHIBITED -> TODO()
Cba9ValidatorState.CASHBOX_FULL -> TODO()
}
}
}
The CBA9 can hold one banknote in escrow, from where it can either be moved into the cash box or returned to the user. When a banknote is successfully validated, it will first be moved into the escrow position. The CBA9 will hold it there and wait for instructions. The application can instruct the validator to reject or accept the banknote by calling rejectBanknote()
or acceptBanknote()
. If either function is called when no banknote is in escrow, then the driver is put into this state and the banknote will be immediately rejected or accepted as soon as it enters into escrow.
The validator object contains a cashbox
property which manages a fill level state flow. You can collect the levels
state flow of this property to get the latest fill levels of the banknote acceptor. Note that there is no way for the driver to know how much money is in the cash box when it starts up. The fill level will therefore only reflect the correct amount when emptying the cash box is recorded by calling the setEmpty
driver function
private fun updateCba9FillLevel(cba9: Cba9, binding: ActivityMainBinding) =
lifecycleScope.launch {
cba9.cba9Validator.filterNotNull().collect {
val currency = it.configData.countryCode
it.cashbox.levels.collect { levelHolder ->
binding.textViewFillLevelValue.text =
getString(
R.string.fillLevel,
currency.countryCode,
levelHolder.banknoteValue,
levelHolder.banknoteCount
)
}
}
}
The validator keeps a status log, which includes banknote acceptance events, recording of hardware counters and cash collection events. The library comes with an implementation of a status log that is based on a Room database.
The status log keeps track of accepted banknotes automatically. However, the user of the library has to ensure that counters kept by the actual device and any cash box collection (emptying) is recorded by calling the functions updateDeviceCounters
and emptyCashbox
respectively.
The updateDeviceCounters
function logs the counters as read from the device with the getCounters
SSP command. This would usually be a good idea at startup and after the cashbox was emptied. The log entries can be used to check the integrity of the banknote counters.
There is a function audit
, which compares the counters kept by the state log with the counters read from the device itself. Calling this function will tell you whether the CBA9 was in use while the driver wasn’t running. It is also a good idea to call this function right after calling updateDeviceCounters
. This should be done on startup and on a regular basis to monitor the correct function of the driver and the device.
Failed audits will be logged and can be reviewed.