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

created flight interface module #3

Merged
merged 47 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
99024a9
created data structure to hold local odometry data
ashum68 May 28, 2024
18b2893
created flight interface module
ashum68 May 28, 2024
2f82a1b
added multiprocessing worker and conversion methods
ashum68 May 29, 2024
850da7a
reformatting
ashum68 May 29, 2024
8bb801e
added type annotations
ashum68 May 29, 2024
5c88f71
added init function docstrings
ashum68 May 29, 2024
a4b9a0f
removed drone_odometry_local data structure
ashum68 May 30, 2024
88006d2
added integration test
ashum68 May 31, 2024
fb447f0
added unit tests
ashum68 Jun 2, 2024
841080f
removed conversions unit test
ashum68 Jun 2, 2024
da5e3bc
added init file and file docstrings
ashum68 Jun 2, 2024
28f1d36
added type annotations
ashum68 Jun 2, 2024
2db85c5
rearranged imports
ashum68 Jun 2, 2024
000dd72
changed integration test variable name to silence linter
ashum68 Jun 2, 2024
0dda125
added pymap3d to requirements
ashum68 Jun 4, 2024
7341fa7
added print statements to integration test
ashum68 Jun 4, 2024
85dd3d7
removed print statements from unit test
ashum68 Jun 4, 2024
6130258
reformatting
ashum68 Jun 4, 2024
49400c9
debugging
ashum68 Jun 4, 2024
f082218
fixed imports
ashum68 Jun 4, 2024
d08bf76
created flight interface module
ashum68 May 28, 2024
720a21b
added multiprocessing worker and conversion methods
ashum68 May 29, 2024
374c95a
reformatting
ashum68 May 29, 2024
a556d68
added type annotations
ashum68 May 29, 2024
c44f8b3
added init function docstrings
ashum68 May 29, 2024
3cbb857
added integration test
ashum68 May 31, 2024
7e8e688
added unit tests
ashum68 Jun 2, 2024
fc4f4c9
removed conversions unit test
ashum68 Jun 2, 2024
89f1d47
added init file and file docstrings
ashum68 Jun 2, 2024
f1b5fa6
added type annotations
ashum68 Jun 2, 2024
bfc099f
rearranged imports
ashum68 Jun 2, 2024
178ffa3
changed integration test variable name to silence linter
ashum68 Jun 2, 2024
26ccf35
added pymap3d to requirements
ashum68 Jun 4, 2024
5a0bdb0
added print statements to integration test
ashum68 Jun 4, 2024
5f5265a
removed print statements from unit test
ashum68 Jun 4, 2024
0af4028
reformatting
ashum68 Jun 4, 2024
acec4b1
debugging
ashum68 Jun 4, 2024
6bbe84b
fixed imports
ashum68 Jun 4, 2024
e43863d
debugged unit test, rebased with main
ashum68 Jun 4, 2024
9bf0e4d
Merge branch 'flight_interface' of https://github.com/UWARG/obstacle-…
ashum68 Jun 4, 2024
d03d585
debugged unit test
ashum68 Jun 4, 2024
f9b1a29
removed mission planner tests
ashum68 Jun 5, 2024
77e430c
Merge branch 'main' into flight_interface
ashum68 Jun 5, 2024
4eae193
debugging
ashum68 Jun 5, 2024
e29414a
reformatting
ashum68 Jun 5, 2024
146fead
rearranged imports
ashum68 Jun 5, 2024
2fcd90c
changed timeout from int to float
ashum68 Jun 5, 2024
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
Empty file.
53 changes: 53 additions & 0 deletions modules/flight_interface/conversions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""
Drone position conversions to and from local (NED) and global (geodetic) space.
"""

import pymap3d as pymap
TongguangZhang marked this conversation as resolved.
Show resolved Hide resolved

from .. import drone_odometry_local
from ..common.mavlink.modules import drone_odometry


def position_global_to_local(
global_position: drone_odometry.DronePosition, home_location: drone_odometry.DronePosition
) -> "tuple[bool, drone_odometry_local.DronePositionLocal | None]":
"""
Converts global position (geodetic) to local position (NED).
"""
north, east, down = pymap.geodetic2ned(
global_position.latitude,
global_position.longitude,
global_position.altitude,
home_location.latitude,
home_location.longitude,
home_location.altitude,
)

result, local_position = drone_odometry_local.DronePositionLocal.create(north, east, down)
if not result:
return False, None

return True, local_position


def position_local_to_global(
local_position: drone_odometry_local.DronePositionLocal,
home_location: drone_odometry.DronePosition,
) -> "tuple[bool, drone_odometry.DronePosition | None]":
"""
Converts local position (NED) to global position (geodetic).
"""
latitude, longitude, altitude = pymap.ned2geodetic(
local_position.north,
local_position.east,
local_position.down,
home_location.latitude,
home_location.longitude,
home_location.altitude,
)

result, global_position = drone_odometry.DronePosition.create(latitude, longitude, altitude)
if not result:
return False, None

return True, global_position
66 changes: 66 additions & 0 deletions modules/flight_interface/flight_interface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"""
Creates flight controller and produces local drone odometry coupled with a timestamp.
"""

from . import conversions
TongguangZhang marked this conversation as resolved.
Show resolved Hide resolved
from .. import drone_odometry_local

from ..common.mavlink.modules import drone_odometry
from ..common.mavlink.modules import flight_controller


class FlightInterface:
"""
Create flight controller and sets home location.
"""

__create_key = object()

@classmethod
def create(cls, address: str, timeout_home: float) -> "tuple[bool, FlightInterface | None]":
"""
address: TCP address or port.
timeout_home: Timeout for home location in seconds.
"""
result, controller = flight_controller.FlightController.create(address)
if not result:
return False, None

result, home_location = controller.get_home_location(timeout_home)
if not result:
return False, None

return True, FlightInterface(cls.__create_key, controller, home_location)

def __init__(
self,
create_key: object,
controller: flight_controller.FlightController,
home_location: drone_odometry.DroneLocation,
) -> None:
"""
Private constructor, use create() method.
"""

assert create_key is FlightInterface.__create_key, "Use create() method"

self.controller = controller
self.home_location = home_location

def run(self) -> "tuple[bool, drone_odometry_local.DroneOdometryLocal | None]":
"""
Returns local drone odometry with timestamp.
"""
result, odometry = self.controller.get_odometry()
if not result:
return False, None

global_position = odometry.position

result, local_position = conversions.global_to_local(global_position, self.home_location)
if not result:
return False, None

drone_orientation = odometry.orientation

return drone_odometry_local.DroneOdometryLocal.create(local_position, drone_orientation)
40 changes: 40 additions & 0 deletions modules/flight_interface/flight_interface_worker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""
Fetches local drone odometry.
"""

