feat: add MQTT client for camera configuration and enhance camera management

This commit is contained in:
2026-02-08 11:58:57 +07:00
parent 78162fc0cb
commit d619534a73
14 changed files with 1254 additions and 111 deletions

303
docs/mqtt-client.md Normal file
View File

@@ -0,0 +1,303 @@
# MQTT Client - Hướng dẫn sử dụng
## Tổng quan
MQTT Client (`mqttClient`) là một utility singleton được thiết kế để quản lý kết nối MQTT trong ứng dụng. File này nằm tại `src/utils/mqttClient.ts`.
## Cài đặt
Package `mqtt` được cài đặt qua npm:
```bash
npm install mqtt
```
## Cấu trúc
```
src/utils/
├── mqttClient.ts # MQTT Client utility
└── wsClient.ts # WebSocket Client utility (legacy)
```
## Cách sử dụng
### 1. Import
```typescript
import { mqttClient } from '@/utils/mqttClient';
```
### 2. Kết nối
```typescript
// Lấy credentials từ user metadata
const { frontend_thing_id, frontend_thing_key } =
initialState?.currentUserProfile?.metadata || {};
// Kết nối
mqttClient.connect({
username: frontend_thing_id,
password: frontend_thing_key,
});
```
### 3. Đăng ký Event Handlers
```typescript
// Khi kết nối thành công
const unConnect = mqttClient.onConnect(() => {
console.log('MQTT Connected!');
});
// Khi có lỗi
const unError = mqttClient.onError((error) => {
console.error('MQTT Error:', error);
});
// Khi kết nối đóng
const unClose = mqttClient.onClose(() => {
console.log('MQTT Closed');
});
// Cleanup khi component unmount
return () => {
unConnect();
unError();
unClose();
mqttClient.disconnect();
};
```
### 4. Publish Message
```typescript
const topic = `channels/${cfg_channel_id}/messages/cameraconfig/gmsv6`;
const payload = JSON.stringify(senmlData);
if (mqttClient.isConnected()) {
mqttClient.publish(topic, payload);
}
```
### 5. Subscribe Topic
```typescript
// Subscribe
mqttClient.subscribe('channels/123/messages/#');
// Nhận message
const unMessage = mqttClient.onMessage((topic, message, packet) => {
console.log('Received:', topic, message.toString());
});
// Unsubscribe
mqttClient.unsubscribe('channels/123/messages/#');
```
## API Reference
| Method | Mô tả |
| ---------------------------- | ---------------------------------- |
| `connect(credentials, url?)` | Kết nối MQTT với username/password |
| `disconnect()` | Ngắt kết nối |
| `subscribe(topic)` | Subscribe vào topic |
| `unsubscribe(topic)` | Unsubscribe khỏi topic |
| `publish(topic, payload)` | Publish message |
| `onConnect(callback)` | Đăng ký callback khi kết nối |
| `onClose(callback)` | Đăng ký callback khi đóng |
| `onError(callback)` | Đăng ký callback khi lỗi |
| `onMessage(callback)` | Đăng ký callback khi nhận message |
| `isConnected()` | Kiểm tra trạng thái kết nối |
| `getClient()` | Lấy MQTT client gốc |
## Cấu hình Proxy (Development)
Trong môi trường development, MQTT được proxy qua config trong `config/proxy.ts`:
```typescript
"/mqtt": {
target: "https://gms.smatec.com.vn",
changeOrigin: true,
ws: true,
},
```
## Format SenML
Dữ liệu MQTT được format theo chuẩn SenML:
```typescript
const senml = [
{
bn: `urn:dev:mac:${mac}:`, // Base name
n: 'ack', // Name
t: Date.now() / 1000, // Timestamp (seconds)
vs: uuidv4(), // Value string (ACK ID)
},
{
n: 'user@email.com', // Email (@ → :)
t: Date.now() / 1000,
vs: JSON.stringify(config), // Config dạng JSON string
},
];
```
## Topic Structure
```
channels/${channel_id}/messages/${type}/${gw_type}
```
| Phần | Mô tả |
| ------------ | ------------------------------------------------------- |
| `channel_id` | ID của kênh (từ `thing.metadata.cfg_channel_id`) |
| `type` | Loại message: `cameraconfig`, `config`, `log`, `notify` |
| `gw_type` | Loại gateway: `gmsv6`, `gmsv5` |
## Ví dụ hoàn chỉnh
```typescript
import { mqttClient } from '@/utils/mqttClient';
import { useModel } from '@umijs/max';
import { useEffect, useState } from 'react';
import { v4 as uuidv4 } from 'uuid';
const MyComponent = ({ thing }) => {
const { initialState } = useModel('@@initialState');
const [connected, setConnected] = useState(false);
useEffect(() => {
const { frontend_thing_id, frontend_thing_key } =
initialState?.currentUserProfile?.metadata || {};
if (!frontend_thing_id || !frontend_thing_key) return;
mqttClient.connect({
username: frontend_thing_id,
password: frontend_thing_key,
});
const unConnect = mqttClient.onConnect(() => setConnected(true));
const unClose = mqttClient.onClose(() => setConnected(false));
return () => {
unConnect();
unClose();
mqttClient.disconnect();
};
}, [initialState]);
const handlePublish = () => {
const { cfg_channel_id, external_id } = thing.metadata;
const topic = `channels/${cfg_channel_id}/messages/cameraconfig/gmsv6`;
const payload = [
{
bn: `urn:dev:mac:${external_id.replaceAll('-', '')}:`,
n: 'ack',
t: Date.now() / 1000,
vs: uuidv4(),
},
{
n: initialState?.currentUserProfile?.email?.replaceAll('@', ':'),
t: Date.now() / 1000,
vs: JSON.stringify({ record_type: 'all' }),
},
];
if (mqttClient.isConnected()) {
mqttClient.publish(topic, JSON.stringify(payload));
}
};
return (
<button onClick={handlePublish} disabled={!connected}>
{connected ? 'Publish' : 'Connecting...'}
</button>
);
};
```
## So sánh mqttClient vs wsClient
Dự án có 2 utilities để giao tiếp real-time:
### Bảng so sánh
| Tiêu chí | mqttClient | wsClient |
| --- | --- | --- |
| **Thư viện** | `mqtt` (MQTT.js) | `reconnecting-websocket` |
| **Protocol** | MQTT over WebSocket | WebSocket thuần |
| **Xác thực** | Username/Password (MQTT credentials) | Token (access_token) hoặc không |
| **Topics** | Hỗ trợ MQTT topics, subscribe/unsubscribe | Không có khái niệm topic |
| **Publish** | `publish(topic, payload)` | `send(data)` |
| **Subscribe** | `subscribe(topic)` + `onMessage()` | `subscribe(callback)` |
| **Reconnect** | Tự động (built-in) | Tự động (reconnecting-websocket) |
| **Use case** | Giao tiếp với IoT Gateway/Devices | Giao tiếp WebSocket server |
### Khi nào dùng mqttClient?
**Dùng mqttClient khi:**
- Gửi/nhận dữ liệu với IoT Gateways (GMSv5, GMSv6)
- Cấu hình thiết bị (camera, nodes, schedules)
- Cần subscribe nhiều topics khác nhau
- Làm việc với Mainflux platform
### Khi nào dùng wsClient?
**Dùng wsClient khi:**
- Cần WebSocket connection đơn giản
- Giao tiếp với WebSocket server không phải MQTT broker
- Xác thực bằng access_token
### Code comparison
**mqttClient:**
```typescript
import { mqttClient } from '@/utils/mqttClient';
// Connect với username/password
mqttClient.connect({
username: 'thing_id',
password: 'thing_key',
});
// Subscribe topic
mqttClient.subscribe('channels/123/messages/#');
// Publish với topic
mqttClient.publish('channels/123/messages/config', payload);
// Nhận message
mqttClient.onMessage((topic, message) => {
console.log(topic, message.toString());
});
```
**wsClient:**
```typescript
import { wsClient } from '@/utils/wsClient';
// Connect với hoặc không có token
wsClient.connect('/mqtt', false);
// Không có subscribe topic
// Chỉ nhận tất cả messages
wsClient.subscribe((data) => {
console.log(data);
});
// Gửi data (không có topic)
wsClient.send({ action: 'update', data: payload });
```
## Xem thêm
- [Mainflux.md](../Mainflux.md) - Tài liệu giao tiếp MQTT chi tiết
- [mqttClient.ts](../src/utils/mqttClient.ts) - Source code MQTT Client
- [wsClient.ts](../src/utils/wsClient.ts) - Source code WebSocket Client