Contributing¶
Contributions are welcome! Here's how to get started.
Development Setup¶
Or with uv:
Running Tests¶
# All unit tests
pytest tests/test_*.py
# With verbose output
pytest tests/test_*.py -v
# Specific test file
pytest tests/test_commands.py
# Specific test
pytest tests/test_commands.py::test_get_frequency_builds_correct_frame
Test Structure¶
tests/
├── conftest.py # Shared fixtures (FakeRadio, packet builders)
├── test_auth.py # Credential encoding, login/conninfo packets
├── test_commands.py # CI-V frame building and parsing
├── test_protocol.py # Header parsing/serialization
├── test_radio.py # IcomRadio high-level API (mocked transport)
├── test_transport.py # IcomTransport (mocked UDP)
├── test_rigctld_handler.py # Rigctld command handler unit tests
├── test_rigctld_protocol.py # Rigctld wire protocol parsing/formatting
├── test_rigctld_server.py # Rigctld server lifecycle tests
├── test_golden_protocol.py # Golden fixture runner (parse → execute → format)
├── test_server_wire.py # TCP wire integration tests (real server, mock radio)
├── test_data_mode.py # DATA/packet mode semantics
├── golden/
│ └── protocol_golden.json # 45 golden response fixtures (all commands + errors)
└── integration/ # Real hardware tests (require a radio)
├── test_connect.py
└── test_get_freq.py
Unit tests use mocked transports — no radio required. Integration tests are in tests/integration/ and require actual hardware.
Golden Protocol Tests¶
The tests/golden/ directory contains JSON fixtures that define the exact wire-level
input/output contract for the rigctld protocol. Each fixture specifies a command input,
mock radio state, and the expected byte-exact response in both normal and extended mode.
To run only golden tests:
When adding a new rigctld command, add corresponding fixtures to protocol_golden.json
to lock down the wire format.
Code Style¶
- Type annotations on all public functions
- Docstrings on all public classes and methods (Google/NumPy style)
- Follow existing code patterns
- No external dependencies for the core library (stdlib only)
Commit Messages¶
We use Conventional Commits:
feat: add AGC level control
fix: handle timeout during conninfo exchange
docs: add IC-705 setup instructions
test: add VFO swap command test
chore: bump version to 0.3.0
Adding a New CI-V Command¶
-
Add the command builder in
commands.py: -
Add a response parser if needed (also in
commands.py) -
Add the high-level method in
radio.py: -
Add a CLI command in
cli.pyif user-facing -
Write tests in
tests/test_commands.pyandtests/test_radio.py -
Update documentation:
docs/guide/commands.md— user-facing command docsdocs/api/radio.md— API referencedocs/api/commands.md— low-level command reference
Adding a New Radio Model¶
- Look up the radio's default CI-V address
- Test basic operations (frequency, mode, meters)
- Document any model-specific behavior
- Add to the radios table in
docs/guide/radios.md - Add the CI-V address constant in
commands.pyif desired
Reporting Bugs¶
Open an issue with:
- Radio model and firmware version
- Python version and OS
- Steps to reproduce
- Debug log output (
logging.basicConfig(level=logging.DEBUG)) - Expected vs actual behavior
Pull Requests¶
- Fork the repository
- Create a feature branch:
git checkout -b feat/my-feature - Make your changes with tests
- Ensure all tests pass:
pytest tests/test_*.py - Commit with conventional commit message
- Open a PR against
main