lrfultisenseparser_web_logo

LrfUltisenseParser C++ library

v1.0.3

Table of contents

Overview

The LrfUltisenseParser C++ library is designed for encoding control commands and decoding responses for Safran Laser Range Finders. The library provides simple interface and doesn’t have third party dependencies to be installed in OS. It uses C++17 standard. Also, the library provides test application to check communication with the LRF via serial ports. Test application depends on SerialPort library (provides methods to work with serial ports). The SerialPort library is not a part of this repository (see Build stand alone).

LrfUltisenseParser is general-purpose software: it is not tied to any sector or application, and the developer of a program decides what the program does with it.

Versions

Table 1 - Library versions.

Version Release date What’s new
1.0.0 30.07.2024 - First version.
1.0.1 27.09.2024 - Fixed decoding distance reply.
1.0.2 26.10.2024 - SerialPort submodule update for test application.
1.0.3 20.10.2026 - Git sub-repositories removed: the test application expects the SerialPort library next to the repository (see Build stand alone).
- Warning options and exploit mitigation options (stack protector, FORTIFY_SOURCE, stack clash protection, control flow protection, full RELRO, non-executable stack, /GS, /guard:cf, /sdl for MSVC) are enabled for the library and the test application. C++ compiler extensions are disabled.
- getCommand(…) returns FALSE if the data pointer is nullptr (the method dereferenced the pointer).
- Command encoding is table driven, decodeResponse(…) does not allocate memory and does not throw exceptions. The encoded commands and the decoded responses are not changed.
- Test application: invalid numbers and the end of the standard input are handled, the mode “just read data” does not send data, an invalid command ID does not repeat the previous command. The parameter –self-test runs the automatic tests of the library.
- Documentation corrected.

Library files

The library is supplied only by source code. The user is given a set of files in the form of a CMake project (repository). The repository structure is shown below:

CMakeLists.txt ---------------------- Main CMake file of the library.
src  -------------------------------- Folder with library source code.
    CMakeLists.txt ------------------ CMake file of the library.
    LrfUltisenseParser.h ------------ Main library header file.
    LrfUltisenseParserVersion.h ----- Header file with library version.
    LrfUltisenseParserVersion.h.in -- CMake service file to generate version header.
    LrfUltisenseParser.cpp ---------- C++ implementation file.
test -------------------------------- Folder for test application files.
    CMakeLists.txt ------------------ CMake file for test application.
    main.cpp ------------------------ Source C++ file of test application.
static ------------------------------ Folder with images of this document.

LrfUltisenseParser class description

LrfUltisenseParser class declaration

namespace rapidpixel
{
class LrfUltisenseParser
{
public:

    /// Get library version.
    static std::string getVersion();

    /// Encode command.
    bool getCommand(uint8_t* data, int& size, LrfUltisenseCommand id, int arg = 0);

    /// Decode response byte-by-byte.
    LrfUltisenseResponse decodeResponse(uint8_t nextByte);

    /// Get current distance.
    float getDistance();
};
}

getVersion method

The getVersion() static method returns a string of current LrfUltisenseParser class version. Method declaration:

static std::string getVersion();

Method can be used without LrfUltisenseParser class instance:

std::cout << "LrfUltisenseParser version: " << rapidpixel::LrfUltisenseParser::getVersion() << std::endl;

Console output:

LrfUltisenseParser version: 1.0.3

getCommand method

The getCommand(…) method encodes (prepares data) control command for range finder. The Ultisense range finder does not send data on its own, but only responds to commands. The method does not change the object and can be called from any thread. Method declaration:

bool getCommand(uint8_t* data, int& size, LrfUltisenseCommand id, int arg = 0);
Parameter Value
data Pointer to buffer for encoded data (command). Must not be nullptr. Buffer must have size >= 64 (the library can not check the size of the buffer, the encoded command takes not more than 16 bytes). The buffer is not changed if the command is not encoded.
size Size of encoded command, bytes. Set to 0 if the command is not encoded.
id Command ID according to LrfUltisenseCommand enum.
arg Command argument. The value depends on command ID (see LrfUltisenseCommand enum). Some commands don’t have arguments: the value is ignored for them.