import time
TongguangZhang marked this conversation as resolved.
Show resolved Hide resolved

from . import flight_interface
from ..worker import queue_wrapper
from ..worker import worker_controller


def flight_interface_worker(
address: str,
timeout: float,
period: float,
output_queue: queue_wrapper.QueueWrapper,
controller: worker_controller.WorkerController,
) -> None:
"""
Worker process.

address, timeout is initial setting.
period is minimum period between loops.
output_queue is the data queue.
controller is how the main process communicates to this worker process.
"""
result, interface = flight_interface.FlightInterface.create(address, timeout)
if not result:
return

while not controller.is_exit_requested():
controller.check_pause()

time.sleep(period)

result, value = interface.run()
if not result:
continue

output_queue.queue.put(value)
70 changes: 70 additions & 0 deletions tests/integration/test_flight_interface_worker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
"""
Flight interface worker integration test.
"""

import multiprocessing as mp
TongguangZhang marked this conversation as resolved.
Show resolved Hide resolved
import queue
import time

from worker import worker_controller
from worker import queue_wrapper

from modules import drone_odometry_local
from modules.flight_interface import flight_interface_worker

QUEUE_MAX_SIZE = 10
FLIGHT_INTERFACE_ADDRESS = "tcp:127.0.0.1:14550"
FLIGHT_INTERFACE_TIMEOUT = 10
FLIGHT_INTERFACE_WORKER_PERIOD = 0.1


