fitbit-to-graphite 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile +3 -0
- data/Gemfile.lock +20 -0
- data/LICENSE +19 -0
- data/README.md +45 -0
- data/Rakefile +2 -0
- data/bin/fitbit-to-graphite.rb +174 -0
- data/fitbit-to-graphite.gemspec +16 -0
- metadata +85 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
fitbit-to-graphite (0.1.0)
|
5
|
+
choice
|
6
|
+
fitgem
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
choice (0.1.6)
|
12
|
+
fitgem (0.8.0)
|
13
|
+
oauth
|
14
|
+
oauth (0.4.7)
|
15
|
+
|
16
|
+
PLATFORMS
|
17
|
+
ruby
|
18
|
+
|
19
|
+
DEPENDENCIES
|
20
|
+
fitbit-to-graphite!
|
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (C) 2013 Daniel Schauenberg
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
4
|
+
this software and associated documentation files (the "Software"), to deal in
|
5
|
+
the Software without restriction, including without limitation the rights to
|
6
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
7
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
8
|
+
so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
11
|
+
copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
19
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# fitbit-to-graphite
|
2
|
+
|
3
|
+
## Synopsis
|
4
|
+
Record sleep data from fitbit to graphite. That's it.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
You can get the script from rubygems:
|
8
|
+
|
9
|
+
gem install fitbit-to-graphite
|
10
|
+
|
11
|
+
## Setup
|
12
|
+
fitbit-to-graphite reads its config file at `~/.fitgem.yml` which at least
|
13
|
+
needs the following settings:
|
14
|
+
```
|
15
|
+
---
|
16
|
+
:oauth:
|
17
|
+
:consumer_key: thekey
|
18
|
+
:consumer_secret: thesecret
|
19
|
+
```
|
20
|
+
Fill in the key and secret from the app you created for it and on first run it
|
21
|
+
should register the OAuth data.
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
```
|
25
|
+
% fitbit-to-graphite.rb --help
|
26
|
+
Usage: fitbit-to-graphite.rb [-hpnv]
|
27
|
+
|
28
|
+
Specific options:
|
29
|
+
-h, --host=HOST The hostname or ip of the host graphite is running on
|
30
|
+
-p, --port=PORT The port graphite is listening on
|
31
|
+
-n, --namespace=NAMESPACE The graphite metric path to store data in
|
32
|
+
|
33
|
+
Common options:
|
34
|
+
--help Show this message
|
35
|
+
-v, --version Show version
|
36
|
+
--debug run in debug mode
|
37
|
+
--jawbone send jawbone compatible data
|
38
|
+
```
|
39
|
+
|
40
|
+
## How to contribute
|
41
|
+
1. Fork the repo
|
42
|
+
2. Hack away
|
43
|
+
3. Push the branch up to GitHub
|
44
|
+
4. Send a pull request
|
45
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,174 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
#
|
4
|
+
# simple script to import your sleep data from Fitbit into Graphite
|
5
|
+
#
|
6
|
+
|
7
|
+
require 'date'
|
8
|
+
require 'yaml'
|
9
|
+
|
10
|
+
require 'choice'
|
11
|
+
require 'fitgem'
|
12
|
+
|
13
|
+
PROGRAM_VERSION = "0.1.0"
|
14
|
+
SLEEP_STATE_TYPES = ['deep', 'light', 'awake']
|
15
|
+
# this is just to be compatible with jawbone data
|
16
|
+
JAWBONE_SLEEP_STATES = { 'awake' => 1, 'light' => 2, 'deep' => 3 }
|
17
|
+
|
18
|
+
Choice.options do
|
19
|
+
header ''
|
20
|
+
header 'Specific options:'
|
21
|
+
|
22
|
+
option :host do
|
23
|
+
short '-h'
|
24
|
+
long '--host=HOST'
|
25
|
+
desc 'The hostname or ip of the host graphite is running on'
|
26
|
+
default '127.0.0.1'
|
27
|
+
end
|
28
|
+
|
29
|
+
option :port do
|
30
|
+
short '-p'
|
31
|
+
long '--port=PORT'
|
32
|
+
desc 'The port graphite is listening on'
|
33
|
+
cast Integer
|
34
|
+
default 2003
|
35
|
+
end
|
36
|
+
|
37
|
+
option :namespace do
|
38
|
+
short '-n'
|
39
|
+
long '--namespace=NAMESPACE'
|
40
|
+
desc 'The graphite metric path to store data in'
|
41
|
+
end
|
42
|
+
|
43
|
+
separator ''
|
44
|
+
separator 'Common options: '
|
45
|
+
|
46
|
+
option :help do
|
47
|
+
long '--help'
|
48
|
+
desc 'Show this message'
|
49
|
+
end
|
50
|
+
|
51
|
+
option :version do
|
52
|
+
short '-v'
|
53
|
+
long '--version'
|
54
|
+
desc 'Show version'
|
55
|
+
action do
|
56
|
+
puts "#{$0} Fitbit sleep data importer v#{PROGRAM_VERSION}"
|
57
|
+
exit
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
option :debug do
|
62
|
+
long '--debug'
|
63
|
+
desc 'run in debug mode'
|
64
|
+
end
|
65
|
+
|
66
|
+
option :jawbone do
|
67
|
+
long '--jawbone'
|
68
|
+
desc 'send jawbone compatible data'
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def client_setup
|
73
|
+
# Load the existing yml config
|
74
|
+
config = begin
|
75
|
+
Fitgem::Client.symbolize_keys(YAML.load(File.open(File.join(ENV['HOME'], ".fitgem.yml"))))
|
76
|
+
rescue ArgumentError => e
|
77
|
+
puts "Could not parse YAML: #{e.message}"
|
78
|
+
exit
|
79
|
+
end
|
80
|
+
|
81
|
+
client = Fitgem::Client.new(config[:oauth])
|
82
|
+
|
83
|
+
# With the token and secret, we will try to use them
|
84
|
+
# to reconstitute a usable Fitgem::Client
|
85
|
+
if config[:oauth][:token] && config[:oauth][:secret]
|
86
|
+
begin
|
87
|
+
access_token = client.reconnect(config[:oauth][:token], config[:oauth][:secret])
|
88
|
+
rescue Exception => e
|
89
|
+
puts "Error: Could not reconnect Fitgem::Client due to invalid keys in .fitgem.yml"
|
90
|
+
exit
|
91
|
+
end
|
92
|
+
# Without the secret and token, initialize the Fitgem::Client
|
93
|
+
# and send the user to login and get a verifier token
|
94
|
+
else
|
95
|
+
request_token = client.request_token
|
96
|
+
token = request_token.token
|
97
|
+
secret = request_token.secret
|
98
|
+
|
99
|
+
puts "Go to http://www.fitbit.com/oauth/authorize?oauth_token=#{token} and then enter the verifier code below"
|
100
|
+
verifier = gets.chomp
|
101
|
+
|
102
|
+
begin
|
103
|
+
access_token = client.authorize(token, secret, { :oauth_verifier => verifier })
|
104
|
+
rescue Exception => e
|
105
|
+
puts "Error: Could not authorize Fitgem::Client with supplied oauth verifier"
|
106
|
+
exit
|
107
|
+
end
|
108
|
+
|
109
|
+
user_id = client.user_info['user']['encodedId']
|
110
|
+
|
111
|
+
config[:oauth].merge!(:token => access_token.token, :secret => access_token.secret, :user_id => user_id)
|
112
|
+
|
113
|
+
# Write the whole oauth token set back to the config file
|
114
|
+
File.open(File.join(ENV['HOME'], ".fitgem.yml"), "w") {|f| f.write(config.to_yaml) }
|
115
|
+
end
|
116
|
+
|
117
|
+
return client
|
118
|
+
end
|
119
|
+
|
120
|
+
def extract_data(client, &block)
|
121
|
+
if block.nil?
|
122
|
+
puts "No block given."
|
123
|
+
return
|
124
|
+
end
|
125
|
+
user_info = client.user_info
|
126
|
+
user_timezone = user_info['user']['timezone']
|
127
|
+
today = Date.today
|
128
|
+
all_sleep_data = client.sleep_on_date(today)['sleep']
|
129
|
+
if all_sleep_data.nil?
|
130
|
+
puts "API rate limit potentially exceeded."
|
131
|
+
return
|
132
|
+
end
|
133
|
+
sleep_data = []
|
134
|
+
sleep_summary = nil
|
135
|
+
all_sleep_data.each do |potential_log|
|
136
|
+
if potential_log['isMainSleep']
|
137
|
+
sleep_data = potential_log['minuteData']
|
138
|
+
sleep_summary = potential_log
|
139
|
+
end
|
140
|
+
end
|
141
|
+
createdate = DateTime.strptime("#{sleep_summary['startTime']}#{user_timezone}",'%Y-%m-%dT%H:%M:%S.%L%z').to_time.to_i
|
142
|
+
msg = ""
|
143
|
+
msg << "#{Choice[:namespace]}.summary.awakenings #{sleep_summary['awakeningsCount']} #{createdate}\n"
|
144
|
+
msg << "#{Choice[:namespace]}.summary.quality #{sleep_summary['efficiency']} #{createdate}\n"
|
145
|
+
msg << "#{Choice[:namespace]}.summary.deep_minutes #{sleep_summary['minutesAsleep']} #{createdate}\n"
|
146
|
+
msg << "#{Choice[:namespace]}.summary.light_minutes #{sleep_summary['minutesAwake']} #{createdate}\n"
|
147
|
+
sleep_data.each do |data|
|
148
|
+
d = DateTime.strptime("#{today.year}-#{today.month}-#{today.day}T#{data['dateTime']}#{user_timezone}",'%Y-%m-%dT%H:%M:%S%z')
|
149
|
+
state_name = SLEEP_STATE_TYPES[ data['value'].to_i - 1 ]
|
150
|
+
value = Choice[:jawbone] == true ? JAWBONE_SLEEP_STATES[state_name] : data['value']
|
151
|
+
msg << "#{Choice[:namespace]}.details.#{state_name} #{value} #{d.to_time.to_i}\n"
|
152
|
+
end
|
153
|
+
yield msg
|
154
|
+
end
|
155
|
+
|
156
|
+
def send_to_graphite(client)
|
157
|
+
extract_data(client) do |msg|
|
158
|
+
socket = TCPSocket.open(Choice[:host], Choice[:port])
|
159
|
+
socket.write(msg)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def print_sleep_data(client)
|
164
|
+
extract_data(client) {|msg| puts msg}
|
165
|
+
end
|
166
|
+
|
167
|
+
|
168
|
+
# main
|
169
|
+
client = client_setup
|
170
|
+
if Choice[:debug]
|
171
|
+
print_sleep_data(client)
|
172
|
+
else
|
173
|
+
send_to_graphite(client)
|
174
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
Gem::Specification.new do |gem|
|
2
|
+
gem.name = 'fitbit-to-graphite'
|
3
|
+
gem.version = '0.1.0'
|
4
|
+
gem.authors = ["Daniel Schauenberg"]
|
5
|
+
gem.email = 'd@unwiredcouch.com'
|
6
|
+
gem.homepage = 'https://github.com/mrtazz/fitbit-to-graphite'
|
7
|
+
gem.summary = "script to import fitbit sleep data into graphite"
|
8
|
+
gem.description = "script to import fitbit sleep data into graphite"
|
9
|
+
gem.license = 'MIT'
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.name = "fitbit-to-graphite"
|
13
|
+
|
14
|
+
gem.add_runtime_dependency "fitgem"
|
15
|
+
gem.add_runtime_dependency "choice"
|
16
|
+
end
|
metadata
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fitbit-to-graphite
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Daniel Schauenberg
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-10-13 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: fitgem
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: choice
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
description: script to import fitbit sleep data into graphite
|
47
|
+
email: d@unwiredcouch.com
|
48
|
+
executables: []
|
49
|
+
extensions: []
|
50
|
+
extra_rdoc_files: []
|
51
|
+
files:
|
52
|
+
- Gemfile
|
53
|
+
- Gemfile.lock
|
54
|
+
- LICENSE
|
55
|
+
- README.md
|
56
|
+
- Rakefile
|
57
|
+
- bin/fitbit-to-graphite.rb
|
58
|
+
- fitbit-to-graphite.gemspec
|
59
|
+
homepage: https://github.com/mrtazz/fitbit-to-graphite
|
60
|
+
licenses:
|
61
|
+
- MIT
|
62
|
+
post_install_message:
|
63
|
+
rdoc_options: []
|
64
|
+
require_paths:
|
65
|
+
- lib
|
66
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ! '>='
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
requirements: []
|
79
|
+
rubyforge_project:
|
80
|
+
rubygems_version: 1.8.25
|
81
|
+
signing_key:
|
82
|
+
specification_version: 3
|
83
|
+
summary: script to import fitbit sleep data into graphite
|
84
|
+
test_files: []
|
85
|
+
has_rdoc:
|