ZMQ Server

The ZMQ framework (actually ØMQ) is used to interconnect applications or parts of applications to other applications via a range of interfaces. It’s really nice since there’s lots of language support and it’s easy to use.

The application uses 4 ports for communicating:

  • 2000: Main command/response socket (server/client)
  • 2001: Logging data and events (publish)
  • 2002: Param events and values(publish)
  • 2003: Connection events (publish)
  • 2004: Control data (pull)

All communication is done using JSON. To test the implementation we have a test client that could be useful to have a look at. Each message sent contains a version field that should always be included.

cfzmq

The client is run using the command line:

$ bin/cfzmq -h
usage: cfzmq [-h] [-u URL] [-d]

optional arguments:
  -h, --help         show this help message and exit
  -u URL, --url URL  URL where ZMQ will accept connections
  -d, --debug        Enable debug output

The default URL is set to only allow local connections:

tcp://127.0.0.1

The following commandline will allow remote control:

$bin/cfqmq --url "tcp://*"

Command socket

The command messages are implemented as server/client, where each request to the server is answered with a response. Each message to the server contains version, command and fields related to the command, Each response from the server will contain version and status, where status 0 means everything was ok. This makes all the calls on this port synchronous, where the server will not reply until the action is completed or it fails.

Example command:

{
  "version": 1,
  "cmd": "command",
  "arg1": "some argument",
  "arg2": "some other argument"
}

Example response of successful command:

{
  "version": 1,
  "status": 0
}

For each command there’s an enumerated set of statuses that will be used (see blow) and each message where status != 0 will contain the field msg detailing the error.

Example response of unsuccessful command:

{
  "version": 1,
  "status": 1,
  "msg": "Something went wrong..."
}

scan

The scan command will trigger a scanning of all of the available interfaces on the server (USB and Crazyradio) and return all the Crazyflies found. If no interfaces are available (no Crazyradio or Crazyflie) the command will return an empty list. Therefore there’s no error conditions for this command, status will always be 0.

Example command:

{
  "version": 1,
  "cmd": "scan"
}

Example response:

{
  "version": 1,
  "status": 0,
  "interfaces":
    [
      {
        "uri": "radio://0/100/250K",
        "info": "This is a Crazyflie"
      },
      {
        "uri": "debug://0/0",
        "info": "Normal connection"
      }
    ]
}

connect

The connect command will connect to the supplied URI, download the logging TOC and parameter TOC/values and return everything. There’s a timeout on the server-side that will be hit if the server can’t connect to a Crazyflie on the supplied URI (of if there’s some other error).

The log TOC will be found in the log dictionary, where the first level is group, the second level is name and the third is the attributes (see below). So the type of altHold.target will be found in log->altHold->target->type.

The param TOC will be found in the param dictionary, where the first level is group, the second level is name and the third is the attributes (see blow). So the RO/RW attribute for altHold.aslAlpha will be found in param->altHold->aslAlpha->access.

Example command:

{
  "version": 1,
  "cmd": "connect",
  "uri": "radio://0/10/250K"
}

Example response of successful command:

