Contributing - icom-lan
Description: Python library for controlling Icom transceivers over LAN

Skip to content

# Contributing

Contributions are welcome! Here's how to get started.

## Development Setup

    git clone https://github.com/morozsm/icom-lan.git
    cd icom-lan
    pip install -e ".[dev]"

Or with uv:

    uv sync --extra dev

## 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)
    └── 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.

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

1. Add the command builder in `commands.py`:

       def get_agc(to_addr: int = IC_7610_ADDR, from_addr: int = CONTROLLER_ADDR) -> bytes:
           """Build a 'get AGC' CI-V command."""
           return build_civ_frame(to_addr, from_addr, 0x16, sub=0x12)

2. Add a response parser if needed (also in `commands.py`)

3. Add the high-level method in `radio.py`:

       async def get_agc(self) -> int:
           """Get the AGC level."""
           self._check_connected()
           civ = get_agc(to_addr=self._radio_addr)
           resp = await self._send_civ_raw(civ)
           return parse_level_response(resp)

4. Add a CLI command in `cli.py` if user-facing

5. Write tests in `tests/test_commands.py` and `tests/test_radio.py`

6. Update documentation:

   - `docs/guide/commands.md` — user-facing command docs
   - `docs/api/radio.md` — API reference
   - `docs/api/commands.md` — low-level command reference

## Adding a New Radio Model

1. Look up the radio's default CI-V address
2. Test basic operations (frequency, mode, meters)
3. Document any model-specific behavior
4. Add to the radios table in `docs/guide/radios.md`
5. Add the CI-V address constant in `commands.py` if 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

1. Fork the repository
2. Create a feature branch: `git checkout -b feat/my-feature`
3. Make your changes with tests
4. Ensure all tests pass: `pytest tests/test_*.py`
5. Commit with conventional commit message
6. Open a PR against `main`