Program Listing for File iio_buffer.cpp

Return to documentation for file (src/iio_buffer.cpp)

// Copyright 2025 Analog Devices, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "adi_iio/iio_buffer.hpp"

IIOBuffer::IIOBuffer(std::shared_ptr<IIONode> nh, std::string device_path)
{
  this->m_nh = nh;
  this->m_device_path = device_path;
  m_canceled = false;
  this->m_buffer = nullptr;
}

IIOBuffer::~IIOBuffer()
{
  destroyIIOBuffer();

  m_stopThread = true;
  if (m_th.joinable()) {
    m_th.join();
  }
}

void IIOBuffer::destroyIIOBuffer()
{
  if (m_buffer) {
    m_canceled = true;
    iio_buffer_cancel(m_buffer);
    std::lock_guard<std::mutex> lock(m_mutex);
    iio_buffer_destroy(m_buffer);
    m_buffer = nullptr;
    RCLCPP_DEBUG(rclcpp::get_logger("rclcpp"), "Destroyed buffer %p", (void *)m_buffer);
  }
}

bool IIOBuffer::createIIOBuffer(std::string & message)
{
  std::lock_guard<std::mutex> lock(m_mutex);
  iio_device * dev = iio_context_find_device(m_nh->ctx(), m_device_path.c_str());
  if (dev == nullptr) {
    message = "Device not found";
    RCLCPP_WARN(
      rclcpp::get_logger(
        "rclcpp"), "could not find device \"%s\" - errno %d - %s", m_device_path.c_str(),
      errno, message.c_str());
    return false;
  }

  // verify channel count
  if (m_channels.size() == 0) {
    message = "No channels to read";
    return false;
  }

  if (m_samples_count == 0) {
    message = "No samples to read";
    return false;
  }

  // validate that all channels exist
  for (auto & channel : m_channels) {
    iio_channel * ch = iio_device_find_channel(dev, channel.c_str(), false);
    if (ch == nullptr) {
      message = strerror(-errno);
      RCLCPP_WARN(
        rclcpp::get_logger(
          "rclcpp"), "could not find channel \"%s\" in device \"%s\" - errno %d - %s",
        channel.c_str(), m_device_path.c_str(), errno, message.c_str());
      return false;
    }
  }

  // disable all channels
  for (unsigned int i = 0; i < iio_device_get_channels_count(dev); i++) {
    iio_channel_disable(iio_device_get_channel(dev, i));
    RCLCPP_DEBUG(rclcpp::get_logger("rclcpp"), "Disabling channel %d", i);
  }

  // enable channels
  for (auto & channel : m_channels) {
    iio_channel * ch = iio_device_find_channel(dev, channel.c_str(), false);
    if (ch == nullptr) {
      message = strerror(-errno);
      RCLCPP_WARN(
        rclcpp::get_logger(
          "rclcpp"), "could not find channel \"%s\" in device \"%s\" - errno %d - %s",
        channel.c_str(), m_device_path.c_str(), errno, message.c_str());
      return false;
    }
    iio_channel_enable(ch);
    RCLCPP_DEBUG(rclcpp::get_logger("rclcpp"), "Enabling channel %s", channel.c_str());
  }

  m_canceled = false;
  m_buffer = iio_device_create_buffer(dev, m_samples_count, false);
  if (m_buffer == nullptr) {
    message = strerror(-errno);
    RCLCPP_WARN(
      rclcpp::get_logger("rclcpp"), "could not create buffer in device \"%s\" - errno %d - %s",
      m_device_path.c_str(), errno, message.c_str());
    return false;
  }

  m_data.layout.dim.clear();
  m_data.layout.dim.push_back(std_msgs::msg::MultiArrayDimension());
  m_data.layout.dim.push_back(std_msgs::msg::MultiArrayDimension());
  m_data.layout.dim[0].label = "samples";
  m_data.layout.dim[0].size = m_samples_count;
  m_data.layout.dim[0].stride = m_channels.size() * m_samples_count;
  m_data.layout.dim[1].label = "channels";
  m_data.layout.dim[1].size = m_channels.size();
  m_data.layout.dim[1].stride = m_channels.size();


  RCLCPP_DEBUG(rclcpp::get_logger("rclcpp"), "Created buffer %p", (void *)m_buffer);
  message = "Success";
  return true;
}

