Import: Add option to sync time on dive computer download

Add an option for users to sync the dive computer time with the PC time
every time dives are downloaded.
Obviously this will only work on dive computers that have time
synchronisation support in libdivecomputer, for other computers a notice
is logged.
The selection for this option is persisted as a preference.

Signed-off-by: Michael Keller <github@ike.ch>
This commit is contained in:
Michael Keller 2023-04-02 18:28:33 +12:00 committed by Dirk Hohndel
parent cb410fe1ba
commit a38ea971a0
18 changed files with 130 additions and 1 deletions

View file

@ -1,3 +1,4 @@
import: add option to synchronise dive computer time when downloading dives
core: fix bug when save sea water salinity given by DC core: fix bug when save sea water salinity given by DC
desktop: add option to force firmware update on OSTC4 desktop: add option to force firmware update on OSTC4
desktop: add column for dive notes to the dive list table desktop: add column for dive notes to the dive list table

View file

@ -374,6 +374,13 @@ of the dive computer (at least for those not charging while connected via USB).
- Do *not* check the checkboxes labelled _Save libdivecomputer logfile_ and - Do *not* check the checkboxes labelled _Save libdivecomputer logfile_ and
_Save libdivecomputer dumpfile_. These are only used as diagnostic tools _Save libdivecomputer dumpfile_. These are only used as diagnostic tools
when there are problems with downloads(see below). when there are problems with downloads(see below).
- With some dive computers it is possible to adjust the clock on the dive
computer based on the PC clock. This can be very helpful when dealing with
daylight savings time changes, or when travelling between different time
zones. In order to synchronise the dive computer clock with the PC clock
every time dives are imported, check _Sync dive computer time_.
- Then select the _Download_ button. - Then select the _Download_ button.
With communication established, you can see how the data are With communication established, you can see how the data are
retrieved from the dive computer. retrieved from the dive computer.
@ -398,6 +405,7 @@ of the dive computer (at least for those not charging while connected via USB).
(Puck Pro)". Refer to the text in the box below. (Puck Pro)". Refer to the text in the box below.
**** ****
*PROBLEMS WITH DATA DOWNLOAD FROM A DIVE COMPUTER?* *PROBLEMS WITH DATA DOWNLOAD FROM A DIVE COMPUTER?*
[icon="images/icons/important.png"] [icon="images/icons/important.png"]

View file

@ -29,6 +29,7 @@ void cliDownloader(const char *vendor, const char *product, const char *device)
data->setForceDownload(false); data->setForceDownload(false);
data->setSaveLog(true); data->setSaveLog(true);
data->setSaveDump(false); data->setSaveDump(false);
data->setSyncTime(false);
diveImportedModel.startDownload(); diveImportedModel.startDownload();
diveImportedModel.waitForDownload(); diveImportedModel.waitForDownload();

View file

