|
| 1 | +.. _best_practices: |
| 2 | + |
| 3 | +============== |
| 4 | +Best Practices |
| 5 | +============== |
| 6 | + |
| 7 | +This guide provides a broad overview of how to use the API, its main programming idioms |
| 8 | +and best practices. More detail information is linked from each section. |
| 9 | + |
| 10 | + |
| 11 | +General considerations |
| 12 | +====================== |
| 13 | + |
| 14 | +DroneKit-Python communicates with vehicle autopilots using the MAVLink protocol, |
| 15 | +which defines how commands, telemetry and vehicle settings/parameters |
| 16 | +are sent between vehicles, companion computers, ground stations and other systems on |
| 17 | +a MAVLink network. |
| 18 | + |
| 19 | +Some general considerations from using this protocol are: |
| 20 | + |
| 21 | +* Messages and message acknowledgments are not guaranteed to arrive (the protocol is not "lossless"). |
| 22 | +* Commands may be silently ignored by the Autopilot if it is not in a state where it can |
| 23 | + safely act on them. |
| 24 | +* Command acknowledgment and completion messages are not sent in most cases |
| 25 | + (and if sent, may not arrive). |
| 26 | +* Commands may be interrupted before completion. |
| 27 | +* Autopilots may choose to interpret the protocol in slightly different ways. |
| 28 | +* Commands can arrive at the autopilot from multiple sources. |
| 29 | + |
| 30 | +Developers should code defensively. Where possible: |
| 31 | + |
| 32 | +* Check that a vehicle is in a state to obey a command (for example, |
| 33 | + poll on :py:func:`Vehicle.is_armable <dronekit.Vehicle.is_armable>` |
| 34 | + before trying to arm the vehicle). |
| 35 | +* Don't assume that a command has succeeded until the changed behaviour is observed. |
| 36 | + In particular we recommend a launch sequence where you check that the mode and arming |
| 37 | + have succeeded before attempting to take off. |
| 38 | +* Monitor for state changes and react accordingly. |
| 39 | + For example, if the user changes the mode from ``GUIDED`` your script should |
| 40 | + stop sending commands. |
| 41 | +* Verify that your script can run inside the normal latency limits for message passing |
| 42 | + from the vehicle and tune any monitoring appropriately. |
| 43 | + |
| 44 | + |
| 45 | +Connecting |
| 46 | +========== |
| 47 | + |
| 48 | +In most cases you'll use the normal way to :ref:`connect to a vehicle <connecting_vehicle>`, |
| 49 | +setting ``wait_ready=True`` to ensure that the vehicle is already populated with attributes |
| 50 | +when the :py:func:`connect() <dronekit.connect>` returns: |
| 51 | + |
| 52 | +.. code:: python |
| 53 | +
|
| 54 | + from dronekit import connect |
| 55 | +
|
| 56 | + # Connect to the Vehicle (in this case a UDP endpoint) |
| 57 | + vehicle = connect('REPLACE_connection_string_for_your_vehicle', wait_ready=True) |
| 58 | + |
| 59 | +The ``connect()`` call will sometimes fail with an exception. |
| 60 | +Additional information about an exception can be obtained by |
| 61 | +running the connect within a ``try-catch`` block as shown: |
| 62 | + |
| 63 | +.. code-block:: python |
| 64 | + |
| 65 | + import dronekit |
| 66 | + import socket |
| 67 | + import exceptions |
| 68 | +
|
| 69 | +
|
| 70 | + try: |
| 71 | + dronekit.connect('REPLACE_connection_string_for_your_vehicle', heartbeat_timeout=15) |
| 72 | + |
| 73 | + # Bad TCP connection |
| 74 | + except socket.error: |
| 75 | + print 'No server exists!' |
| 76 | + except dronekit.APIException: |
| 77 | + print 'Timeout!' |
| 78 | + |
| 79 | + # Bad TTY connection |
| 80 | + except exceptions.OSError as e: |
| 81 | + print 'No serial exists!' |
| 82 | + except dronekit.APIException: |
| 83 | + print 'Timeout!' |
| 84 | + |
| 85 | + # Other error |
| 86 | + except: |
| 87 | + print 'Some other error!' |
| 88 | +
|
| 89 | +.. tip:: |
| 90 | + |
| 91 | + The default ``heartbeat_timeout`` on connection is 30 sections. Usually a connection will |
| 92 | + succeed quite quickly, so you may wish to reduce this in the ``connect()`` method as shown in the |
| 93 | + code snippet above. |
| 94 | + |
| 95 | +If a connection succeeds from a ground station, but not from DroneKit-Python it may be that your baud |
| 96 | +rate is incorrect for your hardware. This rate can also be set in the ``connect()`` method. |
| 97 | + |
| 98 | + |
| 99 | +Launch sequence |
| 100 | +=============== |
| 101 | + |
| 102 | +Generally you should use the standard launch sequence described in :doc:`../guide/taking_off`: |
| 103 | + |
| 104 | +* Poll on :py:func:`Vehicle.is_armable <dronekit.Vehicle.is_armable>` |
| 105 | + until the vehicle is ready to arm. |
| 106 | +* Set the :py:attr:`Vehicle.mode <dronekit.Vehicle.mode>` to ``GUIDED`` |
| 107 | +* Set :py:attr:`Vehicle.armed <dronekit.Vehicle.armed>` to ``True`` and |
| 108 | + poll on the same attribute until the vehicle is armed. |
| 109 | +* Call :py:func:`Vehicle.simple_takeoff <dronekit.Vehicle.simple_takeoff>` |
| 110 | + with a target altitude. |
| 111 | +* Poll on the altitude and allow the code to continue only when it is reached. |
| 112 | + |
| 113 | +The approach ensures that commands are only sent to the vehicle when it is able |
| 114 | +to act on them (e.g. we know :py:func:`Vehicle.is_armable <dronekit.Vehicle.is_armable>` |
| 115 | +is ``True`` before trying to arm, we know |
| 116 | +:py:attr:`Vehicle.armed <dronekit.Vehicle.armed>` is ``True`` before we take off). |
| 117 | +It also makes debugging takeoff problems a lot easier. |
| 118 | + |
| 119 | + |
| 120 | +Movement commands |
| 121 | +================= |
| 122 | + |
| 123 | +DroneKit-Python provides :py:func:`Vehicle.simple_goto <dronekit.Vehicle.simple_goto>` for moving to a specific position (at a defined speed). It is also possible to control movement by sending commands to specify the vehicle's :ref:`velocity components <guided_mode_copter_velocity_control>`. |
| 124 | + |
| 125 | +.. note:: |
| 126 | + |
| 127 | + As with :py:func:`Vehicle.simple_takeoff <dronekit.Vehicle.simple_takeoff>`, movement |
| 128 | + commands are asynchronous, and will be interrupted if another command arrives |
| 129 | + before the vehicle reaches its target. Calling code should block and wait (or |
| 130 | + check that the operation is complete) before preceding to the next command. |
| 131 | + |
| 132 | +For more information see: :ref:`guided_mode_copter`. |
| 133 | + |
| 134 | + |
| 135 | +Vehicle information |
| 136 | +=================== |
| 137 | + |
| 138 | +Vehicle state information is exposed through vehicle *attributes* which can be read and observed (and in some cases written) |
| 139 | +and vehicle settings which can be read, written, iterated and observed using *parameters* (a special attribute). All the attributes are documented in :doc:`../guide/vehicle_state_and_parameters`. |
| 140 | + |
| 141 | +Attributes are populated by MAVLink messages from the vehicle. |
| 142 | +Information read from an attribute may not precisely reflect the actual value on the vehicle. Commands sent |
| 143 | +to the vehicle may not arrive, or may be ignored by the autopilot. |
| 144 | + |
| 145 | +If low-latency is critical, we recommend you verify that the update rate is achievable and |
| 146 | +perhaps modify script behaviour if :py:attr:`Vehicle.last_heartbeat <dronekit.Vehicle.last_heartbeat>` falls outside |
| 147 | +a useful range. |
| 148 | + |
| 149 | +When setting attributes, poll their values to confirm that they have changed. This applies, in particular, |
| 150 | +to :py:attr:`Vehicle.armed <dronekit.Vehicle.armed>` and :py:attr:`Vehicle.mode <dronekit.Vehicle.mode>`. |
| 151 | + |
| 152 | + |
| 153 | + |
| 154 | +Missions and waypoints |
| 155 | +====================== |
| 156 | + |
| 157 | +DroneKit-Python can also :ref:`create and modify autonomous missions <auto_mode_vehicle_control>`. |
| 158 | + |
| 159 | +While it is possible to construct DroneKit-Python apps by dynamically constructing missions "on the fly", we recommend you use guided mode for Copter apps. This generally results in a better experience. |
| 160 | + |
| 161 | +.. tip:: |
| 162 | + |
| 163 | + If a mission command is not available in guided mode, |
| 164 | + it can be useful to switch to a mission and call it, then change |
| 165 | + back to normal guided mode operation. |
| 166 | + |
| 167 | + |
| 168 | +Monitor and react to state changes |
| 169 | +================================== |
| 170 | + |
| 171 | +Almost all attributes can be observed - see :ref:`vehicle_state_observe_attributes` for more information. |
| 172 | + |
| 173 | +Exactly what state information you observe, and how you react to it, depends on your particular script: |
| 174 | + |
| 175 | +* Most standalone apps should monitor the :py:func:`Vehicle.mode <dronekit.Vehicle.mode>` and |
| 176 | + stop sending commands if the mode changes unexpectedly (this usually indicates |
| 177 | + that the user has taken control of the vehicle). |
| 178 | +* Apps might monitor :py:func:`Vehicle.last_heartbeat <dronekit.Vehicle.last_heartbeat>` |
| 179 | + and could attempt to reconnect if the value gets too high. |
| 180 | +* Apps could monitor :py:func:`Vehicle.system_status <dronekit.Vehicle.system_status>` |
| 181 | + for ``CRITICAL`` or ``EMERGENCY`` in order to implement specific emergency handling. |
| 182 | + |
| 183 | + |
| 184 | +Sleep the script when not needed |
| 185 | +================================ |
| 186 | + |
| 187 | +Sleeping your script appropriately can reduce the memory overhead. |
| 188 | + |
| 189 | +For example, at low speeds you might only need to check whether you've reached a target every few seconds. |
| 190 | +Using ``time.sleep(2)`` between checks will be more efficient than checking more often. |
| 191 | + |
| 192 | + |
| 193 | +Exiting a script |
| 194 | +================ |
| 195 | + |
| 196 | +Scripts should call :py:func:`Vehicle.close() <dronekit.Vehicle.close>` |
| 197 | +before exiting to ensure that all messages have flushed before the script completes: |
| 198 | + |
| 199 | +.. code:: python |
| 200 | +
|
| 201 | + # About to exit script |
| 202 | + vehicle.close() |
| 203 | + |
| 204 | +
|
| 205 | +Subclass Vehicle |
| 206 | +===================================== |
| 207 | + |
| 208 | +If you need to use functionality that is specific to particular hardware, we |
| 209 | +recommend you subclass :py:class:`Vehicle <dronekit.Vehicle>` and pass this new class into |
| 210 | +:py:func:`connect() <dronekit.connect>`. |
| 211 | + |
| 212 | +:doc:`../examples/create_attribute` shows how you can do this. |
| 213 | + |
| 214 | + |
| 215 | + |
| 216 | + |
| 217 | +Debugging |
| 218 | +========= |
| 219 | + |
| 220 | +DroneKit-Python apps are ordinary standalone Python scripts, and can be :doc:`debugged using standard Python methods <../guide/debugging>` (including the debugger/IDE of your choice). |
| 221 | + |
| 222 | + |
| 223 | +Launching scripts |
| 224 | +================= |
| 225 | + |
| 226 | +Scripts are run from an ordinary Python command prompt. For example: |
| 227 | + |
| 228 | +.. code:: bash |
| 229 | +
|
| 230 | + python some_python_script.py [arguments] |
| 231 | +
|
| 232 | +Command line arguments are passed into the script as ``sys.argv`` variables (the normal) |
| 233 | +and you can use these directly or via an argument parser (e.g. |
| 234 | +`argparse <https://docs.python.org/3/library/argparse.html>`_). |
| 235 | + |
| 236 | + |
| 237 | +Current script directory |
| 238 | +======================== |
| 239 | + |
| 240 | +You can use normal Python methods for getting file system information: |
| 241 | + |
| 242 | +.. code-block:: python |
| 243 | +
|
| 244 | + import os.path |
| 245 | + full_directory_path_of_current_script = os.path.dirname(os.path.abspath(__file__)) |
| 246 | +
|
0 commit comments