diff --git a/qgroundcontrol.pro b/qgroundcontrol.pro
index 2174a90..f226a05 100644
--- a/qgroundcontrol.pro
+++ b/qgroundcontrol.pro
@@ -356,6 +356,8 @@ HEADERS += \
     src/VehicleSetup/JoystickConfigController.h \
     src/ViewWidgets/CustomCommandWidget.h \
     src/ViewWidgets/CustomCommandWidgetController.h \
+    src/ViewWidgets/LogDownload.h \
+    src/ViewWidgets/LogDownloadController.h \
     src/ViewWidgets/ViewWidgetController.h \
 }
 
@@ -468,6 +470,8 @@ SOURCES += \
     src/VehicleSetup/JoystickConfigController.cc \
     src/ViewWidgets/CustomCommandWidget.cc \
     src/ViewWidgets/CustomCommandWidgetController.cc \
+    src/ViewWidgets/LogDownload.cc \
+    src/ViewWidgets/LogDownloadController.cc \
     src/ViewWidgets/ViewWidgetController.cc \
 }
 
diff --git a/qgroundcontrol.qrc b/qgroundcontrol.qrc
index 8d3a851..37046ce 100644
--- a/qgroundcontrol.qrc
+++ b/qgroundcontrol.qrc
@@ -11,6 +11,7 @@
         <file alias="APMAirframeComponent.qml">src/AutoPilotPlugins/APM/APMAirframeComponent.qml</file>
         <file alias="APMAirframeComponentSummary.qml">src/AutoPilotPlugins/APM/APMAirframeComponentSummary.qml</file>
         <file alias="CustomCommandWidget.qml">src/ViewWidgets/CustomCommandWidget.qml</file>
+        <file alias="LogDownload.qml">src/ViewWidgets/LogDownload.qml</file>
         <file alias="FirmwareUpgrade.qml">src/VehicleSetup/FirmwareUpgrade.qml</file>
         <file alias="FlightDisplayView.qml">src/FlightDisplay/FlightDisplayView.qml</file>
         <file alias="FlightModesComponent.qml">src/AutoPilotPlugins/PX4/FlightModesComponent.qml</file>
diff --git a/src/QGCApplication.cc b/src/QGCApplication.cc
index f7922cb..8a34881 100644
--- a/src/QGCApplication.cc
+++ b/src/QGCApplication.cc
@@ -95,6 +95,7 @@
 #include "FlightDisplayViewController.h"
 #include "VideoSurface.h"
 #include "VideoReceiver.h"
+#include "LogDownloadController.h"
 
 #ifndef __ios__
     #include "SerialLink.h"
@@ -377,7 +378,7 @@ void QGCApplication::_initCommon(void)
     qmlRegisterUncreatableType<JoystickManager>     ("QGroundControl.JoystickManager",  1, 0, "JoystickManager",        "Reference only");
     qmlRegisterUncreatableType<Joystick>            ("QGroundControl.JoystickManager",  1, 0, "Joystick",               "Reference only");
 
-    qmlRegisterType<ParameterEditorController>      ("QGroundControl.Controllers", 1, 0, "ParameterEditorController");
+    qmlRegisterType<ParameterEditorController>          ("QGroundControl.Controllers", 1, 0, "ParameterEditorController");
     qmlRegisterType<APMFlightModesComponentController>  ("QGroundControl.Controllers", 1, 0, "APMFlightModesComponentController");
     qmlRegisterType<FlightModesComponentController>     ("QGroundControl.Controllers", 1, 0, "FlightModesComponentController");
     qmlRegisterType<APMAirframeComponentController>     ("QGroundControl.Controllers", 1, 0, "APMAirframeComponentController");
@@ -396,6 +397,7 @@ void QGCApplication::_initCommon(void)
     qmlRegisterType<CustomCommandWidgetController>  ("QGroundControl.Controllers", 1, 0, "CustomCommandWidgetController");
     qmlRegisterType<FirmwareUpgradeController>      ("QGroundControl.Controllers", 1, 0, "FirmwareUpgradeController");
     qmlRegisterType<JoystickConfigController>       ("QGroundControl.Controllers", 1, 0, "JoystickConfigController");
+    qmlRegisterType<LogDownloadController>          ("QGroundControl.Controllers", 1, 0, "LogDownloadController");
 #endif
 
     // Register Qml Singletons
