14 KiB
Development Guide
Last Updated: May 18, 2026
This guide provides instructions for setting up a development environment, running tests, and contributing to the Akita Meshtastic Meshcore Bridge (AMMB) project.
Setting Up Development Environment
Use Python 3.10 or newer for development and test runs. The dev toolchain now
requires pytest 9.0.3+ to address CVE-2025-71176, and upstream only ships
that fix for Python 3.10+.
-
Clone the repository:
git clone https://github.com/AkitaEngineering/akita-meshtastic-meshcore-bridge.git cd akita-meshtastic-meshcore-bridge -
Create and activate a Python virtual environment:
python -m venv venv # On Windows: .\venv\Scripts\activate # On Linux/macOS: source venv/bin/activateUsing a virtual environment isolates project dependencies.
-
Install runtime and development dependencies:
pip install -r requirements.txt pip install -r requirements-dev.txtThis installs runtime dependencies (
meshtastic,meshcore,asyncio-mqtt,textual,pyserial,pypubsub,paho-mqtt) and development tools (pytest,pytest-cov,flake8,mypy).
Project Structure
akita-meshtastic-meshcore-bridge/
├── ammb/ # Main package
│ ├── __init__.py
│ ├── api.py # Sync REST API server
│ ├── api_async.py # Async REST API server
│ ├── bridge.py # Sync bridge orchestrator
│ ├── bridge_async.py # Async bridge orchestrator
│ ├── config_handler.py # Configuration management
│ ├── health.py # Health monitoring
│ ├── meshcore_handler.py # Sync serial handler
│ ├── meshcore_async_handler.py # Async serial handler
│ ├── meshtastic_handler.py # Meshtastic network handler
│ ├── message_logger.py # Message persistence
│ ├── metrics.py # Metrics collection
│ ├── mqtt_handler.py # Sync MQTT handler
│ ├── mqtt_async_handler.py # Async MQTT handler
│ ├── protocol.py # Serial protocol handlers
│ ├── rate_limiter.py # Rate limiting
│ ├── tui.py # Textual command center
│ ├── utils.py # Utility functions
│ └── validator.py # Message validation
├── tests/ # Test suite
│ ├── __init__.py
│ ├── conftest.py
│ ├── test_config_handler.py
│ ├── test_protocol.py
│ ├── test_tui.py
│ └── test_run_bridge_tui.py
├── docs/ # Documentation
│ ├── api.md
│ ├── architecture.md
│ ├── configuration.md
│ ├── development.md
│ └── usage.md
├── examples/ # Example files
│ ├── config.ini.example
│ └── meshcore_simulator.py
├── run_bridge.py # Sync runtime entry point
├── run_bridge_async.py # Async runtime entry point
├── run_bridge_tui.py # Full-screen terminal command center entry point
├── requirements.txt # Runtime dependencies
├── requirements-dev.txt # Development dependencies
└── README.md # Project overview
Running Tests
The project uses pytest for automated testing.
-
Ensure your virtual environment is active.
-
Navigate to the project root directory.
-
Run all tests:
pytestThis will discover and run all tests located in the
tests/directory. -
Run tests with coverage report:
pytest --cov=ammb --cov-report term-missingThis runs the tests and generates a report showing which lines of the source code in the
ammb/directory were executed by the tests. -
Run specific test file:
pytest tests/test_protocol.py -
Run focused command-center tests:
pytest tests/test_tui.py tests/test_run_bridge_tui.py -
Run with verbose output:
pytest -v -
Smoke-test runtime entrypoints when changing startup behavior or operator UX:
python run_bridge.py python run_bridge_tui.py python run_bridge_async.pyIf the command center exits with an unhandled error, inspect
ammb_tui_crash.login the project root.
Code Style and Linting
We use flake8 for checking code style against PEP 8 guidelines and common errors.
-
Ensure your virtual environment is active.
-
Navigate to the project root directory.
-
Run flake8:
flake8 ammb/ tests/ run_bridge.py run_bridge_async.py run_bridge_tui.pyThis will report any style violations or potential errors. Aim for zero reported issues.
-
Check specific file:
flake8 ammb/bridge.py
Project linting policy ✅
- Max line length: 79 characters (flake8 E501). When long lines are found, prefer targeted wrapping or splitting (for example: split long strings, break complex expressions, or use short helper variables) rather than increasing the line length limit.
- Third-party packages: Do not edit files under
.venvor other external package directories to satisfy linter rules. Instead, exclude those directories from lint runs (we include.venvin the project's.flake8file). - Logging: Prefer parameterized logging calls (e.g.,
logger.info("Connected to %s", port)) instead of long f-strings to keep messages shorter and avoid unnecessary formatting overhead. - Fix process: When addressing E501 issues in project files, make conservative, behavior-preserving edits (wrap strings, reflow docstrings, or adjust logging). Re-run tests and
mypyafter each change to ensure no regressions.
Example: In this revision we fixed several E501 cases in ammb/ and examples/ by wrapping long strings and using parameterized logging; tests and mypy were re-run to confirm the project remains correct.
Static Type Checking
We use mypy for static type checking to catch potential type-related errors before runtime.
-
Ensure your virtual environment is active.
-
Navigate to the project root directory.
-
Run mypy:
mypy ammb/ run_bridge.py run_bridge_async.py run_bridge_tui.pyThis will analyze the type hints in the code and report any inconsistencies or errors. Aim for zero reported issues.
-
Check specific module:
mypy ammb/bridge.py
Code Quality Standards
Type Hints
All function signatures should include type hints:
def process_message(message: Dict[str, Any]) -> bool:
"""Process a message and return success status."""
...
Documentation Strings
All public functions and classes should have docstrings:
class MessageHandler:
"""Handles message processing and validation."""
def validate(self, data: str) -> bool:
"""
Validate message data.
Args:
data: Message data to validate
Returns:
True if valid, False otherwise
"""
...
Error Handling
Use appropriate exception handling:
try:
result = risky_operation()
except SpecificException as e:
logger.error(f"Operation failed: {e}", exc_info=True)
return None
Thread Safety
All shared data structures must use locks:
class ThreadSafeCounter:
def __init__(self):
self._count = 0
self._lock = threading.Lock()
def increment(self):
with self._lock:
self._count += 1
Contribution Guidelines
We welcome contributions! Please follow these steps:
-
Fork the repository on GitHub.
-
Clone your fork locally:
git clone https://github.com/YOUR_FORK_USERNAME/akita-meshtastic-meshcore-bridge.git -
Create a new branch for your feature or bug fix:
git checkout -b feature/your-feature-name # or git checkout -b fix/issue-description -
Set up your development environment as described above.
-
Make your changes. Ensure you:
- Follow the existing code style
- Add type hints to all functions
- Write docstrings for public APIs
- Add tests for new features or bug fixes
- Update documentation if necessary
- Ensure all tests pass (
pytest) - Ensure linters pass (
flake8 ammb/ tests/ run_bridge.py run_bridge_async.py run_bridge_tui.py) - Ensure type checks pass (
mypy ammb/ run_bridge.py run_bridge_async.py run_bridge_tui.py)
-
Commit your changes with clear and descriptive commit messages:
git commit -m "Add feature: description of what was added" git commit -m "Fix bug: description of what was fixed" -
Push your branch to your fork:
git push origin feature/your-feature-name -
Open a Pull Request (PR) from your fork's branch to the
mainbranch of the original repository. -
Clearly describe the changes made in the PR description and link to any relevant issues.
-
Respond to feedback or requested changes during the code review process.
Adding New Features
Adding New Serial Protocols
To support a different serial protocol:
-
Create a new class in
ammb/protocol.pythat inherits fromMeshcoreProtocolHandler. -
Implement the required methods:
class YourProtocol(MeshcoreProtocolHandler): def read(self, serial_port) -> Optional[bytes]: """Read data from serial port.""" ... def encode(self, data: Dict[str, Any]) -> Optional[bytes]: """Encode dictionary to bytes.""" ... def decode(self, raw_data: bytes) -> Optional[Dict[str, Any]]: """Decode bytes to dictionary.""" ... -
Update the
get_serial_protocol_handler()factory function inammb/protocol.py:_serial_protocol_handlers = { 'json_newline': JsonNewlineProtocol, 'raw_serial': RawSerialProtocol, 'your_protocol': YourProtocol, # Add here } -
Add the new protocol name as an option for the
SERIAL_PROTOCOLsetting in:configuration.mdexamples/config.ini.example
-
Add tests for your new protocol handler in
tests/test_protocol.py.
Adding New External Transports
To add a new external transport (e.g., HTTP, WebSocket):
-
Create a new handler class similar to
MeshcoreHandlerorMQTTHandler:class YourTransportHandler: def __init__(self, config, to_meshtastic_queue, from_meshtastic_queue, shutdown_event): # Initialize with metrics, health, validator, rate_limiter ... def connect(self) -> bool: # Implement connection logic ... def start_publisher(self): # Start background threads if needed ... def stop(self): # Clean shutdown ... -
Integrate with existing systems:
- Use
get_metrics()for metrics collection - Use
get_health_monitor()for health tracking - Use
MessageValidatorfor validation - Use
RateLimiterfor rate limiting
- Use
-
Add to
Bridgeclass initialization inammb/bridge.py. -
Update configuration handler to support new transport settings.
-
Add tests for the new transport handler.
Adding New API Endpoints
To add new REST API endpoints:
-
Add handler method in
BridgeAPIHandlerclass inammb/api.py:def _handle_your_endpoint(self): """Handle your new endpoint.""" data = {"your": "data"} self._send_response(200, data) -
Add route in
do_GET()ordo_POST()method:elif path == '/api/your_endpoint': self._handle_your_endpoint() -
Update API documentation in
usage.md.
Testing Guidelines
Unit Tests
Write unit tests for individual functions and classes:
def test_message_validation():
validator = MessageValidator()
valid_msg = {"destination": "^all", "text": "Hello"}
is_valid, error = validator.validate_meshtastic_message(valid_msg)
assert is_valid
assert error is None
Integration Tests
Write integration tests for component interactions:
def test_bridge_initialization():
config = load_config("test_config.ini")
bridge = Bridge(config)
assert bridge.meshtastic_handler is not None
assert bridge.external_handler is not None
Test Fixtures
Use pytest fixtures for common test setup:
@pytest.fixture
def test_config(tmp_path):
config_path = tmp_path / "config.ini"
# Create test configuration
return config_path
Debugging
Enable Debug Logging
Set LOG_LEVEL = DEBUG in config.ini for detailed logging.
Use Python Debugger
Add breakpoints in code:
import pdb; pdb.set_trace()
Or use IDE debugger with breakpoints.
Check Metrics
Use the REST API to check metrics:
curl http://localhost:8080/api/metrics
Check Health Status
Use the REST API to check health:
curl http://localhost:8080/api/health
Release Process
When preparing a release:
- Update version in
ammb/__init__.py - Update
CHANGELOG.mdwith release notes - Update documentation dates
- Run full test suite
- Run linters and type checkers
- Smoke-test the relevant operator entrypoints (
run_bridge.py,run_bridge_tui.py, and/orrun_bridge_async.py) - Create git tag
- Push to repository
Getting Help
If you need help with development:
- Check existing documentation
- Review code comments
- Check test examples
- Open an issue on GitHub
- Contact maintainers