Chuyển đến nội dung

Phụ lục: tự viết receive loop cho Go, Rust và C++

Chỉ đọc trang này nếu bạn viết Go, Rust hoặc C++ và cần vòng kiểm kê liên tục; bản Python và Web Serial đã có sẵn vòng đọc, xem Đọc thẻ và chống trùng. Ở ba ngôn ngữ này, bạn cầm serial port, tự nhận dòng dữ liệu từ đầu đọc, rồi dùng bộ parse của SDK để lấy dữ liệu thẻ. Mỗi mẫu dưới đây là một chương trình hoàn chỉnh, copy vào dự án là chạy được.

Cả ba mẫu có cùng một hình dạng, và cũng là hình dạng của mọi receive loop đúng:

  1. Gửi lệnh bắt đầu kiểm kê, và bảo đảm lệnh dừng được gửi khi thoát.
  2. Đọc serial port theo từng khúc, dồn vào một buffer: dữ liệu có thể về dồn cục hoặc bị cắt giữa chừng, không bao giờ được giả định mỗi lần đọc là một thông báo tròn trịa.
  3. Tách từng frame hoàn chỉnh khỏi buffer theo cấu trúc 5A | PCW(4) | length(2) | data | crc(2), chi tiết ở giao thức đầu đọc NRN.
  4. Với frame là tag notification, đưa phần data cho bộ parse của SDK rồi chống trùng như mọi bản khác.

Go

NewNRNReaderWithTransport là hàm công khai, nên bạn giữ được handle của serial port mà vẫn dùng được ParseEPC.

go
package main

import (
	"bytes"
	"encoding/binary"
	"log"
	"sync"
	"time"

	nrn "github.com/Nextwaves-Industries/nextwaves-sdk/sdk/nation/go"
	"go.bug.st/serial"
)

// crc16 của giao thức NRN: polynomial 0x1021, khởi tạo 0x0000.
func crc16(data []byte) uint16 {
	var crc uint16
	for _, b := range data {
		crc ^= uint16(b) << 8
		for i := 0; i < 8; i++ {
			if crc&0x8000 != 0 {
				crc = crc<<1 ^ 0x1021
			} else {
				crc <<= 1
			}
		}
	}
	return crc
}

// buildFrame dựng một lệnh NRN: 5A | PCW(4) | length(2) | data | crc(2)
func buildFrame(mid uint16, payload []byte) []byte {
	body := []byte{0x00, 0x01, byte(mid >> 8), byte(mid)}
	body = binary.BigEndian.AppendUint16(body, uint16(len(payload)))
	body = append(body, payload...)

	frame := append([]byte{0x5A}, body...)
	return binary.BigEndian.AppendUint16(frame, crc16(body))
}

// nextFrame trả về frame hoàn chỉnh đầu tiên trong b và số byte cần bỏ đi.
func nextFrame(b []byte) (frame []byte, consumed int, ok bool) {
	start := bytes.IndexByte(b, 0x5A)
	if start < 0 {
		return nil, len(b), false // không thấy header, bỏ phần nhiễu
	}
	rest := b[start:]
	if len(rest) < 9 {
		return nil, start, false // có header nhưng frame chưa đủ
	}
	total := 9 + int(binary.BigEndian.Uint16(rest[5:7]))
	if len(rest) < total {
		return nil, start, false
	}
	return rest[:total], start + total, true
}

type deduper struct {
	mu     sync.Mutex
	window time.Duration
	seen   map[string]time.Time
}

func newDeduper(window time.Duration) *deduper {
	return &deduper{window: window, seen: make(map[string]time.Time)}
}

func (d *deduper) accept(epc string) bool {
	now := time.Now()
	d.mu.Lock()
	defer d.mu.Unlock()

	last, found := d.seen[epc]
	d.seen[epc] = now
	if found && now.Sub(last) < d.window {
		return false
	}

	for key, seenAt := range d.seen {
		if now.Sub(seenAt) > d.window*10 {
			delete(d.seen, key)
		}
	}
	return true
}