diff --git a/src/ViewWidgets/LogDownload.cc b/src/ViewWidgets/LogDownload.cc
new file mode 100644
index 0000000..8cce362
--- /dev/null
+++ b/src/ViewWidgets/LogDownload.cc
@@ -0,0 +1,34 @@
+/*=====================================================================
+
+QGroundControl Open Source Ground Control Station
+
+(c) 2009, 2010 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
+
+This file is part of the QGROUNDCONTROL project
+
+    QGROUNDCONTROL 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 of the License, or
+    (at your option) any later version.
+
+    QGROUNDCONTROL 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 QGROUNDCONTROL. If not, see <http://www.gnu.org/licenses/>.
+
+======================================================================*/
+
+#include "LogDownload.h"
+
+LogDownload::LogDownload(const QString& title, QAction* action, QWidget *parent) :
+    QGCQmlWidgetHolder(title, action, parent)
+{
+    Q_UNUSED(title);
+    Q_UNUSED(action);
+    setSource(QUrl::fromUserInput("qrc:/qml/LogDownload.qml"));
+    
+    loadSettings();
+}
diff --git a/src/ViewWidgets/LogDownload.h b/src/ViewWidgets/LogDownload.h
new file mode 100644
index 0000000..d22a3f2
--- /dev/null
+++ b/src/ViewWidgets/LogDownload.h
@@ -0,0 +1,37 @@
+/*=====================================================================
+
+QGroundControl Open Source Ground Control Station
+
+(c) 2009, 2015 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
+
+This file is part of the QGROUNDCONTROL project
+
+    QGROUNDCONTROL 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 of the License, or
+    (at your option) any later version.
+
+    QGROUNDCONTROL 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 QGROUNDCONTROL. If not, see <http://www.gnu.org/licenses/>.
+
+======================================================================*/
+
+#ifndef LogDownload_H
+#define LogDownload_H
+
+#include "QGCQmlWidgetHolder.h"
+
+class LogDownload : public QGCQmlWidgetHolder
+{
+    Q_OBJECT
+	
+public:
+    LogDownload(const QString& title, QAction* action, QWidget *parent = 0);
+};
+
+#endif
diff --git a/src/ViewWidgets/LogDownload.qml b/src/ViewWidgets/LogDownload.qml
new file mode 100644
index 0000000..e609235
--- /dev/null
+++ b/src/ViewWidgets/LogDownload.qml
@@ -0,0 +1,210 @@
+/*=====================================================================
+
+QGroundControl Open Source Ground Control Station
+
+(c) 2009, 2015 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
+
+This file is part of the QGROUNDCONTROL project
+
+QGROUNDCONTROL 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 of the License, or
+(at your option) any later version.
+
+QGROUNDCONTROL 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 QGROUNDCONTROL. If not, see <http://www.gnu.org/licenses/>.
+
+======================================================================*/
+
+import QtQuick                  2.5
+import QtQuick.Controls         1.2
+import QtQuick.Controls.Styles  1.2
+import QtQuick.Dialogs          1.2
+
+import QGroundControl.Palette       1.0
+import QGroundControl.Controls      1.0
+import QGroundControl.Controllers   1.0
+import QGroundControl.ScreenTools   1.0
+
+QGCView {
+    viewPanel:  panel
+
+    property real _margins: ScreenTools.defaultFontPixelHeight
+
+    function numberWithCommas(x) {
+        return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",").replace(/,,/g, ",");
+    }
+
+    LogDownloadController {
+        id:         controller
+        factPanel:  panel
+        onSelectionChanged: {
+            tableView.selection.clear()
+            for(var i = 0; i < controller.model.count; i++) {
+                var o = controller.model.get(i)
+                if (o && o.selected) {
+                    tableView.selection.select(i, i)
+                }
+            }
+        }
+    }
+
+    QGCPalette { id: palette; colorGroupEnabled: enabled }
+
+    QGCViewPanel {
+        id:             panel
+        anchors.fill:   parent
+
+        TableView {
+            id: tableView
+            anchors.margins:    _margins
+            anchors.left:       parent.left
+            anchors.right:      refreshButton.left
+            anchors.top:        parent.top
+            anchors.bottom:     parent.bottom
+            model:              controller.model
+            selectionMode:      SelectionMode.MultiSelection
+
+            TableViewColumn {
+                title: "Id"
+                width: ScreenTools.defaultFontPixelWidth * 4
+                horizontalAlignment: Text.AlignHCenter
+                delegate : Text  {
+                    horizontalAlignment: Text.AlignHCenter
+                    text: {
+                        var o = controller.model.get(styleData.row)
+                        return o ? o.id : ""
+                    }
+                }
+            }
+
+            TableViewColumn {
+                title: "Date"
+                width: ScreenTools.defaultFontPixelWidth * 30
+                horizontalAlignment: Text.AlignHCenter
+                delegate : Text  {
+                    text: {
+                        var o = controller.model.get(styleData.row)
+                        if (o) {
+                            //-- Have we received this entry already?
+                            if(controller.model.get(styleData.row).received) {
+                                var d = controller.model.get(styleData.row).time
+                                if(d.getUTCFullYear() < 1980)
+                                    return "Date Unknown"
+                                else
+                                    return d.toLocaleString()
+                            }
+                        }
+                        return ""
+                    }
+                }
+            }
+
+            TableViewColumn {
+                title: "Size"
+                width: ScreenTools.defaultFontPixelWidth * 12
+                horizontalAlignment: Text.AlignHCenter
+                delegate : Text  {
+                    horizontalAlignment: Text.AlignRight
+                    text: {
+                        var o = controller.model.get(styleData.row)
+                        return o ? numberWithCommas(o.size) : ""
+                    }
+                }
+            }
+
+            TableViewColumn {
+                title: "Status"
+                width: ScreenTools.defaultFontPixelWidth * 18
+                horizontalAlignment: Text.AlignHCenter
+                delegate : Text  {
+                    horizontalAlignment: Text.AlignHCenter
+                    text: {
+                        var o = controller.model.get(styleData.row)
+                        return o ? o.status : ""
+                    }
+                }
+            }
+
+        }
+
+        QGCButton {
+            id:                 refreshButton
+            anchors.margins:    _margins
+            anchors.top:        parent.top
+            anchors.right:      parent.right
+            enabled:            !controller.requestingList && !controller.downloadingLogs
+            text:               "Refresh"
+            onClicked: {
+                controller.refresh()
+            }
+        }
+
+        QGCButton {
+            id:                 downloadButton
+            anchors.margins:    _margins
+            anchors.top:        refreshButton.bottom
+            anchors.right:      parent.right
+            enabled:            !controller.requestingList && !controller.downloadingLogs && tableView.selection.count > 0
+            text:               "Download"
+            onClicked: {
+                //-- Clear selection
+                for(var i = 0; i < controller.model.count; i++) {
+                    var o = controller.model.get(i)
+                    if (o) o.selected = false
+                }
+                //-- Flag selected log files
+                tableView.selection.forEach(function(rowIndex){
+                    var o = controller.model.get(rowIndex)
+                    if (o) o.selected = true
+                })
+                //-- Download them
+                controller.download()
+            }
+        }
+
+        QGCButton {
+            id:                 eraseAllButton
+            anchors.margins:    _margins
+            anchors.top:        downloadButton.bottom
+            anchors.right:      parent.right
+            enabled:            !controller.requestingList && !controller.downloadingLogs && controller.model.count > 0
+            text:               "Erase All"
+            onClicked: {
+                eraseAllDialog.visible = true
+            }
+            MessageDialog {
+                id:         eraseAllDialog
+                visible:    false
+                icon:       StandardIcon.Warning
+                standardButtons: StandardButton.Yes | StandardButton.No
+                title:      "Delete All Log Files"
+                text:       "All log files will be erased permanently. Is this really what you want?"
+                onYes: {
+                    controller.eraseAll()
+                    eraseAllDialog.visible = false
+                }
+                onNo: {
+                    eraseAllDialog.visible = false
+                }
+            }
+        }
+
+        QGCButton {
+            id:                 cancelButton
+            anchors.margins:    _margins
+            anchors.top:        eraseAllButton.bottom
+            anchors.right:      parent.right
+            text:               "Cancel"
+            enabled:            controller.requestingList || controller.downloadingLogs
+            onClicked: {
+                controller.cancel()
+            }
+        }
+    }
+}
diff --git a/src/ViewWidgets/LogDownloadController.cc b/src/ViewWidgets/LogDownloadController.cc
new file mode 100644
index 0000000..127fab9
--- /dev/null
+++ b/src/ViewWidgets/LogDownloadController.cc
@@ -0,0 +1,621 @@
+/*=====================================================================
+
+ QGroundControl Open Source Ground Control Station
+
+ (c) 2009 - 2014 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
+
+ This file is part of the QGROUNDCONTROL project
+
+ QGROUNDCONTROL 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 of the License, or
+ (at your option) any later version.
+
+ QGROUNDCONTROL 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 QGROUNDCONTROL. If not, see <http://www.gnu.org/licenses/>.
+
+ ======================================================================*/
+
+#include "LogDownloadController.h"
+#include "MultiVehicleManager.h"
+#include "QGCMAVLink.h"
+#include "QGCFileDialog.h"
+#include "UAS.h"
+#include "QGCApplication.h"
+#include "QGCToolbox.h"
+#include "Vehicle.h"
+#include "MainWindow.h"
+
+#include <qDebug>
+#include <QSettings>
+#include <QUrl>
+
+#define kTimeOutMilliseconds 500
+
+QGC_LOGGING_CATEGORY(LogDownloadLog, "LogDownloadLog")
+
+//----------------------------------------------------------------------------------------
+LogDownloadData::LogDownloadData(QGCLogEntry* entry_)
+    : ID(entry_->id())
+    , entry(entry_)
+    , written(0)
+{
+
+}
+
+//----------------------------------------------------------------------------------------
+QGCLogEntry:: QGCLogEntry(uint logId, const QDateTime& dateTime, uint logSize, bool received)
+    : _logID(logId)
+    , _logSize(logSize)
+    , _logTimeUTC(dateTime)
+    , _received(received)
+    , _selected(false)
+{
+    _status = "Pending";
+}
+
+//----------------------------------------------------------------------------------------
+LogDownloadController::LogDownloadController(void)
+    : _uas(NULL)
+    , _downloadData(NULL)
+    , _vehicle(NULL)
+    , _requestingLogEntries(false)
+    , _downloadingLogs(false)
+    , _retries(0)
+{
+    MultiVehicleManager *manager = qgcApp()->toolbox()->multiVehicleManager();
+    connect(manager, &MultiVehicleManager::activeVehicleChanged, this, &LogDownloadController::_setActiveVehicle);
+    connect(&_timer, &QTimer::timeout, this, &LogDownloadController::_processDownload);
+    _setActiveVehicle(manager->activeVehicle());
+}
+
+//----------------------------------------------------------------------------------------
+void
+LogDownloadController::_processDownload()
+{
+    if(_requestingLogEntries) {
+        _findMissingEntries();
+    } else if(_downloadingLogs) {
+        _findMissingData();
+    }
+}
+
+//----------------------------------------------------------------------------------------
+void
+LogDownloadController::_setActiveVehicle(Vehicle* vehicle)
+{
+    if((_uas && vehicle && _uas == vehicle->uas()) || !vehicle ) {
+        return;
+    }
+    _vehicle = vehicle;
+    if (_uas) {
+        _logEntriesModel.clear();
+        disconnect(_uas, &UASInterface::logEntry, this, &LogDownloadController::_logEntry);
+        disconnect(_uas, &UASInterface::logData,  this, &LogDownloadController::_logData);
+        _uas = NULL;
+    }
+    _uas = vehicle->uas();
+    connect(_uas, &UASInterface::logEntry, this, &LogDownloadController::_logEntry);
+    connect(_uas, &UASInterface::logData,  this, &LogDownloadController::_logData);
+}
+
+//----------------------------------------------------------------------------------------
+void
+LogDownloadController::_logEntry(UASInterface* uas, uint32_t time_utc, uint32_t size, uint16_t id, uint16_t num_logs, uint16_t /*last_log_num*/)
+{
+    //-- Do we care?
+    if(!_uas || uas != _uas || !_requestingLogEntries) {
+        return;
+    }
+    //-- If this is the first, pre-fill it
+    if(!_logEntriesModel.count()) {
+        for(int i = 0; i < num_logs; i++) {
+            QGCLogEntry *entry = new QGCLogEntry(i);
+            _logEntriesModel.append(entry);
+        }
+    }
+    //-- Update this log record
+    if(id < _logEntriesModel.count()) {
+        QGCLogEntry* entry = _logEntriesModel[id];
+        entry->setSize(size);
+        entry->setTime(QDateTime::fromTime_t(time_utc));
+        entry->setReceived(true);
+        entry->setStatus(QString("Available"));
+    } else {
+        qWarning() << "Received log entry for out-of-bound index:" << id;
+    }
+    //-- Reset retry count
+    _retries = 0;
+    //-- Do we have it all?
+    if(_entriesComplete()) {
+        _receivedAllEntries();
+    } else {
+        //-- Reset timer
+        _timer.start(kTimeOutMilliseconds);
+    }
+}
+
+//----------------------------------------------------------------------------------------
+bool
+LogDownloadController::_entriesComplete()
+{
+    //-- Iterate entries and look for a gap
+    int num_logs = _logEntriesModel.count();
+    for(int i = 0; i < num_logs; i++) {
+        QGCLogEntry* entry = _logEntriesModel[i];
+        if(entry) {
+            if(!entry->received()) {
+               return false;
+            }
+        }
+    }
+    return true;
+}
+
+//----------------------------------------------------------------------------------------
+void
+LogDownloadController::_resetSelection()
+{
+    int num_logs = _logEntriesModel.count();
+    for(int i = 0; i < num_logs; i++) {
+        QGCLogEntry* entry = _logEntriesModel[i];
+        if(entry) {
+            entry->setSelected(false);
+        }
+    }
+    emit selectionChanged();
+}
+
+//----------------------------------------------------------------------------------------
+void
+LogDownloadController::_receivedAllEntries()
+{
+    _timer.stop();
+    _requestingLogEntries = false;
+    emit requestingListChanged();
+}
+
+//----------------------------------------------------------------------------------------
+void
+LogDownloadController::_findMissingEntries()
+{
+    int start = -1;
+    int end   = -1;
+    int num_logs = _logEntriesModel.count();
+    //-- Iterate entries and look for a gap
+    for(int i = 0; i < num_logs; i++) {
+        QGCLogEntry* entry = _logEntriesModel[i];
+        if(entry) {
+            if(!entry->received()) {
+                if(start < 0)
+                    start = i;
+                else
+                    end = i;
+            } else {
+                if(start >= 0) {
+                    break;
+                }
+            }
+        }
+    }
+    //-- Is there something missing?
+    if(start >= 0) {
+        //-- Have we tried too many times?
+        if(_retries++ > 2) {
+            for(int i = 0; i < num_logs; i++) {
+                QGCLogEntry* entry = _logEntriesModel[i];
+                if(entry && !entry->received()) {
+                    entry->setStatus(QString("Error"));
+                }
+            }
+            //-- Give up
+            _receivedAllEntries();
+            qWarning() << "Too many errors retreiving log list. Giving up.";
+            return;
+        }
+        //-- Is it a sequence or just one entry?
+        if(end < 0) {
+            end = start;
+        }
+        //-- Request these entries again
+        _requestLogList((uint32_t)start, (uint32_t) end);
+    } else {
+        _receivedAllEntries();
+    }
+}
+
+//----------------------------------------------------------------------------------------
+void
+LogDownloadController::_logData(UASInterface* uas, uint32_t ofs, uint16_t id, uint8_t count, const uint8_t* data)
+{
+    if(!_uas || uas != _uas || !_downloadData) {
+        return;
+    }
+    if(_downloadData->ID != id) {
+        qWarning() << "Received log data for wrong log";
+        return;
+    }
+    bool result = false;
+    //-- Find offset table entry
+    uint o_index = ofs / MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN;
+    if(o_index <= (uint)_downloadData->offsets.count()) {
+        _downloadData->offsets[o_index] = count;
+        //-- Write chunk to file
+        if(_downloadData->file.seek(ofs)) {
+            if(_downloadData->file.write((const char*)data, count)) {
+                _downloadData->written += count;
+                //-- Update status
+                _downloadData->entry->setStatus(QString::number(_downloadData->written));
+                result = true;
+                //-- reset retries
+                _retries = 0;
+                //-- Reset timer
+                _timer.start(kTimeOutMilliseconds);
+                //-- Do we have it all?
+                if(_logComplete()) {
+                    _downloadData->entry->setStatus(QString("Downloaded"));
+                    //-- Check for more
+                    _receivedAllData();
+                }
+            } else {
+                qWarning() << "Error while writing log file chunk";
+            }
+        } else {
+            qWarning() << "Error while seeking log file offset";
+        }
+    } else {
+        qWarning() << "Received log offset greater than expected";
+    }
+    if(!result) {
+        _downloadData->entry->setStatus(QString("Error"));
+    }
+}
+
+//----------------------------------------------------------------------------------------
+bool
+LogDownloadController::_logComplete()
+{
+    //-- Iterate entries and look for a gap
+    int num_ofs = _downloadData->offsets.count();
+    for(int i = 0; i < num_ofs; i++) {
+        if(_downloadData->offsets[i] == 0) {
+           return false;
+        }
+    }
+    return true;
+}
+
+//----------------------------------------------------------------------------------------
+void
+LogDownloadController::_receivedAllData()
+{
+    _timer.stop();
+    //-- Anything queued up for download?
+    if(_prepareLogDownload()) {
+        //-- Request Log
+        _requestLogData(_downloadData->ID, 0, _downloadData->entry->size());
+    } else {
+        _resetSelection();
+        _downloadingLogs = false;
+        emit downloadingLogsChanged();
+    }
+}
+
+//----------------------------------------------------------------------------------------
+void
+LogDownloadController::_findMissingData()
+{
+    int start = -1;
+    int end   = -1;
+    int num_ofs = _downloadData->offsets.count();
+    //-- Iterate offsets and look for a gap
+    for(int i = 0; i < num_ofs; i++) {
+        if(_downloadData->offsets[i] == 0) {
+            if(start < 0)
+                start = i;
+            else
+                end = i;
+        } else {
+            if(start >= 0) {
+                break;
+            }
+        }
+    }
+    //-- Is there something missing?
+    if(start >= 0) {
+        //-- Have we tried too many times?
+        if(_retries++ > 2) {
+            _downloadData->entry->setStatus(QString("Timed Out"));
+            //-- Give up
+            qWarning() << "Too many errors retreiving log data. Giving up.";
+            _receivedAllData();
+            return;
+        }
+        //-- Is it a sequence or just one entry?
+        if(end < 0) {
+            end = start;
+        }
+        //-- Request these log chunks again
+        _requestLogData(
+            _downloadData->ID,
+            (uint32_t)(start * MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN),
+            (uint32_t)((end - start + 1) * MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN));
+    } else {
+        _receivedAllData();
+    }
+}
+
+//----------------------------------------------------------------------------------------
+void
+LogDownloadController::_requestLogData(uint8_t id, uint32_t offset, uint32_t count)
+{
+    if(_vehicle) {
+        qCDebug(LogDownloadLog) << "Request log data (id:" << id << "offset:" << offset << "size:" << count << ")";
+        mavlink_message_t msg;
+        mavlink_msg_log_request_data_pack(
+            qgcApp()->toolbox()->mavlinkProtocol()->getSystemId(),
+            qgcApp()->toolbox()->mavlinkProtocol()->getComponentId(),
+            &msg,
+            qgcApp()->toolbox()->multiVehicleManager()->activeVehicle()->id(), MAV_COMP_ID_ALL,
+            id, offset, count);
+        _vehicle->sendMessage(msg);
+    }
+}
+
+//----------------------------------------------------------------------------------------
+void
+LogDownloadController::refresh(void)
+{
+    _logEntriesModel.clear();
+    _requestLogList();
+}
+
+//----------------------------------------------------------------------------------------
+void
+LogDownloadController::_requestLogList(uint32_t start, uint32_t end)
+{
+    if(_vehicle && _uas) {
+        qCDebug(LogDownloadLog) << "Request log entry list (" << start << "through" << end << ")";
+        _requestingLogEntries = true;
+        emit requestingListChanged();
+        mavlink_message_t msg;
+        mavlink_msg_log_request_list_pack(
+            qgcApp()->toolbox()->mavlinkProtocol()->getSystemId(),
+            qgcApp()->toolbox()->mavlinkProtocol()->getComponentId(),
+            &msg,
+            _vehicle->id(),
+            MAV_COMP_ID_ALL,
+            start,
+            end);
+        _vehicle->sendMessage(msg);
+        //-- Wait 2 seconds before bitching about not getting anything
+        _timer.start(2000);
+    }
+}
+
+//----------------------------------------------------------------------------------------
+void
+LogDownloadController::download(void)
+{
+    //-- Stop listing just in case
+    _receivedAllEntries();
+    //-- Reset downloads, again just in case
+    if(_downloadData) {
+        delete _downloadData;
+        _downloadData = 0;
+    }
+    _downloadPath.clear();
+    _downloadPath = QGCFileDialog::getExistingDirectory(
+        MainWindow::instance(),
+        "Log Download Directory",
+        QDir::homePath(),
+        QGCFileDialog::ShowDirsOnly | QGCFileDialog::DontResolveSymlinks);
+    if(!_downloadPath.isEmpty()) {
+        if(!_downloadPath.endsWith(QDir::separator()))
+            _downloadPath += QDir::separator();
+        //-- Start download process
+        _downloadingLogs = true;
+        emit downloadingLogsChanged();
+        _receivedAllData();
+    }
+}
+
+//----------------------------------------------------------------------------------------
+QGCLogEntry*
+LogDownloadController::_getNextSelected()
+{
+    //-- Iterate entries and look for a selected file
+    int num_logs = _logEntriesModel.count();
+    for(int i = 0; i < num_logs; i++) {
+        QGCLogEntry* entry = _logEntriesModel[i];
+        if(entry) {
+            if(entry->selected()) {
+               return entry;
+            }
+        }
+    }
+    return NULL;
+}
+
+//----------------------------------------------------------------------------------------
+bool
+LogDownloadController::_prepareLogDownload()
+{
+    if(_downloadData) {
+        delete _downloadData;
+        _downloadData = NULL;
+    }
+    QGCLogEntry* entry = _getNextSelected();
+    if(!entry) {
+        return false;
+    }
+    //-- Deselect file
+    entry->setSelected(false);
+    emit selectionChanged();
+    bool result = false;
+    QString ftime;
+    if(entry->time().date().year() < 1980) {
+        ftime = "UnknownDate";
+    } else {
+        ftime = entry->time().toString("yyyy-M-d-hh-mm-ss");
+    }
+    _downloadData = new LogDownloadData(entry);
+    _downloadData->filename = QString("log_") + QString::number(entry->id()) + "_" + ftime + ".txt";
+    _downloadData->file.setFileName(_downloadPath + _downloadData->filename);
+    //-- Append a number to the end if the filename already exists
+    if (_downloadData->file.exists()){
+        uint num_dups = 0;
+        QStringList filename_spl = _downloadData->filename.split('.');
+        do {
+            num_dups +=1;
+            _downloadData->file.setFileName(filename_spl[0] + '_' + QString::number(num_dups) + '.' + filename_spl[1]);
+        } while( _downloadData->file.exists());
+    }
+    //-- Create file
+    if (!_downloadData->file.open(QIODevice::WriteOnly)) {
+        qWarning() << "Failed to create log file:" <<  _downloadData->filename;
+    } else {
+        //-- Preallocate file
+        if(!_downloadData->file.resize(entry->size())) {
+            qWarning() << "Failed to allocate space for log file:" <<  _downloadData->filename;
+        } else {
+            //-- Prepare Offset Table
+            uint o_count = (uint)ceil(entry->size() / (double)MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN);
+            for(uint i = 0; i < o_count; i++) {
+                _downloadData->offsets.append(0);
+            }
+            result = true;
+        }
+    }
+    if(!result) {
+        if (_downloadData->file.exists()) {
+            _downloadData->file.remove();
+        }
+        _downloadData->entry->setStatus(QString("Error"));
+        delete _downloadData;
+        _downloadData = NULL;
+    }
+    return result;
+}
+
+//----------------------------------------------------------------------------------------
+void
+LogDownloadController::eraseAll(void)
+{
+    if(_vehicle && _uas) {
+        mavlink_message_t msg;
+        mavlink_msg_log_erase_pack(
+            qgcApp()->toolbox()->mavlinkProtocol()->getSystemId(),
+            qgcApp()->toolbox()->mavlinkProtocol()->getComponentId(),
+            &msg,
+            qgcApp()->toolbox()->multiVehicleManager()->activeVehicle()->id(), MAV_COMP_ID_ALL);
+        _vehicle->sendMessage(msg);
+    }
+}
+
+//----------------------------------------------------------------------------------------
+void
+LogDownloadController::cancel(void)
+{
+    if(_uas){
+        _receivedAllEntries();
+    }
+    if(_downloadData) {
+        _downloadData->entry->setStatus(QString("Canceled"));
+        if (_downloadData->file.exists()) {
+            _downloadData->file.remove();
+        }
+        delete _downloadData;
+        _downloadData = 0;
+    }
+    _resetSelection();
+    _downloadingLogs = false;
+    emit downloadingLogsChanged();
+}
+
+//-----------------------------------------------------------------------------
+QGCLogModel::QGCLogModel(QObject* parent)
+    : QAbstractListModel(parent)
+{
+
+}
+
+//-----------------------------------------------------------------------------
+QGCLogEntry*
+QGCLogModel::get(int index)
+{
+    if (index < 0 || index >= _logEntries.count()) {
+        return NULL;
+    }
+    return _logEntries[index];
+}
+
+//-----------------------------------------------------------------------------
+int
+QGCLogModel::count() const
+{
+    return _logEntries.count();
+}
+
+//-----------------------------------------------------------------------------
+void
+QGCLogModel::append(QGCLogEntry* object)
+{
+    beginInsertRows(QModelIndex(), rowCount(), rowCount());
+    _logEntries.append(object);
+    endInsertRows();
+    emit countChanged();
+}
+
+//-----------------------------------------------------------------------------
+void
+QGCLogModel::clear(void)
+{
+    if(!_logEntries.isEmpty()) {
+        beginRemoveRows(QModelIndex(), 0, _logEntries.count());
+        while (_logEntries.count()) {
+            QGCLogEntry* entry = _logEntries.last();
+            if(entry) delete entry;
+            _logEntries.removeLast();
+        }
+        endRemoveRows();
+        emit countChanged();
+    }
+}
+
+//-----------------------------------------------------------------------------
+QGCLogEntry*
+QGCLogModel::operator[](int index)
+{
+    return get(index);
+}
+
+//-----------------------------------------------------------------------------
+int
+QGCLogModel::rowCount(const QModelIndex& /*parent*/) const
+{
+    return _logEntries.count();
+}
+
+//-----------------------------------------------------------------------------
+QVariant
+QGCLogModel::data(const QModelIndex & index, int role) const {
+    if (index.row() < 0 || index.row() >= _logEntries.count())
+        return QVariant();
+    if (role == ObjectRole)
+        return QVariant::fromValue(_logEntries[index.row()]);
+    return QVariant();
+}
+
+//-----------------------------------------------------------------------------
+QHash<int, QByteArray>
+QGCLogModel::roleNames() const {
+    QHash<int, QByteArray> roles;
+    roles[ObjectRole] = "logEntry";
+    return roles;
+}
diff --git a/src/ViewWidgets/LogDownloadController.h b/src/ViewWidgets/LogDownloadController.h
new file mode 100644
index 0000000..befd5bc
--- /dev/null
+++ b/src/ViewWidgets/LogDownloadController.h
@@ -0,0 +1,193 @@
+/*=====================================================================
+
+ QGroundControl Open Source Ground Control Station
+
+ (c) 2009 - 2014 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
+
+ This file is part of the QGROUNDCONTROL project
+
+ QGROUNDCONTROL 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 of the License, or
+ (at your option) any later version.
+
+ QGROUNDCONTROL 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 QGROUNDCONTROL. If not, see <http://www.gnu.org/licenses/>.
+
+ ======================================================================*/
+
+#ifndef LogDownloadController_H
+#define LogDownloadController_H
+
+#include <QObject>
+#include <QTimer>
+#include <QAbstractListModel>
+
+#include <memory>
+
+#include "UASInterface.h"
+#include "AutoPilotPlugin.h"
+#include "FactPanelController.h"
+
+class   MultiVehicleManager;
+class   UASInterface;
+class   Vehicle;
+class   QGCLogEntry;
+class  LogDownloadData;
+
+Q_DECLARE_LOGGING_CATEGORY(LogDownloadLog)
+
+//-----------------------------------------------------------------------------
+class QGCLogModel : public QAbstractListModel
+{
+    Q_OBJECT
+public:
+
+    enum QGCLogModelRoles {
+        ObjectRole = Qt::UserRole + 1
+    };
+
+    QGCLogModel(QObject *parent = 0);
+
+    Q_PROPERTY(int count READ count NOTIFY countChanged)
+    Q_INVOKABLE QGCLogEntry* get(int index);
+
+    int         count           (void) const;
+    void        append          (QGCLogEntry* entry);
+    void        clear           (void);
+    QGCLogEntry*operator[]      (int i);
+
+    int         rowCount        (const QModelIndex & parent = QModelIndex()) const;
+    QVariant    data            (const QModelIndex & index, int role = Qt::DisplayRole) const;
+
+signals:
+    void        countChanged    ();
+
+protected:
+    QHash<int, QByteArray> roleNames() const;
+private:
+    QList<QGCLogEntry*> _logEntries;
+};
+
+//-----------------------------------------------------------------------------
+class QGCLogEntry : public QObject {
+    Q_OBJECT
+    Q_PROPERTY(uint         id          READ id                             CONSTANT)
+    Q_PROPERTY(QDateTime    time        READ time                           NOTIFY timeChanged)
+    Q_PROPERTY(uint         size        READ size                           NOTIFY sizeChanged)
+    Q_PROPERTY(bool         received    READ received                       NOTIFY receivedChanged)
+    Q_PROPERTY(bool         selected    READ selected   WRITE setSelected   NOTIFY selectedChanged)
+    Q_PROPERTY(QString      status      READ status                         NOTIFY statusChanged)
+
+public:
+    QGCLogEntry(uint logId, const QDateTime& dateTime = QDateTime(), uint logSize = 0, bool received = false);
+
+    uint        id          () const { return _logID; }
+    uint        size        () const { return _logSize; }
+    QDateTime   time        () const { return _logTimeUTC; }
+    bool        received    () const { return _received; }
+    bool        selected    () const { return _selected; }
+    QString     status      () const { return _status; }
+
+    void        setId       (uint id_)          { _logID = id_; }
+    void        setSize     (uint size_)        { _logSize = size_;     emit sizeChanged(); }
+    void        setTime     (QDateTime date_)   { _logTimeUTC = date_;  emit timeChanged(); }
+    void        setReceived (bool rec_)         { _received = rec_;     emit receivedChanged(); }
+    void        setSelected (bool sel_)         { _selected = sel_;     emit selectedChanged(); }
+    void        setStatus   (QString stat_)     { _status = stat_;      emit statusChanged(); }
+
+signals:
+    void        idChanged       ();
+    void        timeChanged     ();
+    void        sizeChanged     ();
+    void        receivedChanged ();
+    void        selectedChanged ();
+    void        statusChanged   ();
+
+private:
+    uint        _logID;
+    uint        _logSize;
+    QDateTime   _logTimeUTC;
+    bool        _received;
+    bool        _selected;
+    QString     _status;
+};
+
+//-----------------------------------------------------------------------------
+class LogDownloadData {
+public:
+    LogDownloadData(QGCLogEntry* entry);
+    QList<uint>     offsets;
+    QFile           file;
+    QString         filename;
+    uint            ID;
+    QTimer          processDataTimer;
+    QGCLogEntry*    entry;
+    uint            written;
+};
+
+//-----------------------------------------------------------------------------
+class LogDownloadController : public FactPanelController
+{
+    Q_OBJECT
+public:
+
+    LogDownloadController();
+
+    Q_PROPERTY(QGCLogModel* model           READ model              NOTIFY modelChanged)
+    Q_PROPERTY(bool         requestingList  READ requestingList     NOTIFY requestingListChanged)
+    Q_PROPERTY(bool         downloadingLogs READ downloadingLogs    NOTIFY downloadingLogsChanged)
+
+    QGCLogModel*    model                   () { return &_logEntriesModel; }
+    bool            requestingList          () { return _requestingLogEntries; }
+    bool            downloadingLogs         () { return _downloadingLogs; }
+
+    Q_INVOKABLE void refresh                ();
+    Q_INVOKABLE void download               ();
+    Q_INVOKABLE void eraseAll               ();
+    Q_INVOKABLE void cancel                 ();
+
+signals:
+    void requestingListChanged  ();
+    void downloadingLogsChanged ();
+    void modelChanged           ();
+    void selectionChanged       ();
+
+private slots:
+    void _setActiveVehicle  (Vehicle* vehicle);
+    void _logEntry          (UASInterface *uas, uint32_t time_utc, uint32_t size, uint16_t id, uint16_t num_logs, uint16_t last_log_num);
+    void _logData           (UASInterface *uas, uint32_t ofs, uint16_t id, uint8_t count, const uint8_t *data);
+    void _processDownload   ();
+
+private:
+
+    bool _entriesComplete   ();
+    bool _logComplete       ();
+    void _findMissingEntries();
+    void _receivedAllEntries();
+    void _receivedAllData   ();
+    void _resetSelection    ();
+    void _findMissingData   ();
+    void _requestLogList    (uint32_t start = 0, uint32_t end = 0xFFFF);
+    void _requestLogData    (uint8_t id, uint32_t offset = 0, uint32_t count = 0xFFFFFFFF);
+    bool _prepareLogDownload();
+
+    QGCLogEntry* _getNextSelected();
+
+    UASInterface*       _uas;
+    LogDownloadData*    _downloadData;
+    QTimer              _timer;
+    QGCLogModel         _logEntriesModel;
+    Vehicle*            _vehicle;
+    bool                _requestingLogEntries;
+    bool                _downloadingLogs;
+    int                 _retries;
+    QString             _downloadPath;
+};
+
+#endif
diff --git a/src/uas/UAS.cc b/src/uas/UAS.cc
index 32c6904..907ed64 100644
--- a/src/uas/UAS.cc
+++ b/src/uas/UAS.cc
@@ -960,6 +960,23 @@ void UAS::receiveMessage(mavlink_message_t message)
             emit NavigationControllerDataChanged(this, p.nav_roll, p.nav_pitch, p.nav_bearing, p.target_bearing, p.wp_dist);
         }
             break;
