PayXpert - User documentation
USB/RS232 - Specifications
RS232 specifications are more complex than any other, it implies a strict data structure and data exchange workflow. This section will only talk about technical specifications regarding RS232. The Concert data structure will be fully explained in other section.
So USB/RS232 means that the cash register and the POS terminal will be linked thanks to a cable—USB or RS232. The type of cable does not matter, the communication will work the same way in Concert. Unlike WiFi, USB/RS232 is half-duplex, which means that the cash register and the POS cannot emit and receive messages simultaneously.
This is the list of speeds (baud or symbol/symbols) that PayXpress supports:
Baud | Is supported? |
---|---|
1200 | |
9600 | |
19200 | |
38400 | |
57600 | |
115200 |
Each frame is composed of max 512 data bytes.
A data byte will be composed of 7 bits of data and a bit of parity.
The parity used in Concert specifications is EVEN. This means the number of raised bits (1 bit) is always even in a data byte. If the 7 bits of data contain an even raised bits number, then the parity bit is 0. If it is odd, the parity bit is 1.
This is a security control to prevent any tampering of the data. Regarding the 7 bits of data, they are ASCII-7 and the range of supported characters begins to 0x20 (hex) SPACE
until 0x7F (hex) DEL
. You can find the list of supported characters in this table.
In a frame, the first data byte is a command that defines the meaning of the frame. All the commands are described in the table below:
NAME | DESCRIPTION | HEX VALUE |
---|---|---|
ENQ | Ask to open a new session |
|
ACK | Positive reception acknowledge |
|
NAK | Negative reception acknowledge |
|
STX | Start of a message content |
|
ETX | End of a message content |
|
EOT | End of the session |
|
There is no parity bit for the command data byte.
For your information, most of the time, depending on the software, STX (0x02
), ETX (0x03
) and LRC characters will not be visible on screen because they are not printable text. For example, Confluence does not display them, so when a frame is used as example, it will be a picture instead of text.
The specifications define the order of commands when cash register and POS communicate. Like in Ethernet/WIFI, the POS is in a waiting state and it is up to the cash register to initiate the communication.
ENQ is always the first command to be sent, it opens a new session receiver side
ACK is sent by the receiver to confirm that it successfully received the sender’s message
NAK on the other hand is sent by the receiver to confirm that it did not receive the sender message or data controls failed
STX and ETX are always sent together like this: “STX<message>ETXlrc”. Sometimes, the word “message” in Concert specifications is used to refer to the entire frame.
lrc stands for longitudinal redundancy check. It works like a checksum, it is computed using all the bytes from <message> + ETX byte. You can use online websites such as this one if you want to compute the lrc value and compare it to the received one you got. Once the message is received, the receiver computes lrc on its side and compares its value to the one sent. If it does not match, an error will be thrown.
This exchange data flow is fully explained below with diagram and tables.
Constant/variables names
Name | Description |
---|---|
N1 | Emitting frame counter. Max value is 3 |
N2 | Session counter. Max value is 3 |
T0 | Current timer, value 0 means no timer |
T1 | Timer, 500ms for POS, 600ms for cash register |
RECEIPT | Receipt status, possible values are OK and NOK |
EMIT | Emitting status, possible values are OK, ABORT and NOK |
Listening procedure for cash register & POS
State Event | R0 Standby | R1 Waiting ENQ | R2 Waiting STX | R3 Waiting EOT |
---|---|---|---|---|
Start listening | N1 = 0, N2 = 0 => R1 | T0 = 0 => R1 | Ignored | Ignored |
ENQ receipt | Ignored | T0 = T1 emit ACK => R2 | Ignored | Ignored |
STX<message>ETX receipt and there is no data control error | Ignored | Ignored | T0 = T1 => R3 | Ignored |
STX<message>ETX receipt but | Ignored | Ignored | If N1 < 3 => R2 if N1 = 3 and N2 < 3 => R1 if N1 = 3 and N2 = 3 => R0 |
|
Timeout | Ignored | Ignored | Same as [STX/ETX receipt but lrc is incorrect or there is a parity error/R2] above | RECEIPT = OK => R0 |
EOT receipt | Ignored | Ignored | Ignored | RECEIPT = OK => R0 |
Other characters | Ignored | Ignored | Ignored | Ignored |
Emitting procedure for cash register
State Event | E0 Standby | E1 Start emitting | E2 Waiting ACK | E3 Waiting ACK | E4 Waiting Timeout |
---|---|---|---|---|---|
Start emitting | N1 = 0, N2 = 0 => E1 | T0 = 0 => E2 | Ignored |
| Ignored |
ACK receipt | Ignored | Ignored | T0 = 0 => E3 |
| Ignored |
ENQ receipt | Ignored | Ignored | emit EOT => E0
| Same as [NAK receipt or other characters or parity error/E3] below | EMIT = ABORT => R2 |
NAK receipt or other characters or parity error | Ignored | Ignored | If N2 < 3 => E4 if N2 = 3 => E0 | If N1 < 3 => E3 if N1 = 3 and N2 < 3 => E4 if N1 = 3 and N2 = 3 => E0 | Ignored |
Timeout | Ignored | Ignored | Same as [NAK receipt or other characters or parity error/E2] above | Same as [NAK receipt or other characters or parity error/E3] above | => E1 |
Developer implementation notes
Unlike Ethernet/WIFI, USB/RS232 connection does not allow any multithreading processes. Do not start any new request if there is already one running: this will lead to errors for both of them.
Parity and LRC controls on STX<message>ETXlrc receipt can both be performed at the same time because they only concern one frame and the same data. As a reminder, parity control is only done on ASCII-7 data bytes (not on the command byte) and LRC is computed from all ASCII-7 data bytes + ETX byte. This is a code snippet (Kotlin) which performs both controls:
fun isParityBitOrLrcOk(frame: List<Byte>, lrc: Byte? = null): Boolean {
var isParityAndLrcOk = false
var computedLrc: Byte = 0x0
// frame only contains ASCII-7 data bytes
for (byte in frame) {
val oneBitsCount = byte.countOneBits()
// oneBitsCount is odd, there is a parity error
if (oneBitsCount % 2 != 0) {
return isParityAndLrcOk
}
// LRC Computation
if (lrc != null)
computedLrc = computedLrc xor byte
}
// Frame does not contain ETX command, do a XOR using ETX value manually
if (lrc != null && frame.last() != 0x03.toByte())
computedLrc = computedLrc xor 0x03
// Compare incoming LRC to the computed one
return lrc == null || computedLrc == lrc
}
The same thing has to be done during the message building when the cash register wants to send a message to the POS. This is a code snippet (Kotlin) which builds the data to be sent with parity and lrc computation:
fun buildChunksForEmit(dataToSend: String): String {
val commandStringBuilder = StringBuilder()
var computedLrc: Byte = 0x0
commandStringBuilder.append("02") // STX
for (char in dataToSend) {
val binaryRepresentation = char.code.toByte()
val oneBitsCount = binaryRepresentation.countOneBits()
val isOdd = oneBitsCount % 2 != 0
val binaryRepresentationWithParity =
if (isParityCheckEnabled && isOdd) it.raiseBit(8)
else it
computedLrc = computedLrc xor binaryRepresentationWithParity
commandStringBuilder.append(
binaryRepresentationWithParity
.toUByte()
.toInt()
.toString(16)
)
}
// padStart ensure LRC hex representation length will always be 2
val lrcAsString =
(computedLrc xor 0x03)
.toUByte()
.toInt()
.toString(16)
.padStart(2, '0')
val etxAndLrc = "03$lrcAsString" // ETX + LRC
commandStringBuilder.append(etxAndLrc)
return commandStringBuilder.toString()
}
If you want to use the code snippet above, please be careful, it builds a hex representation of the string, so each byte will become two chars. It is helpful for debugging/logs but make sure when you are sending this hex string that you convert each two chars couples into one byte beforehand.
“02” => 0x00 + 0x02 | |
“02” => 0x02 |
© PayXpert Services Ltd, 2025