Skip to content

ingonoka/cba9-driver

Repository files navigation

Library for managing CBA9 Banknote Validators

Maven Central Version

The library is meant for Android devices that are connected via USB to a CBA9 banknote validator.

The validator is expected to be configured for the SSP™ protocol (the device supports other protocols as well).

Limitation

The library does not support the optional encryption provided in the SSP™ protocol.

Setup

Software

Include the latest library version in your gradle.build dependency block

Replace the latest placeholder with the latest release version
implementation "com.ingonoka:cba9-driver:<latest>"

Hardware

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.

Usage

Configuration of the CBA9 Driver Factory

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.

Sample configuration setting
/**
 * 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
    )
)

Start USB Device Manager

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.

Start of the USB device manager
val usbManager = UsbDeviceManager()
usbManager.start(this)

Monitor Driver Connection and Disconnection Events

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).

Sample monitoring of CBA9 connections
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 */ }

            }
        }
    }

Use the CBA Validator

Monitor the validator status

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.

Sample monitoring of the validator object provided by the driver
/**
 *  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.

Monitor Cash Box Fill Levels

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

Sample monitoring of the cashbox state
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
                )
        }
    }
}

Manage the validator status log

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.