From eb934f167bf28616c8d281034f0e51d71afd5a4f Mon Sep 17 00:00:00 2001
From: Karl Semich <0xloem@gmail.com>
Date: Tue, 18 Sep 2018 00:20:37 +0000
Subject: [PATCH] add support for message commands to all devices

refactored just a little to provide for this
---
 include/osmosdr/CMakeLists.txt |   1 +
 include/osmosdr/messages.h     |  37 ++
 lib/CMakeLists.txt             |   1 +
 lib/common_iface.h             | 368 +++++++++++++++++++
 lib/dev_manager.cc             | 815 +++++++++++++++++++++++++++++++++++++++++
 lib/dev_manager.h              | 176 +++++++++
 lib/sink_iface.h               | 336 +----------------
 lib/sink_impl.cc               | 370 +++----------------
 lib/sink_impl.h                |  15 +-
 lib/source_iface.h             | 337 +----------------
 lib/source_impl.cc             | 446 +++-------------------
 lib/source_impl.h              |  20 +-
 lib/uhd/uhd_sink_c.cc          |   5 +
 lib/uhd/uhd_source_c.cc        |   5 +
 14 files changed, 1530 insertions(+), 1402 deletions(-)
 create mode 100644 include/osmosdr/messages.h
 create mode 100644 lib/common_iface.h
 create mode 100644 lib/dev_manager.cc
 create mode 100644 lib/dev_manager.h

