diff options
author | RALOVICH, Kristof <tade60@freemail.hu> | 2013-10-11 20:14:50 +0200 |
---|---|---|
committer | RALOVICH, Kristof <tade60@freemail.hu> | 2013-10-11 20:14:50 +0200 |
commit | d0868152f37abbf3d700dafe762e98acd2a04735 (patch) | |
tree | 8987c801c48e00993ed6507eb8051a2cc1097d94 | |
parent | c47e9d4d2a7bda87b3db0b3728f424ec739613ec (diff) | |
parent | ce9c817d058083a6290cf7d319b68b9513349eb3 (diff) |
merge gant into antpm
Do this by merging the branch upstream from git+ssh://git.debian.org/git/pkg-running/garmin-ant-downloader.git into the src/gant folder.
-rw-r--r-- | src/gant/GantMonitor.glade | 244 | ||||
-rwxr-xr-x | src/gant/GantMonitor.py | 167 | ||||
-rw-r--r-- | src/gant/Makefile | 13 | ||||
-rw-r--r-- | src/gant/README | 32 | ||||
-rw-r--r-- | src/gant/antdefs.h | 51 | ||||
-rw-r--r-- | src/gant/antlib.c | 629 | ||||
-rw-r--r-- | src/gant/antlib.h | 30 | ||||
-rw-r--r-- | src/gant/gant.c | 1407 | ||||
-rw-r--r-- | src/gant/resources/gant.png | bin | 0 -> 724 bytes |
9 files changed, 2573 insertions, 0 deletions
diff --git a/src/gant/GantMonitor.glade b/src/gant/GantMonitor.glade new file mode 100644 index 0000000..73db308 --- /dev/null +++ b/src/gant/GantMonitor.glade @@ -0,0 +1,244 @@ +<?xml version="1.0"?> +<glade-interface> + <!-- interface-requires gtk+ 2.16 --> + <!-- interface-naming-policy toplevel-contextual --> + <widget class="GtkDialog" id="SettingsDialog"> + <property name="title" translatable="yes" context="yes">Gant Settings</property> + <property name="default_width">318</property> + <property name="default_height">260</property> + <property name="type_hint">menu</property> + <child internal-child="vbox"> + <widget class="GtkVBox" id="dialog1-vbox"> + <property name="visible">True</property> + <child> + <widget class="GtkTable" id="table1"> + <property name="visible">True</property> + <property name="n_rows">3</property> + <property name="n_columns">2</property> + <child> + <widget class="GtkLabel" id="label22"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes" context="yes">label22</property> + </widget> + </child> + <child> + <widget class="GtkLabel" id="label23"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes" context="yes">label23</property> + </widget> + <packing> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + </packing> + </child> + <child> + <widget class="GtkLabel" id="label24"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes" context="yes">label24</property> + </widget> + <packing> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + </packing> + </child> + <child> + <widget class="GtkEntry" id="entry1"> + <property name="visible">True</property> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + </packing> + </child> + <child> + <widget class="GtkEntry" id="entry2"> + <property name="visible">True</property> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + </packing> + </child> + <child> + <widget class="GtkEntry" id="entry3"> + <property name="visible">True</property> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + </packing> + </child> + </widget> + <packing> + <property name="position">2</property> + </packing> + </child> + <child internal-child="action_area"> + <widget class="GtkHButtonBox" id="dialog1-action_area"> + <property name="visible">True</property> + <property name="layout_style">end</property> + <child> + <widget class="GtkButton" id="button1"> + <property name="label" context="yes">gtk-ok</property> + <property name="response_id">1</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="receives_default">False</property> + <property name="use_stock">True</property> + <property name="image_position">top</property> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <widget class="GtkButton" id="button2"> + <property name="label" context="yes">gtk-cancel</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="receives_default">False</property> + <property name="use_stock">True</property> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + </widget> + <packing> + <property name="expand">False</property> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + </widget> + </child> + </widget> + <widget class="GtkDialog" id="DownloadDialog"> + <property name="border_width">5</property> + <property name="title" translatable="yes">Gant Monitor: Downloading data...</property> + <property name="window_position">center</property> + <property name="default_width">415</property> + <property name="default_height">140</property> + <property name="destroy_with_parent">True</property> + <property name="icon">resources/gant.png</property> + <property name="type_hint">normal</property> + <child internal-child="vbox"> + <widget class="GtkVBox" id="dialog-vbox2"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <property name="spacing">2</property> + <child> + <widget class="GtkVBox" id="vbox1"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <child> + <widget class="GtkLabel" id="lbl_status"> + <property name="visible">True</property> + <property name="label" translatable="yes">Gant status:</property> + <property name="ellipsize">start</property> + <property name="single_line_mode">True</property> + </widget> + <packing> + <property name="expand">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <widget class="GtkProgressBar" id="progressbar"> + <property name="visible">True</property> + </widget> + <packing> + <property name="expand">False</property> + <property name="position">1</property> + </packing> + </child> + <child> + <widget class="GtkExpander" id="details_section"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <signal name="activate" handler="on_expand_details" after="yes"/> + <child> + <placeholder/> + </child> + <child> + <widget class="GtkLabel" id="label1"> + <property name="visible">True</property> + <property name="label" translatable="yes">Details</property> + </widget> + <packing> + <property name="type">label_item</property> + </packing> + </child> + </widget> + <packing> + <property name="expand">False</property> + <property name="position">2</property> + </packing> + </child> + </widget> + <packing> + <property name="position">1</property> + </packing> + </child> + <child internal-child="action_area"> + <widget class="GtkHButtonBox" id="dialog-action_area2"> + <property name="visible">True</property> + <property name="layout_style">end</property> + <child> + <widget class="GtkButton" id="button1"> + <property name="label" translatable="yes">Restart</property> + <property name="response_id">4</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <signal name="activate" handler="on_restart_process"/> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <widget class="GtkButton" id="btn_close"> + <property name="label">gtk-stop</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_stock">True</property> + <property name="image_position">right</property> + <signal name="activate" handler="gtk_widget_destroy"/> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + </widget> + <packing> + <property name="expand">False</property> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + </widget> + </child> + </widget> + <widget class="GtkWindow" id="window1"> + <child> + <placeholder/> + </child> + </widget> +</glade-interface> diff --git a/src/gant/GantMonitor.py b/src/gant/GantMonitor.py new file mode 100755 index 0000000..5af7ea4 --- /dev/null +++ b/src/gant/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 = ''' + <ui> + <menubar name="Menubar"> + <menu action="Menu"> + <menuitem action="Download"/> + <menuitem action="Preferences"/> + <separator/> + <menuitem action="About"/> + <separator/> + <menuitem action="Exit"/> + </menu> + </menubar> + </ui>''' + + 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('<b>_Download...</b>') + 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/src/gant/Makefile b/src/gant/Makefile new file mode 100644 index 0000000..c08a90f --- /dev/null +++ b/src/gant/Makefile @@ -0,0 +1,13 @@ +CFLAGS=-g -Wall `xml2-config --cflags` +LDLIBS=-lpthread -lm `xml2-config --libs` + +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/src/gant/README b/src/gant/README new file mode 100644 index 0000000..c258f23 --- /dev/null +++ b/src/gant/README @@ -0,0 +1,32 @@ +Test version of 405 download +Needs lots of work! + +Usage: + +Put 405 in pairing mode +./gant -f paul -a auth405 + +This gives your garmin a name of "paul" and an auth file of "auth405" +The 405 will ask you if you wish to pair with "paul". Obviously +you might want to use a different name :) + +Download with: +./gant -nza auth405 > output + +or -Dnza if you want lots of debug output + +It doesn't seem to be very reliable at starting the download and +I haven't investigated why yet. So, if it doesn't start downloading +press ctrl-c and try again. I find it helps to take the watch out +of power save mode. + +If you've already downloaded the data, then you'll need to set +"force send" to yes on the watch. + +Compare output with .TCX file produce by windows ANT Agent. + +Patch gant.c +Send me patches! + +Paul + diff --git a/src/gant/antdefs.h b/src/gant/antdefs.h new file mode 100644 index 0000000..1112012 --- /dev/null +++ b/src/gant/antdefs.h @@ -0,0 +1,51 @@ +// copyright 2008-2009 paul@ant.sbrk.co.uk. released under GPLv3 +// vers 0.6t + +#ifndef __ANTDEFS_H__ +#define __ANTDEFS_H__ +typedef unsigned char uchar; +typedef uchar (*RESPONSE_FUNC)(uchar chan, uchar msgid); +typedef uchar (*CHANNEL_EVENT_FUNC)(uchar chan, uchar event); + +#define RESPONSE_NO_ERROR 0x00 +#define EVENT_RX_SEARCH_TIMEOUT 0x01 +#define EVENT_RX_FAIL 0x02 +#define EVENT_TRANSFER_RX_FAILED 0x04 +#define EVENT_TRANSFER_TX_COMPLETED 0x05 +#define EVENT_TRANSFER_TX_FAILED 0x06 +#define INVALID_MESSAGE 0x28 +#define MESG_RESPONSE_EVENT_ID 0x40 +#define MESG_UNASSIGN_CHANNEL_ID 0x41 +#define MESG_ASSIGN_CHANNEL_ID 0x42 +#define MESG_CHANNEL_MESG_PERIOD_ID 0x43 +#define MESG_CHANNEL_SEARCH_TIMEOUT_ID 0x44 +#define MESG_CHANNEL_RADIO_FREQ_ID 0x45 +#define MESG_NETWORK_KEY_ID 0x46 +#define MESG_SEARCH_WAVEFORM_ID 0x49 +#define MESG_SYSTEM_RESET_ID 0x4a +#define MESG_OPEN_CHANNEL_ID 0x4b +#define MESG_CLOSE_CHANNEL_ID 0x4c +#define MESG_REQUEST_ID 0x4d +#define MESG_BROADCAST_DATA_ID 0x4e +#define MESG_ACKNOWLEDGED_DATA_ID 0x4f +#define MESG_BURST_DATA_ID 0x50 +#define MESG_CHANNEL_ID_ID 0x51 +#define MESG_CHANNEL_STATUS_ID 0x52 +#define MESG_CAPABILITIES_ID 0x54 +#define MESG_OPEN_RX_SCAN_ID 0x5b +#define MESG_EXT_BROADCAST_DATA_ID 0x5d +#define MESG_EXT_ACKNOWLEDGED_DATA_ID 0x5e +#define MESG_EXT_BURST_DATA_ID 0x5f +#define EVENT_RX_BROADCAST 0x9a +#define EVENT_RX_ACKNOWLEDGED 0x9b +#define EVENT_RX_BURST_PACKET 0x9c +#define EVENT_RX_EXT_BROADCAST 0x9d +#define EVENT_RX_EXT_ACKNOWLEDGED 0x9e +#define EVENT_RX_EXT_BURST_PACKET 0x9f +#define MESG_TX_SYNC 0xa4 +#define EVENT_RX_FAKE_BURST 0xdd +#define MESG_RESPONSE_EVENT_SIZE 3 +#define MESG_DATA_SIZE 30 + +#endif +/* vim: se sw=8 ts=8: */ diff --git a/src/gant/antlib.c b/src/gant/antlib.c new file mode 100644 index 0000000..de20f66 --- /dev/null +++ b/src/gant/antlib.c @@ -0,0 +1,629 @@ +// copyright 2008 paul@ant.sbrk.co.uk. released under GPLv3 +// vers 0.6t +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stdio.h> +#include <string.h> +#include <pthread.h> +#include <termios.h> +#include <stdlib.h> + +#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 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"); + } + } else if (dbg == 2) { + // Wali: additional raw data output + printf(">>>\n 00000000:"); + for (i = 0; i < (len + 4); i++) { + printf(" %02x", buf[i]); + } + putchar('\n'); + } + 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 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); + } else if (dbg == 2) { + // Wali: additional raw data output + printf("<<<\n 00000000:"); + for (i = 0; i < bufc; i++) { + printf(" %02x", buf[i]); + } + putchar('\n'); + } + // 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, (long)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, (long)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, (long)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; +} + +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); + } + } +} + +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((char*)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]; + + 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((char*)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]; + + 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((char*)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 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/src/gant/antlib.h b/src/gant/antlib.h new file mode 100644 index 0000000..a8c95d3 --- /dev/null +++ b/src/gant/antlib.h @@ -0,0 +1,30 @@ +#ifndef __ANTLIB_H__ +#define __ANTLIB_H__ + +#include "antdefs.h" +uchar ANT_ResetSystem(void); +uchar ANT_Cmd55(uchar chan); +uchar ANT_OpenRxScanMode(uchar chan); +uchar ANT_Initf(char *devname, ushort baud); +uchar ANT_Init(uchar devno, ushort baud); +uchar ANT_RequestMessage(uchar chan, uchar mesg); +uchar ANT_SetNetworkKeya(uchar net, uchar *key); +uchar ANT_SetNetworkKey(uchar net, uchar *key); +uchar ANT_AssignChannel(uchar chan, uchar chtype, uchar net); +uchar ANT_UnAssignChannel(uchar chan); +uchar ANT_SetChannelId(uchar chan, ushort dev, uchar devtype, uchar manid); +uchar ANT_SetChannelRFFreq(uchar chan, uchar freq); +uchar ANT_SetChannelPeriod(uchar chan, ushort period); +uchar ANT_SetChannelSearchTimeout(uchar chan, uchar timeout); +uchar ANT_SetSearchWaveform(uchar chan, ushort waveform); +uchar ANT_SendAcknowledgedDataA(uchar chan, uchar *data); // ascii version +uchar ANT_SendAcknowledgedData(uchar chan, uchar *data); +ushort ANT_SendBurstTransferA(uchar chan, uchar *data, ushort numpkts); +ushort ANT_SendBurstTransfer(uchar chan, uchar *data, ushort numpkts); +uchar ANT_OpenChannel(uchar chan); +uchar ANT_CloseChannel(uchar chan); +void ANT_AssignResponseFunction(RESPONSE_FUNC rf, uchar* rbuf); +void ANT_AssignChannelEventFunction(uchar chan, CHANNEL_EVENT_FUNC rf, uchar* rbuf); +int ANT_fd(); + +#endif diff --git a/src/gant/gant.c b/src/gant/gant.c new file mode 100644 index 0000000..1b5ea6b --- /dev/null +++ b/src/gant/gant.c @@ -0,0 +1,1407 @@ +// copyright 2010 Klaus@Ethgen.de. released under GPLv3 +// copyright 2008-2009 paul@ant.sbrk.co.uk +// copyright 2009-2009 Wali +#include <stdio.h> +#include <sys/select.h> +#include <sys/time.h> +#include <sys/types.h> +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <assert.h> +#include <time.h> +#include <math.h> +#include <ctype.h> + +#include <libxml/encoding.h> +#include <libxml/xmlwriter.h> + +#include "antlib.h" +#include "antdefs.h" + +#define DEBUG_OUT(level, ...) {if (dbg && level <= dbg){fprintf(stderr, "DEBUG(%d): ", level); fprintf(stderr, __VA_ARGS__); fprintf(stderr, " in line %d.\n", __LINE__);}} +#define ERROR_OUT(...) {fprintf(stderr, "ERROR: "); fprintf(stderr, __VA_ARGS__); fprintf(stderr, " in line %d.\n", __LINE__);} +#define XML_ERROR_CHECK {if (rc < 0){ERROR_OUT("Error in XML creation"); exit(-1);}} + +typedef enum { + XML_OUTSIDE, + XML_IN_TrainingCenterDatabase, + XML_IN_Activities, + XML_IN_Activity, + XML_IN_Lap, + XML_IN_Track, + XML_IN_Trackpoint, +} xml_pos; + +struct _activity { + unsigned short valid; + unsigned short first_lap; + unsigned short last_lap; + unsigned short sport; +}; + +struct _lap { + unsigned short valid; + time_t timestamp; + unsigned long duration; + float distance; + float max_speed; + unsigned short calories; + int hr_avg; + int hr_max; + int intensity; + int cadence; + int trigger; +}; + +// all version numbering according ant agent for windows 2.2.1 +char *releasetime = "Apr 25 2010, 10:00:00"; +uint majorrelease = 2; +uint minorrelease = 2; +uint majorbuild = 7; +uint minorbuild = 0; + +double round(double); + +int gottype; +int sentauth; +int gotwatchid; +int nopairing = 0; +int nowriteauth = 0; +int reset = 0; +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"; +time_t garmin_epoch = 631065600; + +static uchar 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 = 0; +int verbose = 0; + +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]; +char devname[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; +char *progname; + +#define BSIZE 8*10000 +//uchar blast[BSIZE]; // should be like that, but not working +uchar *blast = 0; // first time reading not initialized, but why it is working reading from adr 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; + snprintf(res, 30, "%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); + + snprintf(time, 50, "%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(xmlTextWriterPtr tcxfile) +{ + int rc; + + /* Start the Document */ + rc = xmlTextWriterStartDocument(tcxfile, "1.0", "UTF-8", "no"); XML_ERROR_CHECK; + + /* Start the TrainingCenterDatabase */ + rc = xmlTextWriterStartElementNS(tcxfile, NULL, BAD_CAST "TrainingCenterDatabase", BAD_CAST "http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2"); XML_ERROR_CHECK; + rc = xmlTextWriterWriteAttributeNS(tcxfile, BAD_CAST "xsi", BAD_CAST "schemaLocation", BAD_CAST "http://www.w3.org/2001/XMLSchema-instance", BAD_CAST "http://www.garmin.com/xmlschemas/ActivityExtension/v2 http://www.garmin.com/xmlschemas/ActivityExtensionv2.xsd http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2 http://www.garmin.com/xmlschemas/TrainingCenterDatabasev2.xsd"); XML_ERROR_CHECK; + + /* Finally start the Activities element */ + rc = xmlTextWriterStartElement(tcxfile, BAD_CAST "Activities"); XML_ERROR_CHECK; + + return; +} + +void print_tcx_footer(xmlTextWriterPtr tcxfile) +{ + int rc; + + /* *INDENT-OFF* */ + rc = xmlTextWriterEndElement(tcxfile); XML_ERROR_CHECK; /* Track (Is it a good idea to do it here?) */ + rc = xmlTextWriterEndElement(tcxfile); XML_ERROR_CHECK; /* Lap (Is it a good idea to do it here?) */ + rc = xmlTextWriterStartElement(tcxfile, BAD_CAST "Creator"); XML_ERROR_CHECK; + rc = xmlTextWriterWriteAttribute(tcxfile, BAD_CAST "xsi:type", BAD_CAST "Device_t"); XML_ERROR_CHECK; + rc = xmlTextWriterWriteFormatElement(tcxfile, BAD_CAST "Name", "%s", devname); XML_ERROR_CHECK; + rc = xmlTextWriterWriteFormatElement(tcxfile, BAD_CAST "UnitId", "%u", unitid); XML_ERROR_CHECK; + rc = xmlTextWriterWriteFormatElement(tcxfile, BAD_CAST "ProductID", "%u", part); XML_ERROR_CHECK; + rc = xmlTextWriterStartElement(tcxfile, BAD_CAST "Version"); XML_ERROR_CHECK; + rc = xmlTextWriterWriteFormatElement(tcxfile, BAD_CAST "VersionMajor", "%u", ver / 100); XML_ERROR_CHECK; + rc = xmlTextWriterWriteFormatElement(tcxfile, BAD_CAST "VersionMinor", "%u", ver % 100); XML_ERROR_CHECK; + rc = xmlTextWriterWriteElement(tcxfile, BAD_CAST "BuildMajor", BAD_CAST "0"); XML_ERROR_CHECK; + rc = xmlTextWriterWriteElement(tcxfile, BAD_CAST "BuildMinor", BAD_CAST "0"); XML_ERROR_CHECK; + rc = xmlTextWriterEndElement(tcxfile); XML_ERROR_CHECK; /* Version */ + rc = xmlTextWriterEndElement(tcxfile); XML_ERROR_CHECK; /* Creator */ + rc = xmlTextWriterEndElement(tcxfile); XML_ERROR_CHECK; /* Activity */ + rc = xmlTextWriterEndElement(tcxfile); XML_ERROR_CHECK; /* Activities */ + rc = xmlTextWriterStartElement(tcxfile, BAD_CAST "Author"); XML_ERROR_CHECK; + rc = xmlTextWriterWriteAttribute(tcxfile, BAD_CAST "xsi:type", BAD_CAST "Application_t"); XML_ERROR_CHECK; + rc = xmlTextWriterWriteElement(tcxfile, BAD_CAST "Name", BAD_CAST "Garmin ANT Agent(tm)"); XML_ERROR_CHECK; + rc = xmlTextWriterStartElement(tcxfile, BAD_CAST "Build"); XML_ERROR_CHECK; + rc = xmlTextWriterStartElement(tcxfile, BAD_CAST "Version"); XML_ERROR_CHECK; + rc = xmlTextWriterWriteFormatElement(tcxfile, BAD_CAST "VersionMajor", "%u", majorrelease); XML_ERROR_CHECK; + rc = xmlTextWriterWriteFormatElement(tcxfile, BAD_CAST "VersionMinor", "%u", minorrelease); XML_ERROR_CHECK; + rc = xmlTextWriterWriteFormatElement(tcxfile, BAD_CAST "BuildMajor", "%u", majorbuild); XML_ERROR_CHECK; + rc = xmlTextWriterWriteFormatElement(tcxfile, BAD_CAST "BuildMinor", "%u", minorbuild); XML_ERROR_CHECK; + rc = xmlTextWriterEndElement(tcxfile); XML_ERROR_CHECK; /* Version */ + rc = xmlTextWriterWriteElement(tcxfile, BAD_CAST "Type", BAD_CAST "Release"); XML_ERROR_CHECK; + rc = xmlTextWriterWriteFormatElement(tcxfile, BAD_CAST "Time", "%s", releasetime); XML_ERROR_CHECK; + rc = xmlTextWriterWriteElement(tcxfile, BAD_CAST "Builder", BAD_CAST "sqa"); XML_ERROR_CHECK; + rc = xmlTextWriterEndElement(tcxfile); XML_ERROR_CHECK; /* Build */ + rc = xmlTextWriterWriteElement(tcxfile, BAD_CAST "LangID", BAD_CAST "EN"); XML_ERROR_CHECK; + rc = xmlTextWriterWriteElement(tcxfile, BAD_CAST "PartNumber", BAD_CAST "006-A0214-00"); XML_ERROR_CHECK; + /* *INDENT-ON* */ + return; +} + +void dump_data(FILE * out, void *data, size_t offset, size_t size) +{ + unsigned char buf[16]; + int i, j; + for (i=0; i < size; i+=16) + { + memset(buf, 0, sizeof buf); + memcpy(buf, data + offset + i, (size-i >= 16)? 16 : (size-i)); + fprintf(out, "0x%04x:", offset + i); + + for (j=0; j < 16; j++) + { + if (j == 8) + fprintf(out, " "); + + if (i+j >= size) + fprintf(out, " "); + else + fprintf(out, " %02x", buf[j]); + } // for (j=0; j < 16; j++) + + fprintf(out, " "); + for (j=0; j < 16; j++) + { + if (i+j >= size) + break; + + if (j == 8) + fprintf(out, " "); + + if (isprint(buf[j])) + fprintf(out, "%c", buf[j]); + else + fprintf(out, "."); + } // for (j=0; j < 16; j++) + + fprintf(out, "\n"); + } // for (i=0; i < size; i+=16) + + return; +} // void dump_data(void *data, siz... + + +#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*256 // max number of tracks to be saved per download + +void decode(ushort bloblen, ushort pkttype, ushort pktlen, int dsize, uchar * data) +{ + int i; + int hr; + int cad; + int u1, u2; + int doff = 20; + char model[256]; + char gpsver[256]; + float alt; + float dist; + time_t tv; + char tbuf[100]; + double lat, lon; + uint ndata; + time_t tv_lap; + static xmlTextWriterPtr tcxfile = NULL; + static ushort track_pause = 0; + int rc; + static xml_pos xml_position = XML_OUTSIDE; + static struct _activity *activities = NULL; + static struct _lap *laps = NULL; + static unsigned short lap_id; + static unsigned short activity_id; + + DEBUG_OUT(3, "decode %d %d %d %d", bloblen, pkttype, pktlen, dsize); + switch (pkttype) + { + case 255: // Identifier + memset(model, 0, sizeof model); + memcpy(model, data + doff + 4, pktlen - 4); + part = data[doff] + data[doff + 1] * 256; + ver = data[doff + 2] + data[doff + 3] * 256; + DEBUG_OUT(1, "Packet %d: Part#: %d Version: %d Name: %s", pkttype, part, ver, model); + break; + case 248: // GPS identifier + memset(gpsver, 0, sizeof gpsver); + memcpy(gpsver, data + doff, pktlen); + DEBUG_OUT(1, "Packet %d: GPS: %s", pkttype, gpsver); + break; + case 253: + DEBUG_OUT(1, "Packet %d: Unknown", pkttype); + if (dbg >= 2) + dump_data(stderr, data+doff, 0, pktlen); + break; + case 525: + memset(devname, 0, sizeof devname); + memcpy(devname, data + doff, pktlen); + DEBUG_OUT(1, "Packet %d: Devname: \"%s\"", pkttype, devname); + break; + case 12: + DEBUG_OUT(1, "Packet %d: xfer complete (subtype %u)", pkttype, data[doff] + data[doff + 1] * 256); + if (dbg >= 2) + dump_data(stderr, data+doff, 2, pktlen-2); + switch (data[doff] + data[doff + 1] * 256) + { + case 6: // Wayboint end + // last file completed, add footer and close file + if (tcxfile) + { + print_tcx_footer(tcxfile); + xml_position = XML_OUTSIDE; + rc = xmlTextWriterEndDocument(tcxfile); XML_ERROR_CHECK; + xmlFreeTextWriter(tcxfile); + tcxfile = NULL; + } + break; + case 117: // Lap informations end + break; + case 450: // Track informations end + break; + default: + break; + } + break; + case 38: + unitid = data[doff] + data[doff + 1] * 256 + data[doff + 2] * 256 * 256 + data[doff + 3] * 256 * 256 * 256; + DEBUG_OUT(1, "Packet %d: UnitID: %u", pkttype, unitid); + break; + case 27: // Number of following packages + ndata = data[doff] + data[doff + 1] * 256; + DEBUG_OUT(2, "Packet %d: Number of following packages: %u", pkttype, ndata); + break; + case 1523: // Max Trackpoints (?) 10000 + case 994: // Drei Limits (?) 200, 25, 200 + case 1066: // 20, 200, 100, ???? + DEBUG_OUT(1, "Packet %d", pkttype); + if (dbg >= 2) + dump_data(stderr, data+doff, 0, pktlen); + break; + case 14: // Current time (UTC) + DEBUG_OUT(1, "Packet %d: Current time: %04u-%02u-%02u %02u:%02u:%02u", pkttype, data[doff + 2] + data[doff + 3] * 256, data[doff + 1], data[doff], data[doff + 4], data[doff + 6], data[doff + 7]); + break; + case 17: + DEBUG_OUT(1, "Packet %d: Position?", pkttype); + if (dbg >= 2) + dump_data(stderr, data+doff, 0, pktlen); + break; + case 990: // Activity specification + DEBUG_OUT(1, "Packet %d: Activity %u: laps %u-%u sport %u", pkttype, data[doff] + data[doff + 1] * 256, data[doff + 2] + data[doff + 3] * 256, data[doff + 4] + data[doff + 5] * 256, data[doff + 6]); + + // Initialize memory if not done before + if (activities == NULL) + { + activities = (struct _activity *)calloc(256 * 256, sizeof(struct _activity)); + if (activities == NULL) + { + ERROR_OUT("Cannot calloc enough memory"); + exit(-1); + } + memset(activities, 0x00, sizeof(struct _activity) * 256 * 256); + } // if (activities == NULL) + + activity_id = data[doff] + data[doff + 1] * 256; + activities[activity_id].first_lap = data[doff + 2] + data[doff + 3] * 256; + activities[activity_id].last_lap = data[doff + 4] + data[doff + 5] * 256; + activities[activity_id].sport = data[doff + 6]; + activities[activity_id].valid = 1; + + // leftover (Most of!) + if (dbg >= 2) + dump_data(stderr, data+doff, 7, pktlen-7); + break; + case 99: + DEBUG_OUT(1, "Packet %d: Activity index: %u", pkttype, data[doff] + data[doff + 1] * 256); + + // close previous file + if (tcxfile) + { + // add xml footer and close file, the next file will be open further down + DEBUG_OUT(1, "Closing file"); + print_tcx_footer(tcxfile); + xml_position = XML_OUTSIDE; + rc = xmlTextWriterEndDocument(tcxfile); XML_ERROR_CHECK; + xmlFreeTextWriter(tcxfile); + tcxfile = NULL; + } + + activity_id = data[doff] + data[doff + 1] * 256; + + if (laps == NULL || activities[activity_id].valid == 0 || laps[activities[activity_id].first_lap].valid == 0) + { + ERROR_OUT("Something is wrong in transfer"); + exit(-1); + } + + lap_id = activities[activity_id].first_lap; + + // use first lap starttime as filename + tv_lap = laps[lap_id].timestamp; + strftime(tbuf, sizeof(tbuf), "%Y-%m-%d-%H%M%S.tcx", localtime(&tv_lap)); + DEBUG_OUT(1, "Open file %s", tbuf); + // open file and start with header of xml file + tcxfile = xmlNewTextWriterFilename(tbuf, 0); + if (tcxfile == NULL) + { + ERROR_OUT("Error in XML creation"); + exit(-1); + } + print_tcx_header(tcxfile); + xml_position = XML_IN_Activities; + + rc = xmlTextWriterStartElement(tcxfile, BAD_CAST "Activity"); XML_ERROR_CHECK; + xml_position++; + switch (activities[activity_id].sport) + { + case 0: + rc = xmlTextWriterWriteAttribute(tcxfile, BAD_CAST "Sport", BAD_CAST "Running"); + break; + case 1: + rc = xmlTextWriterWriteAttribute(tcxfile, BAD_CAST "Sport", BAD_CAST "Biking"); + break; + case 2: + rc = xmlTextWriterWriteAttribute(tcxfile, BAD_CAST "Sport", BAD_CAST "Other"); + break; + default: + rc = xmlTextWriterWriteElement(tcxfile, BAD_CAST "Notes", BAD_CAST "Unknown sport type"); XML_ERROR_CHECK; + rc = xmlTextWriterWriteAttribute(tcxfile, BAD_CAST "Sport", BAD_CAST "Other"); + } + XML_ERROR_CHECK; + + strftime(tbuf, sizeof tbuf, "%Y-%m-%dT%H:%M:%SZ", gmtime(&tv_lap)); + rc = xmlTextWriterWriteFormatElement(tcxfile, BAD_CAST "Id", "%s", tbuf); XML_ERROR_CHECK; + + // leftover (5) + if (dbg >= 2) + dump_data(stderr, data+doff, 2, pktlen-2); + break; + case 1510: + DEBUG_OUT(1, "%d Track package with %u waypoints", pkttype, data[doff] + data[doff + 1] * 256); + + // if trackpoints are split into more than one message 1510, do not add xml head again + + // Do some sanity checks + if (activities[activity_id].valid == 0 || laps[lap_id].valid == 0 || lap_id < activities[activity_id].first_lap || lap_id > activities[activity_id].last_lap) + { + ERROR_OUT("Sanity check failed"); + ERROR_OUT("Activity: %u, lap: %u", activity_id, lap_id); + exit(-1); + } // if (activities[activity_id].va... + + 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 + garmin_epoch; + + if (lap_id < activities[activity_id].last_lap && laps[lap_id + 1].valid && tv >= laps[lap_id + 1].timestamp) + { + lap_id++; + // Close track and lap if still open (Should ever be) + if (xml_position == XML_IN_Track) + { + rc = xmlTextWriterEndElement(tcxfile); XML_ERROR_CHECK; /* Track */ + xml_position--; + rc = xmlTextWriterEndElement(tcxfile); XML_ERROR_CHECK; /* Lap */ + xml_position--; + } // if (xml_position == XML_IN_Tra... + } // if (lap_id < activities[activi... + + // New lap, handle here to only write the code once + if (xml_position == XML_IN_Activity) + { + strftime(tbuf, sizeof tbuf, "%Y-%m-%dT%H:%M:%SZ", gmtime(&laps[lap_id].timestamp)); + rc = xmlTextWriterStartElement(tcxfile, BAD_CAST "Lap"); XML_ERROR_CHECK; + xml_position++; + rc = xmlTextWriterWriteFormatAttribute(tcxfile, BAD_CAST "StartTime", "%s", tbuf); XML_ERROR_CHECK; + rc = xmlTextWriterWriteFormatElement(tcxfile, BAD_CAST "TotalTimeSeconds", "%ld.%02ld00000", laps[lap_id].duration / 100, laps[lap_id].duration % 100); XML_ERROR_CHECK; + rc = xmlTextWriterWriteFormatElement(tcxfile, BAD_CAST "DistanceMeters", "%s", ground(laps[lap_id].distance)); XML_ERROR_CHECK; + rc = xmlTextWriterWriteFormatElement(tcxfile, BAD_CAST "MaximumSpeed", "%s", ground(laps[lap_id].max_speed)); XML_ERROR_CHECK; + rc = xmlTextWriterWriteFormatElement(tcxfile, BAD_CAST "Calories", "%d", laps[lap_id].calories); XML_ERROR_CHECK; + + if (laps[lap_id].hr_avg > 0) + { + rc = xmlTextWriterStartElement(tcxfile, BAD_CAST "AverageHeartRateBpm"); XML_ERROR_CHECK; + rc = xmlTextWriterWriteAttribute(tcxfile, BAD_CAST "xsi:type", BAD_CAST "HeartRateInBeatsPerMinute_t"); XML_ERROR_CHECK; + rc = xmlTextWriterWriteFormatElement(tcxfile, BAD_CAST "Value", "%d", laps[lap_id].hr_avg); XML_ERROR_CHECK; + rc = xmlTextWriterEndElement(tcxfile); XML_ERROR_CHECK; + } + if (laps[lap_id].hr_max > 0) + { + rc = xmlTextWriterStartElement(tcxfile, BAD_CAST "MaximumHeartRateBpm"); XML_ERROR_CHECK; + rc = xmlTextWriterWriteAttribute(tcxfile, BAD_CAST "xsi:type", BAD_CAST "HeartRateInBeatsPerMinute_t"); XML_ERROR_CHECK; + rc = xmlTextWriterWriteFormatElement(tcxfile, BAD_CAST "Value", "%d", laps[lap_id].hr_max); XML_ERROR_CHECK; + rc = xmlTextWriterEndElement(tcxfile); XML_ERROR_CHECK; + } + + switch (laps[lap_id].intensity) + { + case 0: + rc = xmlTextWriterWriteElement(tcxfile, BAD_CAST "Intensity", BAD_CAST "Active"); + break; + case 1: + rc = xmlTextWriterWriteElement(tcxfile, BAD_CAST "Intensity", BAD_CAST "Resting"); + break; + default: + rc = xmlTextWriterWriteElement(tcxfile, BAD_CAST "Intensity", BAD_CAST "Active"); + } + XML_ERROR_CHECK; + + // for bike the average cadence of this lap is here + if (activities[activity_id].sport == 1) + { + if (laps[lap_id].cadence != 255) + { + rc = xmlTextWriterWriteFormatElement(tcxfile, BAD_CAST "Cadence", "%d", laps[lap_id].cadence); XML_ERROR_CHECK; + } + } + + switch (laps[lap_id].trigger) + { + case 4: + rc = xmlTextWriterWriteElement(tcxfile, BAD_CAST "TriggerMethod", BAD_CAST "HeartRate"); + break; + case 3: + rc = xmlTextWriterWriteElement(tcxfile, BAD_CAST "TriggerMethod", BAD_CAST "Time"); + break; + case 2: + rc = xmlTextWriterWriteElement(tcxfile, BAD_CAST "TriggerMethod", BAD_CAST "Location"); + break; + case 1: + rc = xmlTextWriterWriteElement(tcxfile, BAD_CAST "TriggerMethod", BAD_CAST "Distance"); + break; + case 0: + rc = xmlTextWriterWriteElement(tcxfile, BAD_CAST "TriggerMethod", BAD_CAST "Manual"); + break; + default: + rc = xmlTextWriterWriteElement(tcxfile, BAD_CAST "TriggerMethod", BAD_CAST "Manual"); + } + XML_ERROR_CHECK; + + // I prefere the average run cadence here than at the end of this lap according windows ANTagent + if (activities[activity_id].sport == 0) + { + if (laps[lap_id].cadence != 255) + { + rc = xmlTextWriterStartElement(tcxfile, BAD_CAST "Extensions"); XML_ERROR_CHECK; + rc = xmlTextWriterStartElementNS(tcxfile, NULL, BAD_CAST "LX", BAD_CAST "http://www.garmin.com/xmlschemas/ActivityExtension/v2"); XML_ERROR_CHECK; + rc = xmlTextWriterWriteFormatElement(tcxfile, BAD_CAST "AvgRunCadence", "%d", laps[lap_id].cadence); XML_ERROR_CHECK; + rc = xmlTextWriterEndElement(tcxfile); XML_ERROR_CHECK; /* LX */ + rc = xmlTextWriterEndElement(tcxfile); XML_ERROR_CHECK; /* Extensions */ + } + } + rc = xmlTextWriterStartElement(tcxfile, BAD_CAST "Track"); XML_ERROR_CHECK; + xml_position++; + + track_pause = 0; + } // if (xml_position == XML_IN_Act... + + if (xml_position != XML_IN_Track) + { + ERROR_OUT("Need to write trackpoint but I am not in active track (Current: %d)", xml_position); + continue; + } + + strftime(tbuf, sizeof tbuf, "%Y-%m-%dT%H:%M:%SZ", gmtime(&tv)); + 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]; + DEBUG_OUT(2, "lat %.10g lon %.10g hr %d cad %d u1 %d u2 %d tv %d %s alt %f dist %f %02x %02x%02x%02x%02x", lat, lon, hr, cad, u1, u2, (int)tv, tbuf, alt, dist, data[doff + i + 3], data[doff + i + 16], data[doff + i + 17], data[doff + i + 18], data[doff + i + 19]); + // track pause only if following trackpoint is aswell 'timemarker' with utopic distance + if (track_pause && dist > (float)40000000) + { + rc = xmlTextWriterEndElement(tcxfile); XML_ERROR_CHECK; /* Track */ + rc = xmlTextWriterStartElement(tcxfile, BAD_CAST "Track"); XML_ERROR_CHECK; + } + + rc = xmlTextWriterStartElement(tcxfile, BAD_CAST "Trackpoint"); XML_ERROR_CHECK; + xml_position++; + rc = xmlTextWriterWriteFormatElement(tcxfile, BAD_CAST "Time", "%s", tbuf); XML_ERROR_CHECK; + + if (lat < 90) + { + rc = xmlTextWriterStartElement(tcxfile, BAD_CAST "Position"); XML_ERROR_CHECK; + rc = xmlTextWriterWriteFormatElement(tcxfile, BAD_CAST "LatitudeDegrees", "%s", ground(lat)); XML_ERROR_CHECK; + rc = xmlTextWriterWriteFormatElement(tcxfile, BAD_CAST "LongitudeDegrees", "%s", ground(lon)); XML_ERROR_CHECK; + rc = xmlTextWriterEndElement(tcxfile); XML_ERROR_CHECK; /* Position */ + rc = xmlTextWriterWriteFormatElement(tcxfile, BAD_CAST "AltitudeMeters", "%s", ground(alt)); XML_ERROR_CHECK; + } + + // last trackpoint has utopic distance, 40000km should be enough, hack? + if (dist < (float)40000000) + { + rc = xmlTextWriterWriteFormatElement(tcxfile, BAD_CAST "DistanceMeters", "%s", ground(dist)); XML_ERROR_CHECK; + } + + if (hr > 0) + { + rc = xmlTextWriterStartElement(tcxfile, BAD_CAST "HeartRateBpm"); XML_ERROR_CHECK; + rc = xmlTextWriterWriteAttribute(tcxfile, BAD_CAST "xsi:type", BAD_CAST "HeartRateInBeatsPerMinute_t"); XML_ERROR_CHECK; + rc = xmlTextWriterWriteFormatElement(tcxfile, BAD_CAST "Value", "%d", hr); XML_ERROR_CHECK; + rc = xmlTextWriterEndElement(tcxfile); XML_ERROR_CHECK; /* HeartRateBpm */ + } + + // for bikes the cadence is written here and for the footpod in <Extensions>, why garmin? + if (activities[activity_id].sport == 1) + { + if (cad != 255) + { + rc = xmlTextWriterWriteFormatElement(tcxfile, BAD_CAST "Cadence", "%d", cad); XML_ERROR_CHECK; + } + } + + if (dist < (float)40000000) + { + rc = xmlTextWriterWriteElement(tcxfile, BAD_CAST "SensorState", u1 ? BAD_CAST "Present" : BAD_CAST "Absent"); XML_ERROR_CHECK; + if (u1 == 1 || cad != 255) + { + rc = xmlTextWriterStartElement(tcxfile, BAD_CAST "Extensions"); XML_ERROR_CHECK; + rc = xmlTextWriterStartElementNS(tcxfile, NULL, BAD_CAST "TPX", BAD_CAST "http://www.garmin.com/xmlschemas/ActivityExtension/v2"); XML_ERROR_CHECK; + rc = xmlTextWriterWriteFormatElement(tcxfile, BAD_CAST "AvgRunCadence", "%d", cad); XML_ERROR_CHECK; + // get type of pod from data, could not figure it out, so using sporttyp of first track + if (activities[activity_id].sport == 1) + { + rc = xmlTextWriterWriteAttribute(tcxfile, BAD_CAST "CadenceSensor", BAD_CAST "Bike"); XML_ERROR_CHECK; + } + else + { + rc = xmlTextWriterWriteAttribute(tcxfile, BAD_CAST "CadenceSensor", BAD_CAST "Footpod"); XML_ERROR_CHECK; + if (cad != 255) + { + rc = xmlTextWriterWriteFormatElement(tcxfile, BAD_CAST "RunCadence", "%d", cad); XML_ERROR_CHECK; + } + } + rc = xmlTextWriterEndElement(tcxfile); XML_ERROR_CHECK; /* TPX */ + rc = xmlTextWriterEndElement(tcxfile); XML_ERROR_CHECK; /* Extensions */ + } + track_pause = 0; + } + + rc = xmlTextWriterEndElement(tcxfile); XML_ERROR_CHECK; /* Trackpoint */ + xml_position--; + + // 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; + DEBUG_OUT(2, "Track pause (stop and go)"); + } + else + { + track_pause = 0; + } + } // for (i = 4; i < pktlen; i += 2... + break; + case 149: // Lap specification + DEBUG_OUT(1, "%d Lap data id: %u %u", pkttype, data[doff] + data[doff + 1] * 256, data[doff + 2] + data[doff + 3] * 256); + + // Initialize memory if not done before + if (laps == NULL) + { + laps = (struct _lap *)calloc(256 * 256, sizeof(struct _lap)); + if (laps == NULL) + { + ERROR_OUT("Cannot calloc enough memory"); + exit(-1); + } + memset(laps, 0x00, sizeof(struct _lap) * 256 * 256); + } // if (laps == NULL) + + lap_id = data[doff] + data[doff + 1] * 256; + laps[lap_id].timestamp = data[doff + 4] + data[doff + 5] * 256 + data[doff + 6] * 256 * 256 + data[doff + 7] * 256 * 256 * 256 + garmin_epoch; + laps[lap_id].duration = data[doff + 8] + data[doff + 9] * 256 + data[doff + 10] * 256 * 256 + data[doff + 11] * 256 * 256 * 256; + memcpy(&laps[lap_id].distance, &data[doff + 12], 4); // Dirty, but seems to work + memcpy(&laps[lap_id].max_speed, &data[doff + 16], 4); // Dirty, but seems to work + laps[lap_id].calories = data[doff + 36] + data[doff + 37] * 256; + laps[lap_id].hr_avg = data[doff + 38]; + laps[lap_id].hr_max = data[doff + 39]; + laps[lap_id].intensity = data[doff + 40]; + laps[lap_id].cadence = data[doff + 41]; + laps[lap_id].trigger = data[doff + 42]; + + laps[lap_id].valid = 1; + + if (dbg >= 2) + { + dump_data(stderr, data+doff, 20, 16); + dump_data(stderr, data+doff, 43, pktlen-43); + } + + break; + case 247: // Software versions + memset(modelname, 0, sizeof modelname); + memcpy(modelname, data + doff + 88, dsize - 88); + DEBUG_OUT(1, "%d Device name \"%s\"\n", pkttype, modelname); + break; + default: + DEBUG_OUT(1, "Don't know how to decode packet type %d", pkttype); + if (dbg >= 2) + dump_data(stderr, data+doff, 0, pktlen); + } +} + +void usage(void) +{ + /* *INDENT-OFF* */ + fprintf(stderr, "Usage: %s -a authfile\n" + " [ -a authfile ] Authfile (default ~/.gant)\n" + " [ -f name ] (default garmin)\n" + " [ -d devno ] Device no. (default 0)\n" + " [ -i id ] ID for pairing (default random)\n" + " [ -m mydev ] (default 0)\n" + " [ -p ] Passive\n" + " [ -v ] Verbose\n" + " [ -D level ] Debug\n" + " [ -r ] Reset the device\n" + " [ -n ] Do not write auth file\n" + " [ -z ] Do not pair\n" + " [ -h ] This help\n", + progname); + /* *INDENT-ON* */ + exit(1); +} + +uchar chevent(uchar chan, uchar event) +{ + uchar status; + uchar phase; + uint newdata; + struct ack_msg ack; + struct auth_msg auth; + struct pair_msg pair; + uint id; + int i; + uint cid; + + DEBUG_OUT(5, "chevent %02x %02x", 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); + + DEBUG_OUT(6, "cid %08x myid %08x", cid, myid); + if (dbg && event != EVENT_RX_BURST_PACKET) + { + DEBUG_OUT(5, "chan %d event %02x channel open: ", chan, event); + for (i = 0; i < 8; i++) + DEBUG_OUT(6, " -: %02x", cbuf[i]); + } + + switch (event) + { + case EVENT_RX_BROADCAST: + lastphase = phase; // store the last phase we see the watch broadcast + DEBUG_OUT(3, "Lastphase %d", 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)) + { + ERROR_OUT("50 %d and 405 %d", isa50, isa405); + exit(1); + } + } + if (verbose) + { + switch (phase) + { + case 0: + DEBUG_OUT(4, "%s BC0 %02x %d %d %d PID %d %d %d %c%c", 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: + DEBUG_OUT(4, "%s BC1 %02x %d %d %d CID %08x %c%c", timestamp(), cbuf[0], cbuf[1] & 0xd7, cbuf[2], cbuf[3], cid, (cbuf[1] & 0x20) ? 'N' : ' ', (cbuf[1] & 0x08) ? 'P' : ' '); + break; + DEBUG_OUT(4, "%s BCX %02x %d %d %d PID %d %d %d %c%c", 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; + } + } + + DEBUG_OUT(6, "Watch status %02x stage %d id %08x", 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)) + { + DEBUG_OUT(1, "Resetting"); + 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) + { + DEBUG_OUT(1, "Download finished"); + exit(0); + } + // generate a random id if pairing and user didn't specify one + if (pairing && !myid) + { + myid = randno(); + DEBUG_OUT(1, "Pairing, using id %08x", 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; + + DEBUG_OUT(1, "Reading auth data from %s", authfile); + authfd = open(authfile, O_RDONLY); + if (authfd < 0) + { + perror(authfile); + ERROR_OUT("No auth data. Need to pair first"); + exit(1); + } + nr = read(authfd, authdata, 32); + close(authfd); + if (nr != 32 && nr != 24) + { + ERROR_OUT("Bad auth file len %d != 32 or 24", 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); + DEBUG_OUT(4, "dev %08x auth %08x %08x id %08x", 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 + { + DEBUG_OUT(6, "Donebind %d devid %x", donebind, devid); + } + break; + case 1: + DEBUG_OUT(3, "Case 1 %x", peerdev); + if (peerdev) + { + DEBUG_OUT(4, " -: peerdev"); + // if watch has sent id + if (mydev != 0 && peerdev != mydev) + { + DEBUG_OUT(1, "Don't know this device %08x != %08x", peerdev, mydev); + } + else if (!sentauth && !waitauth) + { + DEBUG_OUT(4, " -: diffdev"); + 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 + } + } + DEBUG_OUT(4, " -: cid %x myid %x", cid, myid); + if (!sentack2 && cid == myid && !waitauth) + { + sentack2 = 1; + DEBUG_OUT(4, " -: sending ack2"); + // 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; + DEBUG_OUT(1, "Starting download"); + 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) + { + DEBUG_OUT(1, "Finished download"); + 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 + { + DEBUG_OUT(1, "Not sure why in phase 3"); + if (!sentgetv) + { + sentgetv = 1; + //ANT_SendBurstTransferA(chan, getversion, strlen(getversion)/16); + } + } + break; + default: + DEBUG_OUT(1, "Unknown phase %d", phase); + break; + } + break; + case EVENT_RX_BURST_PACKET: + // now handled in coalesced burst below + DEBUG_OUT(5, "Burst"); + break; + case EVENT_RX_FAKE_BURST: + DEBUG_OUT(5, "rxfake burst pairing %d blast %ld waitauth %d", pairing, (long)blast, waitauth); + blsize = *(int *)(cbuf + 4); + memcpy(&blast, cbuf + 8, 4); + if (dbg) + { + DEBUG_OUT(6, "Fake burst %d %lx", blsize, (long)blast); + for (i = 0; i < blsize && i < 64; i++) + DEBUG_OUT(4, " -: %02x", blast[i]); + for (i = 0; i < blsize; i++) + if (isprint(blast[i])) + { + DEBUG_OUT(6, " --: %c", blast[i]); + } + else + DEBUG_OUT(6, " --: ."); + } + if (sentauth) + { + static int nacksent = 0; + char *ackdata; + static char 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) + { + DEBUG_OUT(2, "bloblen %d, get next data", bloblen); + // request next set of data + ackdata = acks[nacksent++]; + if (!strcmp(ackdata, "")) + { // finished + DEBUG_OUT(2, "ACKs finished, resetting"); + 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); + } + DEBUG_OUT(2, "Got type 0, sending ACK %s", ackdata); + snprintf(ackpkt, 100, "440dffff00000000%s", ackdata); + } + else if (bloblen == 65535) + { + // repeat last ack + DEBUG_OUT(2, "Repeating ACK %s", ackpkt); + ANT_SendBurstTransferA(chan, (uchar *) ackpkt, strlen(ackpkt) / 16); + } + else + { + DEBUG_OUT(2, "Non-0 bloblen %d", bloblen); + decode(bloblen, pkttype, pktlen, blsize, blast); + snprintf(ackpkt, 100, "440dffff0000000006000200%02x%02x0000", pkttype % 256, pkttype / 256); + } + DEBUG_OUT(1, "Received pkttype %d len %d", pkttype, pktlen); + DEBUG_OUT(2, "Acking %s", ackpkt); + ANT_SendBurstTransferA(chan, (uchar *) ackpkt, strlen(ackpkt) / 16); + } + else if (!nopairing && pairing && blast) + { + memcpy(&peerdev, blast + 12, 4); + DEBUG_OUT(1, "Watch id %08x waitauth %d", peerdev, waitauth); + if (mydev != 0 && peerdev != mydev) + { + ERROR_OUT("Don't know this device %08x != %08x", peerdev, mydev); + exit(1); + } + if (waitauth == 2) + { + int nw; + + // should be receiving auth data + if (nowriteauth) + { + ERROR_OUT("Not overwriting auth data"); + exit(1); + } + DEBUG_OUT(1, "Storing auth data in %s", authfile); + authfd = open(authfile, O_WRONLY | O_CREAT, 0644); + if (authfd < 0) + { + perror(authfile); + exit(1); + } + nw = write(authfd, blast, blsize); + if (nw != blsize) + { + ERROR_OUT("Auth write failed fd %d %d", 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 + // DEBUG_OUT(1, "Pair dev name too large %08x \"%d\"\n", peerdev, peerdev) + pair.u1 = strlen(pair.devname); + DEBUG_OUT(1, "Sending pair data for dev %s", 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 + { + DEBUG_OUT(1, "Not pairing"); + } + } + else if (!gotwatchid && (lastphase == 1)) + { + static int once = 0; + + gotwatchid = 1; + // garmin sending authentication/identification data + if (!once) + { + once = 1; + DEBUG_OUT(3, "ID data:"); + } + if (dbg) + for (i = 0; i < blsize; i++) + DEBUG_OUT(4, " -: %02x", blast[i]); + memcpy(&peerdev, blast + 12, 4); + DEBUG_OUT(1, "watch id %08x", peerdev); + if (mydev != 0 && peerdev != mydev) + { + ERROR_OUT("Don't know this device %08x != %08x", peerdev, mydev); + exit(1); + } + } + else if (lastphase == 2) + { + static int once = 0; + + DEBUG_OUT(2, "Once %d", once) + // garmin uploading in response to sendack3 + // in this state we're receiving the workout data + if (!once) + { + DEBUG_OUT(2, "Receiving"); + once = 1; + } + } + DEBUG_OUT(1, "Continuing after burst"); + break; + } + return 1; +} + +uchar revent(uchar chan, uchar event) +{ + int i; + + DEBUG_OUT(5, "Revent: %02x %02x", chan, event); + switch (event) + { + case EVENT_TRANSFER_TX_COMPLETED: + DEBUG_OUT(4, "Transfer complete: %02x", ebuf[1]); + break; + case INVALID_MESSAGE: + DEBUG_OUT(4, "Invalid message: %02x", 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: + DEBUG_OUT(1, "Channel open, waiting for broadcast"); + break; + default: + DEBUG_OUT(4, "Message: %02x NO_ERROR", ebuf[1]); + break; + } + break; + case MESG_CHANNEL_ID_ID: + devid = ebuf[1] + ebuf[2] * 256; + if (mydev == 0 || devid == mydev % 65536) + { + DEBUG_OUT(4, "DevID %08x myid %08x", devid, myid); + } + else + { + DEBUG_OUT(3, "Ignoring unknown device %08x, mydev %08x", devid, mydev); + devid = sentid = 0; // reset + } + break; + case MESG_NETWORK_KEY_ID: + case MESG_SEARCH_WAVEFORM_ID: + case MESG_OPEN_CHANNEL_ID: + DEBUG_OUT(4, "Response event %02x code %02x", event, ebuf[2]); + for (i = 0; i < 8; i++) + DEBUG_OUT(4, " -: %02x", ebuf[i]); + break; + case MESG_CAPABILITIES_ID: + DEBUG_OUT(4, "Capabilities chans %d nets %d opt %02x adv %02x", ebuf[0], ebuf[1], ebuf[2], ebuf[3]); + break; + case MESG_CHANNEL_STATUS_ID: + DEBUG_OUT(4, "Channel status %d", ebuf[1]); + break; + case EVENT_RX_FAIL: + case EVENT_TRANSFER_TX_FAILED: + case EVENT_TRANSFER_RX_FAILED: + // ignore this + break; + case EVENT_RX_SEARCH_TIMEOUT: + printf("Timeout, please make sure the device is not in standby.\n"); + break; + default: + DEBUG_OUT(3, "Unhandled response event %02x", event); + break; + } + return 1; +} + +int 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:f:d:i:m:pvD:rnzh")) != -1) + { + switch (c) + { + case 'a': + authfile = optarg; + break; + case 'f': + fname = optarg; + break; + case 'd': + devnum = atoi(optarg); + break; + case 'i': + myid = atoi(optarg); + break; + case 'm': + mydev = atoi(optarg); + break; + case 'p': + passive = 1; + break; + case 'v': + verbose = 1; + break; + case 'D': + dbg = atoi(optarg); + break; + case 'r': + reset = 1; + break; + case 'n': + nowriteauth = 1; + break; + case 'z': + nopairing = 1; + break; + case 'h': + usage(); + default: + ERROR_OUT("unknown option %s", 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 + { + ERROR_OUT("Open dev %d failed", 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); +} + +/* vim: set shiftwidth=3 softtabstop=3: */ diff --git a/src/gant/resources/gant.png b/src/gant/resources/gant.png Binary files differnew file mode 100644 index 0000000..ae014c1 --- /dev/null +++ b/src/gant/resources/gant.png |