def main() -> int:
"""
Main function.
"""

controller = worker_controller.WorkerController()
manager = mp.Manager()

output_queue = queue_wrapper.QueueWrapper(manager, QUEUE_MAX_SIZE)

args = (
FLIGHT_INTERFACE_ADDRESS,
FLIGHT_INTERFACE_TIMEOUT,
FLIGHT_INTERFACE_WORKER_PERIOD,
output_queue,
controller,
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

place these within the call to make it clear it's the args for this specific process


worker = mp.Process(target=flight_interface_worker, args=args)

worker.start()

time.sleep(3)

while True:
try:
input_data: drone_odometry_local.DroneOdometryLocal = output_queue.queue.get()
assert (
str(type(input_data))
== "<class 'modules.drone_odometry_local\
.DroneOdometryLocal'>"
)
assert input_data.local_position is not None
assert input_data.drone_orientation is not None
except queue.Empty:
break

controller.request_exit()

worker.join()

return 0


if __name__ == "__main__":
result_main = main()
if result_main < 0:
print("error: " + result_main)

print("Done.")
92 changes: 92 additions & 0 deletions tests/unit/test_flight_interface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
"""
Test for flight interface by printing to device.
"""

import time
import pytest


from modules.flight_interface import flight_interface

from ..common.mavlink.modules import drone_odometry


@pytest.fixture()
def create_flight_interface_instance(
address: str, timeout: float
) -> "tuple[bool, FlightInterface | None]":
"""
Construct a flight interface instance.
"""
result, flight_interface_instance = flight_interface.FlightInterface.create(address, timeout)

yield result, flight_interface_instance


@pytest.fixture()
def create_drone_position(
latitude: float, longitude: float, altitude: float
) -> drone_odometry.DronePosition:
"""
Contruct a drone position instance.
"""
result, global_position = drone_odometry.DronePosition.create(latitude, longitude, altitude)
assert result
assert global_position is not None

yield global_position


class TestFlightInterface:
"""
Flight interface tests.
"""

MISSION_PLANNER_ADDRESS = "tcp:127.0.0.1:14550"
TIMEOUT = 1.0

DELAY_TIME = 1.0

def test_create_invalid_address(self) -> None:
"""
Test create method using a valid Mission Planner IP address.
"""
result, instance = create_flight_interface_instance(None, self.TIMEOUT)
assert not result
assert instance is None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

follow the test set up convention:

  1. inputs and expected value - expected_result = False, expected_instance = None
  2. run the thing being tests - actual_result, actual_instance = create_flight_interface...
  3. assertions - assert actual == expected


def test_create_valid_address(self) -> None:
"""
Test create method using a valid Mission Planner IP address.
"""
result, instance = create_flight_interface_instance(
self.MISSION_PLANNER_ADDRESS, self.TIMEOUT
)
assert result
assert instance.controller is not None
assert instance.home_location is not None

def test_flight_interface(self) -> None:
"""
Tests run function and prints results.
"""

result, flight_interface_instance = create_flight_interface_instance(
self.MISSION_PLANNER_ADDRESS, self.TIMEOUT
)

for _ in range(8):
result, local_odometry = flight_interface_instance.run()
assert result
assert local_odometry is not None

print("north: " + str(local_odometry.local_position.north))
print("east: " + str(local_odometry.local_position.east))
print("down: " + str(local_odometry.local_position.down))
print("roll: " + str(local_odometry.drone_orientation.roll))
print("pitch: " + str(local_odometry.drone_orientation.pitch))
print("yaw: " + str(local_odometry.drone_orientation.yaw))
print("timestamp: " + str(local_odometry.timestamp))
print("")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't having prints in tests because we don't look at this output, unit tests are always assertions, this function can be part of the integration test


time.sleep(self.DELAY_TIME)
21 changes: 0 additions & 21 deletions tests/unit/test_sample.py

This file was deleted.

Loading