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

Atur mode inventory

Mode inventory dapat diatur pada program demo:

Inventory mode

Pada contoh di atas reader berjalan di mode Active Mode (Multi-Tag), kami rekomendasi menggunakan Multi-Tag tanpa Single Tag Filtering Time agar output tag yang keluar lebih cepat.

Atur koneksi timeout (khusus TCP/IP - Active Mode)

Default nilai timeout koneksi TCP/IP (mode Active Mode) adalah 5 menit, agar reader dapat mengirim data tanpa terjadi timeout, dapat di atur pada tab “TCPIP Config”.

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)
CMD_INVENTORY = 0x01
CMD_READ_MEMORY = 0x02
CMD_WRITE_MEMORY = 0x03
CMD_READER_INFORMATION = 0x21
CMD_SET_READER_POWER = 0x2F
CMD_SET_LOCK = 0x06
 
 
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 CRC-16/MCRF4XX
        value = 0xFFFF
        for d in serialize:
            value ^= d
            for _ in range(8):
                value = (value >> 1) ^ 0x8408 if value & 0x0001 else (value >> 1)
        crc_msb = value >> 0x08
        crc_lsb = value & 0xFF
 
        serialize = serialize + bytes([crc_lsb])
        serialize = serialize + bytes([crc_msb])
        return serialize
 
3. Response (response.py)
class Response:
    def __init__(self, response_bytes: bytes):
        self.response_bytes = response_bytes
        self.length = response_bytes[0]
        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:-2]
        self.checksum = response_bytes[-2:]
 
    def __str__(self) -> str:
        return_value = ''
        value = '>>> START RESPONSE ================================'
        return_value = f'{return_value}\n{value}'
        value = f'RESPONSE       >> {hex_readable(self.response_bytes)}'  # Response
        return_value = f'{return_value}\n{value}'
        value = f'READER ADDRESS >> {hex_readable(self.reader_address)}'  # Reader Address
        return_value = f'{return_value}\n{value}'
        value = f'COMMAND        >> {hex_readable(self.command)}'  # Command
        return_value = f'{return_value}\n{value}'
        value = f'STATUS         >> {hex_readable(self.status)}'  # Status
        return_value = f'{return_value}\n{value}'
        if self.data:
            value = f'DATA           >> {hex_readable(self.data)}'  # Data
            return_value = f'{return_value}\n{value}'
        value = f'CHECKSUM       >> {hex_readable(self.checksum)}'  # Checksum
        return_value = f'{return_value}\n{value}'
        value = '>>> END RESPONSE   ================================'
        return_value = f'{return_value}\n{value}'
        return return_value.strip()
 
 
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)
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) -> Iterator[bytes]:  # 8.2.1 Inventory (Answer Mode)
        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 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 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())
 
5. main.py

Uncomment fungsi/kode yang ingin digunakan.

from typing import Iterator
 
from response import hex_readable, Response
from transport import SerialTransport, TcpTransport
from reader import Reader
 
transport = SerialTransport('COM6', 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. Set power, please check 8.4.6 Set Power
# power: int = 28
# response_set_power: Response = reader.set_power(power)
# print(response_set_power)
 
#########################################################
 
# 6. 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)
 
#########################################################
 
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);    }  
}

Informasi lainnya

Video