Tatsuno Dispensing Pump Protocol

Yes, It is using Kermit crc16, Thank to Acuy for the tip. the calculation base from 6 digits from the source 00XXXX, use Kermit to get the result which is 2 Byte. XX, If they are 3 byte in the result, truncate the highest byte. (most likely a 1), after that, form the code send back to dispenser is 00 + XX + ASCII 31 20 20 20 (this 4 Bytes is not in the book, it is "1" + 3 Space Characters ), 00 is the command.

Example:
04405105 024051 3030 32463432 0360 1031 04 -> 3030 32463432
04404105 1030 024041 3030 4545 31202020 0313 1031 04 -> 3030 4545 31202020
04405105 024051 36303130 0315 1031 04 -> 36 30 31 30 =POWER ON = ASCII 60 10
I believe i've already correctly caculated that 2 bytes code and send back to pump, but still can't get expected msg from pump to show itself in `Normal` state, could you help to check?
this is the message flow i've got when the console get started and initially comm with the pump:

console send - 0x02 43 41 31 35 03 05 - actively ask for pump state
console send - 0x04 43 51 05 - Enq
pump send - 0x02 43 51 30 30 32 39 32 37 03 1F - ask for decode with content: 0x30 30 32 39 32 37
console send - 0x02 43 41 30 30 44 37 31 20 20 20 03 63 - decoded to 0x44 37, and appended a fixed 0x31 20 20 20 as you suggested.
console send - 0x04 - Eot

but, the above flow is looping, the pump is keep asking for decode and never entered into `Normal` state.
could you help? thank you so much!!!
 
This is the sequence when the Console request the totalizer. The Pump address is 40.
View attachment 5032
So, can you show us your communication logs, so we can figure out what went wrong?
Hello myckchin & Naveed Rasheed
I'm having trouble sending a valid command to the Tatsuno pump — every time I try, I receive a 0x15 (NAK) response.

I’ve followed the protocol as described in the manual and I'm successfully receiving data like the following:02 41 51 30 30 37 43 30 32 03 65 I understand that this message includes the data field 30 30 37 43 30 32, which decodes to "007C02" in ASCII. So, reception seems to work .
However, I cannot send any commands, and for now I’m just trying to read the totalizer. Based on my understanding from the documentation, the request should involve a data field with the value 20 (hex).
I’ve attached the relevant documentation I'm working from. Could anyone please confirm what a valid "read totalizer" request packet should look like (with correct CRC/BCC and formatting)?


View attachment 5023
View attachment 5024
I have the complete communication logs, from polling to the pump status showing as 'standby online' at the POS. However, I am unsure how to convert this into a program for a dispenser simulation that can be integrated into a microcontroller. If you are interested and willing to assist me, please contact me via email. Thank you.

[email protected]
 
I believe i've already correctly caculated that 2 bytes code and send back to pump, but still can't get expected msg from pump to show itself in `Normal` state, could you help to check?
this is the message flow i've got when the console get started and initially comm with the pump:

console send - 0x02 43 41 31 35 03 05 - actively ask for pump state
console send - 0x04 43 51 05 - Enq
pump send - 0x02 43 51 30 30 32 39 32 37 03 1F - ask for decode with content: 0x30 30 32 39 32 37
console send - 0x02 43 41 30 30 44 37 31 20 20 20 03 63 - decoded to 0x44 37, and appended a fixed 0x31 20 20 20 as you suggested.
console send - 0x04 - Eot

but, the above flow is looping, the pump is keep asking for decode and never entered into `Normal` state.
could you help? thank you so much!!!
Hi Please share the protocol with me
My email [email protected]
 
Tatsuno NEO SUNNY SS-LAN Power-ON Authorization Sequence: Pump 5 (ID 44)

1. Log Analysis & Data Extraction

The authorization sequence requires extracting the challenge payload from the polling response, calculating the checksum, and transmitting the solved payload back using the selecting address.

  • Target Pump (SA): 44 (Pump 5)
  • Polling Unit Address (UA-POL): 51
  • Selecting Unit Address (UA-SEL): 41


