Skip to content

Commit 2397c53

Browse files
committed
initial commit
0 parents  commit 2397c53

29 files changed

Lines changed: 1208 additions & 0 deletions

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
build
2+
install
3+
log
4+
__pycache__

.gitmodules

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[submodule "src/px4_msgs"]
2+
path = src/px4_msgs
3+
url = https://github.com/PX4/px4_msgs.git
4+
branch = release/1.14

src/drone_controller/drone_controller/__init__.py

Whitespace-only changes.
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
import time
2+
import os
3+
4+
import rclpy
5+
from rclpy.node import Node
6+
from rclpy.qos import qos_profile_sensor_data
7+
8+
from px4_msgs.msg import LogMessage, VehicleStatus, OffboardControlMode, TrajectorySetpoint, VehicleCommandAck, \
9+
VehicleCommand, VehicleLocalPosition, VehicleGlobalPosition
10+
11+
from std_msgs.msg import String
12+
13+
from enum import Enum
14+
15+
import math
16+
17+
class nav_state(Enum):
18+
NAVIGATION_STATE_MANUAL = 0 # Manual mode
19+
NAVIGATION_STATE_ALTCTL = 1 # Altitude control mode
20+
NAVIGATION_STATE_POSCTL = 2 # Position control mode
21+
NAVIGATION_STATE_AUTO_MISSION = 3 # Auto mission mode
22+
NAVIGATION_STATE_AUTO_LOITER = 4 # Auto loiter mode
23+
NAVIGATION_STATE_AUTO_RTL = 5 # Auto return to launch mode
24+
NAVIGATION_STATE_UNUSED3 = 8 # Free slot
25+
NAVIGATION_STATE_UNUSED = 9 # Free slot
26+
NAVIGATION_STATE_ACRO = 10 # Acro mode
27+
NAVIGATION_STATE_UNUSED1 = 11 # Free slot
28+
NAVIGATION_STATE_DESCEND = 12 # Descend mode (no position control)
29+
NAVIGATION_STATE_TERMINATION = 13 # Termination mode
30+
NAVIGATION_STATE_OFFBOARD = 14
31+
NAVIGATION_STATE_STAB = 15 # Stabilized mode
32+
NAVIGATION_STATE_UNUSED2 = 16 # Free slot
33+
NAVIGATION_STATE_AUTO_TAKEOFF = 17 # Takeoff
34+
NAVIGATION_STATE_AUTO_LAND = 18 # Land
35+
NAVIGATION_STATE_AUTO_FOLLOW_TARGET = 19 # Auto Follow
36+
NAVIGATION_STATE_AUTO_PRECLAND = 20 # Precision land with landing target
37+
NAVIGATION_STATE_ORBIT = 21 # Orbit in a circle
38+
NAVIGATION_STATE_AUTO_VTOL_TAKEOFF = 22 # Takeoff, transition, establish loiter
39+
NAVIGATION_STATE_MAX = 23
40+
41+
42+
class DroneController(Node):
43+
def __init__(self):
44+
super().__init__("drone_controller")
45+
self.initialize_node()
46+
47+
def timer_display_info_callback(self):
48+
os.system('clear')
49+
self.get_logger().info(f"Mode : {nav_state(self.vehicle_status_msg_.nav_state).name} ({'ARM' if self.vehicle_status_msg_.arming_state == VehicleStatus.ARMING_STATE_ARMED else 'DISARM'})")
50+
self.get_logger().info(f"Vehicle local_position(ned) : ({self.vehicle_local_position_msg_.x:.2f}, {self.vehicle_local_position_msg_.y:.2f}, {self.vehicle_local_position_msg_.z:.2f})")
51+
self.get_logger().info(f"last command : {self.last_command}")
52+
53+
self.info_count += 1
54+
55+
match self.info_count:
56+
case 50:
57+
self.control_arm()
58+
case 55:
59+
self.control_takeoff(5)
60+
case 145:
61+
self.control_offboard()
62+
case 155:
63+
self.setpoint[0] = self.vehicle_local_position_msg_.x - 50
64+
self.setpoint[1] = self.vehicle_local_position_msg_.y
65+
self.setpoint[2] = self.vehicle_local_position_msg_.z
66+
self.control_setpoint(self.vehicle_local_position_msg_.x - 50, self.vehicle_local_position_msg_.y, self.vehicle_local_position_msg_.z, self.vehicle_local_position_msg_.heading)
67+
68+
if self.info_count > 155 and math.sqrt((self.setpoint[0] - self.vehicle_local_position_msg_.x)**2 + (self.setpoint[1] - self.vehicle_local_position_msg_.y)**2 + (self.setpoint[2] - self.vehicle_local_position_msg_.z)**2) < 1:
69+
self.control_land()
70+
71+
def vehicle_local_position_callback(self, msg):
72+
self.vehicle_local_position_msg_ = msg
73+
74+
def vehicle_global_position_callback(self, msg):
75+
self.vehicle_global_position_msg_ = msg
76+
77+
def vehicle_status_callback(self, msg):
78+
self.vehicle_status_msg_ = msg
79+
80+
def timer_ocm_callback(self):
81+
self.ocm_publisher_.publish(self.ocm_msg_qhac_)
82+
83+
def control_arm(self):
84+
self.last_command = "arm"
85+
arm_cmd = VehicleCommand()
86+
arm_cmd.target_system = self.system_id_
87+
arm_cmd.command = VehicleCommand.VEHICLE_CMD_COMPONENT_ARM_DISARM
88+
arm_cmd.param1 = 1.0
89+
arm_cmd.confirmation = True
90+
arm_cmd.from_external = True
91+
self.vehicle_command_publisher_.publish(arm_cmd)
92+
93+
def control_takeoff(self, altitude):
94+
self.last_command = "takeoff"
95+
takeoff_cmd = VehicleCommand()
96+
takeoff_cmd.target_system = self.system_id_
97+
takeoff_cmd.command = VehicleCommand.VEHICLE_CMD_NAV_TAKEOFF
98+
takeoff_cmd.param1 = -1.0
99+
takeoff_cmd.param2 = 0.0
100+
takeoff_cmd.param3 = 0.0
101+
takeoff_cmd.param4 = self.vehicle_local_position_msg_.heading
102+
takeoff_cmd.param5 = self.vehicle_global_position_msg_.lat
103+
takeoff_cmd.param6 = self.vehicle_global_position_msg_.lon
104+
takeoff_cmd.param7 = self.vehicle_global_position_msg_.alt + altitude
105+
self.vehicle_command_publisher_.publish(takeoff_cmd)
106+
107+
def control_disarm(self):
108+
self.last_command = "disarm"
109+
disarm_cmd = VehicleCommand()
110+
disarm_cmd.target_system = self.system_id_
111+
disarm_cmd.command = VehicleCommand.VEHICLE_CMD_COMPONENT_ARM_DISARM
112+
disarm_cmd.param1 = 0.0
113+
disarm_cmd.confirmation = True
114+
self.vehicle_command_publisher_.publish(disarm_cmd)
115+
116+
def control_offboard(self):
117+
self.last_command = "offboard"
118+
offboard_cmd = VehicleCommand()
119+
offboard_cmd.target_system = self.system_id_
120+
offboard_cmd.command = VehicleCommand.VEHICLE_CMD_DO_SET_MODE
121+
offboard_cmd.param1 = 1.0
122+
offboard_cmd.param2 = 6.0 #PX4_CUSTOM_MAIN_MODE_OFFBOARD
123+
offboard_cmd.from_external = True
124+
self.vehicle_command_publisher_.publish(offboard_cmd)
125+
126+
def control_setpoint(self, x, y, z, heading=None):
127+
self.last_command = "move"
128+
setpoint_cmd = TrajectorySetpoint()
129+
setpoint_cmd.position[0] = x
130+
setpoint_cmd.position[1] = y
131+
setpoint_cmd.position[2] = z
132+
if heading is not None:
133+
setpoint_cmd.yaw = heading
134+
self.traj_setpoint_publisher_.publish(setpoint_cmd)
135+
136+
def control_land(self):
137+
self.last_command = "land"
138+
landing_cmd = VehicleCommand()
139+
landing_cmd.target_system = self.system_id_
140+
landing_cmd.command = VehicleCommand.VEHICLE_CMD_NAV_LAND
141+
landing_cmd.from_external = True
142+
self.vehicle_command_publisher_.publish(landing_cmd)
143+
144+
def initialize_node(self):
145+
self.declare_parameter('system_id', 1)
146+
self.system_id_ = self.get_parameter('system_id').get_parameter_value().integer_value
147+
self.last_command = "idle"
148+
149+
self.get_logger().info(
150+
f"Configure drone_controller {self.system_id_}")
151+
self.topic_prefix_manager_ = f"vehicle{self.get_parameter('system_id').get_parameter_value().integer_value}/manager/"
152+
self.topic_prefix_fmu_ = f"vehicle{self.get_parameter('system_id').get_parameter_value().integer_value}/fmu/"
153+
self.vehicle_status_subscriber = self.create_subscription(VehicleStatus,
154+
f'{self.topic_prefix_fmu_}out/vehicle_status',
155+
self.vehicle_status_callback,
156+
qos_profile_sensor_data)
157+
self.vehicle_status_msg_ = VehicleStatus()
158+
159+
self.ocm_msg_qhac_ = OffboardControlMode()
160+
self.ocm_msg_qhac_.position = True
161+
self.ocm_msg_qhac_.velocity = False
162+
self.ocm_msg_qhac_.acceleration = False
163+
self.ocm_msg_qhac_.attitude = False
164+
self.ocm_msg_qhac_.body_rate = False
165+
self.ocm_msg_qhac_.actuator = False
166+
self.ocm_publisher_ = self.create_publisher(OffboardControlMode,
167+
f'{self.topic_prefix_fmu_}in/offboard_control_mode',
168+
qos_profile_sensor_data)
169+
170+
self.traj_setpoint_publisher_ = self.create_publisher(TrajectorySetpoint,
171+
f'{self.topic_prefix_fmu_}in/trajectory_setpoint',
172+
qos_profile_sensor_data)
173+
174+
self.vehicle_command_publisher_ = self.create_publisher(VehicleCommand,
175+
f'{self.topic_prefix_fmu_}in/vehicle_command',
176+
qos_profile_sensor_data)
177+
178+
self.vehicle_local_position_subscriber_ = self.create_subscription(VehicleLocalPosition,
179+
f'{self.topic_prefix_fmu_}out/vehicle_local_position',
180+
self.vehicle_local_position_callback,
181+
qos_profile_sensor_data)
182+
self.vehicle_local_position_msg_ = VehicleLocalPosition()
183+
184+
self.vehicle_global_position_subscriber = self.create_subscription(VehicleGlobalPosition,
185+
f'{self.topic_prefix_fmu_}out/vehicle_global_position',
186+
self.vehicle_global_position_callback,
187+
qos_profile_sensor_data)
188+
self.vehicle_global_position_msg_ = VehicleGlobalPosition()
189+
190+
timer_period_ocm = 0.1
191+
self.timer_ocm_ = self.create_timer(timer_period_ocm, self.timer_ocm_callback)
192+
193+
self.display_info = self.create_timer(timer_period_ocm, self.timer_display_info_callback)
194+
self.info_count = 0
195+
self.setpoint = [0, 0, 0]
196+
197+
198+
199+
def main(args=None):
200+
rclpy.init(args=args)
201+
202+
drone_controller = DroneController()
203+
204+
rclpy.spin(drone_controller)
205+
206+
# Destroy the node explicitly
207+
# (optional - otherwise it will be done automatically
208+
# when the garbage collector destroys the node object)
209+
drone_controller.destroy_node()
210+
rclpy.shutdown()
211+
212+
213+
if __name__ == '__main__':
214+
main()

