From d33ae35ee8d782cbad8a3404784ce1c5ceabfdff Mon Sep 17 00:00:00 2001 From: Jordan Miller Date: Sat, 12 Dec 2009 08:33:43 -0600 Subject: [PATCH] Adding Chris Turchin's Python Monitor --- GantMonitor.glade | 244 +++++++++ GantMonitor.py | 167 ++++++ Makefile~ | 13 + antlib.c~ | 620 +++++++++++++++++++++++ antlib.o | Bin 0 -> 43032 bytes auth405 | Bin 0 -> 24 bytes gant | Bin 0 -> 78348 bytes gant.c.orig | 1182 +++++++++++++++++++++++++++++++++++++++++++ gant.c~ | 1182 +++++++++++++++++++++++++++++++++++++++++++ gant.o | Bin 0 -> 86320 bytes output | 408 +++++++++++++++ patch_20090124.diff | 90 ++++ resources/gant.png | Bin 0 -> 724 bytes 13 files changed, 3906 insertions(+) create mode 100644 GantMonitor.glade create mode 100755 GantMonitor.py create mode 100644 Makefile~ create mode 100644 antlib.c~ create mode 100644 antlib.o create mode 100644 auth405 create mode 100755 gant create mode 100644 gant.c.orig create mode 100644 gant.c~ create mode 100644 gant.o create mode 100644 output create mode 100644 patch_20090124.diff create mode 100644 resources/gant.png diff --git a/GantMonitor.glade b/GantMonitor.glade new file mode 100644 index 0000000..73db308 --- /dev/null +++ b/GantMonitor.glade @@ -0,0 +1,244 @@ + + + + + + Gant Settings + 318 + 260 + menu + + + True + + + True + 3 + 2 + + + True + 0 + label22 + + + + + True + 0 + label23 + + + 1 + 2 + + + + + True + 0 + label24 + + + 2 + 3 + + + + + True + + + 1 + 2 + + + + + True + + + 1 + 2 + 1 + 2 + + + + + True + + + 1 + 2 + 2 + 3 + + + + + 2 + + + + + True + end + + + gtk-ok + 1 + True + False + False + True + top + + + False + False + 0 + + + + + gtk-cancel + True + False + False + True + + + False + False + 1 + + + + + False + end + 0 + + + + + + + 5 + Gant Monitor: Downloading data... + center + 415 + 140 + True + resources/gant.png + normal + + + True + vertical + 2 + + + True + vertical + + + True + Gant status: + start + True + + + False + 0 + + + + + True + + + False + 1 + + + + + True + True + + + + + + + True + Details + + + label_item + + + + + False + 2 + + + + + 1 + + + + + True + end + + + Restart + 4 + True + True + True + + + + False + False + 0 + + + + + gtk-stop + True + True + True + True + right + + + + False + False + 1 + + + + + False + end + 0 + + + + + + + + + + + diff --git a/GantMonitor.py b/GantMonitor.py new file mode 100755 index 0000000..5af7ea4 --- /dev/null +++ b/GantMonitor.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python + +import sys +import os +import time + +import gtk +import gtk.glade +import gobject +import vte + +import pygtk +pygtk.require("2.0") + +class DownloadDialog: + """This class is used to show DownloadDialog""" + + def __init__(self): + self.gladefile = "GantMonitor.glade" + self.wTree = gtk.glade.XML(self.gladefile) + + def run(self): + """Configures and runs the download dialog""" + self.wTree = gtk.glade.XML(self.gladefile, "DownloadDialog") + events = { "on_expand_details" : self.on_expand_details, "on_restart_process":self.on_restart_process} + self.wTree.signal_autoconnect(events) + + self.dlg = self.wTree.get_widget("DownloadDialog") + + self.close_button = self.wTree.get_widget("btn_close") + self.close_button.set_use_stock(True) + + self.details_section = self.wTree.get_widget("details_section") + self.status_label = self.wTree.get_widget("lbl_status") + + self.progress_bar = self.wTree.get_widget("progressbar") + self.progress_bar.pulse() + self.timeout_handler_id = gobject.timeout_add(100, self.update_progress_bar) + self.start = time.time() + + terminal= vte.Terminal() + terminal.connect("show", self.on_show_terminal) + terminal.connect('child-exited', self.on_child_exited) + self.details_section.add(terminal) + + self.dlg.show_all() + self.result = self.dlg.run() + self.dlg.destroy() + + def update_progress_bar(self): + self.progress_bar.pulse() + self.status_label.set_text("Gant running... (pid: " + str(self.child_pid) + ") " + time.asctime() ) + return True + + def on_show_terminal(self, terminal): + self.start_gant(terminal) + + def start_gant(self, terminal): + self.child_pid = terminal.fork_command("./gant" , argv = [' -p'] ) + + def on_child_exited(self, child): + """Updates label after download complete""" + child.destroy() + self.status_label.set_text("Gant download complete!") + self.close_button.set_label(gtk.STOCK_CLOSE) + self.close_button.set_use_stock(True) + print "gant exited" + + # doesn't work, dunno why... + def on_expand_details(self, expander): + if not expander.get_expanded(): + self.dlg.resize(415,130) + + #doesn't work, i think gtk dialog buttons are wired to only close the dialog... refactor? + def on_restart_process(self, button): + os.kill(self.child_id, signal.SIGSTOP) + self.start_gant(self.terminal) + +class SettingsDialog: + """This class is used to show SettingsDialog, which has no purpose (yet)""" + + def __init__(self): + self.gladefile = "GantMonitor.glade" + self.wTree = gtk.glade.XML(self.gladefile) + + def run(self): + self.wTree = gtk.glade.XML(self.gladefile, "SettingsDialog") + self.dlg = self.wTree.get_widget("SettingsDialog") + self.result = self.dlg.run() + self.dlg.destroy() + +class GantMonitorStatusIcon(gtk.StatusIcon): + """This class is used to show the tray icon and the menu""" + + def __init__(self): + gtk.StatusIcon.__init__(self) + icon_filename = 'resources/gant.png' + menu = ''' + + + + + + + + + + + + ''' + + actions = [ + ('Menu', None, 'Menu'), + ('Download',icon_filename, '_Download...', None, 'Download data using Gant', self.on_download), + ('Preferences', gtk.STOCK_PREFERENCES, '_Preferences...', None, 'Change Gant Monitor preferences', self.on_preferences), + ('About', gtk.STOCK_ABOUT, '_About...', None, 'About Gant Monitor', self.on_about), + ('Exit', gtk.STOCK_QUIT, '_Exit...', None, 'Exit Gant Monitor', self.on_exit) + ] + + ag = gtk.ActionGroup('Actions') + ag.add_actions(actions) + + self.manager = gtk.UIManager() + self.manager.insert_action_group(ag, 0) + self.manager.add_ui_from_string(menu) + self.menu = self.manager.get_widget('/Menubar/Menu/About').props.parent + + search = self.manager.get_widget('/Menubar/Menu/Download') + search.get_children()[0].set_markup('_Download...') + search.get_children()[0].set_use_underline(True) + search.get_children()[0].set_use_markup(True) + + self.set_from_file(icon_filename) + self.set_tooltip('Gant Monitor') + self.set_visible(True) + self.connect('activate', self.on_download) + self.connect('popup-menu', self.on_popup_menu) + + def on_download(self, data): + downloadDialog=DownloadDialog() + downloadDialog.run() + + def on_exit(self, data): + gtk.main_quit() + + def on_popup_menu(self, status, button, time): + self.menu.popup(None, None, None, button, time) + + # configure default cmd line params and serialize to xml? + def on_preferences(self, data): + print 'Gant preferences - todo' + settings = SettingsDialog() + settings.run() + print settings.result + + def on_about(self, data): + dialog = gtk.AboutDialog() + dialog.set_name('Gant Monitor') + dialog.set_version('0.1.0') + dialog.set_comments('A tray icon to start Gant') + dialog.set_website('http://cgit.get-open.com/cgit.cgi/gant/') + dialog.run() + dialog.destroy() + +if __name__ == '__main__': + GantMonitorStatusIcon() + gtk.main() diff --git a/Makefile~ b/Makefile~ new file mode 100644 index 0000000..568db2c --- /dev/null +++ b/Makefile~ @@ -0,0 +1,13 @@ +CFLAGS=-g -Werror -m32 +LDFLAGS=-lpthread -lm + +all: gant + +gant: gant.o antlib.o + +gant.o: gant.c antdefs.h + +antlib.o: antlib.c antdefs.h + +clean: + rm *.o gant diff --git a/antlib.c~ b/antlib.c~ new file mode 100644 index 0000000..93d3213 --- /dev/null +++ b/antlib.c~ @@ -0,0 +1,620 @@ +// copyright 2008 paul@ant.sbrk.co.uk. released under GPLv3 +// vers 0.6t +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define __declspec(X) + +#include "antdefs.h" +//#include "antdefines.h" +//#include "ANT_Interface.h" +//#include "antmessage.h" +//#include "anttypes.h" + +#define S(e) if (-1 == (e)) {perror(#e);exit(1);} else + +#define MAXMSG 30 // SYNC,LEN,MSG,data[9+],CHKSUM +#define MAXCHAN 32 +#define BSIZE 8*10000 + +#define uchar unsigned char + +#define hexval(c) ((c >= '0' && c <= '9') ? (c-'0') : ((c&0xdf)-'A'+10)) + +static int fd = -1; +static int dbg = 0; +static pthread_t commthread;; +static int commenabled = 1; + +static RESPONSE_FUNC rfn = 0; +static uchar *rbufp; +static CHANNEL_EVENT_FUNC cfn = 0; +static uchar *cbufp; + +static uchar *blast; +static int blsize; + +struct msg_queue { + uchar msgid; + uchar len; +}; + +// send message over serial port +uchar +msg_send(uchar mesg, uchar *inbuf, uchar len) +{ + uchar buf[MAXMSG]; + ssize_t nw; + int i; + uchar chk = MESG_TX_SYNC; + + buf[0] = MESG_TX_SYNC; + buf[1] = len; chk ^= len; + buf[2] = mesg; chk ^= mesg; + for (i = 0; i < len; i++) { + buf[3+i] = inbuf[i]; + chk ^= inbuf[i]; + } + buf[3+i] = chk; + usleep(10*1000); + if (4+i != (nw=write(fd, buf, 4+i))) { + if (dbg) { + perror("failed write"); + } + } + return 1; +} + +// two argument send +uchar +msg_send2(uchar mesg, uchar data1, uchar data2) +{ + uchar buf[2]; + buf[0] = data1; + buf[1] = data2; + return msg_send(mesg, buf, 2); +} + +// three argument send +uchar +msg_send3(uchar mesg, uchar data1, uchar data2, uchar data3) +{ + uchar buf[3]; + buf[0] = data1; + buf[1] = data2; + buf[2] = data3; + return msg_send(mesg, buf, 3); +} + +void *commfn(void* arg) +{ + fd_set readfds, writefds, exceptfds; + int ready; + struct timeval to; + + for(;;) { + FD_ZERO(&readfds); + FD_ZERO(&writefds); + FD_ZERO(&exceptfds); + FD_SET(fd, &readfds); + to.tv_sec = 1; + to.tv_usec = 0; + ready = select(fd+1, &readfds, &writefds, &exceptfds, &to); + if (ready) { + get_data(fd); + } + } +} + +get_data(int fd) +{ + static uchar buf[500]; + static int bufc = 0; + int nr; + int dlen; + int i; + int j; + unsigned char chk = 0; + uchar event; + int found; + int srch; + int next; + + nr = read(fd, buf+bufc, 20); + if (nr > 0) + bufc += nr; + else + return; + if (bufc > 30) { + if (dbg) + fprintf(stderr, "bufc %d\n", bufc); + } + if (bufc > 300) { + fprintf(stderr, "buf too long %d\n", bufc); + for (j = 0; j < bufc; j++) + fprintf(stderr, "%02x", buf[j]); + fprintf(stderr, "\n"); + exit(1); + } + + // some data in buf + // search for possible valid messages + srch = 0; + while (srch < bufc) { + found = 0; + //printf("srch %d bufc %d\n", srch, bufc); + for (i = srch; i < bufc; i++) { + if (buf[i] == MESG_TX_SYNC) { + //fprintf(stderr, "bufc %d sync %d\n", bufc, i); + if (i+1 < bufc && buf[i+1] >= 1 && buf[i+1] <= 13) { + dlen = buf[i+1]; + if (i+3+dlen < bufc) { + chk = 0; + for (j = i; j <= i+3+dlen; j++) + chk ^= buf[j]; + if (0 == chk) { + found = 1; // got a valid message + break; + } else { + fprintf(stderr, "bad chk %02x\n", chk); + for (j = i; j <= i+3+dlen; j++) + fprintf(stderr, "%02x", buf[j]); + fprintf(stderr, "\n"); + } + } + } + } + } + if (found) { + next = j; + //printf("next %d %02x\n", next, buf[j-1]); + // got a valid message, see if any data needs to be discarded + if (i > srch) { + fprintf(stderr, "\nDiscarding: "); + for (j = 0; j < i; j++) + fprintf(stderr, "%02x", buf[j]); + fprintf(stderr, "\n"); + } + + if (dbg) { + fprintf(stderr, "data: "); + for(j = i; j < i+dlen+4; j++) { + fprintf(stderr, "%02x", buf[j]); + } + fprintf(stderr, "\n"); + } + event = 0; + switch (buf[i+2]) { + case MESG_RESPONSE_EVENT_ID: + //if (cfn) { + // memcpy(cbufp, buf+i+4, dlen); + // (*cfn)(buf[i+3], buf[i+5]); + // else + if (rfn) { + memcpy(rbufp, buf+i+3, dlen); + (*rfn)(buf[i+3], buf[i+5]); + } else { + if (dbg) + fprintf(stderr, "no rfn or cfn\n"); + } + break; + case MESG_BROADCAST_DATA_ID: + event = EVENT_RX_BROADCAST; + break; + case MESG_ACKNOWLEDGED_DATA_ID: + event = EVENT_RX_ACKNOWLEDGED; + break; + case MESG_BURST_DATA_ID: + event = EVENT_RX_BURST_PACKET; + // coalesce these and generate a fake event on last packet + // in case client wishes to ignore these events and capture the fake one + { + static uchar *burstbuf[MAXCHAN]; + static int bused[MAXCHAN]; + static int lseq[MAXCHAN]; + int k; + + uchar seq; + uchar last; + uchar chan = *(buf+i+3); + if (dbg) { + fprintf(stderr, "burst %02x ", chan); + for (k = 0; k < 12; k++) + fprintf(stderr, "%02x", buf[i+k]); + fprintf(stderr, "\n"); + } + seq = (chan & 0x60) >> 5; + last = (chan & 0x80) >> 7; + chan &= 0x1f; + if (dbg) fprintf(stderr, "ch %x seq %d last %d\n", chan, seq, last); + if (!burstbuf[chan]) { + if (seq != 0) + fprintf(stderr, "out of sequence ch# %d %d\n", chan, seq); + else { + burstbuf[chan] = malloc(BSIZE); + bzero(burstbuf[chan], BSIZE); + memcpy(burstbuf[chan], buf+i+4, 8); + bused[chan] = 8; + lseq[chan] = seq; + if (dbg) + fprintf(stderr, "init ch# %d %d\n", chan, lseq[chan]); + } + } else { + if (lseq[chan]+1 != seq) { + fprintf(stderr, "out of sequence ch# %d %d l %d\n", chan, seq, lseq[chan]); + free(burstbuf[chan]); + burstbuf[chan] = 0; + if (seq == 0) { + burstbuf[chan] = malloc(BSIZE); + bzero(burstbuf[chan], BSIZE); + memcpy(burstbuf[chan], buf+i+4, 8); + bused[chan] = 8; + lseq[chan] = seq; + fprintf(stderr, "reinit ch# %d %d\n", chan, lseq[chan]); + } + } else { + if ((bused[chan] % BSIZE) == 0) { + burstbuf[chan] = realloc(burstbuf[chan], bused[chan]+BSIZE); + bzero(burstbuf[chan]+bused[chan], BSIZE); + } + memcpy(burstbuf[chan]+bused[chan], buf+i+4, 8); + bused[chan] += 8; + if (dbg) fprintf(stderr, "seq0 %d lseq %d\n", seq, lseq[chan]); + if (seq == 3) + lseq[chan] = 0; + else + lseq[chan] = seq; + if (dbg) fprintf(stderr, "seq1 %d lseq %d\n", seq, lseq[chan]); + } + } + if (burstbuf[chan] && last) { + blast = burstbuf[chan]; + blsize = bused[chan]; + if (dbg) fprintf(stderr, "BU %d %lx\n", blsize, (long)blast); + if (dbg) { + fprintf(stderr, "bused ch# %d %d\n", chan, bused[chan]); + for (k = 0; k < bused[chan]; k++) + fprintf(stderr, "%02x", burstbuf[chan][k]); + fprintf(stderr, "\n"); + } + bused[chan] = 0; + burstbuf[chan] = 0; + } + } + break; + case MESG_EXT_BROADCAST_DATA_ID: + event = EVENT_RX_EXT_BROADCAST; + break; + case MESG_EXT_ACKNOWLEDGED_DATA_ID: + event = EVENT_RX_EXT_ACKNOWLEDGED; + break; + case MESG_EXT_BURST_DATA_ID: + event = EVENT_RX_EXT_BURST_PACKET; + break; + default: + if (rfn) { + // should be this according to the docs, but doesn't fit + // if (dlen > MESG_RESPONSE_EVENT_SIZE) { + if (dlen > MESG_DATA_SIZE) { + fprintf(stderr, "cresponse buffer too small %d > %d\n", dlen, MESG_DATA_SIZE); + for (j = 0; j < dlen; j++) + fprintf(stderr, "%02x", *(buf+i+3+j)); + fprintf(stderr, "\n"); + exit(1); + } + memcpy(rbufp, buf+i+3, dlen); + (*rfn)(buf[i+3], buf[i+2]); + } else { + if (dbg) + fprintf(stderr, "no rfn\n"); + } + } + if (event) { + uchar chan = *(buf+3) & 0x1f; + if (cfn) { + if (dlen > MESG_DATA_SIZE) { + fprintf(stderr, "rresponse buffer too small %d > %d\n", + dlen, MESG_DATA_SIZE); + for (j = 0; j < dlen+1; j++) + fprintf(stderr, "%02x", *(buf+i+4+j)); + fprintf(stderr, "\n"); + exit(1); + } + memcpy(cbufp, buf+i+4, dlen); + if (dbg) { + fprintf(stderr, "xch0#%d %d %lx\n", chan, blsize, blast); + for (j = 0; j < blsize; j++) + fprintf(stderr, "%02x", *(blast+j)); + fprintf(stderr, "\n"); + } + (*cfn)(chan, event); + if (dbg) { + fprintf(stderr, "xch1#%d %d %lx\n", chan, blsize, blast); + for (j = 0; j < blsize; j++) + fprintf(stderr, "%02x", *(blast+j)); + fprintf(stderr, "\n"); + } + // FAKE BURST message + if (event == EVENT_RX_BURST_PACKET && blast && blsize) { + if (dbg) { + fprintf(stderr, "Fake burst ch#%d %d %lx\n", chan, blsize, blast); + for (j = 0; j < blsize; j++) + fprintf(stderr, "%02x", *(blast+j)); + fprintf(stderr, "\n"); + } + *(int *)(cbufp+4) = blsize; + memcpy(cbufp+8, &blast, 4); + (*cfn)(chan, EVENT_RX_FAKE_BURST); + free(blast); + blast = 0; + blsize = 0; + } + } else + if (dbg) + fprintf(stderr, "no cfn\n"); + } + srch = next; + } else + break; + } + if (next < bufc) { + memmove(buf, buf+next, bufc-next); + bufc -= next; + } else + bufc = 0; +} + +uchar +ANT_ResetSystem(void) +{ + uchar filler = 0; + return msg_send(MESG_SYSTEM_RESET_ID, &filler, 1); +} + +uchar +ANT_Cmd55(uchar chan) +{ + return msg_send(0x55, &chan, 1); +} + +uchar +ANT_OpenRxScanMode(uchar chan) +{ + return msg_send(MESG_OPEN_RX_SCAN_ID, &chan, 1); +} + +uchar +ANT_Initf(char *devname, ushort baud) +{ + struct termios tp; + + fd = open(devname, O_RDWR); + if (fd < 0) { + perror(devname); + return 0; + } + + S(tcgetattr(fd, &tp)); + tp.c_iflag &= + ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON|IXOFF|IXANY|INPCK|IUCLC); + tp.c_oflag &= ~OPOST; + tp.c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN|ECHOE); + tp.c_cflag &= ~(CSIZE|PARENB); + tp.c_cflag |= CS8 | CLOCAL | CREAD | CRTSCTS; + + S(cfsetispeed(&tp, B115200)); + S(cfsetospeed(&tp, B115200)); + tp.c_cc[VMIN] = 1; + tp.c_cc[VTIME] = 0; + S(tcsetattr(fd, TCSANOW, &tp)); + + if (pthread_create(&commthread, 0, commfn, 0)); + return 1; +} + +uchar +ANT_Init(uchar devno, ushort baud) +{ + char dev[40]; + + sprintf(dev, "/dev/ttyUSB%d", devno); + return ANT_Initf(dev, devno); +} + +uchar +ANT_RequestMessage(uchar chan, uchar mesg) +{ + return msg_send2(MESG_REQUEST_ID, chan, mesg); +} + +uchar +ANT_SetNetworkKeya(uchar net, uchar *key) +{ + uchar buf[9]; + int i; + + if (strlen(key) != 16) { + fprintf(stderr, "Bad key length %s\n", key); + return 0; + } + buf[0] = net; + for (i = 0; i < 8; i++) + buf[1+i] = hexval(key[i*2])*16+hexval(key[i*2+1]); + return msg_send(MESG_NETWORK_KEY_ID, buf, 9); +} + +uchar +ANT_SetNetworkKey(uchar net, uchar *key) +{ + uchar buf[9]; + int i; + + buf[0] = net; + memcpy(buf+1, key, 8); + return msg_send(MESG_NETWORK_KEY_ID, buf, 9); +} + +uchar +ANT_AssignChannel(uchar chan, uchar chtype, uchar net) +{ + return msg_send3(MESG_ASSIGN_CHANNEL_ID, chan, chtype, net); +} + +uchar +ANT_UnAssignChannel(uchar chan) +{ + return msg_send(MESG_UNASSIGN_CHANNEL_ID, &chan, 1); +} + +uchar +ANT_SetChannelId(uchar chan, ushort dev, uchar devtype, uchar manid) +{ + uchar buf[5]; + buf[0] = chan; + buf[1] = dev%256; + buf[2] = dev/256; + buf[3] = devtype; + buf[4] = manid; + return msg_send(MESG_CHANNEL_ID_ID, buf, 5); +} + +uchar +ANT_SetChannelRFFreq(uchar chan, uchar freq) +{ + return msg_send2(MESG_CHANNEL_RADIO_FREQ_ID, chan, freq); +} + +uchar +ANT_SetChannelPeriod(uchar chan, ushort period) +{ + uchar buf[3]; + buf[0] = chan; + buf[1] = period%256; + buf[2] = period/256; + return msg_send(MESG_CHANNEL_MESG_PERIOD_ID, buf, 3); +} + +uchar +ANT_SetChannelSearchTimeout(uchar chan, uchar timeout) +{ + return msg_send2(MESG_CHANNEL_SEARCH_TIMEOUT_ID, chan, timeout); +} + +uchar +ANT_SetSearchWaveform(uchar chan, ushort waveform) +{ + uchar buf[3]; + buf[0] = chan; + buf[1] = waveform%256; + buf[2] = waveform/256; + return msg_send(MESG_SEARCH_WAVEFORM_ID, buf, 3); +} + +uchar +ANT_SendAcknowledgedDataA(uchar chan, uchar *data) // ascii version +{ + uchar buf[9]; + int i; + + if (strlen(data) != 16) { + fprintf(stderr, "Bad data length %s\n", data); + return 0; + } + buf[0] = chan; + for (i = 0; i < 8; i++) + buf[1+i] = hexval(data[i*2])*16+hexval(data[i*2+1]); + return msg_send(MESG_ACKNOWLEDGED_DATA_ID, buf, 9); +} + +uchar +ANT_SendAcknowledgedData(uchar chan, uchar *data) +{ + uchar buf[9]; + int i; + + buf[0] = chan; + memcpy(buf+1, data, 8); + return msg_send(MESG_ACKNOWLEDGED_DATA_ID, buf, 9); +} + +ushort +ANT_SendBurstTransferA(uchar chan, uchar *data, ushort numpkts) +{ + uchar buf[9]; + int i; + int j; + int seq = 0; + + if (dbg) fprintf(stderr, "numpkts %d data %s\n", numpkts, data); + if (strlen(data) != 16*numpkts) { + fprintf(stderr, "Bad data length %s numpkts %d\n", data, numpkts); + return 0; + } + for (j = 0; j < numpkts; j++) { + buf[0] = chan|(seq<<5)|(j==numpkts-1 ? 0x80 : 0); + for (i = 0; i < 8; i++) + buf[1+i] = hexval(data[j*16+i*2])*16+hexval(data[j*16+i*2+1]); + usleep(20*1000); + msg_send(MESG_BURST_DATA_ID, buf, 9); + seq++; if (seq > 3) seq = 1; + } + return numpkts; +} + +ushort +ANT_SendBurstTransfer(uchar chan, uchar *data, ushort numpkts) +{ + uchar buf[9]; + int i; + int j; + int seq = 0; + + for (j = 0; j < numpkts; j++) { + buf[0] = chan|(seq<<5)|(j==numpkts-1 ? 0x80 : 0); + memcpy(buf+1, data+j*8, 8); + usleep(20*1000); + msg_send(MESG_BURST_DATA_ID, buf, 9); + seq++; if (seq > 3) seq = 1; + } + return numpkts; +} + +uchar +ANT_OpenChannel(uchar chan) +{ + return msg_send(MESG_OPEN_CHANNEL_ID, &chan, 1); +} + +uchar +ANT_CloseChannel(uchar chan) +{ + return msg_send(MESG_CLOSE_CHANNEL_ID, &chan, 1); +} + +void +ANT_AssignResponseFunction(RESPONSE_FUNC rf, uchar* rbuf) +{ + rfn = rf; + rbufp = rbuf; +} + +void +ANT_AssignChannelEventFunction(uchar chan, CHANNEL_EVENT_FUNC rf, uchar* rbuf) +{ + cfn = rf; + cbufp = rbuf; +} + +int ANT_fd() +{ + return fd; +} + +// vim: se ts=2 sw=2: diff --git a/antlib.o b/antlib.o new file mode 100644 index 0000000000000000000000000000000000000000..a8ab4b05e1cb336142e7ba0fe7ea2c2ac1bc3096 GIT binary patch literal 43032 zcmeHw3w%`7)$f^^07n93Fe##d3{O!ZgqMIIgva0@52=s}zCuVQA(BkeWQG70LpxwI z29#8-*!mUhXIpDU@w*lgP=X@hC!a-2mD^H{iY*YdsD1D$EqATG*E+Li&m3!e@9+Nl z``x>B=Is67Yya2UYd_9Dd!Gr7!Pzss9*@PrV@DMl@;_4;JIP(=%^i>)43Q7G}j z{80S#(!_s<60t3b!%cIZXbL`D7HZBKGz8>AiQtJ5t^0#}9^D^oeKco#@ZqN5Uc3|p z_rL{YU&ITf4g_}~Z~M^Z;EoL;AAE;4g?zy+IotiA(!EU!I!#L+Ayazlz1(1KuLj}+d>m=-4^Yk zbJw*-Yxba+%55*xX#A`qx^;pxKqfNmh-~_m| z!R1I(@LjkZgSxft^deibg7JMrQaCH{#7v@lkmi@ zIp6+WI-Ik;H4$tBRWJc1Ui1~Uk$Zp}tD^ct@K_>vv^`h)xghulzGy`%

