Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions c/example/cpp_example/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
cmake_minimum_required(VERSION 2.8.9)
project(libsbp_cpp_example)

set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")

set(CMAKE_C_FLAGS "-Wall -Wextra -Wno-strict-prototypes -Wno-unknown-warning-option -Werror -std=gnu99 ${CMAKE_C_FLAGS}")
set(CMAKE_CXX_FLAGS "-Wall -Wextra -Wno-strict-prototypes -Wno-unknown-warning-option -Werror -std=c++11 ${CMAKE_CXX_FLAGS}")

add_executable(libsbp_cpp_example cpp_example.cc)

find_package(PkgConfig)

link_directories("/usr/local/lib/")
include_directories("/usr/local/include/")

target_link_libraries(libsbp_cpp_example sbp)
49 changes: 49 additions & 0 deletions c/example/cpp_example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
## Example of using the C++ classes for libsbp

This example demonstrates parsing SBP from a file using the C++ classes

## Requirements
On Debian-based systems (including Ubuntu 12.10 or later) you can get all
the requirements with:

```shell
sudo apt-get install build-essential pkg-config cmake
```

On mac:

```shell
brew install cmake
```

On other systems, you can obtain CMake from your operating system
package manager or from http://www.cmake.org/.

## Installation

Once you have the dependencies installed, create a build directory where the example will be built:

```shell
mkdir build
cd build
```

Then invoke CMake to configure the build, and then build,

```shell
cmake ../
make
```

## Usage

```shell
./libsbp_cpp_example
usage: ./libsbp_cpp_example [filename.sbp]
```

## LICENSE

Copyright © 2019 Swift Navigation

Distributed under LGPLv3.0.
80 changes: 80 additions & 0 deletions c/example/cpp_example/cpp_example.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#include <iostream>
#include <fstream>

#include <libsbp/cpp/state.h>
#include <libsbp/cpp/message_handler.h>

void usage(char *prog_name) {
fprintf(stderr, "usage: %s [filename]\n", prog_name);
}

class SbpFileReader : public sbp::IReader {
public:
SbpFileReader(const char *file_path) : file_stream_(file_path, std::ios::binary | std::ios_base::in) { }

bool is_open() const { return file_stream_.is_open(); }
bool eof() const { return file_stream_.eof(); }

s32 read(u8 *buffer, u32 buffer_length) override {
auto start_index = file_stream_.tellg();
if (start_index == -1) {
return -1;
}
file_stream_.read(reinterpret_cast<char *>(buffer), buffer_length);
auto end_index = file_stream_.tellg();
if (end_index == -1 || file_stream_.fail()) {
return -1;
}

return static_cast<s32>(end_index - start_index);
}

private:
std::ifstream file_stream_;
};

class ECEFHandler : private sbp::MessageHandler<msg_gps_time_t, msg_pos_ecef_t> {
public:
ECEFHandler(sbp::State *state) : sbp::MessageHandler<msg_gps_time_t, msg_pos_ecef_t>(state) {
}

void handle_sbp_msg(uint16_t sender_id, uint8_t message_length, const msg_gps_time_t& msg) {
(void)sender_id;
(void)message_length;
std::cout << "Received new GPS_TME message with WN = " << msg.wn << ", TOW = " << msg.tow << "\n";
}

void handle_sbp_msg(uint16_t sender_id, uint8_t message_length, const msg_pos_ecef_t& msg) {
(void)sender_id;
(void)message_length;
std::cout << "Received new POS_ECEF message with TOW = " << msg.tow;
std::cout << ", (X,Y,Z) = (" << msg.x << "," << msg.y << "," << msg.z << ")\n";
}
};

