feat: add MQTT client for camera configuration and enhance camera management
This commit is contained in:
303
docs/mqtt-client.md
Normal file
303
docs/mqtt-client.md
Normal 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
|
||||
Reference in New Issue
Block a user