diff --git a/include/osmosdr/CMakeLists.txt b/include/osmosdr/CMakeLists.txt
index d185ee6..6f92eaa 100644
--- a/include/osmosdr/CMakeLists.txt
+++ b/include/osmosdr/CMakeLists.txt
@@ -26,6 +26,7 @@ install(FILES
     ranges.h
     time_spec.h
     device.h
+    messages.h
     source.h
     sink.h
     DESTINATION include/osmosdr
diff --git a/include/osmosdr/messages.h b/include/osmosdr/messages.h
new file mode 100644
index 0000000..b4aacf0
--- /dev/null
+++ b/include/osmosdr/messages.h
@@ -0,0 +1,37 @@
+/* -*- c++ -*- */
+/*
+ * Copyright 2018 Karl Semich <0xloem@gmail.com>
+ *
+ * GNU Radio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3, or (at your option)
+ * any later version.
+ *
+ * GNU Radio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Radio; see the file COPYING.  If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street,
+ * Boston, MA 02110-1301, USA.
+ */
+#ifndef INCLUDED_OSMOSDR_MESSAGES_H
+#define INCLUDED_OSMOSDR_MESSAGES_H
+
+#include <pmt/pmt.h>
+
+namespace osmosdr {
+
+const pmt::pmt_t CMD_PORT = pmt::mp("command");
+const pmt::pmt_t CMD_CHAN_KEY = pmt::mp("chan");
+const pmt::pmt_t CMD_GAIN_KEY = pmt::mp("gain");
+const pmt::pmt_t CMD_FREQ_KEY = pmt::mp("freq");
+const pmt::pmt_t CMD_RATE_KEY = pmt::mp("rate");
+const pmt::pmt_t CMD_ANTENNA_KEY = pmt::mp("antenna");
+const pmt::pmt_t CMD_BANDWIDTH_KEY = pmt::mp("bandwidth");
+
+} /* namespace osmosdr */
+
+#endif /* INCLUDED_OSMOSDR_MESSAGES_H */
diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt
index c05b8d9..d5ce7f5 100644
--- a/lib/CMakeLists.txt
+++ b/lib/CMakeLists.txt
@@ -38,6 +38,7 @@ ENDMACRO(GR_OSMOSDR_APPEND_LIBS)
 GR_OSMOSDR_APPEND_SRCS(
     source_impl.cc
     sink_impl.cc
+    dev_manager.cc
     ranges.cc
     device.cc
     time_spec.cc
diff --git a/lib/common_iface.h b/lib/common_iface.h
new file mode 100644
index 0000000..b3c6b03
--- /dev/null
+++ b/lib/common_iface.h
@@ -0,0 +1,368 @@
+/* -*- c++ -*- */
+/*
+ * Copyright 2012 Dimitri Stolnikov <horiz0n@gmx.net>
+ *
+ * GNU Radio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3, or (at your option)
+ * any later version.
+ *
+ * GNU Radio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Radio; see the file COPYING.  If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef OSMOSDR_COMMON_IFACE_H
+#define OSMOSDR_COMMON_IFACE_H
+
+#include <osmosdr/ranges.h>
+#include <osmosdr/time_spec.h>
+
+/*!
+ * TODO: document
+ *
+ */
+class common_iface
+{
+public:
+  /*!
+   * Get the number of channels the underlying radio hardware offers.
+   * \return the number of available channels
+   */
+  virtual size_t get_num_channels( void ) = 0;
+
+  /*!
+   * Get the possible sample rates for the underlying radio hardware.
+   * \return a range of rates in Sps
+   */
+  virtual osmosdr::meta_range_t get_sample_rates( void ) = 0;
+
+  /*!
+   * Set the sample rate for the underlying radio hardware.
+   * This also will select the appropriate IF bandpass, if applicable.
+   * \param rate a new rate in Sps
+   */
+  virtual double set_sample_rate( double rate ) = 0;
+
+  /*!
+   * Get the sample rate for the underlying radio hardware.
+   * This is the actual sample rate and may differ from the rate set.
+   * \return the actual rate in Sps
+   */
+  virtual double get_sample_rate( void ) = 0;
+
+  /*!
+   * Get the tunable frequency range for the underlying radio hardware.
+   * \param chan the channel index 0 to N-1
+   * \return the frequency range in Hz
+   */
+  virtual osmosdr::freq_range_t get_freq_range( size_t chan = 0 ) = 0;
+
+  /*!
+   * Tune the underlying radio hardware to the desired center frequency.
+   * This also will select the appropriate RF bandpass.
+   * \param freq the desired frequency in Hz
+   * \param chan the channel index 0 to N-1
+   * \return the actual frequency in Hz
+   */
+  virtual double set_center_freq( double freq, size_t chan = 0 ) = 0;
+
+  /*!
+   * Get the center frequency the underlying radio hardware is tuned to.
+   * This is the actual frequency and may differ from the frequency set.
+   * \param chan the channel index 0 to N-1
+   * \return the frequency in Hz
+   */
+  virtual double get_center_freq( size_t chan = 0 ) = 0;
+
+  /*!
+   * Set the frequency correction value in parts per million.
+   * \param ppm the desired correction value in parts per million
+   * \param chan the channel index 0 to N-1
+   * \return correction value in parts per million
+   */
+  virtual double set_freq_corr( double ppm, size_t chan = 0 ) = 0;
+
+  /*!
+   * Get the frequency correction value.
+   * \param chan the channel index 0 to N-1
+   * \return correction value in parts per million
+   */
+  virtual double get_freq_corr( size_t chan = 0 ) = 0;
+
+  /*!
+   * Get the gain stage names of the underlying radio hardware.
+   * \param chan the channel index 0 to N-1
+   * \return a vector of strings containing the names of gain stages
+   */
+  virtual std::vector<std::string> get_gain_names( size_t chan = 0 ) = 0;
+
+  /*!
+   * Get the settable overall gain range for the underlying radio hardware.
+   * \param chan the channel index 0 to N-1
+   * \return the gain range in dB
+   */
+  virtual osmosdr::gain_range_t get_gain_range( size_t chan = 0 ) = 0;
+
+  /*!
+   * Get the settable gain range for a specific gain stage.
+   * \param name the name of the gain stage
+   * \param chan the channel index 0 to N-1
+   * \return the gain range in dB
+   */
+  virtual osmosdr::gain_range_t get_gain_range( const std::string & name,
+                                                size_t chan = 0 ) = 0;
+
+  /*!
+   * Set the gain mode for the underlying radio hardware.
+   * This might be supported only for certain hardware types.
+   * \param automatic the gain mode (true means automatic gain mode)
+   * \param chan the channel index 0 to N-1
+   * \return the actual gain mode
+   */
+  virtual bool set_gain_mode( bool automatic, size_t chan = 0 ) { return false; }
+
+  /*!
+   * Get the gain mode selected for the underlying radio hardware.
+   * \param chan the channel index 0 to N-1
+   * \return the actual gain mode (true means automatic gain mode)
+   */
+  virtual bool get_gain_mode( size_t chan = 0 ) { return false; }
+
+  /*!
+   * Set the gain for the underlying radio hardware.
+   * This function will automatically distribute the desired gain value over
+   * available gain stages in an appropriate way and return the actual value.
+   * \param gain the gain in dB
+   * \param chan the channel index 0 to N-1
+   * \return the actual gain in dB
+   */
+  virtual double set_gain( double gain, size_t chan = 0 ) = 0;
+
+  /*!
+   * Set the named gain on the underlying radio hardware.
+   * \param gain the gain in dB
+   * \param name the name of the gain stage
+   * \param chan the channel index 0 to N-1
+   * \return the actual gain in dB
+   */
+  virtual double set_gain( double gain,
+                           const std::string & name,
+                           size_t chan = 0 ) = 0;
+
+  /*!
+   * Get the actual gain setting of the underlying radio hardware.
+   * \param chan the channel index 0 to N-1
+   * \return the actual gain in dB
+   */
+  virtual double get_gain( size_t chan = 0 ) = 0;
+
+  /*!
+   * Get the actual gain setting of a named stage.
+   * \param name the name of the gain stage
+   * \param chan the channel index 0 to N-1
+   * \return the actual gain in dB
+   */
+  virtual double get_gain( const std::string & name, size_t chan = 0 ) = 0;
+
+  /*!
+   * Set the IF gain for the underlying radio hardware.
+   * This function will automatically distribute the desired gain value over
+   * available IF gain stages in an appropriate way and return the actual value.
+   * \param gain the gain in dB
+   * \param chan the channel index 0 to N-1
+   * \return the actual gain in dB
+   */
+  virtual double set_if_gain( double gain, size_t chan = 0 ) { return 0; }
+
+  /*!
+   * Set the BB gain for the underlying radio hardware.
+   * This function will automatically distribute the desired gain value over
+   * available BB gain stages in an appropriate way and return the actual value.
+   * \param gain the gain in dB
+   * \param chan the channel index 0 to N-1
+   * \return the actual gain in dB
+   */
+  virtual double set_bb_gain( double gain, size_t chan = 0 ) { return 0; }
+
+  /*!
+   * Get the available antennas of the underlying radio hardware.
+   * \param chan the channel index 0 to N-1
+   * \return a vector of strings containing the names of available antennas
+   */
+  virtual std::vector< std::string > get_antennas( size_t chan = 0 ) = 0;
+
+  /*!
+   * Select the active antenna of the underlying radio hardware.
+   * \param chan the channel index 0 to N-1
+   * \return the actual antenna's name
+   */
+  virtual std::string set_antenna( const std::string & antenna,
+                                   size_t chan = 0 ) = 0;
+
+  /*!
+   * Get the actual underlying radio hardware antenna setting.
+   * \param chan the channel index 0 to N-1
+   * \return the actual antenna's name
+   */
+  virtual std::string get_antenna( size_t chan = 0 ) = 0;
+
+  /*!
+   * Set the frontend DC offset value.
+   * The value is complex to control both I and Q.
+   * For RX, only set this when automatic correction is disabled.
+   *
+   * \param offset the dc offset (1.0 is full-scale)
+   * \param chan the channel index 0 to N-1
+   */
+  virtual void set_dc_offset( const std::complex<double> &offset, size_t chan = 0 ) { }
+
+  /*!
+   * Set the frontend IQ balance correction.
+   * Use this to adjust the magnitude and phase of I and Q.
+   *
+   * \param balance the complex correction value
+   * \param chan the channel index 0 to N-1
+   */
+  virtual void set_iq_balance( const std::complex<double> &balance, size_t chan = 0 ) { }
+
+  /*!
+   * Set the bandpass filter on the radio frontend.
+   * \param bandwidth the filter bandwidth in Hz, set to 0 for automatic selection
+   * \param chan the channel index 0 to N-1
+   * \return the actual filter bandwidth in Hz
+   */
+  virtual double set_bandwidth( double bandwidth, size_t chan = 0 ) { return 0; }
+
+  /*!
+   * Get the actual bandpass filter setting on the radio frontend.
+   * \param chan the channel index 0 to N-1
+   * \return the actual filter bandwidth in Hz
+   */
+  virtual double get_bandwidth( size_t chan = 0 ) { return 0; }
+
+  /*!
+   * Get the possible bandpass filter settings on the radio frontend.
+   * \param chan the channel index 0 to N-1
+   * \return a range of bandwidths in Hz
+   */
+  virtual osmosdr::freq_range_t get_bandwidth_range( size_t chan = 0 )
+    { return osmosdr::freq_range_t(); }
+
+  /*!
+   * Set the time source for the device.
+   * This sets the method of time synchronization,
+   * typically a pulse per second or an encoded time.
+   * Typical options for source: external, MIMO.
+   * \param source a string representing the time source
+   * \param mboard which motherboard to set the config
+   */
+  virtual void set_time_source(const std::string &source,
+                               const size_t mboard = 0) { }
+
+  /*!
+   * Get the currently set time source.
+   * \param mboard which motherboard to get the config
+   * \return the string representing the time source
+   */
+  virtual std::string get_time_source(const size_t mboard) { return ""; }
+
+  /*!
+   * Get a list of possible time sources.
+   * \param mboard which motherboard to get the list
+   * \return a vector of strings for possible settings
+   */
+  virtual std::vector<std::string> get_time_sources(const size_t mboard)
+  {
+    return std::vector<std::string>();
+  }
+
+  /*!
+   * Set the clock source for the device.
+   * This sets the source for a 10 Mhz reference clock.
+   * Typical options for source: internal, external, MIMO.
+   * \param source a string representing the clock source
+   * \param mboard which motherboard to set the config
+   */
+  virtual void set_clock_source(const std::string &source,
+                                const size_t mboard = 0) { }
+
+  /*!
+   * Get the currently set clock source.
+   * \param mboard which motherboard to get the config
+   * \return the string representing the clock source
+   */
+  virtual std::string get_clock_source(const size_t mboard) { return ""; }
+
+  /*!
+   * Get a list of possible clock sources.
+   * \param mboard which motherboard to get the list
+   * \return a vector of strings for possible settings
+   */
+  virtual std::vector<std::string> get_clock_sources(const size_t mboard)
+  {
+    return std::vector<std::string>();
+  }
+
+  /*!
+   * Get the master clock rate.
+   * \param mboard the motherboard index 0 to M-1
+   * \return the clock rate in Hz
+   */
+  virtual double get_clock_rate(size_t mboard = 0) { return 0; }
+
+  /*!
+   * Set the master clock rate.
+   * \param rate the new rate in Hz
+   * \param mboard the motherboard index 0 to M-1
+   */
+  virtual void set_clock_rate(double rate, size_t mboard = 0) { }
+
+  /*!
+   * Get the current time registers.
+   * \param mboard the motherboard index 0 to M-1
+   * \return the current device time
+   */
+  virtual ::osmosdr::time_spec_t get_time_now(size_t mboard = 0)
+  {
+    return ::osmosdr::time_spec_t::get_system_time();
+  }
+
+  /*!
+   * Get the time when the last pps pulse occured.
+   * \param mboard the motherboard index 0 to M-1
+   * \return the current device time
+   */
+  virtual ::osmosdr::time_spec_t get_time_last_pps(size_t mboard = 0)
+  {
+    return ::osmosdr::time_spec_t::get_system_time();
+  }
+
+  /*!
+   * Sets the time registers immediately.
+   * \param time_spec the new time
+   * \param mboard the motherboard index 0 to M-1
+   */
+  virtual void set_time_now(const ::osmosdr::time_spec_t &time_spec,
+                            size_t mboard = 0) { }
+
+  /*!
+   * Set the time registers at the next pps.
+   * \param time_spec the new time
+   */
+  virtual void set_time_next_pps(const ::osmosdr::time_spec_t &time_spec) { }
+
+  /*!
+   * Sync the time registers with an unknown pps edge.
+   * \param time_spec the new time
+   */
+  virtual void set_time_unknown_pps(const ::osmosdr::time_spec_t &time_spec) { }
+};
+
+#endif // OSMOSDR_COMMON_IFACE_H
diff --git a/lib/dev_manager.cc b/lib/dev_manager.cc
new file mode 100644
index 0000000..e2d0319
--- /dev/null
+++ b/lib/dev_manager.cc
@@ -0,0 +1,815 @@
+/* -*- c++ -*- */
+/*
+ * Copyright 2018 Karl Semich <0xloem@gmail.com>
+ *
+ * GNU Radio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3, or (at your option)
+ * any later version.
+ *
+ * GNU Radio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Radio; see the file COPYING.  If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/*
+ * config.h is generated by configure.  It contains the results
+ * of probing for features, options etc.  It should be the first
+ * file included in your .cc file.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "dev_manager.h"
+
+#include <gnuradio/io_signature.h>
+
+#include "source_iface.h"
+
+/*
+ * Create a new instance of dev_manager and return
+ * a boost shared_ptr.  This is effectively the public constructor.
+ */
+dev_manager_sptr
+make_dev_manager ( gr::hier_block2_sptr outer )
+{
+  return gnuradio::get_initial_sptr( new dev_manager ( outer ) );
+}
+
+/*
+ * The private constructor
+ */
+dev_manager::dev_manager ( gr::hier_block2_sptr outer )
+  : gr::block ( "dev_manager",
+      gr::io_signature::make(0, 0, 0),
+      gr::io_signature::make(0, 0, 0) ),
+    _outer(outer),
+    _channels(0),
+    _sample_rate(NAN)
+{
+  message_port_register_in( osmosdr::CMD_PORT );
+  set_msg_handler( osmosdr::CMD_PORT, boost::bind(&dev_manager::msg_handler_command, this, _1) );
+}
+
+int dev_manager::general_work( int noutput_items,
+                               gr_vector_int &ninput_items,
+                               gr_vector_const_void_star &input_items,
+                               gr_vector_void_star &output_items)
+{
+  return 0;
+}
+
+void dev_manager::add_device( gr::basic_block_sptr block, common_iface *iface )
+{
+  if ( iface == nullptr || block.get() == nullptr ) {
+    if ( iface != nullptr || block.get() != nullptr )
+      throw std::runtime_error("Either iface or block are NULL.");
+    return;
+  }
+
+  size_t mboard = get_num_mboards();
+  _devs.push_back( { iface, block, _channels } );
+
+  if ( is_sink( mboard ) ) {
+    // sink block
+    for (size_t i = 0; i < iface->get_num_channels(); i++) {
+      _outer->connect( _outer->self(), _channels, block, i );
+      _chandevs.push_back( mboard );
+      _channels++;
+    }
+  } else {
+    // source block
+    for (size_t i = 0; i < iface->get_num_channels(); i++) {
+#ifdef HAVE_IQBALANCE
+      // add iqbalance blocks if enabled
+      gr::iqbalance::optimize_c::sptr iq_opt = gr::iqbalance::optimize_c::make( 0 );
+      gr::iqbalance::fix_cc::sptr     iq_fix = gr::iqbalance::fix_cc::make();
+
+      connect( block, i, iq_fix, 0 );
+      connect( block, i, iq_opt, 0 );
+      msg_connect( iq_opt, "iqbal_corr", iq_fix, "iqbal_corr" );
+
+      _iq_opt.push_back( iq_opt.get() );
+      _iq_fix.push_back( iq_fix.get() );
+
+      _outer->connect( iq_fix, 0, _outer->self(), _channels );
+ #else
+      _outer->connect( block, i, _outer->self(), _channels );
+ #endif
+      _chandevs.push_back( mboard );
+      _channels++;
+    }
+  }
+}
+
+size_t dev_manager::get_num_mboards() const
+{
+  return _devs.size();
+}
+
+size_t dev_manager::get_mboard_for_channel( size_t chan ) const
+{
+  return _chandevs.at(chan);
+}
+
+bool dev_manager::is_sink( size_t mboard ) const
+{
+  return get_block(mboard)->input_signature()->max_streams() > 0;
+}
+
+gr::basic_block_sptr dev_manager::get_block( size_t mboard ) const
+{
+  return _devs.at(mboard).block;
+}
+
+common_iface * dev_manager::get_iface( size_t mboard ) const
+{
+  return _devs.at(mboard).dev;
+}
+
+size_t dev_manager::get_mboard_channel( size_t mboard ) const
+{
+  return _devs.at(mboard).chan;
+}
+
+void dev_manager::msg_handler_command( pmt::pmt_t msg )
+{
+  if ( !pmt::is_dict(msg) ) {
+    GR_LOG_ERROR( d_logger, boost::format("Command message is neither dict nor pair: %s") % msg );
+    return;
+  }
+
+  // if pair, this throws and converts to dict
+  try {
+    pmt::pmt_t keys = pmt::dict_keys(msg);
+  } catch ( const pmt::wrong_type & ) {
+    GR_LOG_DEBUG( d_debug_logger, boost::format("Converting pair to dict: %s") % msg );
+    msg = pmt::dict_add( pmt::make_dict(), pmt::car(msg), pmt::cdr(msg) );
+  }
+
+  // read chan. defaults to -1, all chans at once
+  int chan;
+  try {
+    chan = int(pmt::to_long(
+          pmt::dict_ref(
+            msg, osmosdr::CMD_CHAN_KEY,
+            pmt::from_long(-1)
+          )
+    ));
+  } catch ( const pmt::wrong_type & ) {
+    GR_LOG_ALERT( d_logger, boost::format("Invalid channel number %s") % pmt::dict_ref( msg, osmosdr::CMD_CHAN_KEY ) );
+    return;
+  }
+  
+  // rate applies to all channels
+  pmt::pmt_t rate = pmt::dict_ref( msg, osmosdr::CMD_RATE_KEY, pmt::PMT_NIL );
+  if ( rate != pmt::PMT_NIL ) {
+    if ( !pmt::is_real(rate) && !pmt::is_integer(rate) ) {
+      GR_LOG_ALERT( d_logger, boost::format("Invalid type for sample rate %s") % rate );
+      return;
+    }
+    set_sample_rate( pmt::to_double(rate) );
+    msg = pmt::dict_delete( msg, osmosdr::CMD_RATE_KEY );
+  }
+
+  // extract commands
+  pmt::pmt_t msg_items = pmt::dict_items(msg);
+
+  // check every device
+  for (size_t mboard = 0; mboard < get_num_mboards(); mboard++) {
+    int cur_chan = get_mboard_channel( mboard );
+    int next_chan = cur_chan + get_iface( mboard )->get_num_channels();
+    if ( chan != -1 && chan >= next_chan )
+      continue;
+
+    // if device accepts command messages, just pass it along
+    gr::basic_block_sptr block = get_block( mboard );
+    if ( block->has_msg_port(osmosdr::CMD_PORT) ) {
+      if ( chan != -1 )
+        block->_post( osmosdr::CMD_PORT, pmt::dict_add(msg, osmosdr::CMD_CHAN_KEY, pmt::from_long(chan - cur_chan)) );
+      else
+        block->_post( osmosdr::CMD_PORT, msg );
+      continue;
+    }
+
+    int start_chan, end_chan;
+    if ( chan == -1 ) {
+      start_chan = cur_chan;
+      end_chan = next_chan;
+    } else {
+      start_chan = chan;
+      end_chan = chan + 1;
+    }
+
+    for (int it_chan = start_chan; it_chan < end_chan; ++ it_chan){
+      // execute every command
+      
+      for (size_t i = 0; i < pmt::length(msg_items); ++ i){
+        pmt::pmt_t const & item = pmt::nth(i, msg_items);
+        try {
+          exec_msg_cmd( pmt::car(item), pmt::cdr(item), it_chan );
+        } catch (const pmt::wrong_type &) {
+          try {
+            GR_LOG_ALERT( d_logger, boost::format("Invalid command value for key %s: %s") % pmt::car(item) % pmt::cdr(item) );
+          } catch (const pmt::wrong_type &) {
+            GR_LOG_ALERT( d_logger, boost::format("Command is not a pair: %s") % item );
+          }
+          return;
+        }
+      }
+    }
+  }
+}
+
+void dev_manager::exec_msg_cmd( const pmt::pmt_t & cmd, const pmt::pmt_t & val, size_t chan )
+{
+  if ( cmd == osmosdr::CMD_FREQ_KEY ) {
+    set_center_freq( pmt::to_double(val), chan );
+  } else if ( cmd == osmosdr::CMD_GAIN_KEY ) {
+    set_gain( pmt::to_double(val), chan );
+  } else if ( cmd == osmosdr::CMD_RATE_KEY ) {
+    // already set in msg_handler_command
+    ;
+  } else if ( cmd == osmosdr::CMD_ANTENNA_KEY ) {
+    set_antenna( pmt::symbol_to_string(val), chan );
+  } else if ( cmd == osmosdr::CMD_BANDWIDTH_KEY ) {
+    set_bandwidth( pmt::to_double(val), chan );
+  }
+  // unfamiliar commands are ignored
+}
+
+size_t dev_manager::get_num_channels()
+{
+  return _channels;
+}
+
+bool dev_manager::seek( long seek_point, int whence, size_t chan )
+{
+  if ( chan > get_num_channels() )
+    return false;
+
+  size_t mboard = get_mboard_for_channel( chan );
+  if ( is_sink( mboard ) )
+    return false;
+
+  size_t dev_chan = chan - get_mboard_channel( mboard );
+  source_iface * dev = static_cast< source_iface * >( get_iface( mboard ) );
+
+  return dev->seek( seek_point, whence, dev_chan );
+}
+
+#define NO_DEVICES_MSG  "FATAL: No device(s) available to work with."
+
+osmosdr::meta_range_t dev_manager::get_sample_rates()
+{
+  if ( get_num_mboards() )
+    return get_iface(0)->get_sample_rates(); // assume same devices used in the group
+#if 0
+  else
+    throw std::runtime_error(NO_DEVICES_MSG);
+#endif
+  return osmosdr::meta_range_t();
+}
+
+double dev_manager::set_sample_rate( double rate )
+{
+  double sample_rate = 0;
+
+  if (_sample_rate != rate) {
+#if 0
+    if (!get_num_mboards())
+      throw std::runtime_error(NO_DEVICES_MSG);
+#endif
+    for (size_t mboard = 0; mboard < get_num_mboards(); mboard++) {
+      common_iface *dev = get_iface( mboard );
+      sample_rate = dev->set_sample_rate(rate);
+      if ( !is_sink( mboard ) ) {
+#ifdef HAVE_IQBALANCE
+        // source block, update iqbalance if enabled
+        size_t channel = get_mboard_channel( mboard );
+        size_t end = channel + dev->get_num_channels();
+        for (; channel < end; channel++) {
+          if ( channel < _iq_opt.size() ) {
+            gr::iqbalance::optimize_c *opt = _iq_opt[channel];
+
+            if ( opt->period() > 0 ) { /* optimize is enabled */
+              opt->set_period( dev->get_sample_rate() / 5 );
+              opt_reset();
+            }
+          }
+        }
+#endif
+      }
+    }
+
+    _sample_rate = sample_rate;
+  }
+
+  return sample_rate;
+}
+
+double dev_manager::get_sample_rate()
+{
+  double sample_rate = 0;
+
+  if (get_num_mboards())
+    sample_rate = get_iface(0)->get_sample_rate(); // assume same devices used in the group
+#if 0
+  else
+    throw std::runtime_error(NO_DEVICES_MSG);
+#endif
+  return sample_rate;
+}
+
+osmosdr::freq_range_t dev_manager::get_freq_range( size_t chan )
+{
+  if ( chan > get_num_channels() )
+    return osmosdr::freq_range_t();
+
+  size_t mboard = get_mboard_for_channel( chan );
+  common_iface *dev = get_iface( mboard );
+  size_t dev_chan = chan - get_mboard_channel( mboard );
+
+  return dev->get_freq_range( dev_chan );
+}
+
+double dev_manager::set_center_freq( double freq, size_t chan )
+{
+  if ( chan > get_num_channels() )
+    return 0;
+
+  size_t mboard = get_mboard_for_channel( chan );
+  common_iface *dev = get_iface( mboard );
+  size_t dev_chan = chan - get_mboard_channel( mboard );
+
+  if ( _center_freq[ chan ] != freq ) {
+    _center_freq[ chan ] = freq;
+    return dev->set_center_freq( freq, dev_chan  );
+  } else { return _center_freq[ chan ]; }
+}
+
+double dev_manager::get_center_freq( size_t chan )
+{
+  if ( chan > get_num_channels() )
+    return 0;
+
+  size_t mboard = get_mboard_for_channel( chan );
+  common_iface *dev = get_iface( mboard );
+  size_t dev_chan = chan - get_mboard_channel( mboard );
+
+  return dev->get_center_freq( dev_chan );
+}
+
+double dev_manager::set_freq_corr( double ppm, size_t chan )
+{
+  if ( chan > get_num_channels() )
+    return 0;
+
+  size_t mboard = get_mboard_for_channel( chan );
+  common_iface *dev = get_iface( mboard );
+  size_t dev_chan = chan - get_mboard_channel( mboard );
+
+  if ( _freq_corr[ chan ] != ppm ) {
+    _freq_corr[ chan ] = ppm;
+    return dev->set_freq_corr( ppm, dev_chan );
+  } else { return _freq_corr[ chan ]; }
+}
+
+double dev_manager::get_freq_corr( size_t chan )
+{
+  if ( chan > get_num_channels() )
+    return 0;
+
+  size_t mboard = get_mboard_for_channel( chan );
+  common_iface *dev = get_iface( mboard );
+  size_t dev_chan = chan - get_mboard_channel( mboard );
+
+  return dev->get_freq_corr( dev_chan );
+}
+
+std::vector<std::string> dev_manager::get_gain_names( size_t chan )
+{
+  if ( chan > get_num_channels() )
+    return std::vector< std::string >();
+
+  size_t mboard = get_mboard_for_channel( chan );
+  common_iface *dev = get_iface( mboard );
+  size_t dev_chan = chan - get_mboard_channel( mboard );
+
+  return dev->get_gain_names( dev_chan );
+}
+
+osmosdr::gain_range_t dev_manager::get_gain_range( size_t chan )
+{
+  if ( chan > get_num_channels() )
+    return osmosdr::gain_range_t();
+
+  size_t mboard = get_mboard_for_channel( chan );
+  common_iface *dev = get_iface( mboard );
+  size_t dev_chan = chan - get_mboard_channel( mboard );
+
+  return dev->get_gain_range( dev_chan );
+}
+
+osmosdr::gain_range_t dev_manager::get_gain_range( const std::string & name, size_t chan )
+{
+  if ( chan > get_num_channels() )
+    return osmosdr::gain_range_t();
+
+  size_t mboard = get_mboard_for_channel( chan );
+  common_iface *dev = get_iface( mboard );
+  size_t dev_chan = chan - get_mboard_channel( mboard );
+
+  return dev->get_gain_range( name, dev_chan );
+}
+
+bool dev_manager::set_gain_mode( bool automatic, size_t chan )
+{
+  if ( chan > get_num_channels() )
+    return false;
+
+  size_t mboard = get_mboard_for_channel( chan );
+  common_iface *dev = get_iface( mboard );
+  size_t dev_chan = chan - get_mboard_channel( mboard );
+
+  if ( _gain_mode[ chan ] != automatic ) {
+    _gain_mode[ chan ] = automatic;
+    bool mode = dev->set_gain_mode( automatic, dev_chan );
+    if (!automatic) // reapply gain value when switched to manual mode
+      dev->set_gain( _gain[ chan ], dev_chan );
+    return mode;
+  } else { return _gain_mode[ chan ]; }
+}
+
+bool dev_manager::get_gain_mode( size_t chan )
+{
+  if ( chan > get_num_channels() )
+    return false;
+
+  size_t mboard = get_mboard_for_channel( chan );
+  common_iface *dev = get_iface( mboard );
+  size_t dev_chan = chan - get_mboard_channel( mboard );
+
+  return dev->get_gain_mode( dev_chan );
+}
+
+double dev_manager::set_gain( double gain, size_t chan )
+{
+  if ( chan > get_num_channels() )
+    return 0;
+
+  size_t mboard = get_mboard_for_channel( chan );
+  common_iface *dev = get_iface( mboard );
+  size_t dev_chan = chan - get_mboard_channel( mboard );
+
+  if ( _gain[ chan ] != gain ) {
+    _gain[ chan ] = gain;
+    return dev->set_gain( gain, dev_chan );
+  } else { return _gain[ chan ]; }
+}
+
+double dev_manager::set_gain( double gain, const std::string & name, size_t chan)
+{
+  if ( chan > get_num_channels() )
+    return 0;
+
+  size_t mboard = get_mboard_for_channel( chan );
+  common_iface *dev = get_iface( mboard );
+  size_t dev_chan = chan - get_mboard_channel( mboard );
+
+  return dev->set_gain( gain, name, dev_chan );
+}
+
+double dev_manager::get_gain( size_t chan )
+{
+  if ( chan > get_num_channels() )
+    return 0;
+
+  size_t mboard = get_mboard_for_channel( chan );
+  common_iface *dev = get_iface( mboard );
+  size_t dev_chan = chan - get_mboard_channel( mboard );
+
+  return dev->get_gain( dev_chan );
+}
+
+double dev_manager::get_gain( const std::string & name, size_t chan )
+{
+  if ( chan > get_num_channels() )
+    return 0;
+
+  size_t mboard = get_mboard_for_channel( chan );
+  common_iface *dev = get_iface( mboard );
+  size_t dev_chan = chan - get_mboard_channel( mboard );
+
+  return dev->get_gain( name, dev_chan );
+}
+
+double dev_manager::set_if_gain( double gain, size_t chan )
+{
+  if ( chan > get_num_channels() )
+    return 0;
+
+  size_t mboard = get_mboard_for_channel( chan );
+  common_iface *dev = get_iface( mboard );
+  size_t dev_chan = chan - get_mboard_channel( mboard );
+
+  if ( _if_gain[ chan ] != gain ) {
+    _if_gain[ chan ] = gain;
+    return dev->set_if_gain( gain, dev_chan );
+  } else { return _if_gain[ chan ]; }
+}
+
+double dev_manager::set_bb_gain( double gain, size_t chan )
+{
+  if ( chan > get_num_channels() )
+    return 0;
+
+  size_t mboard = get_mboard_for_channel( chan );
+  common_iface *dev = get_iface( mboard );
+  size_t dev_chan = chan - get_mboard_channel( mboard );
+
+  if ( _bb_gain[ chan ] != gain ) {
+    _bb_gain[ chan ] = gain;
+    return dev->set_bb_gain( gain, dev_chan );
+  } else { return _bb_gain[ chan ]; }
+}
+
+std::vector< std::string > dev_manager::get_antennas( size_t chan )
+{
+  if ( chan > get_num_channels() )
+    return std::vector< std::string >();
+
+  size_t mboard = get_mboard_for_channel( chan );
+  common_iface *dev = get_iface( mboard );
+  size_t dev_chan = chan - get_mboard_channel( mboard );
+
+  return dev->get_antennas( dev_chan );
+}
+
+std::string dev_manager::set_antenna( const std::string & antenna, size_t chan )
+{
+  if ( chan > get_num_channels() )
+    return "";
+
+  size_t mboard = get_mboard_for_channel( chan );
+  common_iface *dev = get_iface( mboard );
+  size_t dev_chan = chan - get_mboard_channel( mboard );
+
+  if ( _antenna[ chan ] != antenna ) {
+    _antenna[ chan ] = antenna;
+    return dev->set_antenna( antenna, dev_chan );
+  } else { return _antenna[ chan ]; }
+}
+
+std::string dev_manager::get_antenna( size_t chan )
+{
+  if ( chan > get_num_channels() )
+    return "";
+
+  size_t mboard = get_mboard_for_channel( chan );
+  common_iface *dev = get_iface( mboard );
+  size_t dev_chan = chan - get_mboard_channel( mboard );
+
+  return dev->get_antenna( dev_chan );
+}
+
+void dev_manager::set_dc_offset_mode( int mode, size_t chan )
+{
+  if ( chan > get_num_channels() )
+    return;
+
+  size_t mboard = get_mboard_for_channel( chan );
+  if ( is_sink( mboard ) )
+    return;
+
+  size_t dev_chan = chan - get_mboard_channel( mboard );
+  source_iface * dev = static_cast< source_iface * >( get_iface( mboard ) );
+
+  dev->set_dc_offset_mode( mode, dev_chan );
+}
+
+void dev_manager::set_dc_offset( const std::complex<double> &offset, size_t chan )
+{
+  if ( chan > get_num_channels() )
+    return;
+
+  size_t mboard = get_mboard_for_channel( chan );
+  common_iface *dev = get_iface( mboard );
+  size_t dev_chan = chan - get_mboard_channel( mboard );
+
+  dev->set_dc_offset( offset, dev_chan );
+}
+
+void dev_manager::set_iq_balance_mode( int mode, size_t chan )
+{
+  if ( chan > get_num_channels() )
+    return;
+
+  size_t mboard = get_mboard_for_channel( chan );
+  if ( is_sink( mboard ) )
+    return;
+
+  size_t dev_chan = chan - get_mboard_channel( mboard );
+  source_iface * dev = static_cast< source_iface * >( get_iface( mboard ) );
+
+#ifdef HAVE_IQBALANCE
+  if ( chan < _iq_opt.size() && chan < _iq_fix.size() ) {
+    gr::iqbalance::optimize_c *opt = _iq_opt[chan];
+    gr::iqbalance::fix_cc *fix = _iq_fix[chan];
+
+    if ( IQBalanceOff == mode  ) {
+      opt->set_period( 0 );
+      /* store current values in order to be able to restore them later */
+      _vals[ chan ] = std::pair< float, float >( fix->mag(), fix->phase() );
+      fix->set_mag( 0.0f );
+      fix->set_phase( 0.0f );
+    } else if ( IQBalanceManual == mode ) {
+      if ( opt->period() == 0 ) { /* transition from Off to Manual */
+        /* restore previous values */
+        std::pair< float, float > val = _vals[ chan ];
+        fix->set_mag( val.first );
+        fix->set_phase( val.second );
+      }
+      opt->set_period( 0 );
+    } else if ( IQBalanceAutomatic == mode ) {
+      opt->set_period( dev->get_sample_rate() / 5 );
+      opt->reset();
+    }
+  }
+#else
+  return dev->set_iq_balance_mode( mode, dev_chan );
+#endif
+
+}
+
+void dev_manager::set_iq_balance( const std::complex<double> &balance, size_t chan )
+{
+  if ( chan > get_num_channels() )
+    return;
+
+  size_t mboard = get_mboard_for_channel( chan );
+  common_iface *dev = get_iface( mboard );
+  size_t dev_chan = chan - get_mboard_channel( mboard );
+
+#ifdef HAVE_IQBALANCE
+  if ( ! is_sink( mboard ) ) {
+    // source block, update iqbalance if enabled
+    if ( chan < _iq_opt.size() && chan < _iq_fix.size() ) {
+      gr::iqbalance::optimize_c *opt = _iq_opt[chan];
+      gr::iqbalance::fix_cc *fix = _iq_fix[chan];
+
+      if ( opt->period() == 0 ) { /* automatic optimization desabled */
+        fix->set_mag( balance.real() );
+        fix->set_phase( balance.imag() );
+      }
+    }
+  } else {
+    dev->set_iq_balance( balance, dev_chan );
+  }
+#else
+  dev->set_iq_balance( balance, dev_chan );
+#endif
+}
+
+double dev_manager::set_bandwidth( double bandwidth, size_t chan )
+{
+  if ( chan > get_num_channels() )
+    return 0;
+
+  size_t mboard = get_mboard_for_channel( chan );
+  common_iface *dev = get_iface( mboard );
+  size_t dev_chan = chan - get_mboard_channel( mboard );
+
+  if ( _bandwidth[ chan ] != bandwidth || 0.0f == bandwidth ) {
+    _bandwidth[ chan ] = bandwidth;
+    return dev->set_bandwidth( bandwidth, dev_chan );
+  } else { return _bandwidth[ chan ]; }
+}
+
+double dev_manager::get_bandwidth( size_t chan )
+{
+  if ( chan > get_num_channels() )
+    return 0;
+
+  size_t mboard = get_mboard_for_channel( chan );
+  common_iface *dev = get_iface( mboard );
+  size_t dev_chan = chan - get_mboard_channel( mboard );
+
+  return dev->get_bandwidth( dev_chan );
+}
+
+osmosdr::freq_range_t dev_manager::get_bandwidth_range( size_t chan )
+{
+  if ( chan > get_num_channels() )
+    return osmosdr::freq_range_t();
+
+  size_t mboard = get_mboard_for_channel( chan );
+  common_iface *dev = get_iface( mboard );
+  size_t dev_chan = chan - get_mboard_channel( mboard );
+
+  return dev->get_bandwidth_range( dev_chan );
+}
+
+void dev_manager::set_time_source( const std::string &source, const size_t mboard )
+{
+  if ( mboard != osmosdr::ALL_MBOARDS ) {
+      get_iface( mboard )->set_time_source( source );
+      return;
+  }
+
+  for (size_t m = 0; m < get_num_mboards(); m++) { /* propagate ALL_MBOARDS */
+      get_iface(m)->set_time_source( source, osmosdr::ALL_MBOARDS );
+  }
+}
+
+std::string dev_manager::get_time_source( const size_t mboard )
+{
+  return get_iface( mboard )->get_time_source( mboard );
+}
+
+std::vector<std::string> dev_manager::get_time_sources( const size_t mboard )
+{
+  return get_iface( mboard )->get_time_sources( mboard );
+}
+
+void dev_manager::set_clock_source( const std::string &source, const size_t mboard )
+{
+  if (mboard != osmosdr::ALL_MBOARDS){
+      get_iface( mboard )->set_clock_source( source );
+      return;
+  }
+
+  for (size_t m = 0; m < get_num_mboards(); m++){ /* propagate ALL_MBOARDS */
+      get_iface(m)->set_clock_source( source, osmosdr::ALL_MBOARDS );
+  }
+}
+
+std::string dev_manager::get_clock_source( const size_t mboard )
+{
+  return get_iface( mboard )->get_clock_source( mboard );
+}
+
+std::vector<std::string> dev_manager::get_clock_sources( const size_t mboard )
+{
+  return get_iface( mboard )->get_clock_sources( mboard );
+}
+
+double dev_manager::get_clock_rate( size_t mboard )
+{
+  return get_iface( mboard )->get_clock_rate( mboard );
+}
+
+void dev_manager::set_clock_rate( double rate, size_t mboard )
+{
+  if ( mboard != osmosdr::ALL_MBOARDS ) {
+      get_iface( mboard )->set_clock_rate( rate );
+      return;
+  }
+
+  for (size_t m = 0; m < get_num_mboards(); m++) { /* propagate ALL_MBOARDS */
+      get_iface(m)->set_clock_rate( rate, osmosdr::ALL_MBOARDS );
+  }
+}
+
+osmosdr::time_spec_t dev_manager::get_time_now( size_t mboard )
+{
+  return get_iface( mboard )->get_time_now( mboard );
+}
+
+osmosdr::time_spec_t dev_manager::get_time_last_pps( size_t mboard )
+{
+  return get_iface( mboard )->get_time_last_pps( mboard );
+}
+
+void dev_manager::set_time_now( const osmosdr::time_spec_t &time_spec, size_t mboard )
+{
+  if ( mboard != osmosdr::ALL_MBOARDS ) {
+      get_iface( mboard )->set_time_now( time_spec );
+      return;
+   }
+
+  for (size_t m = 0; m < get_num_mboards(); m++) { /* propagate ALL_MBOARDS */
+      get_iface(m)->set_time_now( time_spec, osmosdr::ALL_MBOARDS );
+  }
+}
+
+void dev_manager::set_time_next_pps( const osmosdr::time_spec_t &time_spec )
+{
+  for (size_t i = 0; i < get_num_mboards(); i++) {
+    get_iface(i)->set_time_next_pps( time_spec );
+  }
+}
+
+void dev_manager::set_time_unknown_pps( const osmosdr::time_spec_t &time_spec )
+{
+  for (size_t i = 0; i < get_num_mboards(); i++) {
+    get_iface(i)->set_time_unknown_pps( time_spec );
+  }
+}
diff --git a/lib/dev_manager.h b/lib/dev_manager.h
new file mode 100644
index 0000000..416573f
--- /dev/null
+++ b/lib/dev_manager.h
@@ -0,0 +1,176 @@
+/* -*- c++ -*- */
+/*
+ * Copyright 2018 Karl Semich <0xloem@gmail.com>
+ *
+ * This file is part of GNU Radio
+ *
+ * GNU Radio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3, or (at your option)
+ * any later version.
+ *
+ * GNU Radio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Radio; see the file COPYING.  If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef INCLUDED_DEV_CONTROL_H
+#define INCLUDED_DEV_CONTROL_H
+
+#include <gnuradio/hier_block2.h>
+#include <gnuradio/block.h>
+
+#include <osmosdr/messages.h>
+
+#include "common_iface.h"
+
+class dev_manager;
+
+/*
+ * We use boost::shared_ptr's instead of raw pointers for all access
+ * to gr::blocks (and many other data structures).  The shared_ptr gets
+ * us transparent reference counting, which greatly simplifies storage
+ * management issues.  This is especially helpful in our hybrid
+ * C++ / Python system.
+ *
+ * See http://www.boost.org/libs/smart_ptr/smart_ptr.htm
+ *
+ * As a convention, the _sptr suffix indicates a boost::shared_ptr
+ */
+typedef boost::shared_ptr<dev_manager> dev_manager_sptr;
+
+/*!
+ * \brief Return a shared_ptr to a new instance of dev_manager.
+ *
+ * To avoid accidental use of raw pointers, dev_manager's
+ * constructor is private. make_dev_manager is the public
+ * interface for creating new instances.
+ */
+dev_manager_sptr make_dev_manager ( gr::hier_block2_sptr outer );
+
+/*!
+ * \brief Manages a set of osmosdr devices.
+ * \ingroup block
+ *
+ */
+class dev_manager : public gr::block
+{
+private:
+  // The friend declaration allows make_dev_manager to
+  // access the private constructor.
+
+  friend dev_manager_sptr make_dev_manager ( gr::hier_block2_sptr outer );
+
+  /*!
+   * \brief Manages a set of osmosdr devices.
+   */
+  dev_manager ( gr::hier_block2_sptr outer );    // private constructor
+
+public:
+  int general_work( int noutput_items,
+                    gr_vector_int &ninput_items,
+                    gr_vector_const_void_star &input_items,
+                    gr_vector_void_star &output_items );
+
+  void add_device( gr::basic_block_sptr block, common_iface *iface ); // add a device driver block to be managed
+  size_t get_num_mboards() const;
+  size_t get_mboard_for_channel( size_t chan ) const;
+  bool is_sink( size_t mboard = 0 ) const;
+  gr::basic_block_sptr get_block( size_t mboard ) const;
+  common_iface * get_iface( size_t mboard ) const;
+  size_t get_mboard_channel( size_t mboard ) const;
+
+  size_t get_num_channels();
+
+  bool seek( long seek_point, int whence, size_t chan);
+
+  osmosdr::meta_range_t get_sample_rates( void );
+  double set_sample_rate( double rate );
+  double get_sample_rate( void );
+
+  osmosdr::freq_range_t get_freq_range( size_t chan );
+  double set_center_freq( double freq, size_t chan );
+  double get_center_freq( size_t chan );
+  double set_freq_corr( double ppm, size_t chan );
+  double get_freq_corr( size_t chan );
+
+  std::vector<std::string> get_gain_names( size_t chan );
+  osmosdr::gain_range_t get_gain_range( size_t chan );
+  osmosdr::gain_range_t get_gain_range( const std::string & name, size_t chan );
+  bool set_gain_mode( bool automatic, size_t chan );
+  bool get_gain_mode( size_t chan );
+  double set_gain( double gain, size_t chan );
+  double set_gain( double gain, const std::string & name, size_t chan );
+  double get_gain( size_t chan );
+  double get_gain( const std::string & name, size_t chan );
+
+  double set_if_gain( double gain, size_t chan );
+  double set_bb_gain( double gain, size_t chan );
+
+  std::vector< std::string > get_antennas( size_t chan );
+  std::string set_antenna( const std::string & antenna, size_t chan );
+  std::string get_antenna( size_t chan );
+
+  void set_dc_offset_mode( int mode, size_t chan );
+  void set_dc_offset( const std::complex<double> &offset, size_t chan );
+
+  void set_iq_balance_mode( int mode, size_t chan );
+  void set_iq_balance( const std::complex<double> &balance, size_t chan );
+
+  double set_bandwidth( double bandwidth, size_t chan );
+  double get_bandwidth( size_t chan );
+  osmosdr::freq_range_t get_bandwidth_range( size_t chan );
+
+  void set_time_source(const std::string &source, const size_t mboard = 0);
+  std::string get_time_source(const size_t mboard);
+  std::vector<std::string> get_time_sources(const size_t mboard);
+  void set_clock_source(const std::string &source, const size_t mboard = 0);
+  std::string get_clock_source(const size_t mboard);
+  std::vector<std::string> get_clock_sources(const size_t mboard);
+  double get_clock_rate(size_t mboard = 0);
+  void set_clock_rate(double rate, size_t mboard = 0);
+  ::osmosdr::time_spec_t get_time_now(size_t mboard = 0);
+  ::osmosdr::time_spec_t get_time_last_pps(size_t mboard = 0);
+  void set_time_now(const ::osmosdr::time_spec_t &time_spec, size_t mboard = 0);
+  void set_time_next_pps(const ::osmosdr::time_spec_t &time_spec);
+  void set_time_unknown_pps(const ::osmosdr::time_spec_t &time_spec);
+
+private:
+  void msg_handler_command(pmt::pmt_t msg);
+  void exec_msg_cmd(const pmt::pmt_t &cmd, const pmt::pmt_t &val, size_t chan);
+
+  struct dev_t {
+    common_iface * dev;
+    gr::basic_block_sptr block;
+    size_t chan;
+  };
+
+  std::vector< dev_t > _devs;
+  std::vector< size_t > _chandevs;
+  gr::hier_block2_sptr _outer;
+  size_t _channels;
+
+  /* cache to prevent multiple device calls with the same value coming from grc */
+  double _sample_rate;
+  std::map< size_t, double > _center_freq;
+  std::map< size_t, double > _freq_corr;
+  std::map< size_t, bool > _gain_mode;
+  std::map< size_t, double > _gain;
+  std::map< size_t, double > _if_gain;
+  std::map< size_t, double > _bb_gain;
+  std::map< size_t, std::string > _antenna;
+#ifdef HAVE_IQBALANCE
+  std::vector< gr::iqbalance::fix_cc * > _iq_fix;
+  std::vector< gr::iqbalance::optimize_c * > _iq_opt;
+  std::map< size_t, std::pair<float, float> > _vals;
+#endif
+  std::map< size_t, double > _bandwidth;
+};
+
+#endif /* INCLUDED_DEV_CONTROL_H */
diff --git a/lib/sink_iface.h b/lib/sink_iface.h
index 39aabc7..f28e88b 100644
--- a/lib/sink_iface.h
+++ b/lib/sink_iface.h
@@ -25,344 +25,14 @@
 #include <osmosdr/time_spec.h>
 #include <gnuradio/basic_block.h>
 
+#include "common_iface.h"
+
 /*!
  * TODO: document
  *
  */
-class sink_iface
+class sink_iface : public common_iface
 {
-public:
-  /*!
-   * Get the number of channels the underlying radio hardware offers.
-   * \return the number of available channels
-   */
-  virtual size_t get_num_channels( void ) = 0;
-
-  /*!
-   * Get the possible sample rates for the underlying radio hardware.
-   * \return a range of rates in Sps
-   */
-  virtual osmosdr::meta_range_t get_sample_rates( void ) = 0;
-
-  /*!
-   * Set the sample rate for the underlying radio hardware.
-   * This also will select the appropriate IF bandpass, if applicable.
-   * \param rate a new rate in Sps
-   */
-  virtual double set_sample_rate( double rate ) = 0;
-
-  /*!
-   * Get the sample rate for the underlying radio hardware.
-   * This is the actual sample rate and may differ from the rate set.
-   * \return the actual rate in Sps
-   */
-  virtual double get_sample_rate( void ) = 0;
-
-  /*!
-   * Get the tunable frequency range for the underlying radio hardware.
-   * \param chan the channel index 0 to N-1
-   * \return the frequency range in Hz
-   */
-  virtual osmosdr::freq_range_t get_freq_range( size_t chan = 0 ) = 0;
-
-  /*!
-   * Tune the underlying radio hardware to the desired center frequency.
-   * This also will select the appropriate RF bandpass.
-   * \param freq the desired frequency in Hz
-   * \param chan the channel index 0 to N-1
-   * \return the actual frequency in Hz
-   */
-  virtual double set_center_freq( double freq, size_t chan = 0 ) = 0;
-
-  /*!
-   * Get the center frequency the underlying radio hardware is tuned to.
-   * This is the actual frequency and may differ from the frequency set.
-   * \param chan the channel index 0 to N-1
-   * \return the frequency in Hz
-   */
-  virtual double get_center_freq( size_t chan = 0 ) = 0;
-
-  /*!
-   * Set the frequency correction value in parts per million.
-   * \param ppm the desired correction value in parts per million
-   * \param chan the channel index 0 to N-1
-   * \return correction value in parts per million
-   */
-  virtual double set_freq_corr( double ppm, size_t chan = 0 ) = 0;
-
-  /*!
-   * Get the frequency correction value.
-   * \param chan the channel index 0 to N-1
-   * \return correction value in parts per million
-   */
-  virtual double get_freq_corr( size_t chan = 0 ) = 0;
-
-  /*!
-   * Get the gain stage names of the underlying radio hardware.
-   * \param chan the channel index 0 to N-1
-   * \return a vector of strings containing the names of gain stages
-   */
-  virtual std::vector<std::string> get_gain_names( size_t chan = 0 ) = 0;
-
-  /*!
-   * Get the settable overall gain range for the underlying radio hardware.
-   * \param chan the channel index 0 to N-1
-   * \return the gain range in dB
-   */
-  virtual osmosdr::gain_range_t get_gain_range( size_t chan = 0 ) = 0;
-
-  /*!
-   * Get the settable gain range for a specific gain stage.
-   * \param name the name of the gain stage
-   * \param chan the channel index 0 to N-1
-   * \return the gain range in dB
-   */
-  virtual osmosdr::gain_range_t get_gain_range( const std::string & name,
-                                                size_t chan = 0 ) = 0;
-
-  /*!
-   * Set the gain mode for the underlying radio hardware.
-   * This might be supported only for certain hardware types.
-   * \param automatic the gain mode (true means automatic gain mode)
-   * \param chan the channel index 0 to N-1
-   * \return the actual gain mode
-   */
-  virtual bool set_gain_mode( bool automatic, size_t chan = 0 ) { return false; }
-
-  /*!
-   * Get the gain mode selected for the underlying radio hardware.
-   * \param chan the channel index 0 to N-1
-   * \return the actual gain mode (true means automatic gain mode)
-   */
-  virtual bool get_gain_mode( size_t chan = 0 ) { return false; }
-
-  /*!
-   * Set the gain for the underlying radio hardware.
-   * This function will automatically distribute the desired gain value over
-   * available gain stages in an appropriate way and return the actual value.
-   * \param gain the gain in dB
-   * \param chan the channel index 0 to N-1
-   * \return the actual gain in dB
-   */
-  virtual double set_gain( double gain, size_t chan = 0 ) = 0;
-
-  /*!
-   * Set the named gain on the underlying radio hardware.
-   * \param gain the gain in dB
-   * \param name the name of the gain stage
-   * \param chan the channel index 0 to N-1
-   * \return the actual gain in dB
-   */
-  virtual double set_gain( double gain,
-                           const std::string & name,
-                           size_t chan = 0 ) = 0;
-
-  /*!
-   * Get the actual gain setting of the underlying radio hardware.
-   * \param chan the channel index 0 to N-1
-   * \return the actual gain in dB
-   */
-  virtual double get_gain( size_t chan = 0 ) = 0;
-
-  /*!
-   * Get the actual gain setting of a named stage.
-   * \param name the name of the gain stage
-   * \param chan the channel index 0 to N-1
-   * \return the actual gain in dB
-   */
-  virtual double get_gain( const std::string & name, size_t chan = 0 ) = 0;
-
-  /*!
-   * Set the IF gain for the underlying radio hardware.
-   * This function will automatically distribute the desired gain value over
-   * available IF gain stages in an appropriate way and return the actual value.
-   * \param gain the gain in dB
-   * \param chan the channel index 0 to N-1
-   * \return the actual gain in dB
-   */
-  virtual double set_if_gain( double gain, size_t chan = 0 ) { return 0; }
-
-  /*!
-   * Set the BB gain for the underlying radio hardware.
-   * This function will automatically distribute the desired gain value over
-   * available BB gain stages in an appropriate way and return the actual value.
-   * \param gain the gain in dB
-   * \param chan the channel index 0 to N-1
-   * \return the actual gain in dB
-   */
-  virtual double set_bb_gain( double gain, size_t chan = 0 ) { return 0; }
-
-  /*!
-   * Get the available antennas of the underlying radio hardware.
-   * \param chan the channel index 0 to N-1
-   * \return a vector of strings containing the names of available antennas
-   */
-  virtual std::vector< std::string > get_antennas( size_t chan = 0 ) = 0;
-
-  /*!
-   * Select the active antenna of the underlying radio hardware.
-   * \param chan the channel index 0 to N-1
-   * \return the actual antenna's name
-   */
-  virtual std::string set_antenna( const std::string & antenna,
-                                   size_t chan = 0 ) = 0;
-
-  /*!
-   * Get the actual underlying radio hardware antenna setting.
-   * \param chan the channel index 0 to N-1
-   * \return the actual antenna's name
-   */
-  virtual std::string get_antenna( size_t chan = 0 ) = 0;
-
-  /*!
-   * Set the TX frontend DC offset value.
-   * The value is complex to control both I and Q.
-   *
-   * \param offset the dc offset (1.0 is full-scale)
-   * \param chan the channel index 0 to N-1
-   */
-  virtual void set_dc_offset( const std::complex<double> &offset, size_t chan = 0 ) { }
-
-  /*!
-   * Set the TX frontend IQ balance correction.
-   * Use this to adjust the magnitude and phase of I and Q.
-   *
-   * \param balance the complex correction value
-   * \param chan the channel index 0 to N-1
-   */
-  virtual void set_iq_balance( const std::complex<double> &balance, size_t chan = 0 ) { }
-
-  /*!
-   * Set the bandpass filter on the radio frontend.
-   * \param bandwidth the filter bandwidth in Hz, set to 0 for automatic selection
-   * \param chan the channel index 0 to N-1
-   * \return the actual filter bandwidth in Hz
-   */
-  virtual double set_bandwidth( double bandwidth, size_t chan = 0 ) { return 0; }
-
-  /*!
-   * Get the actual bandpass filter setting on the radio frontend.
-   * \param chan the channel index 0 to N-1
-   * \return the actual filter bandwidth in Hz
-   */
-  virtual double get_bandwidth( size_t chan = 0 ) { return 0; }
-
-  /*!
-   * Get the possible bandpass filter settings on the radio frontend.
-   * \param chan the channel index 0 to N-1
-   * \return a range of bandwidths in Hz
-   */
-  virtual osmosdr::freq_range_t get_bandwidth_range( size_t chan = 0 )
-    { return osmosdr::freq_range_t(); }
-
-  /*!
-   * Set the time source for the device.
-   * This sets the method of time synchronization,
-   * typically a pulse per second or an encoded time.
-   * Typical options for source: external, MIMO.
-   * \param source a string representing the time source
-   * \param mboard which motherboard to set the config
-   */
-  virtual void set_time_source(const std::string &source,
-                               const size_t mboard = 0) { }
-
-  /*!
-   * Get the currently set time source.
-   * \param mboard which motherboard to get the config
-   * \return the string representing the time source
-   */
-  virtual std::string get_time_source(const size_t mboard) { return ""; }
-
-  /*!
-   * Get a list of possible time sources.
-   * \param mboard which motherboard to get the list
-   * \return a vector of strings for possible settings
-   */
-  virtual std::vector<std::string> get_time_sources(const size_t mboard)
-  {
-    return std::vector<std::string>();
-  }
-
-  /*!
-   * Set the clock source for the device.
-   * This sets the source for a 10 Mhz reference clock.
-   * Typical options for source: internal, external, MIMO.
-   * \param source a string representing the clock source
-   * \param mboard which motherboard to set the config
-   */
-  virtual void set_clock_source(const std::string &source,
-                                const size_t mboard = 0) { }
-
-  /*!
-   * Get the currently set clock source.
-   * \param mboard which motherboard to get the config
-   * \return the string representing the clock source
-   */
-  virtual std::string get_clock_source(const size_t mboard) { return ""; }
-
-  /*!
-   * Get a list of possible clock sources.
-   * \param mboard which motherboard to get the list
-   * \return a vector of strings for possible settings
-   */
-  virtual std::vector<std::string> get_clock_sources(const size_t mboard)
-  {
-    return std::vector<std::string>();
-  }
-
-  /*!
-   * Get the master clock rate.
-   * \param mboard the motherboard index 0 to M-1
-   * \return the clock rate in Hz
-   */
-  virtual double get_clock_rate(size_t mboard = 0) { return 0; }
-
-  /*!
-   * Set the master clock rate.
-   * \param rate the new rate in Hz
-   * \param mboard the motherboard index 0 to M-1
-   */
-  virtual void set_clock_rate(double rate, size_t mboard = 0) { }
-
-  /*!
-   * Get the current time registers.
-   * \param mboard the motherboard index 0 to M-1
-   * \return the current device time
-   */
-  virtual ::osmosdr::time_spec_t get_time_now(size_t mboard = 0)
-  {
-    return ::osmosdr::time_spec_t::get_system_time();
-  }
-
-  /*!
-   * Get the time when the last pps pulse occured.
-   * \param mboard the motherboard index 0 to M-1
-   * \return the current device time
-   */
-  virtual ::osmosdr::time_spec_t get_time_last_pps(size_t mboard = 0)
-  {
-    return ::osmosdr::time_spec_t::get_system_time();
-  }
-
-  /*!
-   * Sets the time registers immediately.
-   * \param time_spec the new time
-   * \param mboard the motherboard index 0 to M-1
-   */
-  virtual void set_time_now(const ::osmosdr::time_spec_t &time_spec,
-                            size_t mboard = 0) { }
-
-  /*!
-   * Set the time registers at the next pps.
-   * \param time_spec the new time
-   */
-  virtual void set_time_next_pps(const ::osmosdr::time_spec_t &time_spec) { }
-
-  /*!
-   * Sync the time registers with an unknown pps edge.
-   * \param time_spec the new time
-   */
-  virtual void set_time_unknown_pps(const ::osmosdr::time_spec_t &time_spec) { }
 };
 
 #endif // OSMOSDR_SINK_IFACE_H
diff --git a/lib/sink_impl.cc b/lib/sink_impl.cc
index 285638b..cb0588a 100644
--- a/lib/sink_impl.cc
+++ b/lib/sink_impl.cc
@@ -78,11 +78,14 @@ sink_impl::sink_impl( const std::string &args )
   : gr::hier_block2 ("sink_impl",
         args_to_io_signature(args),
         gr::io_signature::make(0, 0, 0)),
-    _sample_rate(NAN)
+    _manager(make_dev_manager(to_hier_block2()))
 {
   size_t channel = 0;
   bool device_specified = false;
 
+  message_port_register_hier_in( osmosdr::CMD_PORT );
+  msg_connect( self(), osmosdr::CMD_PORT, _manager, osmosdr::CMD_PORT );
+
   std::vector< std::string > arg_list = args_to_vector(args);
 
   std::vector< std::string > dev_types;
@@ -224,18 +227,10 @@ sink_impl::sink_impl( const std::string &args )
     }
 #endif
 
-    if ( iface != NULL && long(block.get()) != 0 ) {
-      _devs.push_back( iface );
-
-      for (size_t i = 0; i < iface->get_num_channels(); i++) {
-        connect(self(), channel++, block, i);
-      }
-    } else if ( (iface != NULL) || (long(block.get()) != 0) )
-      throw std::runtime_error("Either iface or block are NULL.");
-
+    _manager->add_device( block, iface );
   }
 
-  if (!_devs.size())
+  if (!_manager->get_num_mboards())
     throw std::runtime_error("No devices specified via device arguments.");
 #ifdef WORKAROUND_GR_HIER_BLOCK2_BUG
   } catch ( std::exception &ex ) {
@@ -270,448 +265,205 @@ sink_impl::sink_impl( const std::string &args )
 
 size_t sink_impl::get_num_channels()
 {
-  size_t channels = 0;
-
-  BOOST_FOREACH( sink_iface *dev, _devs )
-    channels += dev->get_num_channels();
-
-  return channels;
+  return _manager->get_num_channels();
 }
 
-#define NO_DEVICES_MSG  "FATAL: No device(s) available to work with."
-
 osmosdr::meta_range_t sink_impl::get_sample_rates()
 {
-  if ( ! _devs.empty() )
-    return _devs[0]->get_sample_rates(); // assume same devices used in the group
-#if 0
-  else
-    throw std::runtime_error(NO_DEVICES_MSG);
-#endif
-  return osmosdr::meta_range_t();
+  return _manager->get_sample_rates();
 }
 
-double sink_impl::set_sample_rate(double rate)
+double sink_impl::set_sample_rate( double rate )
 {
-  double sample_rate = 0;
-
-  if (_sample_rate != rate) {
-#if 0
-    if (_devs.empty())
-      throw std::runtime_error(NO_DEVICES_MSG);
-#endif
-    BOOST_FOREACH( sink_iface *dev, _devs )
-      sample_rate = dev->set_sample_rate(rate);
-
-    _sample_rate = sample_rate;
-  }
-
-  return sample_rate;
+  return _manager->set_sample_rate( rate );
 }
 
 double sink_impl::get_sample_rate()
 {
-  double sample_rate = 0;
-
-  if (!_devs.empty())
-    sample_rate = _devs[0]->get_sample_rate(); // assume same devices used in the group
-#if 0
-  else
-    throw std::runtime_error(NO_DEVICES_MSG);
-#endif
-  return sample_rate;
+  return _manager->get_sample_rate();
 }
 
 osmosdr::freq_range_t sink_impl::get_freq_range( size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( sink_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ )
-        return dev->get_freq_range( dev_chan );
-
-  return osmosdr::freq_range_t();
+  return _manager->get_freq_range( chan );
 }
 
 double sink_impl::set_center_freq( double freq, size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( sink_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ ) {
-        if ( _center_freq[ chan ] != freq ) {
-          _center_freq[ chan ] = freq;
-          return dev->set_center_freq( freq, dev_chan );
-        } else { return _center_freq[ chan ]; }
-      }
-
-  return 0;
+  return _manager->set_center_freq( freq, chan );
 }
 
 double sink_impl::get_center_freq( size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( sink_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ )
-        return dev->get_center_freq( dev_chan );
-
-  return 0;
+  return _manager->get_center_freq( chan );
 }
 
 double sink_impl::set_freq_corr( double ppm, size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( sink_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ ) {
-        if ( _freq_corr[ chan ] != ppm ) {
-          _freq_corr[ chan ] = ppm;
-          return dev->set_freq_corr( ppm, dev_chan );
-        } else { return _freq_corr[ chan ]; }
-      }
-
-  return 0;
+  return _manager->set_freq_corr( ppm, chan );
 }
 
 double sink_impl::get_freq_corr( size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( sink_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ )
-        return dev->get_freq_corr( dev_chan );
-
-  return 0;
+  return _manager->get_freq_corr( chan );
 }
 
 std::vector<std::string> sink_impl::get_gain_names( size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( sink_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ )
-        return dev->get_gain_names( dev_chan );
-
-  return std::vector< std::string >();
+  return _manager->get_gain_names( chan );
 }
 
 osmosdr::gain_range_t sink_impl::get_gain_range( size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( sink_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ )
-        return dev->get_gain_range( dev_chan );
-
-  return osmosdr::gain_range_t();
+  return _manager->get_gain_range( chan );
 }
 
 osmosdr::gain_range_t sink_impl::get_gain_range( const std::string & name, size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( sink_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ )
-        return dev->get_gain_range( name, dev_chan );
-
-  return osmosdr::gain_range_t();
+  return _manager->get_gain_range( name, chan );
 }
 
 bool sink_impl::set_gain_mode( bool automatic, size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( sink_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ ) {
-        if ( _gain_mode[ chan ] != automatic ) {
-          _gain_mode[ chan ] = automatic;
-          bool mode = dev->set_gain_mode( automatic, dev_chan );
-          if (!automatic) // reapply gain value when switched to manual mode
-            dev->set_gain( _gain[ chan ], dev_chan );
-          return mode;
-        } else { return _gain_mode[ chan ]; }
-      }
-
-  return false;
+  return _manager->set_gain_mode( automatic, chan );
 }
 
 bool sink_impl::get_gain_mode( size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( sink_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ )
-        return dev->get_gain_mode( dev_chan );
-
-  return false;
+  return _manager->get_gain_mode( chan );
 }
 
 double sink_impl::set_gain( double gain, size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( sink_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ ) {
-        if ( _gain[ chan ] != gain ) {
-          _gain[ chan ] = gain;
-          return dev->set_gain( gain, dev_chan );
-        } else { return _gain[ chan ]; }
-      }
-
-  return 0;
+  return _manager->set_gain( gain, chan );
 }
 
 double sink_impl::set_gain( double gain, const std::string & name, size_t chan)
 {
-  size_t channel = 0;
-  BOOST_FOREACH( sink_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ )
-        return dev->set_gain( gain, name, dev_chan );
-
-  return 0;
+  return _manager->set_gain( gain, name, chan );
 }
 
 double sink_impl::get_gain( size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( sink_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ )
-        return dev->get_gain( dev_chan );
-
-  return 0;
+  return _manager->get_gain( chan );
 }
 
 double sink_impl::get_gain( const std::string & name, size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( sink_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ )
-        return dev->get_gain( name, dev_chan );
-
-  return 0;
+  return _manager->get_gain( name, chan );
 }
 
 double sink_impl::set_if_gain( double gain, size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( sink_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ ) {
-        if ( _if_gain[ chan ] != gain ) {
-          _if_gain[ chan ] = gain;
-          return dev->set_if_gain( gain, dev_chan );
-        } else { return _if_gain[ chan ]; }
-      }
-
-  return 0;
+  return _manager->set_if_gain( gain, chan );
 }
 
 double sink_impl::set_bb_gain( double gain, size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( sink_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ ) {
-        if ( _bb_gain[ chan ] != gain ) {
-          _bb_gain[ chan ] = gain;
-          return dev->set_bb_gain( gain, dev_chan );
-        } else { return _bb_gain[ chan ]; }
-      }
-
-  return 0;
+  return _manager->set_bb_gain( gain, chan );
 }
 
 std::vector< std::string > sink_impl::get_antennas( size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( sink_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ )
-        return dev->get_antennas( dev_chan );
-
-  return std::vector< std::string >();
+  return _manager->get_antennas( chan );
 }
 
 std::string sink_impl::set_antenna( const std::string & antenna, size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( sink_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ ) {
-        if ( _antenna[ chan ] != antenna ) {
-          _antenna[ chan ] = antenna;
-          return dev->set_antenna( antenna, dev_chan );
-        } else { return _antenna[ chan ]; }
-      }
-
-  return "";
+  return _manager->set_antenna( antenna, chan );
 }
 
 std::string sink_impl::get_antenna( size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( sink_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ )
-        return dev->get_antenna( dev_chan );
-
-  return "";
+  return _manager->get_antenna( chan );
 }
 
 void sink_impl::set_dc_offset( const std::complex<double> &offset, size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( sink_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ )
-        dev->set_dc_offset( offset, dev_chan );
+  _manager->set_dc_offset( offset, chan );
 }
 
 void sink_impl::set_iq_balance( const std::complex<double> &balance, size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( sink_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ )
-        dev->set_iq_balance( balance, dev_chan );
+  _manager->set_iq_balance( balance, chan );
 }
 
 double sink_impl::set_bandwidth( double bandwidth, size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( sink_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ ) {
-        if ( _bandwidth[ chan ] != bandwidth || 0.0f == bandwidth ) {
-          _bandwidth[ chan ] = bandwidth;
-          return dev->set_bandwidth( bandwidth, dev_chan );
-        } else { return _bandwidth[ chan ]; }
-      }
-
-  return 0;
+  return _manager->set_bandwidth( bandwidth, chan );
 }
 
 double sink_impl::get_bandwidth( size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( sink_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ )
-        return dev->get_bandwidth( dev_chan );
-
-  return 0;
+  return _manager->get_bandwidth( chan );
 }
 
 osmosdr::freq_range_t sink_impl::get_bandwidth_range( size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( sink_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ )
-        return dev->get_bandwidth_range( dev_chan );
-
-  return osmosdr::freq_range_t();
+  return _manager->get_bandwidth_range( chan );
 }
 
-void sink_impl::set_time_source(const std::string &source, const size_t mboard)
+void sink_impl::set_time_source( const std::string &source, const size_t mboard )
 {
-  if (mboard != osmosdr::ALL_MBOARDS){
-      _devs.at(mboard)->set_time_source( source );
-      return;
-  }
-
-  for (size_t m = 0; m < _devs.size(); m++){ /* propagate ALL_MBOARDS */
-      _devs.at(m)->set_time_source( source, osmosdr::ALL_MBOARDS );
-  }
+  return _manager->set_time_source( source, mboard );
 }
 
-std::string sink_impl::get_time_source(const size_t mboard)
+std::string sink_impl::get_time_source( const size_t mboard )
 {
-  return _devs.at(mboard)->get_time_source( mboard );
+  return _manager->get_time_source( mboard );
 }
 
-std::vector<std::string> sink_impl::get_time_sources(const size_t mboard)
+std::vector<std::string> sink_impl::get_time_sources( const size_t mboard )
 {
-  return _devs.at(mboard)->get_time_sources( mboard );
+  return _manager->get_time_sources( mboard );
 }
 
-void sink_impl::set_clock_source(const std::string &source, const size_t mboard)
+void sink_impl::set_clock_source( const std::string &source, const size_t mboard )
 {
-  if (mboard != osmosdr::ALL_MBOARDS){
-      _devs.at(mboard)->set_clock_source( source );
-      return;
-  }
-
-  for (size_t m = 0; m < _devs.size(); m++){ /* propagate ALL_MBOARDS */
-      _devs.at(m)->set_clock_source( source, osmosdr::ALL_MBOARDS );
-  }
+  return _manager->set_clock_source( source, mboard );
 }
 
-std::string sink_impl::get_clock_source(const size_t mboard)
+std::string sink_impl::get_clock_source( const size_t mboard )
 {
-  return _devs.at(mboard)->get_clock_source( mboard );
+  return _manager->get_clock_source( mboard );
 }
 
-std::vector<std::string> sink_impl::get_clock_sources(const size_t mboard)
+std::vector<std::string> sink_impl::get_clock_sources( const size_t mboard )
 {
-  return _devs.at(mboard)->get_clock_sources( mboard );
+  return _manager->get_clock_sources( mboard );
 }
 
-double sink_impl::get_clock_rate(size_t mboard)
+double sink_impl::get_clock_rate( size_t mboard )
 {
-  return _devs.at(mboard)->get_clock_rate( mboard );
+  return _manager->get_clock_rate( mboard );
 }
 
-void sink_impl::set_clock_rate(double rate, size_t mboard)
+void sink_impl::set_clock_rate( double rate, size_t mboard )
 {
-  if (mboard != osmosdr::ALL_MBOARDS){
-      _devs.at(mboard)->set_clock_rate( rate );
-      return;
-  }
-
-  for (size_t m = 0; m < _devs.size(); m++){ /* propagate ALL_MBOARDS */
-      _devs.at(m)->set_clock_rate( rate, osmosdr::ALL_MBOARDS );
-  }
+  return _manager->set_clock_rate( rate, mboard );
 }
 
-osmosdr::time_spec_t sink_impl::get_time_now(size_t mboard)
+osmosdr::time_spec_t sink_impl::get_time_now( size_t mboard )
 {
-  return _devs.at(mboard)->get_time_now( mboard );
+  return _manager->get_time_now( mboard );
 }
 
-osmosdr::time_spec_t sink_impl::get_time_last_pps(size_t mboard)
+osmosdr::time_spec_t sink_impl::get_time_last_pps( size_t mboard )
 {
-  return _devs.at(mboard)->get_time_last_pps( mboard );
+  return _manager->get_time_last_pps( mboard );
 }
 
-void sink_impl::set_time_now(const osmosdr::time_spec_t &time_spec, size_t mboard)
+void sink_impl::set_time_now( const osmosdr::time_spec_t &time_spec, size_t mboard )
 {
-  if (mboard != osmosdr::ALL_MBOARDS){
-      _devs.at(mboard)->set_time_now( time_spec );
-      return;
-  }
-
-  for (size_t m = 0; m < _devs.size(); m++){ /* propagate ALL_MBOARDS */
-      _devs.at(m)->set_time_now( time_spec, osmosdr::ALL_MBOARDS );
-  }
+  return _manager->set_time_now( time_spec, mboard );
 }
 
-void sink_impl::set_time_next_pps(const osmosdr::time_spec_t &time_spec)
+void sink_impl::set_time_next_pps( const osmosdr::time_spec_t &time_spec )
 {
-  BOOST_FOREACH( sink_iface *dev, _devs )
-  {
-    dev->set_time_next_pps( time_spec );
-  }
+  return _manager->set_time_next_pps( time_spec );
 }
 
-void sink_impl::set_time_unknown_pps(const osmosdr::time_spec_t &time_spec)
+void sink_impl::set_time_unknown_pps( const osmosdr::time_spec_t &time_spec )
 {
-  BOOST_FOREACH( sink_iface *dev, _devs )
-  {
-    dev->set_time_unknown_pps( time_spec );
-  }
+  return _manager->set_time_unknown_pps( time_spec );
 }
diff --git a/lib/sink_impl.h b/lib/sink_impl.h
index 1642669..a4589b5 100644
--- a/lib/sink_impl.h
+++ b/lib/sink_impl.h
@@ -22,7 +22,7 @@
 
 #include "osmosdr/sink.h"
 
-#include "sink_iface.h"
+#include "dev_manager.h"
 
 #include <map>
 
@@ -83,18 +83,7 @@ public:
   void set_time_unknown_pps(const ::osmosdr::time_spec_t &time_spec);
 
 private:
-  std::vector< sink_iface * > _devs;
-
-  /* cache to prevent multiple device calls with the same value coming from grc */
-  double _sample_rate;
-  std::map< size_t, double > _center_freq;
-  std::map< size_t, double > _freq_corr;
-  std::map< size_t, bool > _gain_mode;
-  std::map< size_t, double > _gain;
-  std::map< size_t, double > _if_gain;
-  std::map< size_t, double > _bb_gain;
-  std::map< size_t, std::string > _antenna;
-  std::map< size_t, double > _bandwidth;
+  dev_manager_sptr _manager;
 };
 
 #endif /* INCLUDED_OSMOSDR_SINK_IMPL_H */
