How to start in C++ with Yoctopuce modules

How to start in C++ with Yoctopuce modules

This week, we continue our "for the beginners" series and we are writing about C++. C++ is not an easy language to handle, and even less so for someone who has never coded. But the language is still relevant when you use machines with a limited disk space. Therefore, we are going to see how to implement you first small program using our C++ library.





For this example, we are going to write a short program which controls the output of a Yocto-PowerRelay depending on the temperature measured by a Yocto-Temperature.

This post assumes that you already know how to program in C++ as its aim is not to be a C++ tutorial, but to learn to use our C++ library. In the same way, if you are not familiar with Yoctopuce modules, we recommend that you start by reading the previous posts of this series, in particular the one on the logical structure of Yoctopuce modules.

The program


As said before, the aim is to write a short program which switches a relay when the temperature goes above a given threshold, 25°C for example.

Let's start by adding the required #include directives. We must minimally include "yocto_api.h" and then the "yocto_xxx.h" files corresponding to the functions we want to use. In our case, we are going to use a temperature sensor and a relay, so we must include "yocto_temperature.h" and "yocto_relay.h".

Our file must therefore start with this:

#include "yocto_api.h"
#include "yocto_temperature.h"
#include "yocto_relay.h"



Then we must initialize the API, telling it that we want to work in USB mode, that is that we want to detect only the modules that are connected on the machine USB ports.

To do so, we must call the RegisterHub method of the YAPI class, passing it "usb" as parameter.

string errmsg;
if (YAPI::RegisterHub("usb", errmsg) != YAPI::SUCCESS) {
  cerr << "RegisterHub error: " << errmsg << endl;
  return 1;
}



The call returns YAPI::SUCCESS if everything went well, otherwise an error message is copied into the errmsg string that is passed by reference. The most common cause of error at this level is that there is already another program using the API in USB mode. It's for example the case if the VirtualHub is running. You can find more details on registerHub usages in another post.

The other classic error, if you work under Linux, is not to have sufficient privileges to access the USB ports. In this case, you can either run your program as root, or modify your udev rules. We wrote a full post on this topic: How to begin with Yoctopuce devices on Linux.

Retrieving functions


As you have read the post on the logical structure of Yoctopuce modules, you know that the Yocto-Temperature and the Yocto-PowerRelay provide different functions among which temperature and relay. It's these two functions we are interested in. There are several ways to retrieve them.

FirstXXX


If you are sure to have only one instance of the temperature function and one instance of the relay function available, you can keep it very simple and use the FirstXXX method of the YTemperature and YRelay classes.

YTemperature* t = YTemperature::FirstTemperature();
if (t == NULL) {
  cerr << "No temperature sensor found" << endl;
  return 1;
}

YRelay* r = YRelay::FirstRelay();
if (r == NULL) {
    cerr << "No relay found" << endl;
    return 1;
}


These methods return the first occurrence of the wanted function, or NULL if nothing corresponds. Beware however if you have two Yoctopuce modules providing the same function, nothing guarantees the order in which the modules are enumerated.

FindXXX and serial number


You can also use the FindXXX method of the YTemperature and YRelay classes with the module serial numbers. Let's assume that the serial number of the Yocto-Temperature is TMPSENS1-3EA8F and that the serial number of the Yocto-PowerRelay is RELAYHI1-56FC0, you can use:

YTemperature* t = YTemperature::FindTemperature("TMPSENS1-3EA8F.temperature");
if (t->isOnline()) {
  cerr << "TMPSENS1-3EA8F.temperature sensor not found" << endl;
  return 1;
}

YRelay* r = YRelay::FindRelay("RELAYHI1-56FC0.relay1");
if (r->isOnline()) {
    cerr << "RELAYHI1-56FC0.relay1 not found" << endl;
    return 1;
}



In the opposite to FirstXXX, FindXXX always returns a valid object. Therefore, you must test that it actually corresponds to a connected module with the isOnline() method. This enables you to instantiate objects corresponding to modules which will be connected later on in the program life.

FindXXX and logical names


Instead of the serial number, you can use a logical name that has been assigned previously, for example with the VirtualHub.

Assigning a logical name to the relay of the Yocto-PowerRelay
Assigning a logical name to the relay of the Yocto-PowerRelay


Let's suppose that you name the relay "myRelay" and the temperature sensor "myTemp". You will just have to write:

YTemperature* t = YTemperature::FindTemperature("myTemp");
if (t->isOnline()) {
  cerr << "myTemp sensor not found" << endl;
  return 1;
}

YRelay* r = YRelay::FindRelay("myRelay");
if (r->isOnline()) {
    cerr << "myRelay not found" << endl;
    return 1;
}



The advantage of logical names is twofold: on the one hand they enable you to navigate more easily in complex systems made of numerous Yoctopuce modules, and on the other hand, they allow you to build several copies of the same system without having to adapt the code to the serial numbers of the modules of each copy.

The main loop


The main loop of our program consists in reading the sensor temperature, then to adjust the relay position depending on this temperature, as long as both modules are connected.

while (t->isOnline() && r->isOnline()) {
  double value = t->get_currentValue();
  if (value > 25) {
    r->set_state(YRelay::STATE_B);
  }
  if (value < 24) {
    r->set_state(YRelay::STATE_A);
  }
}



