Git Product home page Git Product logo

easynmea's Introduction

EasyNMEA {#index}

automated_testing Publish Docker image Documentation Status Docker pulls codecov CodeQL

EasyNMEA is a C++ library to get NMEA information from NMEA modules which communicate with NMEA 0183 over UART. It can retrieve NMEA data from any NMEA device sending NMEA 0183 sentences over UART.

For more detailed information, please visit the EasyNMEA documentation.

Features

easynmea provides the EasyNmea class, which uses NMEA 0183 sentences to extract NMEA information from the modules, providing a simple and easy-to-use API.

Getting started

easynmea has been developed and tested in Ubuntu 20.04 Operating System, although it is expected to support Windows 10 and MacOS thanks to Asio, the cross-platform library used to interact with the serial ports.

Prerequisites

easynmea relies on Asio for establishing a serial connection with the NMEA module. On Ubuntu platforms, this can be installed with:

sudo apt install libasio-dev

Build and install

To build the library, run:

cd ~
git clone https://github.com/EduPonz/easynmea.git
mkdir build && cd build
cmake ..
cmake --build .

Once built, the library can be installed in a user specified directory with:

cd ~/easynmea/build
cmake .. -DCMAKE_INSTALL_PREFIX=<user-specified-dir>
cmake --build . --target install

To install the library system-wide, just omit the CMAKE_INSTALL_PREFIX:

cd ~/easynmea/build
cmake ..
cmake --build . --target install

Build documentation

It is possible to generate the library's documentation passing CMake option -DBUILD_DOCUMENTATION=0N (defaults to OFF) on the configuration step:

cd ~/easynmea/build
cmake .. -DCMAKE_INSTALL_PREFIX=<user-specified-dir> -DBUILD_DOCUMENTATION=ON
cmake --build . --target install

Run examples

It is also possible to build and install the library's examples passing CMake option -DBUILD_EXAMPLES=ON (defaults to OFF) on the configuration step:

cd ~/easynmea/build
cmake .. -DCMAKE_INSTALL_PREFIX=<user-specified-dir> -DBUILD_EXAMPLES=ON
cmake --build . --target install

Then, they can be run with:

cd <user-specified-dir>/examples/bin
./gpgga_example

An output example from gpgga_example would be:

Serial port '/dev/ttyACM0' opened. Baudrate: 9600
Please press CTRL-C to stop the example

************** NEW GPGGA SAMPLE **************
Elapsed time ---------> 3468
------------------------------------------
GPGGA Data - GNSS Position Fix
==============================
Message --------------> $GPGGA,072706.000,5703.1740,N,00954.9459,E,1,8,1.28,-21.2,M,42.5,M,,*4E
Timestamp ------------> 72706
Latitude -------------> 57.0529º N
Longitude ------------> 9.91576º E
Fix ------------------>
Number of satellites -> 8
Horizontal precision -> 1.28
Altitude -------------> -21.2

The some example's parameters can be configured using command line options. Run ./gpgga_example --help

------------------------
EasyNMEA - GPGGA Example
------------------------
Usage: ./gpgga_example [OPTIONS]
    -h/--help:                Print this help and exit
    -b/--baudrate [bauds]:    The connection baud rate in bauds [Defaults: 9600]
    -p/--serial_port [port]:  The serial port to use [Defaults: 'dev/ttyACM0']
Example: ./gpgga_example -p /dev/ttyUSB0 -b 9600

Usage

The NMEA information can be retrieved in the following manner (see gpgga_example.cpp):

using namespace eduponz::easynmea;
// Create an EasyNmea object
EasyNmea easynmea;
// Open the serial port
if (easynmea.open("/dev/ttyACM0", 9600) == ReturnCode::RETURN_CODE_OK)
{
    // Create a mask to only wait on data from specific NMEA 0183 sentences
    NMEA0183DataKindMask data_kind_mask = NMEA0183DataKind::GPGGA;
    // This call will block until some data of any of the kinds specified in the mask is available.
    while (easynmea.wait_for_data(data_kind_mask) == ReturnCode::RETURN_CODE_OK)
    {
        // Take all the available data samples of type GPGGA
        GPGGAData gpgga_data;
        while (easynmea.take_next(gpgga_data) == ReturnCode::RETURN_CODE_OK)
        {
            // Do something with the GNSS data
            std::cout << "GNSS position: (" << gpgga_data.latitude << "; " << gpgga_data.longitude << ")" << std::endl;
        }
    }
}
// Close the serial connection
easynmea.close();

Authors

easynmea has been developed by Eduardo Ponz.

License

This project is licensed under the MIT License - see the LICENSE.md file for details.

easynmea's People

Contributors

eduponz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

easynmea's Issues

Add Codecov coverage reports to workflow

This task entails:

  • Create a CMake option to enable coverage reporting and pass the appropriate flags to the compiler
  • Generating coverage reports
  • Register the project in codecov
  • Upload coverage report to codecov
  • Add checks to patch and project coverage
  • Add coverage badge to README.md

Closing down deadlock when the serial port is not receiving any characters

Operating System
Ubuntu 20.04, although it will happen everywhere.

GNSS Interface version
https://github.com/EduPonz/gnss_interface/tree/7508819a324624fd045b725b7bf7f4dc13506f60

CMake options
Default

Describe the bug

Currently, there is a bug in which closing down an serial connection which is not receiving data by calling GnssInterface::close() results in a deadlock. This is because:

  1. On the one hand, GnssInterfaceImpl::close() only calls to SerialInterface::close() once the reading thread has joined.
  2. On the other hand, the reading thread is blocked in asio::serial_port::read_some() (waiting for one character to arrive).
    Since the sender is not sending anything over the serial line, asio::serial_port::read_some() will block indefinitely, thus causing the reading thread in GnssInterfaceImpl to never join, and therefore the call to GnssInterface::close() will never return.

The call to asio::serial_port::read_some() can be unlocked by calling asio::serial_port::close(), which GnssInterfaceImpl::close() already does here, right after the thread joins 😖. Calling asio::serial_port::close() while blocked on asio::serial_port::read_some() returns in the latter returning with and error code, so something must be done to differentiate the case in which the call returns with an error because asio::serial_port::close() was called from the one in which an actual error ocurred.

  1. This could be done by simply calling to asio::serial_port::is_open() whenever asio::serial_port::read_some() returns an error.
  2. Alternatively, it could be done by setting a SerialInterface::open_ flag to true when SerialInterface::open() is called, and to false whenever SerialInterface::close() is called. This way, SerialInterface::read_line() could differentiate between unlocked due to closing or error, maybe even better so, since the error could have been an untriggered close of the port.

However, this will not solve the deadlock completely, as the reading thread calls to GnssInterfaceImpl::close() whenever SerialInterface::read_line() returns false. This call will try to lock on GnssInterfaceImpl::mutex_ which is already taken by the application call to GnssInterfaceImpl::close(), which is waiting on the reading thread to join.
This can be solved by either one of:

  1. Creating a GnssInterfaceImpl::closing_ flag which is set to true only while GnssInterfaceImpl::close() is executing. Then, if another thread calls to GnssInterfaceImpl::close(), the function can simply return ReturnCode::RETURN_CODE_ILLEGAL_OPERATION.
  2. There could a std::mutex GnssInterfaceImpl::mutex_closing_ that GnssInterfaceImpl::close() tries to lock with std::mutex::try_lock(). If the mutex can be locked, then no one else is closing so the thread can proceed; else, GnssInterfaceImpl::close() was previously called from somewhere else, so the call should return ReturnCode::RETURN_CODE_ILLEGAL_OPERATION.

According to std::mutex::try_lock documentation, This function is allowed to fail spuriously and return false even if the mutex is not currently locked by any other thread. so this second approach should be discarded in favor of the first one.

To Reproduce
On one terminal, run:

socat pty,rawer,link=$HOME/dev/testrecv pty,rawer,link=$HOME/dev/testsend

On a second terminal, run:

<path_to_example>/gpgga_example -p /home/eduponz/dev/testrecv

Then, press CTRL-C in the second terminal.

Expected behavior
The example should exit cleanly

Externalize NMEA 0183 sentence parsing

  • Create a dedicated class NMEA0183Parser as a header only for handling the parsing of the sentences. This is useful cause in the future I'll most likely create a NMEA 0183 Parser library from which EasyNMEA will depend. Having it as a header only would ease things. As the parser will most likely be full static, this should not be a problem.
  • NMEA0183Parser design documentation
  • NMEA0183Parser tests
  • NMEA0183Parser integration with GnssInterfaceImpl

UpdateREADME.md and docs to link new documents

  • Add link to CODE_OF_CONDUCT.md to README.md
  • Add link to CODE_OF_CONDUCT.md to documentation
  • Add link to CONTRIBUTING.md to README.md
  • Add link to CONTRIBUTING.md to documentation
  • Cleanup README.md, since most of its content is already covered in the RTD documentation

Make FixedSizeQueue thread safe

It'd be nice if FixedSizeQueue was thread safe, meaning it should have an internal mutex used to protect all API calls. This entails overriding all the base class API to that the mutex is always lock.

Add Quality of Service settings

This ticket tracks the quality of service settings that it'd be cool to have for v0.2.0. The list might get longer before v0.2.0

  • NMEA0183 data queue sizes should be configurable
  • Default timeout for API calls. Zero would mean no timeout (unless specified in the call itself)

Demo and testing Docker image

Is your feature request related to a problem? Please describe.

I'd like to have some Docker image that it is easy to build/deploy/download and can be used to:

  1. Quickly demonstrate the capabilities of the library
  2. Get more visibility for the project
  3. Verify that everything works on a clean environment

Describe the solution you'd like

I'd like to include a Dockerfile that derives directly from ubuntu:20.04 or ubuntu:latest which installs all the dependencies and building tools, and which builds the library with examples, tests, and documentation.
This can be then used to just plug the GNSS module into a computer and directly read data from it using the examples.
Furthermore, it can be used for development.

I'd like to have an automated job which uploads this image to DockerHub with:

  1. Every push to main
  2. Every new tag
    The images should be tagged accordingly, so that users can do something like:
docker run -it gnss-interface:latest bash
docker run -it gnss-interface:v0.1.0 bash

Describe alternatives you've considered

It'd be cool to also upload the image to GitHub packages, but DockerHub is far more spread, so I think GitHub packages can wait for now.

Run CodeQL analysis

Is your feature request related to a problem? Please describe.
GitHub offers a code quality static analysis (CodeQL) that we should run with every contribution (see this)

Describe the solution you'd like
Create a codeql-analysis.yaml

Add optional timeout to API calls

Having a timeout for API blocking calls (mainly wait_for_data and take_next, but also open and close, and to a lesser degree is_open) would increase the real time capabilities of GNSS Interface. This would entail:

  • Having an intermediate class for handling the timers
  • Using Asio asynchronous timers functionality
  • Tests
  • Documentation

Read The Docs documentation does not show PlantUML diagrams

Operating System
Read The Docks build

GNSS Interface version
main branch

Describe the bug
Read The Docs documentation does not show PlantUML diagrams claiming

b'ERROR\n6\nSyntax Error?\nSome diagram description contains errors\n'

Locally I can see the diagrams.

To Reproduce
In this build, look for plantuml

Expected behavior
Read The Docs should show the UML diagrams

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.