Returns: TRUE if commands encoded or FALSE if not (data is nullptr, not valid ID or argument is out of the range given for the command).

The checksum of the command is written as an uppercase hexadecimal number without leading zeros (one digit if the checksum is less than 0x10).

Example of encoding command:

// Encode command.
rapidpixel::LrfUltisenseParser parser;
uint8_t command[64];
int size = 0;
if (parser.getCommand(command, size, rapidpixel::LrfUltisenseCommand::MEASURE_DISTANCE))
{
    // The command is ready: send "size" bytes of "command" via serial port.
    std::cout << "Command size: " << size << std::endl;
}
else
{
    std::cout << "ERROR: Can't encode command" << std::endl;
}

Console output:

Command size: 12

decodeResponse method

The decodeResponse(…) method decodes response from range finder. Once the data from the serial port has been read the user must pass it to the method byte-by-byte (each byte separately in sequence). The library keeps the last 256 received bytes. When the user passes another byte, the library shifts the stored bytes by one byte and copies the passed byte to the end. A response ends with the byte 0x0D (carriage return) and starts with the last byte 0x3E (‘>’) before it. If the library detects a valid response, it returns the response ID (see LrfUltisenseResponse enum description). The distance of a distance response is available by the getDistance method. In all other cases, the library returns a NONE identifier. The checksum of a response is not verified. The library detects the following responses:

  • The acknowledge frame “>AC*84” (ACKNOWLEDGE).
  • The distance frames “>LM,Md,…” and “>LM,Mt,…” (DISTANCE) and the fake distance frame “>LM,Mf,…” (FAKE_DISTANCE). The first range of the frame must be a field of 7 characters (distance in units of 0.01 meter) that follows the characters “,v” (the character “v” in the fake distance frame) and is followed by ‘,’ or ‘*’. The field is converted to an integer (leading white spaces and a sign are accepted, the characters after the digits are ignored) and divided by 100. A frame with a field of another size or with a field which is not a number is not a response.
  • Other frames, for example the responses of the SET_BLOCK_RANGE_REQUEST command (“>LM,Mb,…”), are not decoded.

A recognized frame (also a frame which is not a valid response) is removed from the stored bytes. The method must not be called from several threads at the same time. Method declaration:

LrfUltisenseResponse decodeResponse(uint8_t nextByte);
Parameter Value
nextByte Next read byte from serial port.

Returns: LrfUltisenseResponse (response ID) if response detected or NONE ID if not.

Example of decoding response:

// Data read from serial port (the acknowledge frame).
const std::string data = ">AC*84\r";

// Decode response.
rapidpixel::LrfUltisenseParser parser;
for (size_t i = 0; i < data.size(); ++i)
{
    rapidpixel::LrfUltisenseResponse response = parser.decodeResponse(static_cast<uint8_t>(data[i]));
    if (response != rapidpixel::LrfUltisenseResponse::NONE)
    {
        std::cout << "Response: " << static_cast<int>(response) << std::endl;
    }
}

Console output:

Response: 2

getDistance method

The getDistance() method returns the last decoded distance. The range finder can return multiple measurements but the library returns the first valid measurement. The method can be called from any thread, also while another thread calls decodeResponse(…). Method declaration:

float getDistance();

Returns: Value of last measured distance, meters. The value is -1 if no distance has been decoded yet.

Data structures

LrfUltisenseCommand enum

LrfUltisenseCommand enum declared in the LrfUltisenseParser.h file and includes commands IDs. Enum declaration:

namespace rapidpixel
{
enum class LrfUltisenseCommand
{
    /// Measure distance command. No argument.
    MEASURE_DISTANCE = 1,
    /// Fake measure distance command. No argument.
    FAKE_MEASURE_DISTANCE,
    /// Set a continuous low power laser emission at a wavelength of
    /// 1550 nm (typical). Argument:
    /// arg:  0 - OFF.
    ///       1 - Lowest power.
    ///       2 - ...
    ///       3 - ...
    ///       4 - ...
    ///       5 - ...
    ///       6 - Highest power.
    SET_CONTINUOUS_LOW_POWER_MODE,
    /// Set a continuous laser emission at a wavelength of 830 nm (typical).
    /// Argument:
    /// arg:  0 - OFF.
    ///       1 - ON.
    SET_LASER_POINTER_MODE,
    /// Arm. Enable laser emission. No argument.
    ARM,
    /// Disarm. Disable laser emission. No argument.
    DISARM,
    /// Set operating mode. Argument:
    /// arg:  0 - OFF.
    ///       1 - Stand-by-mode.
    ///       2 - Normal mode.
    SET_OPERATING_MODE,
    /// Initiate a set of x range measurements with a repetition
    /// frequency of 1 Hz. Argument:
    /// arg - number of requests (2,3,4 ...), from 0 to 65535.
    SET_BLOCK_RANGE_REQUEST,
    /// Set continuous range request. Argument:
    /// arg:  0 - STOP.
    ///       1 - Repetition rate of 0.5 Hz.
    ///       2 - Repetition rate of 1 Hz.
    ///       3 - Repetition rate of 3 Hz.
    ///       4 - Repetition rate of 5 Hz.
    ///       5 - Repetition rate of 10 Hz.
    SET_CONTINUOUS_RANGE_REQUEST,
    /// Set min gate range. Argument:
    /// arg - distance in meters, from 0 to 65535 (the documentation of the
    /// range finder gives the range from 0 to 30000, the library does not
    /// check it).
    SET_MIN_GATE_RANGE,
    /// Set max gate range. Argument:
    /// arg - distance in meters, from 0 to 65535 (the documentation of the
    /// range finder gives the range from 0 to 30000, the library does not
    /// check it).
    SET_MAX_GATE_RANGE
};
}

Table 2 - Range finder commands IDs.

Command Description
MEASURE_DISTANCE Command to measure distance once.
FAKE_MEASURE_DISTANCE Command to provide fake measurements. Used to test control algorithms.
SET_CONTINUOUS_LOW_POWER_MODE Set a continuous low power laser emission at a wavelength of 1550 nm (typical). Argument: 0 - Off. 1 - Lowest power, 2 - …, 3 - …, 4 - …, 5 - …, 6 - Highest power.
SET_LASER_POINTER_MODE Set a continuous laser emission at a wavelength of 830 nm (typical). Argument: 0 - Off, 1 - On.
ARM Arm laser range finder.
DISARM Disarm laser range finder.
SET_OPERATING_MODE Set operating mode. Argument: 0 - Off (Note: after this command the range finder will be powered off), 1 - Stand-by-mode, 2 - Normal mode.
SET_BLOCK_RANGE_REQUEST Initiate a set of x range measurements with a repetition frequency of 1 Hz. Argument: number of requests (2, 3, 4, …), from 0 to 65535.
SET_CONTINUOUS_RANGE_REQUEST Set continuous range request. Argument: 0 - STOP, 1 - Repetition rate of 0.5 Hz, 2 - Repetition rate of 1 Hz, 3 - Repetition rate of 3 Hz, 4 - Repetition rate of 5 Hz, 5 - Repetition rate of 10 Hz.
SET_MIN_GATE_RANGE Set min gate range. Argument: minimum gate distance, meters, from 0 to 65535 (the documentation of the range finder gives the range from 0 to 30000, the library does not check it).
SET_MAX_GATE_RANGE Set max gate range. Argument: maximum gate distance, meters, from 0 to 65535 (the documentation of the range finder gives the range from 0 to 30000, the library does not check it).

