Skip to content

Commit

Permalink
Merge pull request #65 from Caltech-AMBER/std-obk-msgs
Browse files Browse the repository at this point in the history
New Obelisk Message Types
  • Loading branch information
alberthli authored Jul 20, 2024
2 parents ebc2951 + 9ae043d commit 6630b5f
Show file tree
Hide file tree
Showing 22 changed files with 2,438 additions and 1,291 deletions.
2 changes: 1 addition & 1 deletion .clang-format
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ PointerAlignment: Left
# AlignAfterOpenBracket: DontAlign
AlignConsecutiveAssignments:
Enabled: true
AcrossEmptyLines: true
AcrossEmptyLines: false
AcrossComments: false
AlignConsecutiveMacros:
Enabled: true
Expand Down
6 changes: 0 additions & 6 deletions dev_setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,21 +33,15 @@ if [ "$dev_sys_deps" = true ]; then
echo -e "\033[1;32mInstalling development system dependencies...\033[0m"
sudo apt-get install -y \
curl \
build-essential \
cmake \
clang-tools-12 \
nano \
vim \
git \
python3-dev \
python-is-python3 \
python3-pip \
python3-argcomplete \
mesa-utils \
x11-apps \
libyaml-dev \
mesa-common-dev \
libglfw3-dev \
locales
else
echo -e "\033[1;32mNot installing development system dependencies. To do so, pass the --dev-sys-deps flag.\033[0m"
Expand Down
8 changes: 1 addition & 7 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,15 @@ RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# basic dependencies from docker_setup.sh (up until sudo and below)
RUN apt-get update && apt-get install -y \
curl \
build-essential \
cmake \
clang-tools-12 \
nano \
vim \
git \
python3-dev \
python-is-python3 \
python3-pip \
python3-argcomplete \
mesa-utils \
x11-apps \
libyaml-dev \
mesa-common-dev \
libglfw3-dev \
locales \
sudo && \
rm -rf /var/lib/apt/lists/* && \
Expand All @@ -49,7 +43,7 @@ RUN groupadd --gid $GID $USER && \
echo "%${USER} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers

# add user to dialout group to enable serial port access from the container
RUN sudo usermod -a -G dialout ${USER}
RUN usermod -a -G dialout ${USER}

# switch to new user and workdir
USER ${UID}
Expand Down
1 change: 1 addition & 0 deletions obelisk/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ set(CMAKE_CXX_STANDARD 17 CACHE STRING "The C++ standard to use")
add_subdirectory(obelisk_cpp)
add_subdirectory(viz)
add_subdirectory(zoo)
add_subdirectory(utils)

if (CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
include(CTest)
Expand Down
1 change: 1 addition & 0 deletions obelisk/cpp/obelisk_cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ find_package(rcl REQUIRED)
find_package(obelisk_control_msgs REQUIRED)
find_package(obelisk_sensor_msgs REQUIRED)
find_package(obelisk_estimator_msgs REQUIRED)
find_package(obelisk_std_msgs REQUIRED)

# ------- Mujoco ------- #
include(FetchContent)
Expand Down
41 changes: 25 additions & 16 deletions obelisk/cpp/obelisk_cpp/include/obelisk_node.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,20 @@
#include <chrono>
#include <tuple>

#include "rcl_interfaces/msg/parameter_event.hpp"
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_lifecycle/lifecycle_node.hpp"

#include "obelisk_control_msgs/msg/position_setpoint.hpp"

#include "obelisk_estimator_msgs/msg/estimated_state.hpp"

#include "obelisk_sensor_msgs/msg/joint_encoders.hpp"
#include "obelisk_sensor_msgs/msg/obk_image.hpp"
#include "obelisk_sensor_msgs/msg/true_sim_state.hpp"
#include "rcl_interfaces/msg/parameter_event.hpp"
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_lifecycle/lifecycle_node.hpp"

#include "obelisk_std_msgs/msg/float_multi_array.hpp"
#include "obelisk_std_msgs/msg/u_int8_multi_array.hpp"

namespace obelisk {
namespace internal {
Expand Down Expand Up @@ -120,13 +127,15 @@ namespace obelisk {
// Allowed Obelisk message types
using ObeliskMsgs =
std::tuple<obelisk_control_msgs::msg::PositionSetpoint, obelisk_estimator_msgs::msg::EstimatedState,
obelisk_sensor_msgs::msg::JointEncoders, obelisk_sensor_msgs::msg::TrueSimState>;
obelisk_sensor_msgs::msg::JointEncoders, obelisk_sensor_msgs::msg::TrueSimState,
obelisk_sensor_msgs::msg::ObkImage, obelisk_std_msgs::msg::FloatMultiArray,
obelisk_std_msgs::msg::UInt8MultiArray>;
// Allowed non-obelisk message types
using ROSAllowedMsgs = std::tuple<rcl_interfaces::msg::ParameterEvent>;
using ROSAllowedMsgs = std::tuple<rcl_interfaces::msg::ParameterEvent>;

inline const std::array<std::string, 2> sensor_message_names = {
obelisk_sensor_msgs::msg::JointEncoders::MESSAGE_NAME,
obelisk_sensor_msgs::msg::TrueSimState::MESSAGE_NAME};
inline const std::array<std::string, 3> sensor_message_names = {
obelisk_sensor_msgs::msg::JointEncoders::MESSAGE_NAME, obelisk_sensor_msgs::msg::TrueSimState::MESSAGE_NAME,
obelisk_sensor_msgs::msg::ObkImage::MESSAGE_NAME};

inline const std::array<std::string, 1> estimator_message_names = {
obelisk_estimator_msgs::msg::EstimatedState::MESSAGE_NAME};
Expand Down Expand Up @@ -618,10 +627,10 @@ namespace obelisk {
std::shared_ptr<SubscriptionT> CreateSubscriptionFromConfigStr(const std::string& config,
CallbackT&& callback) {
// Parse the configuration string
const auto config_map = ParseConfigStr(config);
const std::string topic = GetTopic(config_map);
const int depth = GetHistoryDepth(config_map);
const bool non_obelisk = !GetIsObeliskMsg(config_map);
const auto config_map = ParseConfigStr(config);
const std::string topic = GetTopic(config_map);
const int depth = GetHistoryDepth(config_map);
const bool non_obelisk = !GetIsObeliskMsg(config_map);

rclcpp::CallbackGroup::SharedPtr cbg = nullptr; // default group
try {
Expand Down Expand Up @@ -669,10 +678,10 @@ namespace obelisk {
// Finest frequency is determined by DurationT
using namespace std::chrono_literals;

const auto config_map = ParseConfigStr(config);
const double period_sec = GetPeriod(config_map); // Period in seconds
const auto config_map = ParseConfigStr(config);
const double period_sec = GetPeriod(config_map); // Period in seconds

rclcpp::CallbackGroup::SharedPtr cbg = nullptr; // default group
rclcpp::CallbackGroup::SharedPtr cbg = nullptr; // default group
try {
// Get the callback group based on the string name
cbg = callback_groups_.at(GetCallbackGroupName(config_map));
Expand Down Expand Up @@ -712,7 +721,7 @@ namespace obelisk {

std::string config_id = config.substr(0, val_idx);

std::string val = config.substr(val_idx + 1, type_idx - (val_idx + 1));
std::string val = config.substr(val_idx + 1, type_idx - (val_idx + 1));
config_map.emplace(config_id, val);
config.erase(0, type_idx + type_delim.length());
}
Expand Down
34 changes: 34 additions & 0 deletions obelisk/cpp/utils/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
message(STATUS "Configuring Obelisk Utils")


if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()

# ------- ROS 2 Packages ------- #
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(rclcpp_lifecycle REQUIRED)
find_package(rcl REQUIRED)

# ------- Obelisk Messages ------- #
find_package(obelisk_std_msgs REQUIRED)

# ------- Eigen ------- #
find_package(Eigen3 REQUIRED)

# ------- Source files ------- #
set(UTILS_INC "${CMAKE_CURRENT_SOURCE_DIR}/include")

# ------- Making the library ------- #
add_library(ObkUtils INTERFACE)
add_library(Obelisk::Utils ALIAS ObkUtils) # Namespaced alias
target_include_directories(ObkUtils INTERFACE ${UTILS_INC} ${mujoco_SOURCE_DIR}/include)

target_link_libraries(ObkUtils INTERFACE Eigen3::Eigen)

ament_target_dependencies(ObkUtils INTERFACE
rclcpp
rclcpp_lifecycle
obelisk_std_msgs
std_msgs)
148 changes: 148 additions & 0 deletions obelisk/cpp/utils/include/msg_conversions.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
#include <unsupported/Eigen/CXX11/Tensor>

#include "rclcpp/rclcpp.hpp"

#include "obelisk_std_msgs/msg/float_multi_array.hpp"
#include "obelisk_std_msgs/msg/u_int8_multi_array.hpp"

namespace obelisk::utils::msgs {
namespace internal {
/**
* @brief Internal helper function to create tensors of arbitrary dimension.
*
* @param data the data to assign to the tensor
* @param dims the dimensions of the tensor
* @param Indices the index sequence to use for the parameter pack
*
* @return the Eigen Tensor
*/
template <typename ScalarT, std::size_t N, std::size_t... Indices>
Eigen::Tensor<ScalarT, N> CreateTensor(std::vector<ScalarT>& data, const std::array<int, N>& dims,
std::index_sequence<Indices...>) {
Eigen::TensorMap<Eigen::Tensor<ScalarT, N>> tensor(data.data(), dims[Indices]...);
return tensor;
}
} // namespace internal