func main() {
	const portName = "/dev/ttyUSB0"

	port, err := serial.Open(portName, &serial.Mode{
		BaudRate: 115200,
		DataBits: 8,
		Parity:   serial.NoParity,
		StopBits: serial.OneStopBit,
	})
	if err != nil {
		log.Fatal(err)
	}
	defer port.Close()

	if err := port.SetReadTimeout(500 * time.Millisecond); err != nil {
		log.Fatal(err)
	}

	// Reader chỉ dùng để decode payload của thẻ; serial port do ta giữ.
	decoder := nrn.NewNRNReaderWithTransport(port, portName, 115200)

	mask := nrn.BuildAntennaMask([]int{1})
	payload := binary.BigEndian.AppendUint32(nil, mask)
	payload = append(payload, 0x01) // liên tục

	if _, err := port.Write(buildFrame(0x0210, payload)); err != nil {
		log.Fatal(err)
	}
	defer port.Write(buildFrame(0x02FF, nil)) //nolint:errcheck // cố gắng dừng

	dedupe := newDeduper(2 * time.Second)
	buf := make([]byte, 0, 8192)
	chunk := make([]byte, 1024)
	deadline := time.Now().Add(10 * time.Second)

	for time.Now().Before(deadline) {
		n, err := port.Read(chunk)
		if err != nil {
			log.Fatal(err)
		}
		if n == 0 {
			continue // hết timeout đọc, không có gì trong vùng đọc
		}
		buf = append(buf, chunk[:n]...)

		for {
			frame, consumed, ok := nextFrame(buf)
			if !ok {
				buf = buf[consumed:]
				break
			}

			notify := frame[3]&0x10 != 0
			category := frame[3] & 0x0F
			mid := frame[4]

			if notify && category == 0x02 && mid == 0x10 {
				dataLen := int(binary.BigEndian.Uint16(frame[5:7]))
				tag := decoder.ParseEPC(frame[7 : 7+dataLen])

				if tag.EPC != "" && dedupe.accept(tag.EPC) {
					log.Printf("EPC=%s ăng-ten=%d", tag.EPC, tag.AntennaID)
				}
			}

			buf = buf[consumed:]
		}
	}
}

Rust

Mẫu Rust tự giữ serial port và tự decode dữ liệu thẻ, dùng các hàm tiện ích mà crate export sẵn (build_antenna_mask, calculate_rssi).

rust
use nrn_sdk::{build_antenna_mask, calculate_rssi};
use std::collections::HashMap;
use std::io::Read;
use std::time::{Duration, Instant};

fn crc16(data: &[u8]) -> u16 {
    let mut crc: u16 = 0;
    for &byte in data {
        crc ^= u16::from(byte) << 8;
        for _ in 0..8 {
            crc = if crc & 0x8000 != 0 { (crc << 1) ^ 0x1021 } else { crc << 1 };
        }
    }
    crc
}

fn build_frame(mid: u16, payload: &[u8]) -> Vec<u8> {
    let mut body = vec![0x00, 0x01, (mid >> 8) as u8, mid as u8];
    body.extend_from_slice(&(payload.len() as u16).to_be_bytes());
    body.extend_from_slice(payload);

    let mut frame = vec![0x5A];
    frame.extend_from_slice(&body);
    frame.extend_from_slice(&crc16(&body).to_be_bytes());
    frame
}

struct Tag {
    epc: String,
    antenna_id: u8,
    rssi: Option<i32>,
    frequency: Option<f64>,
}

fn parse_tag(data: &[u8]) -> Option<Tag> {
    if data.len() < 5 {
        return None;
    }
    let epc_len = u16::from_be_bytes([data[0], data[1]]) as usize;
    if data.len() < 2 + epc_len + 3 {
        return None;
    }

    let epc = data[2..2 + epc_len]
        .iter()
        .map(|b| format!("{b:02X}"))
        .collect::<String>();

    let mut tag = Tag { epc, antenna_id: data[2 + epc_len + 2], rssi: None, frequency: None };

    let mut cursor = 2 + epc_len + 3;
    while cursor < data.len() {
        let pid = data[cursor];
        cursor += 1;
        match pid {
            0x01 => {
                tag.rssi = data.get(cursor).map(|&raw| calculate_rssi(raw));
                cursor += 1;
            }
            0x08 if cursor + 4 <= data.len() => {
                let khz = u32::from_be_bytes([
                    data[cursor], data[cursor + 1], data[cursor + 2], data[cursor + 3],
                ]);
                tag.frequency = Some(f64::from(khz) / 1000.0);
                cursor += 4;
            }
            0x09 => cursor += 1,
            _ => break, // parameter lạ, dừng lại còn hơn đọc sai phần còn lại
        }
    }
    Some(tag)
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut port = serialport::new("/dev/ttyUSB0", 115_200)
        .timeout(Duration::from_millis(500))
        .open()?;

    let mask = build_antenna_mask(&[1]);
    let mut payload = mask.to_be_bytes().to_vec();
    payload.push(0x01); // liên tục
    port.write_all(&build_frame(0x0210, &payload))?;

    let window = Duration::from_secs(2);
    let mut seen: HashMap<String, Instant> = HashMap::new();
    let mut buf: Vec<u8> = Vec::with_capacity(8192);
    let mut chunk = [0u8; 1024];
    let deadline = Instant::now() + Duration::from_secs(10);

    while Instant::now() < deadline {
        match port.read(&mut chunk) {
            Ok(0) => continue,
            Ok(n) => buf.extend_from_slice(&chunk[..n]),
            Err(ref e) if e.kind() == std::io::ErrorKind::TimedOut => continue,
            Err(e) => return Err(e.into()),
        }

        loop {
            let Some(start) = buf.iter().position(|&b| b == 0x5A) else {
                buf.clear();
                break;
            };
            buf.drain(..start);
            if buf.len() < 9 {
                break;
            }

            let data_len = u16::from_be_bytes([buf[5], buf[6]]) as usize;
            let total = 9 + data_len;
            if buf.len() < total {
                break;
            }

            let notify = buf[3] & 0x10 != 0;
            let category = buf[3] & 0x0F;
            let mid = buf[4];

            if notify && category == 0x02 && mid == 0x10 {
                if let Some(tag) = parse_tag(&buf[7..7 + data_len]) {
                    let now = Instant::now();
                    let fresh = seen
                        .get(&tag.epc)
                        .is_none_or(|last| now.duration_since(*last) >= window);
                    seen.insert(tag.epc.clone(), now);

                    if fresh {
                        println!("EPC={} ăng-ten={} rssi={:?}", tag.epc, tag.antenna_id, tag.rssi);
                    }
                    seen.retain(|_, last| now.duration_since(*last) < window * 10);
                }
            }

            buf.drain(..total);
        }
    }

    port.write_all(&build_frame(0x02FF, &[]))?;
    Ok(())
}