@ -227,6 +227,7 @@ DCDeviceData::DCDeviceData()
#if defined(Q_OS_ANDROID) #if defined(Q_OS_ANDROID)
data.androidUsbDeviceDescriptor = nullptr; data.androidUsbDeviceDescriptor = nullptr;
#endif #endif
data.sync_time = false;
} }
DCDeviceData *DCDeviceData::instance() DCDeviceData *DCDeviceData::instance()
@ -291,6 +292,11 @@ int DCDeviceData::diveId() const
return data.diveid; return data.diveid;
} }
bool DCDeviceData::syncTime() const
{
return data.sync_time;
}
void DCDeviceData::setVendor(const QString &vendor) void DCDeviceData::setVendor(const QString &vendor)
{ {
data.vendor = copy_qstring(vendor); data.vendor = copy_qstring(vendor);
@ -350,6 +356,11 @@ void DCDeviceData::setDiveId(int diveId)
data.diveid = diveId; data.diveid = diveId;
} }
void DCDeviceData::setSyncTime(bool syncTime)
{
data.sync_time = syncTime;
}
void DCDeviceData::setSaveDump(bool save) void DCDeviceData::setSaveDump(bool save)
{ {
data.libdc_dump = save; data.libdc_dump = save;

View file

@ -32,6 +32,7 @@ public:
bool forceDownload() const; bool forceDownload() const;
bool saveLog() const; bool saveLog() const;
int diveId() const; int diveId() const;
bool syncTime() const;
/* this needs to be a pointer to make the C-API happy */ /* this needs to be a pointer to make the C-API happy */
device_data_t *internalData(); device_data_t *internalData();
@ -54,6 +55,7 @@ public:
#if defined(Q_OS_ANDROID) #if defined(Q_OS_ANDROID)
void setUsbDevice(const android_usb_serial_device_descriptor &usbDescriptor); void setUsbDevice(const android_usb_serial_device_descriptor &usbDescriptor);
#endif #endif
void setSyncTime(bool syncTime);
private: private:
#if defined(Q_OS_ANDROID) #if defined(Q_OS_ANDROID)
struct android_usb_serial_device_descriptor androidUsbDescriptor; struct android_usb_serial_device_descriptor androidUsbDescriptor;

View file

@ -1413,6 +1413,14 @@ dc_status_t divecomputer_device_open(device_data_t *data)
return DC_STATUS_UNSUPPORTED; return DC_STATUS_UNSUPPORTED;
} }
static dc_status_t sync_divecomputer_time(dc_device_t *device)
{
dc_datetime_t now;
dc_datetime_localtime(&now, dc_datetime_now());
return dc_device_timesync(device, &now);
}
const char *do_libdivecomputer_import(device_data_t *data) const char *do_libdivecomputer_import(device_data_t *data)
{ {
dc_status_t rc; dc_status_t rc;
@ -1463,6 +1471,28 @@ const char *do_libdivecomputer_import(device_data_t *data)
dev_info(data, "Starting import ..."); dev_info(data, "Starting import ...");
err = do_device_import(data); err = do_device_import(data);
/* TODO: Show the logfile to the user on error. */ /* TODO: Show the logfile to the user on error. */
dev_info(data, "Import complete");
if (!err && data->sync_time) {
dev_info(data, "Syncing dive computer time ...");
rc = sync_divecomputer_time(data->device);
switch (rc) {
case DC_STATUS_SUCCESS:
dev_info(data, "Time sync complete");
break;
case DC_STATUS_UNSUPPORTED:
dev_info(data, "Time sync not supported by dive computer");
break;
default:
dev_info(data, "Time sync failed");
break;
}
}
dc_device_close(data->device); dc_device_close(data->device);
data->device = NULL; data->device = NULL;
if (!data->log->dives->nr) if (!data->log->dives->nr)

View file

@ -46,6 +46,7 @@ typedef struct {
bool libdc_log; bool libdc_log;
bool libdc_dump; bool libdc_dump;
bool bluetooth_mode; bool bluetooth_mode;
bool sync_time;
FILE *libdc_logfile; FILE *libdc_logfile;
struct divelog *log; struct divelog *log;
void *androidUsbDeviceDescriptor; void *androidUsbDeviceDescriptor;

View file

@ -93,6 +93,7 @@ struct preferences default_prefs = {
.extract_video_thumbnails = true, .extract_video_thumbnails = true,
.extract_video_thumbnails_position = 20, // The first fifth seems like a reasonable place .extract_video_thumbnails_position = 20, // The first fifth seems like a reasonable place
.three_m_based_grid = false, .three_m_based_grid = false,
.sync_dc_time = false,
}; };
/* copy a preferences block, including making copies of all included strings */ /* copy a preferences block, including making copies of all included strings */

View file

@ -96,6 +96,7 @@ struct preferences {
dive_computer_prefs_t dive_computer2; dive_computer_prefs_t dive_computer2;
dive_computer_prefs_t dive_computer3; dive_computer_prefs_t dive_computer3;
dive_computer_prefs_t dive_computer4; dive_computer_prefs_t dive_computer4;
bool sync_dc_time;
// ********** Display ************* // ********** Display *************
bool display_invalid_dives; bool display_invalid_dives;

View file

@ -29,6 +29,8 @@ void qPrefDiveComputer::loadSync(bool doSync)
DISK_DC(2) DISK_DC(2)
DISK_DC(3) DISK_DC(3)
DISK_DC(4) DISK_DC(4)
disk_sync_dc_time(doSync);
} }
// these are the 'active' settings // these are the 'active' settings
@ -54,3 +56,5 @@ HANDLE_PREFERENCE_TXT_EXT_ALT(DiveComputer, "dive_computer_vendor1", vendor, div
HANDLE_PREFERENCE_TXT_EXT_ALT(DiveComputer, "dive_computer_vendor2", vendor, dive_computer, 2) HANDLE_PREFERENCE_TXT_EXT_ALT(DiveComputer, "dive_computer_vendor2", vendor, dive_computer, 2)
HANDLE_PREFERENCE_TXT_EXT_ALT(DiveComputer, "dive_computer_vendor3", vendor, dive_computer, 3) HANDLE_PREFERENCE_TXT_EXT_ALT(DiveComputer, "dive_computer_vendor3", vendor, dive_computer, 3)
HANDLE_PREFERENCE_TXT_EXT_ALT(DiveComputer, "dive_computer_vendor4", vendor, dive_computer, 4) HANDLE_PREFERENCE_TXT_EXT_ALT(DiveComputer, "dive_computer_vendor4", vendor, dive_computer, 4)
HANDLE_PREFERENCE_BOOL(DiveComputer, "sync_dive_computer_time", sync_dc_time);

View file

@ -35,6 +35,8 @@ class qPrefDiveComputer : public QObject {
Q_PROPERTY(QString vendor3 READ vendor3 WRITE set_vendor3 NOTIFY vendor3Changed) Q_PROPERTY(QString vendor3 READ vendor3 WRITE set_vendor3 NOTIFY vendor3Changed)
Q_PROPERTY(QString vendor4 READ vendor4 WRITE set_vendor4 NOTIFY vendor4Changed) Q_PROPERTY(QString vendor4 READ vendor4 WRITE set_vendor4 NOTIFY vendor4Changed)
Q_PROPERTY(bool sync_dc_time READ sync_dc_time WRITE set_sync_dc_time NOTIFY sync_dc_timeChanged)
public: public:
static qPrefDiveComputer *instance(); static qPrefDiveComputer *instance();
@ -49,6 +51,8 @@ public:
IMPLEMENT5GETTERS(product) IMPLEMENT5GETTERS(product)
IMPLEMENT5GETTERS(vendor) IMPLEMENT5GETTERS(vendor)
static bool sync_dc_time() { return prefs.sync_dc_time; }
public slots: public slots:
static void set_device(const QString &device); static void set_device(const QString &device);
static void set_device1(const QString &device); static void set_device1(const QString &device);
@ -74,6 +78,8 @@ public slots:
static void set_vendor3(const QString &vendor); static void set_vendor3(const QString &vendor);
static void set_vendor4(const QString &vendor); static void set_vendor4(const QString &vendor);
static void set_sync_dc_time(bool value);
signals: signals:
void deviceChanged(const QString &device); void deviceChanged(const QString &device);
void device1Changed(const QString &device); void device1Changed(const QString &device);
@ -99,6 +105,8 @@ signals:
void vendor3Changed(const QString &vendor); void vendor3Changed(const QString &vendor);
void vendor4Changed(const QString &vendor); void vendor4Changed(const QString &vendor);
void sync_dc_timeChanged(bool value);
private: private:
qPrefDiveComputer() {} qPrefDiveComputer() {}
@ -127,6 +135,8 @@ private:
static void disk_vendor2(bool doSync); static void disk_vendor2(bool doSync);
static void disk_vendor3(bool doSync); static void disk_vendor3(bool doSync);
static void disk_vendor4(bool doSync); static void disk_vendor4(bool doSync);
static void disk_sync_dc_time(bool doSync);
}; };
#endif #endif

View file

@ -1518,6 +1518,9 @@ const char *do_uemis_import(device_data_t *data)
if (uemis_mem_status != UEMIS_MEM_OK) if (uemis_mem_status != UEMIS_MEM_OK)
result = translate("gettextFromC", ERR_FS_ALMOST_FULL); result = translate("gettextFromC", ERR_FS_ALMOST_FULL);
if (data->sync_time)
uemis_info(translate("gettextFromC", "Time sync not supported by dive computer"));
bail: bail:
(void)uemis_get_answer(mountpath, "terminateSync", 0, 3, &result); (void)uemis_get_answer(mountpath, "terminateSync", 0, 3, &result);
if (!strcmp(param_buff[0], "error")) { if (!strcmp(param_buff[0], "error")) {

View file

@ -60,6 +60,7 @@ DownloadFromDCWidget::DownloadFromDCWidget(QWidget *parent) : QDialog(parent, QF
ui.vendor->setModel(&vendorModel); ui.vendor->setModel(&vendorModel);
ui.search->setEnabled(is_vendor_searchable(ui.vendor->currentText())); ui.search->setEnabled(is_vendor_searchable(ui.vendor->currentText()));
ui.product->setModel(&productModel); ui.product->setModel(&productModel);
ui.syncDiveComputerTime->setChecked(prefs.sync_dc_time);
progress_bar_text = ""; progress_bar_text = "";
@ -72,6 +73,7 @@ DownloadFromDCWidget::DownloadFromDCWidget(QWidget *parent) : QDialog(parent, QF
connect(ui.logToFile, SIGNAL(stateChanged(int)), this, SLOT(checkLogFile(int))); connect(ui.logToFile, SIGNAL(stateChanged(int)), this, SLOT(checkLogFile(int)));
connect(ui.selectAllButton, SIGNAL(clicked()), diveImportedModel, SLOT(selectAll())); connect(ui.selectAllButton, SIGNAL(clicked()), diveImportedModel, SLOT(selectAll()));
connect(ui.unselectAllButton, SIGNAL(clicked()), diveImportedModel, SLOT(selectNone())); connect(ui.unselectAllButton, SIGNAL(clicked()), diveImportedModel, SLOT(selectNone()));
connect(ui.syncDiveComputerTime, &QAbstractButton::toggled, qPrefDiveComputer::instance(), &qPrefDiveComputer::set_sync_dc_time);
connect(timer, SIGNAL(timeout()), this, SLOT(updateProgressBar())); connect(timer, SIGNAL(timeout()), this, SLOT(updateProgressBar()));
connect(close, SIGNAL(activated()), this, SLOT(close())); connect(close, SIGNAL(activated()), this, SLOT(close()));
connect(quit, SIGNAL(activated()), parent, SLOT(close())); connect(quit, SIGNAL(activated()), parent, SLOT(close()));
@ -419,6 +421,7 @@ void DownloadFromDCWidget::on_downloadCancelRetryButton_clicked()
data->setForceDownload(ui.forceDownload->isChecked()); data->setForceDownload(ui.forceDownload->isChecked());
data->setSaveLog(ui.logToFile->isChecked()); data->setSaveLog(ui.logToFile->isChecked());
data->setSaveDump(ui.dumpToFile->isChecked()); data->setSaveDump(ui.dumpToFile->isChecked());
data->setSyncTime(ui.syncDiveComputerTime->isChecked());
qPrefDiveComputer::set_vendor(data->vendor()); qPrefDiveComputer::set_vendor(data->vendor());
qPrefDiveComputer::set_product(data->product()); qPrefDiveComputer::set_product(data->product());
@ -582,6 +585,7 @@ void DownloadFromDCWidget::markChildrenAsDisabled()
ui.unselectAllButton->setEnabled(false); ui.unselectAllButton->setEnabled(false);
ui.bluetoothMode->setEnabled(false); ui.bluetoothMode->setEnabled(false);
ui.chooseBluetoothDevice->setEnabled(false); ui.chooseBluetoothDevice->setEnabled(false);
ui.syncDiveComputerTime->setEnabled(false);
} }
void DownloadFromDCWidget::markChildrenAsEnabled() void DownloadFromDCWidget::markChildrenAsEnabled()
@ -605,6 +609,7 @@ void DownloadFromDCWidget::markChildrenAsEnabled()
ui.bluetoothMode->setEnabled(true); ui.bluetoothMode->setEnabled(true);
ui.chooseBluetoothDevice->setEnabled(true); ui.chooseBluetoothDevice->setEnabled(true);
#endif #endif
ui.syncDiveComputerTime->setEnabled(true);
} }
#if defined(BT_SUPPORT) #if defined(BT_SUPPORT)

View file

@ -85,6 +85,7 @@ private:
BtDeviceSelectionDialog *btDeviceSelectionDialog; BtDeviceSelectionDialog *btDeviceSelectionDialog;
BTDiscovery *btd; BTDiscovery *btd;
#endif #endif
void setSyncDiveComputerTime(bool value);
public: public:
bool preferDownloaded(); bool preferDownloaded();

View file

@ -185,6 +185,16 @@
</item> </item>
</layout> </layout>
</item> </item>
<item row="13" column="0">
<widget class="QCheckBox" name="syncDiveComputerTime">
<property name="text">
<string>Sync dive computer time</string>
</property>
<property name="toolTip">
<string>Adjust the time on the dive computer to match the time on the PC (if supported by the dive computer model).</string>
</property>
</widget>
</item>
</layout> </layout>
</item> </item>
<item> <item>

View file

@ -397,7 +397,7 @@ Kirigami.Page {
} }
RowLayout { RowLayout {
id: downloadOptions id: forceDownloadOption
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 0 Layout.topMargin: 0
spacing: Kirigami.Units.smallSpacing spacing: Kirigami.Units.smallSpacing
@ -421,6 +421,31 @@ Kirigami.Page {
} }
} }
RowLayout {
id: syncTimeOption
Layout.fillWidth: true
Layout.topMargin: 0
spacing: Kirigami.Units.smallSpacing
TemplateCheckBox {
id: syncTimeWithDiveComputer
checked: Backend.sync_dc_time
enabled: syncTimeLabel.visible
visible: enabled
height: syncTimeLabel.height - Kirigami.Units.smallSpacing;
width: height
onClicked: {
Backend.sync_dc_time = checked
}
}
TemplateLabel {
id: syncTimeLabel
text: qsTr("Sync dive computer time")
visible: comboVendor.currentIndex != -1 && comboProduct.currentIndex != -1 &&
comboConnection.currentIndex != -1
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
}
}
ListView { ListView {
id: dlList id: dlList
Layout.topMargin: Kirigami.Units.smallSpacing * 4 Layout.topMargin: Kirigami.Units.smallSpacing * 4

View file

@ -79,6 +79,9 @@ QMLInterface::QMLInterface()
this, &QMLInterface::verbatim_planChanged); this, &QMLInterface::verbatim_planChanged);
connect(qPrefDivePlanner::instance(), &qPrefDivePlanner::display_variationsChanged, connect(qPrefDivePlanner::instance(), &qPrefDivePlanner::display_variationsChanged,
this, &QMLInterface::display_variationsChanged); this, &QMLInterface::display_variationsChanged);
connect(qPrefDiveComputer::instance(), &qPrefDiveComputer::sync_dc_timeChanged,
this, &QMLInterface::sync_dc_timeChanged);
} }
void QMLInterface::setup(QQmlContext *ct) void QMLInterface::setup(QQmlContext *ct)