+
+        case MAVLINK_MSG_ID_LOG_ENTRY:
+        {
+            mavlink_log_entry_t log;
+            mavlink_msg_log_entry_decode(&message, &log);
+            emit logEntry(this, log.time_utc, log.size, log.id, log.num_logs, log.last_log_num);
+        }
+            break;
+
+        case MAVLINK_MSG_ID_LOG_DATA:
+        {
+            mavlink_log_data_t log;
+            mavlink_msg_log_data_decode(&message, &log);
+            emit logData(this, log.ofs, log.id, log.count, log.data);
+        }
+            break;
+
         default:
             break;
         }
diff --git a/src/uas/UASInterface.h b/src/uas/UASInterface.h
index 31cb095..d351fd7 100644
--- a/src/uas/UASInterface.h
+++ b/src/uas/UASInterface.h
@@ -88,25 +88,25 @@ public:
     static QColor getNextColor() {
         /* Create color map */
         static QList<QColor> colors = QList<QColor>()
-		<< QColor(231,72,28)
-		<< QColor(104,64,240)
-		<< QColor(203,254,121)
-		<< QColor(161,252,116)
-               	<< QColor(232,33,47)
-		<< QColor(116,251,110)
-		<< QColor(234,38,107)
-		<< QColor(104,250,138)
+        << QColor(231,72,28)
+        << QColor(104,64,240)
+        << QColor(203,254,121)
+        << QColor(161,252,116)
+                << QColor(232,33,47)
+        << QColor(116,251,110)
+        << QColor(234,38,107)
+        << QColor(104,250,138)
                 << QColor(235,43,165)
-		<< QColor(98,248,176)
-		<< QColor(236,48,221)
-		<< QColor(92,247,217)
+        << QColor(98,248,176)
+        << QColor(236,48,221)
+        << QColor(92,247,217)
                 << QColor(200,54,238)
-		<< QColor(87,231,246)
-		<< QColor(151,59,239)
-		<< QColor(81,183,244)
+        << QColor(87,231,246)
+        << QColor(151,59,239)
+        << QColor(81,183,244)
                 << QColor(75,133,243)
-		<< QColor(242,255,128)
-		<< QColor(230,126,23);
+        << QColor(242,255,128)
+        << QColor(230,126,23);
 
         static int nextColor = -1;
         if(nextColor == 18){//if at the end of the list
@@ -139,10 +139,10 @@ public:
         StartBusConfigActuators,
         EndBusConfigActuators,
     };
-    
+
     /// Starts the specified calibration
     virtual void startCalibration(StartCalibrationType calType) = 0;
-    
+
     /// Ends any current calibration
     virtual void stopCalibration(void) = 0;
 
@@ -232,11 +232,11 @@ signals:
       *
       * Typically this is used to send lowlevel information like the battery voltage to the plotting facilities of
       * the groundstation. The data here should be converted to human-readable values before being passed, so ideally
-	  * SI units.
+      * SI units.
       *
       * @param uasId ID of this system
       * @param name name of the value, e.g. "battery voltage"
-	  * @param unit The units this variable is in as an abbreviation. For system-dependent (such as raw ADC values) use "raw", for bitfields use "bits", for true/false or on/off use "bool", for unitless values use "-".
+      * @param unit The units this variable is in as an abbreviation. For system-dependent (such as raw ADC values) use "raw", for bitfields use "bits", for true/false or on/off use "bool", for unitless values use "-".
       * @param value the value that changed
       * @param msec the timestamp of the message, in milliseconds
       */
@@ -323,6 +323,10 @@ signals:
     // HOME POSITION / ORIGIN CHANGES
     void homePositionChanged(int uas, double lat, double lon, double alt);
 
+    // Log Download Signals
+    void logEntry   (UASInterface* uas, uint32_t time_utc, uint32_t size, uint16_t id, uint16_t num_logs, uint16_t last_log_num);
+    void logData    (UASInterface* uas, uint32_t ofs, uint16_t id, uint8_t count, const uint8_t* data);
+
 protected:
 
     // TIMEOUT CONSTANTS
diff --git a/src/ui/MainWindow.cc b/src/ui/MainWindow.cc
index da85df9..42080bd 100644
--- a/src/ui/MainWindow.cc
+++ b/src/ui/MainWindow.cc
@@ -65,6 +65,7 @@ This file is part of the QGROUNDCONTROL project
 #include "QGCDockWidget.h"
 #include "UASInfoWidget.h"
 #include "HILDockWidget.h"
+#include "LogDownload.h"
 #endif
 
 #ifndef __ios__
@@ -86,7 +87,8 @@ enum DockWidgetTypes {
     STATUS_DETAILS,
     INFO_VIEW,
     HIL_CONFIG,
-    ANALYZE
+    ANALYZE,
+    LOG_DOWNLOAD
 };
 
 static const char *rgDockWidgetNames[] = {
@@ -96,7 +98,8 @@ static const char *rgDockWidgetNames[] = {
     "Status Details",
     "Info View",
     "HIL Config",
-    "Analyze"
+    "Analyze",
+    "Log Download"
 };
 
 #define ARRAY_SIZE(ARRAY) (sizeof(ARRAY) / sizeof(ARRAY[0]))
@@ -358,6 +361,9 @@ void MainWindow::_createInnerDockWidget(const QString& widgetName)
         case ONBOARD_FILES:
             widget = new QGCUASFileViewMulti(widgetName, action, this);
             break;
+        case LOG_DOWNLOAD:
+            widget = new LogDownload(widgetName, action, this);
+            break;
         case STATUS_DETAILS:
             widget = new UASInfoWidget(widgetName, action, this);
             break;