The getCommand(…) method rejects a command ID which is not in the enum and an argument which is out of the given range.

LrfUltisenseResponse enum

LrfUltisenseResponse enum declared in LrfUltisenseParser.h file and describes responses from range finder. Enum declaration:

namespace rapidpixel
{
enum class LrfUltisenseResponse
{
    /// No response.
    NONE = 1,
    /// Acknowledge frame. No arguments.
    ACKNOWLEDGE,
    /// Measured distance. Arguments: arg - distance.
    DISTANCE,
    /// Fake measured distance. Arguments: arg - distance.
    FAKE_DISTANCE
};
}

Table 3 - LRF responses IDs description.

Command Description
NONE Response not detected.
ACKNOWLEDGE Command acknowledge detected. Range finder sends acknowledge message for each input command.
DISTANCE Distance data detected.
FAKE_DISTANCE Fake distance data detected (after command LrfUltisenseCommand::FAKE_MEASURE_DISTANCE).

Test application

Folder LrfUltisenseParser/test contains the test application files. The test application allows you to generate any range finder command, send it to the range over the serial port, receive and decode the response. Once started, the user must enter the serial port name (full name for Linux or just the port number for Windows) and baud rate (57600 for the range finder):

===========================================
Lrf Ultisense Parser Test v1.0.3
===========================================

Set serial port name: /dev/ttyUSB0
Set baudrate (57600): 57600

The serial port is opened with the mode 8E1. After that, the user can choose command (enter command ID). The test application supports the commands of the list below and the mode which only reads data from the serial port (ID 10, no command is sent):

Serial port open!
===========================================
===========================================
Commands:
1 - MEASURE_DISTANCE
2 - FAKE_MEASURE_DISTANCE
3 - SET_CONTINUOUS_LOW_POWER_MODE
4 - SET_LASER_POINTER_MODE
5 - ARM
6 - DISARM
7 - SET_OPERATING_MODE
8 - SET_BLOCK_RANGE_REQUEST
9 - SET_CONTINUOUS_RANGE_REQUEST
10 - Just read data from serial port
==========================================

Chose command:

Then, corresponding command will be generated and sent to range finder. The test application waits 3 seconds, reads the data from the serial port, prints the received data and the decoded responses and shows the list of commands again. An invalid number or an invalid command ID is reported and the test application asks for the command again. If the standard input ends, the test application exits.

If the test application is started with the parameter –self-test it does not use a serial port: it runs the automatic tests of the library (one line per test) and returns 0 if all tests passed:

./LrfUltisenseParserTest --self-test

Build and connect to your project

Build stand alone

The repository does not contain sub-repositories. The library does not depend on other libraries. The test application depends on the SerialPort library: the SerialPort library must be cloned into the same folder as LrfUltisenseParser. The folder structure must be:

<work folder>
    LrfUltisenseParser
    SerialPort

Typical commands to build the library and the test application (use the URLs of the repositories provided to you):

git clone <LrfUltisenseParser repository URL>
git clone <SerialPort repository URL>
cd LrfUltisenseParser
mkdir build
cd build
cmake ..
make

On Windows use cmake --build . --config Release instead of make. If LrfUltisenseParser is the top-level CMake project and the test application is enabled (by default), the main CMakeLists.txt file adds the folder ../SerialPort before the test application. If the folder does not exist CMake stops with an error. If the test application is switched off the folder ../SerialPort is not used (see CMake options).

CMake options

Table 4 - CMake options of the repository. In the options below ${PARENT} is the prefix defined by the top-level project (empty if LrfUltisenseParser is the top-level project).