diff --git a/lib/source_iface.h b/lib/source_iface.h
index abb70eb..ff0149f 100644
--- a/lib/source_iface.h
+++ b/lib/source_iface.h
@@ -25,19 +25,15 @@
 #include <osmosdr/time_spec.h>
 #include <gnuradio/basic_block.h>
 
+#include "common_iface.h"
+
 /*!
  * TODO: document
  *
  */
-class source_iface
+class source_iface : public common_iface
 {
 public:
-  /*!
-   * Get the number of channels the underlying radio hardware offers.
-   * \return the number of available channels
-   */
-  virtual size_t get_num_channels( void ) = 0;
-
   /*!
    * \brief seek file to \p seek_point relative to \p whence
    *
@@ -47,182 +43,6 @@ public:
    */
   virtual bool seek( long seek_point, int whence, size_t chan = 0 ) { return false; }
 
-  /*!
-   * Get the possible sample rates for the underlying radio hardware.
-   * \return a range of rates in Sps
-   */
-  virtual osmosdr::meta_range_t get_sample_rates( void ) = 0;
-
-  /*!
-   * Set the sample rate for the underlying radio hardware.
-   * This also will select the appropriate IF bandpass, if applicable.
-   * \param rate a new rate in Sps
-   */
-  virtual double set_sample_rate( double rate ) = 0;
-
-  /*!
-   * Get the sample rate for the underlying radio hardware.
-   * This is the actual sample rate and may differ from the rate set.
-   * \return the actual rate in Sps
-   */
-  virtual double get_sample_rate( void ) = 0;
-
-  /*!
-   * Get the tunable frequency range for the underlying radio hardware.
-   * \param chan the channel index 0 to N-1
-   * \return the frequency range in Hz
-   */
-  virtual osmosdr::freq_range_t get_freq_range( size_t chan = 0 ) = 0;
-
-  /*!
-   * Tune the underlying radio hardware to the desired center frequency.
-   * This also will select the appropriate RF bandpass.
-   * \param freq the desired frequency in Hz
-   * \param chan the channel index 0 to N-1
-   * \return the actual frequency in Hz
-   */
-  virtual double set_center_freq( double freq, size_t chan = 0 ) = 0;
-
-  /*!
-   * Get the center frequency the underlying radio hardware is tuned to.
-   * This is the actual frequency and may differ from the frequency set.
-   * \param chan the channel index 0 to N-1
-   * \return the frequency in Hz
-   */
-  virtual double get_center_freq( size_t chan = 0 ) = 0;
-
-  /*!
-   * Set the frequency correction value in parts per million.
-   * \param ppm the desired correction value in parts per million
-   * \param chan the channel index 0 to N-1
-   * \return correction value in parts per million
-   */
-  virtual double set_freq_corr( double ppm, size_t chan = 0 ) = 0;
-
-  /*!
-   * Get the frequency correction value.
-   * \param chan the channel index 0 to N-1
-   * \return correction value in parts per million
-   */
-  virtual double get_freq_corr( size_t chan = 0 ) = 0;
-
-  /*!
-   * Get the gain stage names of the underlying radio hardware.
-   * \param chan the channel index 0 to N-1
-   * \return a vector of strings containing the names of gain stages
-   */
-  virtual std::vector<std::string> get_gain_names( size_t chan = 0 ) = 0;
-
-  /*!
-   * Get the settable overall gain range for the underlying radio hardware.
-   * \param chan the channel index 0 to N-1
-   * \return the gain range in dB
-   */
-  virtual osmosdr::gain_range_t get_gain_range( size_t chan = 0 ) = 0;
-
-  /*!
-   * Get the settable gain range for a specific gain stage.
-   * \param name the name of the gain stage
-   * \param chan the channel index 0 to N-1
-   * \return the gain range in dB
-   */
-  virtual osmosdr::gain_range_t get_gain_range( const std::string & name,
-                                                size_t chan = 0 ) = 0;
-
-  /*!
-   * Set the gain mode for the underlying radio hardware.
-   * This might be supported only for certain hardware types.
-   * \param automatic the gain mode (true means automatic gain mode)
-   * \param chan the channel index 0 to N-1
-   * \return the actual gain mode
-   */
-  virtual bool set_gain_mode( bool automatic, size_t chan = 0 ) { return false; }
-
-  /*!
-   * Get the gain mode selected for the underlying radio hardware.
-   * \param chan the channel index 0 to N-1
-   * \return the actual gain mode (true means automatic gain mode)
-   */
-  virtual bool get_gain_mode( size_t chan = 0 ) { return false; }
-
-  /*!
-   * Set the gain for the underlying radio hardware.
-   * This function will automatically distribute the desired gain value over
-   * available gain stages in an appropriate way and return the actual value.
-   * \param gain the gain in dB
-   * \param chan the channel index 0 to N-1
-   * \return the actual gain in dB
-   */
-  virtual double set_gain( double gain, size_t chan = 0 ) = 0;
-
-  /*!
-   * Set the named gain on the underlying radio hardware.
-   * \param gain the gain in dB
-   * \param name the name of the gain stage
-   * \param chan the channel index 0 to N-1
-   * \return the actual gain in dB
-   */
-  virtual double set_gain( double gain,
-                           const std::string & name,
-                           size_t chan = 0 ) = 0;
-
-  /*!
-   * Get the actual gain setting of the underlying radio hardware.
-   * \param chan the channel index 0 to N-1
-   * \return the actual gain in dB
-   */
-  virtual double get_gain( size_t chan = 0 ) = 0;
-
-  /*!
-   * Get the actual gain setting of a named stage.
-   * \param name the name of the gain stage
-   * \param chan the channel index 0 to N-1
-   * \return the actual gain in dB
-   */
-  virtual double get_gain( const std::string & name, size_t chan = 0 ) = 0;
-
-  /*!
-   * Set the IF gain for the underlying radio hardware.
-   * This function will automatically distribute the desired gain value over
-   * available IF gain stages in an appropriate way and return the actual value.
-   * \param gain the gain in dB
-   * \param chan the channel index 0 to N-1
-   * \return the actual gain in dB
-   */
-  virtual double set_if_gain( double gain, size_t chan = 0 ) { return 0; }
-
-  /*!
-   * Set the BB gain for the underlying radio hardware.
-   * This function will automatically distribute the desired gain value over
-   * available BB gain stages in an appropriate way and return the actual value.
-   * \param gain the gain in dB
-   * \param chan the channel index 0 to N-1
-   * \return the actual gain in dB
-   */
-  virtual double set_bb_gain( double gain, size_t chan = 0 ) { return 0; }
-
-  /*!
-   * Get the available antennas of the underlying radio hardware.
-   * \param chan the channel index 0 to N-1
-   * \return a vector of strings containing the names of available antennas
-   */
-  virtual std::vector< std::string > get_antennas( size_t chan = 0 ) = 0;
-
-  /*!
-   * Select the active antenna of the underlying radio hardware.
-   * \param chan the channel index 0 to N-1
-   * \return the actual antenna's name
-   */
-  virtual std::string set_antenna( const std::string & antenna,
-                                   size_t chan = 0 ) = 0;
-
-  /*!
-   * Get the actual underlying radio hardware antenna setting.
-   * \param chan the channel index 0 to N-1
-   * \return the actual antenna's name
-   */
-  virtual std::string get_antenna( size_t chan = 0 ) = 0;
-
   /*!
    * Set the RX frontend DC correction mode.
    * The automatic correction subtracts out the long-run average.
@@ -237,16 +57,6 @@ public:
    */
   virtual void set_dc_offset_mode( int mode, size_t chan = 0 ) { }
 
-  /*!
-   * Set a constant DC offset value.
-   * The value is complex to control both I and Q.
-   * Only set this when automatic correction is disabled.
-   *
-   * \param offset the dc offset (1.0 is full-scale)
-   * \param chan the channel index 0 to N-1
-   */
-  virtual void set_dc_offset( const std::complex<double> &offset, size_t chan = 0 ) { }
-
   /*!
    * Set the RX frontend IQ balance mode.
    *
@@ -254,147 +64,6 @@ public:
    * \param chan the channel index 0 to N-1
    */
   virtual void set_iq_balance_mode( int mode, size_t chan = 0 ) { }
-
-  /*!
-   * Set the RX frontend IQ balance correction.
-   * Use this to adjust the magnitude and phase of I and Q.
-   *
-   * \param balance the complex correction value
-   * \param chan the channel index 0 to N-1
-   */
-  virtual void set_iq_balance( const std::complex<double> &balance, size_t chan = 0 ) { }
-
-  /*!
-   * Set the bandpass filter on the radio frontend.
-   * \param bandwidth the filter bandwidth in Hz, set to 0 for automatic selection
-   * \param chan the channel index 0 to N-1
-   * \return the actual filter bandwidth in Hz
-   */
-  virtual double set_bandwidth( double bandwidth, size_t chan = 0 ) { return 0; }
-
-  /*!
-   * Get the actual bandpass filter setting on the radio frontend.
-   * \param chan the channel index 0 to N-1
-   * \return the actual filter bandwidth in Hz
-   */
-  virtual double get_bandwidth( size_t chan = 0 ) { return 0; }
-
-  /*!
-   * Get the possible bandpass filter settings on the radio frontend.
-   * \param chan the channel index 0 to N-1
-   * \return a range of bandwidths in Hz
-   */
-  virtual osmosdr::freq_range_t get_bandwidth_range( size_t chan = 0 )
-    { return osmosdr::freq_range_t(); }
-
-  /*!
-   * Set the time source for the device.
-   * This sets the method of time synchronization,
-   * typically a pulse per second or an encoded time.
-   * Typical options for source: external, MIMO.
-   * \param source a string representing the time source
-   * \param mboard which motherboard to set the config
-   */
-  virtual void set_time_source(const std::string &source,
-                               const size_t mboard = 0) { }
-
-  /*!
-   * Get the currently set time source.
-   * \param mboard which motherboard to get the config
-   * \return the string representing the time source
-   */
-  virtual std::string get_time_source(const size_t mboard) { return ""; }
-
-  /*!
-   * Get a list of possible time sources.
-   * \param mboard which motherboard to get the list
-   * \return a vector of strings for possible settings
-   */
-  virtual std::vector<std::string> get_time_sources(const size_t mboard)
-  {
-    return std::vector<std::string>();
-  }
-
-  /*!
-   * Set the clock source for the device.
-   * This sets the source for a 10 Mhz reference clock.
-   * Typical options for source: internal, external, MIMO.
-   * \param source a string representing the clock source
-   * \param mboard which motherboard to set the config
-   */
-  virtual void set_clock_source(const std::string &source,
-                                const size_t mboard = 0) { }
-
-  /*!
-   * Get the currently set clock source.
-   * \param mboard which motherboard to get the config
-   * \return the string representing the clock source
-   */
-  virtual std::string get_clock_source(const size_t mboard) { return ""; }
-
-  /*!
-   * Get a list of possible clock sources.
-   * \param mboard which motherboard to get the list
-   * \return a vector of strings for possible settings
-   */
-  virtual std::vector<std::string> get_clock_sources(const size_t mboard)
-  {
-    return std::vector<std::string>();
-  }
-
-  /*!
-   * Get the master clock rate.
-   * \param mboard the motherboard index 0 to M-1
-   * \return the clock rate in Hz
-   */
-  virtual double get_clock_rate(size_t mboard = 0) { return 0; }
-
-  /*!
-   * Set the master clock rate.
-   * \param rate the new rate in Hz
-   * \param mboard the motherboard index 0 to M-1
-   */
-  virtual void set_clock_rate(double rate, size_t mboard = 0) { }
-
-  /*!
-   * Get the current time registers.
-   * \param mboard the motherboard index 0 to M-1
-   * \return the current device time
-   */
-  virtual ::osmosdr::time_spec_t get_time_now(size_t mboard = 0)
-  {
-    return ::osmosdr::time_spec_t::get_system_time();
-  }
-
-  /*!
-   * Get the time when the last pps pulse occured.
-   * \param mboard the motherboard index 0 to M-1
-   * \return the current device time
-   */
-  virtual ::osmosdr::time_spec_t get_time_last_pps(size_t mboard = 0)
-  {
-    return ::osmosdr::time_spec_t::get_system_time();
-  }
-
-  /*!
-   * Sets the time registers immediately.
-   * \param time_spec the new time
-   * \param mboard the motherboard index 0 to M-1
-   */
-  virtual void set_time_now(const ::osmosdr::time_spec_t &time_spec,
-                            size_t mboard = 0) { }
-
-  /*!
-   * Set the time registers at the next pps.
-   * \param time_spec the new time
-   */
-  virtual void set_time_next_pps(const ::osmosdr::time_spec_t &time_spec) { }
-
-  /*!
-   * Sync the time registers with an unknown pps edge.
-   * \param time_spec the new time
-   */
-  virtual void set_time_unknown_pps(const ::osmosdr::time_spec_t &time_spec) { }
 };
 
 #endif // OSMOSDR_SOURCE_IFACE_H
