Metadata-Version: 2.4
Name: blustream
Version: 0.3.0
Summary: Python library and CLI for controlling Blustream audio devices
Author: Cai Durbin
License-Expression: Apache-2.0
Project-URL: Homepage, https://github.com/caidurbin/blustream
Project-URL: Repository, https://github.com/caidurbin/blustream
Project-URL: Issues, https://github.com/caidurbin/blustream/issues
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: NOTICE
Requires-Dist: telnetlib3>=2.0.0
Provides-Extra: dev
Requires-Dist: pytest>=9.0.3; extra == "dev"
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
Requires-Dist: ruff==0.15.16; extra == "dev"
Provides-Extra: codegen
Requires-Dist: Jinja2>=3.0; extra == "codegen"
Requires-Dist: PyYAML>=6.0; extra == "codegen"
Provides-Extra: c4z
Requires-Dist: lxml>=5.0; extra == "c4z"
Dynamic: license-file

# Blustream

Open-source toolkit for controlling [Blustream](https://www.blustream.com/) audio
devices — starting with the DMP168 digital audio matrix processor — across
three home-automation surfaces.

## Components

This repository is a monorepo housing three deliverables that share a single
protocol contract under `spec/`:

1. **Python library + CLI** (`blustream/`). A clean async Python API and a
   `blustream` console script. Ships to **[PyPI](https://pypi.org/project/blustream/)**
   on `v*` tags.
2. **Control4 driver** (`control4/dmp168/`). A Lua driver packaged as a
   `.c4z` archive that exposes the matrix as a standard Control4 audio
   matrix. Ships as a **GitHub release** with the `.c4z` attached on
   `c4-v*` tags.
3. **Home Assistant integration** (`custom_components/blustream/`). A
   [HACS](https://hacs.xyz) integration (domain `blustream`) built to the HA
   Integration Quality Scale **Gold** tier. Ships as a **GitHub release** on
   `hacs-v*` tags, which HACS installs from the repository tree. It depends on
   the published `blustream` PyPI library rather than re-embedding the protocol
   code.

The protocol primitives shared between the library and the driver are
**generated** from `spec/protocol.yaml` via the codegen tooling under
`spec/codegen/`. The Python and Lua emitters write into
`blustream/devices/dmp168/_generated.py` and
`control4/dmp168/src/generated.lua` respectively, and CI fails when those
committed files drift from what the spec would produce.

## Independent versioning

Each component releases on its own cadence using a distinct tag prefix. The
prefixes are disjoint so a tag for one component cannot accidentally trigger
the wrong release workflow.

| Component       | Tag prefix | Workflow                                | Artifact                                 |
| --------------- | ---------- | --------------------------------------- | ---------------------------------------- |
| Python library  | `v*`       | `.github/workflows/release-pypi.yml`    | `blustream` on PyPI (sdist + wheel)      |
| Control4 driver | `c4-v*`    | `.github/workflows/release-c4z.yml`     | GitHub release with attached `.c4z`      |
| HA integration  | `hacs-v*`  | `.github/workflows/release-hacs.yml`    | GitHub release; HACS installs from tree  |

For example, a library bug-fix release tags `v0.2.1`, while a driver-only
update tags `c4-v0.3.0`. The shared `spec/` directory is the coordination
point when a protocol change requires both components to move in lockstep.

## Installation

### Python library + CLI

Once published, the library installs from PyPI:

```bash
pip install blustream
```

For local development from a checkout:

```bash
pip install -e ".[dev]"
```

Or run the CLI directly from the checkout without installing:

```bash
uvx --from . blustream --host 192.0.2.100 status
```

### Control4 driver

Download the `.c4z` from the latest [GitHub release](https://github.com/caidurbin/blustream/releases)
tagged `c4-v*` and install it through Composer Pro. See
`docs/control4-driver-plan.md` for the dealer-load workflow.

### Home Assistant integration

The integration installs through [HACS](https://hacs.xyz) as a **custom
repository** (it is not yet in the default HACS store):

1. In HACS, open the overflow menu → **Custom repositories**.
2. Add `https://github.com/caidurbin/blustream` with category **Integration**.
3. Install **Blustream Audio Matrix**, then restart Home Assistant.
4. Add it from **Settings → Devices & Services** (*Add Integration → Blustream*),
   or accept the DHCP/zeroconf discovery prompt.

Requires Home Assistant ≥ 2026.5.0. HACS installs the integration from the
repository tree at each `hacs-v*` release tag; it depends on the published
`blustream` PyPI library, which Home Assistant installs automatically.

## Library Usage

```python
import asyncio
from blustream import DMP168

async def main():
    # Connect to device
    device = DMP168(host='192.0.2.100', port=23)
    await device.connect()

    # Read status
    status = await device.get_status()
    print(f"Temperature: {status.temperature}°C")
    print(f"DSP Usage: {status.dsp_usage}%")

    # Control device
    await device.power_on()
    await device.set_output_volume(1, 75, unit='percent')
    await device.route_input_to_output(input_ch=2, output=1)

    # Context manager support
    async with DMP168(host='192.0.2.100') as device:
        status = await device.get_status()
        await device.power_on()

    # Disconnect
    await device.disconnect()

# Run the async function
asyncio.run(main())
```

## CLI Usage

Subcommand names are derived from the command registry. Use `blustream --help`
to list all commands and `blustream <command> --help` for parameter details.

### Get Device Status

```bash
blustream --host 192.0.2.100 status
```

### Power Control

```bash
blustream --host 192.0.2.100 power-on
blustream --host 192.0.2.100 power-off
```

### Volume Control

```bash
# Set output 1 volume to 75%
blustream --host 192.0.2.100 output-volume --output 1 --level 75

# Set output 1 volume to -10 dB
blustream --host 192.0.2.100 output-volume --output 1 --level -10 --unit dB

# Increase output 1 volume by one step
blustream --host 192.0.2.100 output-volume --output 1 --increase-level

# Decrease output 1 volume by one step
blustream --host 192.0.2.100 output-volume --output 1 --decrease-level
```

### Mute Control

```bash
# Mute output 1
blustream --host 192.0.2.100 output-mute --output 1 --mute

# Unmute output 1
blustream --host 192.0.2.100 output-mute --output 1 --no-mute
```

### Routing

```bash
# Route input 2 to output 1
blustream --host 192.0.2.100 route --input 2 --output 1
```

### Presets

```bash
# Save current configuration to preset 1
blustream --host 192.0.2.100 preset-save --preset 1

# Recall preset 1
blustream --host 192.0.2.100 preset-recall --preset 1

# Get preset status
blustream --host 192.0.2.100 preset-status --preset 1
```

### JSON Output

```bash
# Get status as JSON
blustream --host 192.0.2.100 status --json
```

## Command Line Options

Global flags must appear **before** the subcommand (e.g. `blustream --yes reboot`, not `blustream reboot --yes`).

- `--device`: Device type (default: dmp168)
- `--host`: Device hostname or IP address (default: localhost)
- `--port`: TCP port (default: 23)
- `--timeout`: Connection timeout in seconds (default: 5.0)
- `--verbose`, `-v`: Enable verbose output
- `--debug`: Enable debug output
- `--json`: Output results as JSON
- `--yes`, `-y`: Skip confirmations

## Architecture

The library is designed with extensibility in mind:

- **Base Framework**: Abstract base classes for devices and connections
- **Device Implementations**: Device-specific code in separate modules
- **Command Registry**: Commands are registered with metadata for introspection
- **Connection Layer**: Pluggable connection implementations (TCP/IP, serial, etc.)

## Supported Devices

- **DMP168**: Digital audio matrix processor (16 inputs, 8 outputs)

## Development

### Project Structure

```
blustream/
├── blustream/              # Main package
│   ├── base/                # Base classes and interfaces
│   ├── connection/          # Connection implementations
│   ├── devices/             # Device-specific implementations
│   │   └── dmp168/          # DMP168 device
│   └── cli/                 # CLI implementation
├── tests/                   # Test suite
├── main.py                  # CLI entry point
└── README.md               # This file
```

### Running Tests

```bash
pytest tests/
```

### Lua development

The Control4 driver lives under `control4/` and targets Lua 5.1 (the version
the Control4 Composer sandbox runs). CI lints all Lua sources with
[`luacheck`](https://github.com/lunarmodules/luacheck) (see
[`lint-control4.yml`](.github/workflows/lint-control4.yml)) and runs the unit
tests with [`busted`](https://lunarmodules.github.io/busted/) (see
[`test-control4.yml`](.github/workflows/test-control4.yml)).

To install the toolchain locally:

```bash
# macOS
brew install lua@5.1 luarocks
luarocks --lua-version=5.1 install luacheck
luarocks --lua-version=5.1 install busted

# Debian / Ubuntu
sudo apt-get install lua5.1 liblua5.1-0-dev luarocks
sudo luarocks --lua-version=5.1 install luacheck
sudo luarocks --lua-version=5.1 install busted
```

Then, from the repo root:

```bash
luacheck .                              # lint all Lua source
busted --pattern=_spec control4/        # run Lua unit tests
```

### Commit hooks (secret scanning + linting)

Commits are gated by [`pre-commit`](https://pre-commit.com), which runs
[`betterleaks`](https://github.com/betterleaks/betterleaks) (a secret scanner —
gitleaks' feature-frozen successor) alongside `ruff`, `ruff-format`, `luacheck`
and a large-file guard. The secret scanner uses the shared
[`.gitleaks.toml`](.gitleaks.toml), which adds custom IPv4/IPv6/MAC rules so a
real device identifier can't be committed — example identifiers must use the
IETF documentation ranges listed in
[`docs/secret-scanning-allowlist.md`](docs/secret-scanning-allowlist.md).

Install once per clone:

```bash
# macOS
brew install betterleaks pre-commit

# Debian / Ubuntu (betterleaks: see its release page or `go install`)
pipx install pre-commit
go install github.com/betterleaks/betterleaks@v1.4.1

pre-commit install          # wire the hooks into git
```

Then commits are scanned automatically. To run everything on demand:

```bash
pre-commit run --all-files                       # all hooks, whole tree
betterleaks detect --no-git --config .gitleaks.toml   # just the secret scan
```

CI enforces the same checks server-side so the gate holds even without the local
hook installed: the secret scan runs in
[`secret-scan.yml`](.github/workflows/secret-scan.yml), and `ruff` / `luacheck` /
the tests run in the per-component `lint-*` and `test-*` workflows. `ruff-format`
and the large-file guard are local-only (CI does not enforce whole-tree
formatting, so legacy files aren't reformatted until touched).

## License

This project is licensed under the [Apache License 2.0](LICENSE).

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request. Before
your first commit, install the [commit hooks](#commit-hooks-secret-scanning--linting)
(`brew install betterleaks pre-commit && pre-commit install`) so the secret scan
and linters run locally.

## Future Enhancements

See [FUTURE_ENHANCEMENTS.md](FUTURE_ENHANCEMENTS.md) for planned features and improvements.

