HW-VX6330K via RS232 atau TCP IP dengan Komputer

HW-VX6330K adalah salah satu UHF reader middle range yang dapat melakukan read maupun write terhadap tag. Komunikasi dengan reader dapat menggunakan bahasa pemrograman apapun asalkan komunikasi sudah sesuai dengan protokol yang ada.

Pada artikel ini kami mencoba komunikasikan reader menggunakan protokol (tidak menggunakan SDK), koneksi via Serial atau TCP/IP.

Persiapan

Sebelum masuk ke bagian kode, perlu disiapkan beberapa hal berikut:

Konverter dapat dibeli di link berikut USB to RS232 CH340 Serial.

Hubungkan Reader ke Komputer

RS232

Proses menghubungkan HW-VX6330K dengan komputer cukup sambungkan dengan menggunakan kabel konverter RS232 to USB Serial.

Konverter RS-232 to USB Serial

Setelah terhubung pastikan sudah terdeteksi adanya serial port di Device Manager.

Port Serial Terdeteksi

TCP/IP

Untuk menghubungkan HW-VX6330K dengan komputer menggunakan kabel ethernet, kita perlu samakan dulu IP Network komputer terhadap reader.

Hubungkan port ethernet

Default IP Address reader adalah 192.168.1.192/24 dengan port 6000.

Bagi pengguna Windows, bisa di atur di Control Panel > Network and Sharing Center > Pilih adapter lalu sesuaikan IP Network-nya.

Contoh: IP reader 192.168.1.192/24, IP komputer: 192.168.1.1/24.

Setting reader

Sebelum memulai, pastikan untuk mengubah Work Mode sesuai kebutuhan. Anda dapat mengikuti panduan pada Cara Mengubah Work Mode. Jika menjalankan perintah read/write memory yang mengirimkan perintah command ke reader, disarankan untuk mengatur mode ke Answer Mode agar response yang diterima sesuai.

Kode dan Penjelasan

Penjelasan blok/tiap byte command/response dapat dilihat di dokumentasi protokol.

Kode dibawah ini dipisah menjadi 5 file:

  • transport: Class ini yang mengatur pengiriman dan penerimaan byte dari/ke reader.
    • Pada contoh, kita akan coba buat 1 base class (dasar), 2 derived class (turunan) yakni untuk Serial dan TCP/IP.
  • command: Class ini akan mempermudah proses parsing data byte yang akan dikirim ke reader. List command yang dapat digunakan dapat dilihat di dokumentasi protokol, pada bagian 4.1 EPC C1 G2(ISO18000-6C)COMMAND.
    • Pada class ini terdapat kode untuk menghitung algoritma checksum, algoritma checksum yang digunakan pada reader HW-VX6330K adalah CRC-16/MCRF4XX.
  • response: Proses parsing penerimaan byte dari reader.
  • reader: Menggabungkan 3 class diatas.
    • Pada class Reader terdapat fungsi:
    1. inventory_answer_mode(): Inventory (answer mode)
    2. inventory_active_mode(): Inventory (active mode)
    3. read_memory(...): Mengambil data pada memory bank tertentu
    4. write_memory(...): Menulis data pada memory bank tertentu
    5. set_power(...): Mengatur jarak baca reader terhadap tag (semakin besar semakin jauh)
    • Saat eksekusi fungsi read_memory(...), write_memory(...), dan set_power(...) disarankan atur inventory ke Answer Mode terlebih dahulu (setting reader) agar tidak terjadi tabrakan data (data response dari reader terhalang dengan response active mode)
  • main: File yang akan dijalankan.

Kode

🐍 Python

  • Python versi 3.11 atau ke atas
  • Library pyserial, digunakan untuk komunikasi Serial (RS232)
    • pip install pyserial
1. Transport (transport.py)
from abc import ABC, abstractmethod
from socket import socket, AF_INET, SOCK_STREAM
from typing import TypeVar
import serial
 
T = TypeVar('T', bound='Parent')
 
 
class Transport(ABC):
    @abstractmethod
    def read_bytes(self, length: int) -> bytes:
        raise NotImplementedError
 
    @abstractmethod
    def write_bytes(self, buffer: bytes) -> None:
        raise NotImplementedError
 
    def read_frame(self) -> bytes | None:
        length_bytes = self.read_bytes(1)
        if not length_bytes:
            return
        frame_length = ord(chr(length_bytes[0]))
        data = length_bytes + self.read_bytes(frame_length)
        return bytearray(data)
 
    @abstractmethod
    def close(self) -> None:
        raise NotImplementedError
 
 
class TcpTransport(Transport):
    def __init__(self, ip_address: str, port: int, timeout: int = 1) -> None:
        self.socket = socket(AF_INET, SOCK_STREAM)
        self.socket.settimeout(timeout)
        self.socket.connect((ip_address, port))
 
    def read_bytes(self, length: int) -> bytes:
        return self.socket.recv(length)
 
    def write_bytes(self, buffer: bytes) -> None:
        self.socket.sendall(buffer)
 
    def close(self) -> None:
        self.socket.close()
 
 
class SerialTransport(Transport):
    def __init__(self, serial_port: str, baud_rate: int, timeout: int = 1) -> None:
        self.serial = serial.Serial(serial_port, baud_rate,
                                    timeout=timeout, write_timeout=timeout)
 
    def read_bytes(self, length: int) -> bytes:
        return self.serial.read(length)
 
    def write_bytes(self, buffer: bytes) -> None:
        self.serial.write(buffer)
 
    def close(self) -> None:
        self.serial.close()
2. Command (command.py)
from utils import calculate_checksum
 
CMD_INVENTORY: int = 0x01
CMD_READ_MEMORY: int = 0x02
CMD_WRITE_MEMORY: int = 0x03
CMD_SET_LOCK: int = 0x06
CMD_SET_READER_POWER: int = 0x2F
CMD_GET_WORK_MODE: int = 0x36
CMD_SET_WORK_MODE: int = 0x35
 
 
class Command:
    def __init__(self, command: int, reader_address: int = 0xFF,
                 data: bytes | int | None = None):
        self.command = command
        self.reader_address = reader_address
        self.data = data
        if isinstance(data, int):
            self.data = bytearray([data])
        if data is None:
            self.data = bytearray()
        self.frame_length = 4 + len(self.data)
        self.base_data = bytearray([self.frame_length, self.reader_address, self.command])
        self.base_data.extend(self.data)
 
    def serialize(self) -> bytes:
        serialize = self.base_data
        checksum = calculate_checksum(serialize)
        serialize.extend(checksum)
        return serialize
3. Response (response.py)
from dataclasses import dataclass
from enum import Enum
 
from utils import calculate_checksum, hex_readable
 
 
class Response:
    def __init__(self, response_bytes: bytes):
        if len(response_bytes) < 6:
            raise ValueError("Response data is too short to be valid.")
 
        self.response_bytes = response_bytes
        self.length = response_bytes[0]
 
        if len(response_bytes) < self.length:
            raise ValueError("Response length mismatch.")
 
        self.reader_address = response_bytes[1]
        self.command = response_bytes[2]
        self.status = response_bytes[3]  # Check 5. LIST OF COMMAND EXECUTION RESULT STATUS
        self.data = response_bytes[4: self.length - 1]
        self.checksum = response_bytes[self.length - 1: self.length + 1]
 
        # Verify checksum
        data = bytearray(self.response_bytes[0:4])
        data.extend(self.data)
        crc_msb, crc_lsb = calculate_checksum(data)
        assert self.checksum[0] == crc_msb and self.checksum[1] == crc_lsb
 
    def __str__(self) -> str:
        lines = [
            ">>> START RESPONSE ================================",
            f"RESPONSE       >> {hex_readable(self.response_bytes)}",
            f"READER ADDRESS >> {hex_readable(self.reader_address)}",
            f"COMMAND        >> {hex_readable(self.command)}",
            f"STATUS         >> {hex_readable(self.status)}",
        ]
 
        if self.data:
            lines.append(f"DATA           >> {hex_readable(self.data)}")
 
        lines.append(f"CHECKSUM       >> {hex_readable(self.checksum)}")
        lines.append(">>> END RESPONSE   ================================")
 
        return "\n".join(lines)
 
 
 
