# 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