/**
* @brief Convert MultiArray message to Eigen Tensor array.
*
* @param msg the message to convert
* @return the Eigen Tensor
*/
template <int Size>
Eigen::Tensor<double, Size> MultiArrayToTensor(const obelisk_std_msgs::msg::FloatMultiArray& msg) {

// Get the flat part of the data
std::vector<double> data(msg.data.begin() + msg.layout.data_offset, msg.data.end());

if (msg.layout.dim.size() != Size) {
RCLCPP_ERROR_STREAM(rclcpp::get_logger("msg_conversion"),
"Templated size does not match the size provided by the message!");
}

std::array<int, Size> sizes;
for (int i = 0; i < Size; i++) {
sizes.at(i) = msg.layout.dim.at(i).size;
}

auto tensor = internal::CreateTensor<double, Size>(data, sizes, std::make_index_sequence<Size>{});

return tensor;
}

/**
* @brief Convert MultiArray message to Eigen Tensor array.
*
* @param msg the message to convert
* @return the Eigen Tensor
*/
template <int Size>
Eigen::Tensor<uint8_t, Size> MultiArrayToTensor(const obelisk_std_msgs::msg::UInt8MultiArray& msg) {

// Get the flat part of the data
std::vector<uint8_t> data(msg.data.begin() + msg.layout.data_offset, msg.data.end());

if (msg.layout.dim.size() != Size) {
RCLCPP_ERROR_STREAM(rclcpp::get_logger("msg_conversion"),
"Templated size does not match the size provided by the message!");
}

std::array<int, Size> sizes;
for (int i = 0; i < Size; i++) {
sizes.at(i) = msg.layout.dim.at(i).size;
}

auto tensor = internal::CreateTensor<uint8_t, Size>(data, sizes, std::make_index_sequence<Size>{});

return tensor;
}