class InventoryWorkMode(Enum):
    ANSWER_MODE: int = 0
    ACTIVE_MODE: int = 1
    TRIGGER_MODE_LOW: int = 2
    TRIGGER_MODE_HIGH: int = 3
 
 
class OutputInterface(Enum):
    WIEGAND: int = 0
    RS232_485: int = 1
    SYRIS485: int = 2
 
 
class Protocol(Enum):
    PROTOCOL_18000_6C: int = 0
    PROTOCOL_18000_6B: int = 1
 
 
class AddressType(Enum):
    WORD: int = 0
    BYTE: int = 1
 
 
class WiegandOutputAddressing(Enum):
    WORD: int = 0
    BYTE: int = 1
 
 
class WiegandFormat(Enum):
    WIEGAND_26BITS: int = 0
    WIEGAND_34BITS: int = 1
 
 
class WiegandBitOrder(Enum):
    HIGH_BIT_FIRST: int = 0
    LOW_BIT_FIRST: int = 1
 
 
class WiegandMode:
    def __init__(self, value: int):
        self.value: int = value
 
        self._wiegand_format: WiegandFormat = WiegandFormat(value & 0b1)
        self._bit_order: WiegandBitOrder = WiegandBitOrder((value & 0b10) >> 1)
 
    def __str__(self) -> str:
        return (f"Wiegand Mode: {self.wiegand_format.name.replace('_', ' ')}, "
                f"Bit Order: {self.bit_order.name.replace('_', ' ')}")
 
    @property
    def wiegand_format(self) -> WiegandFormat:
        return self._wiegand_format
 
    @wiegand_format.setter
    def wiegand_format(self, fmt: WiegandFormat):
        self._wiegand_format = fmt
        self.update_value()
 
    @property
    def bit_order(self) -> WiegandBitOrder:
        return self._bit_order
 
    @bit_order.setter
    def bit_order(self, order: WiegandBitOrder):
        self._bit_order = order
        self.update_value()
 
    def update_value(self):
        self.value = (self._wiegand_format.value & 0b1) | ((self._bit_order.value & 0b1) << 1)
 
 
class WorkModeState:
    def __init__(self, value: int):
        self.value: int = value
 
        self.protocol: Protocol = Protocol(value & 0b1)
        self.output_interface: OutputInterface = OutputInterface((value & 0b10) >> 1)
        self.beep: bool = not bool(value & 0b100)
        self.address_type: AddressType = AddressType(value & 0b1000)
        self.rs485_enable: bool = bool(value & 0b10000)
 
    def __str__(self) -> str:
        return (f"Protocol: {self.protocol.name.replace('_', '-')} "
                f"| Output Mode: {self.output_interface.name.replace('_', '/')} "
                f"| Address Type: {self.address_type} | RS485: {'Enabled' if self.rs485_enable else 'Disabled'} "
                f"| Beep/Buzzer: {'Enabled' if self.beep else 'Disabled'}")
 
    def to_int(self) -> int:
        value = 0
        value |= self.protocol.value  # Bit 0
        value |= (self.output_interface.value << 1)  # Bit 1
        value |= (0 if self.beep else 0b100)  # Bit 2
        value |= (self.address_type.value << 3)  # Bit 3
        value |= (self.rs485_enable << 4)  # Bit 4
        return value
 
 
class InventoryMemoryBank(Enum):
    PASSWORD: int = 0
    EPC: int = 1
    TID: int = 2
    USER: int = 3
    INVENTORY_MULTIPLE: int = 4
    INVENTORY_SINGLE: int = 5
    EAS_ALARM: int = 6
 
 
class WorkMode:
    def __init__(self, response_bytes: bytes):
        self.wiegand_mode = WiegandMode(response_bytes[0])
        self.wiegand_interval = response_bytes[1]
        self.wiegand_pulse_width = response_bytes[2]
        self.wiegand_pulse_interval = response_bytes[3]
        self.inventory_work_mode = InventoryWorkMode(response_bytes[4])
        self.work_mode_state = WorkModeState(response_bytes[5])
        self.memory_bank = InventoryMemoryBank(response_bytes[6])
        self.first_address = response_bytes[7]
        self.word_number = response_bytes[8]
        self.single_tag_time = response_bytes[9]
        self.accuracy = response_bytes[10]
        self.offset_time = response_bytes[11]
 
    def __str__(self) -> str:
        return "\n".join([
            f"Wiegand Mode: {self.wiegand_mode}",
            f"Wiegand Interval: {self.wiegand_interval}",
            f"Wiegand Pulse Width: {self.wiegand_pulse_width}",
            f"Wiegand Pulse Interval: {self.wiegand_pulse_interval}",
            f"Inventory Work Mode: {self.inventory_work_mode.name.replace('_', ' ')}",
            f"Work Mode State: {self.work_mode_state}",
            f"Memory Bank: {self.memory_bank.name.replace('_', ' ')}",
            f"First Address: {self.first_address}",
            f"Word Number: {self.word_number}",
            f"Single Tag Time: {self.single_tag_time}",
            f"Accuracy: {self.accuracy}",
            f"Offset Time: {self.offset_time}"
        ])
 
    def to_bytes(self) -> bytes:
        return bytes([
            self.inventory_work_mode.value,
            self.work_mode_state.to_int(),
            self.memory_bank.value,
            self.first_address,
            self.word_number,
            self.single_tag_time,
        ])