Note that we switch the relay to state B if the temperature is above 25°C, while we go back to state A only if the temperature goes below 24°C. This is called a Schmitt trigger and it prevents the relay from oscillating when the temperature is right at the threshold.

We could stop here, but it's not necessary to run the loop at maximum speed. Anyway, the Yocto-Temperature has a refresh rate of 10Hz. You can therefore insert in the loop a 100ms waiting period without this impacting the reactivity of the system.

  YAPI::Sleep(100, errmsg);



Note that you had better use YAPI::Sleep() rather than usleep() as the latter completely blocks the program execution, while the former allows the API to continue to manage its internal business with modules during the wait. Moreover, usleep() is not available on Windows but YAPI::Sleep() is :-)

Stopping the library


Before exiting the program, you must call the YAPI::FreeAPI method which frees all the resources used by the library and more importantly makes sure that all the communications are correctly ended.

  YAPI::FreeAPI();



When you put all the pieces together, you obtain the complete code of the program:

#include <iostream>

#include "yocto_api.h"
#include "yocto_temperature.h"
#include "yocto_relay.h"

using namespace std;

int main(int argc, const char* argv[])
{
  string errmsg;
  if (YAPI::RegisterHub("usb", errmsg) != YAPI::SUCCESS) {
    cerr << "RegisterHub error: " << errmsg << endl;
    return 1;
  }

  YTemperature* t = YTemperature::FirstTemperature();
  if (t == NULL) {
    cerr << "No temperature sensor found" << endl;
    return 1;
  }

  YRelay* r = YRelay::FirstRelay();
  if (r == NULL) {
    cerr << "No relay found" << endl;
    return 1;
  }

  while (t->isOnline() && r->isOnline()) {
    double value = t->get_currentValue();
    if (value > 25) {
      r->set_state(YRelay::STATE_B);
    }
    if (value < 24) {
      r->set_state(YRelay::STATE_A);
    }
    YAPI::Sleep(100, errmsg);
  }
  YAPI::FreeAPI();
  return 0;
}



Allocating Yxxxxx objects


Note that the library functions always return pointers to objects and that these objects are not explicitly allocated or freed with the new and delete operators.

You don't need to allocate or free objects returned by the Yoctopuce library. The library keeps a reference on all these returned objects and manages by itself the allocation or freeing of the objects. If you call ten times in a row the YTemperature::FindTemperature("myTemp") method, only one object is allocated the first time and the following calls return the same object. These objects are kept in the memory until the library is stopped with the YAPI::FreeAPI() method.

You must therefore never call delete on a Yxxx object, otherwise the library continues to use the pointer to this object and cashes the program.

Downloading the Yoctopuce C++ library


You can either download the zip file from our page , or use Git and clone the library from GitHub. The archive contains a directory with the API documentation and another directory with examples.

The most important directory is Sources which, as its name indicates it, contains the library C++ source files. In this directory, there is also a yapi subdirectory containing the library C source files.

Compiling the program

Now that we have seen the code and that we have downloaded the library, we must compile this program to generate an executable. The simplest way to do so is to use an IDE.

In a previous post, we explained in details how to include the library in a Visual Studio, Code::Block, C++ Builder, and XCode project.

If you want to compile this program in the old way, here are the steps you must perform:

  1. Copy the Sources/yapi directory in the program directory
  2. Copy the yocto_api.h and yocto_api.cpp files of the Sources/yapi directory in the program directory
  3. Copy the yocto_temperature.h and yocto_temperature.cpp files of the Sources/yapi directory in the program directory
  4. Copy the yocto_relay.h and yocto_relay.cpp files of the Sources/yapi directory in the program directory
  5. Compile the C files of the yapi directory (gcc -c option)
  6. Compile the C++ files yocto_api.cpp, yocto_temperature.cpp, and yocto_relay.cpp (gcc -c option)
  7. Compile and link the example, including the previously compiled files and the pthread and usb-1.0 libraries


Here are the commands to compile the example we just implemented under Linux with gcc.

gcc yapi/yapi.c -c gcc yapi/yfifo.c -c gcc yapi/yhash.c -c gcc yapi/yjni.c -c gcc yapi/yjson.c -c gcc yapi/ykey.c -c gcc yapi/ymemory.c -c gcc yapi/ypkt_lin.c -c gcc yapi/ypkt_osx.c -c gcc yapi/ypkt_win.c -c gcc yapi/yprog.c -c gcc yapi/ystream.c -c gcc yapi/ytcp.c -c gcc yapi/ythread.c -c g++ yocto_api.cpp -c g++ yocto_relay.cpp -c g++ yocto_temperature.cpp -c g++ main.cpp *.o -lpthread -lusb-1.0




Note, if you want to cross compile this program, for example to generate a binary for OpenWRT, you will need some environment variables for the compiler to use the header and libraries of your toolchain.

The complete directory of this example is available on GitHub: https://github.com/yoctopuce-examples/cpp_beginners;

Conclusion


We kept this example simple because the aim of this post is to help you start and write you first program. But now that you know how to commute a relay, it's a safe bet that you will want to implement something more complex. In this case, you will certainly find the following posts an interesting read:

Add a comment No comment yet Back to blog












Yoctopuce, get your stuff connected.