User Tools

Site Tools


projects:crazyflie:pc_utils:pylib

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
Next revision Both sides next revision
projects:crazyflie:pc_utils:pylib [2014-02-04 09:32]
macke
projects:crazyflie:pc_utils:pylib [2014-02-12 13:37]
tobias [Examples]
Line 6: Line 6:
   * Firmware implementation of the [[projects:crazyflie:firmware:log | logging]] or [[projects:crazyflie:firmware:param | parameters]]   * Firmware implementation of the [[projects:crazyflie:firmware:log | logging]] or [[projects:crazyflie:firmware:param | parameters]]
  
-===== Structure of the library =====+====== Structure of the library ======
 The library is asynchronous and based on callbacks for events. Functions like ''open_link'' will return immediately, and the callback ''connected'' will be called when the link is opened. The library doesn't contain any threads or locks that will keep the application running, it's up to the application that is using the library to do this. The library is asynchronous and based on callbacks for events. Functions like ''open_link'' will return immediately, and the callback ''connected'' will be called when the link is opened. The library doesn't contain any threads or locks that will keep the application running, it's up to the application that is using the library to do this.
  
-==== Uniform Resource Identifier (URI) ====+===== Uniform Resource Identifier (URI) =====
 All communication links are identified using an URI build up of the following: %%InterfaceType://InterfaceId/InterfaceChannel/InterfaceSpeed%% All communication links are identified using an URI build up of the following: %%InterfaceType://InterfaceId/InterfaceChannel/InterfaceSpeed%%
  
Line 16: Line 16:
   * %%Debug interface, id 0, channel 1: debug://0/1%%   * %%Debug interface, id 0, channel 1: debug://0/1%%
  
-==== Variables and logging ====+===== 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 way: 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 way:
   * Connect to the Crazyflie (log configurations needs a TOC to work)   * Connect to the Crazyflie (log configurations needs a TOC to work)
Line 28: Line 28:
   * The minimum period of a for a log configuration is multiples of 10ms   * The minimum period of a for a log configuration is multiples of 10ms
  
-==== Parameters ====+===== 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't continuously update the parameter values, this should only be done once after connecting. After each write to a parameter the firmware will send back the updated value and this will be forwarded to callbacks registered for reading this parameter. The parameters should be used in the following way: 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't continuously update the parameter values, this should only be done once after connecting. After each write to a parameter the firmware will send back the updated value and this will be forwarded to callbacks registered for reading this parameter. The parameters should be used in the following way:
   * Register parameter updated callbacks at any time in your application   * Register parameter updated callbacks at any time in your application
Line 37: Line 37:
   * For each write all the callbacks registered for this parameter will be called back   * For each write all the callbacks registered for this parameter will be called back
  
-==== Variable and parameter names ====+===== Variable and parameter names =====
 All names of parameters and log variables use the same structure: ''group.name'' All names of parameters and log variables use the same structure: ''group.name''
  
Line 49: Line 49:
   * pid_attitide.pitch_kd   * pid_attitide.pitch_kd
  
-===== Utilities ===== +====== Utilities ====== 
-==== Callbacks ====+===== Callbacks =====
 All callbacks are handled using the ''Caller'' class that contains the following methods: All callbacks are handled using the ''Caller'' class that contains the following methods:
 <code python> <code python>
-    def add_callback(cb)+    add_callback(cb)
         """ Register cb as a new callback. Will not register duplicates. """         """ Register cb as a new callback. Will not register duplicates. """
  
-    def remove_callback(cb):+    remove_callback(cb)
         """ Un-register cb from the callbacks """         """ Un-register cb from the callbacks """
  
-    def call(*args):+    call(*args)
         """ Call the callbacks registered with the arguments args """         """ Call the callbacks registered with the arguments args """
 </code> </code>
  
-==== Debug driver ====+===== Debug driver =====
 The library contains a special link driver, named ''DebugDriver''. This driver will emulate a Crazyflie and is used for testing of the UI and library. Normally this will be hidden from the user except if explicitly enabled. The driver supports the following: The library contains a special link driver, named ''DebugDriver''. This driver will emulate a Crazyflie and is used for testing of the UI and library. Normally this will be hidden from the user except if explicitly enabled. The driver supports the following:
   * Connecting a download param and log TOCs   * Connecting a download param and log TOCs