4. Reader (reader.py)
from typing import Iterator
from transport import Transport
from command import *
from response import *
 
 
class Reader:
    def __init__(self, transport: Transport) -> None:
        self.transport = transport
 
    def close(self) -> None:
        self.transport.close()
 
    def __send_request(self, command: Command) -> None:
        self.transport.write_bytes(command.serialize())
 
    def __get_response(self) -> bytes:
        return self.transport.read_frame()
 
    def inventory_answer_mode(self,
                              start_address_tid: int | None = None,
                              len_tid: int | None = None,
                              ) -> Iterator[bytes]:  # 8.2.1 Inventory (Answer Mode)
        if start_address_tid is not None and len_tid is not None:
            command: Command = Command(CMD_INVENTORY, data=[start_address_tid, len_tid])
        else:
            command: Command = Command(CMD_INVENTORY)
        self.__send_request(command)
 
        response: Response = Response(self.__get_response())
        data: bytes = response.data
 
        if not data:
            return iter(())
 
        tag_count: int = data[0]
 
        n: int = 0
        pointer: int = 1
        while n < tag_count:
            tag_len = int(data[pointer])
            tag_data_start = pointer + 1
            tag_main_start = tag_data_start
            tag_main_end = tag_main_start + tag_len
            next_tag_start = tag_main_end
            tag = data[tag_data_start:tag_main_start] \
                  + data[tag_main_start:tag_main_end] + data[tag_main_end:next_tag_start]
            yield tag
            pointer = next_tag_start
            n += 1
 
    def inventory_active_mode(self) -> Iterator[Response]:
        while True:
            try:
                raw_response: bytes | None = self.__get_response()
            except TimeoutError:
                continue
            if raw_response is None:
                continue
            response: Response = Response(raw_response)
            yield response
 
    def read_memory(self, epc: bytes, memory_bank: int, start_address: int, length: int,
                    access_password: bytes = bytes(4)) -> Response:  # 8.2.2 Read Data
        request_data = bytearray()
        request_data.extend(bytearray([int(len(epc) / 2)]))  # EPC Length in word
        request_data.extend(epc)
        request_data.extend(bytearray([memory_bank, start_address, length]))
        request_data.extend(access_password)
        command: Command = Command(CMD_READ_MEMORY, data=request_data)
        self.__send_request(command)
 
        return Response(self.__get_response())
 
    def write_memory(self, epc: bytes, memory_bank: int, start_address: int,
                     data_to_write: bytes,
                     access_password: bytes = bytes(4)) -> Response:  # 8.2.4 Write Data
        request_data: bytearray = bytearray()
        request_data.extend(bytearray([int(len(data_to_write) / 2)]))  # Data length in word
        request_data.extend(bytearray([int(len(epc) / 2)]))  # EPC Length in word
        request_data.extend(epc)
        request_data.extend(bytearray([memory_bank, start_address]))
        request_data.extend(data_to_write)
        request_data.extend(access_password)
        command: Command = Command(CMD_WRITE_MEMORY, data=request_data)
        self.__send_request(command)
 
        return Response(self.__get_response())
 
    def lock(self, epc: bytes, select: int, set_protect: int, access_password: bytes) -> Response:  # 8.2.6 Lock
        parameter: bytearray = bytearray([int(len(epc) / 2)]) + epc + \
                               bytearray([select, set_protect]) + access_password
 
        command: Command = Command(CMD_SET_LOCK, data=parameter)
        self.__send_request(command)
 
        return Response(self.__get_response())
 
    def set_power(self, power: int) -> Response:  # 8.4.6 Set Power
        assert 0 <= power <= 30
 
        command: Command = Command(CMD_SET_READER_POWER, data=bytearray([power]))
        self.__send_request(command)
 
        return Response(self.__get_response())
 
    def work_mode(self) -> WorkMode:  # 8.4.10 Get WorkMode
        command: Command = Command(CMD_GET_WORK_MODE)
        self.__send_request(command)
 
        return WorkMode(Response(self.__get_response()).data)
 
    def set_work_mode(self, work_mode: WorkMode) -> Response:  # 8.4.9 Set WorkMode
        command: Command = Command(CMD_SET_WORK_MODE, data=work_mode.to_bytes())
        self.__send_request(command)
 
        return Response(self.__get_response())
5. utils.py
def calculate_checksum(data: bytes) -> bytearray:
    value = 0xFFFF
    for d in data:
        value ^= d
        for _ in range(8):
            value = (value >> 1) ^ 0x8408 if value & 0x0001 else (value >> 1)
    crc_msb = value >> 0x08
    crc_lsb = value & 0xFF
    return bytearray([crc_lsb, crc_msb])
 
 
def hex_readable(data: bytes | int, bytes_separator: str = " ") -> str:
    if isinstance(data, int):
        return "{:02X}".format(data)
    return bytes_separator.join("{:02X}".format(x) for x in data)
6. main.py

Uncomment fungsi/kode yang ingin digunakan.

from typing import Iterator
 
from response import hex_readable, Response, WorkMode, InventoryWorkMode, InventoryMemoryBank
from transport import SerialTransport, TcpTransport
from reader import Reader
 
transport = SerialTransport('/dev/ttyUSB0', 57600)
# transport = TcpTransport('192.168.1.192', 6000)
reader = Reader(transport)
 
#########################################################
 
# 1. Inventory - Answer Mode
# tags: Iterator[bytes] = reader.inventory_answer_mode()
# for tag in tags:
#     print(f'Tag: {hex_readable(tag)}')
 
#########################################################
 
# 2. Inventory - Active Mode
try:
    responses: Iterator[Response] = reader.inventory_active_mode()
    for response in responses:
        # print(response)
        tag: bytes = response.data
        print(f'Tag: {hex_readable(tag)}')
except KeyboardInterrupt:  # Handle Ctrl + C
    pass
 
#########################################################
 
# 3. Read specific memory, read memory bank TID (0x02), please check 8.2.2 Read Data
# EPC: 12 34 56 78 90 12 34 56 AD EF 01 23
# epc: bytearray = bytearray([0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, 0xAD, 0xEF, 0x01, 0x23])
# response_read: Response = reader.read_memory(epc=epc, memory_bank=0x02, start_address=0x00, length=0x07)
# print(response_read)
 
#########################################################
 
# 4. Write specific memory, write memory bank EPC (0x01), please check 8.2.3 Write Data
# epc: bytearray = bytearray([0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, 0xAD, 0xEF, 0x01, 0x23])
# data_to_write: bytearray = bytearray([0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00, 0x99, 0x88])
# response_write: Response = reader.write_memory(epc=epc, memory_bank=0x01, start_address=0x02, data_to_write=data_to_write)
# print(response_write)
 
#########################################################
 
# 5. Lock memory bank, please check 8.2.6 Lock
# EPC: E2 00 A8 32 FE 55 83 91 CE 26 89 AB
# epc: bytearray = bytearray([0xE2, 0x00, 0xA8, 0x32, 0xFE, 0x55, 0x83, 0x91, 0xCE, 0x26, 0x89, 0xAB])
# # access_password: bytes = bytes(4)  # Mean 00 00 00 00 (default access password)
# response_lock: Response = reader.lock(epc=epc, select=0x02, set_protect=0x02, access_password=access_password)
# print(response_lock)
 
#########################################################
 
# 6. Set power, please check 8.4.6 Set Power
# power: int = 28
# response_set_power: Response = reader.set_power(power)
# print(response_set_power)
 
#########################################################
 
# 7. Get & set work mode, please check 8.4.9 Set WorkMode
# work_mode: WorkMode = reader.work_mode()
# print(work_mode)
# # Set buzzer On/Off only works when inventory work mode is set to ACTIVE_MODE
# work_mode.work_mode_state.beep = True
# work_mode.inventory_work_mode = InventoryWorkMode.ACTIVE_MODE
# response_set_work_mode: Response = reader.set_work_mode(work_mode)
# print(response_set_work_mode)
#
# # If you want to set back to ANSWER_MODE, you can use the following code
# work_mode.inventory_work_mode = InventoryWorkMode.ANSWER_MODE
# response_set_answer_mode: Response = reader.set_work_mode(work_mode)
# print(response_set_answer_mode)
 
reader.close()
Troubleshooting

Jika ditemukan error [WinError 10054] An existing connection was forcibly closed by the remote host (OS Windows) saat reader sedang idle menunggu response Active Mode, ini dikarenakan reader close koneksi setelah sekian menit tidak mengirimkan data. Atur Connection Timeout di settingan reader menjadi 0.

🦀 Rust

Dependencies yang digunakan:

[dependencies]  
thiserror = "1.0.63"  
serialport = "4.4.0"  
num-traits = "0.2.19"  
num-derive = "0.4.2"  
anyhow = "1.0.86"
1. Transport (transport.rs)
use std::io::Read;  
use std::io::prelude::*;  
use std::time::Duration;  
use anyhow::{Context, Error};  
use serialport::{ClearBuffer, SerialPort, available_ports};  
use std::net::TcpStream;  
use num_derive::FromPrimitive;  
use num_traits::FromPrimitive;  
  