View file

@ -2,10 +2,12 @@
#ifndef QMLINTERFACE_H #ifndef QMLINTERFACE_H
#define QMLINTERFACE_H #define QMLINTERFACE_H
#include "core/qthelper.h" #include "core/qthelper.h"
#include "core/downloadfromdcthread.h"
#include "core/settings/qPrefCloudStorage.h" #include "core/settings/qPrefCloudStorage.h"
#include "core/settings/qPrefUnit.h" #include "core/settings/qPrefUnit.h"
#include "core/settings/qPrefDivePlanner.h" #include "core/settings/qPrefDivePlanner.h"
#include "core/settings/qPrefTechnicalDetails.h" #include "core/settings/qPrefTechnicalDetails.h"
#include "core/settings/qPrefDiveComputer.h"
#include "qt-models/diveplannermodel.h" #include "qt-models/diveplannermodel.h"
#include "backend-shared/plannershared.h" #include "backend-shared/plannershared.h"
@ -78,6 +80,8 @@ class QMLInterface : public QObject {
Q_PROPERTY(bool verbatim_plan READ verbatim_plan WRITE set_verbatim_plan NOTIFY verbatim_planChanged); Q_PROPERTY(bool verbatim_plan READ verbatim_plan WRITE set_verbatim_plan NOTIFY verbatim_planChanged);
Q_PROPERTY(bool display_variations READ display_variations WRITE set_display_variations NOTIFY display_variationsChanged); Q_PROPERTY(bool display_variations READ display_variations WRITE set_display_variations NOTIFY display_variationsChanged);
Q_PROPERTY(bool sync_dc_time READ sync_dc_time WRITE set_sync_dc_time NOTIFY sync_dc_timeChanged);
public: public:
// function to do the needed setup // function to do the needed setup
static void setup(QQmlContext *ct); static void setup(QQmlContext *ct);
@ -212,6 +216,8 @@ public:
bool verbatim_plan() { return prefs.verbatim_plan; } bool verbatim_plan() { return prefs.verbatim_plan; }
bool display_variations() { return prefs.display_variations; } bool display_variations() { return prefs.display_variations; }
bool sync_dc_time() { return prefs.sync_dc_time; }
public slots: public slots:
void set_cloud_verification_status(CLOUD_STATUS value) { qPrefCloudStorage::set_cloud_verification_status(value); } void set_cloud_verification_status(CLOUD_STATUS value) { qPrefCloudStorage::set_cloud_verification_status(value); }
void set_duration_units(DURATION value) { qPrefUnits::set_duration_units((units::DURATION)value); } void set_duration_units(DURATION value) { qPrefUnits::set_duration_units((units::DURATION)value); }
@ -258,6 +264,10 @@ public slots:
void set_display_transitions(bool value) { DivePlannerPointsModel::instance()->setDisplayTransitions(value); } void set_display_transitions(bool value) { DivePlannerPointsModel::instance()->setDisplayTransitions(value); }
void set_verbatim_plan(bool value) { DivePlannerPointsModel::instance()->setVerbatim(value); } void set_verbatim_plan(bool value) { DivePlannerPointsModel::instance()->setVerbatim(value); }
void set_display_variations(bool value) { DivePlannerPointsModel::instance()->setDisplayVariations(value); } void set_display_variations(bool value) { DivePlannerPointsModel::instance()->setDisplayVariations(value); }
void set_sync_dc_time(bool value) {
qPrefDiveComputer::set_sync_dc_time(value);
DCDeviceData::instance()->setSyncTime(value);
}
QString firstDiveDate() { return get_first_dive_date_string(); } QString firstDiveDate() { return get_first_dive_date_string(); }
QString lastDiveDate() { return get_last_dive_date_string(); } QString lastDiveDate() { return get_last_dive_date_string(); }
@ -307,6 +317,8 @@ signals:
void display_transitionsChanged(bool value); void display_transitionsChanged(bool value);
void verbatim_planChanged(bool value); void verbatim_planChanged(bool value);
void display_variationsChanged(bool value); void display_variationsChanged(bool value);
void sync_dc_timeChanged(bool value);
private: private:
QMLInterface(); QMLInterface();
}; };