Line 72: Line 72:
 There are a number of different URIs that will be returned from the driver. These will have different functions, like always returning a random TOC CRC to always trigger TOC downloading or failing during connect. The driver also has support for sending back data with random delays to trigger random re-sending by the library. There are a number of different URIs that will be returned from the driver. These will have different functions, like always returning a random TOC CRC to always trigger TOC downloading or failing during connect. The driver also has support for sending back data with random delays to trigger random re-sending by the library.
  
-===== Initiating the link drivers =====+====== Initiating the link drivers ======
 Before the library can be used the link drivers have to he initialized. This will search for available drivers and instantiate them. Before the library can be used the link drivers have to he initialized. This will search for available drivers and instantiate them.
  
 <code python> <code python>
-    def init_drivers(enable_debug_driver=False)+    init_drivers(enable_debug_driver=False)
        """ Search for and initialize link drivers. If enable_debug_driver is True then the DebugDriver will also be used."""        """ Search for and initialize link drivers. If enable_debug_driver is True then the DebugDriver will also be used."""
 </code> </code>
  
-===== Connection- and link-callbacks =====+====== Connection- and link-callbacks ======
 Operations on the link and connection will return directly and will call the following callbacks when events occur: Operations on the link and connection will return directly and will call the following callbacks when events occur:
  
Line 111: Line 111:
 </code> </code>
  
-===== 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 123: Line 123:
 <code python> <code python>
     crazyflie = Crazyflie()     crazyflie = Crazyflie()
 +    crazyflie.connected.add_callback(crazyflie_connected)
     crazyflie.open_link("radio://0/10/250K")     crazyflie.open_link("radio://0/10/250K")
-    # Do stuff+</code> 
 + 
 +Then you can use the following to close the link again: 
 +<code python>
     crazyflie.close_link()     crazyflie.close_link()
 </code> </code>
-===== Sending control commands ===== + 
-Currently all the communication is either parameters, logging or control commands. The special case of control commands is to minimize latency since it's a faster way than using parameters.+====== Sending control commands ====== 
 +The control commands are not implemented as parameters, instead they have a special API. 
 + 
 +<code python> 
 +    send_setpoint(roll, pitch, yaw, thrust): 
 +        """ 
 +        Send a new control set-point for roll/pitch/yaw/thust to the copter 
 + 
 +        The arguments roll/pitch/yaw/trust is the new set-points that should 
 +        be sent to the copter 
 +        """ 
 +</code>
  
 To send a new control set-point use the following: To send a new control set-point use the following:
Line 139: Line 154:
 </code> </code>
  
-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. Depending on where the Crazyflie is, this can be a Bad Thing! 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 0.01 seconds, for 100 commands a second. This has a nice added benefit of allowing for very precise control. +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 153: Line 169:
 </code> </code>
  
-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 (using the code above) the parameter will be requested again 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).+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> 
 +    add_update_callback(group, name=None, cb=None) 
 +        """ 
 +        Add a callback for a specific parameter name or group. If not name is specified then 
 +        all parameters in the group will trigger the callback. This callback will be executed 
 +        when a new value is read from the Crazyflie. 
 +        """ 
 + 
 +    request_param_update(complete_name) 
 +        """ Request an update of the value for the supplied parameter. """ 
 + 
 +    set_value(complete_name, value) 
 +        """ Set the value for the supplied parameter. """ 
 +</code> 
 + 
 +Here's an example of how to use the calls.
 <code python> <code python>
     crazyflie.param.add_update_callback(group="group", name="name", param_updated_callback)     crazyflie.param.add_update_callback(group="group", name="name", param_updated_callback)
Line 161: Line 194:
 </code> </code>
  