diff --git a/lib/source_impl.cc b/lib/source_impl.cc
index a28f314..a8d5534 100644
--- a/lib/source_impl.cc
+++ b/lib/source_impl.cc
@@ -117,11 +117,14 @@ source_impl::source_impl( const std::string &args )
   : gr::hier_block2 ("source_impl",
         gr::io_signature::make(0, 0, 0),
         args_to_io_signature(args)),
-    _sample_rate(NAN)
+    _manager(make_dev_manager(to_hier_block2()))
 {
   size_t channel = 0;
   bool device_specified = false;
 
+  message_port_register_hier_in( osmosdr::CMD_PORT );
+  msg_connect( self(), osmosdr::CMD_PORT, _manager, osmosdr::CMD_PORT );
+
   std::vector< std::string > arg_list = args_to_vector(args);
 
   std::vector< std::string > dev_types;
@@ -383,32 +386,10 @@ source_impl::source_impl( const std::string &args )
     }
 #endif
 
-    if ( iface != NULL && long(block.get()) != 0 ) {
-      _devs.push_back( iface );
-
-      for (size_t i = 0; i < iface->get_num_channels(); i++) {
-#ifdef HAVE_IQBALANCE
-        gr::iqbalance::optimize_c::sptr iq_opt = gr::iqbalance::optimize_c::make( 0 );
-        gr::iqbalance::fix_cc::sptr     iq_fix = gr::iqbalance::fix_cc::make();
-
-        connect(block, i, iq_fix, 0);
-        connect(iq_fix, 0, self(), channel++);
-
-        connect(block, i, iq_opt, 0);
-        msg_connect(iq_opt, "iqbal_corr", iq_fix, "iqbal_corr");
-
-        _iq_opt.push_back( iq_opt.get() );
-        _iq_fix.push_back( iq_fix.get() );
-#else
-        connect(block, i, self(), channel++);
-#endif
-      }
-    } else if ( (iface != NULL) || (long(block.get()) != 0) )
-      throw std::runtime_error("Either iface or block are NULL.");
-
+    _manager->add_device( block, iface );
   }
 
