Metadata-Version: 2.4
Name: mqtty
Version: 0.1.10
Summary: Bridges MQTT topics to a pseudo-terminal device
Author-email: Mark Featherston <mark@embeddedTS.com>
Project-URL: homepage, https://github.com/embeddedts/mqtty
Project-URL: bugs, https://github.com/embeddedts/mqtty/issues
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: paho-mqtt
Requires-Dist: pyserial
Requires-Dist: tomli; python_version < "3.11"
Requires-Dist: zstandard
Dynamic: license-file

# mqtty

`mqtty` bridges MQTT topics to either your local terminal or a pseudoterminal (PTY), and now also ships log capture and replay helpers for the same serial stream format.

This is useful for debugging, automation, or tunneling serial communication through a networked MQTT broker.

## Commands

```bash
mqtty mqtt://<host>/<topic> [--pts-only]
mqtty <path-below-$MQTTY_URI> [--pts-only]
mqtty --list [mqtt://<host>/<topic-base>]
```

`mqtty` runs in bidirectional terminal mode by default:

* bytes from `stdin` are published to `.../device_serial_input`
* bytes from `.../device_serial_output` are written to `stdout`
* if `stdin` is a TTY, it is placed into raw mode while `mqtty` is running
* type `Ctrl-A Ctrl-X` to exit, matching picocom's default exit sequence
* type `Ctrl-A Ctrl-A` to send a literal `Ctrl-A`

Use `--pts-only` if you only want a PTY device:

* `--pts-only`, `-p`:
  Print the path to the PTS device and keep it open without attaching local stdin/stdout. Useful for tools or scripts that want to use the virtual serial port.

Use `--list` to discover serial ports below a base topic:

* `--list`, `-l`:
  Subscribe below `<topic-base>` for retained `.../_mqtty/available` discovery messages, print matching serial URLs, and exit. If no URI is provided, `MQTTY_URI` is used.
* `--list-timeout`:
  Seconds to wait for retained discovery messages. Defaults to `1.0`.

`mqtty-log` records `.../device_serial_output` into replay files:

```bash
mqtty-log device mqtt://<host>/<serial-server>/<device> capture.jsonl.zst
mqtty-log prefix mqtt://<host>/<topic-prefix> logs/
```

* `device` records one full device URL into a single compressed replay file and adds `.zst` if omitted
* `prefix` subscribes to `<topic-prefix>/#`, keeps topics ending in `device_serial_output`, and rotates per-device logs under `<outdir>/<topic-path-below-prefix>/YYYY-MM-DD_replay.jsonl.zst`
* each replay record stores an absolute Unix epoch timestamp in milliseconds (`ts`) before relative delay (`t`) and payload (`d`)

`mqtty-log-replay` replays either plain or compressed replay logs:

```bash
mqtty-log-replay path/to/replay.jsonl.zst [--raw] [--follow] [--info]
```

Raw `.jsonl` logs are still accepted for replay and inspection.

`mqtty-serial-bridge` discovers local serial devices and bridges them to MQTT:

```bash
mqtty-serial-bridge [--config /etc/mqtty-serial-bridge.toml]
```

It subscribes to:

* `<topic_base>/<port>/device_serial_input`

And publishes:

* `<topic_base>/<port>/device_serial_output`
* retained `<topic_base>/<port>/_mqtty/available`

The retained availability payload is JSON:

```json
{"port":"<port>","alias":"<alias>"}
```

The MQTT broker stores retained discovery messages, so clients only need to connect to the broker. The bridge does not use a database. The bridge clears a port's retained availability message during normal unplug and shutdown paths. If the bridge process crashes before cleanup, removed ports may remain listed until the bridge restarts or the retained messages are cleared from the broker.

Port aliases are resolved in this order:

1. `[serial_aliases]` entries in the bridge config
2. `<serial-base-path>/<port>.alias` text files
3. the resolved local device path, such as `/dev/ttyACM0`

Default config path:

1. `/etc/mqtty-serial-bridge.toml`

The `<topic>` segment of the URI is used as the base path for MQTT messages across both live commands:

* `.../device_serial_input` receives bytes from local input and sends them to the device
* `.../device_serial_output` carries device output back to your terminal or PTY

## Example

```bash
mqtty mqtt://broker.local/mydevice
```

This connects your terminal directly to `mydevice/device_serial_input` and `mydevice/device_serial_output`.

To expose a PTY instead:

```bash
mqtty mqtt://broker.local/mydevice --pts-only
```

This prints the local PTY path and keeps it bridged to the same MQTT topics until interrupted.

For repeated use, set `MQTTY_URI` to your usual broker and base topic:

```bash
export MQTTY_URI=mqtt://broker.local/testbench/mark-desktop
mqtty --list
mqtty platform-ci_hdrc.1-usb-0:1.2:1.0
mqtty --pts-only platform-ci_hdrc.1-usb-0:1.2:1.0
```

Short paths are appended to `MQTTY_URI`; full `mqtt://`, `ws://`, and `wss://` URLs are still accepted anywhere a URI is accepted.

To capture a replay log while a device is running:

```bash
mqtty-log device mqtt://broker.local/mydevice mydevice.jsonl.zst
```

To capture every device under one serial-server or a broader base prefix:

```bash
mqtty-log prefix mqtt://broker.local/testbench/mark-pantry logs/
mqtty-log prefix mqtt://broker.local/testbench logs/
```

To inspect or replay that log later:

```bash
mqtty-log-replay mydevice.jsonl.zst --info
mqtty-log-replay mydevice.jsonl.zst
```

To run the serial bridge:

```bash
mqtty-serial-bridge
```

To list retained serial bridge ports under a base topic:

```bash
export MQTTY_URI=mqtt://broker.local/testbench/serial
mqtty --list
mqtty --list mqtt://broker.local/testbench/serial
mqtty --list mqtt://broker.local/testbench/
```

Example output:

```text
mqtt://broker.local/testbench/serial/platform-ci_hdrc.1-usb-0:1.2:1.0 (/dev/ttyACM0)
mqtt://broker.local/testbench/mark-desktop/platform-ci_hdrc.1-usb-0:1.2:1.0 (/dev/ttyACM0)
```

## Testing

Run the test suite with:

```bash
pytest
```

Integration tests in `tests/test_integration_mqtt_apps.py` spin up a temporary local `mosquitto` broker and use Linux PTYs (`pty.openpty`) as fake serial devices.

Integration tests require `mosquitto` and fail if it is not available on `PATH`.

Repeatable Docker run (includes `mosquitto` in the container):

```bash
./scripts/run-tests-docker.sh
```

Show each executed test (verbose):

```bash
./scripts/run-tests-docker.sh --verbose
```

## License

MIT