j0c6wbJjl%!!N%22#kY%UD2^H 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 literal 0 HcmV?d00001 diff --git a/auth405 b/auth405 new file mode 100644 index 0000000000000000000000000000000000000000..8fce41789894481c582331169e5f867f7a121623 GIT binary patch literal 24 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 N>tV)5-WhYT{taD{L1F*^ literal 0 HcmV?d00001 diff --git a/gant.c.orig b/gant.c.orig new file mode 100644 index 0000000..89a87c7 --- /dev/null +++ b/gant.c.orig @@ -0,0 +1,1182 @@ +// copyright 2008-2009 paul@ant.sbrk.co.uk. released under GPLv3 +// copyright 2009-2009 Wali +// vers 0.6t +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "antdefs.h" + +char *releasetime = "Jan 21 2008, 12:00:00"; +uint majorrelease = 0; +uint minorrelease = 6; + +double round(double); + +int gottype; +int sentauth; +int gotwatchid; +int nopairing; +int nowriteauth; +int reset; +int dbg = 0; +int seenphase0 = 0; +int lastphase; +int sentack2; +int newfreq = 0; +int period = 0x1000; // garmin specific broadcast period +int donebind = 0; +int sentgetv; +char *fname = "garmin"; + +static char ANTSPT_KEY[] = "A8A423B9F55E63C1"; // ANT+Sport key + +static uchar ebuf[MESG_DATA_SIZE]; // response event data gets stored here +static uchar cbuf[MESG_DATA_SIZE]; // channel event data gets stored here + +int passive; +int semipassive; +int verbose; + +int downloadfinished = 0; +int downloadstarted = 0; +int sentid = 0; + +uint mydev = 0; +uint peerdev; +uint myid; +uint devid; +uint myauth1; +uint myauth2; +char authdata[32]; +uint pairing; +uint isa50; +uint isa405; +uint waitauth; +int nphase0; +char modelname[256]; +ushort part = 0; +ushort ver = 0; +uint unitid = 0; + + +//char *getversion = "440dffff00000000fe00000000000000"; +//char *getgpsver = "440dffff0000000006000200ff000000"; +char *acks[] = { + "fe00000000000000", // get version - 255, 248, 253 + "0e02000000000000", // device short name (fr405a) - 525 +// "1c00020000000000", // no data + "0a0002000e000000", // unit id - 38 + "0a00020002000000", // send position + "0a00020005000000", // send time + "0a000200ad020000", // 4 byte something? 0x10270000 = 10000 dec - 1523 + "0a000200c6010000", // 3 x 4 ints? - 994 + "0a00020035020000", // guessing this is # trackpoints per run - 1066 + "0a00020097000000", // load of software versions - 247 + "0a000200c2010000", // download runs - 27 (#runs), 990?, 12? + "0a00020075000000", // download laps - 27 (#laps), 149 laps, 12? + "0a00020006000000", // download trackpoints - 1510/99(run marker), ..1510,12 + "0a000200ac020000", + "" +}; +int sentcmd; + +uchar clientid[3][8]; + +int authfd = -1; +char *authfile; +int outfd; // output file +char *fn = "default_output_file"; +char *progname; + +#define BSIZE 8*100 +uchar *burstbuf = 0; +uchar *blast = 0; +int blsize = 0; +int bused = 0; +int lseq = -1; + +/* round a float as garmin does it! */ +/* shoot me for writing this! */ +char * +ground(double d) +{ + int neg = 0; + static char res[30]; + ulong ival; + ulong l; /* hope it doesn't overflow */ + + if (d < 0) { + neg = 1; + d = -d; + } + ival = floor(d); + d -= ival; + l = floor(d*100000000); + if (l % 10 >= 5) + l = l/10+1; + else + l = l/10; + sprintf(res,"%s%ld.%07ld", neg?"-":"", ival, l); + return res; +} + +char * +timestamp(void) +{ + struct timeval tv; + static char time[50]; + struct tm *tmp; + + gettimeofday(&tv, 0); + tmp = gmtime(&tv.tv_sec); + + sprintf(time, "%02d:%02d:%02d.%02d", + tmp->tm_hour, tmp->tm_min, tmp->tm_sec, (int)tv.tv_usec/10000); + return time; +} + +uint +randno(void) +{ + uint r; + + int fd = open("/dev/urandom", O_RDONLY); + if (fd > 0) { + read(fd, &r, sizeof r); + close(fd); + } + return r; +} + +void +print_tcx_header(FILE *tcxfile) +{ + fprintf(tcxfile, "\n"); + fprintf(tcxfile, "\n\n"); + fprintf(tcxfile, " \n"); + return; +} + +void +print_tcx_footer(FILE *tcxfile) +{ + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " %s\n", modelname); + fprintf(tcxfile, " %u\n", unitid); + fprintf(tcxfile, " %u\n", part); + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " %u\n", ver/100); + fprintf(tcxfile, " %u\n", ver - ver/100*100); + fprintf(tcxfile, " 0\n"); + fprintf(tcxfile, " 0\n"); + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " \n\n"); + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " Garmin ANT for Linux\n"); + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " %u\n", majorrelease); + fprintf(tcxfile, " %u\n", minorrelease); + fprintf(tcxfile, " 0\n"); + fprintf(tcxfile, " 0\n"); + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " Release\n"); + fprintf(tcxfile, " \n", releasetime); + fprintf(tcxfile, " make\n"); + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " EN\n"); + fprintf(tcxfile, " 006-A0XXX-00\n"); + fprintf(tcxfile, " \n\n"); + fprintf(tcxfile, "\n"); + return; +} + + +#pragma pack(1) +struct ack_msg { + uchar code; + uchar atype; + uchar c1; + uchar c2; + uint id; +}; + +struct auth_msg { + uchar code; + uchar atype; + uchar phase; + uchar u1; + uint id; + uint auth1; + uint auth2; + uint fill1; + uint fill2; +}; + +struct pair_msg { + uchar code; + uchar atype; + uchar phase; + uchar u1; + uint id; + char devname[16]; +}; + +#pragma pack() +#define ACKSIZE 8 // above structure must be this size +#define AUTHSIZE 24 // ditto +#define PAIRSIZE 16 +#define MAXLAPS 256 // max of saving laps data before output with trackpoint data +#define MAXTRACK 256 // max number of tracks to be saved per download + +decode(ushort bloblen, ushort pkttype, ushort pktlen, int dsize, uchar *data) +{ + int i; + int j; + int hr; + int hr_av; + int hr_max; + int cal; + float tsec; + float max_speed; + int cad; + int u1, u2; + int doff = 20; + char model[256]; + char gpsver[256]; + char devname[256]; + float alt; + float dist; + uint tv; + uint tv_previous = 0; + time_t ttv; + char tbuf[100]; + struct tm *tmp; + double lat, lon; + uint nruns; + uint tv_lap; + static uchar lapbuf[MAXLAPS][48]; + static ushort lap = 0; + static ushort lastlap = 0; + static ushort track = 0; + static short previoustrack_id = -1; + static short track_id = -1; + static short firsttrack_id = -1; + static short firstlap_id = -1; + static ushort firstlap_id_track[MAXTRACK]; + static uchar sporttyp_track[MAXTRACK]; + static FILE *tcxfile = NULL; + static ushort track_pause = 0; + + + printf("decode %d %d %d %d\n", bloblen, pkttype, pktlen, dsize); + switch (pkttype) { + case 255: + memset(model, 0, sizeof model); + memcpy(model, data+doff+4, dsize-4); + part=data[doff]+data[doff+1]*256; + ver=data[doff+2]+data[doff+3]*256; + printf("%d Part#: %d ver: %d Name: %s\n", pkttype, + part, ver, model); + break; + case 248: + memset(gpsver, 0, sizeof gpsver); + memcpy(gpsver, data+doff, dsize); + printf("%d GPSver: %s\n", pkttype, + gpsver); + break; + case 253: + printf("%d Unknown\n", pkttype); + for (i = 0; i < pktlen; i += 3) + printf("%d.%d.%d\n", data[doff+i], data[doff+i+1], data[doff+i+2]); + break; + case 525: + memset(devname, 0, sizeof devname); + memcpy(devname, data+doff, dsize); + printf("%d Devname %s\n", pkttype, devname); + break; + case 12: + printf("%d xfer complete", pkttype); + for (i = 0; i < pktlen; i += 2) + printf(" %u", data[doff+i] + data[doff+i+1]*256); + printf("\n"); + switch (data[doff] + data[doff+1]*256) { + case 6: + // last file completed, add footer and close file + print_tcx_footer(tcxfile); + fclose(tcxfile); + break; + case 117: + break; + case 450: + break; + default: + break; + } + break; + case 38: + unitid = data[doff] + data[doff+1]*256 + + data[doff+2]*256*256 + data[doff+3]*256*256*256; + printf("%d unitid %u\n", pkttype, unitid); + break; + case 27: + nruns = data[doff] + data[doff+1] * 256; + printf("%d nruns %u\n", pkttype, nruns); + break; + case 1523: + case 994: + case 1066: + printf("%d ints?", pkttype); + for (i = 0; i < pktlen; i += 4) + printf(" %u", data[doff+i] + data[doff+i+1]*256 + + data[doff+i+2]*256*256 + data[doff+i+3]*256*256*256); + printf("\n"); + break; + case 14: + printf("%d time: ", pkttype); + printf("%02u-%02u-%u %02u:%02u:%02u\n", data[doff], data[doff+1], data[doff+2] + data[doff+3]*256, + data[doff+4], data[doff+6], data[doff+7]); + break; + case 17: + printf("%d position ? ", pkttype); + for (i = 0; i < pktlen; i += 4) + printf(" %u", data[doff+i] + data[doff+i+1]*256 + + data[doff+i+2]*256*256 + data[doff+i+3]*256*256*256); + printf("\n"); + break; + case 99: + printf("%d trackindex %u\n", pkttype, data[doff] + data[doff+1]*256); + printf("%d shorts?", pkttype); + for (i = 0; i < pktlen; i += 2) + printf(" %u", data[doff+i] + data[doff+i+1]*256); + printf("\n"); + track_id = data[doff] + data[doff+1]*256; + break; + case 990: + printf("%d track %u lap %u-%u sport %u\n", pkttype, + data[doff] + data[doff+1]*256, data[doff+2] + data[doff+3]*256, + data[doff+4] + data[doff+5]*256, data[doff+6]); + printf("%d shorts?", pkttype); + for (i = 0; i < pktlen; i += 2) + printf(" %u", data[doff+i] + data[doff+i+1]*256); + printf("\n"); + if (firstlap_id == -1) firstlap_id = data[doff+2] + data[doff+3]*256; + if (firsttrack_id == -1) firsttrack_id = data[doff] + data[doff+1]*256; + track = (data[doff] + data[doff+1]*256) - firsttrack_id; + if (track < MAXTRACK) { + firstlap_id_track[track] = data[doff+2] + data[doff+3]*256; + sporttyp_track[track] = data[doff+6]; + } else { + printf("Error: track and lap data temporary array out of range %u!\n", track); + } + break; + case 1510: + printf("%d waypoints", pkttype); + for (i = 0; i < 4 && i < pktlen; i += 4) + printf(" %u", data[doff+i] + data[doff+i+1]*256 + + data[doff+i+2]*256*256 + data[doff+i+3]*256*256*256); + printf("\n"); + // if trackpoints are split into more than one message 1510, do not add xml head again + if (previoustrack_id != track_id) { + // close previous file if it is not the first track to be downloaded + if (previoustrack_id > -1) { + // add xml footer and close file, the next file will be open further down + print_tcx_footer(tcxfile); + fclose(tcxfile); + } + // use first lap starttime as filename + lap = firstlap_id_track[track_id-firsttrack_id] - firstlap_id; + if (dbg) printf("lap %u track_id %u firsttrack_id %u firstlap_id %u\n", lap, track_id, firsttrack_id, firstlap_id); + tv_lap = lapbuf[lap][4] + lapbuf[lap][5]*256 + + lapbuf[lap][6]*256*256 + lapbuf[lap][7]*256*256*256; + ttv = tv_lap + 631065600; // garmin epoch offset + strftime(tbuf, sizeof tbuf, "%Y.%m.%d %H%M%S.TCX", localtime(&ttv)); + // open file and start with header of xml file + tcxfile = fopen(tbuf, "wt"); + print_tcx_header(tcxfile); + } + for (i = 4; i < pktlen; i += 24) { + tv = (data[doff+i+8] + data[doff+i+9]*256 + + data[doff+i+10]*256*256 + data[doff+i+11]*256*256*256); + tv_lap = lapbuf[lap][4] + lapbuf[lap][5]*256 + + lapbuf[lap][6]*256*256 + lapbuf[lap][7]*256*256*256; + if ((tv > tv_lap || (tv == tv_lap && lap == (firstlap_id_track[track_id-firsttrack_id] - firstlap_id))) && lap <= lastlap) { + ttv = tv_lap + 631065600; // garmin epoch offset + strftime(tbuf, sizeof tbuf, "%Y-%m-%dT%H:%M:%SZ", gmtime(&ttv)); + tsec = (lapbuf[lap][8] + lapbuf[lap][9]*256 + + lapbuf[lap][10]*256*256 + lapbuf[lap][11]*256*256*256); + memcpy((void *)&dist, &lapbuf[lap][12], 4); + memcpy((void *)&max_speed, &lapbuf[lap][16], 4); + cal = lapbuf[lap][36] + lapbuf[lap][37]*256; + hr_av = lapbuf[lap][38]; + hr_max = lapbuf[lap][39]; + cad = lapbuf[lap][41]; + if (lap == firstlap_id_track[track_id-firsttrack_id] - firstlap_id) { + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " %s\n", tbuf); + } else { + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " \n"); + } + fprintf(tcxfile, " \n", tbuf); + fprintf(tcxfile, " %s\n", ground(tsec/100)); + fprintf(tcxfile, " %s\n", ground(dist)); + fprintf(tcxfile, " %s\n", ground(max_speed)); + fprintf(tcxfile, " %d\n", cal); + if (hr_av > 0) { + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " %d\n", hr_av); + fprintf(tcxfile, " \n"); + } + if (hr_max > 0) { + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " %d\n", hr_max); + fprintf(tcxfile, " \n"); + } + fprintf(tcxfile, " "); + switch (lapbuf[lap][40]) { + case 0: fprintf(tcxfile, "Active"); break; + case 1: fprintf(tcxfile, "Rest"); break; + default: fprintf(tcxfile, "unknown value: %d", lapbuf[lap][40]); + } + fprintf(tcxfile, "\n"); + if (cad != 255) { + if (sporttyp_track[track_id-firsttrack_id] == 0) { + fprintf(tcxfile, " %d\n", cad); + } else { + fprintf(tcxfile, " %d\n", cad); + } + } + fprintf(tcxfile, " "); + switch(lapbuf[lap][42]) { + case 4: fprintf(tcxfile, "Heartrate"); break; + case 3: fprintf(tcxfile, "Time"); break; + case 2: fprintf(tcxfile, "Location"); break; + case 1: fprintf(tcxfile, "Distance"); break; + case 0: fprintf(tcxfile, "Manual"); break; + default: fprintf(tcxfile, "unknown value: %d", lapbuf[lap][42]); + } + fprintf(tcxfile, "\n"); + fprintf(tcxfile, " \n"); + lap++; + track_pause = 0; + // if the previous trackpoint has same second as lap time display the trackpoint again + if (dbg) printf("i %u tv %d tv_lap %d tv_previous %d\n", i, tv, tv_lap, tv_previous); + if (tv_previous == tv_lap) { + i -= 24; + tv = tv_previous; + } + } // end of if (tv >= tv_lap && lap <= lastlap) + if (track_pause) { + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " \n"); + track_pause = 0; + if (dbg) printf("track pause (stop and go)\n"); + } + ttv = tv+631065600; // garmin epoch offset + tmp = gmtime(&ttv); + strftime(tbuf, sizeof tbuf, "%Y-%m-%dT%H:%M:%SZ", tmp); // format for printing + memcpy((void *)&alt, data+doff+i+12, 4); + memcpy((void *)&dist, data+doff+i+16, 4); + lat = (data[doff+i] + data[doff+i+1]*256 + + data[doff+i+2]*256*256 + data[doff+i+3]*256*256*256)*180.0/0x80000000; + lon = (data[doff+i+4] + data[doff+i+5]*256 + + data[doff+i+6]*256*256 + data[doff+i+7]*256*256*256)*180.0/0x80000000; + hr = data[doff+i+20]; + cad = data[doff+i+21]; + u1 = data[doff+i+22]; + u2 = data[doff+i+23]; + if (dbg) printf("lat %.10g lon %.10g hr %d cad %d u1 %d u2 %d tv %d %s alt %f dist %f %02x %02x%02x%02x%02x\n", lat, lon, + hr, cad, u1, u2, tv, tbuf, alt, dist, data[doff+i+3], data[doff+i+16], data[doff+i+17], data[doff+i+18], data[doff+i+19]); + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " \n",tbuf); + if (lat < 90) { + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " %s\n", + ground(lat)); + fprintf(tcxfile, " %s\n", + ground(lon)); + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " %s\n", ground(alt)); + } + // last trackpoint has utopic distance, 40000km should be enough, hack? + if (dist < (float)40000000) { + fprintf(tcxfile, " %s\n", ground(dist)); + } + if (hr > 0) { + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " %d\n", hr); + fprintf(tcxfile, " \n"); + } + if (u1 > 0 && cad != 255) { + fprintf(tcxfile, " %d\n", cad); + } + if (dist < (float)40000000) { + fprintf(tcxfile, " %s\n", u1 ? "Present" : "Absent"); + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " %d\n", cad); + fprintf(tcxfile, " \n"); + } else { + fprintf(tcxfile, "/>\n"); + } + fprintf(tcxfile, " \n"); + } else { + // maybe if we recieve utopic position and distance this tells pause in the run (stop and go) + if (track_pause == 0) track_pause = 1; + else track_pause = 0; + } + fprintf(tcxfile, " \n"); + tv_previous = tv; + } // end of for (i = 4; i < pktlen; i += 24) + previoustrack_id = track_id; + break; + case 149: + printf("%d Lap data id: %u %u\n", pkttype, + data[doff] + data[doff+1]*256, data[doff+2] + data[doff+3]*256); + if (lap < MAXLAPS) { + memcpy((void *)&lapbuf[lap][0], data+doff, 48); + lastlap = lap; + lap++; + } + break; + case 247: + memset(modelname, 0, sizeof modelname); + memcpy(modelname, data+doff+88, dsize-88); + printf("%d Device name %s\n", pkttype, modelname); + break; + default: + printf("don't know how to decode packet type %d\n", pkttype); + for (i = doff; i < dsize && i < doff+pktlen; i++) + printf("%02x", data[i]); + printf("\n"); + for (i = doff; i < dsize && i < doff+pktlen; i++) + if (isprint(data[i])) + printf("%c", data[i]); + else + printf("."); + printf("\n"); + } +} + +void +usage(void) +{ + fprintf(stderr, "Usage: %s -a authfile\n" + "[ -o outfile ]\n" + "[ -d devno ]\n" + "[ -i id ]\n" + "[ -m mydev ]\n" + "[ -p ]\n", + progname + ); + exit(1); +} + +uchar +chevent(uchar chan, uchar event) +{ + uchar seq; + uchar last; + uchar status; + uchar phase; + uint newdata; + struct ack_msg ack; + struct auth_msg auth; + struct pair_msg pair; + uint id; + int i; + uint cid; + if (dbg) printf("chevent %02x %02x\n", chan, event); + + if (event == EVENT_RX_BROADCAST) { + status = cbuf[1] & 0xd7; + newdata = cbuf[1] & 0x20; + phase = cbuf[2]; + } + cid = cbuf[4]+cbuf[5]*256+cbuf[6]*256*256+cbuf[7]*256*256*256; + memcpy((void *)&id, cbuf+4, 4); + + if (dbg) + fprintf(stderr, "cid %08x myid %08x\n", cid, myid); + if (dbg && event != EVENT_RX_BURST_PACKET) { + fprintf(stderr, "chan %d event %02x channel open: ", chan, event); + for (i = 0; i < 8; i++) + fprintf(stderr, "%02x", cbuf[i]); + fprintf(stderr, "\n"); + } + + switch (event) { + case EVENT_RX_BROADCAST: + lastphase = phase; // store the last phase we see the watch broadcast + if (dbg) printf("lastphase %d\n", lastphase); + if (!pairing && !nopairing) + pairing = cbuf[1] & 8; + if (!gottype) { + gottype = 1; + isa50 = cbuf[1] & 4; + isa405 = cbuf[1] & 1; + if ((isa50 && isa405) || (!isa50 && !isa405)) { + fprintf(stderr, "50 %d and 405 %d\n", isa50, isa405); + exit(1); + } + } + if (verbose) { + switch (phase) { + case 0: + fprintf(stderr, "%s BC0 %02x %d %d %d PID %d %d %d %c%c\n", + timestamp(), + cbuf[0], cbuf[1] & 0xd7, cbuf[2], cbuf[3], + cbuf[4]+cbuf[5]*256, cbuf[6], cbuf[7], + (cbuf[1] & 0x20) ? 'N' : ' ', (cbuf[1] & 0x08) ? 'P' : ' ' + ); + break; + case 1: + fprintf(stderr, "%s BC1 %02x %d %d %d CID %08x %c%c\n", + timestamp(), + cbuf[0], cbuf[1] & 0xd7, cbuf[2], cbuf[3], cid, + (cbuf[1] & 0x20) ? 'N' : ' ', (cbuf[1] & 0x08) ? 'P' : ' ' + ); + break; + fprintf(stderr, "%s BCX %02x %d %d %d PID %d %d %d %c%c\n", + timestamp(), + cbuf[0], cbuf[1] & 0xd7, cbuf[2], cbuf[3], + cbuf[4]+cbuf[5]*256, cbuf[6], cbuf[7], + (cbuf[1] & 0x20) ? 'N' : ' ', (cbuf[1] & 0x08) ? 'P' : ' ' + ); + default: + break; + } + } + + if (dbg) + printf("watch status %02x stage %d id %08x\n", status, phase, id); + + if (!sentid) { + sentid = 1; + ANT_RequestMessage(chan, MESG_CHANNEL_ID_ID); /* request sender id */ + } + + // if we don't see a phase 0 message first, reset the watch + if (reset || (phase != 0 && !seenphase0)) { + fprintf(stderr, "resetting\n"); + ack.code = 0x44; ack.atype = 3; ack.c1 = 0x00; ack.c2 = 0x00; ack.id = 0; + ANT_SendAcknowledgedData(chan, (void *)&ack); // tell garmin we're finished + sleep(1); + exit(1); + } + switch (phase) { + case 0: + seenphase0 = 1; + nphase0++; + if (nphase0 % 10 == 0) + donebind = 0; + if (newfreq) { + // switch to new frequency + ANT_SetChannelPeriod(chan, period); + ANT_SetChannelSearchTimeout(chan, 3); + ANT_SetChannelRFFreq(chan, newfreq); + newfreq = 0; + } + // phase 0 seen after reset at end of download + if (downloadfinished) { + fprintf(stderr, "finished\n"); + exit(0); + } + // generate a random id if pairing and user didn't specify one + if (pairing && !myid) { + myid = randno(); + fprintf(stderr, "pairing, using id %08x\n", myid); + } + // need id codes from auth file if not pairing + // TODO: handle multiple watches + // BUG: myauth1 should be allowed to be 0 + if (!pairing && !myauth1) { + int nr; + printf("reading auth data from %s\n", authfile); + authfd = open(authfile, O_RDONLY); + if (authfd < 0) { + perror(authfile); + fprintf(stderr, "No auth data. Need to pair first\n"); + exit(1); + } + nr = read(authfd, authdata, 32); + close(authfd); + if (nr != 32 && nr != 24) { + fprintf(stderr, "bad auth file len %d != 32 or 24\n", nr); + exit(1); + } + // BUG: auth file not portable + memcpy((void *)&myauth1, authdata+16, 4); + memcpy((void *)&myauth2, authdata+20, 4); + memcpy((void *)&mydev, authdata+12, 4); + memcpy((void *)&myid, authdata+4, 4); + if (dbg) + fprintf(stderr, "dev %08x auth %08x %08x id %08x\n", + mydev, myauth1, myauth2, myid); + } + // bind to watch + if (!donebind && devid) { + donebind = 1; + if (isa405) + newfreq = 0x32; + ack.code = 0x44; ack.atype = 2; ack.c1 = isa50 ? 0x32 : newfreq; ack.c2 = 0x04; + ack.id = myid; + ANT_SendAcknowledgedData(chan, (void *)&ack); // bind + } else { + if (dbg) printf("donebind %d devid %x\n", donebind, devid); + } + break; + case 1: + if (dbg) printf("case 1 %x\n", peerdev); + if (peerdev) { + if (dbg) printf("case 1 peerdev\n"); + // if watch has sent id + if (mydev != 0 && peerdev != mydev) { + fprintf(stderr, "Don't know this device %08x != %08x\n", peerdev, mydev); + } else if (!sentauth && !waitauth) { + if (dbg) printf("case 1 diffdev\n"); + assert(sizeof auth == AUTHSIZE); + auth.code = 0x44; auth.atype = 4; auth.phase = 3; auth.u1 = 8; + auth.id = myid; auth.auth1 = myauth1; auth.auth2 = myauth2; + auth.fill1 = auth.fill2 = 0; + sentauth = 1; + ANT_SendBurstTransfer(chan, (void *)&auth, (sizeof auth)/8); // send our auth data + } + } + if (dbg) printf("case 1 cid %x myid %x\n", cid, myid); + if (!sentack2 && cid == myid && !waitauth) { + sentack2 = 1; + if (dbg) printf("sending ack2\n"); + // if it did bind to me before someone else + ack.code = 0x44; ack.atype = 4; ack.c1 = 0x01; ack.c2 = 0x00; + ack.id = myid; + ANT_SendAcknowledgedData(chan, (void *)&ack); // request id + } + break; + case 2: + // successfully authenticated + if (!downloadstarted) { + downloadstarted = 1; + if (dbg) printf("starting download\n"); + ack.code = 0x44; ack.atype = 6; ack.c1 = 0x01; ack.c2 = 0x00; ack.id = 0; + //ANT_SendAcknowledgedData(chan, (void *)&ack); // tell garmin to start upload + } + if (downloadfinished) { + if (dbg) printf("finished download\n"); + ack.code = 0x44; ack.atype = 3; ack.c1 = 0x00; ack.c2 = 0x00; ack.id = 0; + if (!passive) ANT_SendAcknowledgedData(chan, (void *)&ack); // tell garmin we're finished + } + break; + case 3: + if (pairing) { + // waiting for the user to pair + printf("Please press \"View\" on watch to confirm pairing\n"); + waitauth = 2; // next burst data is auth data + } else { + if (dbg) printf("not sure why in phase 3\n"); + if (!sentgetv) { + sentgetv = 1; + //ANT_SendBurstTransferA(chan, getversion, strlen(getversion)/16); + } + } + break; + default: + if (dbg) fprintf(stderr, "Unknown phase %d\n", phase); + break; + } + break; + case EVENT_RX_BURST_PACKET: + // now handled in coalesced burst below + if (dbg) printf("burst\n"); + break; + case EVENT_RX_FAKE_BURST: + if (dbg) printf("rxfake burst pairing %d blast %ld waitauth %d\n", + pairing, (long)blast, waitauth); + blsize = *(int *)(cbuf+4); + memcpy(&blast, cbuf+8, 4); + if (dbg) { + printf("fake burst %d %lx ", blsize, (long)blast); + for (i = 0; i < blsize && i < 64; i++) + printf("%02x", blast[i]); + printf("\n"); + for (i = 0; i < blsize; i++) + if (isprint(blast[i])) + printf("%c", blast[i]); + else + printf("."); + printf("\n"); + } + if (sentauth) { + static int nacksent = 0; + char *ackdata; + static uchar ackpkt[100]; + // ack the last packet + ushort bloblen = blast[14]+256*blast[15]; + ushort pkttype = blast[16]+256*blast[17]; + ushort pktlen = blast[18]+256*blast[19]; + if (bloblen == 0) { + if (dbg) printf("bloblen %d, get next data\n", bloblen); + // request next set of data + ackdata = acks[nacksent++]; + if (!strcmp(ackdata, "")) { // finished + printf("acks finished, resetting\n"); + ack.code = 0x44; ack.atype = 3; ack.c1 = 0x00; + ack.c2 = 0x00; ack.id = 0; + ANT_SendAcknowledgedData(chan, (void *)&ack); // go to idle + sleep(1); + exit(1); + } + if (dbg) printf("got type 0, sending ack %s\n", ackdata); + sprintf(ackpkt, "440dffff00000000%s", ackdata); + } else if (bloblen == 65535) { + // repeat last ack + if (dbg) printf("repeating ack %s\n", ackpkt); + ANT_SendBurstTransferA(chan, ackpkt, strlen(ackpkt)/16); + } else { + if (dbg) printf("non-0 bloblen %d\n", bloblen); + decode(bloblen, pkttype, pktlen, blsize, blast); + sprintf(ackpkt, "440dffff0000000006000200%02x%02x0000", pkttype%256, pkttype/256); + } + if (dbg) printf("received pkttype %d len %d\n", pkttype, pktlen); + if (dbg) printf("acking %s\n", ackpkt); + ANT_SendBurstTransferA(chan, ackpkt, strlen(ackpkt)/16); + } else if (!nopairing && pairing && blast) { + memcpy(&peerdev, blast+12, 4); + if (dbg) + printf("watch id %08x waitauth %d\n", peerdev, waitauth); + if (mydev != 0 && peerdev != mydev) { + fprintf(stderr, "Don't know this device %08x != %08x\n", peerdev, mydev); + exit(1); + } + if (waitauth == 2) { + int nw; + // should be receiving auth data + if (nowriteauth) { + printf("Not overwriting auth data\n"); + exit(1); + } + printf("storing auth data in %s\n", authfile); + authfd = open(authfile, O_WRONLY|O_CREAT, 0644); + if (authfd < 0) { + perror(authfile); + exit(1); + } + nw = write(authfd, blast, blsize); + if (nw != blsize) { + fprintf(stderr, "auth write failed fd %d %d\n", authfd, nw); + perror("write"); + exit(1); + } + close(authfd); + //exit(1); + pairing = 0; + waitauth = 0; + reset = 1; + } + if (pairing && !waitauth) { + //assert(sizeof pair == PAIRSIZE); + pair.code = 0x44; pair.atype = 4; pair.phase = 2; pair.id = myid; + bzero(pair.devname, sizeof pair.devname); + //if (peerdev <= 9999999) // only allow 7 digits + //sprintf(pair.devname, "%u", peerdev); + strcpy(pair.devname, fname); + //else + // fprintf(stderr, "pair dev name too large %08x \"%d\"\n", peerdev, peerdev); + pair.u1 = strlen(pair.devname); + printf("sending pair data for dev %s\n", pair.devname); + waitauth = 1; + if (isa405 && pairing) { + // go straight to storing auth data + waitauth = 2; + } + ANT_SendBurstTransfer(chan, (void *)&pair, (sizeof pair)/8) ; // send pair data + } else { + if (dbg) printf("not pairing\n"); + } + } else if (!gotwatchid && (lastphase == 1)) { + static int once = 0; + gotwatchid = 1; + // garmin sending authentication/identification data + if (!once) { + int i; + once = 1; + if (dbg) + fprintf(stderr, "id data: "); + } + if (dbg) + for (i = 0; i < blsize; i++) + fprintf(stderr, "%02x", blast[i]); + if (dbg) + fprintf(stderr, "\n"); + memcpy(&peerdev, blast+12, 4); + if (dbg) + printf("watch id %08x\n", peerdev); + if (mydev != 0 && peerdev != mydev) { + fprintf(stderr, "Don't know this device %08x != %08x\n", peerdev, mydev); + exit(1); + } + } else if (lastphase == 2) { + int nw; + static int once = 0; + printf("once %d\n", once); + // garmin uploading in response to sendack3 + // in this state we're receiving the workout data + if (!once) { + printf("receiving\n"); + once = 1; + outfd = open(fn, O_WRONLY|O_CREAT, 0644); + if (outfd < 0) { + perror(fn); + exit(1); + } + } + if (last) { + nw = write(outfd, blast, blsize); + if (nw != blsize) { + fprintf(stderr, "data write failed fd %d %d\n", outfd, nw); + perror("write"); + exit(1); + } + close(outfd); + downloadfinished = 1; + } + } else if (0 && last) { + if (dbg) { + fprintf(stderr, "auth response: "); + for (i = 0; i < blsize; i++) + fprintf(stderr, "%02x", cbuf[i]); + fprintf(stderr, "\n"); + } + if (blast[10] == 2) { + fprintf(stderr, "authentication failed\n"); + exit(1); + } + } else if (last) { + fprintf(stderr, "data in state xx: "); + int i; + for (i = 0; i < blsize; i++) + fprintf(stderr, "%02x", blast[i]); + fprintf(stderr, "\n"); + sentcmd = 1000; + switch (sentcmd) { + case 1000: + break; + case 0: + sentcmd++; + //ANT_SendBurstTransferA(chan, getgpsver, strlen(getgpsver)/16); + break; + case 999: + printf("finished\n"); + exit(1); + default: + sleep(1); + sentcmd = 1; + //printf("sending command %d %s\n", sentcmd-1, cmds[sentcmd-1]); + //ANT_SendBurstTransferA(chan, cmds[sentcmd-1], + // strlen(cmds[sentcmd-1])/16); + sentcmd++; + //if(!strcmp(cmds[sentcmd-1], "END")) + // sentcmd = 999; + break; + } + } + if (dbg) printf("continuing after burst\n"); + break; + } + return 1; +} + +uchar +revent(uchar chan, uchar event) +{ + struct ack_msg ack; + int i; + + if (dbg) printf("revent %02x %02x\n", chan, event); + switch (event) { + case EVENT_TRANSFER_TX_COMPLETED: + if (dbg) printf("Transfer complete %02x\n", ebuf[1]); + break; + case INVALID_MESSAGE: + printf("Invalid message %02x\n", ebuf[1]); + break; + case RESPONSE_NO_ERROR: + switch (ebuf[1]) { + case MESG_ASSIGN_CHANNEL_ID: + ANT_AssignChannelEventFunction(chan, chevent, cbuf); + break; + case MESG_OPEN_CHANNEL_ID: + printf("channel open, waiting for broadcast\n"); + break; + default: + if (dbg) printf("Message %02x NO_ERROR\n", ebuf[1]); + break; + } + break; + case MESG_CHANNEL_ID_ID: + devid = ebuf[1]+ebuf[2]*256; + if (mydev == 0 || devid == mydev%65536) { + if (dbg) + printf("devid %08x myid %08x\n", devid, myid); + } else { + printf("Ignoring unknown device %08x, mydev %08x\n", devid, mydev); + devid = sentid = 0; // reset + } + break; + case MESG_NETWORK_KEY_ID: + case MESG_SEARCH_WAVEFORM_ID: + case MESG_OPEN_CHANNEL_ID: + printf("response event %02x code %02x\n", event, ebuf[2]); + for (i = 0; i < 8; i++) + fprintf(stderr, "%02x", ebuf[i]); + fprintf(stderr, "\n"); + break; + case MESG_CAPABILITIES_ID: + if (dbg) + printf("capabilities chans %d nets %d opt %02x adv %02x\n", + ebuf[0], ebuf[1], ebuf[2], ebuf[3]); + break; + case MESG_CHANNEL_STATUS_ID: + if (dbg) + printf("channel status %d\n", ebuf[1]); + break; + case EVENT_RX_FAIL: + // ignore this + break; + default: + printf("Unhandled response event %02x\n", event); + break; + } + return 1; +} + +main(int ac, char *av[]) +{ + int devnum = 0; + int chan = 0; + int net = 0; + int chtype = 0; // wildcard + int devno = 0; // wildcard + int devtype = 0; // wildcard + int manid = 0; // wildcard + int freq = 0x32; // garmin specific radio frequency + int srchto = 255; // max timeout + int waveform = 0x0053; // aids search somehow + int c; + extern char *optarg; + extern int optind, opterr, optopt; + + // default auth file // + if (getenv("HOME")) { + authfile = malloc(strlen(getenv("HOME"))+strlen("/.gant")+1); + if (authfile) + sprintf(authfile, "%s/.gant", getenv("HOME")); + } + progname = av[0]; + while ((c = getopt(ac, av, "a:o:d:i:m:PpvDrnzf:")) != -1) { + switch(c) { + case 'a': + authfile = optarg; + break; + case 'f': + fname = optarg; + break; + case 'o': + fn = optarg; + break; + case 'd': + devnum = atoi(optarg); + break; + case 'i': + myid = atoi(optarg); + break; + case 'm': + mydev = atoi(optarg); + break; + case 'p': + passive = 1; + semipassive = 0; + break; + case 'P': + passive = 1; + break; + case 'v': + verbose = 1; + break; + case 'D': + dbg = 1; + break; + case 'r': + reset = 1; + break; + case 'n': + nowriteauth = 1; + break; + case 'z': + nopairing = 1; + break; + default: + fprintf(stderr, "unknown option %s\n", optarg); + usage(); + } + } + + ac -= optind; + av += optind; + + if ((!passive && !authfile) || ac) + usage(); + + if (!ANT_Init(devnum, 0)) { // should be 115200 but doesn't fit into a short + fprintf(stderr, "open dev %d failed\n", devnum); + exit(1); + } + ANT_ResetSystem(); + ANT_AssignResponseFunction(revent, ebuf); + ANT_RequestMessage(chan, MESG_CHANNEL_STATUS_ID); //informative + ANT_SetNetworkKeya(net, ANTSPT_KEY); + ANT_AssignChannel(chan, chtype, net); + // Wali: changed order of the following seq. according windows + ANT_SetChannelPeriod(chan, period); + ANT_SetChannelSearchTimeout(chan, srchto); + ANT_RequestMessage(chan, MESG_CAPABILITIES_ID); //informative + ANT_SetChannelRFFreq(chan, freq); + ANT_SetSearchWaveform(chan, waveform); + ANT_SetChannelId(chan, devno, devtype, manid); + ANT_OpenChannel(chan); + ANT_RequestMessage(chan, MESG_CHANNEL_STATUS_ID); //informative + + // everything handled in event functions + for(;;) + sleep(10); +} diff --git a/gant.c~ b/gant.c~ new file mode 100644 index 0000000..5c732ae --- /dev/null +++ b/gant.c~ @@ -0,0 +1,1182 @@ +// copyright 2008-2009 paul@ant.sbrk.co.uk. released under GPLv3 +// copyright 2009-2009 Wali +// vers 0.6t +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "antdefs.h" + +char *releasetime = "Jan 21 2008, 12:00:00"; +uint majorrelease = 0; +uint minorrelease = 6; + +double round(double); + +int gottype; +int sentauth; +int gotwatchid; +int nopairing; +int nowriteauth; +int reset; +int dbg = 0; +int seenphase0 = 0; +int lastphase; +int sentack2; +int newfreq = 0; +int period = 0x1000; // garmin specific broadcast period +int donebind = 0; +int sentgetv; +char *fname = "garmin"; + +static char ANTSPT_KEY[] = "A8A423B9F55E63C1"; // ANT+Sport key + +static uchar ebuf[MESG_DATA_SIZE]; // response event data gets stored here +static uchar cbuf[MESG_DATA_SIZE]; // channel event data gets stored here + +int passive; +int semipassive; +int verbose; + +int downloadfinished = 0; +int downloadstarted = 0; +int sentid = 0; + +uint mydev = 0; +uint peerdev; +uint myid; +uint devid; +uint myauth1; +uint myauth2; +char authdata[32]; +uint pairing; +uint isa50; +uint isa405; +uint waitauth; +int nphase0; +char modelname[256]; +ushort part = 0; +ushort ver = 0; +uint unitid = 0; + + +//char *getversion = "440dffff00000000fe00000000000000"; +//char *getgpsver = "440dffff0000000006000200ff000000"; +char *acks[] = { + "fe00000000000000", // get version - 255, 248, 253 + "0e02000000000000", // device short name (fr405a) - 525 +// "1c00020000000000", // no data + "0a0002000e000000", // unit id - 38 + "0a00020002000000", // send position + "0a00020005000000", // send time + "0a000200ad020000", // 4 byte something? 0x10270000 = 10000 dec - 1523 + "0a000200c6010000", // 3 x 4 ints? - 994 + "0a00020035020000", // guessing this is # trackpoints per run - 1066 + "0a00020097000000", // load of software versions - 247 + "0a000200c2010000", // download runs - 27 (#runs), 990?, 12? + "0a00020075000000", // download laps - 27 (#laps), 149 laps, 12? + "0a00020006000000", // download trackpoints - 1510/99(run marker), ..1510,12 + "0a000200ac020000", + "" +}; +int sentcmd; + +uchar clientid[3][8]; + +int authfd = -1; +char *authfile; +int outfd; // output file +char *fn = "default_output_file"; +char *progname; + +#define BSIZE 8*100 +uchar *burstbuf = 0; +uchar *blast = 0; +int blsize = 0; +int bused = 0; +int lseq = -1; + +/* round a float as garmin does it! */ +/* shoot me for writing this! */ +char * +ground(double d) +{ + int neg = 0; + static char res[30]; + ulong ival; + ulong l; /* hope it doesn't overflow */ + + if (d < 0) { + neg = 1; + d = -d; + } + ival = floor(d); + d -= ival; + l = floor(d*100000000); + if (l % 10 >= 5) + l = l/10+1; + else + l = l/10; + sprintf(res,"%s%ld.%07ld", neg?"-":"", ival, l); + return res; +} + +char * +timestamp(void) +{ + struct timeval tv; + static char time[50]; + struct tm *tmp; + + gettimeofday(&tv, 0); + tmp = gmtime(&tv.tv_sec); + + sprintf(time, "%02d:%02d:%02d.%02d", + tmp->tm_hour, tmp->tm_min, tmp->tm_sec, (int)tv.tv_usec/10000); + return time; +} + +uint +randno(void) +{ + uint r; + + int fd = open("/dev/urandom", O_RDONLY); + if (fd > 0) { + read(fd, &r, sizeof r); + close(fd); + } + return r; +} + +void +print_tcx_header(FILE *tcxfile) +{ + fprintf(tcxfile, "\n"); + fprintf(tcxfile, "\n\n"); + fprintf(tcxfile, " \n"); + return; +} + +void +print_tcx_footer(FILE *tcxfile) +{ + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " %s\n", modelname); + fprintf(tcxfile, " %u\n", unitid); + fprintf(tcxfile, " %u\n", part); + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " %u\n", ver/100); + fprintf(tcxfile, " %u\n", ver - ver/100*100); + fprintf(tcxfile, " 0\n"); + fprintf(tcxfile, " 0\n"); + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " \n\n"); + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " Garmin ANT for Linux\n"); + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " %u\n", majorrelease); + fprintf(tcxfile, " %u\n", minorrelease); + fprintf(tcxfile, " 0\n"); + fprintf(tcxfile, " 0\n"); + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " Release\n"); + fprintf(tcxfile, " \n", releasetime); + fprintf(tcxfile, " make\n"); + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " EN\n"); + fprintf(tcxfile, " 006-A0XXX-00\n"); + fprintf(tcxfile, " \n\n"); + fprintf(tcxfile, "\n"); + return; +} + + +#pragma pack(1) +struct ack_msg { + uchar code; + uchar atype; + uchar c1; + uchar c2; + uint id; +}; + +struct auth_msg { + uchar code; + uchar atype; + uchar phase; + uchar u1; + uint id; + uint auth1; + uint auth2; + uint fill1; + uint fill2; +}; + +struct pair_msg { + uchar code; + uchar atype; + uchar phase; + uchar u1; + uint id; + char devname[16]; +}; + +#pragma pack() +#define ACKSIZE 8 // above structure must be this size +#define AUTHSIZE 24 // ditto +#define PAIRSIZE 16 +#define MAXLAPS 256 // max of saving laps data before output with trackpoint data +#define MAXTRACK 256 // max number of tracks to be saved per download + +decode(ushort bloblen, ushort pkttype, ushort pktlen, int dsize, uchar *data) +{ + int i; + int j; + int hr; + int hr_av; + int hr_max; + int cal; + float tsec; + float max_speed; + int cad; + int u1, u2; + int doff = 20; + char model[256]; + char gpsver[256]; + char devname[256]; + float alt; + float dist; + uint tv; + uint tv_previous = 0; + time_t ttv; + char tbuf[100]; + struct tm *tmp; + double lat, lon; + uint nruns; + uint tv_lap; + static uchar lapbuf[MAXLAPS][48]; + static ushort lap = 0; + static ushort lastlap = 0; + static ushort track = 0; + static short previoustrack_id = -1; + static short track_id = -1; + static short firsttrack_id = -1; + static short firstlap_id = -1; + static ushort firstlap_id_track[MAXTRACK]; + static uchar sporttyp_track[MAXTRACK]; + static FILE *tcxfile = NULL; + static ushort track_pause = 0; + + + printf("decode %d %d %d %d\n", bloblen, pkttype, pktlen, dsize); + switch (pkttype) { + case 255: + memset(model, 0, sizeof model); + memcpy(model, data+doff+4, dsize-4); + part=data[doff]+data[doff+1]*256; + ver=data[doff+2]+data[doff+3]*256; + printf("%d Part#: %d ver: %d Name: %s\n", pkttype, + part, ver, model); + break; + case 248: + memset(gpsver, 0, sizeof gpsver); + memcpy(gpsver, data+doff, dsize); + printf("%d GPSver: %s\n", pkttype, + gpsver); + break; + case 253: + printf("%d Unknown\n", pkttype); + for (i = 0; i < pktlen; i += 3) + printf("%d.%d.%d\n", data[doff+i], data[doff+i+1], data[doff+i+2]); + break; + case 525: + memset(devname, 0, sizeof devname); + memcpy(devname, data+doff, dsize); + printf("%d Devname %s\n", pkttype, devname); + break; + case 12: + printf("%d xfer complete", pkttype); + for (i = 0; i < pktlen; i += 2) + printf(" %u", data[doff+i] + data[doff+i+1]*256); + printf("\n"); + switch (data[doff] + data[doff+1]*256) { + case 6: + // last file completed, add footer and close file + print_tcx_footer(tcxfile); + fclose(tcxfile); + break; + case 117: + break; + case 450: + break; + default: + break; + } + break; + case 38: + unitid = data[doff] + data[doff+1]*256 + + data[doff+2]*256*256 + data[doff+3]*256*256*256; + printf("%d unitid %u\n", pkttype, unitid); + break; + case 27: + nruns = data[doff] + data[doff+1] * 256; + printf("%d nruns %u\n", pkttype, nruns); + break; + case 1523: + case 994: + case 1066: + printf("%d ints?", pkttype); + for (i = 0; i < pktlen; i += 4) + printf(" %u", data[doff+i] + data[doff+i+1]*256 + + data[doff+i+2]*256*256 + data[doff+i+3]*256*256*256); + printf("\n"); + break; + case 14: + printf("%d time: ", pkttype); + printf("%02u-%02u-%u %02u:%02u:%02u\n", data[doff], data[doff+1], data[doff+2] + data[doff+3]*256, + data[doff+4], data[doff+6], data[doff+7]); + break; + case 17: + printf("%d position ? ", pkttype); + for (i = 0; i < pktlen; i += 4) + printf(" %u", data[doff+i] + data[doff+i+1]*256 + + data[doff+i+2]*256*256 + data[doff+i+3]*256*256*256); + printf("\n"); + break; + case 99: + printf("%d trackindex %u\n", pkttype, data[doff] + data[doff+1]*256); + printf("%d shorts?", pkttype); + for (i = 0; i < pktlen; i += 2) + printf(" %u", data[doff+i] + data[doff+i+1]*256); + printf("\n"); + track_id = data[doff] + data[doff+1]*256; + break; + case 990: + printf("%d track %u lap %u-%u sport %u\n", pkttype, + data[doff] + data[doff+1]*256, data[doff+2] + data[doff+3]*256, + data[doff+4] + data[doff+5]*256, data[doff+6]); + printf("%d shorts?", pkttype); + for (i = 0; i < pktlen; i += 2) + printf(" %u", data[doff+i] + data[doff+i+1]*256); + printf("\n"); + if (firstlap_id == -1) firstlap_id = data[doff+2] + data[doff+3]*256; + if (firsttrack_id == -1) firsttrack_id = data[doff] + data[doff+1]*256; + track = (data[doff] + data[doff+1]*256) - firsttrack_id; + if (track < MAXTRACK) { + firstlap_id_track[track] = data[doff+2] + data[doff+3]*256; + sporttyp_track[track] = data[doff+6]; + } else { + printf("Error: track and lap data temporary array out of range %u!\n", track); + } + break; + case 1510: + printf("%d waypoints", pkttype); + for (i = 0; i < 4 && i < pktlen; i += 4) + printf(" %u", data[doff+i] + data[doff+i+1]*256 + + data[doff+i+2]*256*256 + data[doff+i+3]*256*256*256); + printf("\n"); + // if trackpoints are split into more than one message 1510, do not add xml head again + if (previoustrack_id != track_id) { + // close previous file if it is not the first track to be downloaded + if (previoustrack_id > -1) { + // add xml footer and close file, the next file will be open further down + print_tcx_footer(tcxfile); + fclose(tcxfile); + } + // use first lap starttime as filename + lap = firstlap_id_track[track_id-firsttrack_id] - firstlap_id; + if (dbg) printf("lap %u track_id %u firsttrack_id %u firstlap_id %u\n", lap, track_id, firsttrack_id, firstlap_id); + tv_lap = lapbuf[lap][4] + lapbuf[lap][5]*256 + + lapbuf[lap][6]*256*256 + lapbuf[lap][7]*256*256*256; + ttv = tv_lap + 631065600; // garmin epoch offset + strftime(tbuf, sizeof tbuf, "%d.%m.%Y %H%M%S.TCX", localtime(&ttv)); + // open file and start with header of xml file + tcxfile = fopen(tbuf, "wt"); + print_tcx_header(tcxfile); + } + for (i = 4; i < pktlen; i += 24) { + tv = (data[doff+i+8] + data[doff+i+9]*256 + + data[doff+i+10]*256*256 + data[doff+i+11]*256*256*256); + tv_lap = lapbuf[lap][4] + lapbuf[lap][5]*256 + + lapbuf[lap][6]*256*256 + lapbuf[lap][7]*256*256*256; + if ((tv > tv_lap || (tv == tv_lap && lap == (firstlap_id_track[track_id-firsttrack_id] - firstlap_id))) && lap <= lastlap) { + ttv = tv_lap + 631065600; // garmin epoch offset + strftime(tbuf, sizeof tbuf, "%Y-%m-%dT%H:%M:%SZ", gmtime(&ttv)); + tsec = (lapbuf[lap][8] + lapbuf[lap][9]*256 + + lapbuf[lap][10]*256*256 + lapbuf[lap][11]*256*256*256); + memcpy((void *)&dist, &lapbuf[lap][12], 4); + memcpy((void *)&max_speed, &lapbuf[lap][16], 4); + cal = lapbuf[lap][36] + lapbuf[lap][37]*256; + hr_av = lapbuf[lap][38]; + hr_max = lapbuf[lap][39]; + cad = lapbuf[lap][41]; + if (lap == firstlap_id_track[track_id-firsttrack_id] - firstlap_id) { + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " %s\n", tbuf); + } else { + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " \n"); + } + fprintf(tcxfile, " \n", tbuf); + fprintf(tcxfile, " %s\n", ground(tsec/100)); + fprintf(tcxfile, " %s\n", ground(dist)); + fprintf(tcxfile, " %s\n", ground(max_speed)); + fprintf(tcxfile, " %d\n", cal); + if (hr_av > 0) { + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " %d\n", hr_av); + fprintf(tcxfile, " \n"); + } + if (hr_max > 0) { + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " %d\n", hr_max); + fprintf(tcxfile, " \n"); + } + fprintf(tcxfile, " "); + switch (lapbuf[lap][40]) { + case 0: fprintf(tcxfile, "Active"); break; + case 1: fprintf(tcxfile, "Rest"); break; + default: fprintf(tcxfile, "unknown value: %d", lapbuf[lap][40]); + } + fprintf(tcxfile, "\n"); + if (cad != 255) { + if (sporttyp_track[track_id-firsttrack_id] == 0) { + fprintf(tcxfile, " %d\n", cad); + } else { + fprintf(tcxfile, " %d\n", cad); + } + } + fprintf(tcxfile, " "); + switch(lapbuf[lap][42]) { + case 4: fprintf(tcxfile, "Heartrate"); break; + case 3: fprintf(tcxfile, "Time"); break; + case 2: fprintf(tcxfile, "Location"); break; + case 1: fprintf(tcxfile, "Distance"); break; + case 0: fprintf(tcxfile, "Manual"); break; + default: fprintf(tcxfile, "unknown value: %d", lapbuf[lap][42]); + } + fprintf(tcxfile, "\n"); + fprintf(tcxfile, " \n"); + lap++; + track_pause = 0; + // if the previous trackpoint has same second as lap time display the trackpoint again + if (dbg) printf("i %u tv %d tv_lap %d tv_previous %d\n", i, tv, tv_lap, tv_previous); + if (tv_previous == tv_lap) { + i -= 24; + tv = tv_previous; + } + } // end of if (tv >= tv_lap && lap <= lastlap) + if (track_pause) { + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " \n"); + track_pause = 0; + if (dbg) printf("track pause (stop and go)\n"); + } + ttv = tv+631065600; // garmin epoch offset + tmp = gmtime(&ttv); + strftime(tbuf, sizeof tbuf, "%Y-%m-%dT%H:%M:%SZ", tmp); // format for printing + memcpy((void *)&alt, data+doff+i+12, 4); + memcpy((void *)&dist, data+doff+i+16, 4); + lat = (data[doff+i] + data[doff+i+1]*256 + + data[doff+i+2]*256*256 + data[doff+i+3]*256*256*256)*180.0/0x80000000; + lon = (data[doff+i+4] + data[doff+i+5]*256 + + data[doff+i+6]*256*256 + data[doff+i+7]*256*256*256)*180.0/0x80000000; + hr = data[doff+i+20]; + cad = data[doff+i+21]; + u1 = data[doff+i+22]; + u2 = data[doff+i+23]; + if (dbg) printf("lat %.10g lon %.10g hr %d cad %d u1 %d u2 %d tv %d %s alt %f dist %f %02x %02x%02x%02x%02x\n", lat, lon, + hr, cad, u1, u2, tv, tbuf, alt, dist, data[doff+i+3], data[doff+i+16], data[doff+i+17], data[doff+i+18], data[doff+i+19]); + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " \n",tbuf); + if (lat < 90) { + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " %s\n", + ground(lat)); + fprintf(tcxfile, " %s\n", + ground(lon)); + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " %s\n", ground(alt)); + } + // last trackpoint has utopic distance, 40000km should be enough, hack? + if (dist < (float)40000000) { + fprintf(tcxfile, " %s\n", ground(dist)); + } + if (hr > 0) { + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " %d\n", hr); + fprintf(tcxfile, " \n"); + } + if (u1 > 0 && cad != 255) { + fprintf(tcxfile, " %d\n", cad); + } + if (dist < (float)40000000) { + fprintf(tcxfile, " %s\n", u1 ? "Present" : "Absent"); + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " \n"); + fprintf(tcxfile, " %d\n", cad); + fprintf(tcxfile, " \n"); + } else { + fprintf(tcxfile, "/>\n"); + } + fprintf(tcxfile, " \n"); + } else { + // maybe if we recieve utopic position and distance this tells pause in the run (stop and go) + if (track_pause == 0) track_pause = 1; + else track_pause = 0; + } + fprintf(tcxfile, " \n"); + tv_previous = tv; + } // end of for (i = 4; i < pktlen; i += 24) + previoustrack_id = track_id; + break; + case 149: + printf("%d Lap data id: %u %u\n", pkttype, + data[doff] + data[doff+1]*256, data[doff+2] + data[doff+3]*256); + if (lap < MAXLAPS) { + memcpy((void *)&lapbuf[lap][0], data+doff, 48); + lastlap = lap; + lap++; + } + break; + case 247: + memset(modelname, 0, sizeof modelname); + memcpy(modelname, data+doff+88, dsize-88); + printf("%d Device name %s\n", pkttype, modelname); + break; + default: + printf("don't know how to decode packet type %d\n", pkttype); + for (i = doff; i < dsize && i < doff+pktlen; i++) + printf("%02x", data[i]); + printf("\n"); + for (i = doff; i < dsize && i < doff+pktlen; i++) + if (isprint(data[i])) + printf("%c", data[i]); + else + printf("."); + printf("\n"); + } +} + +void +usage(void) +{ + fprintf(stderr, "Usage: %s -a authfile\n" + "[ -o outfile ]\n" + "[ -d devno ]\n" + "[ -i id ]\n" + "[ -m mydev ]\n" + "[ -p ]\n", + progname + ); + exit(1); +} + +uchar +chevent(uchar chan, uchar event) +{ + uchar seq; + uchar last; + uchar status; + uchar phase; + uint newdata; + struct ack_msg ack; + struct auth_msg auth; + struct pair_msg pair; + uint id; + int i; + uint cid; + if (dbg) printf("chevent %02x %02x\n", chan, event); + + if (event == EVENT_RX_BROADCAST) { + status = cbuf[1] & 0xd7; + newdata = cbuf[1] & 0x20; + phase = cbuf[2]; + } + cid = cbuf[4]+cbuf[5]*256+cbuf[6]*256*256+cbuf[7]*256*256*256; + memcpy((void *)&id, cbuf+4, 4); + + if (dbg) + fprintf(stderr, "cid %08x myid %08x\n", cid, myid); + if (dbg && event != EVENT_RX_BURST_PACKET) { + fprintf(stderr, "chan %d event %02x channel open: ", chan, event); + for (i = 0; i < 8; i++) + fprintf(stderr, "%02x", cbuf[i]); + fprintf(stderr, "\n"); + } + + switch (event) { + case EVENT_RX_BROADCAST: + lastphase = phase; // store the last phase we see the watch broadcast + if (dbg) printf("lastphase %d\n", lastphase); + if (!pairing && !nopairing) + pairing = cbuf[1] & 8; + if (!gottype) { + gottype = 1; + isa50 = cbuf[1] & 4; + isa405 = cbuf[1] & 1; + if ((isa50 && isa405) || (!isa50 && !isa405)) { + fprintf(stderr, "50 %d and 405 %d\n", isa50, isa405); + exit(1); + } + } + if (verbose) { + switch (phase) { + case 0: + fprintf(stderr, "%s BC0 %02x %d %d %d PID %d %d %d %c%c\n", + timestamp(), + cbuf[0], cbuf[1] & 0xd7, cbuf[2], cbuf[3], + cbuf[4]+cbuf[5]*256, cbuf[6], cbuf[7], + (cbuf[1] & 0x20) ? 'N' : ' ', (cbuf[1] & 0x08) ? 'P' : ' ' + ); + break; + case 1: + fprintf(stderr, "%s BC1 %02x %d %d %d CID %08x %c%c\n", + timestamp(), + cbuf[0], cbuf[1] & 0xd7, cbuf[2], cbuf[3], cid, + (cbuf[1] & 0x20) ? 'N' : ' ', (cbuf[1] & 0x08) ? 'P' : ' ' + ); + break; + fprintf(stderr, "%s BCX %02x %d %d %d PID %d %d %d %c%c\n", + timestamp(), + cbuf[0], cbuf[1] & 0xd7, cbuf[2], cbuf[3], + cbuf[4]+cbuf[5]*256, cbuf[6], cbuf[7], + (cbuf[1] & 0x20) ? 'N' : ' ', (cbuf[1] & 0x08) ? 'P' : ' ' + ); + default: + break; + } + } + + if (dbg) + printf("watch status %02x stage %d id %08x\n", status, phase, id); + + if (!sentid) { + sentid = 1; + ANT_RequestMessage(chan, MESG_CHANNEL_ID_ID); /* request sender id */ + } + + // if we don't see a phase 0 message first, reset the watch + if (reset || (phase != 0 && !seenphase0)) { + fprintf(stderr, "resetting\n"); + ack.code = 0x44; ack.atype = 3; ack.c1 = 0x00; ack.c2 = 0x00; ack.id = 0; + ANT_SendAcknowledgedData(chan, (void *)&ack); // tell garmin we're finished + sleep(1); + exit(1); + } + switch (phase) { + case 0: + seenphase0 = 1; + nphase0++; + if (nphase0 % 10 == 0) + donebind = 0; + if (newfreq) { + // switch to new frequency + ANT_SetChannelPeriod(chan, period); + ANT_SetChannelSearchTimeout(chan, 3); + ANT_SetChannelRFFreq(chan, newfreq); + newfreq = 0; + } + // phase 0 seen after reset at end of download + if (downloadfinished) { + fprintf(stderr, "finished\n"); + exit(0); + } + // generate a random id if pairing and user didn't specify one + if (pairing && !myid) { + myid = randno(); + fprintf(stderr, "pairing, using id %08x\n", myid); + } + // need id codes from auth file if not pairing + // TODO: handle multiple watches + // BUG: myauth1 should be allowed to be 0 + if (!pairing && !myauth1) { + int nr; + printf("reading auth data from %s\n", authfile); + authfd = open(authfile, O_RDONLY); + if (authfd < 0) { + perror(authfile); + fprintf(stderr, "No auth data. Need to pair first\n"); + exit(1); + } + nr = read(authfd, authdata, 32); + close(authfd); + if (nr != 32 && nr != 24) { + fprintf(stderr, "bad auth file len %d != 32 or 24\n", nr); + exit(1); + } + // BUG: auth file not portable + memcpy((void *)&myauth1, authdata+16, 4); + memcpy((void *)&myauth2, authdata+20, 4); + memcpy((void *)&mydev, authdata+12, 4); + memcpy((void *)&myid, authdata+4, 4); + if (dbg) + fprintf(stderr, "dev %08x auth %08x %08x id %08x\n", + mydev, myauth1, myauth2, myid); + } + // bind to watch + if (!donebind && devid) { + donebind = 1; + if (isa405) + newfreq = 0x32; + ack.code = 0x44; ack.atype = 2; ack.c1 = isa50 ? 0x32 : newfreq; ack.c2 = 0x04; + ack.id = myid; + ANT_SendAcknowledgedData(chan, (void *)&ack); // bind + } else { + if (dbg) printf("donebind %d devid %x\n", donebind, devid); + } + break; + case 1: + if (dbg) printf("case 1 %x\n", peerdev); + if (peerdev) { + if (dbg) printf("case 1 peerdev\n"); + // if watch has sent id + if (mydev != 0 && peerdev != mydev) { + fprintf(stderr, "Don't know this device %08x != %08x\n", peerdev, mydev); + } else if (!sentauth && !waitauth) { + if (dbg) printf("case 1 diffdev\n"); + assert(sizeof auth == AUTHSIZE); + auth.code = 0x44; auth.atype = 4; auth.phase = 3; auth.u1 = 8; + auth.id = myid; auth.auth1 = myauth1; auth.auth2 = myauth2; + auth.fill1 = auth.fill2 = 0; + sentauth = 1; + ANT_SendBurstTransfer(chan, (void *)&auth, (sizeof auth)/8); // send our auth data + } + } + if (dbg) printf("case 1 cid %x myid %x\n", cid, myid); + if (!sentack2 && cid == myid && !waitauth) { + sentack2 = 1; + if (dbg) printf("sending ack2\n"); + // if it did bind to me before someone else + ack.code = 0x44; ack.atype = 4; ack.c1 = 0x01; ack.c2 = 0x00; + ack.id = myid; + ANT_SendAcknowledgedData(chan, (void *)&ack); // request id + } + break; + case 2: + // successfully authenticated + if (!downloadstarted) { + downloadstarted = 1; + if (dbg) printf("starting download\n"); + ack.code = 0x44; ack.atype = 6; ack.c1 = 0x01; ack.c2 = 0x00; ack.id = 0; + //ANT_SendAcknowledgedData(chan, (void *)&ack); // tell garmin to start upload + } + if (downloadfinished) { + if (dbg) printf("finished download\n"); + ack.code = 0x44; ack.atype = 3; ack.c1 = 0x00; ack.c2 = 0x00; ack.id = 0; + if (!passive) ANT_SendAcknowledgedData(chan, (void *)&ack); // tell garmin we're finished + } + break; + case 3: + if (pairing) { + // waiting for the user to pair + printf("Please press \"View\" on watch to confirm pairing\n"); + waitauth = 2; // next burst data is auth data + } else { + if (dbg) printf("not sure why in phase 3\n"); + if (!sentgetv) { + sentgetv = 1; + //ANT_SendBurstTransferA(chan, getversion, strlen(getversion)/16); + } + } + break; + default: + if (dbg) fprintf(stderr, "Unknown phase %d\n", phase); + break; + } + break; + case EVENT_RX_BURST_PACKET: + // now handled in coalesced burst below + if (dbg) printf("burst\n"); + break; + case EVENT_RX_FAKE_BURST: + if (dbg) printf("rxfake burst pairing %d blast %ld waitauth %d\n", + pairing, (long)blast, waitauth); + blsize = *(int *)(cbuf+4); + memcpy(&blast, cbuf+8, 4); + if (dbg) { + printf("fake burst %d %lx ", blsize, (long)blast); + for (i = 0; i < blsize && i < 64; i++) + printf("%02x", blast[i]); + printf("\n"); + for (i = 0; i < blsize; i++) + if (isprint(blast[i])) + printf("%c", blast[i]); + else + printf("."); + printf("\n"); + } + if (sentauth) { + static int nacksent = 0; + char *ackdata; + static uchar ackpkt[100]; + // ack the last packet + ushort bloblen = blast[14]+256*blast[15]; + ushort pkttype = blast[16]+256*blast[17]; + ushort pktlen = blast[18]+256*blast[19]; + if (bloblen == 0) { + if (dbg) printf("bloblen %d, get next data\n", bloblen); + // request next set of data + ackdata = acks[nacksent++]; + if (!strcmp(ackdata, "")) { // finished + printf("acks finished, resetting\n"); + ack.code = 0x44; ack.atype = 3; ack.c1 = 0x00; + ack.c2 = 0x00; ack.id = 0; + ANT_SendAcknowledgedData(chan, (void *)&ack); // go to idle + sleep(1); + exit(1); + } + if (dbg) printf("got type 0, sending ack %s\n", ackdata); + sprintf(ackpkt, "440dffff00000000%s", ackdata); + } else if (bloblen == 65535) { + // repeat last ack + if (dbg) printf("repeating ack %s\n", ackpkt); + ANT_SendBurstTransferA(chan, ackpkt, strlen(ackpkt)/16); + } else { + if (dbg) printf("non-0 bloblen %d\n", bloblen); + decode(bloblen, pkttype, pktlen, blsize, blast); + sprintf(ackpkt, "440dffff0000000006000200%02x%02x0000", pkttype%256, pkttype/256); + } + if (dbg) printf("received pkttype %d len %d\n", pkttype, pktlen); + if (dbg) printf("acking %s\n", ackpkt); + ANT_SendBurstTransferA(chan, ackpkt, strlen(ackpkt)/16); + } else if (!nopairing && pairing && blast) { + memcpy(&peerdev, blast+12, 4); + if (dbg) + printf("watch id %08x waitauth %d\n", peerdev, waitauth); + if (mydev != 0 && peerdev != mydev) { + fprintf(stderr, "Don't know this device %08x != %08x\n", peerdev, mydev); + exit(1); + } + if (waitauth == 2) { + int nw; + // should be receiving auth data + if (nowriteauth) { + printf("Not overwriting auth data\n"); + exit(1); + } + printf("storing auth data in %s\n", authfile); + authfd = open(authfile, O_WRONLY|O_CREAT, 0644); + if (authfd < 0) { + perror(authfile); + exit(1); + } + nw = write(authfd, blast, blsize); + if (nw != blsize) { + fprintf(stderr, "auth write failed fd %d %d\n", authfd, nw); + perror("write"); + exit(1); + } + close(authfd); + //exit(1); + pairing = 0; + waitauth = 0; + reset = 1; + } + if (pairing && !waitauth) { + //assert(sizeof pair == PAIRSIZE); + pair.code = 0x44; pair.atype = 4; pair.phase = 2; pair.id = myid; + bzero(pair.devname, sizeof pair.devname); + //if (peerdev <= 9999999) // only allow 7 digits + //sprintf(pair.devname, "%u", peerdev); + strcpy(pair.devname, fname); + //else + // fprintf(stderr, "pair dev name too large %08x \"%d\"\n", peerdev, peerdev); + pair.u1 = strlen(pair.devname); + printf("sending pair data for dev %s\n", pair.devname); + waitauth = 1; + if (isa405 && pairing) { + // go straight to storing auth data + waitauth = 2; + } + ANT_SendBurstTransfer(chan, (void *)&pair, (sizeof pair)/8) ; // send pair data + } else { + if (dbg) printf("not pairing\n"); + } + } else if (!gotwatchid && (lastphase == 1)) { + static int once = 0; + gotwatchid = 1; + // garmin sending authentication/identification data + if (!once) { + int i; + once = 1; + if (dbg) + fprintf(stderr, "id data: "); + } + if (dbg) + for (i = 0; i < blsize; i++) + fprintf(stderr, "%02x", blast[i]); + if (dbg) + fprintf(stderr, "\n"); + memcpy(&peerdev, blast+12, 4); + if (dbg) + printf("watch id %08x\n", peerdev); + if (mydev != 0 && peerdev != mydev) { + fprintf(stderr, "Don't know this device %08x != %08x\n", peerdev, mydev); + exit(1); + } + } else if (lastphase == 2) { + int nw; + static int once = 0; + printf("once %d\n", once); + // garmin uploading in response to sendack3 + // in this state we're receiving the workout data + if (!once) { + printf("receiving\n"); + once = 1; + outfd = open(fn, O_WRONLY|O_CREAT, 0644); + if (outfd < 0) { + perror(fn); + exit(1); + } + } + if (last) { + nw = write(outfd, blast, blsize); + if (nw != blsize) { + fprintf(stderr, "data write failed fd %d %d\n", outfd, nw); + perror("write"); + exit(1); + } + close(outfd); + downloadfinished = 1; + } + } else if (0 && last) { + if (dbg) { + fprintf(stderr, "auth response: "); + for (i = 0; i < blsize; i++) + fprintf(stderr, "%02x", cbuf[i]); + fprintf(stderr, "\n"); + } + if (blast[10] == 2) { + fprintf(stderr, "authentication failed\n"); + exit(1); + } + } else if (last) { + fprintf(stderr, "data in state xx: "); + int i; + for (i = 0; i < blsize; i++) + fprintf(stderr, "%02x", blast[i]); + fprintf(stderr, "\n"); + sentcmd = 1000; + switch (sentcmd) { + case 1000: + break; + case 0: + sentcmd++; + //ANT_SendBurstTransferA(chan, getgpsver, strlen(getgpsver)/16); + break; + case 999: + printf("finished\n"); + exit(1); + default: + sleep(1); + sentcmd = 1; + //printf("sending command %d %s\n", sentcmd-1, cmds[sentcmd-1]); + //ANT_SendBurstTransferA(chan, cmds[sentcmd-1], + // strlen(cmds[sentcmd-1])/16); + sentcmd++; + //if(!strcmp(cmds[sentcmd-1], "END")) + // sentcmd = 999; + break; + } + } + if (dbg) printf("continuing after burst\n"); + break; + } + return 1; +} + +uchar +revent(uchar chan, uchar event) +{ + struct ack_msg ack; + int i; + + if (dbg) printf("revent %02x %02x\n", chan, event); + switch (event) { + case EVENT_TRANSFER_TX_COMPLETED: + if (dbg) printf("Transfer complete %02x\n", ebuf[1]); + break; + case INVALID_MESSAGE: + printf("Invalid message %02x\n", ebuf[1]); + break; + case RESPONSE_NO_ERROR: + switch (ebuf[1]) { + case MESG_ASSIGN_CHANNEL_ID: + ANT_AssignChannelEventFunction(chan, chevent, cbuf); + break; + case MESG_OPEN_CHANNEL_ID: + printf("channel open, waiting for broadcast\n"); + break; + default: + if (dbg) printf("Message %02x NO_ERROR\n", ebuf[1]); + break; + } + break; + case MESG_CHANNEL_ID_ID: + devid = ebuf[1]+ebuf[2]*256; + if (mydev == 0 || devid == mydev%65536) { + if (dbg) + printf("devid %08x myid %08x\n", devid, myid); + } else { + printf("Ignoring unknown device %08x, mydev %08x\n", devid, mydev); + devid = sentid = 0; // reset + } + break; + case MESG_NETWORK_KEY_ID: + case MESG_SEARCH_WAVEFORM_ID: + case MESG_OPEN_CHANNEL_ID: + printf("response event %02x code %02x\n", event, ebuf[2]); + for (i = 0; i < 8; i++) + fprintf(stderr, "%02x", ebuf[i]); + fprintf(stderr, "\n"); + break; + case MESG_CAPABILITIES_ID: + if (dbg) + printf("capabilities chans %d nets %d opt %02x adv %02x\n", + ebuf[0], ebuf[1], ebuf[2], ebuf[3]); + break; + case MESG_CHANNEL_STATUS_ID: + if (dbg) + printf("channel status %d\n", ebuf[1]); + break; + case EVENT_RX_FAIL: + // ignore this + break; + default: + printf("Unhandled response event %02x\n", event); + break; + } + return 1; +} + +main(int ac, char *av[]) +{ + int devnum = 0; + int chan = 0; + int net = 0; + int chtype = 0; // wildcard + int devno = 0; // wildcard + int devtype = 0; // wildcard + int manid = 0; // wildcard + int freq = 0x32; // garmin specific radio frequency + int srchto = 255; // max timeout + int waveform = 0x0053; // aids search somehow + int c; + extern char *optarg; + extern int optind, opterr, optopt; + + // default auth file // + if (getenv("HOME")) { + authfile = malloc(strlen(getenv("HOME"))+strlen("/.gant")+1); + if (authfile) + sprintf(authfile, "%s/.gant", getenv("HOME")); + } + progname = av[0]; + while ((c = getopt(ac, av, "a:o:d:i:m:PpvDrnzf:")) != -1) { + switch(c) { + case 'a': + authfile = optarg; + break; + case 'f': + fname = optarg; + break; + case 'o': + fn = optarg; + break; + case 'd': + devnum = atoi(optarg); + break; + case 'i': + myid = atoi(optarg); + break; + case 'm': + mydev = atoi(optarg); + break; + case 'p': + passive = 1; + semipassive = 0; + break; + case 'P': + passive = 1; + break; + case 'v': + verbose = 1; + break; + case 'D': + dbg = 1; + break; + case 'r': + reset = 1; + break; + case 'n': + nowriteauth = 1; + break; + case 'z': + nopairing = 1; + break; + default: + fprintf(stderr, "unknown option %s\n", optarg); + usage(); + } + } + + ac -= optind; + av += optind; + + if ((!passive && !authfile) || ac) + usage(); + + if (!ANT_Init(devnum, 0)) { // should be 115200 but doesn't fit into a short + fprintf(stderr, "open dev %d failed\n", devnum); + exit(1); + } + ANT_ResetSystem(); + ANT_AssignResponseFunction(revent, ebuf); + ANT_RequestMessage(chan, MESG_CHANNEL_STATUS_ID); //informative + ANT_SetNetworkKeya(net, ANTSPT_KEY); + ANT_AssignChannel(chan, chtype, net); + // Wali: changed order of the following seq. according windows + ANT_SetChannelPeriod(chan, period); + ANT_SetChannelSearchTimeout(chan, srchto); + ANT_RequestMessage(chan, MESG_CAPABILITIES_ID); //informative + ANT_SetChannelRFFreq(chan, freq); + ANT_SetSearchWaveform(chan, waveform); + ANT_SetChannelId(chan, devno, devtype, manid); + ANT_OpenChannel(chan); + ANT_RequestMessage(chan, MESG_CHANNEL_STATUS_ID); //informative + + // everything handled in event functions + for(;;) + sleep(10); +} diff --git a/gant.o b/gant.o new file mode 100644 index 0000000000000000000000000000000000000000..5b4f6cdf09fbd89bd7f38b5910b764c32a4fb35c GIT binary patch literal 86320 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 char *releasetime = "Jan 24 2009, 16:10:12"; +464,467c464,466 +< if (cad != 255) { +< if (sporttyp_track[track_id-firsttrack_id] == 0) { +< fprintf(tcxfile, " %d\n", cad); +< } else { +--- +> // for bike the average cadence of this lap is here +> if (sporttyp_track[track_id-firsttrack_id] == 1) { +> if (cad != 255) { +480a480,489 +> // I prefere the average run cadence here than at the end of this lap according windows ANTagent +> if (sporttyp_track[track_id-firsttrack_id] == 0) { +> if (cad != 255) { +> fprintf(tcxfile, " \n"); +> fprintf(tcxfile, " \n"); +> fprintf(tcxfile, " %d\n", cad); +> fprintf(tcxfile, " \n"); +> fprintf(tcxfile, " \n"); +> } +> } +483d491 +< track_pause = 0; +490,493d497 +< } // end of if (tv >= tv_lap && lap <= lastlap) +< if (track_pause) { +< fprintf(tcxfile, " \n"); +< fprintf(tcxfile, " \n"); +495,496c499 +< if (dbg) printf("track pause (stop and go)\n"); +< } +--- +> } // end of if (tv >= tv_lap && lap <= lastlap) +511a515,519 +> // track pause only if following trackpoint is aswell 'timemarker' with utopic distance +> if (track_pause && dist > (float)40000000) { +> fprintf(tcxfile, " \n"); +> fprintf(tcxfile, " \n"); +> } +532,533c540,544 +< if (u1 > 0 && cad != 255) { +< fprintf(tcxfile, " %d\n", cad); +--- +> // for bikes the cadence is written here and for the footpod in , why garmin? +> if (sporttyp_track[track_id-firsttrack_id] == 1) { +> if (cad != 255) { +> fprintf(tcxfile, " %d\n", cad); +> } +540,548c551,552 +< switch(sporttyp_track[track_id-firsttrack_id]) { +< case 1: fprintf(tcxfile, "Bike\""); break; +< case 0: +< default: fprintf(tcxfile, "Footpod\""); break; +< } +< if (u1 == 0 && cad != 255) { +< fprintf(tcxfile, ">\n"); +< fprintf(tcxfile, " %d\n", cad); +< fprintf(tcxfile, " \n"); +--- +> if (sporttyp_track[track_id-firsttrack_id] == 1) { +> fprintf(tcxfile, "Bike\"/>\n"); +550c554,561 +< fprintf(tcxfile, "/>\n"); +--- +> fprintf(tcxfile, "Footpod\""); +> if (cad != 255) { +> fprintf(tcxfile, ">\n"); +> fprintf(tcxfile, " %d\n", cad); +> fprintf(tcxfile, " \n"); +> } else { +> fprintf(tcxfile, "/>\n"); +> } +553,556c564 +< } else { +< // maybe if we recieve utopic position and distance this tells pause in the run (stop and go) +< if (track_pause == 0) track_pause = 1; +< else track_pause = 0; +--- +> track_pause = 0; +558a567,573 +> // maybe if we recieve utopic position and distance this tells pause in the run (stop and go) if not begin or end of lap +> if (dist > (float)40000000 && track_pause == 0) { +> track_pause = 1; +> if (dbg) printf("track pause (stop and go)\n"); +> } else { +> track_pause = 0; +> } diff --git a/resources/gant.png b/resources/gant.png new file mode 100644 index 0000000000000000000000000000000000000000..ae014c16f66c9c6764d15d92cf73cfad236de307 GIT binary patch literal 724 zcmV;_0xSKAP)Px#PEbr#MF0Q*0|WpF2o4n$7$PMpG&edxL`O+tg5{{Q>|Ddw600004WQchC#qWWnZgc@6KZwa#Bo1w6u>qK8Mo z3D&?fKB^A}p8+4_2S6h0G_+B|_v!?AM<2Mww=^p3f5JIJJcd1z_skMgN>P~EzoUsf zOP<#)rX=rhwqEl3I}*D?+WF9QGZPxLQkuRH9hDs#O8`2@HXEn>EC;j|h1q~kb4xH$ zaqs%llfnR=y-z`ch<>Q672B;kt!q$0)VYvFOVn1-t^@a^OD@KR7)99_S*{@n;=E2B zaU0XjAhp(Oz1?cntW{gqTBK7*$&*ofS(dA2{rX;(dLtnHO|Qp@=QY7bf9*Lc4XW~5 zcv}&k+V@-9frF8#;W36}D@j=^?bB7MGwCRjvUD~s2$5MKe*I^dqTe^*^e{E~jnvhCKy*+Cr z=cxXG%^JwwgN$~=T}Q;2vTP}Hj63jMPNHpUdpZiBjCQO|y$KFwftza|em&bh&G zXvHWY#3WwbjYAHs8~oVZ+R0Tuv?WB;TbzO6t#cbyZC|KU+17*kVD!*@R=3ku|A}j1 zfS