An NMEA server on TCP for Yoctopuce modules

An NMEA server on TCP for Yoctopuce modules

One of our customers wants to integrate our sensors with software designed for standardized NMEA over TCP/IP sensors (NMEA stands for National Marine Electronic Association). Roughly, a NMEA sensor spontaneously sends its measures at a regular interval, in the shape of a line of text with values separated by commas. The simplest solution is thus to create a small TCP gateway reading any of our USB sensors and periodically sending the measures over TCP to each connected client. As this approach can be useful to others, we coded a short example.

Problem formulation


We want to be able to connect any Yoctopuce sensor by USB and receive its measures periodically on a TCP connection in text mode:

NMEA TCP/IP server achitecture for Yoctopuce sensors
NMEA TCP/IP server achitecture for Yoctopuce sensors



For example, for a PT100 sensor used to measure water temperature, we want to obtain messages of the type "$xxMTW" (Mean Temperature of Water) on the TCP connection:

$YSMTW,25.404,'C*39 $YSMTW,25.396,'C*35 $YSMTW,25.394,'C*37


The $ character at the beginning is compulsory. The first two letters are arbitrary and the next three identify the kind of measure. The star followed by a number at the end of the message corresponds to the checksum computed following the NMEA standard.

To enable much flexibility, we want to specify the NMEA code to use (in our case "MTW") in the sensor logical name. Sensors without an assigned logical name are automatically ignored.

Implementation


We decided to implement this software in C/C++, to made deployment as simple as possible: a short executable, available on any OS (Windows, Linux, Mac OS X) and which can run as a command line during tests as well as installed as a service.

To enable operation as a service, it's important to manage connections and disconnections correctly. Indeed, the sensor can be connected before or after the service starts. So the service initialization code looks like this:

YAPI::RegisterDeviceArrivalCallback(deviceArrival);
YAPI::RegisterDeviceRemovalCallback(deviceRemoval);
YAPI::DisableExceptions();
if (YAPI::RegisterHub("usb", errmsg) != YAPI::SUCCESS) {
    cerr << "RegisterHub error : " << errmsg << endl;
    return 1;
}


When a Yoctopuce sensor is connected, we look up all the functions of type "Sensor" available on the module and, if they have a logical name, we configure them to spontaneously send periodical measures at the chosen frequency. The two magical methods of this piece of code are set_reportFrequency and registerTimedReportCallback: thanks to them, the Yoctopuce sensor takes charge on its own to compute the mean value on a given periodic interval and to call our callback for each new measure.

static void deviceArrival(YModule *m)
{
    string serial = m->get_serialNumber();
    YSensor *sensor = YSensor::FirstSensor();
    while(sensor) {
        string name = sensor->get_logicalName();
        if(name.length() > 0 &&
           sensor->get_module()->get_serialNumber() == serial)
        {
            sensor->set_reportFrequency(Globalp.freq);
            sensor->registerTimedReportCallback(sensorTimedReportCallBack);
        }
        sensor = sensor->nextSensor();
    }
}


The callback called for each periodic measure simply sends the measure in the chosen format to each TCP client connected to the service. We included a configuration global parameter enabling us to chose between the true NMEA format and a simpler format separated by commas (CSV), without checksum and without all the trimmings.

static void sensorTimedReportCallBack(YSensor *fct, YMeasure measure)
{
    const char *header = (Globalp.encodeNMEA ? "$YS" : "");
    char       buf[512];

    snprintf(buf, sizeof(buf), "%s%s,%.3f,%s", header,
             fct->get_logicalName().c_str(),
             measure.get_averageValue(),
             fct->get_unit().c_str());

    if(Globalp.encodeNMEA) {
        // NMEA format: compute checksum and append it to the message
        int      i, len = (int)strlen(buf);
        unsigned chksum = 0;
        for(i = 1; i < len; i++) {
            chksum ^= (unsigned char)buf[i];
        }
        snprintf(buf+len, sizeof(buf)-len, "*%02X\r\n", chksum);
    } else {
        // raw format, just append CR-LF
        strcat(buf, "\r\n");
    }
    TCPBroadcast(buf);
}


From that moment on, everything is managed by callback, from the module hot-plug to measure management. The software main loop only needs to accept connections and to call the functions managing the callbacks when necessary:

while (Globalp.runMore) {
    TCPAccept(srvsock);
    YAPI::UpdateDeviceList(errmsg);
    YAPI::Sleep(300, errmsg);
}


The TCPAccept function simply accepts incoming connections on a new socket and adds this socket to a chained list.
The TCPBroadcast function used in the callback only needs to run down this chain to send a message on each socket. As this code contains nothing specific to Yoctopuce modules, we don't detail it here. For the same reason, you won't find details here on the Windows mechanism managing how the service mode works.

Practical information


Options supported in command line:

demo.exe [-n] [-p <port>] [-f <freq>] [-i|-u|-d] -n : adds the NMEA checksum to each line -p <port> : selection of the TCP/IP port used by the server -f <freq> : selection of measure frequency (for ex. 1/s, 20/m, 12/m, 30/h)


Windows specific options:

-i : installs the software as a service, with the specified options -u : uninstalls the software from the service list


Unix specific option:

-d : goes on running as a background task (daemon)


If you are interested in this piece of software, as such or to adapt it to your own needs, you can find the whole source code, makefiles, projects, and even the compiled binaries, in the examples of our latest C++ library, in the Prog-NMEA directory.

Add a comment No comment yet Back to blog












Yoctopuce, get your stuff connected.