This week's post is dedicated to beginners who have just received their first Yoctopuce module and who wonder about the kind of code they are going to need to write to make it work. So we are going to talk about the general structure of an application using Yoctopuce modules.
If you are a beginner and if you want to go fast, you can start from the programming examples provided with each programming library. Some programming by imitation will probably enable you to reach your goal. However, if you want to understand in details what these examples do, take a few minutes to read this post.
Installing the API
The first thing to do is to install the Yoctopuce library of your choosing. You can find in the "Tutorials" section the first steps that you must follow for each programming language. When you have performed this installation, you can get to the heart of the matter.
Initializing the Yoctopuce API
The Yoctopuce API is the interface between your program and Yoctopuce modules. To be able to use it, it is imperative that you initialize this API. Otherwise, no call will work. The easiest way consists in calling the registerHub function which takes two parameters:
- url: this parameter defines the API working mode. It can take the "usb" value to indicate that you want to use the modules connected to the local machine by USB. But it can also be the IP address of a remote machine, in which case this machine can be a regular computer running the VirtualHub or a YoctoHub.
- errmsg: this parameter contains, upon return, a potential error message. In languages which do not allow to pass parameters by addressing, errmsg is in fact a simple object containing a character string.
The RegisterHub() function returns the YAPI.SUCCESS value if everything went well. Otherwise, errmsg contains a description of the problem. The error that you will encounter most frequently at this stage is "Another process is already using yAPI" because you can't run two Yoctopuce applications at the same time which both use the USB mode on the same machine. This is one of the reasons why you must initialize the application as soon as possible in the life of your application. If the API initialization fails, it's probably not worth it to go on.
You can very well call RegisterHub() several times with different parameters and thus access at the same time modules connected to different machines. This being said, calling RegisterHub() several times with the same URL serves no purpose.
The beginning of you application must therefore contain a piece of code looking more or less like this:
{ Console.WriteLine("RegisterHub error: " + errmsg);
Environment.Exit(0);
}
Note that RegisterHub() is a blocking function, but there is a non-blocking equivalent: PreregisterHub().
Accessing Yoctopuce modules
When you have initialized the API, you need to access your module. From the API standpoint, all the Yoctopuce modules are presented as of a set of functions which are independent from one another. If this sentence is not clear enough, we strongly recommend you to read this post which explains the logical structure of Yoctopuce modules. Each type of function is coded in a separate class that you need to include in one way or another in your project. To obtain a reference on the function that you are interested in, there are two possible approaches:
Enumerating
You can call the "FirstXxxxx()" method of the class corresponding to the function you are looking for and you'll obtain the first available instance, or the NULL value if no corresponding instance was found. If you want to list all matching functions across all connected devices, just repetitively call the nextXxxx() method from the last returned object. It's easy and convenient, but you have no guarantee on the order in which you obtain these references.
YTemperature t = YTemperature.FirstTemperature();
if (t==null) Console.WriteLine("not found");
YTemperature t = YTemperature.FirstTemperature();
while(t!=null)
{ Console.WriteLine("found"+t.get_friendlyName());
t=t.nextTemperature();
}
Name addressing
If there is a risk of having several functions of the same type in your system, the best method to find one in particular consists in calling the FindXxxx() method of the said function. This function takes a parameter which is the serial number of the module hosting the function, followed by a dot and by the name of the function. For example, if you are looking for the temperature function present on a Yocto-Meteo-V2 with serial number METEOMK2-12BBB2, you can write:
The serial number of your modules is located on the packaging label. You can also use the VirtualHub to retrieve the serial number of a module.
Instead of the serial number and of the function name, you can also use logical names that you must have previously assigned. These logical names are persistent and will survive to a to a power loss. You can use the VirtualHub or the API to assign them.
When you have obtained a reference on the function you are interested in, this reference remains valid for the duration of the application, you don't need to call FindXxxxx() again.
But it is important to understand that FindXxxxx() always returns a reference to a valid Xxxx object, even if the module corresponding to the name passed as parameter is not connected or even if this name doesn't correspond to anything. To test whether object Xxxx corresponds to a function which is actually present in the system, you must use the isOnline() method of this object.
if (!t.isOnline()) Console.WriteLine("not connected.");
However, if you connect the Yocto-Meteo-V2 later in the life of the application, you don't need to call FindXxxx() again. The YTemperature object returned by the first call to FindXxxx() will automatically become usable.
Whatever the programming language that you are using, you must never deallocate yourself the objects returned by the API.
Interacting with a module
When you have the reference on the function you are interested in, you only need to use it. But you must be aware that Yoctopuce modules being USB devices, there is an important risk for them to be disconnected sometime in the life of the application. You can adapt to this risk by making sure that the said function is actually present right before using it, with isOnline()
{ double value = t.get_currentValue();
Console.WriteLine("valeur = "+v.toString());
}
However, nothing guaranties that no one will disconnect your module in the moment between when you call isOnline and when you call get_currentValue. Moreover, a call to isOnline takes time: expect about 20ms. An alternative consists in using exceptions to detect problems.
try
{ value = t.get_currentValue();
Console.WriteLine("value = "+v.toString());
}
catch (Exception e)
{ Console.WriteLine(e.Message);
}
If you don't like to work with exceptions, you can disable them and settle for testing the returned value.
...
double value= t.get_currentValue();
if (value!= YAltitude.CURRENTRAWVALUE_INVALID)
Console.WriteLine("value = "+v.toString());
else
Console.WriteLine("Oops!");
Efficiency
As long as you need to interact with your Yoctopuce modules only occasionally, let's say less than once per second, you can settle for this kind of somewhat simplistic code. But you must know that any call which interacts directly with a Yoctopuce module can take 20 to 30ms. If you need to frequently interact with your modules, you will have to replace this quick and dirty polling technique with callback programming.
Time delay
Most likely, the heart of your application is based on a loop. If you need to add a time delay in this loop, avoid using the system sleep() function. Rather use the YAPI.Sleep() instead. Without you noticing it, there are many computations occurring in the background in the Yoctopuce API. And the API needs to take control of the main thread once in a while to perform its work. In particular, the different callbacks generated by the Yoctopuce API are called only during YAPI.Sleep(), YAPI.HandleEvents(), and YAPI.UpdateDeviceList().
{
try
{ value t.get_currentValue();
Console.WriteLine("value = "+v.toString());
}
catch (Exception e)
{ Console.WriteLine(e.Message);
}
// Thread.Sleep(1000); // Bad idea
YAPI.Sleep(1000,ref errmsg); // It's better
}
Ending the application
When your application ends, it's good practice to call YAPI.FreeAPI() which frees the resources used by the API. It's not formally mandatory, as the operating system will do so in any case, but not doing it can have unforeseen consequences. You must know that some calls to the API are asynchronous, therefore YAPI.FreeAPI() waits for all these asynchronous operations to end before handing it over. The typical case is a program which simply performs a whole series of set_xxxx() to configure a module. If the program ends without a call to YAPI.FreeAPI(), there is a risk that the last few set_xxxx() don't have time to end.
Conclusion
As a conclusion, we offer you a small complete program which queries a Yoctopuce sensor once per second during one minute. It takes advantage from the fact that all the classes corresponding to a Yoctopuce sensor inherit from the YSensor class. Here is the C# version:
namespace example
{ class Program
{
static void Main(string[] args)
{
string errmsg = "";
if (YAPI.RegisterHub("usb",ref errmsg) !=YAPI.SUCCESS)
{ Console.WriteLine(errmsg);
Environment.Exit(0);
}
String name = "METEOMK2-12BBB2.temperature";
YSensor sensor;
sensor = YSensor.FirstSensor();
if (sensor==null)
{ Console.WriteLine("no available sensor");
Environment.Exit(0);
}
/* variant
sensor = YSensor.FindSensor(name);
if (!sensor.isOnline())
{ Console.WriteLine("sensor not found");
Environment.Exit(0);
}*/
for (int i=0;i<60;i++)
{ if (sensor.isOnline())
Console.WriteLine(sensor.get_currentValue().ToString()+ sensor.get_unit());
else
Console.WriteLine("not connected");
YAPI.Sleep(1000, ref errmsg);
}
YAPI.FreeAPI();
}
}
}
And to make a good impression, exactly the same code, but in Python this time:
# -*- coding: utf-8 -*-
import os, sys
from yocto_api import *
errmsg = YRefParam()
if YAPI.RegisterHub("usb", errmsg) != YAPI.SUCCESS:
sys.exit(errmsg.value)
name = "METEOMK2-12BBB2.temperature"
sensor = YSensor.FirstSensor()
if sensor is None:
sys.exit("no available sensor")
# variant
#sensor = YSensor.FindSensor(name)
#if (not sensor.isOnline()):
# sys.exit("sensor not found")
for i in range(0,60):
if (sensor.isOnline()):
print(str(sensor.get_currentValue())+ sensor.get_unit())
else:
print("not connected")
YAPI.Sleep(1000, errmsg)
YAPI.FreeAPI()