{
  "version": 1,
  "status": 0,
  "log": {
    "acc": {
      "mag2": {"type": "float"},
      "x": {"type": "float"},
      "y": {"type": "float"},
      "z": {"type": "float"},
      "zw": {"type": "float"}
    },
    "altHold": {
      "err": {"type": "float"},
      "target": {"type": "float"},
      "vSpeed": {"type": "float"},
      "vSpeedASL": {"type": "float"},
      "vSpeedAcc": {"type": "float"},
      "zSpeed": {"type": "float"}
    },
    "baro": {
      "asl": {"type": "float"},
      "aslLong": {"type": "float"},
      "aslRaw": {"type": "float"},
      "pressure": {"type": "float"},
      "temp": {"type": "float"}
    },
    "gyro": {
      "x": {"type": "float"},
      "y": {"type": "float"},
      "z": {"type": "float"}
    },
    "mag": {
      "x": {"type": "float"},
      "y": {"type": "float"},
      "z": {"type": "float"}
    },
    "mag_raw": {
      "x": {"type": "int16_t"},
      "y": {"type": "int16_t"},
      "z": {"type": "int16_t"}
    },
    "motor": {
      "m1": {"type": "int32_t"},
      "m2": {"type": "int32_t"},
      "m3": {"type": "int32_t"},
      "m4": {"type": "int32_t"}
    }
},
"param": {
  "altHold": {
    "altHoldChangeSens": {
      "access": "RW",
      "type": "float",
      "value": "200.0"
    },
    "altHoldErrMax": {
      "access": "RW",
      "type": "float",
      "value": "1.0"
    },
    "aslAlpha": {
      "access": "RW",
      "type": "float",
      "value": "0.920000016689"
    }
  }
}

If no Crazyflie is found status 1 will be returned and an error message will be supplied from the driver.

Example response of unsuccessful command:

{
  "version": 1,
  "status": 1,
  "msg": "Too many packages lost"
}

For the log variables (found in log) the following attributes are set:

Field Type Comment
type string (u)int8, (u)int16, (u)int32, float

For the parameters (found in param) the following attributes are set:

Field Type Comment
access string RO for read only parameters, RW for writable
type string (u)int8_t, (u)int16_t, (u)int32_t, float
value string String representation of the current parameter value

log

Logging data from the Crazyflie is done by setting up log configurations that will push log data at a specified interval (more info here). There are four command associated with log configurations: create, start, stop and delete. Create and delete handles if the log configuration is stored in the Crazyflie memory or not. Start and stop handles if the log data is actually being sent or not from the Crazyflie to the host. Before a log config can be started is has to be created, before it can be stopped it has to be started and before it can be deleted is has to be created. Note that log block are automatically started once they have been created.

Note: When a host connects to a Crazyflie the log configurations are all deleted. So if you connect, set up log configurations, disconnect and then connect again the configurations will be deleted.

Below is an example for creating a logging configuration and starting it. The configuration contains the two variables pm.vbat and stabilizer.roll that will be sent at 1 Hz. Data will be published to the log socket.

Each action for log configurations (create, start, stop, delete) will be broadcasted on the log data socket. Log data will also be broadcasted on the same socket.

First create the configuration:

{
  "version": 1,
  "cmd": "log",
  "action": "create",
  "name": "Test log block",
  "period": 1000,
  "variables": [
      "pm.vbat",
      "stabilizer.roll"
  ]
}

Example response:

{
  "version": 1,
  "status": 0
}

Then start the configuration:

{
  "version": 1,
  "cmd": "log",
  "action": "start",
  "name": "Test log block"
}

Example response:

{
  "version": 1,
  "status": 0
}

The following attributes should be set in the request packet:

Field Type Comment Mandatory for
name string Name of configuration all
action string create, start, stop, delete all
period int Period (in ms) for data to be sent create
variables list List of variables “group.name” create

The following errors can be seen in the response packet:

Action Status Comment
create 0x01 One or more variables were not found in the TOC
create 0x02 The period is either too small/large of the configuration too large
create 0x03 Timeout was hit when performing action.
start/stop/delete 0x01 Config name not found
start/stop/delete 0x02 Timeout was hit when performing action

note: The Python API supports logging variables using different types than what the variables is declared as in the firmware. I.e you can log a uint32_t as a uint8_t, retaining the 8 MSB (more info here). This is still not implemented.

param

During run-time it’s possible to set parameters that are mapped directly to variables in the firmware(more info here). Each parameter update is also published on the param socket.

Below is an example command to set the flightctrl.xmode parameter.

Example command:

{
    "version": 1,
    "cmd": "param",
    "name": "flightctrl.xmode",
    "value": True
}

Example response of successful command:

{
    "version": 1,
    "status": 0,
    "name": "flightctrl.xmode",
    "value": "1"
}

The following errors can be seen in the response packet:

Status Comment
0x01 The parameter was not found in the TOC
0x02 The parameter is RO and cannot be set
0x03 The timeout was reached

Example response of un-successful command:

{
    "version": 1,
    "status": 1,
    "msg": "Could not find flightctrl.xmode in TOC"
}

The API accepts values as unsigned/signed/float/bool. Booleans are stored as uint8_t and will be converted to a number (0 for false, 1 for true). The type should match the type that is in the TOC (i.e don’t try to set a float for a uint_8 variable).

Field Type Comment
name string Name of parameter (group.name)
value unsigned/signed/float/bool When received a string is created from the value

Log socket

This socket is used for sending log configuration events as well as log data. The events that are sent is for creating, starting, stopping and deleting a configuration. For every started configuration the log data will be sent over this socket. To control this see the log configuration above.

Each message contains an event field (see below) and a name field referring to the log configuration name.

The following events are sent:

Event Comment
created When a configuration is created
started When a configuration is started
stopped When a configuration is stopped
deleted When a configuration is deleted
data Log data (see below)

Example of a started event:

{
  "version": 1,
  "name": "Test log block",
  "event": "started"
}

The following fields is in the data event:

Field Type Comment
name string Name of the config that triggered the data
timestamp int Time since system start (in ms)
variables dict Dictionary where the keys are variable names (group.name) and the values are the variable values

Example of a data event:

{
  "version": 1,
  "name": "Test log block",
  "event": "data",
  "timestamp": 1004,
  "variables":
    {
      "pm.vbat": 3.5,
      "stabilizer.roll": -80.0
    }
}

Param socket

This socket is used to broadcast parameter updates done on the command socket

For each update the variable name and value is sent.

{
    "version": 1,
    "name": "flightctrl.xmode",
    "value": "1"
}

Connection socket

This socket is used to broadcast changes in the connection state as events. Connecting the Crazyflie is a synchronous call to the command socket but for instance a lost connection will be asynchronous and broadcasted on this socket.

Each event has a name and uri, there might also be an optional message. Note that disconnected is always sent no matter the reason. So a requested disconnect will send a disconnected event, and a lost connection will send a lost event as well as a disconnected event.

There’s a number of different events:

Event Comment Msg field
requested A connection has been requested No
connected A Crazyflie has been connected and the TOCs has been downloaded No
failed A connection request has failed Yes
disconnected A Crazyflie has been disconnected No
lost An open connection has been lost Yes

Example of a lost connection:

{
    "version": 1,
    "event": "failed",
    "uri": "radio://0/10/250K",
    "msg": "Too many packets lost!"
}

Control socket

Control commands can be sent at any time after the Crazyflie has been connected and has the following scaling/format:

{
  "version": 1,
  "roll": 0.0,
  "pitch": 0.0,
  "yaw": 0.0,
  "thrust": 0.0
}
Param Unit Limit
roll degrees N/A
pitch degrees N/A
yaw degrees/s N/A
thrust PWM 20 000 - 60 000