2. Step-by-Step Sequence Trace

Step 2.1: Polling (Requesting the Power-ON Challenge)
The console initiates a poll to retrieve the locked state challenge.
  • [TX] 04 44 51 05 (EOT | SA: 44 | UA: 51 | ENQ)
  • [RX] 02 44 51 30 30 34 37 37 30 03 12 (STX | SA | UA | TEXT_ID: 30 30 | DATA: 34 37 37 30 | ETX | BCC)
  • [TX] 10 31 (ACK1)
  • [RX] 04 (EOT)

Step 2.2: Payload Processing & Cryptographic Calculation

This step details the manual process of transforming the raw payload into the authorized response.

1. CRC-16 Calculation (Kermit)
Calculates the 16-bit checksum of the raw payload.
  • Parameters:
    • Polynomial: 0x1021 (x^16 + x^12 + x^5 + 1)
    • Initialization: 0x0000
    • Reflect In: True
    • Reflect Out: True
    • XOR Out: 0x0000
  • Input: 30 30 34 37 37 30 (TEXT_ID + DATA)
  • Output: 0xED9D

2. Split High and Low Bytes
Separates the 16-bit CRC result into two 8-bit components.
  • Input: 0xED9D
  • Output:
    • High Byte = 0xED
    • Low Byte = 0x9D

3. Byte Addition
Sums the High and Low bytes together using standard integer addition.
  • Input: 0xED and 0x9D
  • Output: 0xED + 0x9D = 0x18A

4. 8-Bit Masking (Modulo 256)
Applies a bitwise AND mask (& 0xFF) to discard the overflow bit and compress the sum back into a single byte.
  • Input: 0x18A
  • Output: 0x18A & 0xFF = 0x8A

5. ASCII Hex Encoding
Splits the final 8-bit hex value into individual characters and converts them to their ASCII hex equivalents for transmission.
  • Input: 0x8A
  • Output: Character '8' -> 0x38 | Character 'A' -> 0x41 = 38 41

Step 2.3: Selecting (Transmitting the Authorization Response)
The console switches to the selecting address (41) to transmit the solved ASCII hex string.
  • [TX] 04 44 41 05 (EOT | SA: 44 | UA: 41 | ENQ)
  • [RX] 10 30 (ACK0)
  • Frame Assembly: 02 + 44 + 41 + 30 30 + 38 41 + 03.
  • BCC Calculation: XOR of all bytes from SA (44) to ETX (03) yields the Block Check Character 7F.
  • [TX] 02 44 41 30 30 38 41 03 7F
  • [RX] 10 31 (ACK1)

Step 2.4: Verification (Status Polling)
The console resumes standard polling. The pump responds with its operational condition payload 36 30 31 30. This confirms the authorization was successful and the pump is now unlocked, as defined by the protocol's condition transmission specifications:

  • Payload Breakdown (36 30 31 30 -> ASCII "6010"):
    • 36 30 (ASCII "60"): TEXT discrimination code indicating "Transmission of the dispensing pump condition".
    • 31 (ASCII "1"): Condition code 1, meaning "Power ON (Dispensing pump is controllable)".
    • 30 (ASCII "0"): Contents code, specified as "All the time '0'".
  • [TX] 04 44 51 05
  • [RX] 02 44 51 36 30 31 30 03 11


3. Python Implementation for Pump 5 Verification

Python:
import binascii

def calculate_kermit_crc(data: bytes) -> int:
    """
    Computes the Kermit CRC-16 (CCITT polynomial 0x1021).
    Note: Kermit processes bits LSB first, requiring the reversed polynomial 0x8408.
    """
    crc = 0
    for byte in data:
        crc ^= byte
        for _ in range(8):
            if crc & 1:
                crc = (crc >> 1) ^ 0x8408
            else:
                crc >>= 1
    return crc

