Index: libs/libmythtv/dvbdevtree.cpp
===================================================================
--- libs/libmythtv/dvbdevtree.cpp	(revision 0)
+++ libs/libmythtv/dvbdevtree.cpp	(revision 0)
@@ -0,0 +1,1845 @@
+/*
+ * \file dvbdevtree.cpp
+ * \brief DVB-S Device Tree Control Classes.
+ * \author Copyright (C) 2006, Yeasah Pell
+ */
+
+#include <sys/time.h>
+#include <string.h>
+#include <cmath>
+#include "mythcontext.h"
+#include "mythdbcon.h"
+#include "dvbtypes.h"
+#include "dvbdevtree.h"
+
+#define LOC     QString("DVBDevTree: ")
+#define LOC_ERR QString("DVBDevTree: ")
+
+//////////////////////////////////////// DVBDevSettings
+
+DVBDevSettings::DVBDevSettings()
+    : m_input_id((unsigned int)-1)
+{
+}
+
+bool DVBDevSettings::Load(unsigned int card_input_id)
+{
+    if(card_input_id != m_input_id)
+    {
+        m_config.clear();
+        
+        // load settings from DB
+        MSqlQuery query(MSqlQuery::InitCon());
+        query.prepare("SELECT dtv_dev_id, value "
+                      "FROM dtv_device_config "
+                      "WHERE cardinputid = :INPUTID");
+        query.bindValue(":INPUTID", card_input_id);
+        if(query.exec() && query.isActive())
+            while(query.next())
+                m_config[query.value(0).toInt()] = query.value(1).toDouble();
+        
+        m_input_id = card_input_id;
+    }
+
+    return true;
+}
+
+bool DVBDevSettings::Store(unsigned int card_input_id) const
+{
+    {
+        // clear out previous settings
+        MSqlQuery query(MSqlQuery::InitCon());
+        query.prepare("DELETE from dtv_device_config"
+                      " WHERE cardinputid = :INPUTID");
+        query.bindValue(":INPUTID", card_input_id);
+        if(!query.exec())
+            return false;
+    }
+
+    {
+        // insert new settings
+        MSqlQuery query(MSqlQuery::InitCon());
+        query.prepare("INSERT INTO dtv_device_config"
+                      " (cardinputid, dtv_dev_id, value) VALUES"
+                      " (:INPUTID, :DEV_ID, :VALUE)");
+        for(CONFIG::const_iterator i = m_config.begin(); i != m_config.end(); i++)
+        {
+            query.bindValue(":INPUTID", card_input_id);
+            query.bindValue(":DEV_ID", i->first);
+            query.bindValue(":VALUE", i->second);
+            if(!query.exec())
+                return false;
+        }
+    }
+
+    return true;
+}
+
+double DVBDevSettings::GetValue(int dtv_dev_id) const
+{
+    double ret = 0.0;
+    CONFIG::const_iterator cfg = m_config.find(dtv_dev_id);
+    if(cfg != m_config.end())
+        ret = cfg->second;
+    return ret;
+}
+
+void DVBDevSettings::SetValue(int dtv_dev_id, double value)
+{
+    m_config[dtv_dev_id] = value;
+    m_input_id = (unsigned int)-1;
+}
+
+//////////////////////////////////////// DVBDev
+
+DVBDevTree* DVBDev::FindTree(unsigned int card_id)
+{
+    return m_trees.FindTree(card_id);
+}
+
+void DVBDev::InvalidateTrees()
+{
+    m_trees.InvalidateTrees();
+}
+
+DVBDevTrees DVBDev::m_trees;
+
+//////////////////////////////////////// DVBDevTrees
+
+DVBDevTrees::~DVBDevTrees()
+{
+    InvalidateTrees();
+}
+
+DVBDevTree* DVBDevTrees::FindTree(unsigned int card_id)
+{
+    QMutexLocker lock(&m_trees_lock);
+
+    DVBDevTree* tree = NULL;
+    TREES::iterator i = m_trees.find(card_id);
+    if(i == m_trees.end())
+    {
+        tree = new DVBDevTree;
+        tree->Load(card_id);
+        m_trees.insert(std::make_pair(card_id, tree));
+    }
+    else
+        tree = i->second;
+    return tree;
+}
+
+void DVBDevTrees::InvalidateTrees()
+{
+    QMutexLocker lock(&m_trees_lock);
+    for(TREES::iterator i = m_trees.begin(); i != m_trees.end(); i++)
+        delete i->second;
+    m_trees.clear();
+}
+
+//////////////////////////////////////// DVBDevTree
+
+DVBDevTree::DVBDevTree()
+    : m_fd_frontend(-1), m_root(NULL), m_next_cnt(0)
+{
+    Reset();
+}
+
+DVBDevTree::~DVBDevTree()
+{
+    delete m_root;
+}
+
+bool DVBDevTree::Load(unsigned int card_id)
+{
+    // clear children
+    delete m_root;
+    m_delete.clear();
+    m_root = NULL;
+
+    // lookup configuration for this card
+    MSqlQuery query(MSqlQuery::InitCon());
+    query.prepare("SELECT dtv_dev_id FROM capturecard "
+                  "WHERE cardid = :CARDID");
+    query.bindValue(":CARDID", card_id);
+
+    if(query.exec() && query.next())
+        m_root = DVBDevDevice::CreateById(*this, query.value(0).toInt());
+    else
+        VERBOSE(VB_IMPORTANT, LOC_ERR +
+                QString("No device tree for cardid %1").arg(card_id));
+
+    return m_root != NULL;
+}
+
+bool DVBDevTree::Store(unsigned int card_id)
+{
+    // apply pending node deletions
+    if(!m_delete.empty())
+    {
+        MSqlQuery query(MSqlQuery::InitCon());
+        query.prepare("DELETE FROM dtv_device_tree WHERE dtv_dev_id = :DEVID");
+        MSqlQuery squery(MSqlQuery::InitCon());
+        squery.prepare("DELETE FROM dtv_device_config WHERE dtv_dev_id = :DEVID");
+        for(list<unsigned int>::const_iterator i = m_delete.begin(); i != m_delete.end(); i++)
+        {
+            query.bindValue(":DEVID", *i);
+            query.exec();
+            squery.bindValue(":DEVID", *i);
+            squery.exec();
+        }
+        m_delete.clear();
+    }
+
+    // store changed and new nodes
+    bool success = true;
+    if(m_root)
+        success = m_root->Store();
+    MSqlQuery query(MSqlQuery::InitCon());
+    query.prepare("UPDATE capturecard"
+                  " SET dtv_dev_id = :DEVID"
+                  " WHERE cardid = :CARDID");
+    if(m_root)
+        query.bindValue(":DEVID", m_root->DeviceID());
+    query.bindValue(":CARDID", card_id);
+    return success && query.exec();
+}
+
+bool DVBDevTree::SetTone(bool on)
+{
+    bool success = false;
+    for(unsigned int retry = 0; !success && retry < TIMEOUT_RETRIES; retry++)
+    {
+        if (ioctl(m_fd_frontend, FE_SET_TONE, 
+                  on ? SEC_TONE_ON : SEC_TONE_OFF) == 0)
+            success = true;
+        else
+            usleep(DISEQC_SHORT_WAIT);
+    }
+    if(!success)
+        VERBOSE(VB_IMPORTANT, LOC_ERR + "FE_SET_TONE failed" + ENO);
+    return success;
+}
+
+bool DVBDevTree::MiniDiseqc(fe_sec_mini_cmd cmd)
+{
+    bool success = false;
+    for(unsigned int retry = 0; !success && retry < TIMEOUT_RETRIES; retry++)
+    {
+        if (ioctl(m_fd_frontend, FE_DISEQC_SEND_BURST, cmd) == 0)
+            success = true;
+        else
+            usleep(DISEQC_SHORT_WAIT);
+    }
+    if(!success)
+        VERBOSE(VB_IMPORTANT, LOC_ERR + "FE_DISEQC_SEND_BURST failed" + ENO);
+    return success;
+}
+
+bool DVBDevTree::SendDiseqc(const dvb_diseqc_master_cmd& cmd)
+{
+    bool success = false;
+    for(unsigned int retry = 0; !success && retry < TIMEOUT_RETRIES; retry++)
+    {
+        if (ioctl(m_fd_frontend, FE_DISEQC_SEND_MASTER_CMD, &cmd) == 0) 
+            success = true;
+        else
+            usleep(DISEQC_SHORT_WAIT);
+    }
+    if(!success)
+        VERBOSE(VB_IMPORTANT, LOC_ERR + 
+                "FE_DISEQC_SEND_MASTER_CMD failed" + ENO);
+    return success;
+}
+
+bool DVBDevTree::Execute(const DVBDevSettings& settings,
+                         const DVBTuning& tuning)
+{
+    if(m_root)
+    {
+        // apply any voltage change
+        ApplyVoltage(settings, tuning);
+
+        // turn off tone burst first if commands need to be sent
+        if(m_root->NeedsCommand(settings))
+        {
+            SetTone(false);
+            usleep(DISEQC_SHORT_WAIT);
+        }
+
+        return m_root->Execute(settings, tuning);
+    }
+    else
+    {
+        VERBOSE(VB_IMPORTANT, LOC_ERR + "No root device tree node!");
+        return false;
+    }
+}
+
+void DVBDevTree::Reset()
+{
+    if(m_root)
+        m_root->Reset();
+    m_last_voltage = (fe_sec_voltage)-1;
+}
+
+DVBDevRotor* DVBDevTree::FindRotor(const DVBDevSettings& settings,
+                                   unsigned int index)
+{
+    DVBDevRotor* rotor = NULL;
+    DVBDevDevice* node = m_root;
+    unsigned int count = 0;
+    while(node)
+    {
+        rotor = dynamic_cast<DVBDevRotor*>(node);
+        if(rotor && ++count > index)
+            break;
+        node = node->SelectedChild(settings);
+    }
+    return rotor;
+}
+
+DVBDevLnb* DVBDevTree::FindLNB(const DVBDevSettings& settings)
+{
+    DVBDevLnb* lnb = NULL;
+    DVBDevDevice* node = m_root;
+    while(node)
+    {
+        lnb = dynamic_cast<DVBDevLnb*>(node);
+        if(lnb)
+            break;
+        node = node->SelectedChild(settings);
+    }
+    return lnb;
+}
+
+DVBDevDevice* DVBDevTree::FindDevice(int dev_id)
+{
+    DVBDevDevice* dev = NULL;
+    if(m_root)
+        dev = m_root->FindDevice(dev_id);
+    return dev;
+}
+
+void DVBDevTree::SetRoot(DVBDevDevice* root)
+{
+    delete m_root;
+    m_root = root;
+}
+
+bool DVBDevTree::SendCommand(unsigned int adr,
+                             unsigned int cmd,
+                             unsigned int repeats,
+                             unsigned int data_len,
+                             unsigned char* data)
+{
+    // check payload validity
+    if(data_len > 3 || (data_len > 0 && data == NULL))
+    {
+        VERBOSE(VB_IMPORTANT, LOC_ERR + "Bad DiSEqC command");
+        return false;
+    }
+
+    // prepare command
+    dvb_diseqc_master_cmd mcmd;
+    mcmd.msg[0] = DISEQC_FRM;
+    mcmd.msg[1] = adr;
+    mcmd.msg[2] = cmd;
+    mcmd.msg_len = data_len + 3;
+    if(data_len > 0)
+        memcpy(mcmd.msg+3, data, data_len);
+
+    // diagnostic
+    QString cmdstr;
+    for(unsigned int byte = 0; byte < mcmd.msg_len; byte++)
+        cmdstr += QString("%1 ").arg(mcmd.msg[byte], 2, 16);
+    VERBOSE(VB_CHANNEL, LOC + "Sending DiSEqC Command: " + cmdstr);
+
+    // send the command
+    for(unsigned int i = 0; i <= repeats; i++)
+    {
+        if (!SendDiseqc(mcmd))
+        {
+            VERBOSE(VB_IMPORTANT, LOC_ERR + "DiSEqC command failed" + ENO);
+            return false;
+        }
+        mcmd.msg[0] |= DISEQC_FRM_REPEAT;
+        usleep(DISEQC_SHORT_WAIT);
+    }
+
+    return true;
+}
+
+bool DVBDevTree::ResetDiseqc(bool hard_reset)
+{
+    Reset();
+
+    VERBOSE(VB_CHANNEL, LOC + "Resetting DiSEqC Bus");
+
+    // issue a global reset command
+    if(!SendCommand(DISEQC_ADR_ALL, DISEQC_CMD_RESET))
+    {
+        VERBOSE(VB_IMPORTANT, LOC_ERR +
+                "DiSEqC reset failed" + ENO);
+        return false;
+    }
+    usleep(DISEQC_LONG_WAIT);
+
+    // power cycle the bus if requested
+    if(hard_reset)
+    {
+        VERBOSE(VB_CHANNEL, LOC + "Power-cycling DiSEqC Bus");
+        SetVoltage(SEC_VOLTAGE_OFF);
+        usleep(DISEQC_LONG_WAIT);
+        SetVoltage(SEC_VOLTAGE_18);
+        usleep(DISEQC_LONG_WAIT);
+    }
+
+    return true;
+}
+
+void DVBDevTree::Open(int fd_frontend)
+{
+    m_fd_frontend = fd_frontend;
+    ResetDiseqc(true);
+}
+
+bool DVBDevTree::SetVoltage(fe_sec_voltage voltage)
+{
+    bool success = true;
+    if(voltage != m_last_voltage)
+    {
+        int volts = 0;
+        if(voltage == SEC_VOLTAGE_18)
+            volts = 18;
+        else if(voltage == SEC_VOLTAGE_13)
+            volts = 13;
+        VERBOSE(VB_CHANNEL, LOC + 
+                QString("Changing LNB voltage to %1V").arg(volts));
+
+        success = false;
+        for(unsigned int retry = 0; !success && retry < TIMEOUT_RETRIES; retry++)
+        {
+            if (ioctl(m_fd_frontend, FE_SET_VOLTAGE, voltage) == 0)
+                success = true;
+            else
+                usleep(DISEQC_SHORT_WAIT);
+        }
+
+        if(!success)
+            VERBOSE(VB_IMPORTANT, LOC_ERR + "FE_SET_VOLTAGE failed" + ENO);
+        else
+            m_last_voltage = voltage;
+    }
+
+    return success;
+}
+
+bool DVBDevTree::ApplyVoltage(const DVBDevSettings& settings,
+                              const DVBTuning& tuning)
+{
+    fe_sec_voltage voltage = SEC_VOLTAGE_18;
+    if(m_root)
+        voltage = m_root->GetVoltage(settings, tuning);
+    return SetVoltage(voltage);
+}
+
+//////////////////////////////////////// DVBDevDevice
+
+DVBDevDevice::DVBDevDevice(DVBDevTree& tree, int dtv_dev_id)
+    : m_dtv_dev_id(dtv_dev_id), m_tree(tree), m_parent(NULL), m_ordinal(0),
+      m_repeat(0)
+{
+}
+
+DVBDevDevice::~DVBDevDevice()
+{
+    if(DeviceID() >= 0)
+        m_tree.AddDeferredDelete(DeviceID());
+}
+
+DVBDevDevice* DVBDevDevice::FindDevice(int dev_id)
+{
+    DVBDevDevice* dev = NULL;
+
+    if(m_dtv_dev_id == dev_id)
+        dev = this;
+
+    unsigned int num_children = NumChildren();
+    for(unsigned int ch = 0; !dev && ch < num_children; ch++)
+    {
+        DVBDevDevice* child = GetChild(ch);
+        if(child)
+        {
+            if(child->DeviceID() == dev_id)
+                dev = child;
+            else
+                dev = child->FindDevice(dev_id);
+        }
+    }
+    return dev;
+}
+
+DVBDevDevice* DVBDevDevice::CreateById(DVBDevTree& tree,
+                                       int dtv_dev_id)
+{
+    DVBDevDevice* node = NULL;
+
+    // load settings from DB
+    MSqlQuery query(MSqlQuery::InitCon());
+    query.prepare("SELECT dtv_dev_type, dtv_dev_descr"
+                  " FROM dtv_device_tree"
+                  " WHERE dtv_dev_id = :DTV_DEV_ID");
+    query.bindValue(":DTV_DEV_ID", dtv_dev_id);
+
+    if(query.exec() && query.next())
+    {
+        dvbdev_t type = DevTypeFromString(query.value(0).toString());
+        node = CreateByType(tree, type, dtv_dev_id);
+ 
+        QString descr = query.value(1).toString();
+        if(node)
+        {
+            node->SetDescription(descr);
+            node->Load();
+        }
+    }
+
+    return node;
+}
+
+DVBDevDevice* DVBDevDevice::CreateByType(DVBDevTree& tree,
+                                         dvbdev_t type,
+                                         int dev_id)
+{
+    if(dev_id == -1)
+        dev_id = tree.NextFakeID();
+
+    DVBDevDevice* node = NULL;
+    switch(type)
+    {
+    case DVBDEV_SWITCH:
+        node = new DVBDevSwitch(tree, dev_id);
+        if(node)
+            node->SetDescription("Switch");
+        break;
+    case DVBDEV_ROTOR:
+        node = new DVBDevRotor(tree, dev_id);
+        if(node)
+            node->SetDescription("Rotor");
+        break;
+    case DVBDEV_LNB:
+        node = new DVBDevLnb(tree, dev_id);
+        if(node)
+            node->SetDescription("LNB");
+        break;
+    default:
+        break;
+    }
+    if(node)
+        node->SetDeviceType(type);
+    return node;
+}
+
+//////////////////////////////////////// DVBDevSwitch
+
+DVBDevSwitch::DVBDevSwitch(DVBDevTree& tree,
+                           int dtv_dev_id)
+    : DVBDevDevice(tree, dtv_dev_id),
+      m_type(SWITCH_TONE), m_num_ports(2)
+{
+    m_children.resize(m_num_ports);
+    for(unsigned int i = 0; i < m_num_ports; i++)
+        m_children[i] = NULL;
+    Reset();
+}
+
+DVBDevSwitch::~DVBDevSwitch()
+{
+    for(CHILDREN::iterator i = m_children.begin(); i != m_children.end(); i++)
+        delete *i;
+}
+
+bool DVBDevSwitch::Execute(const DVBDevSettings& settings,
+                           const DVBTuning& tuning)
+{
+    bool success = true;
+
+    // sanity check switch position
+    unsigned int pos;
+    if(!GetPosition(settings, pos))
+        return false;
+
+    // determine if switch command needs to be sent based on last pos
+    if(m_last_pos != pos)
+    {
+        // perform switching
+        switch(m_type)
+        {
+        case SWITCH_TONE:
+            success = ExecuteTone(settings, tuning, pos);
+            break;
+        case SWITCH_DISEQC_COMMITTED:
+        case SWITCH_DISEQC_UNCOMMITTED:
+            success = ExecuteDiseqc(settings, tuning, pos);
+            break;
+        case SWITCH_LEGACY_SW21:
+        case SWITCH_LEGACY_SW42:
+        case SWITCH_LEGACY_SW64:
+            success = ExecuteLegacy(settings, tuning, pos);
+            break;
+        default:
+            success = false;
+            VERBOSE(VB_IMPORTANT, LOC_ERR +
+                    QString("Unknown switch type (%1)")
+                    .arg((unsigned int)m_type));
+            break;
+        }
+
+        // if a child device will be sending a diseqc command, wait 100ms
+        if(m_children[pos]->NeedsCommand(settings))
+        {
+            VERBOSE(VB_CHANNEL, LOC + "Waiting for switch");
+            usleep(DISEQC_LONG_WAIT);
+        }
+
+        m_last_pos = pos;
+    }
+
+    // chain to child if the switch was successful
+    if(success)
+        success = m_children[pos]->Execute(settings, tuning);
+    
+    return success;
+}
+
+void DVBDevSwitch::Reset()
+{
+    m_last_pos = (unsigned int)-1;
+    for(CHILDREN::iterator i = m_children.begin(); i != m_children.end(); i++)
+        if(*i)
+            (*i)->Reset();
+}
+
+bool DVBDevSwitch::NeedsCommand(const DVBDevSettings& settings) const
+{
+    // sanity check switch position
+    unsigned int pos;
+    if(!GetPosition(settings, pos))
+        return false;
+
+    // if position is changing, a command is definitely needed
+    if(pos != m_last_pos)
+        return true;
+
+    // otherwise, the child that will be selected may need a command
+    else
+        return m_children[pos]->NeedsCommand(settings);
+}
+
+DVBDevDevice* DVBDevSwitch::SelectedChild(const DVBDevSettings& settings) const
+{
+    DVBDevDevice* child = NULL;
+    unsigned int pos;
+    if(GetPosition(settings, pos))
+        child = m_children[pos];
+    return child;
+}
+
+unsigned int DVBDevSwitch::NumChildren() const
+{
+    return m_num_ports;
+}
+
+DVBDevDevice* DVBDevSwitch::GetChild(unsigned int ordinal)
+{
+    if(ordinal < m_children.size())
+        return m_children[ordinal];
+    else
+        return NULL;
+}
+
+bool DVBDevSwitch::SetChild(unsigned int ordinal, DVBDevDevice* device)
+{
+    if(ordinal < m_children.size())
+    {
+        delete m_children[ordinal];
+        m_children[ordinal] = device;
+        if(device)
+        {
+            device->SetOrdinal(ordinal);
+            device->SetParent(this);
+        }
+        return true;
+    }
+    return false;
+}
+
+fe_sec_voltage DVBDevSwitch::GetVoltage(const DVBDevSettings& settings,
+                                        const DVBTuning& tuning) const
+{
+    fe_sec_voltage voltage = SEC_VOLTAGE_18;
+    DVBDevDevice* child = SelectedChild(settings);
+    if(child)
+        voltage = child->GetVoltage(settings, tuning);
+    return voltage;
+}
+
+bool DVBDevSwitch::Load()
+{
+    // clear old children
+    for(CHILDREN::iterator i = m_children.begin(); i != m_children.end(); i++)
+        delete *i;
+    m_children.clear();
+
+    // populate switch parameters from db
+    {
+        MSqlQuery query(MSqlQuery::InitCon());
+        query.prepare("SELECT dtv_dev_subtype, switch_ports"
+                      " FROM dtv_device_tree"
+                      " WHERE dtv_dev_id = :DTV_DEV_ID");
+        query.bindValue(":DTV_DEV_ID", DeviceID());
+        
+        if(query.exec() && query.next())
+        {
+            m_type = SwitchTypeFromString(query.value(0).toString());
+            m_num_ports = query.value(1).toInt();
+            m_children.resize(m_num_ports);
+            for(unsigned int i=0; i < m_num_ports; i++)
+                m_children[i] = NULL;
+        }
+    }
+
+    // load children from db
+    {
+        MSqlQuery query(MSqlQuery::InitCon());
+        query.prepare("SELECT dtv_dev_id, ordinal FROM dtv_device_tree "
+                      "WHERE parent = :DTV_DEV_ID");
+        query.bindValue(":DTV_DEV_ID", DeviceID());
+        if(query.exec())
+        {
+            while(query.next())
+            {
+                unsigned int child_dev_id = query.value(0).toInt();
+                unsigned int ordinal = query.value(1).toInt();
+                DVBDevDevice* child = CreateById(m_tree, child_dev_id);
+                if(!SetChild(ordinal, child))
+                {
+                    VERBOSE(VB_IMPORTANT, LOC_ERR +
+                            QString("Switch port out of range (%d > %d)")
+                            .arg(ordinal + 1)
+                            .arg(m_num_ports));
+                    delete child;
+                }
+            }
+        }
+    }
+
+    return true;
+}
+
+bool DVBDevSwitch::Store()
+{
+    QString type = SwitchTypeToString(m_type);
+    MSqlQuery query(MSqlQuery::InitCon());
+
+    // insert new or update old
+    if(m_dtv_dev_id >= 0)
+    {
+        query.prepare("UPDATE dtv_device_tree"
+                      " SET parent = :PARENT,"
+                      " ordinal = :ORDINAL,"
+                      " dtv_dev_type = 'switch',"
+                      " dtv_dev_descr = :DESCR,"
+                      " dtv_dev_subtype = :TYPE,"
+                      " switch_ports = :PORTS"
+                      " WHERE dtv_dev_id = :DTV_DEV_ID");
+    }
+    else
+    {
+        query.prepare("INSERT INTO dtv_device_tree"
+                      " (parent, ordinal, dtv_dev_type, dtv_dev_descr,"
+                      " dtv_dev_subtype, switch_ports)"
+                      " VALUES (:PARENT, :ORDINAL, 'switch', :DESCR,"
+                      " :TYPE, :PORTS)");
+    }
+    if(m_parent)
+        query.bindValue(":PARENT", m_parent->DeviceID());
+    query.bindValue(":ORDINAL", m_ordinal);
+    query.bindValue(":DESCR", GetDescription());
+    query.bindValue(":TYPE", type);
+    query.bindValue(":PORTS", m_num_ports);
+    query.bindValue(":DTV_DEV_ID", DeviceID());
+
+    // chain to children
+    bool success = query.exec();
+    if(success)
+    { 
+        if(m_dtv_dev_id < 0)
+            m_dtv_dev_id = query.lastInsertId().toInt();
+        for(unsigned int ch = 0; ch < m_children.size(); ch++)
+            if(m_children[ch])
+                success = success && m_children[ch]->Store();
+    }
+
+    return success;
+}
+
+void DVBDevSwitch::SetNumPorts(unsigned int num_ports)
+{
+    unsigned int old_num = m_children.size();
+    if(old_num > num_ports)
+    {
+        for(unsigned int ch = num_ports; ch < old_num; ch++)
+            delete m_children[ch];
+        m_children.resize(num_ports);
+    }
+    else if(old_num < num_ports)
+    {
+        m_children.resize(num_ports);
+        for(unsigned int ch = old_num; ch < num_ports; ch++)
+            m_children[ch] = NULL;
+    }
+    m_num_ports = num_ports;
+}
+
+bool DVBDevSwitch::ExecuteLegacy(const DVBDevSettings& settings,
+                                 const DVBTuning& tuning,
+                                 unsigned int pos)
+{
+#ifdef FE_DISHNETWORK_SEND_LEGACY_CMD
+
+    static const unsigned char sw21_cmds[] = { 0x34, 0x65 };
+    static const unsigned char sw42_cmds[] = { 0x46, 0x17 };
+    static const unsigned char sw64_v_cmds[] = { 0x39, 0x4b, 0x0d };
+    static const unsigned char sw64_h_cmds[] = { 0x1a, 0x5c, 0x2e };
+
+    const unsigned char *cmds = NULL;
+    unsigned int num_ports = 0;
+
+    // determine polarity from lnb
+    bool horizontal = false;
+    DVBDevLnb* lnb = m_tree.FindLNB(settings);
+    if(lnb)
+        horizontal = lnb->IsHorizontal(tuning);
+
+    // get command table for this switch
+    switch (m_type)
+    {
+    case SWITCH_LEGACY_SW21:
+        cmds = sw21_cmds;
+        num_ports = 2;
+        break;
+    case SWITCH_LEGACY_SW42:
+        cmds = sw42_cmds;
+        num_ports = 2;
+        break;
+    case SWITCH_LEGACY_SW64:
+        if (horizontal)
+            cmds = sw64_h_cmds;
+        else
+            cmds = sw64_v_cmds;
+        num_ports = 3;
+        break;
+    default:
+        return false;
+    }
+    pos %= num_ports;
+
+    VERBOSE(VB_CHANNEL, LOC + QString("Changing to Legacy switch port %1/%2")
+            .arg(pos+1)
+            .arg(num_ports));
+
+    // send command
+    if (ioctl(m_tree.FrontendFD(), FE_DISHNETWORK_SEND_LEGACY_CMD, cmds[pos]) == -1)
+    {
+        VERBOSE(VB_IMPORTANT, LOC_ERR +
+                "FE_DISHNETWORK_SEND_LEGACY_CMD failed" + ENO);
+        return false;
+    }
+
+    return true;
+
+#else
+
+    VERBOSE(VB_IMPORTANT, LOC_ERR + 
+            "DVB API does not support FE_DISHNETWORK_SEND_LEGACY_CMD.");
+    return false;
+
+#endif
+}
+
+bool DVBDevSwitch::ExecuteTone(const DVBDevSettings& /*settings*/,
+                               const DVBTuning& /*tuning*/,
+                               unsigned int pos)
+{
+    VERBOSE(VB_CHANNEL, LOC + QString("Changing to Tone switch port %1/2")
+            .arg(pos+1));
+
+    bool success = m_tree.MiniDiseqc(pos == 0 ? SEC_MINI_A : SEC_MINI_B);
+    if(!success)
+        VERBOSE(VB_IMPORTANT, LOC_ERR + "Setting Tone Switch failed." + ENO);
+    return success;
+}
+
+bool DVBDevSwitch::ExecuteDiseqc(const DVBDevSettings& settings,
+                                 const DVBTuning& tuning,
+                                 unsigned int pos)
+{
+    // retrieve LNB info
+    bool high_band = false;
+    bool horizontal = false;
+    DVBDevLnb* lnb = m_tree.FindLNB(settings);
+    if(lnb)
+    {
+        high_band = lnb->IsHighBand(tuning);
+        horizontal = lnb->IsHorizontal(tuning);
+    }
+
+    // check number of ports
+    if(m_type == SWITCH_DISEQC_COMMITTED && m_num_ports > 4 ||
+       m_type == SWITCH_DISEQC_UNCOMMITTED && m_num_ports > 16)
+    {
+        VERBOSE(VB_IMPORTANT, LOC_ERR +
+                QString("Invalid number of ports for DiSEqC 1.x Switch (%1)")
+                .arg(m_num_ports));
+        return false;
+    }
+
+    // build command
+    unsigned int cmd;
+    unsigned char data;
+    if(m_type == SWITCH_DISEQC_UNCOMMITTED)
+    {
+        cmd = DISEQC_CMD_WRITE_N1;
+        data = pos;
+    }
+    else
+    {
+        cmd = DISEQC_CMD_WRITE_N0;
+        data = ((pos << 2) |
+                (horizontal ? 2 : 0) |
+                (high_band ? 1 : 0));
+    }
+    data |= 0xf0;
+
+    VERBOSE(VB_CHANNEL, LOC +
+            QString("Changing to DiSEqC switch port %1/%2")
+            .arg(pos+1)
+            .arg(m_num_ports));
+
+    return m_tree.SendCommand(DISEQC_ADR_SW_ALL,
+                              cmd,
+                              m_repeat,
+                              1,
+                              &data);
+}
+
+bool DVBDevSwitch::GetPosition(const DVBDevSettings& settings,
+                               unsigned int& pos) const
+{
+    pos = (unsigned int)settings.GetValue(m_dtv_dev_id);
+    if(pos >= m_num_ports)
+    {
+        VERBOSE(VB_IMPORTANT, LOC_ERR +
+                QString("Port number out of range (%1 > %2)")
+                .arg(pos+1)
+                .arg(m_num_ports));
+        return false;
+    }
+    if(m_children[pos] == NULL)
+    {
+        VERBOSE(VB_IMPORTANT, LOC_ERR +
+                QString("Port has no connected devices configured (%1)")
+                .arg(pos+1));
+        return false;
+    }
+
+    return true;
+}
+
+//////////////////////////////////////// DVBDevRotor
+
+DVBDevRotor::DVBDevRotor(DVBDevTree& tree,
+                         int dtv_dev_id)
+    : DVBDevDevice(tree, dtv_dev_id),
+      m_type(ROTOR_DISEQC_1_3), m_speed_hi(2.5), m_speed_lo(1.9),
+      m_child(NULL), m_last_position(0.0), m_last_azimuth(0.0),
+      m_move_time(0.0), m_last_pos_known(false)
+{
+    Reset();
+}
+
+DVBDevRotor::DVBDevRotor::~DVBDevRotor()
+{
+    delete m_child;
+}
+
+bool DVBDevRotor::Execute(const DVBDevSettings& settings,
+                          const DVBTuning& tuning)
+{
+    bool success = true;
+
+    double position = settings.GetValue(m_dtv_dev_id);
+    if(!m_last_pos_known || position != m_last_position)
+    {
+        switch(m_type)
+        {
+        case ROTOR_DISEQC_1_2:
+            success = ExecuteRotor(settings, tuning, position);
+            break;
+        case ROTOR_DISEQC_1_3:
+            success = ExecuteUSALS(settings, tuning, position);
+            break;
+        default:
+            success = false;
+            VERBOSE(VB_IMPORTANT, LOC_ERR +
+                    QString("Unknown rotor type (%1)")
+                    .arg((unsigned int)m_type));
+            break;
+        }
+        
+        m_last_position = position;
+    }
+
+    // chain to child
+    if(success && m_child)
+        success = m_child->Execute(settings, tuning);
+
+    return success;
+}
+
+void DVBDevRotor::Reset()
+{
+    if(m_child)
+        m_child->Reset();
+}
+
+bool DVBDevRotor::NeedsCommand(const DVBDevSettings& settings) const
+{
+    double position = settings.GetValue(m_dtv_dev_id);
+    if(position != m_last_position)
+        return true;
+    else if(m_child)
+        return m_child->NeedsCommand(settings);
+    else
+        return false;
+}
+
+DVBDevDevice* DVBDevRotor::SelectedChild(const DVBDevSettings& /*settings*/) const
+{
+    return m_child;
+}
+
+bool DVBDevRotor::SetChild(unsigned int ordinal, DVBDevDevice* device)
+{
+    if(ordinal == 0)
+    {
+        delete m_child;
+        m_child = device;
+        if(m_child)
+        {
+            m_child->SetOrdinal(ordinal);
+            m_child->SetParent(this);
+        }
+        return true;
+    }
+    return false;
+}
+
+fe_sec_voltage DVBDevRotor::GetVoltage(const DVBDevSettings& settings,
+                                       const DVBTuning& tuning) const
+{
+    fe_sec_voltage voltage = SEC_VOLTAGE_18;
+
+    // override voltage if the last position is known and the rotor is moving
+    if(!(m_last_pos_known && Progress() < 1.0) && m_child)
+        voltage = m_child->GetVoltage(settings, tuning);
+
+    return voltage;
+}
+
+bool DVBDevRotor::Load()
+{
+    // populate switch parameters from db
+    {
+        MSqlQuery query(MSqlQuery::InitCon());
+        query.prepare("SELECT dtv_dev_subtype,"
+                      " rotor_hi_speed, rotor_lo_speed, rotor_positions"
+                      " FROM dtv_device_tree"
+                      " WHERE dtv_dev_id = :DTV_DEV_ID");
+        query.bindValue(":DTV_DEV_ID", DeviceID());
+        
+        if(query.exec() && query.next())
+        {
+            m_type = RotorTypeFromString(query.value(0).toString());
+            m_speed_hi = query.value(1).toDouble();
+            m_speed_lo = query.value(2).toDouble();
+
+            // form of "angle1=index1:angle2=index2:..."
+            QString positions = query.value(3).toString();
+            QStringList pos = QStringList::split(":", positions);
+            for(unsigned int i=0; i < pos.count(); i++)
+            {
+                QStringList eq = QStringList::split("=", pos[i]);
+                if(eq.count() == 2)
+                    m_posmap[eq[0].toFloat()] = eq[1].toUInt();
+            }
+        }
+    }
+
+    // load children from db
+    delete m_child;
+    m_child = NULL;
+    {
+        MSqlQuery query(MSqlQuery::InitCon());
+        query.prepare("SELECT dtv_dev_id FROM dtv_device_tree "
+                      "WHERE parent = :DTV_DEV_ID");
+        query.bindValue(":DTV_DEV_ID", DeviceID());
+        
+        if(query.exec() && query.next())
+        {
+            int child_dev_id = query.value(0).toInt();
+            SetChild(0, CreateById(m_tree, child_dev_id));
+        }
+    }
+
+    return true;
+}
+
+bool DVBDevRotor::Store()
+{
+    QString type = RotorTypeToString(m_type);
+    QString posmap;
+
+    if(!m_posmap.empty())
+    {
+        QStringList pos;
+        for(INTPOSMAP::iterator i = m_posmap.begin(); i != m_posmap.end(); i++)
+            pos.push_back(QString("%1=%2").arg(i->first).arg(i->second));
+        posmap = pos.join(":");
+    }
+
+    MSqlQuery query(MSqlQuery::InitCon());
+
+    // insert new or update old
+    if(m_dtv_dev_id >= 0)
+    {
+        query.prepare("UPDATE dtv_device_tree"
+                      " SET parent = :PARENT,"
+                      " ordinal = :ORDINAL,"
+                      " dtv_dev_type = 'rotor',"
+                      " dtv_dev_descr = :DESCR,"
+                      " dtv_dev_subtype = :TYPE,"
+                      " rotor_hi_speed = :HISPEED,"
+                      " rotor_lo_speed = :LOSPEED,"
+                      " rotor_positions = :POSMAP"
+                      " WHERE dtv_dev_id = :DTV_DEV_ID");
+    }
+    else
+    {
+        query.prepare("INSERT INTO dtv_device_tree"
+                      " (parent, ordinal, dtv_dev_type, dtv_dev_descr,"
+                      " dtv_dev_subtype, rotor_hi_speed,"
+                      " rotor_lo_speed, rotor_positions)"
+                      " VALUES (:PARENT, :ORDINAL, 'rotor', :DESCR,"
+                      " :TYPE, :HISPEED, :LOSPEED, :POSMAP)");
+    }
+    if(m_parent)
+        query.bindValue(":PARENT", m_parent->DeviceID());
+    query.bindValue(":ORDINAL", m_ordinal);
+    query.bindValue(":DESCR", GetDescription());
+    query.bindValue(":TYPE", type);
+    query.bindValue(":HISPEED", m_speed_hi);
+    query.bindValue(":LOSPEED", m_speed_lo);
+    query.bindValue(":POSMAP", posmap);
+    query.bindValue(":DTV_DEV_ID", DeviceID());
+
+    // chain to child
+    bool success = query.exec();
+    if(success)
+    { 
+        if(m_dtv_dev_id < 0)
+            m_dtv_dev_id = query.lastInsertId().toInt();
+        if(m_child)
+            success = m_child->Store();
+    }
+
+    return success;
+}
+
+double DVBDevRotor::Progress() const
+{
+    double completed = 1.0;
+
+    if(m_move_time != 0.0)
+    {
+        // calculate duration of move
+        double speed = 
+            (m_tree.GetVoltage() == SEC_VOLTAGE_18) ? m_speed_hi : m_speed_lo;
+        double change = fabs(m_desired_azimuth - m_last_azimuth);
+        double duration = change / speed;
+
+        // determine completion percentage
+        struct timeval curtime;
+        gettimeofday(&curtime, NULL);
+        double cursecond = curtime.tv_sec + (double)curtime.tv_usec / 1000000;
+        double time_since_move = cursecond - m_move_time;
+        completed = time_since_move / duration;
+
+        // move completed, finish up
+        if(completed > 1.0)
+            completed = 1.0;
+    }
+
+    return completed;
+}
+
+DVBDevRotor::POSMAP DVBDevRotor::GetPosMap() const
+{
+    POSMAP retposmap;
+    INTPOSMAP::const_iterator i;
+    for(i = m_posmap.begin(); i != m_posmap.end(); i++)
+        retposmap.insert(make_pair(i->second, i->first));
+    return retposmap;
+}
+
+void DVBDevRotor::SetPosMap(const POSMAP& posmap)
+{
+    m_posmap.clear();
+    POSMAP::const_iterator i;
+    for(i = posmap.begin(); i != posmap.end(); i++)
+        m_posmap.insert(make_pair(i->second, i->first));
+}
+
+bool DVBDevRotor::ExecuteRotor(const DVBDevSettings& /*settings*/,
+                               const DVBTuning& /*tuning*/,
+                               double angle)
+{
+    // determine stored position from position map
+    INTPOSMAP::const_iterator i = m_posmap.find(angle);
+    unsigned char index;
+    if(i != m_posmap.end())
+    {
+        index = i->second;
+        RotorMoving(CalculateAzimuth(angle));
+    }
+    else
+        index = (unsigned int) angle;
+
+    VERBOSE(VB_CHANNEL, LOC + QString("Rotor - Goto Stored Position %1")
+            .arg(index));
+
+    return m_tree.SendCommand(DISEQC_ADR_POS_AZ,
+                              DISEQC_CMD_GOTO_POS,
+                              m_repeat,
+                              1,
+                              &index);
+}
+
+bool DVBDevRotor::ExecuteUSALS(const DVBDevSettings& /*settings*/,
+                               const DVBTuning& /*tuning*/,
+                               double angle)
+{
+    double azimuth = CalculateAzimuth(angle);
+    RotorMoving(azimuth);
+    VERBOSE(VB_CHANNEL, LOC + QString("USALS Rotor - Goto %1 (Azimuth %2)")
+            .arg(angle)
+            .arg(azimuth));
+
+    uint az16 = (unsigned int) (abs(azimuth) * 16.0);
+    unsigned char cmd[2];
+    cmd[0] = ((azimuth > 0.0) ? 0xE0 : 0xD0) | ((az16 >> 8) & 0x0f);
+    cmd[1] = (az16 & 0xff);
+
+    return m_tree.SendCommand(DISEQC_ADR_POS_AZ,
+                              DISEQC_CMD_GOTO_X,
+                              m_repeat,
+                              2,
+                              cmd);
+}
+
+#define TO_RADS (M_PI / 180.0)
+#define TO_DEC  (180.0 / M_PI)
+
+double DVBDevRotor::CalculateAzimuth(double angle) const
+{
+    // Equation lifted from VDR rotor plugin by
+    // Thomas Bergwinkl <Thomas.Bergwinkl@t-online.de>
+
+    // Earth Station Latitude and Longitude in radians
+    double P  = gContext->GetSetting("Latitude",  "").toFloat() * TO_RADS;
+    double Ue = gContext->GetSetting("Longitude", "").toFloat() * TO_RADS;
+
+    // Satellite Longitude in radians
+    double Us = angle * TO_RADS;
+
+    double az      = M_PI + atan( tan(Us - Ue) / sin(P) );
+    double x       = acos( cos(Us - Ue) * cos(P) );
+    double el      = atan( (cos(x) - 0.1513) / sin(x) );
+    double tmp_a   = -cos(el) * sin(az);
+    double tmp_b   = (sin(el) * cos(P)) - (cos(el) * sin(P) * cos(az));
+    double azimuth = atan(tmp_a / tmp_b) * TO_DEC;
+
+    return azimuth;
+}
+
+double DVBDevRotor::GetApproxAzimuth()
+{
+    double approx = m_last_azimuth;
+
+    if(m_move_time != 0.0)
+    {
+        double change = m_desired_azimuth - m_last_azimuth;
+        double progress = Progress();
+        approx += (change * progress);
+        if(progress >= 1.0)
+            m_move_time = 0.0;
+    }
+
+    return approx;
+}
+
+void DVBDevRotor::RotorMoving(double azimuth)
+{
+    // set last to approximate current position (or worst case if unknown)
+    if(m_last_pos_known)
+        m_last_azimuth = GetApproxAzimuth();
+    else
+        m_last_azimuth = azimuth > 0.0 ? -75.0 : 75.0;
+
+    // save time and angle of this command
+    m_desired_azimuth = azimuth;
+    struct timeval curtime;
+    gettimeofday(&curtime, NULL);
+    m_move_time = curtime.tv_sec + (double)curtime.tv_usec / 1000000;
+
+    m_last_pos_known = true;
+}
+
+////////////////////////////////////////
+
+DVBDevLnb::DVBDevLnb(DVBDevTree& tree,
+                     int dtv_dev_id)
+    : DVBDevDevice(tree, dtv_dev_id),
+      m_type(LNB_VOLTAGE_TONE), 
+      m_lof_switch(11700000),
+      m_lof_hi(10600000),
+      m_lof_lo(9750000)
+{
+    Reset();
+}
+
+bool DVBDevLnb::Execute(const DVBDevSettings& /*settings*/,
+                        const DVBTuning& tuning)
+{
+    // set voltage for polarization
+    if((m_type == LNB_VOLTAGE || m_type == LNB_VOLTAGE_TONE))
+    {
+        bool horiz = IsHorizontal(tuning);
+        fe_sec_voltage voltage = (horiz ? SEC_VOLTAGE_18 : SEC_VOLTAGE_13);
+        m_tree.SetVoltage(voltage);
+    }
+
+    // set tone for bandselect
+    bool high_band = IsHighBand(tuning);
+    if(m_type == LNB_VOLTAGE_TONE)
+        m_tree.SetTone(high_band);
+
+    return true;
+}
+
+void DVBDevLnb::Reset()
+{
+    // i.e. diseqc lnbs would need reset
+}
+
+bool DVBDevLnb::NeedsCommand(const DVBDevSettings& /*settings*/) const
+{
+    // i.e. diseqc lnbs would return true
+    return false;
+}
+
+fe_sec_voltage DVBDevLnb::GetVoltage(const DVBDevSettings& /*settings*/,
+                                     const DVBTuning& tuning) const
+{
+    fe_sec_voltage voltage = SEC_VOLTAGE_18;
+
+    if((m_type == LNB_VOLTAGE || m_type == LNB_VOLTAGE_TONE))
+        voltage = (IsHorizontal(tuning) ? SEC_VOLTAGE_18 : SEC_VOLTAGE_13);
+
+    return voltage;
+}
+
+bool DVBDevLnb::Load()
+{
+    // populate lnb parameters from db
+    MSqlQuery query(MSqlQuery::InitCon());
+    query.prepare("SELECT dtv_dev_subtype, lnb_lof_switch,"
+                  " lnb_lof_hi, lnb_lof_lo"
+                  " FROM dtv_device_tree WHERE dtv_dev_id = :DTV_DEV_ID");
+    query.bindValue(":DTV_DEV_ID", DeviceID());
+    
+    if(query.exec() && query.next())
+    {
+        m_type = LnbTypeFromString(query.value(0).toString());
+        m_lof_switch = query.value(1).toInt();
+        m_lof_hi = query.value(2).toInt();
+        m_lof_lo = query.value(3).toInt();
+    }
+
+    return true;
+}
+
+bool DVBDevLnb::Store()
+{
+    QString type = LnbTypeToString(m_type);
+    MSqlQuery query(MSqlQuery::InitCon());
+
+    // insert new or update old
+    if(m_dtv_dev_id >= 0)
+    {
+        query.prepare("UPDATE dtv_device_tree"
+                      " SET parent = :PARENT,"
+                      " ordinal = :ORDINAL,"
+                      " dtv_dev_type = 'lnb',"
+                      " dtv_dev_descr = :DESCR,"
+                      " dtv_dev_subtype = :TYPE,"
+                      " lnb_lof_switch = :LOFSW,"
+                      " lnb_lof_lo = :LOFLO,"
+                      " lnb_lof_hi = :LOFHI"
+                      " WHERE dtv_dev_id = :DTV_DEV_ID");
+    }
+    else
+    {
+        query.prepare("INSERT INTO dtv_device_tree"
+                      " (parent, ordinal, dtv_dev_type, dtv_dev_descr,"
+                      " dtv_dev_subtype, lnb_lof_switch,"
+                      " lnb_lof_lo, lnb_lof_hi)"
+                      " VALUES (:PARENT, :ORDINAL, 'lnb', :DESCR,"
+                      " :TYPE, :LOFSW, :LOFLO, :LOFHI)");
+    }
+    if(m_parent)
+        query.bindValue(":PARENT", m_parent->DeviceID());
+    query.bindValue(":ORDINAL", m_ordinal);
+    query.bindValue(":DESCR", GetDescription());
+    query.bindValue(":TYPE", type);
+    query.bindValue(":LOFSW", m_lof_switch);
+    query.bindValue(":LOFLO", m_lof_lo);
+    query.bindValue(":LOFHI", m_lof_hi);
+    query.bindValue(":DTV_DEV_ID", DeviceID());
+
+    // update dev_id
+    bool success = query.exec();
+    if(success && m_dtv_dev_id < 0)
+        m_dtv_dev_id = query.lastInsertId().toInt();
+
+    return success;
+}
+
+bool DVBDevLnb::IsHighBand(const DVBTuning& tuning) const
+{
+    bool high_band = false;
+    if(m_type == LNB_VOLTAGE_TONE)
+        high_band = (tuning.params.frequency > m_lof_switch);
+    return high_band;
+}
+    
+bool DVBDevLnb::IsHorizontal(const DVBTuning& tuning) const
+{
+    char pol = tuning.PolarityChar();
+    return (pol == 'h' || pol == 'l');
+}
+
+__u32 DVBDevLnb::GetIF(const DVBDevSettings& /*settings*/,
+                       const DVBTuning& tuning) const
+{
+    unsigned int abs_freq = tuning.params.frequency;
+    unsigned int lof = (IsHighBand(tuning) ? m_lof_hi : m_lof_lo);
+    return (lof > abs_freq ? lof - abs_freq : abs_freq - lof);
+}
+
+////////////////////////////////////////
+
+struct TypeTable
+{
+    const char* name;
+    unsigned int value;
+};
+
+static QString TableToString(unsigned int type, const TypeTable* table)
+{
+    QString str;
+    for( ; table->name != NULL; table++)
+    {
+        if(type == table->value)
+        {
+            str = table->name;
+            break;
+        }
+    }
+    return str;
+}
+
+static unsigned int TableFromString(const QString& type, const TypeTable*table)
+{
+    for( ; table->name != NULL; table++)
+        if(type == table->name)
+            break;
+    return table->value;
+}
+
+static TypeTable DevTypeTable[] = 
+{
+    { "switch", DVBDEV_SWITCH },
+    { "rotor", DVBDEV_ROTOR },
+    { "lnb", DVBDEV_LNB },
+    { NULL, DVBDEV_LNB }
+};
+
+static TypeTable SwitchTypeTable[] =
+{
+    { "legacy_sw21", SWITCH_LEGACY_SW21 },
+    { "legacy_sw42", SWITCH_LEGACY_SW42 },
+    { "legacy_sw64", SWITCH_LEGACY_SW64 },
+    { "tone", SWITCH_TONE },
+    { "diseqc", SWITCH_DISEQC_COMMITTED },
+    { "diseqc_uncom", SWITCH_DISEQC_UNCOMMITTED },
+    { NULL, SWITCH_TONE }
+};
+
+static TypeTable RotorTypeTable[] =
+{
+    { "diseqc_1_2", ROTOR_DISEQC_1_2 },
+    { "diseqc_1_3", ROTOR_DISEQC_1_3 },
+    { NULL, ROTOR_DISEQC_1_3 }
+};
+
+static TypeTable LnbTypeTable[] =
+{
+    { "fixed", LNB_FIXED },
+    { "voltage", LNB_VOLTAGE },
+    { "voltage_tone", LNB_VOLTAGE_TONE },
+    { NULL, LNB_VOLTAGE_TONE }
+};
+
+QString DevTypeToString(dvbdev_t type)
+{
+    return TableToString((unsigned int)type, DevTypeTable);
+}
+
+dvbdev_t DevTypeFromString(const QString& type)
+{
+    return (dvbdev_t)TableFromString(type, DevTypeTable);
+}
+
+QString SwitchTypeToString(dvbdev_switch_t type)
+{
+    return TableToString((unsigned int)type, SwitchTypeTable);
+}
+
+dvbdev_switch_t SwitchTypeFromString(const QString& type)
+{
+    return (dvbdev_switch_t)TableFromString(type, SwitchTypeTable);
+}
+
+QString RotorTypeToString(dvbdev_rotor_t type)
+{
+    return TableToString((unsigned int)type, RotorTypeTable);
+}
+
+dvbdev_rotor_t RotorTypeFromString(const QString& type)
+{
+    return (dvbdev_rotor_t)TableFromString(type, RotorTypeTable);
+}
+
+QString LnbTypeToString(dvbdev_lnb_t type)
+{
+    return TableToString((unsigned int)type, LnbTypeTable);
+}
+
+dvbdev_lnb_t LnbTypeFromString(const QString& type)
+{
+    return (dvbdev_lnb_t)TableFromString(type, LnbTypeTable);
+}
+
+//////////////////////////////////////// Database Upgrade
+
+enum OLD_DISEQC_TYPES
+{
+    DISEQC_SINGLE                  = 0,
+    DISEQC_MINI_2                  = 1,
+    DISEQC_SWITCH_2_1_0            = 2,
+    DISEQC_SWITCH_2_1_1            = 3,
+    DISEQC_SWITCH_4_1_0            = 4,
+    DISEQC_SWITCH_4_1_1            = 5,
+    DISEQC_POSITIONER_1_2          = 6,
+    DISEQC_POSITIONER_X            = 7,
+    DISEQC_POSITIONER_1_2_SWITCH_2 = 8,
+    DISEQC_POSITIONER_X_SWITCH_2   = 9,
+    DISEQC_SW21                    = 10,
+    DISEQC_SW64                    = 11,
+};
+
+bool DatabaseDiseqcUpgrade()
+{
+    bool success = true;
+    {
+        MSqlQuery query(MSqlQuery::InitCon());
+        query.prepare("CREATE TABLE dtv_device_config ("
+                      "cardinputid int(11) unsigned NOT NULL,"
+                      "dtv_dev_id int(11) unsigned NOT NULL,"
+                      "value varchar(16) NOT NULL,"
+                      "KEY id (cardinputid) )");
+        success = success && query.exec();
+    }
+    {
+        MSqlQuery query(MSqlQuery::InitCon());
+        query.prepare("CREATE TABLE dtv_device_tree ("
+                      "dtv_dev_id int(11) unsigned NOT NULL auto_increment,"
+                      "parent int(11) unsigned default NULL,"
+                      "ordinal tinyint(3) unsigned NOT NULL,"
+                      "dtv_dev_type varchar(16) NOT NULL,"
+                      "dtv_dev_subtype varchar(16) NOT NULL,"
+                      "dtv_dev_descr varchar(32),"
+                      "switch_ports tinyint(3) unsigned default NULL,"
+                      "rotor_hi_speed float default NULL,"
+                      "rotor_lo_speed float default NULL,"
+                      "rotor_positions varchar(256) default NULL,"
+                      "lnb_lof_switch int(11) default NULL,"
+                      "lnb_lof_hi int(11) default NULL,"
+                      "lnb_lof_lo int(11) default NULL,"
+                      "PRIMARY KEY (dtv_dev_id),"
+                      "KEY parent (parent) )");
+        success = success && query.exec();
+    }
+    {
+        MSqlQuery query(MSqlQuery::InitCon());
+        query.prepare("ALTER TABLE capturecard"
+                      " ADD dtv_dev_id int(10) unsigned default NULL");
+        success = success && query.exec();
+    }
+
+    /* Deprecated fields:
+       ALTER TABLE capturecard DROP dvb_diseqc_type;
+       ALTER TABLE cardinput DROP diseqc_port;
+       ALTER TABLE cardinput DROP diseqc_pos;
+       ALTER TABLE cardinput DROP lnb_lof_switch;
+       ALTER TABLE cardinput DROP lnb_lof_hi;
+       ALTER TABLE cardinput DROP lnb_lof_lo; */
+
+    return success;
+}
+
+// import old diseqc configuration into tree
+bool DatabaseDiseqcImport()
+{
+    MSqlQuery iquery(MSqlQuery::InitCon());
+
+    iquery.prepare("SELECT cardinputid, diseqc_port, diseqc_pos,"
+                   " lnb_lof_switch, lnb_lof_hi, lnb_lof_lo"
+                   " FROM cardinput"
+                   " WHERE cardinput.cardid = :CARDID");
+
+    MSqlQuery cquery(MSqlQuery::InitCon());
+    cquery.prepare("SELECT cardid, dvb_diseqc_type FROM capturecard"
+                   " WHERE dvb_diseqc_type IS NOT NULL AND"
+                   " dtv_dev_id IS NULL");
+    
+    // iterate through cards
+    if(!cquery.exec())
+        return false;
+    while(cquery.next())
+    {
+        unsigned cardid = cquery.value(0).toUInt();
+        OLD_DISEQC_TYPES type = (OLD_DISEQC_TYPES)cquery.value(1).toUInt();
+
+        DVBDevTree tree;
+        DVBDevDevice* root = NULL;
+        unsigned int add_lnbs = 0;
+        dvbdev_lnb_t lnb_type = LNB_VOLTAGE_TONE;
+     
+        // create root of tree
+        switch(type)
+        {
+        case DISEQC_SINGLE:
+        {
+            // single LNB
+            root = DVBDevDevice::CreateByType(tree, DVBDEV_LNB);
+            break;
+        }
+        
+        case DISEQC_MINI_2:
+        {
+            // tone switch + 2 LNBs
+            root = DVBDevDevice::CreateByType(tree, DVBDEV_SWITCH);
+            DVBDevSwitch* sw = dynamic_cast<DVBDevSwitch*>(root);
+            sw->SetType(SWITCH_TONE);
+            sw->SetNumPorts(2);
+            add_lnbs = 2;
+            break;
+        }
+        
+        case DISEQC_SWITCH_2_1_0:
+        case DISEQC_SWITCH_2_1_1:
+        {
+            // 2 port diseqc + 2 LNBs
+            root = DVBDevDevice::CreateByType(tree, DVBDEV_SWITCH);
+            DVBDevSwitch* sw = dynamic_cast<DVBDevSwitch*>(root);
+            sw->SetType(SWITCH_DISEQC_COMMITTED);
+            sw->SetNumPorts(2);
+            add_lnbs = 2;
+            break;
+        }
+            
+        case DISEQC_SWITCH_4_1_0:
+        case DISEQC_SWITCH_4_1_1:
+        {
+            // 4 port diseqc + 4 LNBs
+            root = DVBDevDevice::CreateByType(tree, DVBDEV_SWITCH);
+            DVBDevSwitch* sw = dynamic_cast<DVBDevSwitch*>(root);
+            sw->SetType(SWITCH_DISEQC_COMMITTED);
+            sw->SetNumPorts(4);
+            add_lnbs = 4;
+            break;
+        }
+            
+        case DISEQC_POSITIONER_1_2:
+        {
+            // non-usals positioner + LNB
+            root = DVBDevDevice::CreateByType(tree, DVBDEV_ROTOR);
+            DVBDevRotor* rotor = dynamic_cast<DVBDevRotor*>(root);
+            rotor->SetType(ROTOR_DISEQC_1_2);
+            add_lnbs = 1;
+            break;
+        }
+            
+        case DISEQC_POSITIONER_X:
+        {
+            // usals positioner + LNB (diseqc_pos)
+            root = DVBDevDevice::CreateByType(tree, DVBDEV_ROTOR);
+            DVBDevRotor* rotor = dynamic_cast<DVBDevRotor*>(root);
+            rotor->SetType(ROTOR_DISEQC_1_3);
+            add_lnbs = 1;
+            break;
+        }
+            
+        case DISEQC_POSITIONER_1_2_SWITCH_2:
+        {
+            // 10 port uncommitted switch + 10 LNBs
+            root = DVBDevDevice::CreateByType(tree, DVBDEV_SWITCH);
+            DVBDevSwitch* sw = dynamic_cast<DVBDevSwitch*>(root);
+            sw->SetType(SWITCH_DISEQC_UNCOMMITTED);
+            sw->SetNumPorts(10);
+            add_lnbs = 10;
+            break;
+        }
+            
+        case DISEQC_SW21:
+        {
+            // legacy SW21 + 2 fixed lnbs
+            root = DVBDevDevice::CreateByType(tree, DVBDEV_SWITCH);
+            DVBDevSwitch* sw = dynamic_cast<DVBDevSwitch*>(root);
+            sw->SetType(SWITCH_LEGACY_SW21);
+            sw->SetNumPorts(2);
+            add_lnbs = 2;
+            lnb_type = LNB_FIXED;
+            break;
+        }
+            
+        case DISEQC_SW64:
+        {
+            // legacy SW64 + 3 fixed lnbs
+            root = DVBDevDevice::CreateByType(tree, DVBDEV_SWITCH);
+            DVBDevSwitch* sw = dynamic_cast<DVBDevSwitch*>(root);
+            sw->SetType(SWITCH_LEGACY_SW64);
+            sw->SetNumPorts(3);
+            add_lnbs = 3;
+            lnb_type = LNB_FIXED;
+            break;
+        }
+
+        default:
+            VERBOSE(VB_IMPORTANT, "Unknown DiSEqC device type, ignoring card");
+            break;
+        }
+        if(!root)
+            continue;
+        tree.SetRoot(root);
+        
+        // create LNBs
+        for(unsigned int i=0; i < add_lnbs; i++)
+        {
+            DVBDevLnb* lnb = dynamic_cast<DVBDevLnb*>
+                (DVBDevDevice::CreateByType(tree, DVBDEV_LNB));
+            lnb->SetType(lnb_type);
+            lnb->SetDescription(QString("LNB #%1").arg(i+1));
+            if(!root->SetChild(i, lnb))
+                delete lnb;
+        }
+        
+        // save the tree to get real device ids
+        tree.Store(cardid);
+        
+        // iterate inputs
+        DVBDevSettings set;
+        iquery.bindValue(":CARDID", cardid);
+        if(!iquery.exec())
+            return false;
+        while(iquery.next())
+        {
+            unsigned int inputid = iquery.value(0).toUInt();
+            unsigned int port = iquery.value(1).toUInt();
+            double pos = iquery.value(2).toDouble();
+            DVBDevLnb* lnb = NULL;
+            
+            // configure LNB and settings
+            switch(type)
+            {
+            case DISEQC_SINGLE:
+                lnb = dynamic_cast<DVBDevLnb*>(root);
+                break;
+                
+            case DISEQC_MINI_2:
+            case DISEQC_SWITCH_2_1_0:
+            case DISEQC_SWITCH_2_1_1:
+            case DISEQC_SWITCH_4_1_0:
+            case DISEQC_SWITCH_4_1_1:
+            case DISEQC_SW21:
+            case DISEQC_SW64:
+            case DISEQC_POSITIONER_1_2_SWITCH_2:
+                lnb = dynamic_cast<DVBDevLnb*>(root->GetChild(port));
+                set.SetValue(root->DeviceID(), port);
+                break;
+                
+            case DISEQC_POSITIONER_1_2:
+            case DISEQC_POSITIONER_X:
+                lnb = dynamic_cast<DVBDevLnb*>(root->GetChild(0));
+                set.SetValue(root->DeviceID(), pos);
+                break;
+                
+            default:
+                break;
+            }
+            
+            // configure lnb
+            if(lnb)
+            {
+                lnb->SetLOFSwitch(iquery.value(3).toUInt());
+                lnb->SetLOFHigh(iquery.value(4).toUInt());
+                lnb->SetLOFLow(iquery.value(5).toUInt());
+            }
+            
+            // save settings
+            set.Store(inputid);
+        }
+        
+        // save any LNB changes
+        tree.Store(cardid);
+
+        // invalidate cached devices
+        DVBDev trees;
+        trees.InvalidateTrees();
+    }
+
+    return true;
+}
Index: libs/libmythtv/dvbdevtree.h
===================================================================
--- libs/libmythtv/dvbdevtree.h	(revision 0)
+++ libs/libmythtv/dvbdevtree.h	(revision 0)
@@ -0,0 +1,550 @@
+/*
+ * \file dvbdevtree.h
+ * \brief DVB-S Device Tree Control Classes.
+ * \author Copyright (C) 2006, Yeasah Pell
+ */
+
+#ifndef DVBDEVTREE_H
+#define DVBDEVTREE_H
+
+// prerequisites
+#include "dvbtypes.h"
+#include <linux/dvb/frontend.h>
+#include <map>
+#include <vector>
+
+// DiSEqC sleep intervals per eutelsat spec
+#define DISEQC_SHORT_WAIT (15 * 1000)
+#define DISEQC_LONG_WAIT  (100 * 1000)
+
+// Number of times to retry ioctls after receiving ETIMEDOUT before giving up
+#define TIMEOUT_RETRIES 3
+
+// Framing byte
+#define DISEQC_FRM            0xe0
+#define DISEQC_FRM_REPEAT     (1 << 0)
+#define DISEQC_FRM_REPLY_REQ  (1 << 1)
+
+// Address byte
+#define DISEQC_ADR_ALL        0x00
+#define DISEQC_ADR_SW_ALL     0x10
+#define DISEQC_ADR_LNB        0x11
+#define DISEQC_ADR_LNB_SW     0x12
+#define DISEQC_ADR_SW_BLK     0x14
+#define DISEQC_ADR_SW         0x15
+#define DISEQC_ADR_SMATV      0x18
+#define DISEQC_ADR_POL_ALL    0x20
+#define DISEQC_ADR_POL_LIN    0x21
+#define DISEQC_ADR_POS_ALL    0x30
+#define DISEQC_ADR_POS_AZ     0x31
+#define DISEQC_ADR_POS_EL     0x32
+
+// Command byte
+#define DISEQC_CMD_RESET      0x00
+#define DISEQC_CMD_CLR_RESET  0x01
+#define DISEQC_CMD_WRITE_N0   0x38
+#define DISEQC_CMD_WRITE_N1   0x39
+#define DISEQC_CMD_WRITE_FREQ 0x58
+#define DISEQC_CMD_HALT       0x60
+#define DISEQC_CMD_LMT_OFF    0x63
+#define DISEQC_CMD_LMT_E      0x66
+#define DISEQC_CMD_LMT_W      0x67
+#define DISEQC_CMD_DRIVE_E    0x68
+#define DISEQC_CMD_DRIVE_W    0x69
+#define DISEQC_CMD_STORE_POS  0x6a
+#define DISEQC_CMD_GOTO_POS   0x6b
+#define DISEQC_CMD_GOTO_X     0x6e
+
+enum dvbdev_t
+{
+    DVBDEV_SWITCH = 0,
+    DVBDEV_ROTOR,
+    DVBDEV_LNB
+};
+
+QString DevTypeToString(dvbdev_t type);
+dvbdev_t DevTypeFromString(const QString& type);
+
+enum dvbdev_switch_t
+{
+    SWITCH_TONE = 0,
+    SWITCH_DISEQC_COMMITTED,
+    SWITCH_DISEQC_UNCOMMITTED,
+    SWITCH_LEGACY_SW21,
+    SWITCH_LEGACY_SW42,
+    SWITCH_LEGACY_SW64
+};
+
+QString SwitchTypeToString(dvbdev_switch_t type);
+dvbdev_switch_t SwitchTypeFromString(const QString& type);
+
+enum dvbdev_rotor_t
+{
+    ROTOR_DISEQC_1_2 = 0,
+    ROTOR_DISEQC_1_3
+};
+
+QString RotorTypeToString(dvbdev_rotor_t type);
+dvbdev_rotor_t RotorTypeFromString(const QString& type);
+
+enum dvbdev_lnb_t
+{
+    LNB_FIXED = 0,
+    LNB_VOLTAGE,
+    LNB_VOLTAGE_TONE
+};
+
+QString LnbTypeToString(dvbdev_lnb_t type);
+dvbdev_lnb_t LnbTypeFromString(const QString& type);
+
+/** DVB-S device settings class. Represents a single possible configuration
+    of a given network of DVB-S devices. */
+class DVBDevSettings
+{
+  public:
+    DVBDevSettings();
+
+    /** Loads configuration chain from DB for specified card input id.
+        \param card_input_id Desired capture card input ID.
+        \return True if successful. */
+    bool Load(unsigned int card_input_id);
+    
+    /** Stores configuration chain to DB for specified card input id.
+        \param card_input_id Desired capture card input ID.
+        \return True if successful. */
+    bool Store(unsigned int card_input_id) const;
+
+    /** Retrieves a value from this configuration chain by device id.
+        \param dtv_dev_id Device id.
+        \return Device scalar value. */
+    double GetValue(int dtv_dev_id) const;
+
+    /** Sets a value for this configuration chain by device id.
+        \param dtv_dev_id Device id.
+        \param value Device scalar value. */
+    void SetValue(int dtv_dev_id, double value);
+    
+  protected:
+
+    // map of dev tree id to configuration value
+    typedef std::map<int, double> CONFIG;
+    CONFIG m_config;
+
+    // current input id
+    unsigned int m_input_id;
+};
+
+class DVBDevTrees;
+class DVBDevTree;
+class DVBDevDevice;
+class DVBDevRotor;
+class DVBDevLnb;
+
+/** Main DVB-S device interface. */
+class DVBDev
+{
+  public:
+
+    /** Retrieve device tree.
+        \param card_id Capture card id.
+        \param fd_frontend DVB frontend device file descriptor. */
+    DVBDevTree* FindTree(unsigned int card_id);
+
+    /** Invalidate cached trees. */
+    void InvalidateTrees();
+    
+  protected:
+    static DVBDevTrees m_trees;
+};
+
+/** Static-scoped locked tree list class. */
+class DVBDevTrees
+{
+  public:
+    ~DVBDevTrees();
+
+    DVBDevTree* FindTree(unsigned int card_id);
+    void InvalidateTrees();
+    
+  protected:
+    typedef std::map<unsigned int, DVBDevTree*> TREES;
+    TREES m_trees;
+    QMutex m_trees_lock;
+};
+
+/** DVB-S device tree class. Represents a tree of DVB-S devices. */
+class DVBDevTree
+{
+  public:
+    /** Constructor. */
+    DVBDevTree();
+    ~DVBDevTree();
+
+    /** Loads the device tree from the database.
+        \param card_id Capture card id.
+        \return True if successful. */
+    bool Load(unsigned int card_id);
+
+    /** Stores the device tree to the database.
+        \param card_id Capture card id.
+        \return True if successful. */
+    bool Store(unsigned int card_id);
+    
+    /** Applies settings to the entire tree.
+        \param settings Configuration chain to apply.
+        \param tuning Tuning parameters.
+        \return True if execution completed successfully. */
+    bool Execute(const DVBDevSettings& settings,
+                 const DVBTuning& tuning);
+
+    /** Reset state of nodes in tree, forcing updates on the
+        next Execute command.
+        \return True if reset completed successfully. */
+    void Reset();
+
+    /** Returns the nth rotor device object in the tree.
+        \param settings Configuration chain in effect.
+        \param index 0 for first rotor, 1 for second, etc.
+        \return Pointer to rotor object if found, NULL otherwise. */
+    DVBDevRotor* FindRotor(const DVBDevSettings& settings,
+                           unsigned int index = 0);
+
+    /** Returns the LNB device object selected by the configuration chain.
+        \param settings Configuration chain in effect.
+        \return Pointer to LNB object if found, NULL otherwise. */
+    DVBDevLnb* FindLNB(const DVBDevSettings& settings);
+
+    /** Returns a device by ID.
+        \param dev_id Device ID to find.
+        \return Pointer to device, or NULL if not found in this tree. */
+    DVBDevDevice* FindDevice(int dev_id);
+
+    /** Retrieves the root node in the tree.
+        \return Pointer to device, or NULL if no root. */
+    DVBDevDevice* Root() { return m_root; }
+
+    /** Changes the root node of the tree.
+        \param root New root node (may be NULL). */
+    void SetRoot(DVBDevDevice* root);
+
+    /** Sends a DiSEqC command.
+        \param adr DiSEqC destination address.
+        \param cmd DiSEqC command.
+        \param repeats Number of times to repeat command.
+        \param data_len Length of optional data.
+        \param data Pointer to optional data. */
+    bool SendCommand(unsigned int adr,
+                     unsigned int cmd,
+                     unsigned int repeats = 0,
+                     unsigned int data_len = 0,
+                     unsigned char* data = NULL);
+
+    /** Resets the DiSEqC bus.
+        \param hard_reset If true, the bus will be power cycled.
+        \return True if successful. */
+    bool ResetDiseqc(bool hard_reset);
+    
+    // frontend fd
+    void Open(int fd_frontend);
+    void Close() { m_fd_frontend = -1; }
+    int FrontendFD() const { return m_fd_frontend; }
+
+    // frontend operations
+    bool SetTone(bool on);
+    bool MiniDiseqc(fe_sec_mini_cmd cmd);
+    bool SetVoltage(fe_sec_voltage voltage);
+    bool SendDiseqc(const dvb_diseqc_master_cmd& cmd);
+    fe_sec_voltage GetVoltage() const { return m_last_voltage; }
+
+    // tree management
+    void AddDeferredDelete(unsigned int dev_id) { m_delete.push_back(dev_id); }
+    int NextFakeID() { return --m_next_cnt; }
+    
+  protected:
+    bool ApplyVoltage(const DVBDevSettings& settings,
+                      const DVBTuning& tuning);
+    
+    int m_fd_frontend;
+    DVBDevDevice *m_root;
+    fe_sec_voltage m_last_voltage;
+    mutable std::list<unsigned int> m_delete;
+
+    int m_next_cnt;
+};
+
+/** DVB-S device class. Represents a node in a DVB-S device network. */
+class DVBDevDevice
+{
+  public:
+    /** Constructor.
+        \param tree Parent reference to tree object.
+        \param dtv_dev_id Device ID of this node. */
+    DVBDevDevice(DVBDevTree& tree, int dtv_dev_id);
+
+    virtual ~DVBDevDevice();
+
+    /** Applies DiSEqC settings to this node and any children.
+        \param settings Configuration chain to apply.
+        \param tuning Tuning parameters.
+        \return True if execution completed successfully. */
+    virtual bool Execute(const DVBDevSettings& settings,
+                         const DVBTuning& tuning) = 0;
+
+    /** Resets the last known settings for this device. Device
+        will not actually have commands issued until next Execute() method. */
+    virtual void Reset() = 0;
+
+    /** Determines if this device or any child will be sending a command
+        for the given configuration chain.
+        \param settings Configuration chain in effect.
+        \return True if a command would be sent if Execute() were called. */
+    virtual bool NeedsCommand(const DVBDevSettings& settings) const = 0;
+
+    /** Retrieves the selected child for this configuration, if any.
+        \param settings Configuration chain in effect.
+        \return Child node object, or NULL if none. */
+    virtual DVBDevDevice* SelectedChild(const DVBDevSettings& settings) const = 0;
+
+    /** Retrieves the proper number of children for this node.
+        \return Number of children */
+    virtual unsigned int NumChildren() const = 0;
+
+    /** Retrieves the nth child of this node.
+        \param ordinal Child number (starting at 0).
+        \return Pointer to device object, or NULL if no child. */
+    virtual DVBDevDevice* GetChild(unsigned int ordinal) = 0;
+
+    /** Changes the nth child of this node.
+        \param ordinal Child number (starting at 0).
+        \param device New child device. (may be NULL)
+        \return true if object was added to tree. */
+    virtual bool SetChild(unsigned int ordinal,
+                          DVBDevDevice* device) = 0;
+
+    /** Retrives the desired voltage for this config.
+        \param settings Configuration chain in effect.
+        \param tuning Tuning parameters.
+        \return Voltage required. */
+    virtual fe_sec_voltage GetVoltage(const DVBDevSettings& settings,
+                                      const DVBTuning& tuning) const = 0;
+    
+    /** Loads this device from the database.
+        \return True if successful. */
+    virtual bool Load() = 0;
+    
+    /** Stores this device to the database.
+        \return True if successful. */
+    virtual bool Store() = 0;
+   
+    /** Returns a device by ID.
+        \param dev_id Device ID to find.
+        \return Pointer to device, or NULL if not found in this tree. */
+    DVBDevDevice* FindDevice(int dev_id);
+    
+    static DVBDevDevice* CreateById(DVBDevTree& tree,
+                                    int dtv_dev_id);
+    static DVBDevDevice* CreateByType(DVBDevTree& tree,
+                                      dvbdev_t type,
+                                      int dev_id = -1);
+
+    int DeviceID() const { return m_dtv_dev_id; }
+
+    DVBDevDevice* GetParent() const { return m_parent; }
+    void SetParent(DVBDevDevice* parent) { m_parent = parent; }
+    unsigned int GetOrdinal() const { return m_ordinal; }
+    void SetOrdinal(unsigned int ordinal) { m_ordinal = ordinal; }
+
+    void SetDeviceType(dvbdev_t type) { m_dev_type = type; }
+    dvbdev_t GetDeviceType() const { return m_dev_type; }
+
+    QString GetDescription() const { return m_descr; }
+    void SetDescription(const QString& descr) { m_descr = descr; }
+   
+  protected:
+
+    int m_dtv_dev_id;
+    dvbdev_t m_dev_type;
+    QString m_descr;
+    DVBDevTree& m_tree;
+    DVBDevDevice* m_parent;
+    unsigned int m_ordinal;
+    unsigned int m_repeat;
+};
+
+/** Switch class, including tone, legacy and DiSEqC switches. */
+class DVBDevSwitch : public DVBDevDevice
+{
+  public:
+    DVBDevSwitch(DVBDevTree& tree, int dtv_dev_id);
+    ~DVBDevSwitch();
+
+    virtual bool Execute(const DVBDevSettings& settings,
+                         const DVBTuning& tuning);
+    virtual void Reset();
+    virtual bool NeedsCommand(const DVBDevSettings& settings) const;
+    virtual DVBDevDevice* SelectedChild(const DVBDevSettings& settings) const;
+    virtual unsigned int NumChildren() const;
+    virtual DVBDevDevice* GetChild(unsigned int ordinal);
+    virtual bool SetChild(unsigned int ordinal, DVBDevDevice* device);
+    virtual fe_sec_voltage GetVoltage(const DVBDevSettings& settings,
+                                      const DVBTuning& tuning) const;
+    virtual bool Load();
+    virtual bool Store();
+
+    dvbdev_switch_t GetType() const { return m_type; }
+    void SetType(dvbdev_switch_t type) { m_type = type; }
+    unsigned int GetNumPorts() const { return m_num_ports; }
+    void SetNumPorts(unsigned int num_ports);
+    
+  protected:
+    bool ExecuteLegacy(const DVBDevSettings& settings,
+                       const DVBTuning& tuning,
+                       unsigned int pos);
+    bool ExecuteTone(const DVBDevSettings& settings,
+                     const DVBTuning& tuning,
+                     unsigned int pos);
+    bool ExecuteDiseqc(const DVBDevSettings& settings,
+                       const DVBTuning& tuning,
+                       unsigned int pos);
+    bool GetPosition(const DVBDevSettings& settings,
+                     unsigned int& pos) const;
+    
+  private:
+    dvbdev_switch_t m_type;
+    unsigned int m_num_ports;
+    unsigned int m_last_pos;
+
+    typedef std::vector<DVBDevDevice*> CHILDREN;
+    CHILDREN m_children;
+};
+
+/** Rotor class. */
+class DVBDevRotor : public DVBDevDevice
+{
+  public:
+    DVBDevRotor(DVBDevTree& tree, int dtv_dev_id);
+    ~DVBDevRotor();
+
+    virtual bool Execute(const DVBDevSettings& settings,
+                         const DVBTuning& tuning);
+    virtual void Reset();
+    virtual bool NeedsCommand(const DVBDevSettings& settings) const;
+    virtual DVBDevDevice* SelectedChild(const DVBDevSettings& settings) const;
+    virtual unsigned int NumChildren() const { return 1; }
+    virtual DVBDevDevice* GetChild(unsigned int) { return m_child; }
+    virtual bool SetChild(unsigned int ordinal, DVBDevDevice* device);
+    virtual fe_sec_voltage GetVoltage(const DVBDevSettings& settings,
+                                      const DVBTuning& tuning) const;
+    virtual bool Load();
+    virtual bool Store();
+
+    /** Returns an indication of rotor progress.
+        \return Scale from 0.0..1.0 indicating percentage complete of current
+        move. A value of 1.0 indicates motion is complete. */
+    double Progress() const;
+
+    /** Returns true if there is reasonable confidence in the value returned
+        by Progress() -- otherwise, Progress() returns progress toward
+        the time when the position will be approximately known. */
+    bool IsPositionKnown() const { return m_last_pos_known; }
+
+    dvbdev_rotor_t GetType() const { return m_type; }
+    void SetType(dvbdev_rotor_t type) { m_type = type; }
+    double GetLoSpeed() const { return m_speed_lo; }
+    void SetLoSpeed(double speed) { m_speed_lo = speed; }
+    double GetHiSpeed() const { return m_speed_hi; }
+    void SetHiSpeed(double speed) { m_speed_hi = speed; }
+
+    typedef std::map<unsigned int, double> POSMAP;
+    POSMAP GetPosMap() const;
+    void SetPosMap(const POSMAP& posmap);
+    
+  protected:
+    bool ExecuteRotor(const DVBDevSettings& settings,
+                      const DVBTuning& tuning,
+                      double angle);
+    bool ExecuteUSALS(const DVBDevSettings& settings,
+                      const DVBTuning& tuning,
+                      double angle);
+
+    double CalculateAzimuth(double angle) const;
+    double GetApproxAzimuth();
+    void RotorMoving(double azimuth);
+    
+  private:
+    // configuration
+    dvbdev_rotor_t m_type;
+    double m_speed_hi;
+    double m_speed_lo;
+    typedef std::map<double, unsigned int> INTPOSMAP;
+    INTPOSMAP m_posmap;
+    DVBDevDevice *m_child;
+
+    // state
+    double m_last_position;
+    double m_last_azimuth;
+    double m_desired_azimuth;
+    double m_move_time;
+    bool m_last_pos_known;
+};
+
+/** LNB Class. */
+class DVBDevLnb : public DVBDevDevice
+{
+  public:
+    DVBDevLnb(DVBDevTree& tree, int dtv_dev_id);
+
+    virtual bool Execute(const DVBDevSettings& settings,
+                         const DVBTuning& tuning);
+    virtual void Reset();
+    virtual bool NeedsCommand(const DVBDevSettings& settings) const;
+
+    // no children on LNBs
+    virtual DVBDevDevice* SelectedChild(const DVBDevSettings&) const { return NULL; }
+    virtual unsigned int NumChildren() const { return 0; }
+    virtual DVBDevDevice* GetChild(unsigned int) { return NULL; }
+    virtual bool SetChild(unsigned int, DVBDevDevice*) { return false; }
+    fe_sec_voltage GetVoltage(const DVBDevSettings& settings,
+                              const DVBTuning& tuning) const;
+    virtual bool Load();    
+    virtual bool Store();
+    
+    /** Determine if the high frequency band is active (for switchable LNBs).
+        \param tuning Tuning parameters.
+        \return True if high band is active. */
+    bool IsHighBand(const DVBTuning& tuning) const;
+
+    /** Determine if horizontal polarity is active (for switchable LNBs).
+        \param tuning Tuning parameters.
+        \return True if polarity is horizontal. */
+    bool IsHorizontal(const DVBTuning& tuning) const;
+
+    /** Calculate proper intermediate frequency for the given settings and
+        tuning parameters.
+        \param settings Configuration chain in effect.
+        \param tuning Tuning parameters.
+        \return Frequency for use with FE_SET_FRONTEND. */
+    __u32 GetIF(const DVBDevSettings& settings,
+                const DVBTuning& tuning) const;
+
+    dvbdev_lnb_t GetType() const { return m_type; }
+    void SetType(dvbdev_lnb_t type) { m_type = type; }
+    unsigned int GetLOFSwitch() const { return m_lof_switch; }
+    void SetLOFSwitch(unsigned int lof_switch) { m_lof_switch = lof_switch; }
+    unsigned int GetLOFHigh() const { return m_lof_hi; }
+    void SetLOFHigh(unsigned int lof_hi) { m_lof_hi = lof_hi; }
+    unsigned int GetLOFLow() const { return m_lof_lo; }
+    void SetLOFLow(unsigned int lof_lo) { m_lof_lo = lof_lo; }
+    
+  private:
+    dvbdev_lnb_t m_type;
+    
+    unsigned int m_lof_switch;
+    unsigned int m_lof_hi;
+    unsigned int m_lof_lo;
+};
+
+bool DatabaseDiseqcImport();
+bool DatabaseDiseqcUpgrade();
+
+#endif // DVBDISEQC_H