#[non_exhaustive]  
#[derive(Debug, PartialEq, FromPrimitive, Clone)]  
pub enum BaudRate {  
    Bps9600 = 9600,  
    Bps57600 = 57600,  
    Bps115200 = 115200,  
}  
  
impl From<BaudRate> for u32 {  
    fn from(value: BaudRate) -> Self {  
        value as u32  
    }  
}  
  
#[allow(dead_code)]  
#[derive(Debug)]  
pub struct BaudRateError {  
    pub value: u32,  
}  
  
#[allow(dead_code)]  
impl BaudRate {  
    fn from_u32(value: u32) -> Result<BaudRate, BaudRateError> {  
        FromPrimitive::from_u32(value).ok_or(BaudRateError { value })  
    }  
}  
  
#[allow(dead_code)]  
pub trait Transport {  
    fn read(&mut self) -> Result<Vec<u8>, Error>;  
    fn write(&mut self, buffer: &Vec<u8>) -> Result<(), Error>;  
    fn clear_buffer(&mut self) -> Result<(), Error>;  
    fn close(&mut self) -> Result<(), Error>;  
}  
  
#[allow(dead_code)]  
#[non_exhaustive]  
pub struct SerialTransport<'serial> {  
    serial: Box<dyn SerialPort>,  
  
    pub port: &'serial str,  
    pub baud_rate: BaudRate,  
    pub timeout: u64  
}  
  
impl<'serial> Transport for SerialTransport<'serial> {  
    fn read(&mut self) -> Result<Vec<u8>, Error> {  
        // Get length  
        let mut buffer_length: [u8; 1] = [0u8; 1];  
        self.serial.read(buffer_length.as_mut_slice())  
            .with_context(|| "Can't read buffer length.".to_string())?;  
  
        // Get data by length  
        let mut data: Vec<u8> = vec![0u8; buffer_length[0] as usize];  
        self.serial.read(data.as_mut_slice())  
            .with_context(|| "Can't read buffer data.".to_string())?;  
  
        // Frame (length + data)  
        let mut frame: Vec<u8> = buffer_length.to_vec();  
        frame.extend(data);  
  
        Ok(frame)  
    }  
  
    fn write(&mut self, buffer: &Vec<u8>) -> Result<(), Error> {  
        Ok(  
            self.serial.write_all(buffer)  
                .with_context(|| "Can't write buffer.".to_string())?  
        )  
    }  
  
    fn clear_buffer(&mut self) -> Result<(), Error> {  
        Ok(  
            self.serial.clear(ClearBuffer::All)  
                .with_context(|| "Can't clear all buffer.".to_string())?  
        )  
    }  
  
    fn close(&mut self) -> Result<(), Error> {  
        Ok(())  
    }  
}  
  
impl<'serial> SerialTransport<'serial> {  
    pub fn new(port: &'serial str, baud_rate: BaudRate, timeout: Option<u64>) -> Self {  
        let baud_rate_clone: BaudRate = baud_rate.clone();  
        let timeout: u64 = timeout.unwrap_or(3);  
  
        let serial = serialport::new(port, baud_rate_clone.into())  
            .timeout(Duration::from_secs(timeout))  
            .open().with_context(|| format!("Can't open {}", port)).unwrap();  
  
        SerialTransport {  
            serial,  
            port,  
            baud_rate,  
            timeout,  
        }  
    }  
  
    pub fn scan() -> Vec<String> {  
        let mut ports: Vec<String> = vec![];  
        for port in available_ports().unwrap() {  
            // Windows  
            if port.port_name.contains("COM") {  
                ports.push(port.port_name);  
            }  
  
            // Linux  
            else if port.port_name.contains("/dev/ttyUSB") {  
                ports.push(port.port_name);  
            }  
        }  
        ports  
    }  
}  
  
#[allow(dead_code)]  
#[non_exhaustive]  
pub struct TcpIpTransport<'tcp> {  
    tcp_stream: TcpStream,  
  
    pub ip_address: &'tcp str,  
    pub port: u32,  
    pub timeout: u64  
}  
  
impl<'tcp> Transport for TcpIpTransport<'tcp> {  
    fn read(&mut self) -> Result<Vec<u8>, Error> {  
        // Get length  
        let mut buffer_length: [u8; 1] = [0u8; 1];  
        self.tcp_stream.read(buffer_length.as_mut_slice())  
            .with_context(|| "Can't read buffer length.".to_string())?;  
  
        // Get data by length  
        let mut data: Vec<u8> = vec![0u8; buffer_length[0] as usize];  
        self.tcp_stream.read(data.as_mut_slice())  
            .with_context(|| "Can't read buffer data.".to_string())?;  
  
        // Frame (length + data)  
        let mut frame: Vec<u8> = buffer_length.to_vec();  
        frame.extend(data);  
  
        Ok(frame)  
    }  
  
    fn write(&mut self, buffer: &Vec<u8>) -> Result<(), Error> {  
        Ok(  
            self.tcp_stream.write_all(buffer)  
                .with_context(|| "Can't write buffer.".to_string())?  
        )  
    }  
  
    fn clear_buffer(&mut self) -> Result<(), Error> {  
        todo!()  
    }  
  
    fn close(&mut self) -> Result<(), Error> {  
        Ok(())  
    }  
}  
  
impl<'tcp> TcpIpTransport<'tcp> {  
    pub fn new(ip_address: &'tcp str, port: u32, timeout: Option<u64>) -> Self {  
        let timeout: u64 = timeout.unwrap_or(3);  
  
        let ip_address_port: String = format!("{}:{}", ip_address, port);  
        let tcp_stream = TcpStream::connect(&ip_address_port)  
            .with_context(|| format!("Can't open {}", &ip_address_port)).unwrap();  
  
        // Set timeout  
        tcp_stream.set_read_timeout(Some(Duration::from_secs(timeout))).unwrap();  
        tcp_stream.set_write_timeout(Some(Duration::from_secs(timeout))).unwrap();  
  
        TcpIpTransport {  
            tcp_stream,  
            ip_address,  
            port,  
            timeout,  
        }  
    }  
}
2. Command (command.rs)
use std::fmt::{Display, Formatter, Result as FmtResult};  
use num_derive::FromPrimitive;  
use num_traits::{FromPrimitive, ToPrimitive};  
use std::convert::From;  
  
#[non_exhaustive]  
#[derive(Debug, Clone, PartialEq, FromPrimitive)]  
pub enum Command {  
    Inventory = 0x01,  
    ReadMemory = 0x02,  
    WriteMemory = 0x03,  
    ReaderInformation = 0x21,  
    SetPower = 0x2F,  
    SetLock = 0x06,  
}  
  
impl From<Command> for u8 {  
    fn from(value: Command) -> Self {  
        value as u8  
    }  
}  
  
#[derive(Debug)]  
pub struct CommandError {  
    pub value: u8,  
}  
  
impl Command {  
    pub fn from_u8(value: u8) -> Result<Command, CommandError> {  
        FromPrimitive::from_u8(value).ok_or(CommandError { value })  
    }  
}  
  
impl Display for Command {  
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {  
        match self {  
            Command::Inventory => write!(f, "Inventory"),  
            Command::ReadMemory => write!(f, "Read memory"),  
            Command::WriteMemory => write!(f, "Write memory"),  
            Command::ReaderInformation => write!(f, "Reader information"),  
            Command::SetPower => write!(f, "Set power"),  
            Command::SetLock => write!(f, "Set lock"),  
        }  
    }  
}  
  
#[allow(dead_code)]  
#[non_exhaustive]  
#[derive(Debug)]  
pub struct CommandRequest {  
    command: Command,  
    reader_address: u8,  
    data: Vec<u8>,  
  
    base_data: Vec<u8>,  
}  
  