def calculate_xor_bcc(frame_hex: str) -> str:
    """Computes the Block Check Character (XOR checksum) from SA to ETX."""
    frame_bytes = bytes.fromhex(frame_hex.replace(" ", ""))
    bcc = 0
    for byte in frame_bytes:
        bcc ^= byte
    return f"{bcc:02X}"

if __name__ == "__main__":
    # 1. Payload Extraction
    # Target payload: TEXT_ID (30 30) + DATA (34 37 37 30) from the polling RX frame
    rx_payload_hex = "303034373730"
    payload_bytes = bytes.fromhex(rx_payload_hex)

    # 2. Cryptographic Checksum Calculation
    crc_16 = calculate_kermit_crc(payload_bytes)
    high_byte = (crc_16 >> 8) & 0xFF
    low_byte = crc_16 & 0xFF

    # 3. Byte Folding & 8-bit Overflow Masking
    # Sum the High and Low bytes, then constrain to 8 bits (Modulo 256)
    sum_val = (high_byte + low_byte) & 0xFF
    hex_sum = f"{sum_val:02X}"

    # 4. ASCII Hex Encoding
    # Convert the 8-bit hex string characters into their ASCII hex equivalents
    ascii_hex_data = hex_sum.encode('ascii').hex().upper() 

    # 5. Frame Assembly
    # SA=44 (Pump 5), UA=41 (Selecting), TEXT_ID=3030 (Standard sequence), ETX=03
    sa, ua_sel, text_id, etx = "44", "41", "3030", "03"

    # Calculate BCC over the frame body (excluding STX)
    frame_body = f"{sa}{ua_sel}{text_id}{ascii_hex_data}{etx}"
    bcc_hex = calculate_xor_bcc(frame_body)

    # Construct final transmission frame
    tx_frame = f"02 {sa} {ua_sel} {text_id[:2]} {text_id[2:]} {ascii_hex_data[:2]} {ascii_hex_data[2:]} {etx} {bcc_hex}"

    # Print the results to verify against logs
    print(f"Extracted Payload:   {rx_payload_hex}")
    print(f"Calculated TX Frame: {tx_frame}")

    # ==========================================
    # Example Output:
    # Extracted Payload:   303034373730
    # Calculated TX Frame: 02 44 41 30 30 38 41 03 7F
    # ==========================================

4. Full Tatsuno Communication Log

Communication Legend:
  • [TX] (Transmit): Console / Master node initiating requests or acknowledging data.
  • [RX] (Receive): Dispensing Pump / Slave node returning payloads or statuses.

Code:
[TX] - 04 44 51 05 
[RX] - 02 44 51 30 30 34 37 37 30 03 12 
[TX] - 10 31 
[RX] - 04 
[TX] - 04 44 41 05 
[RX] - 10 30 
[TX] - 02 44 41 30 30 38 41 03 7F 
[RX] - 10 31 
[TX] - 04 44 51 05 
[RX] - 02 44 51 36 30 31 30 03 11
 

Attachments

For those who prefer C language:

C:
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <ctype.h>

// Calculate Kermit CRC-16 (CCITT polynomial 0x1021) with reversed polynomial 0x8408
uint16_t calculate_kermit_crc(const uint8_t *data, size_t length) {
    uint16_t crc = 0;
    for (size_t i = 0; i < length; i++) {
        crc ^= data[i];
        for (int bit = 0; bit < 8; bit++) {
            if (crc & 1) {
                crc = (crc >> 1) ^ 0x8408;
            } else {
                crc >>= 1;
            }
        }
    }
    return crc;
}

// Convert hex character to its integer value
int hex_char_to_int(char c) {
    if ('0' <= c && c <= '9') return c - '0';
    if ('A' <= c && c <= 'F') return c - 'A' + 10;
    if ('a' <= c && c <= 'f') return c - 'a' + 10;
    return -1;
}