int main(int argc, char **argv)
{
if (argc <= 1) {
usage(argv[0]);
exit(EXIT_FAILURE);
}

const char *file_path = argv[1];

SbpFileReader reader(file_path);
if (!reader.is_open()) {
usage(argv[0]);
exit(EXIT_FAILURE);
}

sbp::State s;
ECEFHandler handler(&s);

s.set_reader(&reader);

while(!reader.eof()) {
s.process();
}

return 0;
}
174 changes: 174 additions & 0 deletions c/include/libsbp/cpp/message_handler.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/**
* Copyright (C) 2019 Swift Navigation Inc.
* Contact: Swift Navigation <dev@swiftnav.com>
*
* This source is subject to the license found in the file 'LICENSE' which must
* be be distributed together with this source. All other rights reserved.
*
* THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
* EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
*/

#ifndef SBP_CPP_MESSAGE_HANDLER_H_
#define SBP_CPP_MESSAGE_HANDLER_H_

#include <cassert>
#include <array>

#include <libsbp/cpp/state.h>
#include <libsbp/cpp/message_traits.h>

namespace sbp {

/**
* A convenience type alias for class member functions that accept SBP message callbacks
*/
template<typename ClassT, typename ArgT>
using CallbackMemFn = void (ClassT::*)(uint16_t, uint8_t, const ArgT &);

/**
* A helper function for calling a C++ object member function from a libsbp callback.
*
* This function is registered with libsbp as a callback and calls a specific member function
* of an object from that callback with the SBP message decoded. The member function pointer
* is encoded into the template parameter, and the instance of the object is passed in via
* the `context` parameter.
*
* @tparam MsgT The message type to decode the payload as
* @tparam ClassT The class type to call the function on
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to call the same function on multiple class types? I find this is a common pattern for me (i.e do the same thing for any ephemeris message received)

Copy link
Contributor Author

@jbangelo jbangelo Oct 22, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not with how this PR currently stands. The problem with that is that all of the message types are distinct and there's little to no information relating the content of similar messages. The closest thing we could get is defining a base class that handles each of the distinct message types and converts them into a common type and then calls another member function that works on the common type.

Something like this:

class CommonEphemerisHandler : public sbp::MessageHandler<EphemerisMessage1, EphemerisMessage2> {
  public:
    struct CommonEphemerisType { ... };

    virtual void handle_common_ephemeris(const CommonEphemerisType& eph) = 0;

    void handle_sbp_msg(uint16_t sender_id, uint8_t message_length, const EphemerisMessage1& msg) {
      CommonEphemerisType eph = convertEph1(msg);
      handle_common_ephemeris(eph);
    }

    void handle_sbp_msg(uint16_t sender_id, uint8_t message_length, const EphemerisMessage2& msg) {
      CommonEphemerisType eph = convertEph2(msg);
      handle_common_ephemeris(eph);
    }
};

class CustomEphemerisHandler : public CommonEphemerisHandler {
  public:
    void handle_common_ephemeris(const CommonEphemerisType& eph) { ... }
};

class OtherEphemerisHandler : public CommonEphemerisHandler {
  public:
    void handle_common_ephemeris(const CommonEphemerisType& eph) { ... }
};

* @tparam func Pointer to the member function to call
*
* @param sender_id The decoded sender ID, is forwarded on to `func`
* @param len The length of the message, is forwarded on to `func`
* @param msg The raw message payload
* @param context Pointer to an instance of `ClassT` to call `func` on
*/
template<typename MsgT, typename ClassT, CallbackMemFn<ClassT, MsgT> func>
inline void sbp_cb_passthrough(uint16_t sender_id, uint8_t len, uint8_t msg[], void *context) {
assert(nullptr != context);

auto instance = static_cast<ClassT *>(context);
auto val = reinterpret_cast<MsgT *>(msg);
((*instance).*(func))(sender_id, len, *val);
}

namespace details {

/**
* Recursive interface type for defining the interface functions for `MessageHandler`.
*
* These types define a virtual `operator()` for handling a specific SBP message type,
* as well as a function for registering it as a SBP callback.
*
* @note These types aren't meant to be used directly by application code.
*
* @tparam MsgType The type of SBP message to define an interface for
* @tparam OtherTypes Other types to recursively define interfaces for
*/
template<typename MsgType, typename... OtherTypes>
class CallbackInterface : CallbackInterface<OtherTypes...> {
public:
CallbackInterface() = default;
virtual ~CallbackInterface() = default;

using CallbackInterface<OtherTypes...>::handle_sbp_msg;
virtual void handle_sbp_msg(uint16_t sender_id, uint8_t message_length, const MsgType& msg) = 0;

protected:
void register_callback(sbp_state_t *state, sbp_msg_callbacks_node_t nodes[]) {
sbp_register_callback(state,
sbp::MessageTraits<MsgType>::id,
&sbp_cb_passthrough<MsgType, CallbackInterface, &CallbackInterface::handle_sbp_msg>,
this,
&nodes[0]);
CallbackInterface<OtherTypes...>::register_callback(state, &nodes[1]);
}
};

template<typename MsgType>
class CallbackInterface<MsgType> {
public:
CallbackInterface() = default;
virtual ~CallbackInterface() = default;

virtual void handle_sbp_msg(uint16_t sender_id, uint8_t message_length, const MsgType& msg) = 0;
protected:
void register_callback(sbp_state_t *state, sbp_msg_callbacks_node_t nodes[]) {
sbp_register_callback(state,
sbp::MessageTraits<MsgType>::id,
&sbp_cb_passthrough<MsgType, CallbackInterface, &CallbackInterface::handle_sbp_msg>,
this,
&nodes[0]);
}
};

} // namespace details

/**
* Base type for defining classes that handle SBP messages
*
* Application classes should derive from this class if they wish to handle
* SBP messages with a member function. `MessageHandler` instantiates all of
* the callback nodes, and registers the member functions with the given
* `sbp_state_t`.
*
* Classes that derive from `MessageHandler` need to implement
* void handle_sbp_msg(uint16_t sender_id, uint8_t message_length, const MsgType& msg) const;
* for each `MsgType` in the list of message types given as template parameters.
*
* Due to the nature of the callback registration in libsbp we dissallow copying or
* moving of `MessageHandler`.
*
* @note It should not matter if the class derives publicly or privately from `MessageHandler`
* or if the `handle_sbp_msg` functions are public or private.
*
* @example
* class ECEFHandler : private sbp::MessageHandler<msg_gps_time_t, msg_pos_ecef_t> {
* public:
* ECEFHandler(sbp::State *state) : sbp::MessageHandler<msg_gps_time_t, msg_pos_ecef_t>(state) {
* // The callbacks have already been registered
* // Perform other initialization tasks
* }
*
* void handle_sbp_msg(uint16_t sender_id, uint8_t message_length, const msg_gps_time_t& msg) {
* // handle GPS time message
* }
*
* void handle_sbp_msg(uint16_t sender_id, uint8_t message_length, const msg_pos_ecef_t& msg) {
* // handle pos ECEF message
* }
* };
*
* @tparam MsgTypes List of SBP message types to register callbacks for
*/
template<typename... MsgTypes>
class MessageHandler : public details::CallbackInterface<MsgTypes...> {
static constexpr size_t kMsgCount = sizeof...(MsgTypes);

State &state_;
std::array<sbp_msg_callbacks_node_t, kMsgCount> callback_nodes_;

public:

explicit MessageHandler(State *state) : details::CallbackInterface<MsgTypes...>(), state_(*state), callback_nodes_() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason the ctor argument isn't State&?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The State needs to be mutable, and our C++ guidelines say any non-const parameter should be passed as a pointer.

details::CallbackInterface<MsgTypes...>::register_callback(state_.get_state(), callback_nodes_.data());
}

virtual ~MessageHandler() {
for (auto& node : callback_nodes_) {
sbp_remove_callback(state_.get_state(), &node);
}
}

MessageHandler(const MessageHandler&) = delete;
MessageHandler(MessageHandler&& other) = delete;
MessageHandler& operator=(const MessageHandler&) = delete;
MessageHandler& operator=(MessageHandler&&) = delete;

using details::CallbackInterface<MsgTypes...>::handle_sbp_msg;
};

} /* namespace sbp */

#endif /* SBP_CPP_MESSAGE_HANDLER_H_ */
Loading