Metadata-Version: 2.4
Name: mqtty
Version: 0.1.9
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 --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.
* `--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. Removed ports may remain listed until 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.

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
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