impl CommandRequest {  
    pub fn new(command: Command,  
               reader_address: Option<u8>, data: Option<Vec<u8>>) -> CommandRequest {  
        let reader_address: u8 = reader_address.unwrap_or(0xFF);  
        let data: Vec<u8> = data.unwrap_or(Vec::new());  
  
        let frame_length: u8 = (4 + data.len()).to_u8().unwrap();  
        let command_value: u8 = command.clone().into();  
  
        let mut base_data: Vec<u8> = vec![frame_length, reader_address, command_value];  
        let data_base_data_clone = data.clone();  
        base_data.extend(data_base_data_clone);  
  
        CommandRequest {  
            command,  
            reader_address,  
            data,  
            base_data  
        }  
    }  
  
    pub fn serialize(&self) -> Vec<u8> {  
        let checksum: u16 = self.calculate_crc();  
        let mut frame: Vec<u8> = self.base_data.clone();  
  
        frame.push((checksum & 0xFF) as u8);  
        frame.push((checksum >> 8) as u8);  
  
        frame  
    }  
  
    fn calculate_crc(&self) -> u16 {  
        let mut value = 0xFFFF;  
        for &byte in self.base_data.iter() {  
            value ^= u16::from(byte);  
            for _ in 0..8 {  
                value = if value & 0x0001 != 0 {  
                    (value >> 1) ^ 0x8408  
                } else {  
                    value >> 1  
                };  
            }  
        }  
        value  
    }  
}
3. Response (response.rs)
use std::fmt::{Display, Formatter, Result as FmtResult};  
use num_derive::FromPrimitive;  
use num_traits::{FromPrimitive, ToPrimitive};  
use crate::command::Command;  
use crate::utils::hex_readable;  
  
#[non_exhaustive]  
#[derive(Debug, Clone, PartialEq, FromPrimitive)]  
pub enum Status {  
    Success = 0x00,  
  
    ReturnBeforeInvFinish = 0x01,  
    InvOverflow = 0x02,  
    MoreData = 0x03,  
    FlashFull = 0x04,  
  
    InvalidAccessPwd = 0x05,  
    InvalidKillPwd = 0x09,  
    KillPwdNoZero = 0x0A,  
    UnsupportedCommand = 0x0B,  
    AccessPwdNoZero = 0x0C,  
    TagProtected = 0x0D,  
    TagUnprotected = 0x0E,  
  
    WriteError6B = 0x10,  
    LockError6B = 0x11,  
    TagProtected6B = 0x12,  
  
    SaveError = 0x13,  
    PowerLocked = 0x14,  
  
    ReturnBeforeInvFinish6B = 0x15,  
    InvOverflow6B = 0x16,  
    MoreData6B = 0x17,  
    FlashFull6B = 0x18,  
    EasAlarmError = 0x19,  
  
    CommandError = 0xF9,  
    WeakSignal = 0xFA,  
    NoTagFound = 0xFB,  
    TagError = 0xFC,  
    InvalidCommandLength = 0xFD,  
    CommandNotFound = 0xFE,  
    ParameterError = 0xFF,  
}  
  
impl From<Status> for u8 {  
    fn from(value: Status) -> Self {  
        value as u8  
    }  
}  
  
impl Display for Status {  
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {  
        match self {  
            Status::Success => write!(f, "Success"),  
            Status::ReturnBeforeInvFinish => write!(f, "Return before inventory finished"),  
            Status::InvOverflow => write!(f, "Inventory scan time overflow"),  
            Status::MoreData => write!(f, "More data"),  
            Status::FlashFull => write!(f, "Reader module flash is full"),  
  
            Status::InvalidAccessPwd => write!(f, "Invalid access password"),  
            Status::InvalidKillPwd => write!(f, "Invalid kill password"),  
            Status::KillPwdNoZero => write!(f, "Kill password error can't be zero"),  
            Status::UnsupportedCommand => write!(f, "Unsupported command"),  
            Status::AccessPwdNoZero => write!(f, "Access password error can't be zero"),  
            Status::TagProtected => write!(f, "Tag protected"),  
            Status::TagUnprotected => write!(f, "Tag unprotected"),  
  
            Status::WriteError6B => write!(f, "Write error, some bytes locked for 6B tag"),  
            Status::LockError6B => write!(f, "Lock error, can't lock for 6B tag"),  
            Status::TagProtected6B => write!(f, "Tag protected for 6B tag"),  
  
            Status::SaveError => write!(f, "Save fail, the parameter save is error"),  
            Status::PowerLocked => write!(f, "Power locked"),  
  
            Status::ReturnBeforeInvFinish6B => write!(f, "Return before inventory \  
            finished for 6B tag"),  
            Status::InvOverflow6B => write!(f, "Inventory scan time overflow for 6B tag"),  
            Status::MoreData6B => write!(f, "More data for 6B tag"),  
            Status::FlashFull6B => write!(f, "Reader module flash is full for 6B tag"),  
            Status::EasAlarmError => write!(f, "EAS Alarm error / unsupported command \  
            / invalid access password"),  
  
            Status::CommandError => write!(f, "Command error"),  
            Status::WeakSignal => write!(f, "Weak signal, bring tag closer"),  
            Status::NoTagFound => write!(f, "No tag found"),  
            Status::TagError => write!(f, "Tag error"),  
            Status::InvalidCommandLength => write!(f, "Invalid command length"),  
            Status::CommandNotFound => write!(f, "Command not found"),  
            Status::ParameterError => write!(f, "Parameter error"),  
  
            // _ => write!(f, "Error not found")  
        }  
    }  
}  
  
#[allow(dead_code)]  
#[derive(Debug)]  
pub struct StatusError {  
    pub value: u8,  
}  
  
#[allow(dead_code)]  
impl StatusError {  
    pub fn from_u8(value: u8) -> Result<Status, StatusError> {  
        FromPrimitive::from_u8(value).ok_or(StatusError { value })  
    }  
}  
  
#[non_exhaustive]  
#[derive(Debug)]  
pub struct Response {  
    pub length: u8,  
    pub reader_address: u8,  
    pub command: Command,  
    pub status: Status,  
    pub data: Vec<u8>,  
    pub checksum: Vec<u8>,  
}  
  
impl Response {  
    // TODO: Check CommandError  
  
    pub fn new(response: &Vec<u8>) -> Response {  
        Response {  
            length: response[0],  
            reader_address: response[1],  
            command: Command::from_u8(response[2]).unwrap(),  
            status: Status::from_u8(response[3]).unwrap(),  
            data: response[4..response.len() - 2].to_owned(),  
            checksum: response[response.len() - 2..].to_owned(),  
        }  
    }  
}  
  
impl Display for Response {  
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {  
        write!(f,  
               "Response {{  
               length: {},  
               reader_address: {},  
               command: {},  
               status: {},  
               data: {},  
               checksum: {}  
               }}",  
               self.length,  
               hex_readable(&[self.reader_address]),  
               self.command,  
               self.status,  
               hex_readable(&self.data),  
               hex_readable(&self.checksum)  
        )  
    }  
}  
  
#[non_exhaustive]  
pub struct ResponseInventory<'rsp> {  
    pub response: &'rsp Response,  
    pub tags: Vec<&'rsp [u8]>,  
}  
  
