I work as a slot machine technician at a casino. As part of our usual maintenance routines, we have a spreadsheet to keep track of bill validator acceptance/rejection rates for bills and barcode tickets. We run a program called Accload (part of JCM's tool suite, which can be found at http://slot-tech.com/interesting_stuff/jcm/JCM%20Apps/UBAToolSuiteStandardEditionVer106.zip), plug a USB cable into the front of the bill validator, and Accload displays the relevant data, which we then insert into the spreadsheet manually along with the bill validator's serial number and model type.
I decided I wanted to reverse engineer the communication between Accload and the bill validator so that I could automate the entry of these data. (And because I thought it would be fun.) For this project, I used two bill validators made by JCM: alternately a UBA-10 and an iVizion. Service manuals for both can be found at http://users.kopachr.is/~chris/files/jcm/
When the JCM tool suite is installed, both bill validators show up as USB COM ports in windows. To begin with, I logged serial communication using HHD's free version of their serial port monitor while using JCM's tool suite with those bill validators. I then attempted to mimic this communication using pyserial, but it seemed that the generic USB serial driver pyserial tried to use conflicted with the driver installed as part of the JCM toolsuite, and I wasn't able to open the COM port. On Linux (Fedora Workstation 24), the UBA showed up as /dev/ttyACM0
and I was able to open the serial port and mimic the logged communication using default port settings (9600 baud, 8 bits, no parity, 1 stop bit). The iVizion did not show up at first, but did show up as /dev/ttyUSB0
after echoing the vendor and product ID to the generic usb-serial driver's new_id
endpoint: echo 2475 0105 > /sys/bus/usb-serial/drivers/generic/new_id
When the JCM tool suite (before opening Accload) is connected to a bill validator, it polls for its status, boot ROM version, flash ROM status, serial number, firmware version, and firmware CRC. These messages appear to consist of 0xAA
as the header for a request, followed by the total length byte, and then a byte representing which item is being requested. The response consists of 0xBB
for the header, followed by the total length byte, the same byte indicating which item was requested, and then the data that was requested. These are example requests seen with the UBA. The basic protocol appears to be identical between the UBA and the iVizion.
0xAA 0x03 0x00
-0xBB 0x04 0x00 0x00
- Appears to be a status request.
0x00
indicates status okay.
- Appears to be a status request.
0xAA 0x03 0x01
-0xBB 0x05 0x01 0x07 0x00
- Unknown
0xAA 0x03 0x03
-0xBB 0x05 0x03 0x02 0x01
- Unknown. Possibly indicates command to initiate memory transfer? See below.
0xAA 0x03 0x04
-0xBB 0x08 0x04 "B03"
- Boot ROM version as null-terminated string
0xAA 0x03 0x05
-0xBB 0x04 0x05 0x03
- Unknown. Possibly flash ROM status because the rest of the commands seem to go in order that they're displayed in JCM tool suite?
0xAA 0x03 0x06
-0xBB 0x0f 0x06 "12345678901"
- Serial number
0xAA 0x03 0x07
-0xBB 0x28 0x07 "U(USA)-10-SS ID003-03V208-31 18NOV11"
- Version string (same as returned by the
0x88
request in the ID-003 protocol)
- Version string (same as returned by the
0xAA 0x03 0x08
-0xBB 0x05 0x08 0x04 0x1C
- Firmware 16-bit CRC in little-endian format
0xAA 0x03 0x09
-0xBB 0x04 0x09 0x03
- Unknown
0xAA 0x04 0x02 0x01
-0xBB 0x04 0x02 0x01
- Ready for memory dump? This command appears to be required before sending commands requesting memory pages. The response to
0x03
is0x02 0x01
, perhaps this is variable among different models/markets.
- Ready for memory dump? This command appears to be required before sending commands requesting memory pages. The response to
The iVizion and the UBA use different commands to fetch statistics data. In both, however, it appears the meaning of the commands is less "give me statistics data" and more "dump a certain page of your nonvolatile memory." For the UBA, the command has a 0x10
header byte, followed by a length following byte (as opposed to the total length used in the basic protocol), then the bytes 0xD1 0x12
, a byte indicating the page of memory to retrieve, and a simple checksum byte. The bill validator will respond similarly, attaching the data and the checksum at the end. For example, sending 0x10 0x04 0xD1 0x12 0x01 0xF8
will cause the bill validator to respond with 0x10 0x08 0xD1 0x12 0x01 0x00 0x02 0x00 0x01 0xFF
.
For the iVizion, the command has a two-byte header 0x02 0x00
followed by a total length byte, then 0x71 0xD1 0x12
and the page to retrieve. The iVizion does not use a checksum. Example: sending 0x02 0x00 0x07 0x71 0xD1 0x12 0x01
will yield 0x02 0x00 0x0C 0x71 0xD1 0x12 0x01 0x03 0x00 0x02 0x00 0x32
.
For both the UBA and the iVizion, pages start at 0x01, incrementing until the bill validator doesn't return any more data. There should be less than 256 pages in any case, with each page less than 256 bytes long.
I suggest that the "get statistics" commands are actually "dump memory" commands because it was immediately apparent that the data retrieved is not divided into sections logically, but by length instead. Some pages do appear to be self-contained sections (for example, iVizion page 0x06 only contains a record of the last four digits of the last ten barcodes read by the bill validator), but others run together (iVizion page 0x35 contains the last few bytes of one previous statistics record and the beginning of another). Furthermore, several pages at the end only contain null bytes. Therefore, to make the data easier to work with, I suggest concatenating all pages together and treating them as one memory-mapped file. As each item is a specific length and always appears in the same position relative to other items, particular items can be retrieved simply by pointing to the right address and reading.
After reading from both a UBA and an iVizion, I had Accload save the data to a file so that I could continue to analyze it without a bill validator connected. I opened the saved files in a hex editor and found that the layout was almost exactly the same as the serial data that I logged following the page number. When I took a closer look, the only differences appeared to be that the file was saved in little-endian format, while the data sent by the bill validator was all big-endian, and the saved file for the iVizion had some null bytes removed (73 bytes near the beginning).
To determine the memory structure, all I had to do was look for particular numbers, change them in the file, and reload the file in Accload to see if it was what I thought it was. After identifying the locations of certain key items, I checked another bill validator to confirm the addresses were the same.
For both UBA and iVizion, it appears that most or all integers are 16 bits, unsigned, with big endianness. Treating all the data received from the bill validator as one memory-mapped file, these are some example offsets where useful information can be found:
UBA
- Offset
0x0084
- Currency assign table. Each denomination is essentially a two-byte float: one byte (signed?) for the exponent and one byte for the significand. Each denomination is followed by four null bytes or one null byte and three ASCII spaces (0x20), e.g.0x01 0x05 0x00 0x20 0x20 0x20
in the US software is a $5 bill. The UBA supports 20 denominations. For unused denominations, the exponent will be 0x01 and the significand will be 0x00. - Offset
0x00FC
- Total inserted notes. (Immediately follows currency assign table.) - Offset
0x00FE
- Beginning of note acceptance data. Note acceptance data is broken down by direction the note was inserted (four directions) and by denomination. Certain markets may have multiple records for each denomination as different print series of notes are treated as separate denominations. The UBA can have up to 20 denominations assigned. Unused denominations are still present in the data as null bytes so as not to affect the positions of other items. In the latest US version of the UBA's firmware, there are 16 denominations used: $1 bills and three series each of $5, $10, $20, $50, and $100 bills. The UBA does not accept $2 bills. Each full record is 34 bytes and consists of the number of accepted notes of that denomination followed by the number of rejected notes for each of 16 reject reasons. - Offset
0x0BE2
- Start date for statistics inddmmyyyy
format, followed by M/C# without separator. M/C# is 20 characters, right-filled with spaces. - Offset
0x0C08
- Tickets accepted followed by tickets rejected for each of 32 reject reasons (16 with index mark, i.e. upside-down, and 16 without).
iVizion
- Offset
0x0065
- Currency assign table. Like UBA, except following the first null byte is a byte indicating the banknote series, e.g.0x01 0x0A 0x00 0x60 0x00 0x00
for the US software is $10, series 1996 (10 * 10^1, series 96). Unused denominations still use a null byte for the series, but denominations of unknown series use0xFF
. - Offset
0x01DB
- Last four digits of the last ten barcodes read. ASCII, no separator. - Offset
0x0203
- Beginning of note acceptance data. Like UBA, except there are 50 denominations, plus tickets for each direction. Full record still 34 bytes (accepted plus rejects for 16 reject reasons). For tickets, the rejection record is elsewhere, and the remaining 32 bytes in this section are null. - Offset
0x1635
- Tickets rejected for each of 16 reject reasons. Not separated by direction, all in one 32-byte record. - Offset
0x1E23
- Start date for statistics inddmmyyyy
format followed by M/C# without a separator. Like UBA.
If you're using an iVizion bill validator, you'll need to bind the iVizion's vendor ID and product ID to the generic usb-serial driver before plugging it in. This can be done, for example, by executing echo 2475 0105 > /sys/bus/usb-serial/drivers/generic/new_id
as root. You'll need to execute that command after every reboot if you choose to do it that way.
After plugging in the iVizion, it will show up as /dev/ttyUSB0
(or /dev/ttyUSB1
or 2
or 3
or whatever if you have multiple USB serial ports - the example script tries to use /dev/ttyUSB0
). After plugging in a UBA, it should show up as /dev/ttyACM0
.
Plug in the bill validator, using the front USB port on the bill validator. Run the example script and type in either "UBA" or "iVizion" according to which model you're using. The script will retrieve and print the acceptance/rejection rate for notes and tickets and save the raw data as bv.dat
. Note that the saved file will not be compatible with Accload for either the UBA or the iVizion, as it will be saved in big-endian format just as is received from the bill validator.
The contents of this entire gist, plus the serial logs and Accload files I used for testing can be found at http://users.kopachr.is/~chris/files/jcm/stats.zip
The WBA is kind of a special case. It doesn't have a front USB port, so all communications must go through the rear harness. The communication is TxD on pin 4 and RxD on pin 6. The WBA uses TTL-level opto-isolated serial. After shifting the levels (e.g. through a MAX232), any USB-serial adapter should work.
The WBA does not support the basic "AA/BB" protocol described above, but otherwise supports memory dumps exactly like the UBA, with the exception that the WBA uses even parity in its communications and requires a longer read timeout because of slower processing speed. Because the WBA has less memory than the UBA, the addresses of the items mentioned above are a bit different.
- Offset
0x0084
- Currency assign table, exactly like UBA except the WBA only supports 16 denominations. - Offset
0x00E4
- Total notes, immediately following currency assign table just like UBA. - Offset
0x00E6
- Beginning of note acceptance data. Like UBA, except only 16 denominations. - Offset
0x09AA
- Begin date and M/C#, like UBA. On every WBA I tested so far, the start date was????????
. - Offset
0x09D0
- Accepted tickets followed by rejected tickets for 32 reject reasons (16 each upside-down and right side up).