Merge pull request #2705 from hehaijunandhenry/main

add MQTT channel support
This commit is contained in:
美電球 2026-05-08 18:50:08 +08:00 committed by GitHub
commit 8508f80608
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 1951 additions and 13 deletions

View file

@ -447,7 +447,7 @@ For full provider configuration details, see [Providers & Models](docs/guides/pr
## 💬 Channels (Chat Apps)
Talk to your PicoClaw through 18+ messaging platforms:
Talk to your PicoClaw through 19+ messaging platforms:
| Channel | Setup | Protocol | Docs |
|---------|-------|----------|------|
@ -465,6 +465,7 @@ Talk to your PicoClaw through 18+ messaging platforms:
| **VK** | Easy (group token) | Long Poll | [Guide](docs/channels/vk/README.md) |
| **IRC** | Medium (server + nick) | IRC protocol | [Guide](docs/guides/chat-apps.md#irc) |
| **OneBot** | Medium (WebSocket URL) | OneBot v11 | [Guide](docs/channels/onebot/README.md) |
| **MQTT** | Easy (broker + agent_id) | MQTT pub/sub | [Guide](docs/channels/mqtt/README.md) |
| **MaixCam** | Easy (enable) | TCP socket | [Guide](docs/channels/maixcam/README.md) |
| **Pico** | Easy (enable) | Native protocol | Built-in |
| **Pico Client** | Easy (WebSocket URL) | WebSocket | Built-in |
@ -617,7 +618,7 @@ For detailed guides beyond this README:
| Topic | Description |
|-------|-------------|
| [Docker & Quick Start](docs/guides/docker.md) | Docker Compose setup, Launcher/Agent modes |
| [Chat Apps](docs/guides/chat-apps.md) | All 17+ channel setup guides |
| [Chat Apps](docs/guides/chat-apps.md) | All 18+ channel setup guides |
| [Configuration](docs/guides/configuration.md) | Environment variables, workspace layout, security sandbox |
| [MCP Server CLI](docs/reference/mcp-cli.md) | Add, list, test, edit, and remove MCP server entries from the CLI |
| [Scheduled Tasks and Cron Jobs](docs/reference/cron.md) | Cron schedule types, deliver modes, command gates, job storage |

View file

@ -0,0 +1,140 @@
# 📡 Canal MQTT
PicoClaw prend en charge n'importe quel client MQTT comme canal de messagerie. Les appareils ou services publient des requêtes vers un broker ; PicoClaw s'abonne, les traite et publie les réponses en retour.
## 🚀 Démarrage rapide
**1. Ajouter le canal dans `~/.picoclaw/config.json` :**
```json
{
"channel_list": {
"mqtt": {
"enabled": true,
"type": "mqtt",
"settings": {
"broker": "tcp://localhost:1883",
"agent_id": "assistant"
}
}
}
}
```
**2. Démarrer la passerelle :**
```bash
picoclaw gateway
```
**3. Envoyer un message depuis n'importe quel client MQTT :**
```bash
mosquitto_pub -t "/picoclaw/assistant/device1/request" \
-m '{"text": "Quel est l'\''usage CPU ?"}'
```
**4. S'abonner pour recevoir la réponse :**
```bash
mosquitto_sub -t "/picoclaw/assistant/device1/response"
```
---
## 📨 Structure des topics
```
{prefix}/{agent_id}/{client_id}/request # Client → PicoClaw
{prefix}/{agent_id}/{client_id}/response # PicoClaw → Client
```
| Segment | Description |
|---------|-------------|
| `prefix` | Préfixe de topic configuré côté serveur. Défaut : `/picoclaw` |
| `agent_id` | Identifiant de l'instance PicoClaw, défini dans le champ `agent_id` |
| `client_id` | Identifiant de session défini par le client — utiliser un ID stable par appareil pour maintenir le contexte |
### Payload du message (JSON)
```json
{ "text": "votre message ici" }
```
---
## ⚙️ Configuration
### config.json
```json
{
"channel_list": {
"mqtt": {
"enabled": true,
"type": "mqtt",
"settings": {
"broker": "ssl://votre-broker:8883",
"agent_id": "assistant",
"topic_prefix": "/picoclaw",
"client_id": "",
"keep_alive": 60,
"qos": 0
}
}
}
}
```
### .security.yml (identifiants)
Le nom d'utilisateur et le mot de passe sont stockés dans `~/.picoclaw/.security.yml`, pas dans `config.json` :
```yaml
channel_list:
mqtt:
settings:
username: votre_utilisateur
password: votre_mot_de_passe
```
### Champs de configuration
| Champ | Emplacement | Requis | Défaut | Description |
|-------|-------------|--------|--------|-------------|
| `broker` | `settings` | Oui | — | URL du broker MQTT, ex. `tcp://host:1883`, `ssl://host:8883` |
| `agent_id` | `settings` | Oui | — | Identifiant de l'agent, utilisé dans le chemin du topic |
| `topic_prefix` | `settings` | Non | `/picoclaw` | Préfixe de l'espace de noms des topics |
| `username` | `.security.yml` | Non | — | Nom d'utilisateur pour l'authentification au broker |
| `password` | `.security.yml` | Non | — | Mot de passe pour l'authentification au broker |
| `client_id` | `settings` | Non | auto-généré | ID client paho envoyé au broker. Auto-généré sous la forme `picoclaw-mqtt-{agent_id}-{8 hex}` ; fixe pour la durée du processus, réutilisé à la reconnexion |
| `keep_alive` | `settings` | Non | `60` | Intervalle keepalive MQTT en secondes |
| `qos` | `settings` | Non | `0` | Niveau QoS pour la publication et l'abonnement : `0`, `1` ou `2` |
### Variables d'environnement
| Variable | Champ |
|----------|-------|
| `PICOCLAW_CHANNELS_MQTT_BROKER` | `broker` |
| `PICOCLAW_CHANNELS_MQTT_AGENT_ID` | `agent_id` |
| `PICOCLAW_CHANNELS_MQTT_TOPIC_PREFIX` | `topic_prefix` |
| `PICOCLAW_CHANNELS_MQTT_USERNAME` | `username` |
| `PICOCLAW_CHANNELS_MQTT_PASSWORD` | `password` |
| `PICOCLAW_CHANNELS_MQTT_CLIENT_ID` | `client_id` |
| `PICOCLAW_CHANNELS_MQTT_KEEP_ALIVE` | `keep_alive` |
| `PICOCLAW_CHANNELS_MQTT_QOS` | `qos` |
---
## 🔄 Reconnexion
PicoClaw se reconnecte automatiquement au broker en cas de perte de connexion, avec un intervalle de 5 secondes. L'abonnement est rétabli automatiquement. L'ID client côté broker reste identique à chaque reconnexion.
---
## ⚠️ Remarques
- **TLS** : SSL/TLS est supporté (URL broker en `ssl://`). La vérification du certificat est désactivée par défaut.
- **Réponses en streaming** : Les réponses en streaming envoient plusieurs messages vers le topic de réponse ; les concaténer dans l'ordre pour obtenir la réponse complète.
- **client_id vs ID de session** : Le `client_id` dans le chemin du topic est défini par votre application cliente. Il est distinct de l'ID client paho utilisé par PicoClaw pour se connecter au broker.
- **Instances multiples** : Si plusieurs instances PicoClaw utilisent le même `agent_id` sur le même broker, définir des `client_id` distincts pour éviter les conflits.

View file

@ -0,0 +1,140 @@
# 📡 MQTT チャンネル
PicoClaw は任意の MQTT クライアントをメッセージチャンネルとして使用できます。デバイスやサービスがブローカーにリクエストをパブリッシュし、PicoClaw がサブスクライブして処理し、レスポンスをパブリッシュして返します。
## 🚀 クイックスタート
**1. `~/.picoclaw/config.json` にチャンネルを追加:**
```json
{
"channel_list": {
"mqtt": {
"enabled": true,
"type": "mqtt",
"settings": {
"broker": "tcp://localhost:1883",
"agent_id": "assistant"
}
}
}
}
```
**2. ゲートウェイを起動:**
```bash
picoclaw gateway
```
**3. 任意の MQTT クライアントからメッセージを送信:**
```bash
mosquitto_pub -t "/picoclaw/assistant/device1/request" \
-m '{"text": "CPU使用率を確認してください"}'
```
**4. レスポンスを受信するためにサブスクライブ:**
```bash
mosquitto_sub -t "/picoclaw/assistant/device1/response"
```
---
## 📨 トピック構造
```
{prefix}/{agent_id}/{client_id}/request # クライアント → PicoClaw
{prefix}/{agent_id}/{client_id}/response # PicoClaw → クライアント
```
| セグメント | 説明 |
|-----------|------|
| `prefix` | トピックのプレフィックス。サーバー側で設定。デフォルト:`/picoclaw` |
| `agent_id` | PicoClaw インスタンスの識別子。`agent_id` フィールドに設定 |
| `client_id` | クライアントが定義するセッション識別子。デバイスごとに同一の ID を使用するとコンテキストが維持される |
### メッセージペイロードJSON
```json
{ "text": "メッセージ内容" }
```
---
## ⚙️ 設定
### config.json
```json
{
"channel_list": {
"mqtt": {
"enabled": true,
"type": "mqtt",
"settings": {
"broker": "ssl://your-broker:8883",
"agent_id": "assistant",
"topic_prefix": "/picoclaw",
"client_id": "",
"keep_alive": 60,
"qos": 0
}
}
}
}
```
### .security.yml認証情報
ユーザー名とパスワードは `config.json` ではなく `~/.picoclaw/.security.yml` に保存します:
```yaml
channel_list:
mqtt:
settings:
username: your_username
password: your_password
```
### 設定フィールド
| フィールド | 場所 | 必須 | デフォルト | 説明 |
|-----------|------|------|-----------|------|
| `broker` | `settings` | はい | — | MQTT ブローカー URL。例`tcp://host:1883``ssl://host:8883` |
| `agent_id` | `settings` | はい | — | エージェント識別子。トピックパスの一部として使用される |
| `topic_prefix` | `settings` | いいえ | `/picoclaw` | トピックの名前空間プレフィックス |
| `username` | `.security.yml` | いいえ | — | ブローカー認証のユーザー名 |
| `password` | `.security.yml` | いいえ | — | ブローカー認証のパスワード |
| `client_id` | `settings` | いいえ | 自動生成 | ブローカーに送信する paho クライアント ID。未設定の場合 `picoclaw-mqtt-{agent_id}-{8桁hex}` で自動生成。プロセスの生存期間中は固定され、再接続時も同じ ID を使用 |
| `keep_alive` | `settings` | いいえ | `60` | MQTT キープアライブ間隔(秒) |
| `qos` | `settings` | いいえ | `0` | パブリッシュおよびサブスクライブの QoS レベル:`0``1``2` |
### 環境変数
| 変数 | フィールド |
|------|----------|
| `PICOCLAW_CHANNELS_MQTT_BROKER` | `broker` |
| `PICOCLAW_CHANNELS_MQTT_AGENT_ID` | `agent_id` |
| `PICOCLAW_CHANNELS_MQTT_TOPIC_PREFIX` | `topic_prefix` |
| `PICOCLAW_CHANNELS_MQTT_USERNAME` | `username` |
| `PICOCLAW_CHANNELS_MQTT_PASSWORD` | `password` |
| `PICOCLAW_CHANNELS_MQTT_CLIENT_ID` | `client_id` |
| `PICOCLAW_CHANNELS_MQTT_KEEP_ALIVE` | `keep_alive` |
| `PICOCLAW_CHANNELS_MQTT_QOS` | `qos` |
---
## 🔄 再接続
接続が切断された場合、PicoClaw は 5 秒間隔で自動的にブローカーに再接続します。再接続後はサブスクリプションも自動的に再確立されます。再接続時はブローカー側のクライアント ID が同一に保たれるため、ブローカーは同じセッションとして認識します。
---
## ⚠️ 注意事項
- **TLS**SSL/TLS をサポートしています(ブローカー URL に `ssl://` を使用)。デフォルトでは証明書検証をスキップします。
- **ストリーミングレスポンス**:ストリーミング出力時はレスポンストピックに複数のメッセージが送信されます。順番に結合すると完全なレスポンスになります。
- **client_id とセッション ID の違い**:トピックパスの `client_id` はクライアントアプリケーションが設定するセッション識別子です。PicoClaw がブローカーへの接続に使用する paho クライアント ID とは別の概念です。
- **複数インスタンス**:同じ `agent_id` で複数の PicoClaw インスタンスを同一ブローカーに接続する場合、ブローカーレベルの競合を避けるために各インスタンスに異なる `client_id` を設定してください。

View file

@ -0,0 +1,142 @@
# 📡 MQTT Channel
PicoClaw supports any MQTT client as a chat channel. Devices or services publish requests to a broker; PicoClaw subscribes, processes them, and publishes responses back.
## 🚀 Quick Start
**1. Add the channel to `~/.picoclaw/config.json`:**
```json
{
"channel_list": {
"mqtt": {
"enabled": true,
"type": "mqtt",
"settings": {
"broker": "tcp://localhost:1883",
"agent_id": "assistant"
}
}
}
}
```
**2. Start the gateway:**
```bash
picoclaw gateway
```
**3. Send a message from any MQTT client:**
```bash
mosquitto_pub -t "/picoclaw/assistant/device1/request" \
-m '{"text": "What is the CPU usage?"}'
```
**4. Subscribe to receive the response:**
```bash
mosquitto_sub -t "/picoclaw/assistant/device1/response"
```
---
## 📨 Topic Structure
```
{prefix}/{agent_id}/{client_id}/request # Client → PicoClaw
{prefix}/{agent_id}/{client_id}/response # PicoClaw → Client
```
| Segment | Description |
|---------|-------------|
| `prefix` | Topic prefix, configured server-side. Default: `/picoclaw` |
| `agent_id` | PicoClaw instance identifier, set in `agent_id` config field |
| `client_id` | Client-defined session identifier — use a stable ID per device to maintain conversation context |
### Message Payload (JSON)
```json
{ "text": "your message here" }
```
---
## ⚙️ Configuration
### config.json
```json
{
"channel_list": {
"mqtt": {
"enabled": true,
"type": "mqtt",
"settings": {
"broker": "ssl://your-broker:8883",
"agent_id": "assistant",
"topic_prefix": "/picoclaw",
"client_id": "",
"keep_alive": 60,
"qos": 0
}
}
}
}
```
### .security.yml (credentials)
Username and password are stored in `~/.picoclaw/.security.yml`, not in `config.json`:
```yaml
channel_list:
mqtt:
settings:
username: your_username
password: your_password
```
### Configuration Fields
| Field | Location | Required | Default | Description |
|-------|----------|----------|---------|-------------|
| `broker` | `settings` | Yes | — | MQTT broker URL, e.g. `tcp://host:1883`, `ssl://host:8883` |
| `agent_id` | `settings` | Yes | — | Agent identifier, used as part of the topic path |
| `topic_prefix` | `settings` | No | `/picoclaw` | Topic namespace prefix |
| `username` | `.security.yml` | No | — | Broker authentication username |
| `password` | `.security.yml` | No | — | Broker authentication password |
| `client_id` | `settings` | No | auto-generated | Paho client ID sent to the broker. Auto-generated as `picoclaw-mqtt-{agent_id}-{8-char hex}` if not set; stays fixed for the process lifetime so reconnects reuse the same ID |
| `keep_alive` | `settings` | No | `60` | MQTT keepalive interval in seconds |
| `qos` | `settings` | No | `0` | QoS level for publish and subscribe: `0`, `1`, or `2` |
### Environment Variables
All fields can be set via environment variables:
| Variable | Field |
|----------|-------|
| `PICOCLAW_CHANNELS_MQTT_BROKER` | `broker` |
| `PICOCLAW_CHANNELS_MQTT_AGENT_ID` | `agent_id` |
| `PICOCLAW_CHANNELS_MQTT_TOPIC_PREFIX` | `topic_prefix` |
| `PICOCLAW_CHANNELS_MQTT_USERNAME` | `username` |
| `PICOCLAW_CHANNELS_MQTT_PASSWORD` | `password` |
| `PICOCLAW_CHANNELS_MQTT_CLIENT_ID` | `client_id` |
| `PICOCLAW_CHANNELS_MQTT_KEEP_ALIVE` | `keep_alive` |
| `PICOCLAW_CHANNELS_MQTT_QOS` | `qos` |
---
## 🔄 Reconnection
PicoClaw automatically reconnects to the broker if the connection is lost, with a 5-second retry interval. On reconnect, the subscription is re-established automatically. The broker-side client ID stays the same across reconnects so the broker correctly identifies it as the same session.
---
## ⚠️ Notes
- **TLS**: SSL/TLS is supported (`ssl://` broker URL). Certificate verification is skipped by default.
- **Streaming**: Streaming responses send multiple messages to the response topic; concatenate them in order.
- **client_id vs session ID**: The `client_id` in the topic path is set by your client application and identifies the conversation session. It is separate from the broker-level client ID used by PicoClaw's paho connection.
- **Multiple instances**: If you run multiple PicoClaw instances against the same broker with the same `agent_id`, set distinct `client_id` values to avoid broker-level conflicts.

View file

@ -0,0 +1,140 @@
# 📡 Canal MQTT
O PicoClaw suporta qualquer cliente MQTT como canal de mensagens. Dispositivos ou serviços publicam requisições para um broker; o PicoClaw assina, processa e publica as respostas de volta.
## 🚀 Início rápido
**1. Adicione o canal ao `~/.picoclaw/config.json`:**
```json
{
"channel_list": {
"mqtt": {
"enabled": true,
"type": "mqtt",
"settings": {
"broker": "tcp://localhost:1883",
"agent_id": "assistant"
}
}
}
}
```
**2. Inicie o gateway:**
```bash
picoclaw gateway
```
**3. Envie uma mensagem de qualquer cliente MQTT:**
```bash
mosquitto_pub -t "/picoclaw/assistant/device1/request" \
-m '{"text": "Qual é o uso de CPU?"}'
```
**4. Assine para receber a resposta:**
```bash
mosquitto_sub -t "/picoclaw/assistant/device1/response"
```
---
## 📨 Estrutura de tópicos
```
{prefix}/{agent_id}/{client_id}/request # Cliente → PicoClaw
{prefix}/{agent_id}/{client_id}/response # PicoClaw → Cliente
```
| Segmento | Descrição |
|----------|-----------|
| `prefix` | Prefixo do tópico configurado no servidor. Padrão: `/picoclaw` |
| `agent_id` | Identificador da instância do PicoClaw, definido no campo `agent_id` |
| `client_id` | Identificador de sessão definido pelo cliente — use um ID estável por dispositivo para manter o contexto da conversa |
### Payload da mensagem (JSON)
```json
{ "text": "sua mensagem aqui" }
```
---
## ⚙️ Configuração
### config.json
```json
{
"channel_list": {
"mqtt": {
"enabled": true,
"type": "mqtt",
"settings": {
"broker": "ssl://seu-broker:8883",
"agent_id": "assistant",
"topic_prefix": "/picoclaw",
"client_id": "",
"keep_alive": 60,
"qos": 0
}
}
}
}
```
### .security.yml (credenciais)
O nome de usuário e a senha são armazenados em `~/.picoclaw/.security.yml`, não no `config.json`:
```yaml
channel_list:
mqtt:
settings:
username: seu_usuario
password: sua_senha
```
### Campos de configuração
| Campo | Local | Obrigatório | Padrão | Descrição |
|-------|-------|-------------|--------|-----------|
| `broker` | `settings` | Sim | — | URL do broker MQTT, ex. `tcp://host:1883`, `ssl://host:8883` |
| `agent_id` | `settings` | Sim | — | Identificador do agente, usado como parte do caminho do tópico |
| `topic_prefix` | `settings` | Não | `/picoclaw` | Prefixo do namespace dos tópicos |
| `username` | `.security.yml` | Não | — | Nome de usuário para autenticação no broker |
| `password` | `.security.yml` | Não | — | Senha para autenticação no broker |
| `client_id` | `settings` | Não | gerado automaticamente | ID de cliente paho enviado ao broker. Gerado automaticamente como `picoclaw-mqtt-{agent_id}-{8 hex}` se não definido; fixo durante o tempo de vida do processo e reutilizado nas reconexões |
| `keep_alive` | `settings` | Não | `60` | Intervalo de keepalive MQTT em segundos |
| `qos` | `settings` | Não | `0` | Nível de QoS para publicação e assinatura: `0`, `1` ou `2` |
### Variáveis de ambiente
| Variável | Campo |
|----------|-------|
| `PICOCLAW_CHANNELS_MQTT_BROKER` | `broker` |
| `PICOCLAW_CHANNELS_MQTT_AGENT_ID` | `agent_id` |
| `PICOCLAW_CHANNELS_MQTT_TOPIC_PREFIX` | `topic_prefix` |
| `PICOCLAW_CHANNELS_MQTT_USERNAME` | `username` |
| `PICOCLAW_CHANNELS_MQTT_PASSWORD` | `password` |
| `PICOCLAW_CHANNELS_MQTT_CLIENT_ID` | `client_id` |
| `PICOCLAW_CHANNELS_MQTT_KEEP_ALIVE` | `keep_alive` |
| `PICOCLAW_CHANNELS_MQTT_QOS` | `qos` |
---
## 🔄 Reconexão
O PicoClaw reconecta automaticamente ao broker se a conexão for perdida, com intervalo de 5 segundos. Após a reconexão, a assinatura é restabelecida automaticamente. O ID de cliente no broker permanece o mesmo nas reconexões, permitindo que o broker identifique corretamente a mesma sessão.
---
## ⚠️ Observações
- **TLS**: SSL/TLS é suportado (URL do broker com `ssl://`). A verificação de certificado é ignorada por padrão.
- **Respostas em streaming**: Respostas em streaming enviam múltiplas mensagens para o tópico de resposta; concatene-as na ordem recebida para obter a resposta completa.
- **client_id vs ID de sessão**: O `client_id` no caminho do tópico é definido pela sua aplicação cliente e identifica a sessão. É separado do ID de cliente paho usado pelo PicoClaw para se conectar ao broker.
- **Múltiplas instâncias**: Se várias instâncias do PicoClaw usarem o mesmo `agent_id` no mesmo broker, defina `client_id` distintos para evitar conflitos no nível do broker.

View file

@ -0,0 +1,140 @@
# 📡 Kênh MQTT
PicoClaw hỗ trợ bất kỳ client MQTT nào làm kênh nhắn tin. Thiết bị hoặc dịch vụ publish yêu cầu lên broker; PicoClaw subscribe, xử lý và publish phản hồi trở lại.
## 🚀 Bắt đầu nhanh
**1. Thêm kênh vào `~/.picoclaw/config.json`:**
```json
{
"channel_list": {
"mqtt": {
"enabled": true,
"type": "mqtt",
"settings": {
"broker": "tcp://localhost:1883",
"agent_id": "assistant"
}
}
}
}
```
**2. Khởi động gateway:**
```bash
picoclaw gateway
```
**3. Gửi tin nhắn từ bất kỳ client MQTT nào:**
```bash
mosquitto_pub -t "/picoclaw/assistant/device1/request" \
-m '{"text": "CPU đang dùng bao nhiêu phần trăm?"}'
```
**4. Subscribe để nhận phản hồi:**
```bash
mosquitto_sub -t "/picoclaw/assistant/device1/response"
```
---
## 📨 Cấu trúc topic
```
{prefix}/{agent_id}/{client_id}/request # Client → PicoClaw
{prefix}/{agent_id}/{client_id}/response # PicoClaw → Client
```
| Phân đoạn | Mô tả |
|-----------|-------|
| `prefix` | Tiền tố topic, cấu hình phía server. Mặc định: `/picoclaw` |
| `agent_id` | Định danh instance PicoClaw, đặt trong trường `agent_id` |
| `client_id` | Định danh phiên do client xác định — dùng ID ổn định cho mỗi thiết bị để duy trì ngữ cảnh hội thoại |
### Payload tin nhắn (JSON)
```json
{ "text": "nội dung tin nhắn" }
```
---
## ⚙️ Cấu hình
### config.json
```json
{
"channel_list": {
"mqtt": {
"enabled": true,
"type": "mqtt",
"settings": {
"broker": "ssl://your-broker:8883",
"agent_id": "assistant",
"topic_prefix": "/picoclaw",
"client_id": "",
"keep_alive": 60,
"qos": 0
}
}
}
}
```
### .security.yml (thông tin xác thực)
Tên người dùng và mật khẩu được lưu trong `~/.picoclaw/.security.yml`, không phải trong `config.json`:
```yaml
channel_list:
mqtt:
settings:
username: ten_nguoi_dung
password: mat_khau
```
### Các trường cấu hình
| Trường | Vị trí | Bắt buộc | Mặc định | Mô tả |
|--------|--------|----------|----------|-------|
| `broker` | `settings` | Có | — | URL của MQTT broker, ví dụ `tcp://host:1883`, `ssl://host:8883` |
| `agent_id` | `settings` | Có | — | Định danh agent, dùng làm một phần của đường dẫn topic |
| `topic_prefix` | `settings` | Không | `/picoclaw` | Tiền tố không gian tên topic |
| `username` | `.security.yml` | Không | — | Tên người dùng xác thực với broker |
| `password` | `.security.yml` | Không | — | Mật khẩu xác thực với broker |
| `client_id` | `settings` | Không | tự động tạo | Client ID paho gửi đến broker. Tự động tạo dạng `picoclaw-mqtt-{agent_id}-{8 hex}` nếu không đặt; cố định trong suốt vòng đời tiến trình, tái sử dụng khi kết nối lại |
| `keep_alive` | `settings` | Không | `60` | Khoảng thời gian keepalive MQTT (giây) |
| `qos` | `settings` | Không | `0` | Mức QoS cho publish và subscribe: `0`, `1` hoặc `2` |
### Biến môi trường
| Biến | Trường |
|------|--------|
| `PICOCLAW_CHANNELS_MQTT_BROKER` | `broker` |
| `PICOCLAW_CHANNELS_MQTT_AGENT_ID` | `agent_id` |
| `PICOCLAW_CHANNELS_MQTT_TOPIC_PREFIX` | `topic_prefix` |
| `PICOCLAW_CHANNELS_MQTT_USERNAME` | `username` |
| `PICOCLAW_CHANNELS_MQTT_PASSWORD` | `password` |
| `PICOCLAW_CHANNELS_MQTT_CLIENT_ID` | `client_id` |
| `PICOCLAW_CHANNELS_MQTT_KEEP_ALIVE` | `keep_alive` |
| `PICOCLAW_CHANNELS_MQTT_QOS` | `qos` |
---
## 🔄 Kết nối lại
PicoClaw tự động kết nối lại với broker nếu mất kết nối, với khoảng thời gian thử lại 5 giây. Sau khi kết nối lại, subscription được tái thiết lập tự động. Client ID phía broker giữ nguyên qua các lần kết nối lại, giúp broker nhận diện chính xác cùng một phiên.
---
## ⚠️ Lưu ý
- **TLS**: Hỗ trợ SSL/TLS (URL broker dùng `ssl://`). Mặc định bỏ qua xác minh chứng chỉ.
- **Phản hồi streaming**: Phản hồi streaming gửi nhiều tin nhắn đến topic response; ghép nối chúng theo thứ tự để có phản hồi đầy đủ.
- **client_id và ID phiên**: `client_id` trong đường dẫn topic được đặt bởi ứng dụng client của bạn và xác định phiên hội thoại. Nó khác với client ID paho mà PicoClaw dùng để kết nối broker.
- **Nhiều instance**: Nếu nhiều instance PicoClaw dùng cùng `agent_id` trên cùng broker, hãy đặt `client_id` riêng biệt cho từng instance để tránh xung đột ở tầng broker.

View file

@ -0,0 +1,142 @@
# 📡 MQTT 渠道
PicoClaw 支持将任意 MQTT 客户端作为消息渠道。设备或服务向 Broker 发布请求PicoClaw 订阅后处理并将响应发布回去。
## 🚀 快速开始
**1. 在 `~/.picoclaw/config.json` 中添加渠道:**
```json
{
"channel_list": {
"mqtt": {
"enabled": true,
"type": "mqtt",
"settings": {
"broker": "tcp://localhost:1883",
"agent_id": "assistant"
}
}
}
}
```
**2. 启动网关:**
```bash
picoclaw gateway
```
**3. 用任意 MQTT 客户端发送消息:**
```bash
mosquitto_pub -t "/picoclaw/assistant/device1/request" \
-m '{"text": "查一下CPU使用率"}'
```
**4. 订阅响应:**
```bash
mosquitto_sub -t "/picoclaw/assistant/device1/response"
```
---
## 📨 Topic 结构
```
{prefix}/{agent_id}/{client_id}/request # 客户端 → PicoClaw
{prefix}/{agent_id}/{client_id}/response # PicoClaw → 客户端
```
| 段 | 说明 |
|----|------|
| `prefix` | Topic 前缀,由服务端配置,默认 `/picoclaw` |
| `agent_id` | PicoClaw 实例标识,对应配置中的 `agent_id` 字段 |
| `client_id` | 客户端自定义会话标识——同一设备保持相同 ID 可维持上下文连续性 |
### 消息体JSON
```json
{ "text": "你的消息内容" }
```
---
## ⚙️ 配置说明
### config.json
```json
{
"channel_list": {
"mqtt": {
"enabled": true,
"type": "mqtt",
"settings": {
"broker": "ssl://your-broker:8883",
"agent_id": "assistant",
"topic_prefix": "/picoclaw",
"client_id": "",
"keep_alive": 60,
"qos": 0
}
}
}
}
```
### .security.yml用户名和密码
用户名和密码存储于 `~/.picoclaw/.security.yml`,不写入 `config.json`
```yaml
channel_list:
mqtt:
settings:
username: your_username
password: your_password
```
### 字段说明
| 字段 | 位置 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| `broker` | `settings` | 是 | — | MQTT Broker 地址,如 `tcp://host:1883``ssl://host:8883` |
| `agent_id` | `settings` | 是 | — | Agent 标识,作为 topic 路径的一部分 |
| `topic_prefix` | `settings` | 否 | `/picoclaw` | Topic 命名空间前缀 |
| `username` | `.security.yml` | 否 | — | Broker 认证用户名 |
| `password` | `.security.yml` | 否 | — | Broker 认证密码 |
| `client_id` | `settings` | 否 | 自动生成 | 发送给 Broker 的 paho 客户端 ID。未配置时自动生成为 `picoclaw-mqtt-{agent_id}-{8位hex}`,进程生命周期内固定不变,断线重连时复用同一 ID |
| `keep_alive` | `settings` | 否 | `60` | MQTT 心跳间隔(秒) |
| `qos` | `settings` | 否 | `0` | 发布和订阅的 QoS 级别:`0``1``2` |
### 环境变量
所有字段均可通过环境变量配置:
| 环境变量 | 对应字段 |
|----------|----------|
| `PICOCLAW_CHANNELS_MQTT_BROKER` | `broker` |
| `PICOCLAW_CHANNELS_MQTT_AGENT_ID` | `agent_id` |
| `PICOCLAW_CHANNELS_MQTT_TOPIC_PREFIX` | `topic_prefix` |
| `PICOCLAW_CHANNELS_MQTT_USERNAME` | `username` |
| `PICOCLAW_CHANNELS_MQTT_PASSWORD` | `password` |
| `PICOCLAW_CHANNELS_MQTT_CLIENT_ID` | `client_id` |
| `PICOCLAW_CHANNELS_MQTT_KEEP_ALIVE` | `keep_alive` |
| `PICOCLAW_CHANNELS_MQTT_QOS` | `qos` |
---
## 🔄 断线重连
连接断开后 PicoClaw 会自动以 5 秒间隔重连 Broker重连成功后自动重新订阅。断线重连时复用相同的 Broker 客户端 IDBroker 能正确识别为同一连接。
---
## ⚠️ 注意事项
- **TLS**:支持 SSL/TLSBroker 地址使用 `ssl://`),默认跳过证书验证。
- **流式响应**:流式输出时会向 response topic 发送多条消息,客户端按顺序拼接即为完整回复。
- **client_id 与会话 ID 的区别**topic 路径中的 `client_id` 由客户端应用自行设置,用于区分会话;它与 PicoClaw paho 连接 Broker 时使用的客户端 ID 是两个独立的概念。
- **多实例部署**:若多个 PicoClaw 实例使用相同 `agent_id` 连接同一 Broker需为每个实例配置不同的 `client_id` 以避免 Broker 层面的冲突。

View file

@ -4,7 +4,7 @@
## 💬 Applications de Chat
Communiquez avec votre PicoClaw via Telegram, Discord, WhatsApp, Matrix, QQ, DingTalk, LINE, WeCom, Feishu, Slack, IRC, OneBot ou MaixCam.
Communiquez avec votre PicoClaw via Telegram, Discord, WhatsApp, Matrix, QQ, DingTalk, LINE, WeCom, Feishu, Slack, IRC, OneBot, MQTT ou MaixCam.
> **Note** : Tous les canaux basés sur les webhooks (LINE, WeCom, etc.) sont servis sur un seul serveur HTTP Gateway partagé (`gateway.host`:`gateway.port`, par défaut `127.0.0.1:18790`). Il n'y a pas de ports par canal à configurer. Note : Feishu utilise le mode WebSocket/SDK et n'utilise pas le serveur HTTP webhook partagé.
@ -23,6 +23,7 @@ Communiquez avec votre PicoClaw via Telegram, Discord, WhatsApp, Matrix, QQ, Din
| **Feishu (飞书)** | ⭐⭐⭐ Avancé | Collaboration entreprise, fonctionnalités riches | [Documentation](../channels/feishu/README.fr.md) |
| **IRC** | ⭐⭐ Moyen | Serveur + configuration TLS | [Documentation](#irc) |
| **OneBot** | ⭐⭐ Moyen | Compatible NapCat/Go-CQHTTP, écosystème communautaire | [Documentation](../channels/onebot/README.fr.md) |
| **MQTT** | ⭐ Facile | N'importe quel client MQTT via broker pub/sub | [Documentation](../channels/mqtt/README.fr.md) |
| **MaixCam** | ⭐ Facile | Canal d'intégration matérielle pour caméras AI Sipeed | [Documentation](../channels/maixcam/README.fr.md) |
| **Pico** | ⭐ Facile | Canal protocole natif PicoClaw | |
@ -681,3 +682,67 @@ picoclaw gateway
```
</details>
<a id="mqtt"></a>
<details>
<summary><b>MQTT</b></summary>
N'importe quel client MQTT peut communiquer avec PicoClaw via un broker. Les appareils ou services publient des requêtes vers le broker ; PicoClaw s'abonne, les traite et publie les réponses en retour.
**1. Configurer**
```json
{
"channel_list": {
"mqtt": {
"enabled": true,
"type": "mqtt",
"settings": {
"broker": "ssl://votre-broker:8883",
"agent_id": "assistant",
"topic_prefix": "/picoclaw",
"keep_alive": 60,
"qos": 0
}
}
}
}
```
Nom d'utilisateur et mot de passe dans `~/.picoclaw/.security.yml` :
```yaml
channel_list:
mqtt:
settings:
username: votre_utilisateur
password: votre_mot_de_passe
```
**Format des topics**
```
{prefix}/{agent_id}/{client_id}/request # Client → PicoClaw
{prefix}/{agent_id}/{client_id}/response # PicoClaw → Client
```
Le `client_id` est défini par votre application cliente pour identifier les appareils ou sessions.
**2. Lancer**
```bash
picoclaw gateway
```
**3. Tester**
```bash
mosquitto_pub -t "/picoclaw/assistant/device1/request" \
-m '{"text": "Bonjour"}'
mosquitto_sub -t "/picoclaw/assistant/device1/response"
```
Pour les options complètes, voir [Documentation du canal MQTT](../channels/mqtt/README.fr.md).
</details>

View file

@ -25,6 +25,7 @@ PicoClaw は複数のチャットプラットフォームをサポートして
| **Feishu (飛書)** | ⭐⭐⭐ やや難 | エンタープライズコラボレーション、機能豊富 | [ドキュメント](../channels/feishu/README.ja.md) |
| **IRC** | ⭐⭐ 中程度 | サーバー + TLS 設定 | [ドキュメント](#irc) |
| **OneBot** | ⭐⭐ 中程度 | NapCat/Go-CQHTTP 互換、コミュニティエコシステム充実 | [ドキュメント](../channels/onebot/README.ja.md) |
| **MQTT** | ⭐ 簡単 | ブローカー経由で任意の MQTT クライアントと通信 | [ドキュメント](../channels/mqtt/README.ja.md) |
| **MaixCam** | ⭐ 簡単 | Sipeed AI カメラハードウェア統合チャネル | [ドキュメント](../channels/maixcam/README.ja.md) |
| **Pico** | ⭐ 簡単 | PicoClaw ネイティブプロトコルチャネル | |
@ -670,3 +671,67 @@ picoclaw gateway
```
</details>
<a id="mqtt"></a>
<details>
<summary><b>MQTT</b></summary>
任意の MQTT クライアントがブローカーを介して PicoClaw と通信できます。デバイスやサービスがブローカーにリクエストをパブリッシュし、PicoClaw がサブスクライブして処理し、レスポンスをパブリッシュして返します。
**1. 設定**
```json
{
"channel_list": {
"mqtt": {
"enabled": true,
"type": "mqtt",
"settings": {
"broker": "ssl://your-broker:8883",
"agent_id": "assistant",
"topic_prefix": "/picoclaw",
"keep_alive": 60,
"qos": 0
}
}
}
}
```
ユーザー名とパスワードは `~/.picoclaw/.security.yml` に記載します:
```yaml
channel_list:
mqtt:
settings:
username: your_username
password: your_password
```
**トピック形式**
```
{prefix}/{agent_id}/{client_id}/request # クライアント → PicoClaw
{prefix}/{agent_id}/{client_id}/response # PicoClaw → クライアント
```
`client_id` はクライアントアプリケーションがデバイスやセッションを識別するために設定します。
**2. 起動**
```bash
picoclaw gateway
```
**3. テスト**
```bash
mosquitto_pub -t "/picoclaw/assistant/device1/request" \
-m '{"text": "こんにちは"}'
mosquitto_sub -t "/picoclaw/assistant/device1/response"
```
完全な設定オプションは [MQTT チャンネルドキュメント](../channels/mqtt/README.ja.md) を参照してください。
</details>

View file

@ -4,7 +4,7 @@
## 💬 Chat Apps
Talk to your picoclaw through Telegram, Discord, WhatsApp, Matrix, QQ, DingTalk, LINE, WeCom, Feishu, Slack, IRC, OneBot, MaixCam, or Pico (native protocol)
Talk to your picoclaw through Telegram, Discord, WhatsApp, Matrix, QQ, DingTalk, LINE, WeCom, Feishu, Slack, IRC, OneBot, MQTT, MaixCam, or Pico (native protocol)
> **Note**: Channels that rely on HTTP callbacks share a single Gateway HTTP server (`gateway.host`:`gateway.port`, default `127.0.0.1:18790`). Socket/stream-based channels such as Feishu, DingTalk, and WeCom do not rely on the shared webhook server for inbound delivery.
@ -23,6 +23,7 @@ Talk to your picoclaw through Telegram, Discord, WhatsApp, Matrix, QQ, DingTalk,
| **Feishu (飞书)** | ⭐⭐⭐ Advanced | Enterprise collaboration, feature-rich | [Docs](../channels/feishu/README.md) |
| **IRC** | ⭐⭐ Medium | Server + TLS configuration | [Docs](#irc) |
| **OneBot** | ⭐⭐ Medium | NapCat/Go-CQHTTP compatible, community ecosystem | [Docs](../channels/onebot/README.md) |
| **MQTT** | ⭐ Easy | Any MQTT client via broker pub/sub | [Docs](../channels/mqtt/README.md) |
| **MaixCam** | ⭐ Easy | Hardware integration channel for Sipeed AI cameras | [Docs](../channels/maixcam/README.md) |
| **Pico** | ⭐ Easy | Native PicoClaw protocol channel | |
@ -587,3 +588,69 @@ picoclaw gateway
```
</details>
<a id="mqtt"></a>
<details>
<summary><b>MQTT</b></summary>
Any MQTT client can communicate with PicoClaw via a broker. Devices or services publish requests to the broker; PicoClaw subscribes, processes them, and publishes responses back.
**1. Configure**
```json
{
"channel_list": {
"mqtt": {
"enabled": true,
"type": "mqtt",
"settings": {
"broker": "ssl://your-broker:8883",
"agent_id": "assistant",
"topic_prefix": "/picoclaw",
"keep_alive": 60,
"qos": 0
}
}
}
}
```
Username and password go in `~/.picoclaw/.security.yml`:
```yaml
channel_list:
mqtt:
settings:
username: your_username
password: your_password
```
**Topic format**
```
{prefix}/{agent_id}/{client_id}/request # Client → PicoClaw
{prefix}/{agent_id}/{client_id}/response # PicoClaw → Client
```
`client_id` is set by your client application to identify different devices or sessions.
**2. Run**
```bash
picoclaw gateway
```
**3. Test**
```bash
# Send a message
mosquitto_pub -t "/picoclaw/assistant/device1/request" \
-m '{"text": "Hello"}'
# Subscribe to responses
mosquitto_sub -t "/picoclaw/assistant/device1/response"
```
For full configuration options see [MQTT Channel Docs](../channels/mqtt/README.md).
</details>

View file

@ -4,7 +4,7 @@
## 💬 Aplikasi Sembang
Berbual dengan picoclaw anda melalui Telegram, Discord, WhatsApp, Matrix, QQ, DingTalk, LINE, WeCom, Feishu, Slack, IRC, OneBot, MaixCam, atau Pico (protokol asli)
Berbual dengan picoclaw anda melalui Telegram, Discord, WhatsApp, Matrix, QQ, DingTalk, LINE, WeCom, Feishu, Slack, IRC, OneBot, MQTT, MaixCam, atau Pico (protokol asli)
> **Nota**: Semua saluran berasaskan webhook (LINE, WeCom, dan sebagainya) diservis pada satu pelayan HTTP Gateway yang dikongsi (`gateway.host`:`gateway.port`, lalai `127.0.0.1:18790`). Tiada port khusus per saluran untuk dikonfigurasikan. Nota: Feishu menggunakan mod WebSocket/SDK dan tidak menggunakan pelayan HTTP webhook yang dikongsi.
@ -22,6 +22,7 @@ Berbual dengan picoclaw anda melalui Telegram, Discord, WhatsApp, Matrix, QQ, Di
| **Slack** | Sederhana (Bot token + App token) |
| **IRC** | Sederhana (pelayan + konfigurasi TLS) |
| **OneBot** | Sederhana (QQ melalui protokol OneBot) |
| **MQTT** | Mudah (broker + agent_id) |
| **MaixCam** | Mudah (integrasi perkakasan Sipeed) |
| **Pico** | Protokol PicoClaw asli |
@ -445,3 +446,67 @@ picoclaw gateway
> **Nota**: WeCom AI Bot menggunakan protokol streaming pull — tiada isu timeout balasan. Tugasan panjang (>30 saat) akan bertukar secara automatik kepada penghantaran push `response_url`.
</details>
<a id="mqtt"></a>
<details>
<summary><b>MQTT</b></summary>
Mana-mana client MQTT boleh berkomunikasi dengan PicoClaw melalui broker. Peranti atau perkhidmatan menerbitkan permintaan ke broker; PicoClaw melanggan, memproses dan menerbitkan respons kembali.
**1. Konfigurasi**
```json
{
"channel_list": {
"mqtt": {
"enabled": true,
"type": "mqtt",
"settings": {
"broker": "ssl://your-broker:8883",
"agent_id": "assistant",
"topic_prefix": "/picoclaw",
"keep_alive": 60,
"qos": 0
}
}
}
}
```
Nama pengguna dan kata laluan dalam `~/.picoclaw/.security.yml`:
```yaml
channel_list:
mqtt:
settings:
username: nama_pengguna
password: kata_laluan
```
**Format topik**
```
{prefix}/{agent_id}/{client_id}/request # Client → PicoClaw
{prefix}/{agent_id}/{client_id}/response # PicoClaw → Client
```
`client_id` ditetapkan oleh aplikasi client anda untuk mengenal pasti peranti atau sesi.
**2. Jalankan**
```bash
picoclaw gateway
```
**3. Uji**
```bash
mosquitto_pub -t "/picoclaw/assistant/device1/request" \
-m '{"text": "Helo"}'
mosquitto_sub -t "/picoclaw/assistant/device1/response"
```
Untuk semua pilihan konfigurasi, lihat [Dokumentasi Saluran MQTT](../channels/mqtt/README.md).
</details>

View file

@ -4,7 +4,7 @@
## 💬 Aplicativos de Chat
Converse com seu picoclaw através do Telegram, Discord, WhatsApp, Matrix, QQ, DingTalk, LINE, WeCom, Feishu, Slack, IRC, OneBot ou MaixCam
Converse com seu picoclaw através do Telegram, Discord, WhatsApp, Matrix, QQ, DingTalk, LINE, WeCom, Feishu, Slack, IRC, OneBot, MQTT ou MaixCam
> **Nota**: Todos os canais baseados em webhook (LINE, WeCom, etc.) são servidos em um único servidor HTTP Gateway compartilhado (`gateway.host`:`gateway.port`, padrão `127.0.0.1:18790`). Não há portas por canal para configurar. Nota: Feishu usa o modo WebSocket/SDK e não utiliza o servidor HTTP webhook compartilhado.
@ -23,6 +23,7 @@ Converse com seu picoclaw através do Telegram, Discord, WhatsApp, Matrix, QQ, D
| **Feishu (飞书)** | ⭐⭐⭐ Avançado | Colaboração empresarial, rico em recursos | [Documentação](../channels/feishu/README.pt-br.md) |
| **IRC** | ⭐⭐ Médio | Servidor + configuração TLS | [Documentação](#irc) |
| **OneBot** | ⭐⭐ Médio | Compatível com NapCat/Go-CQHTTP, ecossistema comunitário | [Documentação](../channels/onebot/README.pt-br.md) |
| **MQTT** | ⭐ Fácil | Qualquer cliente MQTT via broker pub/sub | [Documentação](../channels/mqtt/README.pt-br.md) |
| **MaixCam** | ⭐ Fácil | Canal de integração de hardware para câmeras AI Sipeed | [Documentação](../channels/maixcam/README.pt-br.md) |
| **Pico** | ⭐ Fácil | Canal de protocolo nativo PicoClaw | |
@ -695,3 +696,67 @@ picoclaw gateway
```
</details>
<a id="mqtt"></a>
<details>
<summary><b>MQTT</b></summary>
Qualquer cliente MQTT pode se comunicar com o PicoClaw via broker. Dispositivos ou serviços publicam requisições para o broker; o PicoClaw assina, processa e publica as respostas de volta.
**1. Configurar**
```json
{
"channel_list": {
"mqtt": {
"enabled": true,
"type": "mqtt",
"settings": {
"broker": "ssl://seu-broker:8883",
"agent_id": "assistant",
"topic_prefix": "/picoclaw",
"keep_alive": 60,
"qos": 0
}
}
}
}
```
Nome de usuário e senha em `~/.picoclaw/.security.yml`:
```yaml
channel_list:
mqtt:
settings:
username: seu_usuario
password: sua_senha
```
**Formato dos tópicos**
```
{prefix}/{agent_id}/{client_id}/request # Cliente → PicoClaw
{prefix}/{agent_id}/{client_id}/response # PicoClaw → Cliente
```
O `client_id` é definido pela sua aplicação cliente para identificar dispositivos ou sessões.
**2. Iniciar**
```bash
picoclaw gateway
```
**3. Testar**
```bash
mosquitto_pub -t "/picoclaw/assistant/device1/request" \
-m '{"text": "Olá"}'
mosquitto_sub -t "/picoclaw/assistant/device1/response"
```
Para todas as opções de configuração, veja a [Documentação do Canal MQTT](../channels/mqtt/README.pt-br.md).
</details>

View file

@ -4,7 +4,7 @@
## 💬 Ứng Dụng Chat
Trò chuyện với picoclaw của bạn qua Telegram, Discord, WhatsApp, Matrix, QQ, DingTalk, LINE, WeCom, Feishu, Slack, IRC, OneBot hoặc MaixCam
Trò chuyện với picoclaw của bạn qua Telegram, Discord, WhatsApp, Matrix, QQ, DingTalk, LINE, WeCom, Feishu, Slack, IRC, OneBot, MQTT hoặc MaixCam
> **Lưu ý**: Tất cả các kênh dựa trên webhook (LINE, WeCom, v.v.) được phục vụ trên một máy chủ HTTP Gateway chung (`gateway.host`:`gateway.port`, mặc định `127.0.0.1:18790`). Không có port riêng cho từng kênh. Lưu ý: Feishu sử dụng chế độ WebSocket/SDK và không sử dụng máy chủ HTTP webhook chung.
@ -23,6 +23,7 @@ Trò chuyện với picoclaw của bạn qua Telegram, Discord, WhatsApp, Matrix
| **Feishu (飞书)** | ⭐⭐⭐ Nâng cao | Cộng tác doanh nghiệp, nhiều tính năng | [Tài liệu](../channels/feishu/README.vi.md) |
| **IRC** | ⭐⭐ Trung bình | Máy chủ + cấu hình TLS | [Tài liệu](#irc) |
| **OneBot** | ⭐⭐ Trung bình | Tương thích NapCat/Go-CQHTTP, hệ sinh thái cộng đồng | [Tài liệu](../channels/onebot/README.vi.md) |
| **MQTT** | ⭐ Dễ | Bất kỳ client MQTT nào qua broker pub/sub | [Tài liệu](../channels/mqtt/README.vi.md) |
| **MaixCam** | ⭐ Dễ | Kênh tích hợp phần cứng cho camera AI Sipeed | [Tài liệu](../channels/maixcam/README.vi.md) |
| **Pico** | ⭐ Dễ | Kênh giao thức bản địa PicoClaw | |
@ -696,3 +697,67 @@ picoclaw gateway
```
</details>
<a id="mqtt"></a>
<details>
<summary><b>MQTT</b></summary>
Bất kỳ client MQTT nào đều có thể giao tiếp với PicoClaw qua broker. Thiết bị hoặc dịch vụ publish yêu cầu lên broker; PicoClaw subscribe, xử lý và publish phản hồi trở lại.
**1. Cấu hình**
```json
{
"channel_list": {
"mqtt": {
"enabled": true,
"type": "mqtt",
"settings": {
"broker": "ssl://your-broker:8883",
"agent_id": "assistant",
"topic_prefix": "/picoclaw",
"keep_alive": 60,
"qos": 0
}
}
}
}
```
Tên người dùng và mật khẩu trong `~/.picoclaw/.security.yml`:
```yaml
channel_list:
mqtt:
settings:
username: ten_nguoi_dung
password: mat_khau
```
**Định dạng topic**
```
{prefix}/{agent_id}/{client_id}/request # Client → PicoClaw
{prefix}/{agent_id}/{client_id}/response # PicoClaw → Client
```
`client_id` do ứng dụng client đặt để phân biệt thiết bị hoặc phiên.
**2. Khởi động**
```bash
picoclaw gateway
```
**3. Kiểm tra**
```bash
mosquitto_pub -t "/picoclaw/assistant/device1/request" \
-m '{"text": "Xin chào"}'
mosquitto_sub -t "/picoclaw/assistant/device1/response"
```
Xem đầy đủ tùy chọn cấu hình tại [Tài liệu Kênh MQTT](../channels/mqtt/README.vi.md).
</details>

View file

@ -4,7 +4,7 @@
## 💬 聊天应用集成 (Chat Apps)
PicoClaw 支持多种聊天平台,使您的 Agent 能够连接到任何地方。
PicoClaw 支持多种聊天平台,使您的 Agent 能够连接到任何地方,包括 Telegram、Discord、WhatsApp、微信、QQ、钉钉、LINE、企业微信、飞书、Slack、IRC、OneBot、MQTT、MaixCam 等
> **注意**: 依赖 HTTP 回调的渠道共用同一个 Gateway HTTP 服务器(`gateway.host`:`gateway.port`,默认 `127.0.0.1:18790`),无需为每个渠道单独配置端口。飞书、钉钉、企业微信这类 Socket/Stream 模式渠道不依赖共享 webhook 服务器来接收入站消息。
@ -25,6 +25,7 @@ PicoClaw 支持多种聊天平台,使您的 Agent 能够连接到任何地方
| **飞书 (Feishu)** | ⭐⭐⭐ 较难 | 企业级协作,功能丰富 | [查看文档](../channels/feishu/README.zh.md) |
| **IRC** | ⭐⭐ 中等 | 服务器 + TLS 配置 | [查看文档](#irc) |
| **OneBot** | ⭐⭐ 中等 | 兼容 NapCat/Go-CQHTTP社区生态丰富 | [查看文档](../channels/onebot/README.zh.md) |
| **MQTT** | ⭐ 简单 | 任意 MQTT 客户端通过 Broker 收发消息 | [查看文档](../channels/mqtt/README.zh.md) |
| **MaixCam** | ⭐ 简单 | 专为 AI 摄像头设计的硬件集成通道 | [查看文档](../channels/maixcam/README.zh.md) |
| **Pico** | ⭐ 简单 | PicoClaw 原生协议通道 | |
@ -610,3 +611,69 @@ picoclaw gateway
```
</details>
<a id="mqtt"></a>
<details>
<summary><b>MQTT</b></summary>
任意 MQTT 客户端均可通过 Broker 与 PicoClaw 通信。设备或服务向 Broker 发布请求PicoClaw 订阅后处理并将响应发布回去。
**1. 配置**
```json
{
"channel_list": {
"mqtt": {
"enabled": true,
"type": "mqtt",
"settings": {
"broker": "ssl://your-broker:8883",
"agent_id": "assistant",
"topic_prefix": "/picoclaw",
"keep_alive": 60,
"qos": 0
}
}
}
}
```
用户名和密码存储于 `~/.picoclaw/.security.yml`
```yaml
channel_list:
mqtt:
settings:
username: your_username
password: your_password
```
**Topic 格式**
```
{prefix}/{agent_id}/{client_id}/request # 客户端 → PicoClaw
{prefix}/{agent_id}/{client_id}/response # PicoClaw → 客户端
```
`client_id` 由客户端自行指定,用于区分不同设备或会话。
**2. 运行**
```bash
picoclaw gateway
```
**3. 测试**
```bash
# 发送消息
mosquitto_pub -t "/picoclaw/assistant/device1/request" \
-m '{"text": "你好"}'
# 订阅响应
mosquitto_sub -t "/picoclaw/assistant/device1/response"
```
完整配置选项请参考 [MQTT 渠道文档](../channels/mqtt/README.zh.md)。
</details>

View file

@ -89,6 +89,13 @@ channels:
nickserv_password: "your-irc-nickserv-password"
sasl_password: "your-irc-sasl-password"
# Channel Settings (nested format for channels that use settings block)
channel_list:
mqtt:
settings:
username: "your-mqtt-username"
password: "your-mqtt-password"
# Web Tool API Keys
web:
brave:
@ -226,6 +233,19 @@ channels:
- `channels.feishu.app_secret``config.channels.feishu.app_secret`
- etc.
Channels that use a `settings` block (e.g. MQTT) use the `channel_list` key instead:
```yaml
channel_list:
mqtt:
settings:
username: "value"
password: "value"
```
- `channel_list.mqtt.settings.username``config.channel_list.mqtt.settings.username`
- `channel_list.mqtt.settings.password``config.channel_list.mqtt.settings.password`
### Web Tools
**Brave, Tavily, Perplexity:**

1
go.mod
View file

@ -76,6 +76,7 @@ require (
github.com/coder/websocket v1.8.14 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/eclipse/paho.mqtt.golang v1.5.1 // indirect
github.com/elliotchance/orderedmap/v3 v3.1.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect

2
go.sum
View file

@ -95,6 +95,8 @@ github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/eclipse/paho.mqtt.golang v1.5.1 h1:/VSOv3oDLlpqR2Epjn1Q7b2bSTplJIeV2ISgCl2W7nE=
github.com/eclipse/paho.mqtt.golang v1.5.1/go.mod h1:1/yJCneuyOoCOzKSsOTUc0AJfpsItBGWvYpBLimhArU=
github.com/elliotchance/orderedmap/v3 v3.1.0 h1:j4DJ5ObEmMBt/lcwIecKcoRxIQUEnw0L804lXYDt/pg=
github.com/elliotchance/orderedmap/v3 v3.1.0/go.mod h1:G+Hc2RwaZvJMcS4JpGCOyViCnGeKf0bTYCGTO4uhjSo=
github.com/ergochat/irc-go v0.6.0 h1:Y0AGV76aeihJfCtLaQh+OyJKFiKGrYC0VTkeMZ6XW28=

View file

@ -1310,6 +1310,7 @@ make test # Full test suite
| `pkg/channels/whatsapp/` | `"whatsapp"` | — (Bridge mode) |
| `pkg/channels/whatsapp_native/` | `"whatsapp_native"` | — (Native whatsmeow mode) |
| `pkg/channels/maixcam/` | `"maixcam"` | — |
| `pkg/channels/mqtt/` | `"mqtt"` | — |
| `pkg/channels/pico/` | `"pico"` | TypingCapable, PlaceholderCapable, MessageEditor, WebhookHandler |
### A.3 Interface Quick Reference

View file

@ -1308,6 +1308,7 @@ make test # 全量测试
| `pkg/channels/whatsapp/` | `"whatsapp"` | — (Bridge 模式) |
| `pkg/channels/whatsapp_native/` | `"whatsapp_native"` | — (原生 whatsmeow 模式) |
| `pkg/channels/maixcam/` | `"maixcam"` | — |
| `pkg/channels/mqtt/` | `"mqtt"` | — |
| `pkg/channels/pico/` | `"pico"` | TypingCapable, PlaceholderCapable, MessageEditor, WebhookHandler |
### A.3 接口速查表

View file

@ -672,6 +672,8 @@ func (m *Manager) getChannelConfigAndEnabled(channelName string) (*config.Channe
return bc, settings.Token.String() != ""
case *config.VKSettings:
return bc, settings.GroupID != 0 && settings.Token.String() != ""
case *config.MQTTSettings:
return bc, settings.Broker != "" && settings.AgentID != ""
}
return bc, bc.Enabled

View file

@ -102,6 +102,11 @@ func hiddenValues(key string, value map[string]any, ch *config.Channel) {
}
}
value["webhooks"] = webhooks
case "mqtt":
if settings, ok := v.(*config.MQTTSettings); ok {
value["username"] = settings.Username.String()
value["password"] = settings.Password.String()
}
}
}

16
pkg/channels/mqtt/init.go Normal file
View file

@ -0,0 +1,16 @@
package mqtt
import (
"github.com/sipeed/picoclaw/pkg/bus"
"github.com/sipeed/picoclaw/pkg/channels"
"github.com/sipeed/picoclaw/pkg/config"
)
func init() {
channels.RegisterSafeFactory(
config.ChannelMQTT,
func(bc *config.Channel, cfg *config.MQTTSettings, b *bus.MessageBus) (channels.Channel, error) {
return NewMQTTChannel(bc, cfg, b)
},
)
}

255
pkg/channels/mqtt/mqtt.go Normal file
View file

@ -0,0 +1,255 @@
package mqtt
import (
"context"
"crypto/rand"
"crypto/tls"
"encoding/hex"
"encoding/json"
"fmt"
"strings"
"sync"
"time"
pahomqtt "github.com/eclipse/paho.mqtt.golang"
"github.com/sipeed/picoclaw/pkg/bus"
"github.com/sipeed/picoclaw/pkg/channels"
"github.com/sipeed/picoclaw/pkg/config"
"github.com/sipeed/picoclaw/pkg/logger"
)
// mqttPayload is the JSON payload for both inbound and outbound messages.
type mqttPayload struct {
Text string `json:"text"`
}
// MQTTChannel implements the Channel interface for MQTT-based communication.
type MQTTChannel struct {
*channels.BaseChannel
bc *config.Channel
cfg *config.MQTTSettings
client pahomqtt.Client
qos byte
clientID string
}
// NewMQTTChannel creates a new MQTT channel instance.
func NewMQTTChannel(bc *config.Channel, cfg *config.MQTTSettings, b *bus.MessageBus) (*MQTTChannel, error) {
if cfg.Broker == "" {
return nil, fmt.Errorf("mqtt broker is required")
}
if cfg.AgentID == "" {
return nil, fmt.Errorf("mqtt agent_id is required")
}
base := channels.NewBaseChannel("mqtt", cfg, b, bc.AllowFrom,
channels.WithGroupTrigger(bc.GroupTrigger),
channels.WithReasoningChannelID(bc.ReasoningChannelID),
)
mqttClientID := cfg.ClientID
if mqttClientID == "" {
var suffix [4]byte
_, _ = rand.Read(suffix[:])
mqttClientID = fmt.Sprintf("picoclaw-mqtt-%s-%s", cfg.AgentID, hex.EncodeToString(suffix[:]))
}
return &MQTTChannel{
BaseChannel: base,
bc: bc,
cfg: cfg,
qos: byte(cfg.QoS),
clientID: mqttClientID,
}, nil
}
// Start connects to the MQTT broker and begins listening for inbound messages.
func (c *MQTTChannel) Start(ctx context.Context) error {
logger.InfoC("mqtt", "Starting MQTT channel")
keepAlive := c.cfg.KeepAlive
if keepAlive <= 0 {
keepAlive = 60
}
opts := pahomqtt.NewClientOptions()
opts.AddBroker(c.cfg.Broker)
opts.SetClientID(c.clientID)
opts.SetKeepAlive(time.Duration(keepAlive) * time.Second)
opts.SetAutoReconnect(true)
opts.SetConnectRetry(true)
opts.SetConnectRetryInterval(5 * time.Second)
opts.SetTLSConfig(&tls.Config{InsecureSkipVerify: true}) //nolint:gosec
if c.cfg.Username.String() != "" {
opts.SetUsername(c.cfg.Username.String())
opts.SetPassword(c.cfg.Password.String())
}
firstSubscribe := make(chan error, 1)
var once sync.Once
opts.SetOnConnectHandler(func(client pahomqtt.Client) {
logger.InfoC("mqtt", "MQTT connected, subscribing to inbound topic")
err := c.subscribe(client)
once.Do(func() { firstSubscribe <- err })
})
opts.SetConnectionLostHandler(func(_ pahomqtt.Client, err error) {
logger.WarnCF("mqtt", "MQTT connection lost", map[string]any{"error": err.Error()})
})
client := pahomqtt.NewClient(opts)
token := client.Connect()
if !token.WaitTimeout(10 * time.Second) {
client.Disconnect(250)
return fmt.Errorf("mqtt connect timed out after 10s (broker: %s)", c.cfg.Broker)
}
if err := token.Error(); err != nil {
client.Disconnect(250)
return fmt.Errorf("mqtt connect failed: %w", err)
}
if err := <-firstSubscribe; err != nil {
client.Disconnect(250)
return fmt.Errorf("mqtt subscribe failed: %w", err)
}
c.client = client
c.SetRunning(true)
logger.InfoCF("mqtt", "MQTT channel started", map[string]any{
"broker": c.cfg.Broker,
"agent_id": c.cfg.AgentID,
})
return nil
}
// topicPrefix returns the configured topic prefix, normalizing slashes.
// Trailing slashes are stripped; the result may or may not have a leading slash
// depending on what the user configured.
func (c *MQTTChannel) topicPrefix() string {
p := strings.TrimRight(c.cfg.TopicPrefix, "/")
if p == "" {
return "/picoclaw"
}
return p
}
// clientIDFromTopic extracts the client_id segment from a received topic.
// Topic structure: {prefix}/{agent_id}/{client_id}/request
func (c *MQTTChannel) clientIDFromTopic(topic string) (string, bool) {
prefix := c.topicPrefix()
// Build the expected fixed portion: {prefix}/{agent_id}/
fixed := prefix + "/" + c.cfg.AgentID + "/"
after, ok := strings.CutPrefix(topic, fixed)
if !ok {
return "", false
}
// after = "{client_id}/request"
slash := strings.IndexByte(after, '/')
if slash < 0 {
return "", false
}
return after[:slash], true
}
// subscribe subscribes to the inbound topic for this agent.
func (c *MQTTChannel) subscribe(client pahomqtt.Client) error {
topic := fmt.Sprintf("%s/%s/+/request", c.topicPrefix(), c.cfg.AgentID)
token := client.Subscribe(topic, c.qos, func(_ pahomqtt.Client, msg pahomqtt.Message) {
c.handleInbound(msg)
})
token.Wait()
if err := token.Error(); err != nil {
logger.ErrorCF("mqtt", "Failed to subscribe", map[string]any{
"topic": topic,
"error": err.Error(),
})
return err
}
logger.InfoCF("mqtt", "Subscribed to inbound topic", map[string]any{"topic": topic})
return nil
}
// handleInbound processes an inbound MQTT message.
func (c *MQTTChannel) handleInbound(msg pahomqtt.Message) {
topic := msg.Topic()
clientID, ok := c.clientIDFromTopic(topic)
if !ok {
logger.WarnCF("mqtt", "Unexpected topic format", map[string]any{"topic": topic})
return
}
chatID := "mqtt:" + clientID
var payload mqttPayload
if err := json.Unmarshal(msg.Payload(), &payload); err != nil {
logger.WarnCF("mqtt", "Failed to parse inbound payload", map[string]any{
"topic": topic,
"error": err.Error(),
})
return
}
if payload.Text == "" {
logger.WarnCF("mqtt", "Inbound payload missing text", map[string]any{"topic": topic})
return
}
inboundCtx := bus.InboundContext{
Channel: "mqtt",
ChatID: chatID,
ChatType: "direct",
SenderID: clientID,
}
c.HandleInboundContext(context.Background(), chatID, payload.Text, nil, inboundCtx)
}
// Stop disconnects from the MQTT broker.
func (c *MQTTChannel) Stop(_ context.Context) error {
logger.InfoC("mqtt", "Stopping MQTT channel")
c.SetRunning(false)
if c.client != nil {
c.client.Disconnect(500)
}
logger.InfoC("mqtt", "MQTT channel stopped")
return nil
}
// Send publishes a response to the client via MQTT.
func (c *MQTTChannel) Send(_ context.Context, msg bus.OutboundMessage) ([]string, error) {
if !c.IsRunning() {
return nil, channels.ErrNotRunning
}
if strings.TrimSpace(msg.Content) == "" {
return nil, nil
}
clientID := strings.TrimPrefix(msg.ChatID, "mqtt:")
if clientID == msg.ChatID {
logger.WarnCF("mqtt", "Send called with unexpected chatID format", map[string]any{"chat_id": msg.ChatID})
return nil, nil
}
topic := fmt.Sprintf("%s/%s/%s/response", c.topicPrefix(), c.cfg.AgentID, clientID)
data, err := json.Marshal(mqttPayload{Text: msg.Content})
if err != nil {
return nil, fmt.Errorf("mqtt: failed to marshal outbound payload: %w", err)
}
token := c.client.Publish(topic, c.qos, false, data)
token.Wait()
if err := token.Error(); err != nil {
return nil, fmt.Errorf("mqtt: publish failed: %w", err)
}
logger.DebugCF("mqtt", "Published response", map[string]any{"topic": topic})
return nil, nil
}

View file

@ -516,6 +516,17 @@ type TeamsWebhookTarget struct {
Title string `json:"title,omitempty" yaml:"-"`
}
type MQTTSettings struct {
Broker string `json:"broker" yaml:"-" env:"PICOCLAW_CHANNELS_MQTT_BROKER"`
AgentID string `json:"agent_id" yaml:"-" env:"PICOCLAW_CHANNELS_MQTT_AGENT_ID"`
TopicPrefix string `json:"topic_prefix,omitempty" yaml:"-" env:"PICOCLAW_CHANNELS_MQTT_TOPIC_PREFIX"`
Username SecureString `json:"username,omitzero" yaml:"username,omitempty" env:"PICOCLAW_CHANNELS_MQTT_USERNAME"`
Password SecureString `json:"password,omitzero" yaml:"password,omitempty" env:"PICOCLAW_CHANNELS_MQTT_PASSWORD"`
ClientID string `json:"client_id,omitempty" yaml:"-" env:"PICOCLAW_CHANNELS_MQTT_CLIENT_ID"`
KeepAlive int `json:"keep_alive,omitempty" yaml:"-" env:"PICOCLAW_CHANNELS_MQTT_KEEP_ALIVE"`
QoS int `json:"qos,omitempty" yaml:"-" env:"PICOCLAW_CHANNELS_MQTT_QOS"`
}
type HeartbeatConfig struct {
Enabled bool `json:"enabled" env:"PICOCLAW_HEARTBEAT_ENABLED"`
Interval int `json:"interval" env:"PICOCLAW_HEARTBEAT_INTERVAL"` // minutes, min 5

View file

@ -33,6 +33,7 @@ const (
ChannelWhatsApp = "whatsapp"
ChannelWhatsAppNative = "whatsapp_native"
ChannelTeamsWebHook = "teams_webhook"
ChannelMQTT = "mqtt"
)
func initChannel() {
@ -640,6 +641,7 @@ var channelSettingsFactory = map[string]any{
ChannelWhatsApp: (WhatsAppSettings{}),
ChannelWhatsAppNative: (WhatsAppSettings{}),
ChannelTeamsWebHook: (TeamsWebhookSettings{}),
ChannelMQTT: (MQTTSettings{}),
}
// newChannelSettings creates a fresh zero-value pointer for the given channel type.

View file

@ -25,6 +25,7 @@ import (
_ "github.com/sipeed/picoclaw/pkg/channels/irc"
_ "github.com/sipeed/picoclaw/pkg/channels/line"
_ "github.com/sipeed/picoclaw/pkg/channels/maixcam"
_ "github.com/sipeed/picoclaw/pkg/channels/mqtt"
_ "github.com/sipeed/picoclaw/pkg/channels/onebot"
_ "github.com/sipeed/picoclaw/pkg/channels/pico"
_ "github.com/sipeed/picoclaw/pkg/channels/qq"

View file

@ -47,7 +47,7 @@ The current frontend exposes these major pages and flows:
- Current built-in flows: OpenAI, Anthropic, and Google Antigravity.
- `/channels/*`
- Configure supported channels from a shared catalog.
- Current catalog: `weixin`, `telegram`, `discord`, `slack`, `feishu`, `dingtalk`, `line`, `qq`, `onebot`, `wecom`, `whatsapp`, `whatsapp_native`, `pico`, `maixcam`, `matrix`, `irc`.
- Current catalog: `weixin`, `telegram`, `discord`, `slack`, `feishu`, `dingtalk`, `line`, `qq`, `onebot`, `wecom`, `whatsapp`, `whatsapp_native`, `pico`, `maixcam`, `matrix`, `irc`, `mqtt`.
- Includes QR-based binding helpers for WeChat and WeCom.
- `/agent/skills`
- Browse built-in, global, and workspace skills.

View file

@ -30,6 +30,7 @@ var channelCatalog = []channelCatalogItem{
{Name: "maixcam", ConfigKey: "maixcam"},
{Name: "matrix", ConfigKey: "matrix"},
{Name: "irc", ConfigKey: "irc"},
{Name: "mqtt", ConfigKey: "mqtt"},
}
type channelConfigResponse struct {
@ -106,6 +107,7 @@ var channelSecretFieldMap = map[string][]string{
"whatsapp": {},
"whatsapp_native": {},
"maixcam": {},
"mqtt": {"username", "password"},
}
func buildChannelConfigResponse(cfg *config.Config, item channelCatalogItem) channelConfigResponse {

View file

@ -14,6 +14,7 @@ export const SECRET_FIELD_MAP = {
encrypt_key: "_encrypt_key",
verification_token: "_verification_token",
secret: "_secret",
username: "_username",
password: "_password",
nickserv_password: "_nickserv_password",
sasl_password: "_sasl_password",
@ -33,6 +34,7 @@ const CHANNEL_SECRET_FIELDS: Record<string, string[]> = {
pico: ["token"],
matrix: ["access_token"],
irc: ["password", "nickserv_password", "sasl_password"],
mqtt: ["username", "password"],
}
const SECRET_FIELD_SET = new Set(Object.keys(SECRET_FIELD_MAP))

View file

@ -24,6 +24,7 @@ import { getChannelDisplayName } from "@/components/channels/channel-display-nam
import { DiscordForm } from "@/components/channels/channel-forms/discord-form"
import { FeishuForm } from "@/components/channels/channel-forms/feishu-form"
import { GenericForm } from "@/components/channels/channel-forms/generic-form"
import { MqttForm } from "@/components/channels/channel-forms/mqtt-form"
import { SlackForm } from "@/components/channels/channel-forms/slack-form"
import { TelegramForm } from "@/components/channels/channel-forms/telegram-form"
import { WecomForm } from "@/components/channels/channel-forms/wecom-form"
@ -215,6 +216,8 @@ function isConfigured(
)
case "irc":
return hasValue("server")
case "mqtt":
return hasValue("broker") && hasValue("agent_id")
default:
return false
}
@ -250,6 +253,8 @@ function getRequiredFieldKeys(channelName: string): string[] {
return ["homeserver", "user_id", "access_token"]
case "irc":
return ["server"]
case "mqtt":
return ["broker", "agent_id"]
default:
return []
}
@ -279,6 +284,7 @@ const CHANNELS_WITHOUT_DOCS = new Set([
"irc",
"whatsapp",
"whatsapp_native",
"mqtt",
])
export function ChannelConfigPage({ channelName }: ChannelConfigPageProps) {
@ -618,6 +624,15 @@ export function ChannelConfigPage({ channelName }: ChannelConfigPageProps) {
arrayFieldResetVersion={arrayFieldResetVersion}
/>
)
case "mqtt":
return (
<MqttForm
config={editConfig}
onChange={handleChange}
configuredSecrets={configuredSecrets}
fieldErrors={fieldErrors}
/>
)
case "weixin":
return (
<WeixinForm

View file

@ -0,0 +1,248 @@
import type { ChannelConfig } from "@/api/channels"
import { getSecretInputPlaceholder } from "@/components/channels/channel-config-fields"
import { Field, KeyInput } from "@/components/shared-form"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { useTranslation } from "react-i18next"
interface MqttFormProps {
config: ChannelConfig
onChange: (key: string, value: unknown) => void
configuredSecrets: string[]
fieldErrors?: Record<string, string>
}
function asString(value: unknown): string {
return typeof value === "string" ? value : ""
}
function asNumber(value: unknown): string {
if (typeof value === "number") return String(value)
if (typeof value === "string" && value !== "") return value
return ""
}
function CodeLine({ children }: { children: string }) {
return (
<code className="bg-muted text-foreground block rounded px-3 py-1 font-mono text-xs">
{children}
</code>
)
}
export function MqttForm({
config,
onChange,
configuredSecrets,
fieldErrors = {},
}: MqttFormProps) {
const { t } = useTranslation()
const prefix = asString(config.topic_prefix) || "/picoclaw"
const agentID = asString(config.agent_id) || "{agent_id}"
const topicBase = `${prefix}/${agentID}/{client_id}`
return (
<div className="space-y-6">
<Card className="shadow-sm">
<CardContent className="divide-border/60 divide-y px-6 py-0 [&>div]:py-5">
<Field
label="Broker"
required
hint={t("channels.form.desc.broker")}
error={fieldErrors.broker}
>
<Input
value={asString(config.broker)}
onChange={(e) => onChange("broker", e.target.value)}
placeholder="mqtt://broker.example.com:1883"
/>
</Field>
<Field
label="Agent ID"
required
hint={t("channels.form.desc.mqttAgentId")}
error={fieldErrors.agent_id}
>
<Input
value={asString(config.agent_id)}
onChange={(e) => onChange("agent_id", e.target.value)}
placeholder="my-agent"
/>
</Field>
<Field
label="Topic Prefix"
hint={t("channels.form.desc.topicPrefix")}
>
<Input
value={asString(config.topic_prefix)}
onChange={(e) => onChange("topic_prefix", e.target.value)}
placeholder="/picoclaw"
/>
</Field>
</CardContent>
</Card>
<Card className="shadow-sm">
<CardContent className="divide-border/60 divide-y px-6 py-0 [&>div]:py-5">
<Field
label="Username"
hint={t("channels.form.desc.mqttUsername")}
>
<KeyInput
value={asString(config._username)}
onChange={(v) => onChange("_username", v)}
placeholder={getSecretInputPlaceholder(
configuredSecrets,
"username",
t("channels.mqtt.secretSet"),
t("channels.mqtt.secretEmpty"),
)}
/>
</Field>
<Field
label="Password"
hint={t("channels.form.desc.mqttPassword")}
>
<KeyInput
value={asString(config._password)}
onChange={(v) => onChange("_password", v)}
placeholder={getSecretInputPlaceholder(
configuredSecrets,
"password",
t("channels.mqtt.secretSet"),
t("channels.mqtt.secretEmpty"),
)}
/>
</Field>
</CardContent>
</Card>
<Card className="shadow-sm">
<CardContent className="divide-border/60 divide-y px-6 py-0 [&>div]:py-5">
<Field
label="Client ID"
hint={t("channels.form.desc.mqttClientId")}
>
<Input
value={asString(config.client_id)}
onChange={(e) => onChange("client_id", e.target.value)}
placeholder={t("channels.mqtt.clientIdPlaceholder")}
/>
</Field>
<Field
label="Keep Alive"
hint={t("channels.form.desc.keepAlive")}
>
<Input
type="number"
value={asNumber(config.keep_alive)}
onChange={(e) => onChange("keep_alive", Number(e.target.value))}
placeholder="60"
/>
</Field>
<Field
label="QoS"
hint={t("channels.form.desc.qos")}
>
<Input
type="number"
value={asNumber(config.qos)}
onChange={(e) => onChange("qos", Number(e.target.value))}
placeholder="0"
/>
</Field>
</CardContent>
</Card>
<Card className="shadow-sm">
<CardHeader className="border-border/60 border-b px-6 py-4">
<CardTitle className="text-foreground text-sm font-medium">
{t("channels.mqtt.protocolTitle")}
</CardTitle>
<CardDescription>
{t("channels.mqtt.protocolDesc")}
</CardDescription>
</CardHeader>
<CardContent className="space-y-5 px-6 py-5">
<div className="space-y-2">
<p className="text-muted-foreground text-xs font-medium uppercase tracking-wide">
{t("channels.mqtt.uplink")}
</p>
<CodeLine>{`${topicBase}/request`}</CodeLine>
<pre className="bg-muted text-foreground rounded px-3 py-2 font-mono text-xs leading-relaxed">
{`{\n "text": "your message"\n}`}
</pre>
<div className="text-muted-foreground space-y-1 text-xs">
<p>
<span className="text-foreground font-medium">
{t("channels.mqtt.fieldText")}
</span>
{" — "}
{t("channels.mqtt.uplinkTextDesc")}
</p>
</div>
</div>
<div className="space-y-2">
<p className="text-muted-foreground text-xs font-medium uppercase tracking-wide">
{t("channels.mqtt.downlink")}
</p>
<CodeLine>{`${topicBase}/response`}</CodeLine>
<pre className="bg-muted text-foreground rounded px-3 py-2 font-mono text-xs leading-relaxed">
{`{\n "text": "agent response"\n}`}
</pre>
<div className="text-muted-foreground space-y-1 text-xs">
<p>
<span className="text-foreground font-medium">
{t("channels.mqtt.fieldText")}
</span>
{" — "}
{t("channels.mqtt.downlinkTextDesc")}
</p>
</div>
</div>
<div className="space-y-2">
<p className="text-muted-foreground text-xs font-medium uppercase tracking-wide">
{t("channels.mqtt.topicParams")}
</p>
<div className="text-muted-foreground space-y-1 text-xs">
<p>
<span className="text-foreground font-mono font-medium">
{prefix}
</span>
{" — "}
{t("channels.mqtt.topicPrefixDesc")}
</p>
<p>
<span className="text-foreground font-mono font-medium">
{agentID}
</span>
{" — "}
{t("channels.mqtt.agentIdDesc")}
</p>
<p>
<span className="text-foreground font-mono font-medium">
{"{client_id}"}
</span>
{" — "}
{t("channels.mqtt.clientIdDesc")}
</p>
</div>
</div>
</CardContent>
</Card>
</div>
)
}

View file

@ -332,7 +332,8 @@
"maixcam": "MaixCam",
"matrix": "Matrix",
"irc": "IRC",
"weixin": "WeChat"
"weixin": "WeChat",
"mqtt": "MQTT"
},
"weixin": {
"bindTitle": "WeChat Account Binding",
@ -457,11 +458,35 @@
"channels": "IRC channels to join.",
"requestCaps": "IRC capability list requested on connect.",
"maxBase64FileSizeMiB": "Maximum size in MiB for converting local files to base64 before upload. 0 means unlimited. Applies only to local files, not URL uploads.",
"genericField": "Used to configure {{field}}."
"genericField": "Used to configure {{field}}.",
"broker": "MQTT broker address.",
"mqttAgentId": "Unique identifier for this instance, used to build the topic path.",
"topicPrefix": "Topic prefix. Defaults to /picoclaw.",
"mqttUsername": "Broker authentication username (optional).",
"mqttPassword": "Broker authentication password (optional).",
"mqttClientId": "MQTT client ID. Leave blank to auto-generate.",
"keepAlive": "Keepalive interval in seconds. Defaults to 60.",
"qos": "Message quality of service level: 0 = at most once, 1 = at least once, 2 = exactly once."
}
},
"validation": {
"requiredField": "This field is required."
},
"mqtt": {
"protocolTitle": "Protocol Reference",
"protocolDesc": "Clients send and receive messages using the following topic and payload format.",
"uplink": "Uplink (Client → Agent)",
"downlink": "Downlink (Agent → Client)",
"topicParams": "Topic Parameters",
"fieldText": "text",
"uplinkTextDesc": "Natural language instruction from the user (required).",
"downlinkTextDesc": "Agent reply text. In streaming mode, concatenate multiple messages in order for the full response.",
"topicPrefixDesc": "Topic prefix, matches the configuration above.",
"agentIdDesc": "Agent ID, matches the configuration above.",
"clientIdDesc": "Client-defined identifier. Recommended: generate a UUID on first launch and persist it so the same device always uses the same ID.",
"clientIdPlaceholder": "Auto-generated if blank",
"secretSet": "Already configured. Leave blank to keep unchanged.",
"secretEmpty": "Not configured"
}
},
"pages": {

View file

@ -332,7 +332,8 @@
"maixcam": "MaixCam",
"matrix": "Matrix",
"irc": "IRC",
"weixin": "微信"
"weixin": "微信",
"mqtt": "MQTT"
},
"weixin": {
"bindTitle": "微信账号绑定",
@ -457,11 +458,35 @@
"channels": "要加入的 IRC 频道列表",
"requestCaps": "连接时请求的 IRC 扩展能力列表",
"maxBase64FileSizeMiB": "本地文件转为 base64 上传的最大体积,单位 MiB0 表示不限制,仅影响本地文件,不影响 URL 直传",
"genericField": "用于配置{{field}}"
"genericField": "用于配置{{field}}",
"broker": "MQTT Broker 地址。",
"mqttAgentId": "本实例的唯一标识,用于构造 topic 路径。",
"topicPrefix": "Topic 前缀,默认为 /picoclaw。",
"mqttUsername": "Broker 认证用户名(可选)。",
"mqttPassword": "Broker 认证密码(可选)。",
"mqttClientId": "MQTT 客户端 ID留空自动生成。",
"keepAlive": "心跳间隔(秒),默认 60。",
"qos": "消息质量等级0=最多一次1=至少一次2=恰好一次。"
}
},
"validation": {
"requiredField": "请填写该字段"
},
"mqtt": {
"protocolTitle": "接入协议",
"protocolDesc": "客户端按以下 topic 和 payload 格式收发消息",
"uplink": "上行(客户端 → Agent",
"downlink": "下行Agent → 客户端)",
"topicParams": "Topic 参数说明",
"fieldText": "text",
"uplinkTextDesc": "用户自然语言指令(必填)",
"downlinkTextDesc": "Agent 回复文本,流式场景下多条按序拼接即为完整回复",
"topicPrefixDesc": "topic 前缀,与上方配置一致",
"agentIdDesc": "Agent ID与上方配置一致",
"clientIdDesc": "客户端自定义标识,建议首次启动时生成 UUID 并持久化,同一设备保持不变",
"clientIdPlaceholder": "留空自动生成",
"secretSet": "已设置,留空表示不修改",
"secretEmpty": "未配置"
}
},
"pages": {