C++

Mẫu C++ tạo một NRNReader chỉ để parse dữ liệu thẻ (parse_epc hoạt động độc lập với kết nối), còn serial I/O do bạn giữ.

cpp
#include "nrn.hpp"

#include <chrono>
#include <cstdint>
#include <string>
#include <unordered_map>
#include <vector>

using Clock = std::chrono::steady_clock;

uint16_t crc16(const std::vector<uint8_t>& data) {
    uint16_t crc = 0;
    for (uint8_t byte : data) {
        crc ^= static_cast<uint16_t>(byte) << 8;
        for (int i = 0; i < 8; ++i) {
            crc = (crc & 0x8000) ? static_cast<uint16_t>((crc << 1) ^ 0x1021)
                                 : static_cast<uint16_t>(crc << 1);
        }
    }
    return crc;
}

std::vector<uint8_t> build_frame(uint16_t mid, const std::vector<uint8_t>& payload) {
    std::vector<uint8_t> body{0x00, 0x01, static_cast<uint8_t>(mid >> 8),
                              static_cast<uint8_t>(mid & 0xFF)};
    body.push_back(static_cast<uint8_t>(payload.size() >> 8));
    body.push_back(static_cast<uint8_t>(payload.size() & 0xFF));
    body.insert(body.end(), payload.begin(), payload.end());

    std::vector<uint8_t> frame{0x5A};
    frame.insert(frame.end(), body.begin(), body.end());

    uint16_t crc = crc16(body);
    frame.push_back(static_cast<uint8_t>(crc >> 8));
    frame.push_back(static_cast<uint8_t>(crc & 0xFF));
    return frame;
}

int main() {
    // Không bao giờ gọi open(): chỉ dùng cho parse_epc.
    nrn::NRNReader codec("", 115200);

    // Serial handle của riêng bạn. open_port/read_port/write_port là mã theo nền tảng.
    auto port = open_port("/dev/ttyUSB0", 115200);

    uint32_t mask = nrn::build_antenna_mask({1});
    std::vector<uint8_t> payload{
        static_cast<uint8_t>(mask >> 24), static_cast<uint8_t>(mask >> 16),
        static_cast<uint8_t>(mask >> 8),  static_cast<uint8_t>(mask),
        0x01,  // liên tục
    };
    write_port(port, build_frame(0x0210, payload));

    const auto window = std::chrono::seconds(2);
    std::unordered_map<std::string, Clock::time_point> seen;
    std::vector<uint8_t> buf;

    while (running) {
        auto chunk = read_port(port);           // có thể trả về 0 byte khi timeout
        buf.insert(buf.end(), chunk.begin(), chunk.end());

        while (true) {
            auto it = std::find(buf.begin(), buf.end(), 0x5A);
            if (it == buf.end()) { buf.clear(); break; }
            buf.erase(buf.begin(), it);
            if (buf.size() < 9) break;

            size_t data_len = (static_cast<size_t>(buf[5]) << 8) | buf[6];
            size_t total = 9 + data_len;
            if (buf.size() < total) break;

            bool notify = (buf[3] & 0x10) != 0;
            uint8_t category = buf[3] & 0x0F;
            uint8_t mid = buf[4];

            if (notify && category == 0x02 && mid == 0x10) {
                std::vector<uint8_t> data(buf.begin() + 7, buf.begin() + total);
                nrn::TagData tag = codec.parse_epc(data);

                auto now = Clock::now();
                auto found = seen.find(tag.epc);
                bool fresh = found == seen.end() || (now - found->second) >= window;
                seen[tag.epc] = now;

                if (!tag.epc.empty() && fresh) {
                    std::cout << tag.epc << " ăng-ten " << int(tag.antenna_id) << std::endl;
                }
            }

            buf.erase(buf.begin(), buf.begin() + total);
        }
    }

    write_port(port, build_frame(0x02FF, {}));
    return 0;
}

Đọc tiếp

Tài liệu vận hành Easy Inventory và tích hợp RFID