# F3 Picoclaw XMPP An XMPP ↔ PicoClaw bridge over MQTT, built as two independent long-running services that talk to each other over localhost HTTP. Each service owns exactly one persistent connection (one XMPP, one MQTT), so there is no connection contention between them. ## How it works ``` XMPP user <─> [xmpp_service.py] ──HTTP /publish──> [mqtt_service.py] <─> MQTT broker ^ XMPP conn :19080 MQTT conn :19081 └───────────────── HTTP /send ◄──────────────────┘ ``` - **`src/xmpp_service.py`** (port `19080`) — one XMPP connection (receive and send). Exposes `POST /send` `{jid, text}` to send an outgoing XMPP message (chat, or groupchat when the JID is a known room). Every incoming XMPP message is forwarded via `POST /publish` to the MQTT service. - **`src/mqtt_service.py`** (port `19081`) — one MQTT connection subscribed to `{prefix}/{agent_id}/+/response`. Exposes `POST /publish` `{jid, text}` to publish `{"text": ...}` to `{prefix}/{agent_id}/{jid}/request`. Every MQTT response is forwarded via `POST /send` to the XMPP service. The `{jid}` segment is the bare JID of the XMPP sender, so PicoClaw keeps an independent conversation per sender. PicoClaw's streamed reply chunks arrive as separate MQTT messages and are each forwarded as their own XMPP message. If a service cannot reach the other over HTTP, it logs the error and continues. ## Requirements - Python >= 3.10 - [uv](https://docs.astral.sh/uv/) package manager - An MQTT broker (e.g. Mosquitto, EMQX) - A running [PicoClaw](https://github.com/sipeed/picoclaw) gateway with the MQTT channel enabled (`channel_list.mqtt` with a matching `agent_id` and `topic_prefix`) ## Setup ```bash # Install dependencies uv sync # Configure environment cp .env.example .env # Edit .env with your XMPP credentials and MQTT settings ``` ## Environment Variables | Variable | Description | |---|---| | `XMPP_USERNAME` | Full JID for the XMPP bot account | | `XMPP_PASSWORD` | XMPP account password | | `XMPP_ROOMS` | Comma-separated list of MUC rooms to join | | `MQTT_BROKER` | MQTT broker URL, e.g. `tcp://host:1883` or `ssl://host:8883` | | `MQTT_USERNAME` | Optional broker username | | `MQTT_PASSWORD` | Optional broker password | | `MQTT_TOPIC_PREFIX` | Topic namespace prefix (default `/picoclaw`) | | `MQTT_AGENT_ID` | PicoClaw agent id used in the topic path (default `assistant`) | | `MQTT_CONN_CLIENT_ID` | Broker-level client id for MQTT connections | | `MQTT_QOS` | MQTT QoS for publish/subscribe: `0`, `1`, or `2` (default `0`) | | `DEBUG` | Logging level: `debug`, `info`, `warn`, `error`, `critical` | The service ports are hardcoded: XMPP service on `127.0.0.1:19080`, MQTT service on `127.0.0.1:19081`. ## Usage Run both services (e.g. two terminals, or a process manager like systemd): ```bash # Terminal 1 - XMPP side uv run python -m src.xmpp_service # Terminal 2 - MQTT side uv run python -m src.mqtt_service ``` ## Project Structure ``` src/ ├── xmpp_service.py # XMPP connection + HTTP :19080 (incoming -> MQTT, /send) ├── mqtt_service.py # MQTT connection + HTTP :19081 (responses -> XMPP, /publish) ├── xmpp.py # XMPP bot client (slixmpp) ├── picoclaw.py # PicoClaw MQTT client (paho-mqtt) └── log.py # Logging configuration ``` ## Tech Stack - Python 3.14 / asyncio - [slixmpp](https://github.com/poezio/slixmpp) for XMPP - [paho-mqtt](https://github.com/eclipse/paho.mqtt.python) for MQTT - [aiohttp](https://docs.aiohttp.org/) for the HTTP service APIs