impl<'rsp> ResponseInventory<'rsp> {  
    pub fn new(response: &'rsp Response) -> ResponseInventory {  
        let mut tags: Vec<&[u8]> = Vec::new();  
  
        if response.data.len() != 0 {  
            let tag_count = response.data[0];  
            let mut pointer: usize = 1;  
            for _ in 0..tag_count {  
                let tag_length: usize = response.data[pointer].to_usize().unwrap();  
                let tag_data_start: usize = pointer + 1;  
                let tag_main_start: usize = tag_data_start;  
                let tag_main_end: usize = tag_main_start + tag_length;  
                let next_tag_start: usize = tag_main_end;  
                let tag: &[u8] = &response.data[tag_main_start..tag_main_end];  
                pointer = next_tag_start;  
                tags.push(tag);  
            }  
        }  
  
        ResponseInventory {  
            response,  
            tags  
        }  
    }  
}
4. Reader (reader.rs)
use num_traits::{ToPrimitive, FromPrimitive};  
use num_derive::FromPrimitive;  
use thiserror::Error;  
use crate::command::{Command, CommandError, CommandRequest};  
use crate::response::{Response, ResponseInventory};  
use crate::transport::Transport;  
  
#[non_exhaustive]  
#[derive(Debug, Clone, PartialEq, FromPrimitive)]  
pub enum MemoryBank {  
    Password = 0x00,  
    EPC = 0x01,  
    TID = 0x02,  
    User = 0x03,  
}  
  
impl From<MemoryBank> for u8 {  
    fn from(value: MemoryBank) -> Self {  
        value as u8  
    }  
}  
  
#[derive(Debug)]  
pub struct MemoryBankError {  
    pub value: u8,  
}  
  
impl MemoryBank {  
    pub fn from_u8(value: u8) -> Result<MemoryBank, MemoryBankError> {  
        FromPrimitive::from_u8(value).ok_or(MemoryBankError { value })  
    }  
}  
  
#[allow(dead_code)]  
#[non_exhaustive]  
#[derive(Debug, Error)]  
pub enum ReaderError {  
    #[error("Invalid command: {value}")]  
    InvalidCommand {  
        value: u8  
    },  
    #[error("Invalid response")]  
    InvalidResponse,  
}  
  
  
impl From<CommandError> for ReaderError {  
    fn from(error: CommandError) -> Self {  
        Self::InvalidCommand{ value: error.value }  
    }  
}  
  
#[non_exhaustive]  
pub struct Reader {  
    pub reader_address: u8,  
    pub transport: Box<dyn Transport>,  
}  
  
impl Reader {  
    pub fn new(transport: impl Transport + 'static, reader_address: Option<u8>) -> Reader {  
        let reader_address: u8 = reader_address.unwrap_or(0xFF);  
  
        Reader {  
            reader_address,  
            transport: Box::new(transport)  
        }  
    }  
  
    pub fn inventory_answer_mode(&mut self) -> Vec<Vec<u8>> {  
        let command: CommandRequest = CommandRequest::new(Command::Inventory,  
                                                          Some(self.reader_address),  
                                                          None);  
        self.transport.write(&command.serialize()).unwrap();  
  
        let read_result = self.transport.read();  
        let frame: Vec<u8> = read_result.unwrap();  
  
        let response: Response = Response::new(&frame);  
        let response_inventory: ResponseInventory = ResponseInventory::new(&response);  
        let tags: Vec<Vec<u8>> = response_inventory.tags  
            .iter().map(|tag| tag.to_vec()).collect();  
        tags  
    }  
  
    pub fn read_memory(&mut self, selected_epc: &[u8], memory_bank: MemoryBank,  
                       start_address: u8, length: u8,  
                       access_password: Option<[u8; 4]>) -> Response {  
        let epc_length: u8 = (selected_epc.len() / 2).to_u8().unwrap();  
        let mask_length: u8 = selected_epc.len().to_u8().unwrap();  
  
        let mut command_data: Vec<u8> = vec![epc_length];  
        command_data.extend(selected_epc);  
        command_data.extend([memory_bank.into(), start_address, length]);  
        command_data.extend(access_password.unwrap_or([0u8; 4]));  
        command_data.extend([0x00, mask_length]);  
  
        let command: CommandRequest = CommandRequest::new(Command::ReadMemory,  
                                                          Some(self.reader_address),  
                                                          Some(command_data));  
        self.transport.write(&command.serialize()).unwrap();  
        let read_result = self.transport.read();  
        let frame: Vec<u8> = read_result.unwrap();  
        Response::new(&frame)  
    }  
  
