Tài liệu mô tả cách ứng dụng ScienEDU Pro (sensor_dashboard) kết nối tới thiết bị đo (USB Serial, Bluetooth LE, chế độ giả lập), luồng dữ liệu trong code, và định dạng frame / payload (giao thức T25 v1).
Thiết bị → [USB / BLE / Mock] → ConnectionService → DataService / BleDataService
↓
T25V1Protocol + T25PayloadProcessor
↓
JSON (UTF-8) → UI / DeviceState
ConnectionManager (lib/t25/device/connection_manager.dart): chọn loại kết nối hiện tại và lấy dịch vụ tương ứng.ConnectionFactory: ánh xạ ConnectionType → SimpleUsbSerialService / AndroidUsbSerialService / SimpleBleService / SimpleMockService.DeviceStateManager (lib/providers/device_provider.dart): mở kết nối, gửi lệnh discovery / ping / ghi dữ liệu, nhận byte thô và đưa qua DataService.process.| Kiểu | ConnectionType | Nền tảng | Lớp dịch vụ chính |
|---|---|---|---|
| USB Serial | USB_SERIAL | Windows / desktop | SimpleUsbSerialService + UsbSerial (flutter_libserialport) |
| USB Serial | USB_SERIAL | Android | AndroidUsbSerialService + AndroidUsbSerial |
| Bluetooth LE | BLE | Đa nền tảng (universal_ble) | SimpleBleService |
| Giả lập | MOCK | Mọi nền tảng | SimpleMockService |
Trạng thái kết nối: ConnectionStatus — DISCONNECTED, CONNECTING, CONNECTED, ERROR (lib/t25/connection/connection_service.dart).
SerialPort.availablePorts (Windows/desktop); Android dùng đường riêng trong AndroidUsbSerialService (xem scanAsync).lib/t25/connection/usb/usb_serial.dart): 8 bit dữ liệu, không parity, 1 stop bit; RTS/DTR bật tùy cấu hình driver.openConnection(..., baudRate: ...). Giá trị mặc định tham chiếu trong AppConfigs.serialBaudRate (921600). Lớp UsbSerial khởi tạo 115200 nếu không gọi setBaudRate trước khi mở cổng — cần đảm bảo UI/luồng kết nối truyền đúng baud thiết bị thực tế.SerialPortReader đẩy Uint8List vào listener; log debug có thể in hex (convert/hex).UniversalBle.startScan(), khoảng 3 giây, kết quả gom trong cache (làm mới cache nếu lần quét trước cách > 10 giây). Chỉ giữ thiết bị có tên và deviceId.UniversalBle.connect, sau đó discoverServices, bật notification/indication cho mọi characteristic hỗ trợ.write() chủ yếu dùng để đưa dữ liệu vào _processIncomingCommand (đường nội bộ). Thiết bị được giả định tự đẩy dữ liệu qua notify.\n (0x0A), buffer tạm ghép với lần nhận trước; giới hạn độ dài buffer: AppConfigs.maxBleBufferSize.Dùng để phát dữ liệu giả lập không cần phần cứng; cùng pipeline T25/JSON với USB khi tích hợp đúng.
Định nghĩa trong lib/t25/protocol/t25/v1/t25_v1_protocol.dart.
Mỗi gói T25 v1 có dạng:
| Phần | Độ dài | Mô tả |
|---|---|---|
| Start indicator | 2 byte | 0x00 0x00 — đánh dấu bắt đầu frame |
| Descriptor (DESC) | 2 byte | Mã hóa thuật toán mã hóa (4 bit cao của byte đầu) + độ dài payload (12 bit) |
| Payload | N byte | Dữ liệu (có thể đã mã hóa tùy thuật toán) |
0x0FFF).0x00 0x00 và tách từng khung.CipherAlgorithm)| Giá trị | Ý nghĩa | Ghi chú |
|---|---|---|
0 | NONE | Payload là plaintext |
1 | CHACHA20 | Mặc định khi gửi từ app (T25V1Protocol.send) |
2 | CHACHA20_POLY1305 | Được liệt kê; phần giải mã có thể báo chưa hỗ trợ |
3 | AES128 | Giải mã qua EncryptFactory |
Factory mã hóa: lib/t25/protocol/encrypt/.
send)DeviceProvider → DataService.send → T25V1Protocol.send(jsonEncode(cmd), sender):
connectionService.write.feed)feed(Uint8List) nối byte vào buffer, tách frame theo 0x00 0x00, đọc DESC, cắt đúng payloadLength, sau đó:
NONE → đưa payload thẳng tới bước tiếp;CHACHA20 / AES128 → giải mã rồi đưa tiếp.Lỗi độ dài / frame không đủ byte → callback onError (chuỗi kiểu "Frame error").
Định nghĩa trong lib/t25/protocol/t25/payload/t25_payload_processor.dart và ascii_control.dart.
0x02 (Start of Text)0x03 (End of Text)Build: STX + UTF-8(JSON) + ETX.
Parse: Tích lũy byte trong buffer (tối đa ~500 KiB); khi gặp ETX, tìm STX gần nhất, lấy phần giữa làm nội dung JSON (string hoặc bytes tùy tham số isString).
Sau khi có chuỗi JSON, DeviceStateManager có thể chạy _fixDuplicateKeys để xử lý key trùng trong dataStructure hoặc live data.
Định nghĩa trong lib/t25/device/cmd/. Trường chính: s — mã tín hiệu (T25CmdSignal).
s | Hằng | Ý nghĩa |
|---|---|---|
| 0 | DISCOVERY | Yêu cầu / phản hồi thông tin thiết bị |
| 1 | CONFIG | Cấu hình (kèm d trong T25CmdConfig) |
| 2 | REQUEST_DATA | Bắt đầu / yêu cầu luồng dữ liệu |
| 3 | REQUEST_STOP_DATA | Dừng luồng dữ liệu |
T25BaseCmd.PING là object rỗng {} — gửi định kỳ khi đã ổn định (_startPingPong).T25CmdDiscovery mỗi 2 giây cho tới khi nhận được JSON thiết bị và parse thành T25Device.T25Device (lib/t25/device/t25_device.dart): mô tả thiết bị sau discovery (JSON), gồm dataStructure — map các kênh (thường key "0"…"3") tới DeviceDataStructureDefinition (sensor s, presentation p, kiểu t)."0", "1", … — giá trị số (double) tương ứng từng cổng cảm biến. UI/logic có thể kiểm tra đủ 4 key cho layout 4 kênh.Khi dữ liệu đến từ BLE (sau khi decode UTF-8 và tách theo \n), chuỗi được đưa vào BleDataService (lib/t25/device/ble/ble_data_service.dart).
@ — phiên bản chuẩn / không mã hóa payload theo cấu hình;: — phiên bản “pro”, payload thường được coi là đã mã hóa.0 — BLE_DEVICE_INFO_FRAME_VERSION (thông tin thiết bị, JSON string sau khi xử lý).1 — BLE_DATA_FRAME_VERSION (dữ liệu cảm biến).Ví dụ ý tưởng: 1@<payload> hoặc 1:<hex hoặc ciphertext>.
BleDataConfig.isEncrypted: nếu true, phần sau separator có thể được giải AES-128 qua CodecUtils.tryDecryptFromAes128 (khóa từ .env — AES_KEY).1 (dữ liệu đo).1)Chuỗi dạng các khối lặp:
A[<sensorType>:<giá trị>], B[...], C[...], D[...]
"0"–"3".: là số thực (double).Sau khi parse, app chuyển thành JSON kiểu {"0": 25.256, "1": 0.0, ...} (UTF-8 bytes) rồi bọc lại qua T25PayloadProcessor + T25V1Protocol với CipherAlgorithm.NONE để thống nhất pipeline với USB (_wrapIntoProtocol trong simple_ble_service.dart).
AppConfigs)File: lib/configs/app_configs.dart (một phần dùng cho tích hợp / lọc BLE trong tiện ích khác):
bleServiceIds, typeSensorCharacteristicIds, writeCharacteristicIds — UUID GATT tham chiếu.blePrefix — tiền tố tên thiết bị gợi ý (ví dụ ScienEDU, BoThuNhanSoLieu).serialBaudRate — baud mặc định tham chiếu (921600).maxBleBufferSize, maxSerialBufferSize — giới hạn buffer.| Nội dung | Đường dẫn |
|---|---|
| Loại kết nối / trạng thái | lib/t25/connection/connection_service.dart |
| USB desktop | lib/t25/connection/usb/usb_serial.dart, lib/t25/device/serial/simple_usb_serial_service.dart |
| USB Android | lib/t25/connection/usb/android_usb_serial.dart, lib/t25/device/serial/android_usb_serial_service.dart |
| BLE | lib/t25/device/ble/simple_ble_service.dart, lib/t25/device/ble/ble_data_service.dart |
| T25 v1 | lib/t25/protocol/t25/v1/t25_v1_protocol.dart, cipher_algorithm.dart |
| Payload STX/ETX | lib/t25/protocol/t25/payload/t25_payload_processor.dart, ascii_control.dart |
| Lệnh JSON | lib/t25/device/cmd/t25_cmd.dart, t25_cmd_*.dart |
| Mô hình thiết bị | lib/t25/device/t25_device.dart |
| Điều phối UI | lib/providers/device_provider.dart |
| Connection manager | lib/t25/device/connection_manager.dart |
\n nếu dùng định dạng text; nếu bật mã hóa, cấu hình AES_KEY trùng với thiết bị..env không commit lên kho công khai; luồng gửi mặc định dùng ChaCha20 — cần khớp với implementation phía thiết bị / gateway.Tài liệu phản ánh trạng thái mã nguồn tại thời điểm viết; khi refactor SimpleBleService hoặc DataService, nên cập nhật lại mục tương ứng.