/**
* @brief Convert an Eigen Tensor into a multiarray message.
*
* @param tensor the tensor to convert
* @return the multiarray message
*/
template <int Size>
obelisk_std_msgs::msg::FloatMultiArray TensorToMultiArray(const Eigen::Tensor<double, Size>& tensor) {
obelisk_std_msgs::msg::FloatMultiArray msg;
msg.layout.data_offset = 0;

// Get data into flat vector
msg.data.resize(tensor.size());
std::copy(tensor.data(), tensor.data() + tensor.size(), msg.data.begin());

// Compute stride lengths
std_msgs::msg::MultiArrayDimension dim;
dim.label = "dim_" + std::to_string(Size);
dim.size = tensor.dimension(Size - 1);
dim.stride = 1;

msg.layout.dim.emplace_back(dim); // The stride for the last dimension
for (int i = tensor.dimensions().size() - 2; i >= 0; --i) {
dim.label = "dim_" + std::to_string(i);
dim.size = tensor.dimension(i);
dim.stride = msg.layout.dim.back().stride * tensor.dimension(i + 1);

msg.layout.dim.emplace_back(dim);
}
std::reverse(msg.layout.dim.begin(), msg.layout.dim.end()); // Reverse to match dimension order

return msg;
}

/**
* @brief Convert an Eigen Tensor into a multiarray message.
*
* @param tensor the tensor to convert
* @return the multiarray message
*/
template <int Size>
obelisk_std_msgs::msg::UInt8MultiArray TensorToMultiArray(const Eigen::Tensor<uint8_t, Size>& tensor) {
obelisk_std_msgs::msg::UInt8MultiArray msg;
msg.layout.data_offset = 0;

// Get data into flat vector
msg.data.resize(tensor.size());
std::copy(tensor.data(), tensor.data() + tensor.size(), msg.data.begin());

// Compute stride lengths
std_msgs::msg::MultiArrayDimension dim;
dim.label = "dim_" + std::to_string(Size);
dim.size = tensor.dimension(Size - 1);
dim.stride = 1;

msg.layout.dim.emplace_back(dim); // The stride for the last dimension
for (int i = tensor.dimensions().size() - 2; i >= 0; --i) {
dim.label = "dim_" + std::to_string(i);
dim.size = tensor.dimension(i);
dim.stride = msg.layout.dim.back().stride * tensor.dimension(i + 1);

msg.layout.dim.emplace_back(dim);
}
std::reverse(msg.layout.dim.begin(), msg.layout.dim.end()); // Reverse to match dimension order

return msg;
}
} // namespace obelisk::utils::msgs
2 changes: 1 addition & 1 deletion obelisk/python/obelisk_py/core/sim/mujoco.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def timer_callback() -> None:
y = []
for sensor_name in sensor_names:
y.append(self.mj_data.sensor(sensor_name).data)
msg.y = list(np.concatenate(y)) # like we assume all ObeliskControlMsg objs have a u field, sensors have y
msg.y = list(np.concatenate(y)) # assume all ObeliskSensorMsg objs have a y field
pub_sensor.publish(msg)

return timer_callback
Expand Down
Loading

0 comments on commit 6630b5f

Please sign in to comment.