Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 54 additions & 50 deletions ttblue.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "bbatt.h"
#include "ttops.h"
#include "util.h"
#include "ttblue.h"

const char *PLEASE_SETCAP_ME =
"**********************************************************\n"
Expand Down Expand Up @@ -228,12 +229,12 @@ read_gqf_status(TTDEV *ttd, int debug)
time_t last_update = 0;
uint8_t *fbuf;
int length;
if ((length=tt_read_file(ttd, ttd->files->gps_status, debug, &fbuf)) < 0) {
fprintf(stderr, "WARNING: Could not read GPS status file 0x%08x from watch.\n", ttd->files->gps_status);
if ((length=tt_read_file(ttd, TTBLUE_FILE_GPS_STATUS, debug, &fbuf)) < 0) {
fprintf(stderr, "WARNING: Could not read GPS status file 0x%08x from watch.\n", TTBLUE_FILE_GPS_STATUS);
last_update = -1;
} else {
#ifdef DUMP_0x00020001
save_buf_to_file(make_tt_filename(ttd->files->gps_status, "bin"), "wxb", fbuf, length, 2, true);
save_buf_to_file(make_tt_filename(TTBLUE_FILE_GPS_STATUS, "bin"), "wxb", fbuf, length, 2, true);
#endif
if (length > 6 && (fbuf[0x02] | fbuf[0x03] | fbuf[0x04] | fbuf[0x05]) != 0) {
struct tm tmp = { .tm_mday = fbuf[0x05], .tm_mon = fbuf[0x04]-1, .tm_year = (((int)fbuf[0x02])<<8) + fbuf[0x03] - 1900 };
Expand Down Expand Up @@ -284,28 +285,29 @@ save_buf_to_file(const char *filename, const char *mode, const void *fbuf, int l
int debug=1;
int get_activities=0, set_time=0, update_gps=0, version=0, daemonize=0, new_pair=1;
int sleep_success=3600, sleep_fail=10;
uint32_t dev_code;
char dev_code[6];
char *read_code;
char *activity_store=".", *dev_address=NULL, *interface=NULL, *postproc=NULL, *gqf_url=GQF_GPS_URL;

struct poptOption options[] = {
{ "auto", 'a', POPT_ARG_NONE, NULL, 'a', "Same as --get-activities --update-gps --set-time --version" },
{ "get-activities", 0, POPT_ARG_NONE, &get_activities, 0, "Downloads and deletes .ttbin activity files from the watch" },
{ "set-time", 0, POPT_ARG_NONE, &set_time, 0, "Set time zone on the watch to match this computer" },
{ "activity-store", 's', POPT_ARG_STRING|POPT_ARGFLAG_SHOW_DEFAULT, &activity_store, 0, "Location to store .ttbin activity files", "PATH" },
{ "post", 'p', POPT_ARG_STRING, &postproc, 0, "Command to run (with .ttbin file as argument) for every activity file", "CMD" },
{ "update-gps", 0, POPT_ARG_NONE, NULL, 'G', "Download TomTom QuickFix update file and send it to the watch (if repeated, forces update even if not needed)" },
{ "glonass", 0, POPT_ARG_NONE, NULL, 'g', "Use TomTom's GLONASS version of QuickFix update file." },
{ "qf-url", 0, POPT_ARG_STRING, &gqf_url, 0, "Alternate URL for QuickFix update (ephemeris) file." },
{ "device", 'd', POPT_ARG_STRING, &dev_address, 0, "Bluetooth MAC address of the watch (E4:04:39:__:__:__); will use first TomTom device if unspecified", "MACADDR" },
{ "interface", 'i', POPT_ARG_STRING, &interface, 0, "Bluetooth HCI interface to use", "hciX" },
{ "code", 'c', POPT_ARG_INT, &dev_code, 'c', "6-digit pairing code for the watch (if already paired)", "NUMBER" },
{ "version", 'v', POPT_ARG_NONE, &version, 0, "Show watch firmware version and identifiers" },
{ "debug", 'D', POPT_ARG_NONE, 0, 'D', "Increase level of debugging output" },
{ "quiet", 'q', POPT_ARG_VAL, &debug, 0, "Suppress debugging output" },
{ "daemon", 0, POPT_ARG_NONE, &daemonize, 0, "Run as a daemon which will try to connect repeatedly" },
{ "wait-success", 'w', POPT_ARG_INT|POPT_ARGFLAG_SHOW_DEFAULT, &sleep_success, 0, "Wait time after successful connection to watch", "SECONDS" },
{ "wait-fail", 'W', POPT_ARG_INT|POPT_ARGFLAG_SHOW_DEFAULT, &sleep_fail, 10, "Wait time after failed connection to watch", "SECONDS" },
// { "no-config", 'C', POPT_ARG_NONE, &config, 0, "Do not load or save settings from ~/.ttblue config file" },
{ "auto", 'a', POPT_ARG_NONE, NULL, 0, "Same as --get-activities --update-gps --set-time --version" },
{ "get-activities", 0, POPT_ARG_NONE, &get_activities, 1, "Downloads and deletes .ttbin activity files from the watch" },
{ "set-time", 0, POPT_ARG_NONE, &set_time, 2, "Set time zone on the watch to match this computer" },
{ "activity-store", 's', POPT_ARG_STRING|POPT_ARGFLAG_SHOW_DEFAULT, &activity_store, 3, "Location to store .ttbin activity files", "PATH" },
{ "post", 'p', POPT_ARG_STRING, &postproc, 4, "Command to run (with .ttbin file as argument) for every activity file", "CMD" },
{ "update-gps", 0, POPT_ARG_NONE, NULL, 5, "Download TomTom QuickFix update file and send it to the watch (if repeated, forces update even if not needed)" },
{ "glonass", 0, POPT_ARG_NONE, NULL, 6, "Use TomTom's GLONASS version of QuickFix update file." },
{ "qf-url", 0, POPT_ARG_STRING, &gqf_url, 7, "Alternate URL for QuickFix update (ephemeris) file." },
{ "device", 'd', POPT_ARG_STRING, &dev_address, 8, "Bluetooth MAC address of the watch (E4:04:39:__:__:__); will use first TomTom device if unspecified", "MACADDR" },
{ "interface", 'i', POPT_ARG_STRING, &interface, 9, "Bluetooth HCI interface to use", "hciX" },
{ "code", 'c', POPT_ARG_STRING, &read_code, 10, "6-digit pairing code for the watch (if already paired)", "NUMBER" },
{ "version", 'v', POPT_ARG_NONE, &version, 11, "Show watch firmware version and identifiers" },
{ "debug", 'D', POPT_ARG_NONE, 0, 12, "Increase level of debugging output" },
{ "quiet", 'q', POPT_ARG_VAL, &debug, 13, "Suppress debugging output" },
{ "daemon", 0, POPT_ARG_NONE, &daemonize, 14, "Run as a daemon which will try to connect repeatedly" },
{ "wait-success", 'w', POPT_ARG_INT|POPT_ARGFLAG_SHOW_DEFAULT, &sleep_success, 15, "Wait time after successful connection to watch", "SECONDS" },
{ "wait-fail", 'W', POPT_ARG_INT|POPT_ARGFLAG_SHOW_DEFAULT, &sleep_fail, 16, "Wait time after failed connection to watch", "SECONDS" },
// { "no-config", 'C', POPT_ARG_NONE, &config, 17, "Do not load or save settings from ~/.ttblue config file" },
POPT_AUTOHELP
POPT_TABLEEND
};
Expand All @@ -323,16 +325,15 @@ int main(int argc, const char **argv)

// parse args
int ch;
char dangling;
poptContext optCon = poptGetContext(NULL, argc, argv, options, 0);

while ((ch=poptGetNextOpt(optCon))>=0) {
switch (ch) {
case 'c': new_pair=false; break;
case 'D': debug++; break;
case 'a': get_activities = update_gps = set_time = version = true; break;
case 'G': update_gps++; break;
case 'g': gqf_url = GQF_GLONASS_URL; break;
case 10: new_pair=false; break;
case 12: debug++; break;
case 0 : get_activities = update_gps = set_time = version = true; break;
case 5 : update_gps++; break;
case 6 : gqf_url = GQF_GLONASS_URL; break;
}
}
if (ch<-1) {
Expand Down Expand Up @@ -368,6 +369,10 @@ int main(int argc, const char **argv)
char hostname[32];
gethostname(hostname, sizeof hostname);

if (read_code != NULL) {
strcpy(dev_code, read_code);
}

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style nitpick: please remove the braces, since there's only 1 statement in 'em.

// prompt user to put device in pairing mode
if (new_pair) {
fputs(PAIRING_MODE_PROMPT, stderr);
Expand Down Expand Up @@ -505,15 +510,12 @@ int main(int argc, const char **argv)
// prompt for pairing code
if (new_pair) {
fputs(PAIRING_CODE_PROMPT, stderr);
if (!(scanf("%d%c", &dev_code, &dangling) && isspace(dangling))) {
fprintf(stderr, "Pairing code should be 6-digit number.\n");
goto fatal;
}
fgets(dev_code, 7, stdin);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do the string-to-integer confusion here, using strtoul, rather than in the tt_authorize function.

If the code isn't a valid decimal integer, we want to catch and report it rather than silently change it to (int)0 (which is the effect of using atoi without any other validation).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll dust off my v1 and v2 device and have a look at this PR again when I get some time!

}

// authorize with the device
if (tt_authorize(ttd, dev_code) < 0) {
fprintf(stderr, "Device didn't accept pairing code %d.\n", dev_code);
if (tt_authorize(ttd, dev_code, new_pair) < 0) {
fprintf(stderr, "Device didn't accept pairing code %s.\n", dev_code);
if (first) goto fatal; else goto fail;
}

Expand All @@ -529,23 +531,25 @@ int main(int argc, const char **argv)
int length;

fprintf(stderr, "Setting PHONE menu to '%s'.\n", hostname);
tt_delete_file(ttd, ttd->files->hostname);
tt_write_file(ttd, ttd->files->hostname, false, (uint8_t*)hostname, strlen(hostname), write_delay);
tt_delete_file(ttd, TTBLUE_FILE_HOSTNAME1);
tt_write_file(ttd, TTBLUE_FILE_HOSTNAME1, false, (uint8_t*)hostname, strlen(hostname), write_delay);
tt_delete_file(ttd, TTBLUE_FILE_HOSTNAME2); // Write name to two files as V1 and V2 devices seem to use different files
tt_write_file(ttd, TTBLUE_FILE_HOSTNAME2, false, (uint8_t*)hostname, strlen(hostname), write_delay);

#ifdef DUMP_0x000f20000
fprintf(stderr, "Reading preference file 0x%08x from watch...\n", ttd->files->preference);
if ((length=tt_read_file(ttd, ttd->files->preference, debug, &fbuf)) < 0) {
fprintf(stderr, "WARNING: Could not read preferences file 0x%08x from watch.\n", ttd->files->preference);
fprintf(stderr, "Reading preference file 0x%08x from watch...\n", TTBLUE_FILE_PREFERENCES_XML);
if ((length=tt_read_file(ttd, TTBLUE_FILE_PREFERENCES_XML, debug, &fbuf)) < 0) {
fprintf(stderr, "WARNING: Could not read preferences file 0x%08x from watch.\n", TTBLUE_FILE_PREFERENCES_XML);
} else {
save_buf_to_file(make_tt_filename(ttd->files->preference, "xml"), "wxb", fbuf, length, 2, true);
save_buf_to_file(make_tt_filename(TTBLUE_FILE_PREFERENCES_XML, "xml"), "wxb", fbuf, length, 2, true);
free(fbuf);
}
#endif

if (set_time) {
fprintf(stderr, "Checking watch settings manifest file 0x%08x...\n", ttd->files->manifest);
if ((length = tt_read_file(ttd, ttd->files->manifest, debug, &fbuf)) < 0) {
fprintf(stderr, "WARNING: Could not read settings manifest file 0x%08x from watch!\n", ttd->files->manifest);
fprintf(stderr, "Checking watch settings manifest file 0x%08x...\n", TTBLUE_FILE_MANIFEST1);
if ((length = tt_read_file(ttd, TTBLUE_FILE_MANIFEST1, debug, &fbuf)) < 0) {
fprintf(stderr, "WARNING: Could not read settings manifest file 0x%08x from watch!\n", TTBLUE_FILE_MANIFEST1);
} else {
// based on: https://github.com/ryanbinns/ttwatch/tree/master/manifest
// the position of the UTC-offset in the manifest is 169 in all known firmware versions, so lazily hard-coded here for now
Expand All @@ -565,8 +569,8 @@ int main(int argc, const char **argv)
if (btohl(*watch_timezone) != lt->tm_gmtoff) {
fprintf(stderr, " Changing timezone from UTC%+d to UTC%+ld.\n", btohl(*watch_timezone), lt->tm_gmtoff);
*watch_timezone = htobl(lt->tm_gmtoff);
tt_delete_file(ttd, ttd->files->manifest);
tt_write_file(ttd, ttd->files->manifest, false, fbuf, length, write_delay);
tt_delete_file(ttd, TTBLUE_FILE_MANIFEST1);
tt_write_file(ttd, TTBLUE_FILE_MANIFEST1, false, fbuf, length, write_delay);
needs_reboot = true;
}
}
Expand All @@ -576,15 +580,15 @@ int main(int argc, const char **argv)

if (get_activities) {
uint16_t *list;
int n_files = tt_list_sub_files(ttd, ttd->files->activity_start, &list);
int n_files = tt_list_sub_files(ttd, TTBLUE_FILE_TTBIN_DATA, &list);

if (n_files < 0) {
fprintf(stderr, "Could not list activity files on watch!\n");
goto fail;
}
fprintf(stderr, "Found %d activity files on watch.\n", n_files);
for (int ii=0; ii<n_files; ii++) {
uint32_t fileno = ttd->files->activity_start + list[ii];
uint32_t fileno = TTBLUE_FILE_TTBIN_DATA + list[ii];

fprintf(stderr, " Reading activity file 0x%08X ...\n", fileno);
term_title("ttblue: Transferring activity %d/%d", ii+1, n_files);
Expand Down Expand Up @@ -668,8 +672,8 @@ int main(int argc, const char **argv)
goto fail;
} else {
fclose (f);
tt_delete_file(ttd, ttd->files->quickgps);
result = tt_write_file(ttd, ttd->files->quickgps, debug, fbuf, length, write_delay);
tt_delete_file(ttd, TTBLUE_FILE_GPSQUICKFIX_DATA);
result = tt_write_file(ttd, TTBLUE_FILE_GPSQUICKFIX_DATA, debug, fbuf, length, write_delay);
free(fbuf);
if (result < 0) {
fputs("Failed to send QuickFixGPS update to watch.\n", stderr);
Expand All @@ -678,7 +682,7 @@ int main(int argc, const char **argv)
// official TomTom Android app seems to only issue this
// "magic" update command when the GPS is brand new or
// after a factory reset, or with 3x --update-gps
att_wrreq(ttd->fd, ttd->h->cmd_status, BARRAY(0x05, 0x01, 0x00, 0x01), 4);
att_wrreq(ttd->fd, ttd->h->cmd_status, BARRAY(MSG_UPDATE_EPHEMERIS, 0x01, 0x00, 0x01), 4);

time_t last_gqf_update = read_gqf_status(ttd, debug-1);
if (last_gqf_update != -1 && last_gqf_update != 0)
Expand Down
42 changes: 42 additions & 0 deletions ttblue.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#ifndef __TTBLUE_H__
#define __TTBLUE_H__
/*****************************************************************************/

/******************************************************************************
* Messages *
******************************************************************************/
#define MSG_OPEN_FILE_WRITE (0x02)
Copy link
Contributor Author

@Grimler91 Grimler91 Dec 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0x02 is defined twice in the list, seem to be an error (this line should probably be deleted)

#define MSG_WRITE (0x00)
#define MSG_READ (0x01)
#define MSG_CANCEL_TRANSFER (0x02)
#define MSG_LIST_FILES (0x03)
#define MSG_DELETE (0x04)
#define MSG_UPDATE_EPHEMERIS (0x05) // Used on the quickgps file
#define MSG_UPDATE_GOLF_MAPS (0x06) // No idea how this one works
#define MSG_RESET_DEVICE (0x07)
#define MSG_SET_TIME (0x08)
#define MSG_UIPROD (0x09) // Used on the rest_proto_file and for phone notifications
#define MSG_WRITE_RECOVERABLE (0x0a) // Used on V2 devices instead of MSG_WRITE
#define MSG_READ_RECOVERABLE (0x0b) // Used on V2 devices instead of MSG_READ

/******************************************************************************
* File IDs *
******************************************************************************/
#define TTBLUE_FILE_NOTIFICATION (0x00b50000) // for text messages, phone calls
#define TTBLUE_FILE_GPS_STATUS (0x00020001)
#define TTBLUE_FILE_HOSTNAME1 (0x00020002) // Hostname on V1 devices
#define TTBLUE_FILE_HOSTNAME2 (0x00020003) // Hostname on V2 devices (offical
// tomtom app only sets this one)
#define TTBLUE_FILE_GOLF_MANIFEST (0x00b00000)
#define TTBLUE_FILE_STEP_BUCKET (0x00b10000)
#define TTBLUE_FILE_GOLF_SCORECARDS (0x00940000) // Also called GOLF_ROUNDS?
#define TTBLUE_FILE_REST_PROTO_FILE (0x00890000) // Only file uiprod command is used
// on except notification files
#define TTBLUE_FILE_FIRMWARE_CHUNK (0x00fd0000) // Used when doing OTA updates?
#define TTBLUE_FILE_BRIDGEHEAD (0x00000000) // ??
#define TTBLUE_FILE_MANIFEST1 (0x00850000)
#define TTBLUE_FILE_PREFERENCES_XML (0x00f20000)
#define TTBLUE_FILE_TTBIN_DATA (0x00910000)
#define TTBLUE_FILE_GPSQUICKFIX_DATA (0x00010100)

#endif // __TTBLUE_H__
Loading