-===== Logging =====+====== Logging ======
 The logging framework is used to enable the "automatic" sending of variable values at specified intervals to the client. This functionality should be used when: The logging framework is used to enable the "automatic" sending of variable values at specified intervals to the client. This functionality should be used when:
   * The variable is changed by the Crazyflie and not by the client   * The variable is changed by the Crazyflie and not by the client
   * The variable is updated at high rate and you want to read the value periodically   * The variable is updated at high rate and you want to read the value periodically
 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.
 +
 +
 +
 +The API to create and get information from LogConfig:
 +<code python>
 +    # Called when new logging data arrives
 +    data_received_cb = Caller()
 +    # Called when there's an error
 +    error_cb = Caller()
 +    # Called when the log configuration is confirmed to be started
 +    started_cb = Caller()
 +    # Called when the log configuration is confirmed to be added
 +    added_cb = Caller()
 +
 +    add_variable(name, fetch_as=None)
 +        """Add a new variable to the configuration.
 +
 +        name - Complete name of the variable in the form group.name
 +        fetch_as - String representation of the type the variable should be
 +                   fetched as (i.e uint8_t, float, FP16, etc)
 +
 +        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)."""
 +
 +    start()
 +        """Start the logging for this entry"""
 +
 +    stop()
 +        """Stop the logging for this entry"""
 +
 +    delete()
 +        """Delete this entry in the Crazyflie"""
 +</code>
 +
 +The API for the log in the Crazyflie:
 +<code python>
 +    add_config(logconf)
 +        """Add a log configuration to the logging framework.
 +
 +        When doing this the contents of the log configuration will be validated
 +        and listeners for new log configurations will be notified. When
 +        validating the configuration the variables are checked against the TOC
 +        to see that they actually exist. If they don't then the configuration
 +        cannot be used. Since a valid TOC is required, a Crazyflie has to be
 +        connected when calling this method, otherwise it will fail."""
 +</code>
 +
  
 To create a logging configuration the following can be used: To create a logging configuration the following can be used:
Line 186: Line 267:
     # Callback called when the connection is established to the Crazyflie     # Callback called when the connection is established to the Crazyflie
     def connected(link_uri):     def connected(link_uri):
-        crazyflie.log.add_coonfig(logconf)+        crazyflie.log.add_config(logconf)
  
         if logconf.valid:         if logconf.valid:
Line 198: Line 279:
         print "[%d][%s]: %s" % (timestamp, logconf.name, data)         print "[%d][%s]: %s" % (timestamp, logconf.name, data)
                          
-    def logging_error(logconf, msg)+    def logging_error(logconf, msg)
 +        print "Error when logging %s" % logconf.name
 </code> </code>
  
 +====== Examples ======
 +The examples are now placed in the repository in the examples folder.
  
-====== Logging and parameter examples ====== 
-Below are the two examples described in the video <insert link to video here when done> that shows the parameter and logging framework. They have been re-written to fit outside the QT context so they do not match the video 1:1 but they show the same concept. 
  
