Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release/v0.2.0 #12

Merged
merged 3 commits into from
Jan 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "radio-telemetry-tracker-drone-comms-package"
version = "0.1.2"
version = "0.2.0"
description = ""
authors = ["Tyler Flar <[email protected]>"]
license = "Other"
Expand Down
2 changes: 1 addition & 1 deletion radio_telemetry_tracker_drone_comms_package/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Package initialization for the Radio Telemetry Tracker Drone Comms Package.
"""

__version__ = "0.1.2"
__version__ = "0.2.0"

from radio_telemetry_tracker_drone_comms_package.data_models import (
ConfigRequestData,
Expand Down
2 changes: 2 additions & 0 deletions radio_telemetry_tracker_drone_comms_package/data_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
class SyncRequestData:
"""Data container for synchronization request packets."""

ack_timeout: float
max_retries: int
packet_id: int | None = None
timestamp: int | None = None

Expand Down
22 changes: 16 additions & 6 deletions radio_telemetry_tracker_drone_comms_package/drone_comms.py
Original file line number Diff line number Diff line change
Expand Up @@ -553,16 +553,21 @@ def unregister_error_handler(self, callback: Callable[[ErrorData], None]) -> boo
# Public send methods
# --------------------------------------------------------------------------

def send_sync_request(self, _: SyncRequestData) -> tuple[int, bool, int]:
def send_sync_request(self, data: SyncRequestData) -> tuple[int, bool, int]:
"""Send a sync request packet.

Args:
data: Sync request data to send