// Convert hex string (without spaces) to byte array
// Returns number of bytes converted, or -1 on error
int hexstr_to_bytes(const char *hexstr, uint8_t *out_bytes, size_t max_len) {
    size_t len = strlen(hexstr);
    if (len % 2 != 0) return -1;
    size_t bytes_len = len / 2;
    if (bytes_len > max_len) return -1;

    for (size_t i = 0; i < bytes_len; i++) {
        int hi = hex_char_to_int(hexstr[2*i]);
        int lo = hex_char_to_int(hexstr[2*i + 1]);
        if (hi < 0 || lo < 0) return -1;
        out_bytes[i] = (hi << 4) | lo;
    }
    return (int)bytes_len;
}

// Calculate XOR BCC checksum over hex string frame (without spaces)
uint8_t calculate_xor_bcc(const char *frame_hex) {
    uint8_t bcc = 0;
    uint8_t bytes[256];
    int len = hexstr_to_bytes(frame_hex, bytes, sizeof(bytes));
    if (len < 0) return 0; // error fallback

    for (int i = 0; i < len; i++) {
        bcc ^= bytes[i];
    }
    return bcc;
}

// Convert a byte to uppercase hex string (2 chars)
void byte_to_hex(uint8_t byte, char *out) {
    const char hex_digits[] = "0123456789ABCDEF";
    out[0] = hex_digits[(byte >> 4) & 0xF];
    out[1] = hex_digits[byte & 0xF];
    out[2] = '\0';
}

int main() {
    // 1. Payload Extraction
    // Target payload: TEXT_ID (30 30) + DATA (34 37 37 30) from the polling RX frame
    const char *rx_payload_hex = "303034373730";
    uint8_t payload_bytes[256];
    int payload_len = hexstr_to_bytes(rx_payload_hex, payload_bytes, sizeof(payload_bytes));
    if (payload_len < 0) {
        printf("Invalid payload hex string.\n");
        return 1;
    }

    // 2. Cryptographic Checksum Calculation
    uint16_t crc_16 = calculate_kermit_crc(payload_bytes, payload_len);
    uint8_t high_byte = (crc_16 >> 8) & 0xFF;
    uint8_t low_byte = crc_16 & 0xFF;

    // 3. Byte Folding & 8-bit Overflow Masking
    uint8_t sum_val = (high_byte + low_byte) & 0xFF;

    // 4. ASCII Hex Encoding
    char hex_sum[3];
    byte_to_hex(sum_val, hex_sum);

    // Convert hex_sum string to ASCII hex representation (each char to its hex code)
    // For example, '8' -> 0x38, 'A' -> 0x41
    // Then convert those bytes to uppercase hex string again
    // So "8A" -> bytes {0x38, 0x41} -> hex string "3841"
    char ascii_hex_data[5]; // 4 chars + null
    for (int i = 0; i < 2; i++) {
        uint8_t c = (uint8_t)hex_sum[i];
        byte_to_hex(c, &ascii_hex_data[i*2]);
    }
    ascii_hex_data[4] = '\0';

    // 5. Frame Assembly
    const char *sa = "44";       // Pump 5
    const char *ua_sel = "41";   // Selecting
    const char *text_id = "3030"; // Standard sequence
    const char *etx = "03";

    // Frame body for BCC calculation (excluding STX)
    // frame_body = sa + ua_sel + text_id + ascii_hex_data + etx
    char frame_body[256];
    snprintf(frame_body, sizeof(frame_body), "%s%s%s%s%s", sa, ua_sel, text_id, ascii_hex_data, etx);

    uint8_t bcc = calculate_xor_bcc(frame_body);

    // Construct final transmission frame:
    // "02 44 41 30 30 38 41 03 7F"
    // Split text_id and ascii_hex_data into two-byte parts for spacing
    printf("Extracted Payload:   %s\n", rx_payload_hex);
    printf("Calculated TX Frame: 02 %s %s %c%c %c%c %c%c %s %02X\n",
           sa,
           ua_sel,
           text_id[0], text_id[1],
           text_id[2], text_id[3],
           ascii_hex_data[0], ascii_hex_data[1],
           etx,
           bcc);

    return 0;
}
 
Top