bool IIOBuffer::refill(std::string & message)
{
  std::lock_guard<std::mutex> lock(m_mutex);
  iio_device * dev = iio_context_find_device(m_nh->ctx(), m_device_path.c_str());
  if (!m_buffer || m_canceled) {
    message = "Buffer not created";
    return false;
  }

  ssize_t size = iio_buffer_refill(m_buffer);
  RCLCPP_DEBUG(rclcpp::get_logger("rclcpp"), "Refilled buffer with %ld bytes from HW", size);

  if (size < 0) {
    message = strerror(-errno);
    RCLCPP_WARN(
      rclcpp::get_logger("rclcpp"), "could not refill buffer in device \"%s\" - errno %d - %s",
      m_device_path.c_str(), errno, message.c_str());
    return false;
  }

  m_data.data.clear();
  for (int i = 0; i < m_samples_count; i++) {
    for (auto & channel : m_channels) {
      iio_channel * ch = iio_device_find_channel(dev, channel.c_str(), false);

      uint8_t * base_ptr = reinterpret_cast<uint8_t *>(iio_buffer_first(m_buffer, ch));
      size_t step = iio_buffer_step(m_buffer); // Should be in bytes
      uint8_t * sample = base_ptr + (step * i);

      //uint8_t* sample = (((uint8_t*)iio_buffer_first(m_buffer, ch)) + iio_buffer_step(m_buffer) * i);
      int32_t val = 0;
      iio_channel_convert(ch, &val, sample);
      const iio_data_format * fmt = iio_channel_get_data_format(ch);
      // val = val & ((1<<fmt->bits) - 1);
      if (val & (1 << (fmt->bits))) {  // sign extension
        val = val | (~((1 << fmt->bits) - 1));
      }
      m_data.data.push_back(val);
    }
  }
  message = "Success";
  return true;
}

bool IIOBuffer::push(std::string & message, std_msgs::msg::Int32MultiArray & data)
{
  std::lock_guard<std::mutex> lock(m_mutex);
  iio_device * dev = iio_context_find_device(m_nh->ctx(), m_device_path.c_str());
  if (!m_buffer) {
    message = "Buffer not created";
    return false;
  }

  // get data from multiarray and add it to a memory "arena"
  for (int i = 0; i < m_samples_count; i++) {
    for (size_t j = 0; j < m_channels.size(); j++) {
      iio_channel * ch = iio_device_find_channel(dev, m_channels[j].c_str(), true);
      void * sample = reinterpret_cast<int32_t *>(iio_buffer_first(m_buffer, ch)) +
        iio_buffer_step(m_buffer) * i;
      int32_t val = data.data[i * m_channels.size() + j];
      iio_channel_convert(ch, sample, &val);
    }
  }

  iio_buffer_push(m_buffer);
  message = "Success";
  return true;
}


void IIOBuffer::enableTopic(std::string topic_name)
{
  m_topic_enabled = true;
  m_stopThread = false;

  if (topic_name == "") {
    m_topic_name = m_nh->convertAttrPathToTopicName(m_device_path);
  } else {
    m_topic_name = m_nh->convertAttrPathToTopicName(topic_name);
  }


  m_pub = m_nh->create_publisher<std_msgs::msg::Int32MultiArray>(
    m_topic_name + BUFFER_READ_SUFFIX,
    BUFFER_QOS_QUEUE_SIZE);
  m_th = std::thread(&IIOBuffer::publishingLoop, this);
}
// create topic
// create thread that refills
// update publishes to topic
// move Int32MultiArray to class - and unlock mutex to get data in refill service

void IIOBuffer::publishingLoop()
{
  while (rclcpp::ok() && !m_stopThread) {
    std::string msg;
    std_msgs::msg::Int32MultiArray buffer;
    if (refill(msg)) {
      m_pub->publish(m_data);
    }
    // Sleep to maintain the loop rate
    //std::this_thread::yield();
  }
}

void IIOBuffer::disableTopic()
{
  m_topic_enabled = false;
  m_stopThread = true;

  if (m_th.joinable()) {
    m_th.join();  // stop thread
  }
  m_pub.reset();  // destroy topic
  RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Disabled IIOBuffer topic");
}