diff --git a/README.md b/README.md index 30ac67d8f..6e5dc66e0 100644 --- a/README.md +++ b/README.md @@ -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 | diff --git a/docs/channels/mqtt/README.fr.md b/docs/channels/mqtt/README.fr.md new file mode 100644 index 000000000..c16868a32 --- /dev/null +++ b/docs/channels/mqtt/README.fr.md @@ -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. diff --git a/docs/channels/mqtt/README.ja.md b/docs/channels/mqtt/README.ja.md new file mode 100644 index 000000000..80ccafdc5 --- /dev/null +++ b/docs/channels/mqtt/README.ja.md @@ -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` を設定してください。 diff --git a/docs/channels/mqtt/README.md b/docs/channels/mqtt/README.md new file mode 100644 index 000000000..c894d77f7 --- /dev/null +++ b/docs/channels/mqtt/README.md @@ -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. diff --git a/docs/channels/mqtt/README.pt-br.md b/docs/channels/mqtt/README.pt-br.md new file mode 100644 index 000000000..da95b6ba6 --- /dev/null +++ b/docs/channels/mqtt/README.pt-br.md @@ -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. diff --git a/docs/channels/mqtt/README.vi.md b/docs/channels/mqtt/README.vi.md new file mode 100644 index 000000000..f680c78bb --- /dev/null +++ b/docs/channels/mqtt/README.vi.md @@ -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. diff --git a/docs/channels/mqtt/README.zh.md b/docs/channels/mqtt/README.zh.md new file mode 100644 index 000000000..e7e529cde --- /dev/null +++ b/docs/channels/mqtt/README.zh.md @@ -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 客户端 ID,Broker 能正确识别为同一连接。 + +--- + +## ⚠️ 注意事项 + +- **TLS**:支持 SSL/TLS(Broker 地址使用 `ssl://`),默认跳过证书验证。 +- **流式响应**:流式输出时会向 response topic 发送多条消息,客户端按顺序拼接即为完整回复。 +- **client_id 与会话 ID 的区别**:topic 路径中的 `client_id` 由客户端应用自行设置,用于区分会话;它与 PicoClaw paho 连接 Broker 时使用的客户端 ID 是两个独立的概念。 +- **多实例部署**:若多个 PicoClaw 实例使用相同 `agent_id` 连接同一 Broker,需为每个实例配置不同的 `client_id` 以避免 Broker 层面的冲突。 diff --git a/docs/guides/chat-apps.fr.md b/docs/guides/chat-apps.fr.md index d9112c595..a03141e5e 100644 --- a/docs/guides/chat-apps.fr.md +++ b/docs/guides/chat-apps.fr.md @@ -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 ``` + + +
+MQTT + +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). + +
diff --git a/docs/guides/chat-apps.ja.md b/docs/guides/chat-apps.ja.md index 49c41a66e..cc9671bd5 100644 --- a/docs/guides/chat-apps.ja.md +++ b/docs/guides/chat-apps.ja.md @@ -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 ``` + + +
+MQTT + +任意の 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) を参照してください。 + +
diff --git a/docs/guides/chat-apps.md b/docs/guides/chat-apps.md index 62418f91a..4fcf12653 100644 --- a/docs/guides/chat-apps.md +++ b/docs/guides/chat-apps.md @@ -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 ``` + + +
+MQTT + +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). + +
diff --git a/docs/guides/chat-apps.ms.md b/docs/guides/chat-apps.ms.md index 6bfa7565e..03e8d36ca 100644 --- a/docs/guides/chat-apps.ms.md +++ b/docs/guides/chat-apps.ms.md @@ -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`. + + +
+MQTT + +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). + +
diff --git a/docs/guides/chat-apps.pt-br.md b/docs/guides/chat-apps.pt-br.md index 6d4fbdc23..f6b89ca3b 100644 --- a/docs/guides/chat-apps.pt-br.md +++ b/docs/guides/chat-apps.pt-br.md @@ -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 ``` + + +
+MQTT + +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). + +
diff --git a/docs/guides/chat-apps.vi.md b/docs/guides/chat-apps.vi.md index 8d0b4ee32..8071c9d3d 100644 --- a/docs/guides/chat-apps.vi.md +++ b/docs/guides/chat-apps.vi.md @@ -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 ``` + + +
+MQTT + +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). + +
diff --git a/docs/guides/chat-apps.zh.md b/docs/guides/chat-apps.zh.md index b5891dc69..d7400cd83 100644 --- a/docs/guides/chat-apps.zh.md +++ b/docs/guides/chat-apps.zh.md @@ -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 ``` + + +
+MQTT + +任意 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)。 + +
diff --git a/docs/security/security_configuration.md b/docs/security/security_configuration.md index 065eb1e76..ad4b4f183 100644 --- a/docs/security/security_configuration.md +++ b/docs/security/security_configuration.md @@ -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:** diff --git a/go.mod b/go.mod index c7e77c0f9..bc5874870 100644 --- a/go.mod +++ b/go.mod @@ -75,6 +75,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 diff --git a/go.sum b/go.sum index 5cd39ec8d..18dced20d 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/pkg/channels/README.md b/pkg/channels/README.md index 1cab1a4a6..c3decd242 100644 --- a/pkg/channels/README.md +++ b/pkg/channels/README.md @@ -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 diff --git a/pkg/channels/README.zh.md b/pkg/channels/README.zh.md index c44859c20..d71c30104 100644 --- a/pkg/channels/README.zh.md +++ b/pkg/channels/README.zh.md @@ -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 接口速查表 diff --git a/pkg/channels/manager.go b/pkg/channels/manager.go index d56c4fd9b..472849e66 100644 --- a/pkg/channels/manager.go +++ b/pkg/channels/manager.go @@ -612,6 +612,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 diff --git a/pkg/channels/mqtt/init.go b/pkg/channels/mqtt/init.go new file mode 100644 index 000000000..c9cec7e83 --- /dev/null +++ b/pkg/channels/mqtt/init.go @@ -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) + }, + ) +} diff --git a/pkg/channels/mqtt/mqtt.go b/pkg/channels/mqtt/mqtt.go new file mode 100644 index 000000000..d183fcc3e --- /dev/null +++ b/pkg/channels/mqtt/mqtt.go @@ -0,0 +1,242 @@ +package mqtt + +import ( + "context" + "crypto/rand" + "crypto/tls" + "encoding/hex" + "encoding/json" + "fmt" + "strings" + "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()) + } + + opts.SetOnConnectHandler(func(client pahomqtt.Client) { + logger.InfoC("mqtt", "MQTT connected, subscribing to inbound topic") + c.subscribe(client) + }) + + 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) { + return fmt.Errorf("mqtt connect timed out after 10s (broker: %s)", c.cfg.Broker) + } + if err := token.Error(); err != nil { + return fmt.Errorf("mqtt connect 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) { + 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(), + }) + } else { + logger.InfoCF("mqtt", "Subscribed to inbound topic", map[string]any{"topic": topic}) + } +} + +// 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.IsConnected() { + 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 +} diff --git a/pkg/config/config.go b/pkg/config/config.go index dc9e88949..cf8422ab0 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -512,6 +512,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 diff --git a/pkg/config/config_channel.go b/pkg/config/config_channel.go index 4e87fcc3e..fe7ee4b98 100644 --- a/pkg/config/config_channel.go +++ b/pkg/config/config_channel.go @@ -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. diff --git a/pkg/gateway/gateway.go b/pkg/gateway/gateway.go index f58590d5b..c7ea7fc71 100644 --- a/pkg/gateway/gateway.go +++ b/pkg/gateway/gateway.go @@ -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" diff --git a/web/README.md b/web/README.md index 2a57524e0..87760cd94 100644 --- a/web/README.md +++ b/web/README.md @@ -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. diff --git a/web/backend/api/channels.go b/web/backend/api/channels.go index 82cd54b72..e77b11f8b 100644 --- a/web/backend/api/channels.go +++ b/web/backend/api/channels.go @@ -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 { diff --git a/web/frontend/src/components/channels/channel-config-fields.ts b/web/frontend/src/components/channels/channel-config-fields.ts index 35356954b..cf8f50adf 100644 --- a/web/frontend/src/components/channels/channel-config-fields.ts +++ b/web/frontend/src/components/channels/channel-config-fields.ts @@ -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 = { 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)) diff --git a/web/frontend/src/components/channels/channel-config-page.tsx b/web/frontend/src/components/channels/channel-config-page.tsx index d253980f8..8a8300d08 100644 --- a/web/frontend/src/components/channels/channel-config-page.tsx +++ b/web/frontend/src/components/channels/channel-config-page.tsx @@ -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 ( + + ) case "weixin": return ( void + configuredSecrets: string[] + fieldErrors?: Record +} + +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 ( + + {children} + + ) +} + +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 ( +
+ + + + onChange("broker", e.target.value)} + placeholder="mqtt://broker.example.com:1883" + /> + + + + onChange("agent_id", e.target.value)} + placeholder="my-agent" + /> + + + + onChange("topic_prefix", e.target.value)} + placeholder="/picoclaw" + /> + + + + + + + + onChange("_username", v)} + placeholder={getSecretInputPlaceholder( + configuredSecrets, + "username", + t("channels.mqtt.secretSet"), + t("channels.mqtt.secretEmpty"), + )} + /> + + + + onChange("_password", v)} + placeholder={getSecretInputPlaceholder( + configuredSecrets, + "password", + t("channels.mqtt.secretSet"), + t("channels.mqtt.secretEmpty"), + )} + /> + + + + + + + + onChange("client_id", e.target.value)} + placeholder={t("channels.mqtt.clientIdPlaceholder")} + /> + + + + onChange("keep_alive", Number(e.target.value))} + placeholder="60" + /> + + + + onChange("qos", Number(e.target.value))} + placeholder="0" + /> + + + + + + + + {t("channels.mqtt.protocolTitle")} + + + {t("channels.mqtt.protocolDesc")} + + + +
+

