From 1ae11251367efffb9002b9480a00854cd8becf04 Mon Sep 17 00:00:00 2001 From: Tyler Flar Date: Thu, 23 Jan 2025 23:42:17 -0800 Subject: [PATCH] Update README with detailed package information, add .gitignore entries for Protobuf files, refactor TowerComms message handling methods, and adjust data types in packets.proto. --- .github/workflows/lint.yml | 40 +++++ .github/workflows/test.yml | 40 +++++ .gitignore | 4 + LICENSE | 11 ++ README.md | 144 +++++++++++++++++- .../proto/packets.proto | 4 +- .../tower_comms.py | 8 +- tests/test_meshtastic_mesh.py | 15 +- tests/test_tower_comms.py | 6 +- 9 files changed, 255 insertions(+), 17 deletions(-) create mode 100644 .github/workflows/lint.yml create mode 100644 .github/workflows/test.yml create mode 100644 LICENSE diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..8cd62f1 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,40 @@ +name: Run Ruff Linter + +on: + pull_request: + branches: + - main + - dev + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - name: Check out code + uses: actions/checkout@v3 + + - name: Set up Python 3.12 + uses: actions/setup-python@v4 + with: + python-version: '3.12' + + - name: Cache dependencies + uses: actions/cache@v3 + with: + path: | + ~/.cache/pypoetry + ~/.cache/pip + key: ${{ runner.os }}-poetry-${{ hashFiles('poetry.lock') }} + restore-keys: | + ${{ runner.os }}-poetry- + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install poetry + poetry install --with dev + + - name: Run ruff linter + run: | + poetry run ruff check . \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..ffcb89c --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,40 @@ +name: Run Tests + +on: + pull_request: + branches: + - main + - dev + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Check out code + uses: actions/checkout@v3 + + - name: Set up Python 3.12 + uses: actions/setup-python@v4 + with: + python-version: '3.12' + + - name: Cache dependencies + uses: actions/cache@v3 + with: + path: | + ~/.cache/pypoetry + ~/.cache/pip + key: ${{ runner.os }}-poetry-${{ hashFiles('poetry.lock') }} + restore-keys: | + ${{ runner.os }}-poetry- + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install poetry + poetry install --with dev + + - name: Run tests + run: | + poetry run pytest \ No newline at end of file diff --git a/.gitignore b/.gitignore index ef1e442..65daaf8 100644 --- a/.gitignore +++ b/.gitignore @@ -176,3 +176,7 @@ cython_debug/ # Poetry poetry.lock + +# Protobuf +*_pb2.py + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4f9b5f3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,11 @@ +This software is Copyright © 2024 The Regents of the University of California. All Rights Reserved. Permission to copy, modify, and distribute this software and its documentation for educational, research and non-profit purposes, without fee, and without a written agreement is hereby granted, provided that the above copyright notice, this paragraph and the following three paragraphs appear in all copies. Permission to make commercial use of this software may be obtained by contacting: + +Office of Innovation and Commercialization +9500 Gilman Drive, Mail Code 0910 +University of California +La Jolla, CA 92093-0910 +innovation@ucsd.edu + +This software program and documentation are copyrighted by The Regents of the University of California. The software program and documentation are supplied “as is”, without any accompanying services from The Regents. The Regents does not warrant that the operation of the program will be uninterrupted or error-free. The end-user understands that the program was developed for research purposes and is advised not to rely exclusively on the program for any reason. + +IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN “AS IS” BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. \ No newline at end of file diff --git a/README.md b/README.md index a30a8b4..689ef2c 100644 --- a/README.md +++ b/README.md @@ -1 +1,143 @@ -# radio-telemetry-tracker-tower-comms-package \ No newline at end of file +# Radio Telemetry Tracker Tower Communications Package (Comms Package) + +The **Radio Telemetry Tracker Tower Communications Package** is a Python-based library designed to facilitate mesh network communication between radio telemetry towers using Meshtastic devices. It provides a robust framework for configuration management, ping data transmission, and error handling between towers in a distributed network. + +> Note: This package is intended as a shared component for tower-based radio telemetry systems. It provides the communication infrastructure between towers but is not meant for standalone use. + +## Table of Contents +- [Radio Telemetry Tracker Tower Communications Package (Comms Package)](#radio-telemetry-tracker-tower-communications-package-comms-package) + - [Table of Contents](#table-of-contents) + - [Overview](#overview) + - [Prerequisites](#prerequisites) + - [Installation](#installation) + - [Configuration](#configuration) + - [Usage](#usage) + - [Message Types](#message-types) + - [Development](#development) + - [License](#license) + +## Overview + +This package provides: + +- **Mesh Network Communication**: Interface with Meshtastic devices for tower-to-tower communication +- **Message Types**: Protobuf-defined messages for configuration, pings, and errors +- **Acknowledgments**: Built-in support for message acknowledgments and retries +- **Position Tracking**: Integration with GPS data from Meshtastic devices +- **Simulation Support**: Simulated mesh interface for development and testing + +## Prerequisites + +- Python 3.13 +- Poetry for dependency management +- Meshtastic-compatible device for real deployment +- Protocol Buffers compiler for development + +## Installation + +1. Add as a dependency to your project: + ```bash + poetry add git+https://github.com/UCSD-E4E/radio-telemetry-tracker-tower-comms-package.git + ``` +2. Or clone for development: + ```bash + bash + git clone https://github.com/UCSD-E4E/radio-telemetry-tracker-tower-comms-package.git + + cd radio-telemetry-tracker-tower-comms-package + + poetry install + ``` + +## Configuration + +The library supports two interface types: + +1. **Meshtastic Interface**: + ```python + from radio_telemetry_tracker_tower_comms_package import NodeConfig, TowerComms + + config = NodeConfig( + interface_type="meshtastic", + device="/dev/ttyUSB0", # Serial port for Meshtastic device + ) + ``` +2. **Simulated Interface** (for testing): + ```python + from radio_telemetry_tracker_tower_comms_package import NodeConfig, TowerComms + + config = NodeConfig( + interface_type="simulated", + numeric_id=1, # Unique node ID + user_id="Tower1" # Human-readable name + ) + + ``` + +## Usage + +Basic usage pattern: + +1. **Initialize communications**: + ```python + from radio_telemetry_tracker_tower_comms_package import TowerComms, NodeConfig + + def on_ack_success(packet_id: int): + print(f"Packet {packet_id} acknowledged") + + def on_ack_failure(packet_id: int): + print(f"Packet {packet_id} failed") + + config = NodeConfig(interface_type="meshtastic", device="/dev/ttyUSB0") + comms = TowerComms(config, on_ack_success, on_ack_failure) + ``` +2. **Register message handlers**: + ```python + def handle_ping(data: PingData): + print(f"Ping received from {data.node_id} at freq {data.frequency}") + + comms.register_ping_handler(handle_ping) + ``` +3. **Start communication**: + ```python + comms.start() # Opens the mesh interface + ``` +4. **Send messages**: + ```python + comms.send_request_config(destination=2, want_ack=True) + + ping_data = PingData(frequency=915000000, amplitude=0.8, latitude=32.7, longitude=-117.1, altitude=100) + comms.send_ping(ping_data, destination=None) # Broadcast + + ``` +5. **Stop communication**: + ```python + comms.stop() # Closes the mesh interface + ``` + + +## Message Types + +- **ConfigData**: Tower configuration parameters +- **PingData**: Radio ping detection data with GPS coordinates +- **ErrorData**: Error messages and diagnostics +- **RequestConfigData**: Configuration request messages + +## Development + +1. Install development dependencies: + ```bash + poetry install --with dev + ``` +2. Run tests: + ```bash + poetry run pytest + ``` +3. Check code style: + ```bash + poetry run ruff check . --fix + ``` + +## License + +This project is licensed under the terms specified in the [LICENSE](LICENSE) file. \ No newline at end of file diff --git a/radio_telemetry_tracker_tower_comms_package/proto/packets.proto b/radio_telemetry_tracker_tower_comms_package/proto/packets.proto index 14daf30..29d1bfd 100644 --- a/radio_telemetry_tracker_tower_comms_package/proto/packets.proto +++ b/radio_telemetry_tracker_tower_comms_package/proto/packets.proto @@ -22,8 +22,8 @@ message ConfigPacket { bool enable_test_data = 6; int32 ping_width_ms = 7; int32 ping_min_snr = 8; - int32 ping_max_len_mult = 9; - int32 ping_min_len_mult = 10; + float ping_max_len_mult = 9; + float ping_min_len_mult = 10; repeated int32 target_frequencies = 11; } diff --git a/radio_telemetry_tracker_tower_comms_package/tower_comms.py b/radio_telemetry_tracker_tower_comms_package/tower_comms.py index c85b945..49253fa 100644 --- a/radio_telemetry_tracker_tower_comms_package/tower_comms.py +++ b/radio_telemetry_tracker_tower_comms_package/tower_comms.py @@ -389,16 +389,16 @@ def _on_raw_packet(self, raw_data: bytes) -> None: which = pb.WhichOneof("msg") if which == "request_config": data = self._extract_request_config(pb.request_config) - self._handle_request_config(data) + self._invoke_request_config(data) elif which == "config": data = self._extract_config(pb.config) - self._handle_config(data) + self._invoke_config(data) elif which == "ping": data = self._extract_ping(pb.ping) - self._handle_ping(data) + self._invoke_ping(data) elif which == "error": data = self._extract_error(pb.error) - self._handle_error(data) + self._invoke_error(data) else: logger.debug("Received unknown message type: %s", which) except Exception: diff --git a/tests/test_meshtastic_mesh.py b/tests/test_meshtastic_mesh.py index 8c1b53a..1302500 100644 --- a/tests/test_meshtastic_mesh.py +++ b/tests/test_meshtastic_mesh.py @@ -1,8 +1,9 @@ """Tests for MeshtasticMeshInterface implementation.""" -from unittest.mock import MagicMock, patch +from unittest.mock import MagicMock, create_autospec, patch import pytest +from meshtastic.serial_interface import SerialInterface from radio_telemetry_tracker_tower_comms_package.mesh_interface import MeshConnectionError from radio_telemetry_tracker_tower_comms_package.meshtastic_mesh import MeshtasticMeshInterface @@ -20,11 +21,11 @@ def test_meshtastic_mesh_connect_success(mock_serial_interface: MagicMock) -> No mock_serial_interface.assert_called_once_with("test_device") -@pytest.mark.usefixtures("mock_serial_interface") -@patch("meshtastic.serial_interface.SerialInterface", side_effect=Exception("Port not found")) def test_meshtastic_mesh_connect_failure() -> None: """Test handling of connection failure to Meshtastic device.""" - mesh = MeshtasticMeshInterface(serial_device="nonexistent") - with pytest.raises(MeshConnectionError) as exc: - mesh.connect() - assert "Failed to connect" in str(exc.value) # noqa: S101 + with patch("meshtastic.serial_interface.SerialInterface", create_autospec(SerialInterface)) as mock: + mock.side_effect = Exception("Port not found") + mesh = MeshtasticMeshInterface(serial_device="nonexistent") + with pytest.raises(MeshConnectionError) as exc: + mesh.connect() + assert "Failed to connect" in str(exc.value) # noqa: S101 diff --git a/tests/test_tower_comms.py b/tests/test_tower_comms.py index 8c75655..49b5b35 100644 --- a/tests/test_tower_comms.py +++ b/tests/test_tower_comms.py @@ -24,12 +24,12 @@ TEST_SAMPLE_RATE = 48000 TEST_CENTER_FREQ = 915000000 TEST_RUN_NUM = 999 -TEST_PING_WIDTH = 15.0 -TEST_MIN_SNR = 5.0 +TEST_PING_WIDTH = 15 +TEST_MIN_SNR = 5 TEST_MAX_LEN_MULT = 2.0 TEST_MIN_LEN_MULT = 1.0 TEST_TARGET_FREQS = [100, 200, 300] -TEST_PING_FREQ = 440.0 +TEST_PING_FREQ = 440 TEST_PING_AMP = 0.75 TEST_PING_LAT = 37.0 TEST_PING_LON = -122.0