Garmin Forerunner 410 on linux

Sports watches are poorly supported by their manufacturers regarding linux. After I started running I decided on a Garmin FR 410, since it was quite cheap and has all the functionality I wanted. I neglected to look up how it works under linux and for the first couple of months after I got it I had to boot up my XP in VM to transfer data to and from the watch.

Then I came across a nice python tool that made it possible to get data off the watch and upload it to Connect Garmin. The tool is called python_ant_downloader and you can find supported watch models on their github page. It is simple to install through pip (use sudo if needed):

user@host $ sudo pip install python_ant_downloader

Edit 3rd July 2014: If you use Anaconda for virtual python environments as I do, then you might be having issues with python module dbm not being installed when running the ant-downloader:

user@host $ ant-downloader
Traceback (most recent call last):
...
import dbm
ImportError: No module named dbm

If so, you can install it straight from github:

user@host $ sudo pip install git+git://github.com/braiden/python-ant-downloader.git

This gives you a terminal program called ant-downloader that can retrieve data from a connected garmin watch and upload it to garmin. First, I had a problem where my ant+ stick was not getting permissions to mount. In Ubuntu (and derived systems, and probably other distros as well) non-standard usb sticks are not allowed to mount automatically. Those devices follow permission rules that are present in /etc/udev/rules.d/. To generate a rule for our USB Ant+ stick we need to figure out the sticks ID:

user@host $ lsusb
Bus 002 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub
Bus 003 Device 002: ID 0fcf:1008 Dynastream Innovations, Inc.
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

In my case I identified my USB stick by running the command with it unplugged and then again plugged in. Here the Dynastream Innovation, Inc. is the stick and note the ID : 0fcf:1008. Now we generate a rule for that device:

user@host $ sudo echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="0fcf", ATTR{idProduct}=="1008", MODE="666"' > /etc/udev/rules.d/99-garmin.rules

user@host $ sudo bash -c "echo 'SUBSYSTEM==\"usb\", ATTR{idVendor}==\"0fcf\", ATTR{idProduct}==\"1008\", MODE=\"666\"' > /etc/udev/rules.d/99-garmin.rules"

Edit 3rd July 2014: On Arch based system (I’m using Manjaro at the moment) the command is a bit different:

user@host $ sudo bash -c "echo 'SUBSYSTEMS==\"usb\", ATTRS{idVendor}==\"0fcf\", ATTRS{idProduct}==\"1008\", MODE=\"666\"' > /etc/udev/rules.d/99-garmin.rules"

Note that the vendor and product attributes use their respective parts of the ID above. Saving this and plugin the USB again should allow you to run

user@host $ ant-downloader

The first time it runs, it generates configs and tries to pair with your watch. After that, you can edit the file ~/.antd/antd.cfg and add your connect.garmin details under the config [antd.connect] (and set enabled = True) for automatic upload to their server. Your workouts are also stored as .tcx files that you can upload to other servers than garmin, you find them in the folder ~/.antd/0xDEVICENR/tcx/ .

Hope this will be of use to someone 🙂 Please let me know of any issues you find with this method.

Binni out.

