yuanying
5/7/2012 - 7:33 AM

Syncronize from Garmin Connect activity to Runkeeper

Syncronize from Garmin Connect activity to Runkeeper

#!/usr/bin/env ruby -wKU
# Syncronize from Garmin Connect to Runkeeper
require 'rubygems'
require 'runkeeper'
require 'rss/1.0'
require 'rss/2.0'
require 'rss/dublincore'
require 'open-uri'
require 'uri'
require 'cgi'
require 'optparse'
require "rexml/document"
require 'time'

runkeeper_client_id       = nil
runkeeper_client_secret   = nil
runkeeper_access_token    = nil
garmin_username = nil
proxy_host      = nil
proxy_port      = nil

opts = OptionParser.new
opts.on('-c', '--runkeeper-client-id CLIENT_ID') { |v| runkeeper_client_id = v }
opts.on('-s', '--runkeeper-client-secret CLIENT_SECRET') { |v| runkeeper_client_secret = v }
opts.on('-a', '--runkeeper-access-token ACCESS_TOKEN') { |v| runkeeper_access_token = v }
opts.on('-g', '--garmin-username GARMIN_USERNAME') { |v| garmin_username = v }
opts.on('--proxy-host PROXY_HOST') { |v| proxy_host = v }
opts.on('--proxy-port PROXY_PORT', Integer) { |v| proxy_port = v }
opts.parse!(ARGV)

tmp = '/tmp/last-garmin-connect-to-runkeeper-url.txt'
url = nil
if File.exist?(tmp)
  open(tmp) do |io|
    url = io.read.chomp
  end
end

rss = nil
rss_url = "http://connect.garmin.com/feed/rss/activities?feedname=Garmin#{Time.now.strftime('%Y%m%d%H')}&explore=true&activityType=running&eventType=all&activitySummarySumDistance-unit=kilometer&activitySummarySumDuration-unit=hour&activitySummaryGainElevation-unit=meter&owner=#{garmin_username}&sortField=beginTimestamp"
open(rss_url) do |io|
  rss = RSS::Parser.parse(io.read)
end

Runkeeper.configure do |config|
  config.client_id = runkeeper_client_id
  config.client_secret = runkeeper_client_secret
end

if proxy_host && proxy_port
  require 'runkeeper/connection'
  class Runkeeper
    class Connection
      http_proxy proxy_host, proxy_port
    end
  end
end

class Runkeeper
  class Activity
    attr_accessor :heart_rate

    alias old_optional_attrs optional_attrs

    def optional_attrs
      h = {}
      [
        :total_distance,
        :average_heart_rate,
        :total_calories,
        :heart_rate,
        :notes,
        :path,
        :post_to_facebook,
        :post_to_twitter
      ].each do |method_name|
        v = send(method_name)
        h[method_name] = v if v
      end
      h
    end
  end
end

class Garmin2Runkeeper
  attr_accessor :runkeeper

  def initialize runkeeper
    @runkeeper = runkeeper
  end

  def post activity_id
    type = 'Running'
    start_time = nil
    total_distance = 0
    duration = nil
    # average_heart_rate = 0
    heart_rate = []
    total_calories = 0
    notes = nil
    path = []

    open("http://connect.garmin.com/proxy/activity-service-1.1/gpx/activity/#{activity_id}?full=true") do |gpx|
      doc = REXML::Document.new gpx
      title =  doc.elements['/gpx/trk/name'] ? doc.elements['/gpx/trk/name'].text : 'Untitle'
      desc  = doc.elements['/gpx/trk/desc'] ? doc.elements['/gpx/trk/desc'].text : ''
      notes = "#{title}, #{desc}"
    end

    open("http://connect.garmin.com/proxy/activity-service-1.1/tcx/activity/#{activity_id}?full=true") do |tcx|
      doc = REXML::Document.new tcx

      doc.elements.each('/TrainingCenterDatabase/Activities/Activity/Lap') do |lap|

        if distance = lap.elements['DistanceMeters']
          distance = distance.text.to_i
          total_distance += distance
        end

        if calories = lap.elements['Calories']
          calories = calories.text.to_i
          total_calories += calories
        end

        trigger_method = trigger_method.text.downcase if trigger_method = lap.elements['TriggerMethod']

        lap.elements.each('Track/Trackpoint') do |tp|
          if time = tp.elements['Time']
            time = Time.parse(time.text)
            start_time = time unless start_time
            duration = time.to_i - start_time.to_i

            latitude = tp.elements['Position/LatitudeDegrees']
            longitude = tp.elements['Position/LongitudeDegrees']
            altitude = tp.elements['AltitudeMeters']
            trigger_method = trigger_method || 'manual'

            if  latitude && longitude && altitude
              wgs84 = {}
              wgs84[:timestamp] = duration
              wgs84[:latitude] = latitude.text
              wgs84[:longitude] = longitude.text
              wgs84[:altitude] = altitude.text
              wgs84[:type] = trigger_method
              path << wgs84
            end

            if hr_value = tp.elements['HeartRateBpm/Value']
              hr_value = hr_value.text.to_i
              hr = {}
              hr[:timestamp] = duration
              hr[:heart_rate] = hr_value
              heart_rate << hr
            end
          end
        end
      end
    end

    activity = runkeeper.new_activity(
      type: type,
      start_time: start_time.localtime,
      duration: duration,
      heart_rate: heart_rate,
      path: path,
      total_distance: total_distance,
      total_calories: total_calories,
      notes: notes,
      post_to_facebook: true,
      post_to_twitter: true
    )
    activity.save
  end
end

user = Runkeeper.new(runkeeper_access_token)
synchronizer = Garmin2Runkeeper.new(user)

urls = []
rss.items.each do |item|
  if item.link == url
    break
  end
  urls << item.link
end

urls.reverse.each do |url|
  activity_id = url.split('/').last
  synchronizer.post(activity_id)
  
  open(tmp, 'w') do |io|
    io.write url
  end
end