    pub fn write_memory(&mut self, data_to_write: Vec<u8>, selected_epc: &[u8],  
                        memory_bank: MemoryBank, start_address: u8,  
                        access_password: Option<[u8; 4]>) -> Response {  
        let data_to_write_length: u8 = (data_to_write.len() / 2).to_u8().unwrap();  
        let epc_length: u8 = (selected_epc.len() / 2).to_u8().unwrap();  
        let mask_length: u8 = selected_epc.len().to_u8().unwrap();  
  
        let mut command_data: Vec<u8> = vec![data_to_write_length, epc_length];  
        command_data.extend(selected_epc);  
        command_data.extend([memory_bank.into(), start_address]);  
        command_data.extend(data_to_write);  
        command_data.extend(access_password.unwrap_or([0u8; 4]));  
        command_data.extend([0x00, mask_length]);  
  
        let command: CommandRequest = CommandRequest::new(Command::WriteMemory,  
                                                          Some(self.reader_address),  
                                                          Some(command_data));  
        self.transport.write(&command.serialize()).unwrap();  
        let read_result = self.transport.read();  
        let frame: Vec<u8> = read_result.unwrap();  
        Response::new(&frame)  
    }  
}
5. utils.rs
pub fn hex_readable(buffer: &[u8]) -> String {  
    let mut result = String::new();  
  
    for (i, byte) in buffer.iter().enumerate() {  
        result.push_str(  
            &format!("{:02X}{}", byte, if i + 1 == buffer.len() { "" } else { " " })  
        );  
    }  
    result  
}
6. `main.rs

Uncomment fungsi/kode yang ingin digunakan. Jalankan dengan command:

cargo test test::test_reader -- --nocapture

mod transport;  
mod command;  
mod response;  
mod utils;  
mod reader;  
  
fn main() {  
    println!("Hello, world!");  
}  
  
#[cfg(test)]  
mod test {  
    use crate::reader::{MemoryBank, Reader};  
    use crate::transport::{BaudRate, SerialTransport, TcpIpTransport};  
    use crate::utils::hex_readable;  
  
    #[test]  
    pub fn test_reader() {  
        //// Serial  
        // let ports: Vec<String> = SerialTransport::scan();        // println!("Ports: {:?}", ports);        // let transport: SerialTransport = SerialTransport::new("/dev/ttyUSB0",        //                                                       BaudRate::Bps57600, Some(3));  
        //// TCP IP       let transport: TcpIpTransport = TcpIpTransport::new("192.168.1.198",  
                                                           6000, Some(3));  
        let mut reader: Reader = Reader::new(transport, Some(0xFF));  
  
        //// Inventory - Answer Mode  
        let tags = reader.inventory_answer_mode();  
        for tag in tags.iter() {  
            println!("Tag: {}", hex_readable(tag));  
        }  
        if tags.len() == 0 {  
            return;  
        }  
  
        //// Read memory  
        let selected_tag: &Vec<u8> = &tags[0];  
        let response_read_memory = reader.read_memory(selected_tag,  
                                                      MemoryBank::TID, 0, 6,  
                                                      None);  
        println!("{response_read_memory}");  
        println!("Data (TID): {}", hex_readable(&response_read_memory.data));  
  
        //// Write memory  
        // let data_to_write: Vec<u8> = vec![0x30, 0x00,        //                                   0x11, 0x22, 0x33, 0x44, 0x55, 0x66,        //                                   0x77, 0x88, 0x99, 0x00, 0xAA, 0xB1];        // let response_write_memory = reader.write_memory(data_to_write, selected_tag,        //                                                 MemoryBank::EPC, 0x01, None);        // println!("{response_write_memory}");        // println!("Write - status: {}", response_write_memory.status);    }  
}

☕️ Java

Package yang digunakan untuk berkomunikasi dengan Serial port: https://github.com/Fazecast/jSerialComm (silahkan import .jar ke project).

1. Transport (Transport.java)
import java.net.Socket;
import com.fazecast.jSerialComm.SerialPort;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
 
abstract class Transport {
    public abstract byte[] readBytes(int length) throws IOException;
 
    public abstract void writeBytes(byte[] buffer) throws IOException;
 
    public byte[] readFrame() throws IOException {
        byte[] lengthBytes = readBytes(1);
        if (lengthBytes.length == 0) {
            return null;
        }
 
        int frameLength = lengthBytes[0] & 0xFF; // Convert byte to int
        byte[] data = new byte[1 + frameLength]; // Full frame
        System.arraycopy(lengthBytes, 0, data, 0, 1);
        byte[] payload = readBytes(frameLength);
        System.arraycopy(payload, 0, data, 1, frameLength);
 
        return data;
    }
 
    public abstract void close();
}
 
class TcpTransport extends Transport {
    private final Socket socket;
    private final InputStream inputStream;
    private final OutputStream outputStream;
 
    public TcpTransport(String ipAddress, int port, int timeout) throws IOException {
        this.socket = new Socket(ipAddress, port);
        this.socket.setSoTimeout(timeout * 1000);
        this.inputStream = socket.getInputStream();
        this.outputStream = socket.getOutputStream();
    }
 
    @Override
    public byte[] readBytes(int length) throws IOException {
        byte[] buffer = new byte[length];
        int bytesRead = inputStream.read(buffer);
        if (bytesRead == -1) {
            throw new IOException("End of stream reached");
        }
        return buffer;
    }
 
    @Override
    public void writeBytes(byte[] buffer) throws IOException {
        outputStream.write(buffer);
        outputStream.flush();
    }
 
    @Override
    public void close() {
        try {
            socket.close();
            System.out.println("Port closed.");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
 
class SerialTransport extends Transport {
    private final SerialPort serialPort;
    private final InputStream inputStream;
    private final OutputStream outputStream;
 
    public SerialTransport(String portName, int baudRate, int timeout) {
        serialPort = SerialPort.getCommPort(portName);
        serialPort.setBaudRate(baudRate);
        serialPort.setNumDataBits(8);
        serialPort.setNumStopBits(SerialPort.ONE_STOP_BIT);
        serialPort.setParity(SerialPort.NO_PARITY);
        serialPort.setComPortTimeouts(SerialPort.TIMEOUT_READ_BLOCKING, timeout * 1000, timeout * 1000);
 
        if (serialPort.openPort()) {
            serialPort.flushDataListener();
 
            System.out.println("Port opened: " + portName);
            inputStream = serialPort.getInputStream();
            outputStream = serialPort.getOutputStream();
        } else {
            throw new RuntimeException("Failed to open port: " + portName);
        }
    }
 
    @Override
    public byte[] readBytes(int length) throws IOException {
        byte[] buffer = new byte[length];
        int bytesRead = inputStream.read(buffer);
        if (bytesRead < 0) {
            throw new IOException("No data read from serial port.");
        }
        return buffer;
    }
 
    @Override
    public void writeBytes(byte[] buffer) throws IOException {
        outputStream.write(buffer);
        outputStream.flush();
    }
 
    @Override
    public void close() {
        try {
            inputStream.close();
            outputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        serialPort.closePort();
        System.out.println("Port closed.");
    }
}
 
2. Command (Command.java)
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
 
class Command {
    private final byte[] baseData;
 
    public Command(int command, int readerAddress, Object data) {
 
        byte[] dataTemp;
        if (data instanceof Integer) {
            dataTemp = new byte[]{(byte) ((int) data)};
        } else if (data instanceof byte[]) {
            dataTemp = (byte[]) data;
        } else {
            dataTemp = new byte[0];
        }
 
        int frameLength = 4 + dataTemp.length;
        List<Byte> baseDataList = new ArrayList<>();
        baseDataList.add((byte) frameLength);
        baseDataList.add((byte) readerAddress);
        baseDataList.add((byte) command);
        for (byte b : dataTemp) {
            baseDataList.add(b);
        }
 
        this.baseData = new byte[baseDataList.size()];
        for (int i = 0; i < baseDataList.size(); i++) {
            this.baseData[i] = baseDataList.get(i);
        }
    }
 
    public byte[] serialize() {
        byte[] checksum = Utils.calculateChecksum(this.baseData);
        ByteBuffer buffer = ByteBuffer.allocate(this.baseData.length + checksum.length);
        buffer.put(this.baseData);
        buffer.put(checksum);
        return buffer.array();
    }
}
 
3. Response (Response.java)
import java.util.Arrays;
 
class Response {
    public byte[] responseBytes;
    public int length;
    public int readerAddress;
    public int command;
    public int status;
    public byte[] data;
    public byte[] checksum;
 
    public Response(byte[] responseBytes) {
        if (responseBytes.length < 6) {
            throw new IllegalArgumentException("Response data is too short to be valid.");
        }
 
        this.responseBytes = responseBytes;
        this.length = Byte.toUnsignedInt(responseBytes[0]);
 
        if (responseBytes.length < this.length) {
            throw new IllegalArgumentException("Response length mismatch.");
        }
 
        this.readerAddress = Byte.toUnsignedInt(responseBytes[1]);
        this.command = Byte.toUnsignedInt(responseBytes[2]);
        this.status = Byte.toUnsignedInt(responseBytes[3]);
 
        this.data = Arrays.copyOfRange(responseBytes, 4, this.length - 1);
        this.checksum = Arrays.copyOfRange(responseBytes, this.length - 1, this.length + 1);
 
        // Verify checksum
        byte[] dataForChecksum = new byte[4 + this.data.length];
        System.arraycopy(responseBytes, 0, dataForChecksum, 0, 4);
        System.arraycopy(this.data, 0, dataForChecksum, 4, this.data.length);
 
        byte[] calculatedChecksum = calculateChecksum(dataForChecksum);
        if (this.checksum[0] != calculatedChecksum[0] || this.checksum[1] != calculatedChecksum[1]) {
            throw new IllegalArgumentException("Checksum verification failed.");
        }
    }
 
    public String toString() {
        StringBuilder returnValue = new StringBuilder();
        returnValue.append(">>> START RESPONSE ================================\n");
        returnValue.append("RESPONSE       >> ").append(Utils.hexReadable(this.responseBytes)).append("\n");
        returnValue.append("READER ADDRESS >> ").append(Utils.hexReadable(this.readerAddress)).append("\n");
        returnValue.append("COMMAND        >> ").append(Utils.hexReadable(this.command)).append("\n");
        returnValue.append("STATUS         >> ").append(Utils.hexReadable(this.status)).append("\n");
        if (this.data.length > 0) {
            returnValue.append("DATA           >> ").append(Utils.hexReadable(this.data)).append("\n");
        }
        returnValue.append("CHECKSUM       >> ").append(Utils.hexReadable(this.checksum)).append("\n");
        returnValue.append(">>> END RESPONSE   ================================");
        return returnValue.toString();
    }
 
    private byte[] calculateChecksum(byte[] data) {
        int value = 0xFFFF;
        for (byte d : data) {
            value ^= (d & 0xFF);
            for (int i = 0; i < 8; i++) {
                if ((value & 0x0001) != 0) {
                    value = (value >> 1) ^ 0x8408;
                } else {
                    value = value >> 1;
                }
            }
        }
        byte crcLsb = (byte) (value & 0xFF);
        byte crcMsb = (byte) ((value >> 8) & 0xFF);
        return new byte[]{crcLsb, crcMsb};
    }
}
 
4. Utils (Utils.java)
public class Utils {
    public static byte[] calculateChecksum(byte[] data) {
        int value = 0xFFFF;
        for (byte d : data) {
            value ^= (d & 0xFF);
            for (int i = 0; i < 8; i++) {
                if ((value & 0x0001) != 0) {
                    value = (value >> 1) ^ 0x8408;
                } else {
                    value = value >> 1;
                }
            }
        }
        byte crcLsb = (byte) (value & 0xFF);
        byte crcMsb = (byte) ((value >> 8) & 0xFF);
        return new byte[]{crcLsb, crcMsb};
    }
 
    public static String hexReadable(int data) {
        return String.format("%02X", data);
    }
 
    public static String hexReadable(byte[] data) {
        StringBuilder sb = new StringBuilder();
        for (byte b : data) {
            sb.append(String.format("%02X ", b));
        }
        return sb.toString().trim();
    }
}
5. Reader (Reader.java)
import java.io.IOException;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.concurrent.TimeoutException;
 
public class Reader {
    private final Transport transport;
 
    public Reader(Transport transport) {
        this.transport = transport;
    }
 
    public void close() throws IOException {
        transport.close();
    }
 
    private void sendRequest(Command command) throws IOException {
        transport.writeBytes(command.serialize());
    }
 
    private byte[] getResponse() throws IOException {
        return transport.readFrame();
    }
 
    public Iterator<Response> inventoryActiveMode() {
        return new Iterator<>() {
            @Override
            public boolean hasNext() {
                return true;
            }
 
            @Override
            public Response next() {
                while (true) {
                    try {
                        byte[] rawResponse = getResponse();
                        if (rawResponse != null && rawResponse.length > 0) {
                            return new Response(rawResponse);
                        }
                    } catch (IOException e) {
                        // Check if the exception is due to a timeout and continue looping
                        if (e.getMessage().contains("timed out")) {
                            continue; // Ignore timeout and retry
                        }
                        throw new NoSuchElementException("Error retrieving response: " + e.getMessage());
                    }
                }
            }
        };
    }
 
    public Iterator<byte[]> inventoryAnswerMode() throws IOException { // 8.2.1 Inventory (Answer Mode)
        Command command = new Command(0x01, 0xFF, null);
        sendRequest(command);
 
        Response response = new Response(getResponse());
        byte[] data = response.data;
 
        if (data.length == 0) {
            return new EmptyIterator<>();
        }
 
        return new InventoryIterator(data);
    }
 
    public Response readMemory(byte[] epc, int memoryBank, int startAddress, int length, byte[] accessPassword) throws IOException {
        byte[] requestData = new byte[epc.length + 8];
        requestData[0] = (byte) (epc.length / 2);
        System.arraycopy(epc, 0, requestData, 1, epc.length);
        requestData[epc.length + 1] = (byte) memoryBank;
        requestData[epc.length + 2] = (byte) startAddress;
        requestData[epc.length + 3] = (byte) length;
        System.arraycopy(accessPassword, 0, requestData, epc.length + 4, 4);
 
        Command command = new Command(0x02, 0xFF, requestData);
        sendRequest(command);
 
        return new Response(getResponse());
    }
 
    public Response writeMemory(byte[] epc, int memoryBank, int startAddress, byte[] dataToWrite, byte[] accessPassword) throws IOException {
        byte[] requestData = new byte[dataToWrite.length + epc.length + 8];
        requestData[0] = (byte) (dataToWrite.length / 2);
        requestData[1] = (byte) (epc.length / 2);
        System.arraycopy(epc, 0, requestData, 2, epc.length);
        requestData[epc.length + 2] = (byte) memoryBank;
        requestData[epc.length + 3] = (byte) startAddress;
        System.arraycopy(dataToWrite, 0, requestData, epc.length + 4, dataToWrite.length);
        System.arraycopy(accessPassword, 0, requestData, epc.length + dataToWrite.length + 4, 4);
 
        Command command = new Command(0x03, 0xFF, requestData);
        sendRequest(command);
 
        return new Response(getResponse());
    }
 
    public Response setPower(int power) throws IOException {
        Command command = new Command(0x2F, 0xFF, new byte[]{(byte) power});
        sendRequest(command);
 
        return new Response(getResponse());
    }
 
    private static class InventoryIterator implements Iterator<byte[]> {
        private final byte[] data;
        private int pointer = 1;
        private final int tagCount;
        private int count = 0;
 
        InventoryIterator(byte[] data) {
            this.data = data;
            this.tagCount = data[0];
        }
 
        @Override
        public boolean hasNext() {
            return count < tagCount;
        }
 
        @Override
        public byte[] next() {
            if (!hasNext()) {
                throw new NoSuchElementException();
            }
 
            int tagLen = data[pointer] & 0xFF;
            int tagStart = pointer + 1;
            int tagEnd = tagStart + tagLen;
            byte[] tag = new byte[tagLen];
            System.arraycopy(data, tagStart, tag, 0, tagLen);
 
            pointer = tagEnd;
            count++;
            return tag;
        }
    }
 
    private static class EmptyIterator<T> implements Iterator<T> {
        @Override
        public boolean hasNext() {
            return false;
        }
 
        @Override
        public T next() {
            throw new NoSuchElementException();
        }
    }
}
 
6. Main.java
import java.io.IOException;
import java.util.Iterator;
 
public class Main {
    public static void main(String[] args) throws IOException {
//        TcpTransport transport = new TcpTransport("192.168.1.190", 6_000, 1);
        SerialTransport transport = new SerialTransport("/dev/ttyUSB0", 57_600, 1);
        Reader reader = new Reader(transport);
 
        byte[] selectedTag = new byte[0];
 
//        // 1. Inventory - Answer Mode
//        Iterator<byte[]> tags = reader.inventoryAnswerMode();
//        while (tags.hasNext()) {
//            selectedTag = tags.next();
//            System.out.println("Tag: " + Utils.hexReadable(selectedTag));
//        }
 
//////////////////////////////////////////////
 
        // 2. Inventory - Active Mode
        Iterator<Response> iterator = reader.inventoryActiveMode();
        while (iterator.hasNext()) {
            Response response = iterator.next();
            if (response.data.length == 0) {
                continue;
            }
            System.out.println("Tag: " + Utils.hexReadable(response.data));
        }
 
//////////////////////////////////////////////
 
//        // 3. Read memory
//        Response response_read = reader.readMemory(selectedTag, 0x02, 0x00, 0x06, new byte[4]);
//        System.out.println(response_read);
 
//////////////////////////////////////////////
 
//        // 4. Write memory
//        byte[] newEpc = new byte[] {
//                (byte) 0x12, (byte) 0x34, (byte) 0x56, (byte) 0x78,
//                (byte) 0x90, (byte) 0x12, (byte) 0x34, (byte) 0x56,
//                (byte) 0x78, (byte) 0x90, (byte) 0xAB, (byte) 0xEF
//        };
//        Response response_write = reader.writeMemory(selectedTag, 0x01, 0x02, newEpc, new byte[4]);
//        System.out.println(response_write);
 
//////////////////////////////////////////////
 
//        // 5. Set power
//        Response response_power = reader.setPower(10);
//        System.out.println(response_power);
 
        reader.close();
    }
}

Informasi lainnya

Video