-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. 
- 
-===== Adding a parameter ===== 
-In this example we will add a parameter that will be used to "freeze" the LED update function. This isn't very useful but it shows how to use parameters :-) 
- 
-First of all we need to add the parameter to the firmware, this is done by using the macros PARAM_GROUP_START, PARAM_ADD and PARAM_GROUP_STOP. In //crazyflie-firmware/drivers/src/led.c// we insert the following to add a parameter: 
-<code c> 
-#include "param.h" 
- 
-bool ledFreeze = false; 
- 
-PARAM_GROUP_START(led) 
-PARAM_ADD(PARAM_UINT8, freeze, &ledFreeze) 
-PARAM_GROUP_STOP(led) 
-</code> 
- 
-This will add a parameter in the TOC named //led.freeze// of the type uint8_t. 
- 
-Now we should use this parameter on the client side. This can either be done by reading or writing the parameter. First we add the code to write the parameter: 
-<code python> 
-# crazyflie is an instance of the Crazyflie class that has been instantiated and connected 
-    crazyflie.param.setParamValue("led.freeze", True) 
-</code> 
- 
-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 the following is used: 
-<code python> 
-    crazyflie = Crazyflie() 
-    crazyflie.param.addParamUpdateCallback("led.freeze", paramUpdateCallback) 
- 
-    def paramUpdateCallback(name, value): 
-        print "%s has value %s" % (name, value) # This will in our example print: led.freeze has value True 
-</code> 
- 
-===== Adding loggable variables ===== 
-In this example we will add logging for the raw gyro values read from the sensor. 
- 
-First of all we add the variables to the logging TOC by using the macros LOG_GROUP_START, LOG_ADD and LOG_GROUP_STOP. In our example we edit the file //crazyflie-firmware/modules/src/stabalizer.c//: 
-<code c> 
-#include "log.h" 
-// The raw gyro values are stored in the gyro struct 
-LOG_GROUP_START(gyro) 
-LOG_ADD(LOG_FLOAT, x, &gyro.x) 
-LOG_ADD(LOG_FLOAT, y, &gyro.y) 
-LOG_ADD(LOG_FLOAT, z, &gyro.z) 
-LOG_GROUP_STOP(gyro) 
-</code> 
- 
-This will add the variables //gyro.x//, //gyro.y// and //gyro.z// to the logging TOC as floats. 
- 
-On the client side we now add the log configuration, start the logging and then a callback will be called every 10 ms when data arrives from the Crazyflie: 
-<code python> 
-    # Callback called when the connection is established to the Crazyflie 
-    def connected(linkURI): 
-        gyroconf = LogConfig("Gyro", 10) 
-        gyroconf.addVariable(LogVariable("gyro.x", "float")) 
-        gyroconf.addVariable(LogVariable("gyro.y", "float")) 
-        gyroconf.addVariable(LogVariable("gyro.z", "float")) 
- 
-        # crazyflie is an instance of the Crazyflie class that has been instantiated and connected 
-        gyrolog = crazyflie.log.newLogPacket(gyroconf) 
- 
-        if (gyrolog != None): 
-            gyrolog.data_received.addCallback(gyroData) 
-            gyrolog.startLogging() 
-        else: 
-            print "gyro.x/y/z not found in log TOC" 
- 
-    def gyroData(data): 
-        print "Gyrodata: x=%.2f, y=%.2f, z=%.2f" % (data["gyro.x"], data["gyro.y"], data["gyro.z"]) 
- 
-</code> 
- 
-====== Code example to add accelerometer logging====== 
-This example requires Crazyflie firmware newer then [[https://bitbucket.org/bitcraze/crazyflie-firmware/commits/bea9f1570f3b8bb2cc9691efec7d9b28342e7d8a?at=default|this]] commit where the accelerometer variables has been exposed by the firmware. (It is not included in the 2013.4 release) 
- 
-<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("radio://0/10/250K") 
- 
-    def connectSetupFinished(self, linkURI): 
-        """ 
-        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's 
-        data dictionary as logging info. 
-        """ 
-        # Set accelerometer logging config 
-        accel_log_conf = LogConfig("Accel", 10) 
-        accel_log_conf.addVariable(LogVariable("acc.x", "float")) 
-        accel_log_conf.addVariable(LogVariable("acc.y", "float")) 
-        accel_log_conf.addVariable(LogVariable("acc.z", "float")) 
- 
-        # Now that the connection is established, start logging 
-        self.accel_log = self.crazyflie.log.create_log_packet(accel_log_conf) 
- 
-        if self.accel_log is not None: 
-            self.accel_log.data_received.add_callback(self.log_accel_data) 
-            self.accel_log.start() 
-        else: 
-            print("acc.x/y/z not found in log TOC") 
- 
-    def log_accel_data(self, data): 
-        logging.info("Accelerometer: x=%.2f, y=%.2f, z=%.2f" % 
-                        (data["acc.x"], data["acc.y"], data["acc.z"])) 
- 
-Main() 
-</code> 
- 
-====== Simple code example ====== 
-As a starter guide, this application connects to the Crazyflie, turns on the motors, ramps up/down the rpm and then quits. 
- 
-<code python> 
-import time, sys 
-from threading import Thread 
-  
-sys.path.append("lib") 
-import cflib 
-from cflib.crazyflie import Crazyflie 
-  
-class Main: 
-    def __init__(self): 
-        self.crazyflie = Crazyflie() 
-        cflib.crtp.init_drivers() 
-  
-        # You may need to update this value if your Crazyradio uses a different frequency. 
-        self.crazyflie.open_link("radio://0/10/250K") 
-        # Set up the callback when connected 
-        self.crazyflie.connectSetupFinished.add_callback(self.connectSetupFinished) 
-  
-    def connectSetupFinished(self, linkURI): 
-        # Start a separate thread to do the motor test. 
-        # Do not hijack the calling thread! 
-        Thread(target=self.pulse_command).start() 
- 
-    def pulse_command(self): 
-        thrust_mult = 1 
-        thrust_step = 500 
- thrust = 20000 
-        pitch = 0 
-        roll = 0 
-        yawrate = 0 
-        while thrust >= 20000: 
-     self.crazyflie.commander.send_setpoint(roll, pitch, yawrate, thrust) 
-            time.sleep(0.1) 
-            if (thrust >= 25000): 
-                thrust_mult = -1 
-            thrust = thrust + (thrust_step * thrust_mult) 
-        self.crazyflie.commander.send_setpoint(0,0,0,0) 
-        # Make sure that the last packet leaves before the link is closed 
-        # since the message queue is not flushed before closing 
- time.sleep(0.1) 
-        self.crazyflie.close_link() 
- 
-Main() 
-</code> 
projects/crazyflie/pc_utils/pylib.txt · Last modified: 2021-06-24 16:58 by kimberly