-  if (!_devs.size())
+  if (!_manager->get_num_mboards())
     throw std::runtime_error("No devices specified via device arguments.");
 #ifdef WORKAROUND_GR_HIER_BLOCK2_BUG
   } catch ( std::exception &ex ) {
@@ -443,545 +424,220 @@ source_impl::source_impl( const std::string &args )
 
 size_t source_impl::get_num_channels()
 {
-  size_t channels = 0;
-
-  BOOST_FOREACH( source_iface *dev, _devs )
-    channels += dev->get_num_channels();
-
-  return channels;
+  return _manager->get_num_channels();
 }
 
 bool source_impl::seek( long seek_point, int whence, size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( source_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ )
-        return dev->seek( seek_point, whence, dev_chan );
-
-  return false;
+  return _manager->seek( seek_point, whence, chan );
 }
 
-#define NO_DEVICES_MSG  "FATAL: No device(s) available to work with."
-
 osmosdr::meta_range_t source_impl::get_sample_rates()
 {
-  if ( ! _devs.empty() )
-    return _devs[0]->get_sample_rates(); // assume same devices used in the group
-#if 0
-  else
-    throw std::runtime_error(NO_DEVICES_MSG);
-#endif
-  return osmosdr::meta_range_t();;
+  return _manager->get_sample_rates();
 }
 
-double source_impl::set_sample_rate(double rate)
+double source_impl::set_sample_rate( double rate )
 {
-  double sample_rate = 0;
-
-  if (_sample_rate != rate) {
-#if 0
-    if (_devs.empty())
-      throw std::runtime_error(NO_DEVICES_MSG);
-#endif
-    BOOST_FOREACH( source_iface *dev, _devs )
-      sample_rate = dev->set_sample_rate(rate);
-
-#ifdef HAVE_IQBALANCE
-    size_t channel = 0;
-    BOOST_FOREACH( source_iface *dev, _devs ) {
-      for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++) {
-        if ( channel < _iq_opt.size() ) {
-          gr::iqbalance::optimize_c *opt = _iq_opt[channel];
-
-          if ( opt->period() > 0 ) { /* optimize is enabled */
-            opt->set_period( dev->get_sample_rate() / 5 );
-            opt->reset();
-          }
-        }
-
-        channel++;
-      }
-    }
-#endif
-
-    _sample_rate = sample_rate;
-  }
-
-  return sample_rate;
+  return _manager->set_sample_rate(rate);
 }
 
 double source_impl::get_sample_rate()
 {
-  double sample_rate = 0;
-
-  if (!_devs.empty())
-    sample_rate = _devs[0]->get_sample_rate(); // assume same devices used in the group
-#if 0
-  else
-    throw std::runtime_error(NO_DEVICES_MSG);
-#endif
-  return sample_rate;
+  return _manager->get_sample_rate();
 }
 
 osmosdr::freq_range_t source_impl::get_freq_range( size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( source_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ )
-        return dev->get_freq_range( dev_chan );
-
-  return osmosdr::freq_range_t();
+  return _manager->get_freq_range( chan );
 }
 
 double source_impl::set_center_freq( double freq, size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( source_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ ) {
-        if ( _center_freq[ chan ] != freq ) {
-          _center_freq[ chan ] = freq;
-          return dev->set_center_freq( freq, dev_chan );
-        } else { return _center_freq[ chan ]; }
-      }
-
-  return 0;
+  return _manager->set_center_freq( freq, chan );
 }
 
 double source_impl::get_center_freq( size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( source_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ )
-        return dev->get_center_freq( dev_chan );
-
-  return 0;
+  return _manager->get_center_freq( chan );
 }
 
 double source_impl::set_freq_corr( double ppm, size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( source_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ ) {
-        if ( _freq_corr[ chan ] != ppm ) {
-          _freq_corr[ chan ] = ppm;
-          return dev->set_freq_corr( ppm, dev_chan );
-        } else { return _freq_corr[ chan ]; }
-      }
-
-  return 0;
+  return _manager->set_freq_corr( ppm, chan );
 }
 
 double source_impl::get_freq_corr( size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( source_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ )
-        return dev->get_freq_corr( dev_chan );
-
-  return 0;
+  return _manager->get_freq_corr( chan );
 }
 
 std::vector<std::string> source_impl::get_gain_names( size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( source_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ )
-        return dev->get_gain_names( dev_chan );
-
-  return std::vector< std::string >();
+  return _manager->get_gain_names( chan );
 }
 
 osmosdr::gain_range_t source_impl::get_gain_range( size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( source_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ )
-        return dev->get_gain_range( dev_chan );
-
-  return osmosdr::gain_range_t();
+  return _manager->get_gain_range( chan );
 }
 
 osmosdr::gain_range_t source_impl::get_gain_range( const std::string & name, size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( source_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ )
-        return dev->get_gain_range( name, dev_chan );
-
-  return osmosdr::gain_range_t();
+  return _manager->get_gain_range( name, chan );
 }
 
 bool source_impl::set_gain_mode( bool automatic, size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( source_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ ) {
-        if ( _gain_mode[ chan ] != automatic ) {
-          _gain_mode[ chan ] = automatic;
-          bool mode = dev->set_gain_mode( automatic, dev_chan );
-          if (!automatic) // reapply gain value when switched to manual mode
-            dev->set_gain( _gain[ chan ], dev_chan );
-          return mode;
-        } else { return _gain_mode[ chan ]; }
-      }
-
-  return false;
+  return _manager->set_gain_mode( automatic, chan );
 }
 
 bool source_impl::get_gain_mode( size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( source_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ )
-        return dev->get_gain_mode( dev_chan );
-
-  return false;
+  return _manager->get_gain_mode( chan );
 }
 
 double source_impl::set_gain( double gain, size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( source_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ ) {
-        if ( _gain[ chan ] != gain ) {
-          _gain[ chan ] = gain;
-          return dev->set_gain( gain, dev_chan );
-        } else { return _gain[ chan ]; }
-      }
-
-  return 0;
+  return _manager->set_gain( gain, chan );
 }
 
 double source_impl::set_gain( double gain, const std::string & name, size_t chan)
 {
-  size_t channel = 0;
-  BOOST_FOREACH( source_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ )
-        return dev->set_gain( gain, name, dev_chan );
-
-  return 0;
+  return _manager->set_gain( gain, name, chan );
 }
 
 double source_impl::get_gain( size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( source_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ )
-        return dev->get_gain( dev_chan );
-
-  return 0;
+  return _manager->get_gain( chan );
 }
 
 double source_impl::get_gain( const std::string & name, size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( source_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ )
-        return dev->get_gain( name, dev_chan );
-
-  return 0;
+  return _manager->get_gain( name, chan );
 }
 
 double source_impl::set_if_gain( double gain, size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( source_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ ) {
-        if ( _if_gain[ chan ] != gain ) {
-          _if_gain[ chan ] = gain;
-          return dev->set_if_gain( gain, dev_chan );
-        } else { return _if_gain[ chan ]; }
-      }
-
-  return 0;
+  return _manager->set_if_gain( gain, chan );
 }
 
 double source_impl::set_bb_gain( double gain, size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( source_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ ) {
-        if ( _bb_gain[ chan ] != gain ) {
-          _bb_gain[ chan ] = gain;
-          return dev->set_bb_gain( gain, dev_chan );
-        } else { return _bb_gain[ chan ]; }
-      }
-
-  return 0;
+  return _manager->set_bb_gain( gain, chan );
 }
 
 std::vector< std::string > source_impl::get_antennas( size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( source_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ )
-        return dev->get_antennas( dev_chan );
-
-  return std::vector< std::string >();
+  return _manager->get_antennas( chan );
 }
 
 std::string source_impl::set_antenna( const std::string & antenna, size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( source_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ ) {
-        if ( _antenna[ chan ] != antenna ) {
-          _antenna[ chan ] = antenna;
-          return dev->set_antenna( antenna, dev_chan );
-        } else { return _antenna[ chan ]; }
-      }
-
-  return "";
+  return _manager->set_antenna( antenna, chan );
 }
 
 std::string source_impl::get_antenna( size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( source_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ )
-        return dev->get_antenna( dev_chan );
-
-  return "";
+  return _manager->get_antenna( chan );
 }
 
 void source_impl::set_dc_offset_mode( int mode, size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( source_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ )
-        dev->set_dc_offset_mode( mode, dev_chan );
+  _manager->set_dc_offset_mode( mode, chan );
 }
 
 void source_impl::set_dc_offset( const std::complex<double> &offset, size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( source_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ )
-        dev->set_dc_offset( offset, dev_chan );
+  _manager->set_dc_offset( offset, chan );
 }
 
 void source_impl::set_iq_balance_mode( int mode, size_t chan )
 {
-  size_t channel = 0;
-#ifdef HAVE_IQBALANCE
-  BOOST_FOREACH( source_iface *dev, _devs ) {
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++) {
-      if ( chan == channel++ ) {
-        if ( chan < _iq_opt.size() && chan < _iq_fix.size() ) {
-          gr::iqbalance::optimize_c *opt = _iq_opt[chan];
-          gr::iqbalance::fix_cc *fix = _iq_fix[chan];
-
-          if ( IQBalanceOff == mode  ) {
-            opt->set_period( 0 );
-            /* store current values in order to be able to restore them later */
-            _vals[ chan ] = std::pair< float, float >( fix->mag(), fix->phase() );
-            fix->set_mag( 0.0f );
-            fix->set_phase( 0.0f );
-          } else if ( IQBalanceManual == mode ) {
-            if ( opt->period() == 0 ) { /* transition from Off to Manual */
-              /* restore previous values */
-              std::pair< float, float > val = _vals[ chan ];
-              fix->set_mag( val.first );
-              fix->set_phase( val.second );
-            }
-            opt->set_period( 0 );
-          } else if ( IQBalanceAutomatic == mode ) {
-            opt->set_period( dev->get_sample_rate() / 5 );
-            opt->reset();
-          }
-        }
-      }
-    }
-  }
-#else
-  BOOST_FOREACH( source_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ )
-        return dev->set_iq_balance_mode( mode, dev_chan );
-#endif
+  _manager->set_iq_balance_mode( mode, chan );
 }
 
 void source_impl::set_iq_balance( const std::complex<double> &balance, size_t chan )
 {
-  size_t channel = 0;
-#ifdef HAVE_IQBALANCE
-  BOOST_FOREACH( source_iface *dev, _devs ) {
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++) {
-      if ( chan == channel++ ) {
-        if ( chan < _iq_opt.size() && chan < _iq_fix.size() ) {
-          gr::iqbalance::optimize_c *opt = _iq_opt[chan];
-          gr::iqbalance::fix_cc *fix = _iq_fix[chan];
-
-          if ( opt->period() == 0 ) { /* automatic optimization desabled */
-            fix->set_mag( balance.real() );
-            fix->set_phase( balance.imag() );
-          }
-        }
-      }
-    }
-  }
-#else
-  BOOST_FOREACH( source_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ )
-        return dev->set_iq_balance( balance, dev_chan );
-#endif
+  _manager->set_iq_balance( balance, chan );
 }
 
 double source_impl::set_bandwidth( double bandwidth, size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( source_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ ) {
-        if ( _bandwidth[ chan ] != bandwidth || 0.0f == bandwidth ) {
-          _bandwidth[ chan ] = bandwidth;
-          return dev->set_bandwidth( bandwidth, dev_chan );
-        } else { return _bandwidth[ chan ]; }
-      }
-
-  return 0;
+  return _manager->set_bandwidth( bandwidth, chan );
 }
 
 double source_impl::get_bandwidth( size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( source_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ )
-        return dev->get_bandwidth( dev_chan );
-
-  return 0;
+  return _manager->get_bandwidth( chan );
 }
 
 osmosdr::freq_range_t source_impl::get_bandwidth_range( size_t chan )
 {
-  size_t channel = 0;
-  BOOST_FOREACH( source_iface *dev, _devs )
-    for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++)
-      if ( chan == channel++ )
-        return dev->get_bandwidth_range( dev_chan );
-
-  return osmosdr::freq_range_t();
+  return _manager->get_bandwidth_range( chan );
 }
 
 void source_impl::set_time_source(const std::string &source, const size_t mboard)
 {
-  if (mboard != osmosdr::ALL_MBOARDS){
-      _devs.at(mboard)->set_time_source( source );
-      return;
-  }
-
-  for (size_t m = 0; m < _devs.size(); m++){ /* propagate ALL_MBOARDS */
-      _devs.at(m)->set_time_source( source, osmosdr::ALL_MBOARDS );
-  }
+  return _manager->set_time_source( source, mboard );
 }
 
 std::string source_impl::get_time_source(const size_t mboard)
 {
-  return _devs.at(mboard)->get_time_source( mboard );
+  return _manager->get_time_source( mboard );
 }
 
 std::vector<std::string> source_impl::get_time_sources(const size_t mboard)
 {
-  return _devs.at(mboard)->get_time_sources( mboard );
+  return _manager->get_time_sources( mboard );
 }
 
 void source_impl::set_clock_source(const std::string &source, const size_t mboard)
 {
-  if (mboard != osmosdr::ALL_MBOARDS){
-      _devs.at(mboard)->set_clock_source( source );
-      return;
-  }
-
-  for (size_t m = 0; m < _devs.size(); m++){ /* propagate ALL_MBOARDS */
-      _devs.at(m)->set_clock_source( source, osmosdr::ALL_MBOARDS );
-  }
+  return _manager->set_clock_source( source, mboard );
 }
 
 std::string source_impl::get_clock_source(const size_t mboard)
 {
-  return _devs.at(mboard)->get_clock_source( mboard );
+  return _manager->get_clock_source( mboard );
 }
 
 std::vector<std::string> source_impl::get_clock_sources(const size_t mboard)
 {
-  return _devs.at(mboard)->get_clock_sources( mboard );
+  return _manager->get_clock_sources( mboard );
 }
 
 double source_impl::get_clock_rate(size_t mboard)
 {
-  return _devs.at(mboard)->get_clock_rate( mboard );
+  return _manager->get_clock_rate( mboard );
 }
 
 void source_impl::set_clock_rate(double rate, size_t mboard)
 {
-  if (mboard != osmosdr::ALL_MBOARDS){
-      _devs.at(mboard)->set_clock_rate( rate );
-      return;
-  }
-
-  for (size_t m = 0; m < _devs.size(); m++){ /* propagate ALL_MBOARDS */
-      _devs.at(m)->set_clock_rate( rate, osmosdr::ALL_MBOARDS );
-  }
+  return _manager->set_clock_rate( rate, mboard );
 }
 
 osmosdr::time_spec_t source_impl::get_time_now(size_t mboard)
 {
-  return _devs.at(mboard)->get_time_now( mboard );
+  return _manager->get_time_now( mboard );
 }
 
 osmosdr::time_spec_t source_impl::get_time_last_pps(size_t mboard)
 {
-  return _devs.at(mboard)->get_time_last_pps( mboard );
+  return _manager->get_time_last_pps( mboard );
 }
 
 void source_impl::set_time_now(const osmosdr::time_spec_t &time_spec, size_t mboard)
 {
-  if (mboard != osmosdr::ALL_MBOARDS){
-      _devs.at(mboard)->set_time_now( time_spec );
-      return;
-  }
-
-  for (size_t m = 0; m < _devs.size(); m++){ /* propagate ALL_MBOARDS */
-      _devs.at(m)->set_time_now( time_spec, osmosdr::ALL_MBOARDS );
-  }
+  return _manager->set_time_now( time_spec, mboard );
 }
 
 void source_impl::set_time_next_pps(const osmosdr::time_spec_t &time_spec)
 {
-  BOOST_FOREACH( source_iface *dev, _devs )
-  {
-    dev->set_time_next_pps( time_spec );
-  }
+  return _manager->set_time_next_pps( time_spec );
 }
 
 void source_impl::set_time_unknown_pps(const osmosdr::time_spec_t &time_spec)
 {
-  BOOST_FOREACH( source_iface *dev, _devs )
-  {
-    dev->set_time_unknown_pps( time_spec );
-  }
+  return _manager->set_time_unknown_pps( time_spec );
 }
