This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision Next revision Both sides next revision | ||
projects:crazyflie:pc_utils:pylib [2013-06-03 15:56] daniel [Simple code example] |
projects:crazyflie:pc_utils:pylib [2014-02-12 13:37] tobias [Examples] |
||
---|---|---|---|
Line 6: | Line 6: | ||
* Firmware implementation of the [[projects: | * Firmware implementation of the [[projects: | ||
- | ===== Structure of the library ===== | + | ====== Structure of the library |
- | **TODO** | + | The library is asynchronous and based on callbacks for events. Functions like '' |
- | ===== Connection- and link-callbacks | + | ===== Uniform Resource Identifier (URI) ===== |
- | The library uses callbacks for asynchronous events such as when the connection is dropped or when new logging data arrives. Here's a list of linkstatus callbacks: | + | All communication links are identified using an URI build up of the following: %%InterfaceType://InterfaceId/InterfaceChannel/InterfaceSpeed%% |
- | * //connectionInitiated// - Called when a request has been made to the library to establish a connection | + | |
- | * // | + | |
- | * // | + | |
- | * // | + | |
- | * // | + | |
- | * // | + | |
- | To register | + | Currently only //radio// and //debug// interfaces are used but there' |
+ | * %%Radio interface, USB dongle number 0, radio channel 10 and radio speed 250 Kbit/s: radio:// | ||
+ | * %%Debug interface, id 0, channel 1: debug:// | ||
+ | |||
+ | ===== Variables and logging ===== | ||
+ | The library supports setting up logging configurations that are used for logging variables from the firmware. Each log configuration contains a number of variables that should be logged as well as a time period (in ms) of how often the data should be sent back to the host. Once the log configuration is added to the firmware the firmware will automatically send back the data at every period. These configurations are used in the following | ||
+ | * Connect to the Crazyflie (log configurations needs a TOC to work) | ||
+ | * Create a log configuration that contains a number of variables to log and a period at which they should be logged | ||
+ | * Add the log configuration. This will also validate that the log configuration | ||
+ | * After checking that the configuration is valid, set up callbacks for the data in your application and start the log configuration | ||
+ | * Each time the firmware sends data back to the host the callback will the called with a time-stamp and the data | ||
+ | |||
+ | There' | ||
+ | * Each packet is limited to 32bytes, which means that the data that is logged and the packet that is sent to set it up cannot be larger than this. It limits the logging to about 14 variables, but this is dependent on what types they are | ||
+ | * The minimum period of a for a log configuration is multiples of 10ms | ||
+ | |||
+ | ===== Parameters ===== | ||
+ | The library supports reading and writing parameters at run-time to the firmware. This is intended to be used for data that is not continuously being changed by the firmware, like setting regulation parameters and reading out if the power-on self-tests passed. Parameters should only change in the firmware when being set from the host or during start-up. The library doesn' | ||
+ | * Register parameter updated callbacks at any time in your application | ||
+ | * Connect to your Crazyflie (this will download the parameter TOC) | ||
+ | * Request updates for all the parameters | ||
+ | * The library will call all the callbacks registered | ||
+ | * The host can now write parameters that will be forwarded to the firmware | ||
+ | * For each write all the callbacks registered for this parameter will be called back | ||
+ | |||
+ | ===== Variable and parameter names ===== | ||
+ | All names of parameters and log variables use the same structure: '' | ||
+ | |||
+ | The group should be used to bundle together logical groups, like everything that has to do with the stabilizer should be in the group '' | ||
+ | |||
+ | There' | ||
+ | * stabilizer.roll | ||
+ | * stabilizer.pitch | ||
+ | * pm.vbat | ||
+ | * imu_tests.MPU6050 | ||
+ | * pid_attitide.pitch_kd | ||
+ | |||
+ | ====== Utilities ====== | ||
+ | ===== Callbacks ===== | ||
+ | All callbacks are handled using the '' | ||
<code python> | <code python> | ||
- | | + | |
- | | + | """ |
+ | |||
+ | remove_callback(cb) | ||
+ | """ | ||
+ | |||
+ | call(*args) | ||
+ | """ | ||
</ | </ | ||
- | There are more callbacks than this available in the Crazyflie | + | ===== Debug driver ===== |
+ | The library contains a special link driver, named '' | ||
+ | * Connecting a download param and log TOCs | ||
+ | * Setting up log configurations and sending back fake data | ||
+ | * Setting and reading parameters | ||
+ | * Bootloading | ||
- | ===== Uniform Resource Identifier (URI) ===== | + | There are a number |
- | All communication links are identified using an URI build up of the following: %%InterfaceType:// | + | |
- | Currently only //radio// and //debug// interfaces are used but there' | + | ====== Initiating the link drivers ====== |
- | * %%radio:// | + | Before the library can be used the link drivers have to he initialized. This will search |
- | * %%debug:// | + | |
- | ===== Variable and parameter names ===== | + | <code python> |
- | All names of parameters | + | init_drivers(enable_debug_driver=False) |
+ | """ | ||
+ | </code> | ||
- | There' | + | ====== Connection- and link-callbacks ====== |
- | * stabalizer.roll | + | Operations on the link and connection will return directly and will call the following callbacks when events occur: |
- | * system.battery | + | |
+ | <code python> | ||
+ | # Called on disconnect, no matter the reason | ||
+ | disconnected = Caller() | ||
+ | # Called on unintentional disconnect only | ||
+ | connection_lost = Caller() | ||
+ | # Called when the first packet | ||
+ | link_established = Caller() | ||
+ | # Called when the user requests a connection | ||
+ | connection_requested = Caller() | ||
+ | # Called when the link is established | ||
+ | # have been downloaded | ||
+ | connected = Caller() | ||
+ | # Called if establishing of the link fails (i.e times out) | ||
+ | | ||
+ | # Called for every packet received | ||
+ | packet_received = Caller() | ||
+ | # Called for every packet sent | ||
+ | packet_sent = Caller() | ||
+ | # Called when the link driver updates the link quality measurement | ||
+ | link_quality_updated = Caller() | ||
+ | </ | ||
+ | |||
+ | To register for callbacks the following is used: | ||
+ | <code python> | ||
+ | crazyflie = Crazyflie() | ||
+ | crazyflie.connected.add_callback(crazyflie_connected) | ||
+ | </ | ||
- | ===== Finding a Crazyflie and connecting ===== | + | ====== Finding a Crazyflie and connecting |
The first thing to do is to find a Crazyflie quadcopter that we can connect to. This is done by queuing the library that will scan all the available interfaces (currently the debug and radio interface). | The first thing to do is to find a Crazyflie quadcopter that we can connect to. This is done by queuing the library that will scan all the available interfaces (currently the debug and radio interface). | ||
<code python> | <code python> | ||
Line 52: | Line 123: | ||
<code python> | <code python> | ||
crazyflie = Crazyflie() | crazyflie = Crazyflie() | ||
+ | crazyflie.connected.add_callback(crazyflie_connected) | ||
crazyflie.open_link(" | crazyflie.open_link(" | ||
- | # Do stuff | + | </ |
+ | |||
+ | Then you can use the following to close the link again: | ||
+ | <code python> | ||
crazyflie.close_link() | crazyflie.close_link() | ||
</ | </ | ||
- | ===== Sending control commands ===== | + | |
- | Currently all the communication is either parameters, logging or control commands. The special | + | ====== Sending control commands |
+ | The control commands | ||
+ | |||
+ | <code python> | ||
+ | send_setpoint(roll, | ||
+ | """ | ||
+ | Send a new control | ||
+ | |||
+ | The arguments roll/ | ||
+ | be sent to the copter | ||
+ | """ | ||
+ | </ | ||
To send a new control set-point use the following: | To send a new control set-point use the following: | ||
Line 68: | Line 154: | ||
</ | </ | ||
- | Thrust is an integer value ranging from 10001 (next to no power) to 60000 (full power). Sending a command makes it apply for 0.5 seconds, after which the firmware will cut out the power. | + | Thrust is an integer value ranging from 10001 (next to no power) to 60000 (full power). Sending a command makes it apply for 500 ms, after which the firmware will cut out the power. With this in mind, you need to try and maintain a thrust level, with a tick being sent at least once every 2 seconds. Ideally you should be sending one tick every 10 ms, for 100 commands a second. This has a nice added benefit of allowing for very precise control. |
- | ===== Parameters ===== | + | |
+ | ====== Parameters | ||
The parameter framework is used to read and set parameters. This functionality should be used when: | The parameter framework is used to read and set parameters. This functionality should be used when: | ||
* The parameter is not changed by the Crazyflie but by the client | * The parameter is not changed by the Crazyflie but by the client | ||
Line 77: | Line 164: | ||
To set a parameter you have to the connected to the Crazyflie. A parameter is set using: | To set a parameter you have to the connected to the Crazyflie. A parameter is set using: | ||
<code python> | <code python> | ||
- | | + | |
- | | + | |
- | crazyflie.param.setParamValue(parameterName, parameterValue) | + | crazyflie.param.set_value(param_name, param_value) |
</ | </ | ||
- | The parameter reading is done using callbacks. When the connection is established to the Crazyflie all the parameters are read. When a parameter is updated from the client | + | The parameter reading is done using callbacks. When a parameter is updated from the host (using the code above) the parameter will be read back by the library and this will trigger the callbacks. Parameter callbacks can be added at any time (you don't have to be connected to a Crazyflie). |
<code python> | <code python> | ||
- | | + | |
+ | | ||
+ | Add a callback for a specific parameter name or group. | ||
+ | all parameters in the group will trigger the callback. This callback will be executed | ||
+ | when a new value is read from the Crazyflie. | ||
+ | "" | ||
- | | + | |
- | | + | """ |
+ | |||
+ | set_value(complete_name, value) | ||
+ | """ | ||
</ | </ | ||
- | A callback can also be removed using: | + | Here's an example of how to use the calls. |
<code python> | <code python> | ||
- | crazyflie.param.addParamUpdateCallback(" | + | |
+ | |||
+ | def param_updated_callback(name, | ||
+ | print "%s has value %d" % (name, value) | ||
</ | </ | ||
- | ===== Logging ===== | + | ====== Logging |
The logging framework is used to enable the " | The logging framework is used to enable the " | ||
* The variable is changed by the Crazyflie and not by the client | * The variable is changed by the Crazyflie and not by the client | ||
Line 101: | Line 200: | ||
If this is not the case then the parameter framework should be used instead. | If this is not the case then the parameter framework should be used instead. | ||
- | To create a logging configuration the following can be used: | ||
- | <code python> | ||
- | periodInMilliSeconds = 10 | ||
- | logconf = LogConfig(" | ||
- | logconf.addVariable(LogVariable(" | ||
- | logconf.addVariable(LogVariable(" | ||
- | logconf.addVariable(LogVariable(" | ||
- | </ | ||
- | The datatype should match what has been configured in the Crazyflie firmware. The valid datatypes are: | ||
- | * float | ||
- | * uint8_t and int8_t | ||
- | * uint16_t and int16_t | ||
- | * uint32_t and int32_t | ||
- | * FP16 (this is a fixed point version of floating point) | ||
- | The logging cannot be started until your are connected | + | The API to create and get information from LogConfig: |
<code python> | <code python> | ||
- | # Callback called | + | # Called when new logging data arrives |
- | | + | data_received_cb = Caller() |
- | | + | # Called when there' |
+ | error_cb = Caller() | ||
+ | # Called | ||
+ | | ||
+ | # Called when the log configuration is confirmed to be added | ||
+ | added_cb = Caller() | ||
- | if (logpacket != None): | + | add_variable(name, fetch_as=None) |
- | logpacket.dataReceived.addCallback(dataCallback) | + | """ |
- | logpacket.startLogging() | + | |
- | | + | |
- | print "One or more of the variables in the configuration | + | |
- | def dataCallback(data): | + | name - Complete name of the variable in the form group.name |
- | | + | |
- | for k in data.keys(): | + | fetched as (i.e uint8_t, float, FP16, etc) |
- | print "Key: %s=%s" % (k, str(data[k])) | + | |
- | </ | + | |
+ | If no fetch_as type is supplied, then the stored as type will be used | ||
+ | (i.e the type of the fetched variable is the same as it's stored in the | ||
+ | Crazyflie).""" | ||
- | ====== Logging and parameter examples ====== | + | start() |
- | Below are the two examples described in the video <insert link to video here when done> that shows the parameter and logging | + | """ |
- | Remember that if you use the callbacks for parameters and logging together with QT and the callbacks manipulate objects in the UI then they have to be wrapped using signals. Otherwise you will en up with timing issues that will (at some point) crash the application since UI objects should only be updated with the same thread that draws the objects. | + | stop() |
+ | """ | ||
- | ===== Adding a parameter ===== | + | delete() |
- | In this example we will add a parameter that will be used to "freeze" | + | "" |
- | + | ||
- | First of all we need to add the parameter to the firmware, | + | |
- | <code c> | + | |
- | # | + | |
- | + | ||
- | bool ledFreeze = false; | + | |
- | + | ||
- | PARAM_GROUP_START(led) | + | |
- | PARAM_ADD(PARAM_UINT8, | + | |
- | PARAM_GROUP_STOP(led) | + | |
</ | </ | ||
- | This will add a parameter | + | The API for the log in the Crazyflie: |
+ | <code python> | ||
+ | add_config(logconf) | ||
+ | """ | ||
- | Now we should use this parameter on the client side. This can either be done by reading or writing | + | When doing this the contents of the log configuration will be validated |
- | <code python> | + | and listeners for new log configurations will be notified. When |
- | # crazyflie | + | validating |
- | | + | |
+ | | ||
+ | | ||
</ | </ | ||
- | The parameter-framework relies on the fact that the parameters are changed from the client or that the client polls the value of parameters. If you are interested in logging changes that the Crazyflie does itself then the logging-framework is better. If you are interested in reading a parameter this should be done using a callback that will be called from the framework when the value for the parameter is updated. This happens in two cases: | ||
- | * When the Crazyflie has connected values for all the parameters in the TOC are fetched | ||
- | * When the client changes a value for a parameter the new value will be sent back from the Crazyflie | ||
- | To register for a callback and to implement the callback | + | To create |
<code python> | <code python> | ||
- | | + | |
- | | + | |
- | + | | |
- | | + | |
- | | + | |
</ | </ | ||
- | ===== Adding loggable variables ===== | + | The datatype should match what has been configured in the Crazyflie firmware. The valid datatypes are: |
- | In this example we will add logging for the raw gyro values read from the sensor. | + | * float |
+ | * uint8_t and int8_t | ||
+ | * uint16_t and int16_t | ||
+ | * uint32_t and int32_t | ||
+ | * FP16 (this is a fixed point version of floating point) | ||
- | First of all we add the variables to the logging | + | The logging |
- | <code c> | + | |
- | #include " | + | |
- | // The raw gyro values | + | |
- | LOG_GROUP_START(gyro) | + | |
- | LOG_ADD(LOG_FLOAT, | + | |
- | LOG_ADD(LOG_FLOAT, | + | |
- | LOG_ADD(LOG_FLOAT, | + | |
- | LOG_GROUP_STOP(gyro) | + | |
- | </ | + | |
- | + | ||
- | This will add the variables //gyro.x//, //gyro.y// and // | + | |
- | + | ||
- | On the client side we now add the log configuration, | + | |
<code python> | <code python> | ||
# Callback called when the connection is established to the Crazyflie | # Callback called when the connection is established to the Crazyflie | ||
- | def connected(linkURI): | + | def connected(link_uri): |
- | | + | |
- | gyroconf.addVariable(LogVariable(" | + | |
- | gyroconf.addVariable(LogVariable(" | + | |
- | gyroconf.addVariable(LogVariable(" | + | |
- | | + | |
- | | + | |
- | + | | |
- | if (gyrolog != None): | + | |
- | | + | |
- | | + | |
else: | else: | ||
- | print "gyro.x/ | + | print "One or more of the variables in the configuration was not found in log TOC. No logging will be possible." |
- | + | ||
- | def gyroData(data): | + | |
- | print " | + | |
+ | def data_received_callback(timestamp, | ||
+ | print " | ||
+ | | ||
+ | def logging_error(logconf, | ||
+ | print "Error when logging %s" % logconf.name | ||
</ | </ | ||
- | ====== | + | ====== |
- | As a starter guide, this application connects to the Crazyflie, turns on the motors and then asks the user to set the thrust level. | + | The examples are now placed in the repository in the examples folder. |
- | <code python> | ||
- | import logging | ||
- | import cflib.crtp | ||
- | from cfclient.utils.logconfigreader import LogConfig | ||
- | from cfclient.utils.logconfigreader import LogVariable | ||
- | from cflib.crazyflie import Crazyflie | ||
- | |||
- | logging.basicConfig(level=logging.DEBUG) | ||
- | |||
- | |||
- | class Main: | ||
- | """ | ||
- | Class is required so that methods can access the object fields. | ||
- | """ | ||
- | def __init__(self): | ||
- | """ | ||
- | Connect to Crazyflie, initialize drivers and set up callback. | ||
- | |||
- | The callback takes care of logging the accelerometer values. | ||
- | """ | ||
- | self.crazyflie = Crazyflie() | ||
- | cflib.crtp.init_drivers() | ||
- | |||
- | self.crazyflie.connectSetupFinished.add_callback( | ||
- | self.connectSetupFinished) | ||
- | |||
- | self.crazyflie.open_link(" | ||
- | |||
- | def connectSetupFinished(self, | ||
- | """ | ||
- | Configure the logger to log accelerometer values and start recording. | ||
- | |||
- | The logging variables are added one after another to the logging | ||
- | configuration. Then the configuration is used to create a log packet | ||
- | which is cached on the Crazyflie. If the log packet is None, the | ||
- | program exits. Otherwise the logging packet receives a callback when | ||
- | it receives data, which prints the data from the logging packet' | ||
- | data dictionary as logging info. | ||
- | """ | ||
- | # Set accelerometer logging config | ||
- | accel_log_conf = LogConfig(" | ||
- | accel_log_conf.addVariable(LogVariable(" | ||
- | accel_log_conf.addVariable(LogVariable(" | ||
- | accel_log_conf.addVariable(LogVariable(" | ||
- | |||
- | # Now that the connection is established, | ||
- | self.accel_log = self.crazyflie.log.create_log_packet(accel_log_conf) | ||
- | |||
- | if self.accel_log is not None: | ||
- | self.accel_log.dataReceived.add_callback(self.log_accel_data) | ||
- | self.accel_log.start() | ||
- | else: | ||
- | print(" | ||
- | |||
- | def log_accel_data(self, | ||
- | logging.info(" | ||
- | (data[" | ||
- | |||
- | Main() | ||
- | </ |