diff --git a/docs/user-guides/sbs_connect_log_param.md b/docs/user-guides/sbs_connect_log_param.md index a708744bc..f468a8cb6 100644 --- a/docs/user-guides/sbs_connect_log_param.md +++ b/docs/user-guides/sbs_connect_log_param.md @@ -11,7 +11,7 @@ On this step by step guide we will show you how to connect to your Crazyflie thr We will assume that you already know this before you start with the tutorial: * Some basic experience with python -* Followed the [crazyflie getting started guide](https://www.bitcraze.io/documentation/tutorials/getting-started-with-crazyflie-2-x/). +* Followed the [Crazyflie getting started guide](https://www.bitcraze.io/documentation/tutorials/getting-started-with-crazyflie-2-x/). * Able to connect the crazyflie to the CFClient and look at the log tabs and parameters (here is a [userguide](https://www.bitcraze.io/documentation/repository/crazyflie-clients-python/master/userguides/userguide_client/)). @@ -23,7 +23,7 @@ Make sure that you have [python3](https://www.python.org), which should contain This should have been installed if you installed the cfclient already (on a linux system), but it is always good to double check this :) -## Step 1. Connecting with the crazyflie +## Step 1. Connecting with the Crazyflie ### Begin the python script @@ -32,7 +32,7 @@ Open up a python script anywhere that is convenient for you. We use Visual Studi * For python editor: select file->new * For VS code: select file-> new file -You can call it `connect_log_param.py` (that is what we are using in this tutorial) +You can call it `connect_log_param.py` (that is what we are using in this tutorial). Then you would need to start with the following standard python libraries. @@ -66,7 +66,7 @@ After these imports, start the script with: uri = uri_helper.uri_from_env(default='radio://0/80/2M/E7E7E7E7E7') ``` -This is the radio uri of the crazyflie, it can be set by setting the environment variable `CFLIB_URI`, if not set it uses the default. It should be probably fine but if you do not know what the uri of your Crazyfie is you can check that with an usb cable and looking at the config ([here](https://www.bitcraze.io/documentation/repository/crazyflie-clients-python/master/userguides/userguide_client/#firmware-configuration) are the instructions) +This is the radio uri of the Crazyflie, it can be set by setting the environment variable `CFLIB_URI`, if not set it uses the default. It should be probably fine but if you do not know what the uri of your Crazyfie is you can check that with an usb cable and looking at the config ([here](https://www.bitcraze.io/documentation/repository/crazyflie-clients-python/master/userguides/userguide_client/#firmware-configuration) are the instructions). ### Main @@ -83,7 +83,7 @@ if __name__ == '__main__': The `syncCrazyflie` will create a synchronous Crazyflie instance with the specified link_uri. As you can see we are currently calling an non-existing function, so you will need to make that function first before you run the script. -### Function for connecting with the crazyflie +### Function for connecting with the Crazyflie Start a function above the main function (but below the URI) which you call simple connect: @@ -99,6 +99,7 @@ def simple_connect(): ### Run the script +Make sure your Crazyflie is on and your Crazyradio is connected to your computer, and that the Crazyflie is not connected to anything else (like the cfclient). Now run the script in your terminal: @@ -111,12 +112,12 @@ Now I will disconnect :'( ``` -The script connected with your Crazyflie, synced and disconnected after a few seconds. You can see that the M4 LED is flashing yellow, which means that the Crazyflie is connected to the script, but as soon as it leaves the `simple_connect()` function, the LED turns of. The Crazyflie is no longer connected +The script connected with your Crazyflie, synced and disconnected after a few seconds. You can see that the M4 LED is flashing yellow, which means that the Crazyflie is connected to the script, but as soon as it leaves the `simple_connect()` function, the LED turns of. The Crazyflie is no longer connected. Not super exciting stuff yet but it is a great start! It is also a good test if everything is correctly configured on your system. -If you are getting an error, retrace your steps or check if your code matches the entire code underneath here. Also make sure your Crazyflie is on and your crazyradio PA connected to you computer, and that the Crazyflie is not connected to anything else (like the cfclient). If everything is peachy, please continue to the next part! +If you are getting an error, retrace your steps or check if your code matches the entire code underneath here. If everything is peachy, please continue to the next part! ```python import logging @@ -125,9 +126,10 @@ import time import cflib.crtp from cflib.crazyflie import Crazyflie from cflib.crazyflie.syncCrazyflie import SyncCrazyflie +from cflib.utils import uri_helper # URI to the Crazyflie to connect to -uri = 'radio://0/80/2M/E7E7E7E7E7' +uri = uri_helper.uri_from_env(default='radio://0/80/2M/E7E7E7E7E7') def simple_connect(): @@ -148,13 +150,13 @@ if __name__ == '__main__': -Alright, now taking a step up. We will now add to the script means to read out logging variables! +Alright, now taking a step up. We will now add means to read out logging variables to the script! ### More imports -Now we need to add several imports on the top of the script connect_log_param.py +Now we need to add imports to the top of the script connect_log_param.py: ```python ... @@ -167,7 +169,7 @@ from cflib.crazyflie.syncLogger import SyncLogger from the Crazyflie * The SyncLogger class provides synchronous access to log data from the Crazyflie. -Also add the following underneath URI +Also add the following underneath URI: ```python # Only output errors from the logging framework @@ -205,7 +207,7 @@ Notice that now you will need to include the SyncCrazyflie instance (`scf`) and Now the logging instances will be inserted by adding the following after you configured the lg_stab: ```python - with SyncLogger(scf, lg_stab) as logger: + with SyncLogger(scf, logconf) as logger: for log_entry in logger: @@ -221,14 +223,14 @@ Now the logging instances will be inserted by adding the following after you con ### Test the script: -First change the `simple_connect()` in _main_ in `simple_log(scf, lg_stab)`. Now run the script (`python3 connect_log_param.py`) like before. +First change the `simple_connect()` in _main_ to `simple_log(scf, lg_stab)`. Now run the script (`python3 connect_log_param.py`) like before. If everything is fine it should continuously print the logging variables, like this: `[1486704][]: {'stabilizer.roll': -0.054723262786865234, 'stabilizer.pitch': 0.006269464734941721, 'stabilizer.yaw': -0.008503230288624763}` -If you want to continuously receive the messages in the for loop, remove the `break`. You can stop the script with _ctrl+c_ +If you want to continuously receive the messages in the for loop, remove the `break`. You can stop the script with _ctrl+c_. If you are getting errors, check if your script corresponds with the full code: ```python @@ -238,12 +240,13 @@ import time import cflib.crtp from cflib.crazyflie import Crazyflie from cflib.crazyflie.syncCrazyflie import SyncCrazyflie +from cflib.utils import uri_helper from cflib.crazyflie.log import LogConfig from cflib.crazyflie.syncLogger import SyncLogger # URI to the Crazyflie to connect to -uri = 'radio://0/80/2M/E7E7E7E7E7' +uri = uri_helper.uri_from_env(default='radio://0/80/2M/E7E7E7E7E7') # Only output errors from the logging framework logging.basicConfig(level=logging.ERROR) @@ -296,7 +299,7 @@ def simple_log_async(scf, logconf): cf.log.add_config(logconf) ``` -Here you add the logging configuration to to the logging framework of the Crazyflie. It will check if the log configuration is part of the TOC, which is a list of all the logging variables defined in the Crazyflie. You can test this out by changing one of the `lg_stab` variables to a completely bogus name like `'not.real'`. In this case you would receive the following message: +Here you add the logging configuration to the logging framework of the Crazyflie. It will check if the log configuration is part of the TOC, which is a list of all the logging variables defined in the Crazyflie. You can test this out by changing one of the `lg_stab` variables to a completely bogus name like `'not.real'`. In this case you would receive the following message: `KeyError: 'Variable not.real not in TOC'` @@ -308,7 +311,7 @@ def log_stab_callback(timestamp, data, logconf): print('[%d][%s]: %s' % (timestamp, logconf.name, data)) ``` -This callback will be called once the log variables have received it and prints the contents. The callback function added to the logging framework by adding it to the log config in `simple_log_async(..)`: +This callback will be called once the log variables have received it and prints the contents. The callback function is added to the logging framework by adding it to the log config in `simple_log_async(..)`: ```python logconf.data_received_cb.add_callback(log_stab_callback) @@ -328,7 +331,7 @@ Make sure to replace the `simple_log(...)` to `simple_log_async(...)` in the `__ `[18101][Stabilizer]: {'stabilizer.roll': -174.58396911621094, 'stabilizer.pitch': 42.82120132446289, 'stabilizer.yaw': 166.29837036132812}` -If something went wrong, check if your script corresponds to the this: +If something went wrong, check if your script corresponds to this: ```python import logging @@ -337,12 +340,13 @@ import time import cflib.crtp from cflib.crazyflie import Crazyflie from cflib.crazyflie.syncCrazyflie import SyncCrazyflie +from cflib.utils import uri_helper from cflib.crazyflie.log import LogConfig from cflib.crazyflie.syncLogger import SyncLogger # URI to the Crazyflie to connect to -uri = 'radio://0/80/2M/E7E7E7E7E7' +uri = uri_helper.uri_from_env(default='radio://0/80/2M/E7E7E7E7E7') # Only output errors from the logging framework logging.basicConfig(level=logging.ERROR) @@ -355,7 +359,7 @@ def simple_log_async(scf, logconf): cf.log.add_config(logconf) logconf.data_received_cb.add_callback(log_stab_callback) logconf.start() - time.sleep(5) + time.sleep(5) # Your possibility to interact with the Crazyflie logconf.stop() (...) @@ -430,7 +434,7 @@ If you would run the script now you will also get this message: This means that the Crazyflie has changed the parameter value to 2, which is another methods it uses for state estimation. This can also be done to change the color on the ledring, or to initiate the highlevel commander. -What it can't do is to set a Read Only (RO) parameter, only Read Write (RW) parameters, which can be checked by the parameter TOC in the CFclient. You can check this by changing the parameter name to group `'CPU' ` and name `flash'`. Then you will get the following error: +What it can't do is to set a Read Only (RO) parameter, only Read Write (RW) parameters, which can be checked by the parameter TOC in the CFclient. You can check this by changing the parameter name to group `'cpu' ` and name `flash'`. Then you will get the following error: `AttributeError: cpu.flash is read-only!` @@ -454,9 +458,10 @@ from cflib.crazyflie import Crazyflie from cflib.crazyflie.log import LogConfig from cflib.crazyflie.syncCrazyflie import SyncCrazyflie from cflib.crazyflie.syncLogger import SyncLogger +from cflib.utils import uri_helper # URI to the Crazyflie to connect to -uri = 'radio://0/80/2M/E7E7E7E7E7' +uri = uri_helper.uri_from_env(default='radio://0/80/2M/E7E7E7E7E7') # Only output errors from the logging framework logging.basicConfig(level=logging.ERROR) diff --git a/docs/user-guides/sbs_motion_commander.md b/docs/user-guides/sbs_motion_commander.md index 303f49536..28220f09c 100644 --- a/docs/user-guides/sbs_motion_commander.md +++ b/docs/user-guides/sbs_motion_commander.md @@ -3,19 +3,19 @@ title: "Step-by-Step: Motion Commander" page_id: sbs_motion_commander --- -Here we will go through step-by-step how to make your crazyflie move based on a motion script. For the first part of this tutorial, you just need the crazyflie and the flow deck. For the second part, it would be handy to have the multiranger present. +Here we will go through step-by-step how to make your Crazyflie move based on a motion script. For the first part of this tutorial, you just need the Crazyflie and the flow deck. For the second part, it would be handy to have the multiranger present. ## Prerequisites We will assume that you already know this before you start with the tutorial: -* Some basic experience with python -* Followed the [crazyflie getting started guide](https://www.bitcraze.io/documentation/tutorials/getting-started-with-crazyflie-2-x/) and [the connecting, logging and parameters tutorial](/docs/user-guides/sbs_connect_log_param.md). +* Some basic experience with Python +* Followed the [Crazyflie getting started guide](https://www.bitcraze.io/documentation/tutorials/getting-started-with-crazyflie-2-x/) and [the connecting, logging and parameters tutorial](/docs/user-guides/sbs_connect_log_param.md). ## Get the script started -Since you should have installed cflib in the previous step by step tutorial, you are all ready to got now. Open up an new python script called `motion_flying.py`. First you will start by adding the following import to the script: +Since you should have installed cflib in the previous step by step tutorial, you are all ready to go now. Open up a new python script called `motion_flying.py`. First you will start by adding the following to the script: ```python import logging @@ -30,11 +30,9 @@ from cflib.crazyflie.syncCrazyflie import SyncCrazyflie from cflib.positioning.motion_commander import MotionCommander from cflib.utils import uri_helper - URI = uri_helper.uri_from_env(default='radio://0/80/2M/E7E7E7E7E7') -DEFAULT_HEIGHT = 0.5 -BOX_LIMIT = 0.5 +logging.basicConfig(level=logging.ERROR) if __name__ == '__main__': @@ -42,17 +40,17 @@ if __name__ == '__main__': with SyncCrazyflie(URI, cf=Crazyflie(rw_cache='./cache')) as scf: ``` -This probably all looks pretty familiar, except for one thing line, namely: +This probably all looks pretty familiar, except for one line, namely: `from cflib.positioning.motion_commander import MotionCommander` -This imports the motion commander, which is pretty much a wrapper around the position setpoint frame work of the crazyflie. You probably have unknowingly experienced this a when trying out the assist modes in this [tutorial with the flow deck in the cfclient](https://www.bitcraze.io/documentation/tutorials/getting-started-with-flow-deck/) +This imports the motion commander, which is pretty much a wrapper around the position setpoint frame work of the Crazyflie. You probably have unknowingly experienced this when trying out the assist modes in this [tutorial with the flow deck in the cfclient](https://www.bitcraze.io/documentation/tutorials/getting-started-with-flow-deck/). ## Step 1: Security before flying -Since this tutorial won't be a table top tutorial like last time, but an actual flying one, we need to put some securities in place. The flow deck that you are using, should be correctly attached to the crazyflie. If it is not, it will try to fly anyway without a good position estimate and for sure is going to crash. +Since this tutorial won't be a table top tutorial like last time, but an actual flying one, we need to put some securities in place. The flow deck that you are using, should be correctly attached to the Crazyflie. If it is not, it will try to fly anyway without a good position estimate and for sure is going to crash. -We want to know if the deck is correctly attached before flying, therefore we will add a callback for the `"deck.bcFlow2"` parameter. Add the following line after the `...SyncCrazyflie(...)` in `__main__` +We want to know if the deck is correctly attached before flying, therefore we will add a callback for the `"deck.bcFlow2"` parameter. Add the following line after the `...SyncCrazyflie(...)` in `__main__`: ```python with SyncCrazyflie(URI, cf=Crazyflie(rw_cache='./cache')) as scf: @@ -74,8 +72,9 @@ def param_deck_flow(_, value_str): print('Deck is NOT attached!') ``` -The `deck_attached_event` is a global variable which should be defined under `URI`. Note that the value type that the `param_deck_flow()` is a string type, so you will need to convert it to a number first before you can do any operations with it. +Note that the value type that the `param_deck_flow()` gets is a string type, so you will need to convert it to a number first before you can do any operations with it. +The `deck_attached_event` is a global variable which should be defined under `URI`: ```python ... URI = uri_helper.uri_from_env(default='radio://0/80/2M/E7E7E7E7E7') @@ -144,34 +143,33 @@ So now we are going to start up the SyncCrazyflie and start a function in the `_ take_off_simple(scf) ``` -See that we are now using `deck_attached_event.wait()`? If this returns false, the function will not be called and the crazyflie will not take off. +See that we are now using `deck_attached_event.wait()`? If this returns false, the function will not be called and the Crazyflie will not take off. Now make the function `take_off_simple(..)` above `__main__`, which will contain the motion commander instance. ```python def take_off_simple(scf): - with MotionCommander(scf, default_height=DEFAULT_HEIGHT) as mc: + with MotionCommander(scf) as mc: time.sleep(3) - mc.stop() ``` -If you run the python script, you will see the crazyflie connect and immediately take off. After flying for 3 seconds it will land again. +If you run the python script, you will see the Crazyflie connect and immediately take off. After flying for 3 seconds it will land again. -The reason for the crazyflie to immediately take off, is that the motion commander if intialized with a take off function that will already start sending position setpoints to the crazyflie. Once the script goes out of the instance, the motion commander instance will close with a land function. +The reason for the Crazyflie immediately taking off is that the Motion commander is initialized with a takeoff that starts sending position setpoints to the Crazyflie. Once the script goes out of the instance, the motion commander instance will close with a land function. ### Changing the height -Currently the motion commander had 0.3 meters height as default but this can of course be changed. +Currently the motion commander has 0.3 meters height as default but this can of course be changed. -Change the following line in `take_off_simple(...)`: +Change the following lines in `take_off_simple(...)`: ```python with MotionCommander(scf) as mc: + time.sleep(3) mc.up(0.3) time.sleep(3) - mc.stop() ``` -Run the script again. The crazyflie will first take off to 0.3 meters and then goes up for another 0.3 meters. +Run the script again. The Crazyflie will first take off to 0.3 meters and then goes up for another 0.3 meters. The same can be achieved by adjusting the default_height of the motion_commander, which is what we will do for now on in this tutorial. Remove the `mc.up(0.3)` and replace the motion commander line with ```python @@ -210,7 +208,6 @@ logging.basicConfig(level=logging.ERROR) def take_off_simple(scf): with MotionCommander(scf, default_height=DEFAULT_HEIGHT) as mc: time.sleep(3) - mc.stop() def param_deck_flow(name, value_str): @@ -237,7 +234,7 @@ if __name__ == '__main__': ``` -## Step 3 Go Forward, Turn and Go back +## Step 3: Go Forward, Turn and Go back So now we know how to take off, so the second step is to move in a direction! Start a new function above `def take_off_simple(scf)`: @@ -253,7 +250,7 @@ def move_linear_simple(scf): If you replace `take_off_simple(scf)` in `__main__` with `move_linear_simple(scf)`, try to run the script. -You will see the crazyflie take off, fly 0.5 m forward, fly backwards and land again. +You will see the Crazyflie take off, fly 0.5 m forward, fly backwards and land again. Now we are going to add a turn into it. Replace the content under motion commander in `move_linear_simple(..)` with the following: @@ -267,7 +264,7 @@ Now we are going to add a turn into it. Replace the content under motion command time.sleep(1) ``` -Try to run the script again. Now you can see the crazyflie take off, go forward, turn 180 degrees and go forward again to its initial position. The `mc.back()` needed to be replaced with the forward since the motion commander sends the velocity setpoints in the __body fixed coordinate__ system. This means that the commands forward will go forward to wherever the current heading (the front) of the crazyflie points to. +Try to run the script again. Now you can see the Crazyflie take off, go forward, turn 180 degrees and go forward again to its initial position. The `mc.back()` needed to be replaced with the forward since the motion commander sends the velocity setpoints in the __body fixed coordinate__ system. This means that the commands forward will go forward to wherever the current heading (the front) of the Crazyflie points to. Double check if your code is still correct: @@ -331,7 +328,7 @@ if __name__ == '__main__': ## Step 4: Logging while flying -When the motion commander commands have been executed, the script stops and the crazyflie lands... however that is a bit boring. Maybe you would like for it to keep flying and responding certain elements in the mean time! +When the motion commander commands have been executed, the script stops and the Crazyflie lands... however that is a bit boring. Maybe you would like for it to keep flying and responding certain elements in the mean time! Let's integrate some logging to this as well. Add the following log config right into `__main__` under `SyncCrazyflie` @@ -363,7 +360,7 @@ def log_pos_callback(timestamp, data, logconf): print(data) ``` -NOW: Make global variable which is a list called `position_estimate` and fill this in in the logging callback function with the x and y position. The `data` is a dict structure. +NOW: Make global variable which is a list called `position_estimate` and fill this in the logging callback function with the x and y position. The `data` is a dict structure. Just double check that everything has been implemented correctly and then run the script. You will see the same behavior as with the previous step but then with the position estimated printed at the same time. @@ -464,7 +461,7 @@ def move_box_limit(scf): ``` -If you would run this (don't forget to replace `move_linear_simple()` in `__main__`), you see the crazyflie take off but it will stay in the air. A keyboard interrupt (ctrl+c) will stop the script and make the crazyflie land again. +If you would run this (don't forget to replace `move_linear_simple()` in `__main__`), you see the Crazyflie take off but it will stay in the air. A keyboard interrupt (ctrl+c) will stop the script and make the Crazyflie land again. Now we will add some behavior in the while loop: @@ -484,9 +481,9 @@ def move_box_limit(scf): Add `BOX_LIMIT = 0.5` underneath the definition of the `DEFAULT_HEIGHT = 0.5`. -Run the script and you will see that the crazyflie will start moving back and forth until you hit ctrl+c. It changes its command based on the logging input, which is the state estimate x and y position. Once it indicates that it reached the border of the 'virtual' limit, it will change it's direction. +Run the script and you will see that the Crazyflie will start moving back and forth until you hit ctrl+c. It changes its command based on the logging input, which is the state estimate x and y position. Once it indicates that it reached the border of the 'virtual' limit, it will change its direction. -You probably also noticed that we are using `mc.start_back()` and `mc.start_forward()` instead of the `mc.forward(0.5)` and `mc.back(0.5)` used in the previous steps. The main difference is that the *mc.forward* and *mc.back* are **blocking** functions that won't continue the code until the distance has been reached. The *mc.start_...()* will start the crazyflie in a direction and will not stop until the `mc.stop()` is given, which is done automatically when the motion commander instance is exited. That is why these are nice functions to use in reactive scenarios like this. +You probably also noticed that we are using `mc.start_back()` and `mc.start_forward()` instead of the `mc.forward(0.5)` and `mc.back(0.5)` used in the previous steps. The main difference is that the *mc.forward* and *mc.back* are **blocking** functions that won't continue the code until the distance has been reached. The *mc.start_...()* will start the Crazyflie in a direction and will not stop until the `mc.stop()` is given, which is done automatically when the motion commander instance is exited. That is why these are nice functions to use in reactive scenarios like this. ```python import logging @@ -586,7 +583,7 @@ Let's take it up a notch! Replace the content in the while loop with the followi This will now start a linear motion into a certain direction, and makes the Crazyflie bounce around in a virtual box of which the size is indicated by 'BOX_LIMIT'. So before you fly make sure that you pick a box_limit small enough so that it able to fit in your flying area. -**Note**: if you are using the flow deck, it might be that the orientation of this box will seem to change. This is due to that the flow deck is not able to provide an absolute heading estimate, which will be only based on gyroscope measurements. This will drift over time, which is accelerated if you incorporate many turns in your application. There are also reports that happens quickly when the crazyflie is still on the ground. This should not happen with MoCap or the lighthouse deck. +**Note**: if you are using the flow deck, it might be that the orientation of this box will seem to change. This is due to that the flow deck is not able to provide an absolute heading estimate, which will be only based on gyroscope measurements. This will drift over time, which is accelerated if you incorporate many turns in your application. There are also reports that happens quickly when the Crazyflie is still on the ground. This should not happen with MoCap or the lighthouse deck. Check out if your code still matches the full code and run the script! @@ -695,6 +692,6 @@ if __name__ == '__main__': You're done! The full code of this tutorial can be found in the example/step-by-step/ folder. -## What is next ? +## What is next? Now you are able to send velocity commands to the Crazyflie and react upon logging and parameters variables, so one step closer to writing your own application with the Crazyflie python library! Check out the [motion_commander_demo.py](https://github.com/bitcraze/crazyflie-lib-python/blob/master/examples/step-by-step/sbs_motion_commander.py) in the example folder of the cflib if you would like to see what the commander can do. diff --git a/docs/user-guides/sbs_swarm_interface.md b/docs/user-guides/sbs_swarm_interface.md index ebe7a909f..eb94f032a 100644 --- a/docs/user-guides/sbs_swarm_interface.md +++ b/docs/user-guides/sbs_swarm_interface.md @@ -3,20 +3,21 @@ title: "Step-by-Step: Swarm Interface" page_id: sbs_swarm_interface --- -Here we will go through step-by-step how to interface with a swarm of crazyflies and make all the copters of the swarm hover and fly simultaneously in a square shape using the `Swarm()` class of the cflib. For this tutorial you will need a swarm (2 or more) of crazyflies with the latest firmware version installed and a positioning system (Lighthouse, Loco or MoCap) that is able to provide data for the position estimation of the crazyflies. You can also use the Flowdeck but keep in mind that you should command relative movements of each Crazyflie and due to its nature it may lead to accumulative errors and unexpected behavior over time. +Here we will go through step-by-step how to interface with a swarm of Crazyflies and make all the copters of the swarm hover and fly simultaneously in a square shape using the `Swarm()` class of the cflib. For this tutorial you will need a swarm (2 or more) of Crazyflies with the latest firmware version installed and a [positioning system](https://www.bitcraze.io/documentation/system/positioning/) (Lighthouse, Loco or MoCap) that is able to provide data for the position estimation of the Crazyflies. You can also use the Flow Deck, but keep in mind that you should command relative movements of each Crazyflie and due to its nature it may lead to accumulative errors and unexpected behavior over time. ## Prerequisites -We will assume that you already know this before you start with the tutorial: +We will assume that you already have completed the following steps before you start with the tutorial: -* Some basic experience with python -* Followed the [crazyflie getting started guide](https://www.bitcraze.io/documentation/tutorials/getting-started-with-crazyflie-2-x/) -* Read the [high level commander](https://www.bitcraze.io/documentation/repository/crazyflie-lib-python/master/api/cflib/crazyflie/high_level_commander/), [swarm](https://www.bitcraze.io/documentation/repository/crazyflie-lib-python/master/api/cflib/crazyflie/swarm/) and [SyncCrazyflie](https://www.bitcraze.io/documentation/repository/crazyflie-lib-python/master/api/cflib/crazyflie/syncCrazyflie/) documentation . +* Have some basic experience with Python +* Followed the [Crazyflie getting started guide](https://www.bitcraze.io/documentation/tutorials/getting-started-with-crazyflie-2-x/) +* Set up the positioning system of your choice +* Read the [high level commander](https://www.bitcraze.io/documentation/repository/crazyflie-lib-python/master/api/cflib/crazyflie/high_level_commander/), [swarm](https://www.bitcraze.io/documentation/repository/crazyflie-lib-python/master/api/cflib/crazyflie/swarm/) and [SyncCrazyflie](https://www.bitcraze.io/documentation/repository/crazyflie-lib-python/master/api/cflib/crazyflie/syncCrazyflie/) documentation. ## Get the script started -Since you should have installed cflib in the previous step by step tutorial, you all ready to go now. Open up a new python script called `swarm_rectangle.py`. First you will start by adding the following import to the script: +Since you should have installed cflib in the previous step by step tutorial, you're all ready to go now. Open up a new python script called `swarm_rectangle.py`. First you will start by adding the following import to the script: ```python import time @@ -47,9 +48,9 @@ The radio addresses of the copters are defined in the `uris` list and you can ad ## Step 1: Light Check In order to verify everything is setup and working properly a light check will be performed. During this check, all the copters will light up red for a short period of time and then return to normal. -This is achieved by setting the parameter `led.bitmask` to 255 which results to all the LED's of each copter light up simultaneously. +This is achieved by setting the parameter `led.bitmask` to 255 which results in all the LEDs of each copter lighting up simultaneously. -Add the helper functions `activate_led_bit_mask`,`deactivate_led_bit_mask` and the function`light_check` above `__main__`: +Add the helper functions `activate_led_bit_mask`, `deactivate_led_bit_mask` and the function `light_check` above `__main__`: ```python def activate_led_bit_mask(scf): scf.cf.param.set_value('led.bitmask', 255) @@ -64,12 +65,12 @@ def light_check(scf): ``` `light_check` will light up a copter red for 2 seconds and then return them to normal. -Below `... Swarm(...)` in `__main__`, execute the light check for each copter: +Below `... Swarm(...)` in `__main__`, execute the light check for each copter: ```python swarm.parallel_safe(light_check) ``` -The `light_check()` is going to be called through the `parallel_safe()` method which will execute it for for all Crazyflies in the swarm, in parallel. One thread per Crazyflie is started to execute the function. The threads are joined at the end and if one or more of the threads raised an exception this function will also raise an exception. +The `light_check()` is going to be called through the `parallel_safe()` method which will execute it for all Crazyflies in the swarm, in parallel. One thread per Crazyflie is started to execute the function. The threads are joined at the end and if one or more of the threads raised an exception this function will also raise an exception. ```python import time @@ -108,18 +109,18 @@ if __name__ == '__main__': swarm.parallel_safe(light_check) ``` -If everything is working properly, you can move to the next step . +If everything is working properly, you can move to the next step. ## Step 2: Security Before Flying Before executing any take off and flight manoeuvres, the copters need to make sure that they have a precise enough position estimation. Otherwise it will take off anyway and it is very likely to crash. This is done through `reset_estimators()` by resetting the internal position estimator of each copter and waiting until the variance of the position estimation drops below a certain threshold. ```python with Swarm(uris, factory=factory) as swarm: - swarm.parallel_safe(lightCheck) + swarm.parallel_safe(light_check) swarm.reset_estimators() ``` ## Step 3: Taking off and Landing Sequentially -Now we are going to execute the fist take off and landing using the high level commander. The high level commander (more information [here](https://www.bitcraze.io/documentation/repository/crazyflie-firmware/master/functional-areas/sensor-to-control/commanders_setpoints/#high-level-commander)) is a class that handles all the high level commands like takeoff, landing, hover, go to position and others. The high level commander is usually preferred since it needs less communication and provides more autonomy for the Crazyflie. It is always on, but just in a lower priority so you just need to execute the take off and land commands through the below functions: +Make sure that your positioning system of your choice is set up and ready to go. Now we are going to execute the first take off and landing using the high level commander. The high level commander (more information [here](https://www.bitcraze.io/documentation/repository/crazyflie-firmware/master/functional-areas/sensor-to-control/commanders_setpoints/#high-level-commander)) is a class that handles all the high level commands like takeoff, landing, hover, go to position and others. The high level commander is usually preferred since it needs less communication and provides more autonomy for the Crazyflie. It is always on, but just in a lower priority so you just need to execute the take off and land commands through the below functions: ```python def take_off(scf): commander= scf.cf.high_level_commander @@ -140,7 +141,7 @@ def hover_sequence(scf): land(scf) ``` -Initially , we want only one copter at a time executing the `hover_sequence` so it is going to be called through the `sequential()` method of the `Swarm` in the following way: +Initially, we want only one copter at a time executing the `hover_sequence` so it is going to be called through the `sequential()` method of the `Swarm` in the following way: ```python swarm.sequential(hover_sequence) @@ -198,13 +199,13 @@ if __name__ == '__main__': cflib.crtp.init_drivers() factory = CachedCfFactory(rw_cache='./cache') with Swarm(uris, factory=factory) as swarm: - print('Connected to Crazyflies') - swarm.parallel_safe(lightCheck) + print('Connected to Crazyflies') + swarm.parallel_safe(light_check) swarm.reset_estimators() swarm.sequential(hover_sequence) ``` -After executing it you will see all copters performing the light check and then each copter take off , hover and land. This process is repeated for all copters in the swarm. +After executing it you will see all copters performing the light check and then each copter take off, hover and land. This process is repeated for all copters in the swarm. ## Step 4: Taking off and Landing in Sync If you want to take off and land in sync, you can use the `parallel_safe()` method of the `Swarm` class. @@ -216,7 +217,7 @@ If you want to take off and land in sync, you can use the `parallel_safe()` meth Now the same action is happening but for all the copters in parallel. ## Step 5: Performing a square shape flight -To make the swarm fly in a square shape, we will use the `go_to` method of the high level commander. Each copter executes 4 relative movements to its current position covering a square shape. +To make the swarm fly in a square shape, we will use the `go_to` method of the high level commander. Each copter independently executes 4 relative movements (+X, +Y, -X, -Y) tracing a 1×1 m square and returning to its own starting position. Since the movements are relative, all copters fly the same square path simultaneously regardless of where they started, and you should see the entire swarm move in sync. ```python def run_square_sequence(scf): @@ -274,20 +275,20 @@ def land(scf): def run_square_sequence(scf: SyncCrazyflie): ... -uris = { +uris = [ 'radio://0/20/2M/E7E7E7E701', 'radio://0/20/2M/E7E7E7E702', 'radio://0/20/2M/E7E7E7E703', 'radio://0/20/2M/E7E7E7E704', # Add more URIs if you want more copters in the swarm # URIs in a swarm using the same radio must also be on the same channel -} +] if __name__ == '__main__': cflib.crtp.init_drivers() factory = CachedCfFactory(rw_cache='./cache') with Swarm(uris, factory=factory) as swarm: - print('Connected to Crazyflies') + print('Connected to Crazyflies') swarm.parallel_safe(light_check) swarm.reset_estimators() @@ -296,21 +297,24 @@ if __name__ == '__main__': swarm.parallel_safe(land) ``` -## Step 6: Performing a flight with different arguments -You can also feed different arguments to each Crazyflie in the swarm. This can be done by providing a dictionary `args_dict` to the `parallel_safe()`,`parallel()` and `sequential()` methods following the below format. +## Step 6: Performing a flight with different arguments +You can also feed different arguments to each Crazyflie in the swarm. The `parallel_safe()`, `parallel()`, and `sequential()` methods accept an optional `args_dict` parameter which is a dictionary that maps each URI to a list of extra arguments to pass to the per-Crazyflie function. + +You create this dictionary yourself, with the URI (radio address) as the key and a list of arguments as the value: ```python -args_dict = { +my_args = { URI0: [optional_param0_cf0, optional_param1_cf0], URI1: [optional_param0_cf1, optional_param1_cf1], ... } -``` -where the key is the radio address of the copter and the value is a list of optional arguments. In this way you can differentiate the behavior of each copter and execute different actions based on the copter and its particular parameters. +swarm.parallel_safe(my_function, args_dict=my_args) +``` +The arguments in each list are appended to the `scf` argument when the function is called, so `my_function(scf, optional_param0, optional_param1)` is what gets executed for each copter. -In this example, the copters will be placed in a square shape as shown below (pay attention to the order of the Crazyflies) and each one of them will execute different relative movements. +In the example below, four copters are placed in a square (pay attention to the order of the Crazyflies) and each one executes a different movement sequence. The dictionary `seq_args` maps each URI to its own sequence, which is then passed as the `sequence` argument to `run_sequence`. ```python @@ -326,6 +330,13 @@ In this example, the copters will be placed in a square shape as shown below (pa # 3 2 . +uris = [ + 'radio://0/20/2M/E7E7E7E701', + 'radio://0/20/2M/E7E7E7E702', + 'radio://0/20/2M/E7E7E7E703', + 'radio://0/20/2M/E7E7E7E704', +] + h = 0.0 # remain constant height similar to take off height x0, y0 = +1.0, +1.0 x1, y1 = -1.0, -1.0 @@ -382,7 +393,7 @@ And in the main code of the swarm, you can execute the sequence as follows: swarm.parallel_safe(run_sequence, args_dict=seq_args) ``` -The final script is going to look like this : +The final script is going to look like this: ```python import time @@ -390,7 +401,7 @@ import time import cflib.crtp from cflib.crazyflie.swarm import CachedCfFactory from cflib.crazyflie.swarm import Swarm -from cflib.crazyflie import syncCrazyflie +from cflib.crazyflie.syncCrazyflie import SyncCrazyflie def activate_led_bit_mask(scf): @@ -441,7 +452,7 @@ sequence3 = ... seq_args = ... -def run_sequence(scf: syncCrazyflie.SyncCrazyflie, sequence): +def run_sequence(scf: SyncCrazyflie, sequence): ... @@ -449,7 +460,7 @@ if __name__ == '__main__': cflib.crtp.init_drivers() factory = CachedCfFactory(rw_cache='./cache') with Swarm(uris, factory=factory) as swarm: - print('Connected to Crazyflies') + print('Connected to Crazyflies') swarm.parallel_safe(light_check) swarm.reset_estimators() @@ -462,5 +473,5 @@ if __name__ == '__main__': You’re done! The full code of this tutorial can be found in the `example/step-by-step/` folder. -## What is next ? +## What is next? Now you are able to control a swarm of Crazyflies and you can experiment with different behaviors for each one of them while maintaining the functionality, simplicity of working with just one since the parallelism is handled internally and you can just focus on creating awesome applications! For more examples and inspiration on the Swarm functionality, you can check out the `examples/swarm/` folder of the cflib. diff --git a/examples/step-by-step/sbs_motion_commander.py b/examples/step-by-step/sbs_motion_commander.py index b557cce2d..16f2be488 100644 --- a/examples/step-by-step/sbs_motion_commander.py +++ b/examples/step-by-step/sbs_motion_commander.py @@ -84,9 +84,8 @@ def move_linear_simple(scf): def take_off_simple(scf): - with MotionCommander(scf, default_height=DEFAULT_HEIGHT) as mc: + with MotionCommander(scf, default_height=DEFAULT_HEIGHT): time.sleep(3) - mc.stop() def log_pos_callback(timestamp, data, logconf):