Skip to content
Rémi Bèges edited this page Mar 3, 2016 · 9 revisions

This tutorial will guide you on how to communicate in both ways with an Mbed device, to your computer, in just a few lines of code and a matters of minutes.

If you don't know yet how to setup telemetry on mbed, see the previous tutorial.

For this tutorial, you will also need to import the mbed library.

Publishing

Let's start off by sending data from the mbed board to the computer. With Telemetry, sending data is called publishing.

In your main.cpp file, instantiate the Telemetry class. By default it will use the serial port (UART) with a baudrate of 9600 bauds.

#include "Telemetry.h"
#include "mbed.h"

int main()
{
   Telemetry TM;

Create a timer to limit the rate at which we are going to publish.

    Timer refresh;
    refresh.start();

Finally, the next code publishes some random data on a topic called "rand" every hundred milliseconds

    for( ; ; )
    {
        if(refresh.read_ms() > 300)
        {
            refresh.reset();
            TM.pub_u32("rand",rand());    
        }
    }

Full code on mbed and importable program

If you connect to the device with pytelemetrycli, you can run a few commands to observe the result desktop-side: todo : animated ttty gif

 >: ls
foo
 >: print foo
123

Subscribing

Minimal code

At this point, you know how to send the data from the device to the computer. But what about the other way around ?

telemetry can notify you by calling one of your own custom function every time a frame is received. This is called subscribing. Here is how you do it.

The function called for telemetry should all have the same signature. Such a function must return void, and has two arguments TM_state* and TM_msg*. In a first time, let's ignore those parameters.

Here is an example of such a function.

void myCustomFunction(TM_state * state,  TM_msg * msg)
{
    // For now, do nothing
}

In the main function, myCustomFunction will be subscribed. Each time new data is received from the computer, telemetry will call myCustomFunction.

Consider the following code.

static uint8_t myInc;

void myCustomFunction(TM_state * state, TM_msg * msg)
{
    myInc++;
}

void main()
{
    myInc = 0;
    init_telemetry();
    subscribe(myCustomFunction); // Subscribe our function to be notified 

    for( ; ; )
    {
        update_telemetry(0.0);
    }
}

In this code, each time a new frame is received, myCustomFunction is called (during update_telemetry), causing the variable myInc to be incremented.

Filtering by topic

Incrementing a variable for any received frame is fine but not that much interesting. Let's spice things up a little bit.

Now, the variable myInc will be incremented only if a frame with the topic "foobar" was received.

You can get the topic associated with any frame using the second argument TM_msg * msg of myCustomFunction.

TM_msg is a data structure, defined by telemetry, that contains all the informations about a received frame (its topic, the payload, etc). TM_msg has the following definition.

struct TM_msg {
  TM_type type;
  char * topic;
  void * buffer;
  uint32_t size;
};

Let's modify just our myCustomFunction, to read the topic contained in TM_msg and do something with it.

void myCustomFunction(TM_state * state,  TM_msg * msg)
{
   // If both string match strcmp will return 0
   // See http://www.cplusplus.com/reference/cstring/strcmp/

   if(strcmp("foobar",msg->topic) == 0)
   {
       myInc++; // myInc is now only incremented if received topic is foobar !
   }
}
// no change in main

This code is pretty much self-explanatory. We are using the standard strcmp function to compare two strings, to see if received topic matches with foobar.

Filtering by data type

It is also possible to filter by received data type.

Telemetry supports all standard portable datatypes and strings.

In the next example, myInc will be incremented only if the received data was of type uint8_t.

The data type of a frame is also contained in TM_msg, precisely under msg->type. All supported datatypes are enumerated by telemetry in TM_type:

enum TM_type {
  TM_float32 = 0, // float
  TM_uint8 = 1,   // unsigned char number
  TM_uint16 = 2,  // unsigned short number
  TM_uint32 = 3,  // unsigned int number
  TM_int8 = 4,    // signed char number
  TM_int16 = 5,   // signed short number
  TM_int32 = 6,   // signed int number
  TM_string = 7   // string
};

Again, just a couple of modifications are required to myCustomFunction to filter by datatype.

#include "string.h"
static uint8_t myInc;

void myCustomFunction(TM_state * state,  TM_msg * msg)
{
   if(msg->type == TM_uint8) // Filter by datatype
   {
       myInc++;
   }
}

void main()
{
    myInc = 0;
    init_telemetry();
    subscribe(myCustomFunction); 

    for( ; ; )
    {
        update_telemetry(0.0);
    }
}

Writing the received data into the variable

Now comes the interesting part. We will no longer increment a variable ; instead, we are going to write in myInc the received data.

We will still only do this if:

  • the data was send under topic foobar
  • the received data was of type uint8_t

The TM_msg data provides direct access to the data, but in a raw, undecoded form. To transform this raw data into something that can be written in a variable, we need to use the emplace functions. There are different variants of the emplace functions:

src/telemetry/headers/telemetry.h

uint32_t emplace(TM_msg * m, char * buf, size_t bufSize);
uint32_t emplace_u8(TM_msg * m, uint8_t * dst);
uint32_t emplace_u16(TM_msg * m, uint16_t * dst);
uint32_t emplace_u32(TM_msg * m, uint32_t * dst);
uint32_t emplace_i8(TM_msg * m, int8_t * dst);
uint32_t emplace_i16(TM_msg * m, int16_t * dst);
uint32_t emplace_i32(TM_msg * m, int32_t * dst);
uint32_t emplace_f32(TM_msg * m, float * dst);
```

Because our `myInc` variable is of type `uint8_t`, we are going to use `emplace_u8`. `myCustomFunction` will only require a couple of modifications so that `myInc` can be written with the received data.
```c

static uint8_t myInc;

void myCustomFunction(TM_state * state,  TM_msg * msg)
{
   if(strcmp("foofoo",msg->topic) == 0) // filter by topic
   {
      if(msg->type == TM_uint8) // then filter by datatype
      {
         if(emplace_u8(msg, &myInc) // Write the received value inside myInc
         {
            // Success ! myInc now still contains the received data
         }
      }
   }
}
```
 
### Conclusion and going further

This way of specifying behavior is extremely flexible. You build your application with the protocol, and it doesn't get in the way.
What if, on topic `booyaa` you wanted to start a function, but on topic `PID` you need to register values somewhere ? Using `telemetry`, there is nothing preventing you to do so.

Now, you may have noticed that, to store received values in a variable, a global variable (`myInc`) was used. There is a better way to make data accessible both from `myCustomFunction` and the `main` function. This is covered in the next tutorial called [Advanced usage](https://github.com/Overdrivr/Telemetry/wiki/Advanced-usage).

You may also want to check at this point the [list of examples](https://github.com/Overdrivr/Telemetry/wiki/Projects-using-telemetry).

Setup

Get started for embedded platforms

Get started for remote debug and remote control

  • Fast data visualization with the command line interface (todo)
  • Fast prototyping remote program control with python (todo)

General knowledge

Troubleshooting

  • Frequently Asked Questions todo

Examples and projects

Clone this wiki locally