Returns:
tuple: (packet_id, need_ack, timestamp)
"""
packet = RadioPacket()
packet.syn_rqt.base.packet_id = self.generate_packet_id()
packet.syn_rqt.base.need_ack = True
packet.syn_rqt.base.timestamp = _current_timestamp_us()
packet.syn_rqt.ack_timeout = data.ack_timeout
packet.syn_rqt.max_retries = data.max_retries
self.enqueue_packet(packet)
return (
packet.syn_rqt.base.packet_id,
Expand All @@ -581,7 +586,7 @@ def send_sync_response(self, data: SyncResponseData) -> tuple[int, bool, int]:
"""
packet = RadioPacket()
packet.syn_rsp.base.packet_id = self.generate_packet_id()
packet.syn_rsp.base.need_ack = False
packet.syn_rsp.base.need_ack = True
packet.syn_rsp.base.timestamp = _current_timestamp_us()
packet.syn_rsp.success = data.success
self.enqueue_packet(packet)
Expand Down Expand Up @@ -632,7 +637,7 @@ def send_config_response(self, data: ConfigResponseData) -> tuple[int, bool, int
"""
packet = RadioPacket()
packet.cfg_rsp.base.packet_id = self.generate_packet_id()
packet.cfg_rsp.base.need_ack = False
packet.cfg_rsp.base.need_ack = True
packet.cfg_rsp.base.timestamp = _current_timestamp_us()
packet.cfg_rsp.success = data.success
self.enqueue_packet(packet)
Expand Down Expand Up @@ -745,7 +750,7 @@ def send_start_response(self, data: StartResponseData) -> tuple[int, bool, int]:
"""
packet = RadioPacket()
packet.str_rsp.base.packet_id = self.generate_packet_id()
packet.str_rsp.base.need_ack = False
packet.str_rsp.base.need_ack = True
packet.str_rsp.base.timestamp = _current_timestamp_us()
packet.str_rsp.success = data.success
self.enqueue_packet(packet)
Expand Down Expand Up @@ -783,7 +788,7 @@ def send_stop_response(self, data: StopResponseData) -> tuple[int, bool, int]:
"""
packet = RadioPacket()
packet.stp_rsp.base.packet_id = self.generate_packet_id()
packet.stp_rsp.base.need_ack = False
packet.stp_rsp.base.need_ack = True
packet.stp_rsp.base.timestamp = _current_timestamp_us()
packet.stp_rsp.success = data.success
self.enqueue_packet(packet)
Expand All @@ -801,7 +806,7 @@ def send_error(self, _: ErrorData) -> tuple[int, bool, int]:
"""
packet = RadioPacket()
packet.err_pkt.base.packet_id = self.generate_packet_id()
packet.err_pkt.base.need_ack = False
packet.err_pkt.base.need_ack = True
packet.err_pkt.base.timestamp = _current_timestamp_us()
self.enqueue_packet(packet)
return (
Expand All @@ -818,6 +823,8 @@ def _extract_sync_request(self, packet: SyncRequestPacket) -> SyncRequestData:
return SyncRequestData(
packet_id=packet.base.packet_id,
timestamp=packet.base.timestamp,
ack_timeout=packet.ack_timeout,
max_retries=packet.max_retries,
)

def _extract_sync_response(self, packet: SyncResponsePacket) -> SyncResponseData:
Expand Down Expand Up @@ -923,6 +930,9 @@ def _extract_error(self, packet: ErrorPacket) -> ErrorData:
# --------------------------------------------------------------------------

def _handle_sync_request(self, data: SyncRequestData) -> None:
# Update ack_timeout and max_retries with values from sync request
self.ack_timeout = data.ack_timeout
self.max_retries = data.max_retries
self._invoke_handlers(self._sync_request_handlers, data)

def _handle_sync_response(self, data: SyncResponseData) -> None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ message AckPacket {

message SyncRequestPacket {
BasePacket base = 1;
float ack_timeout = 2;
int32 max_retries = 3;
}

message SyncResponsePacket {
Expand Down
13 changes: 11 additions & 2 deletions tests/test_data_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,22 @@
TEST_TARGET_FREQUENCIES = [100, 200, 300]
TEST_SYNC_PACKET_ID = 123
TEST_SYNC_TIMESTAMP = 456789
TEST_SYNC_ACK_TIMEOUT = 2.0
TEST_SYNC_MAX_RETRIES = 5


def test_sync_request_data() -> None:
"""Test SyncRequestData initialization and attribute access."""
data = SyncRequestData(packet_id=TEST_SYNC_PACKET_ID, timestamp=TEST_SYNC_TIMESTAMP)
"""Test SyncRequestData model."""
data = SyncRequestData(
packet_id=TEST_SYNC_PACKET_ID,
timestamp=TEST_SYNC_TIMESTAMP,
ack_timeout=TEST_SYNC_ACK_TIMEOUT,
max_retries=TEST_SYNC_MAX_RETRIES,
)
assert data.packet_id == TEST_SYNC_PACKET_ID # noqa: S101
assert data.timestamp == TEST_SYNC_TIMESTAMP # noqa: S101
assert data.ack_timeout == TEST_SYNC_ACK_TIMEOUT # noqa: S101
assert data.max_retries == TEST_SYNC_MAX_RETRIES # noqa: S101


def test_config_request_data() -> None:
Expand Down
11 changes: 7 additions & 4 deletions tests/test_drone_comms.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
RadioConfig,
)
from radio_telemetry_tracker_drone_comms_package.proto.packets_pb2 import RadioPacket
from tests.test_data_models import TEST_SYNC_ACK_TIMEOUT, TEST_SYNC_MAX_RETRIES


class MockInterface(Protocol):
Expand Down Expand Up @@ -88,11 +89,13 @@ def test_drone_comms_init_invalid_interface() -> None:


def test_drone_comms_send_sync_request(drone_comms: DroneComms) -> None:
"""Test sending sync request returns valid packet ID and timestamp."""
pid, need_ack, timestamp = drone_comms.send_sync_request(SyncRequestData())
"""Test sending sync request packet."""
pid, need_ack, timestamp = drone_comms.send_sync_request(
SyncRequestData(ack_timeout=TEST_SYNC_ACK_TIMEOUT, max_retries=TEST_SYNC_MAX_RETRIES),
)
assert pid > 0 # noqa: S101
assert need_ack is True # noqa: S101
assert pid != 0 # noqa: S101
assert timestamp != 0 # noqa: S101
assert timestamp > 0 # noqa: S101


def test_drone_comms_send_config_request(drone_comms: DroneComms) -> None:
Expand Down
49 changes: 41 additions & 8 deletions tools/gcs_fds_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ class GCSFDSCLI(cmd.Cmd):
intro = "Welcome to the GCS/FDS CLI. Type help or ? to list commands.\n"
prompt = "(gcs-fds) "

# Default values for sync request
DEFAULT_ACK_TIMEOUT = 2.0
DEFAULT_MAX_RETRIES = 5

# Argument indices for sync request
ACK_TIMEOUT_ARG_IDX = 1
MAX_RETRIES_ARG_IDX = 2

def __init__(self, radio_config: RadioConfig) -> None:
"""Initialize the CLI with radio configuration.

Expand Down Expand Up @@ -98,7 +106,17 @@ def do_start(self, arg: str) -> None: # noqa: ARG002
logger.info("Already started.")
return

self.drone_comms = DroneComms(radio_config=self.radio_config)
def on_ack_timeout(packet_id: int) -> None:
logger.warning("Packet %d acknowledgment timed out", packet_id)

def on_ack_success(packet_id: int) -> None:
logger.info("Packet %d successfully acknowledged", packet_id)

self.drone_comms = DroneComms(
radio_config=self.radio_config,
on_ack_callback=on_ack_timeout,
on_ack_success=on_ack_success,
)
self.drone_comms.start()
self.started = True
logger.info("Started drone communications.")
Expand Down Expand Up @@ -158,10 +176,7 @@ def do_unregister(self, arg: str) -> None:
logger.info("Unknown packet type: %s", pkt_type)
return

if (
pkt_type not in self.registered_callbacks
or not self.registered_callbacks[pkt_type]
):
if pkt_type not in self.registered_callbacks or not self.registered_callbacks[pkt_type]:
logger.info("No callbacks registered for '%s'.", pkt_type)
return

Expand All @@ -180,13 +195,31 @@ def do_unregister(self, arg: str) -> None:
else:
logger.info("Failed to unregister callbacks (unexpected error).")

def do_send_sync_request(self, arg: str) -> None: # noqa: ARG002
def do_send_sync_request(self, arg: str) -> None:
"""Send a sync request packet.

Args:
arg: Command argument (unused)
arg: Command argument in format: [ack_timeout] [max_retries]
"""
pid, ack, ts = self.drone_comms.send_sync_request(SyncRequestData())
# Default values
data = SyncRequestData(
ack_timeout=self.DEFAULT_ACK_TIMEOUT,
max_retries=self.DEFAULT_MAX_RETRIES,
)

# Parse arguments if provided
parts = arg.split()
if parts:
try:
if len(parts) >= self.ACK_TIMEOUT_ARG_IDX:
data.ack_timeout = float(parts[0])
if len(parts) >= self.MAX_RETRIES_ARG_IDX:
data.max_retries = int(parts[1])
except ValueError:
logger.exception("Invalid argument format")
return

pid, ack, ts = self.drone_comms.send_sync_request(data)
logger.info(
"Sent SyncRequest (packet_id=%s, need_ack=%s, timestamp=%s)",
pid,
Expand Down
Loading