diff --git a/lib/source_impl.h b/lib/source_impl.h
index 4b65125..238bc6c 100644
--- a/lib/source_impl.h
+++ b/lib/source_impl.h
@@ -27,7 +27,7 @@
 #include <gnuradio/iqbalance/fix_cc.h>
 #endif
 
-#include <source_iface.h>
+#include "dev_manager.h"
 
 #include <map>
 
@@ -92,23 +92,7 @@ public:
   void set_time_unknown_pps(const ::osmosdr::time_spec_t &time_spec);
 
 private:
-  std::vector< source_iface * > _devs;
-
-  /* cache to prevent multiple device calls with the same value coming from grc */
-  double _sample_rate;
-  std::map< size_t, double > _center_freq;
-  std::map< size_t, double > _freq_corr;
-  std::map< size_t, bool > _gain_mode;
-  std::map< size_t, double > _gain;
-  std::map< size_t, double > _if_gain;
-  std::map< size_t, double > _bb_gain;
-  std::map< size_t, std::string > _antenna;
-#ifdef HAVE_IQBALANCE
-  std::vector< gr::iqbalance::fix_cc * > _iq_fix;
-  std::vector< gr::iqbalance::optimize_c * > _iq_opt;
-  std::map< size_t, std::pair<float, float> > _vals;
-#endif
-  std::map< size_t, double > _bandwidth;
+  dev_manager_sptr _manager;
 };
 
 #endif /* INCLUDED_OSMOSDR_SOURCE_IMPL_H */