src/drone_controller/package.xml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?xml version="1.0"?>
2+
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
3+
<package format="3">
4+
<name>drone_controller</name>
5+
<version>0.0.0</version>
6+
<description>TODO: Package description</description>
7+
<maintainer email="kmk6061602@naver.com">kmk</maintainer>
8+
<license>TODO: License declaration</license>
9+
10+
<test_depend>ament_copyright</test_depend>
11+
<test_depend>ament_flake8</test_depend>
12+
<test_depend>ament_pep257</test_depend>
13+
<test_depend>python3-pytest</test_depend>
14+
15+
<export>
16+
<build_type>ament_python</build_type>
17+
</export>
18+
</package>

src/drone_controller/resource/drone_controller

Whitespace-only changes.

src/drone_controller/setup.cfg

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[develop]
2+
script_dir=$base/lib/drone_controller
3+
[install]
4+
install_scripts=$base/lib/drone_controller

src/drone_controller/setup.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from setuptools import find_packages, setup
2+
3+
package_name = 'drone_controller'
4+
5+
setup(
6+
name=package_name,
7+
version='0.0.0',
8+
packages=find_packages(exclude=['test']),
9+
data_files=[
10+
('share/ament_index/resource_index/packages',
11+
['resource/' + package_name]),
12+
('share/' + package_name, ['package.xml']),
13+
],
14+
install_requires=['setuptools'],
15+
zip_safe=True,
16+
maintainer='kmk',
17+
maintainer_email='kmk6061602@naver.com',
18+
description='TODO: Package description',
19+
license='TODO: License declaration',
20+
tests_require=['pytest'],
21+
entry_points={
22+
'console_scripts': [
23+
'drone_controller = drone_controller.drone_controller:main'
24+
],
25+
},
26+
)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Copyright 2015 Open Source Robotics Foundation, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from ament_copyright.main import main
16+
import pytest
17+
18+
19+
# Remove the `skip` decorator once the source file(s) have a copyright header
20+
@pytest.mark.skip(reason='No copyright header has been placed in the generated source file.')
21+
@pytest.mark.copyright
22+
@pytest.mark.linter
23+
def test_copyright():
24+
rc = main(argv=['.', 'test'])
25+
assert rc == 0, 'Found errors'
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Copyright 2017 Open Source Robotics Foundation, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from ament_flake8.main import main_with_errors
16+
import pytest
17+
18+
19+
@pytest.mark.flake8
20+
@pytest.mark.linter
21+
def test_flake8():
22+
rc, errors = main_with_errors(argv=[])
23+
assert rc == 0, \
24+
'Found %d code style errors / warnings:\n' % len(errors) + \
25+
'\n'.join(errors)

0 commit comments

Comments
 (0)