summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorRALOVICH, Kristof <tade60@freemail.hu>2013-10-11 20:14:50 +0200
committerRALOVICH, Kristof <tade60@freemail.hu>2013-10-11 20:14:50 +0200
commitd0868152f37abbf3d700dafe762e98acd2a04735 (patch)
tree8987c801c48e00993ed6507eb8051a2cc1097d94 /src
parentc47e9d4d2a7bda87b3db0b3728f424ec739613ec (diff)
parentce9c817d058083a6290cf7d319b68b9513349eb3 (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.
Diffstat (limited to 'src')
-rw-r--r--src/gant/GantMonitor.glade244
-rwxr-xr-xsrc/gant/GantMonitor.py167
-rw-r--r--src/gant/Makefile13
-rw-r--r--src/gant/README32
-rw-r--r--src/gant/antdefs.h51
-rw-r--r--src/gant/antlib.c629
-rw-r--r--src/gant/antlib.h30
-rw-r--r--src/gant/gant.c1407
-rw-r--r--src/gant/resources/gant.pngbin0 -> 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
new file mode 100644
index 0000000..ae014c1
--- /dev/null
+++ b/src/gant/resources/gant.png
Binary files differ