+ {t("channels.mqtt.uplink")} +

+ {`${topicBase}/request`} +
+              {`{\n  "text": "your message"\n}`}
+            
+
+

+ + {t("channels.mqtt.fieldText")} + + {" — "} + {t("channels.mqtt.uplinkTextDesc")} +

+
+
+ +
+

+ {t("channels.mqtt.downlink")} +

+ {`${topicBase}/response`} +
+              {`{\n  "text": "agent response"\n}`}
+            
+
+

+ + {t("channels.mqtt.fieldText")} + + {" — "} + {t("channels.mqtt.downlinkTextDesc")} +

+
+
+ +
+

+ {t("channels.mqtt.topicParams")} +

+
+

+ + {prefix} + + {" — "} + {t("channels.mqtt.topicPrefixDesc")} +

+

+ + {agentID} + + {" — "} + {t("channels.mqtt.agentIdDesc")} +

+

+ + {"{client_id}"} + + {" — "} + {t("channels.mqtt.clientIdDesc")} +

+
+
+
+
+
+ ) +} diff --git a/web/frontend/src/i18n/locales/en.json b/web/frontend/src/i18n/locales/en.json index 4e7a0c818..f79aed1a5 100644 --- a/web/frontend/src/i18n/locales/en.json +++ b/web/frontend/src/i18n/locales/en.json @@ -325,7 +325,8 @@ "maixcam": "MaixCam", "matrix": "Matrix", "irc": "IRC", - "weixin": "WeChat" + "weixin": "WeChat", + "mqtt": "MQTT" }, "weixin": { "bindTitle": "WeChat Account Binding", @@ -450,11 +451,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": { diff --git a/web/frontend/src/i18n/locales/zh.json b/web/frontend/src/i18n/locales/zh.json index fa7d56418..571232b67 100644 --- a/web/frontend/src/i18n/locales/zh.json +++ b/web/frontend/src/i18n/locales/zh.json @@ -325,7 +325,8 @@ "maixcam": "MaixCam", "matrix": "Matrix", "irc": "IRC", - "weixin": "微信" + "weixin": "微信", + "mqtt": "MQTT" }, "weixin": { "bindTitle": "微信账号绑定", @@ -450,11 +451,35 @@ "channels": "要加入的 IRC 频道列表", "requestCaps": "连接时请求的 IRC 扩展能力列表", "maxBase64FileSizeMiB": "本地文件转为 base64 上传的最大体积,单位 MiB;0 表示不限制,仅影响本地文件,不影响 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": {