diff --git a/lib/uhd/uhd_sink_c.cc b/lib/uhd/uhd_sink_c.cc
index a154556..fdac2a6 100644
--- a/lib/uhd/uhd_sink_c.cc
+++ b/lib/uhd/uhd_sink_c.cc
@@ -24,6 +24,8 @@
 
 //#include <uhd/property_tree.hpp>
 
+#include <osmosdr/messages.h>
+
 #include "arg_helpers.h"
 
 #include "uhd_sink_c.h"
@@ -124,6 +126,9 @@ uhd_sink_c::uhd_sink_c(const std::string &args) :
 #endif
   for ( size_t i = 0; i < nchan; i++ )
     connect( self(), i, _snk, i );
+  
+  message_port_register_hier_in( osmosdr::CMD_PORT );
+  msg_connect( self(), osmosdr::CMD_PORT, _snk, osmosdr::CMD_PORT );
 }
 
 uhd_sink_c::~uhd_sink_c()
diff --git a/lib/uhd/uhd_source_c.cc b/lib/uhd/uhd_source_c.cc
index fc13017..98fd5f1 100644
--- a/lib/uhd/uhd_source_c.cc
+++ b/lib/uhd/uhd_source_c.cc
@@ -24,6 +24,8 @@
 
 //#include <uhd/property_tree.hpp>
 
+#include <osmosdr/messages.h>
+
 #include "arg_helpers.h"
 
 #include "uhd_source_c.h"
@@ -125,6 +127,9 @@ uhd_source_c::uhd_source_c(const std::string &args) :
 #endif
   for ( size_t i = 0; i < nchan; i++ )
     connect( _src, i, self(), i );
+  
+  message_port_register_hier_in( osmosdr::CMD_PORT );
+  msg_connect( self(), osmosdr::CMD_PORT, _src, osmosdr::CMD_PORT );
 }
 
 uhd_source_c::~uhd_source_c()
-- 
2.15.1