Option Default Description
${PARENT}_ULTISENSE_PARSER ON Build the library.
${PARENT}_ULTISENSE_PARSER_TEST ON (stand alone), OFF (included in other project) Build the test application.
${PARENT}_SUBMODULE_CACHE_OVERWRITE ON Forced rewriting of the options above in the CMake cache. The top-level project sets it to OFF when it includes LrfUltisenseParser as a sub-repository. To change the options above from the command line set it to OFF, for example: cmake .. -D_SUBMODULE_CACHE_OVERWRITE=OFF -D_ULTISENSE_PARSER_TEST=OFF.
CMAKE_BUILD_TYPE Release (if not specified) Build type.

Connect to your project

If you want to connect LrfUltisenseParser library to your CMake project as source code you can do the following. For example, if your repository has structure:

CMakeLists.txt
src
    CMakeList.txt
    yourLib.h
    yourLib.cpp

Create folder 3rdparty and copy folder of LrfUltisenseParser repository there. Structure of your repository:

CMakeLists.txt
src
    CMakeList.txt
    yourLib.h
    yourLib.cpp
3rdparty
    LrfUltisenseParser

Create CMakeLists.txt file in 3rdparty folder. CMakeLists.txt should contain:

cmake_minimum_required(VERSION 3.13)

################################################################################
## 3RD-PARTY
## dependencies for the project
################################################################################
project(3rdparty LANGUAGES CXX)

################################################################################
## SETTINGS
## basic 3rd-party settings before use
################################################################################
# To inherit the top-level architecture when the project is used as a submodule.
SET(PARENT ${PARENT}_YOUR_PROJECT_3RDPARTY)
# Disable self-overwriting of parameters inside included subdirectories.
SET(${PARENT}_SUBMODULE_CACHE_OVERWRITE OFF CACHE BOOL "" FORCE)

################################################################################
## CONFIGURATION
## 3rd-party submodules configuration
################################################################################
SET(${PARENT}_SUBMODULE_ULTISENSE_PARSER                ON  CACHE BOOL "" FORCE)
if (${PARENT}_SUBMODULE_ULTISENSE_PARSER)
    SET(${PARENT}_ULTISENSE_PARSER                      ON  CACHE BOOL "" FORCE)
    SET(${PARENT}_ULTISENSE_PARSER_TEST                 OFF CACHE BOOL "" FORCE)
endif()

################################################################################
## INCLUDING SUBDIRECTORIES
## Adding subdirectories according to the 3rd-party configuration
################################################################################
if (${PARENT}_SUBMODULE_ULTISENSE_PARSER)
    add_subdirectory(LrfUltisenseParser)
endif()

File 3rdparty/CMakeLists.txt adds folder LrfUltisenseParser to your project and excludes test application from compiling. Structure of your repository:

CMakeLists.txt
src
    CMakeList.txt
    yourLib.h
    yourLib.cpp
3rdparty
    CMakeLists.txt
    LrfUltisenseParser

The library does not depend on other libraries, so nothing else is added to your project for the library. The library does not add its dependencies when it is included in another project: if you switch the test application on (the option ${PARENT}_ULTISENSE_PARSER_TEST) your project must add the SerialPort library by itself, otherwise the compilation of the test application stops with the error “SerialPort.h: No such file or directory”.

Next you need to include folder 3rdparty in main CMakeLists.txt file of your repository. Add string at the end of your main CMakeLists.txt:

add_subdirectory(3rdparty)

Next you have to include LrfUltisenseParser library in your src/CMakeLists.txt file:

target_link_libraries(${PROJECT_NAME} LrfUltisenseParser)

Done!

Portability

The library processes the bytes of a text protocol: it does not depend on the byte order of the CPU. It does not contain SIMD code or code specific to an operating system. The library requires a C++17 compiler and an int type with at least 32 bits (checked at compile time). The library and the test application were built with GCC 11 and GCC 12 for x86-64 and with GCC 11 for AArch64, and the automatic tests of the test application were run for these targets (AArch64 under QEMU user mode emulation).


Table of contents