19 thoughts on “Garmin Forerunner 410 on linux

    • Nice to know, I also see that some use quite different names, e.g SYS instead of ATTR(S) and some other changes.

  1. Hello,
    I have tried your procedure but unfortunately It does work for me (ubuntu 12.04.3, Forerunner 410):
    sudo ant-downloader gives:
    [MainThread] 2014-01-02 14:40:36,109 WARNING Caught exception trying to cleanup resources.
    Traceback (most recent call last):
    File “/usr/local/lib/python2.7/dist-packages/antd/ant.py”, line 557, in __init__
    try: self.close()
    File “/usr/local/lib/python2.7/dist-packages/antd/ant.py”, line 577, in close
    self.reset_system()
    File “/usr/local/lib/python2.7/dist-packages/antd/ant.py”, line 592, in reset_system
    cap = self.get_capabilities()
    File “/usr/local/lib/python2.7/dist-packages/antd/ant.py”, line 607, in get_capabilities
    return self._send(RequestMessage(0, Capabilities.ID))
    File “/usr/local/lib/python2.7/dist-packages/antd/ant.py”, line 680, in _send
    raise cmd.error
    AntTimeoutError: No reply to command. REQUEST_MESSAGE(channel_number=0, msg_id=84)
    Traceback (most recent call last):
    File “/usr/local/bin/ant-downloader”, line 9, in
    load_entry_point(‘python-ant-downloader==13.02.24’, ‘console_scripts’, ‘ant-downloader’)()
    File “/usr/local/lib/python2.7/dist-packages/antd/main.py”, line 75, in downloader
    host = antd.cfg.create_antfs_host()
    File “/usr/local/lib/python2.7/dist-packages/antd/cfg.py”, line 118, in create_antfs_host
    host = antfs.Host(create_ant_session(), keys)
    File “/usr/local/lib/python2.7/dist-packages/antd/cfg.py”, line 105, in create_ant_session
    session = ant.Session(create_ant_core())
    File “/usr/local/lib/python2.7/dist-packages/antd/ant.py”, line 559, in __init__
    finally: raise e
    antd.ant.AntTimeoutError: No reply to command. REQUEST_MESSAGE(channel_number=0, msg_id=84)

    Thanks a lot for you help!

      • Hello binnisb,
        First of al : thank you for your help!

        lsusb gives: Bus 005 Device 002: ID 0fcf:1008 Dynastream Innovations, Inc.
        And today when I want to generate a rule, It return “permission not allowed”:
        sudo echo ‘SUBSYSTEM==”usb”, ATTR{idVendor}==”0fcf”, ATTR{idProduct}==”1008″, MODE=”666″‘ > /etc/udev/rules.d/99-garmin.rules
        bash: /etc/udev/rules.d/99-garmin.rules: Permission non accordée

        Unfortunately, I can’t remember if I had this message last week…

        ls /etc/udev/rules.d returns:
        70-persistent-cd.rules 70-persistent-net.rules ant-usbstick2.rules README

    • The command I give in the blogpost is not quite correct. Try running this command to create the garmin rule:

      sudo bash -c "echo 'SUBSYSTEM==\"usb\", ATTR{idVendor}==\"0fcf\", ATTR{idProduct}==\"1008\", MODE=\"666\"' > /etc/udev/rules.d/99-garmin.rules"

      • Thanks a lot!!!!! It works !!! I bought my forerunner 410 1 year ago, and for the first time I have automatically uploaded my tracks on garmin connect without using W….! Note that” False” should be replaced by “true” under the config [antd.connect]. Thank you for your help!

      • Great to hear that it works 🙂 I included the enabled parameter that you had to change to True in the tutorial.

  2. Hi, I have been following your instructions but I stumble across this error:
    patrick@patrick-linux:~$ ant-downloader
    Traceback (most recent call last):
    File “/usr/local/lib/python2.7/dist-packages/logging-0.4.9.6-py2.7.egg/logging/__init__.py”, line 723, in emit
    msg = self.format(record)
    File “/usr/local/lib/python2.7/dist-packages/logging-0.4.9.6-py2.7.egg/logging/__init__.py”, line 609, in format
    return fmt.format(record)
    File “/usr/local/lib/python2.7/dist-packages/logging-0.4.9.6-py2.7.egg/logging/__init__.py”, line 402, in format
    s = self._fmt % record.__dict__
    KeyError: ‘threadName’
    Traceback (most recent call last):
    File “/usr/local/lib/python2.7/dist-packages/logging-0.4.9.6-py2.7.egg/logging/__init__.py”, line 723, in emit
    msg = self.format(record)
    File “/usr/local/lib/python2.7/dist-packages/logging-0.4.9.6-py2.7.egg/logging/__init__.py”, line 609, in format
    return fmt.format(record)
    File “/usr/local/lib/python2.7/dist-packages/logging-0.4.9.6-py2.7.egg/logging/__init__.py”, line 402, in format
    s = self._fmt % record.__dict__
    KeyError: ‘threadName’
    Traceback (most recent call last):
    File “/usr/local/lib/python2.7/dist-packages/logging-0.4.9.6-py2.7.egg/logging/__init__.py”, line 723, in emit
    msg = self.format(record)
    File “/usr/local/lib/python2.7/dist-packages/logging-0.4.9.6-py2.7.egg/logging/__init__.py”, line 609, in format
    return fmt.format(record)
    File “/usr/local/lib/python2.7/dist-packages/logging-0.4.9.6-py2.7.egg/logging/__init__.py”, line 402, in format
    s = self._fmt % record.__dict__
    KeyError: ‘threadName’
    Traceback (most recent call last):
    File “/usr/local/bin/ant-downloader”, line 9, in
    load_entry_point(‘python-ant-downloader==13.02.24’, ‘console_scripts’, ‘ant-downloader’)()
    File “/usr/local/lib/python2.7/dist-packages/antd/main.py”, line 75, in downloader
    host = antd.cfg.create_antfs_host()
    File “/usr/local/lib/python2.7/dist-packages/antd/cfg.py”, line 118, in create_antfs_host
    host = antfs.Host(create_ant_session(), keys)
    File “/usr/local/lib/python2.7/dist-packages/antd/cfg.py”, line 105, in create_ant_session
    session = ant.Session(create_ant_core())
    File “/usr/local/lib/python2.7/dist-packages/antd/cfg.py”, line 101, in create_ant_core
    return ant.Core(create_hardware())
    File “/usr/local/lib/python2.7/dist-packages/antd/cfg.py”, line 97, in create_hardware
    return hw.SerialHardware(tty, 115200)
    File “/usr/local/lib/python2.7/dist-packages/antd/hw.py”, line 81, in __init__
    self.dev = serial.Serial(port=dev, baudrate=baudrate, timeout=1)
    File “/usr/lib/python2.7/dist-packages/serial/serialutil.py”, line 261, in __init__
    self.open()
    File “/usr/lib/python2.7/dist-packages/serial/serialposix.py”, line 278, in open
    raise SerialException(“could not open port %s: %s” % (self._port, msg))
    serial.serialutil.SerialException: could not open port /dev/ttyUSB0: [Errno 13] Permission denied: ‘/dev/ttyUSB0’

    Any idea what’s the problem?
    Thanks in advance

    • Did you generate the rule to allow you access to the usb stick?

      user@host $ sudo bash -c "echo 'SUBSYSTEM==\"usb\", ATTR{idVendor}==\"0fcf\", ATTR{idProduct}==\"1008\", MODE=\"666\"' > /etc/udev/rules.d/99-garmin.rules"

      After running this, do

      user@host $ ls -al /etc/udev/rules.d/

      And make sure it exists

  3. I have this issue when I run the downloader. Thoughts?

    growl@GROWL:~$ ant-downloader
    [MainThread] 2014-11-11 17:30:30,911 INFO Found device with vid(0x0fcf) pid(0x1008), but interface already claimed.
    [MainThread] 2014-11-11 17:30:30,915 WARNING Failed to find Garmin nRF24AP2 (newer) USB Stick.
    Traceback (most recent call last):
    File “/usr/local/lib/python2.7/dist-packages/antd/cfg.py”, line 92, in create_hardware
    return hw.UsbHardware(id_vendor, id_product, bulk_endpoint)
    File “/usr/local/lib/python2.7/dist-packages/antd/hw.py”, line 61, in __init__
    raise NoUsbHardwareFound(errno.ENOENT, “No available device matching vid(0x%04x) pid(0x%04x).” % (id_vendor, id_product))
    NoUsbHardwareFound: [Errno 2] No available device matching vid(0x0fcf) pid(0x1008).
    [MainThread] 2014-11-11 17:30:30,916 WARNING Looking for nRF24AP1 (older) Serial USB Stick.
    Traceback (most recent call last):
    File “/usr/local/bin/ant-downloader”, line 9, in
    load_entry_point(‘python-ant-downloader==13.02.24’, ‘console_scripts’, ‘ant-downloader’)()
    File “/usr/local/lib/python2.7/dist-packages/antd/main.py”, line 75, in downloader
    host = antd.cfg.create_antfs_host()
    File “/usr/local/lib/python2.7/dist-packages/antd/cfg.py”, line 118, in create_antfs_host
    host = antfs.Host(create_ant_session(), keys)
    File “/usr/local/lib/python2.7/dist-packages/antd/cfg.py”, line 105, in create_ant_session
    session = ant.Session(create_ant_core())
    File “/usr/local/lib/python2.7/dist-packages/antd/cfg.py”, line 101, in create_ant_core
    return ant.Core(create_hardware())
    File “/usr/local/lib/python2.7/dist-packages/antd/cfg.py”, line 97, in create_hardware
    return hw.SerialHardware(tty, 115200)
    File “/usr/local/lib/python2.7/dist-packages/antd/hw.py”, line 81, in __init__
    self.dev = serial.Serial(port=dev, baudrate=baudrate, timeout=1)
    File “/usr/lib/python2.7/dist-packages/serial/serialutil.py”, line 261, in __init__
    self.open()
    File “/usr/lib/python2.7/dist-packages/serial/serialposix.py”, line 278, in open
    raise SerialException(“could not open port %s: %s” % (self._port, msg))
    serial.serialutil.SerialException: could not open port /dev/ttyUSB0: [Errno 2] No such file or directory: ‘/dev/ttyUSB0’

Leave a reply to binnisb Cancel reply