In [ ]:
import base64, json, subprocess, urllib, urllib2, csv
from IPython.display import HTML

Fluxtream interface library


In [2]:
# By default, the upload function will send data to the main server at fluxtream.org.  
# If you want to have this use a different fluxtream server, change it here
# and make sure the username and password entered below are valid on that server.
fluxtream_server = 'fluxtream.org'

def fluxtream_authenticate(username, password):
    global fluxtream_server, fluxtream_username, fluxtream_password, fluxtream_guest_id
    fluxtream_username = username
    fluxtream_password = password
    fluxtream_guest_id = None

    try:
        req = urllib2.Request('http://%s/api/guest' % fluxtream_server,
                        headers = fluxtream_headers())
        response = json.loads(urllib2.urlopen(req).read())
        fluxtream_guest_id = int(response['id'])
    except urllib2.HTTPError, e:
        print 'Failed to authenticate (server says %s)' % e
        return False        
    print 'Verified credentials for user %s on %s (guest ID=%d)' % (fluxtream_username, fluxtream_server, fluxtream_guest_id)
    return True

def fluxtream_headers():
    auth = base64.encodestring('%s:%s' % (fluxtream_username, fluxtream_password)).replace('\n', '')
    return {'Authorization': 'Basic ' + auth}

def fluxtream_confirm_authenticated():
    global fluxtream_guest_id
    if fluxtream_guest_id == None:
        raise Exception('Need to enter Fluxtream credentials before connecting.')

def fluxtream_upload(dev_nickname, channel_names, data):
    post_data = {
        'dev_nickname': dev_nickname,
        'channel_names': json.dumps(channel_names),
        'data': json.dumps(data)
    }
    
    print 'Uploading %d data points to %s\'s account on server %s, device %s, channels %s' % (len(data), 
                                                                                              fluxtream_username,
                                                                                              fluxtream_server, 
                                                                                              dev_nickname,
                                                                                              channel_names)

    req = urllib2.Request('https://%s/api/bodytrack/upload' % fluxtream_server,
                          data = urllib.urlencode(post_data),
                          headers = fluxtream_headers())

    response = urllib2.urlopen(req).read()
    if json.loads(response)['result'] != 'OK':
        raise Exception('fluxtream_upload: expected OK, got %s' % response)
    return True
    
# To get your own data, pass in the global fluxtream_guest_id which is computed 
# in setup_fluxtream_credentials() when you execute the Fluxtream login cell.
# To get a buddy's data, you first need to figure out what their Guest ID is.
# This will show up in the Chrome developer console in tile requests when you 
# look at their data in the timeline or BodyTrack app.  

# For example, if the test account is my buddy, I would select 
# 'View test test's data' from the upper right 
# hand menu, turn on developer tools, and go to the Fluxtream
# timeline tab.  In the developer tools' network tab I would 
# see fetches that look like:
#    7.21370.json
#    /api/bodytrack/tiles/1/BodyMedia.activityType
# The value between 'tiles' and the device_name.channel_name is
# that account's Guest ID.  In that case, I would call
# fluxtream_get_sources_list with an arg of 1.
# TODO: patch this back into Fluxtream-Library
def fluxtream_get_sources_list(guest_id):
    req = urllib2.Request('http://%s/api/bodytrack/users/%d/sources/list' % (fluxtream_server, guest_id),
                          headers = fluxtream_headers())
    return json.loads(urllib2.urlopen(req).read())

def fluxtream_get_device_names(sources_list):
    device_names = []
    for dev in sources_list['sources']:
        device_names.append(dev['name'])
        
    return device_names

def fluxtream_get_device_info(device_name, sources_list):
    for dev in sources_list['sources']:
        if(dev['name'] == device_name):
            return dev
        
    return None

def fluxtream_get_channel_names(device_name, sources_list):
    dev_info = fluxtream_get_device_info(device_name, sources_list)

    channel_names = []
    
    for channel in dev_info['channels']:
        channel_names.append(channel['name'])
        
    return channel_names

def fluxtream_get_channel_info(device_name, channel_name, sources_list):
    dev_info = fluxtream_get_device_info(device_name, sources_list)
    
    # Check to make sure that we found info for the requested device.
    # If not, return None
    if not dev_info:
        return None
    
    for channel_info in dev_info['channels']:
        if(channel_info['name'] == channel_name):
            return channel_info
        
    return None

# Takes a guest_id, an array of <device_name>.<channel_name> strings, and a time range and returns a CSV reader.
# Iterate over the rows using reader.next(), which returns a row array with entries corresponding to 
#   Epoch, [dev_ch_names]
# Where Epoch is the epoch timestamp (aka unixtime) for the values in the row, and the i+1'th column of the row 
# corresponds to the channel in dev_ch_names[i]

# See comment on fluxtream_get_sources_list for info about how to choose the value for guest_id
def fluxtream_get_csv(guest_id, dev_ch_names, start_time, end_time):
    global fluxtream_server
    fluxtream_confirm_authenticated()

    # Send to BodyTrack upload API, documented at 
    #   https://fluxtream.atlassian.net/wiki/display/FLX/BodyTrack+server+APIs#BodyTrackserverAPIs-Storingdata
        
    # Need to convert the dev_ch_names array into json and URL encode it to create the channels arg
    # TODO: how do we confirm that dev_ch_names is in fact an array?
    ch_spec_str = json.dumps(dev_ch_names)
    ch_spec_str = urllib.quote(ch_spec_str)
    url = ('https://%s/api/bodytrack/exportCSV/%d/fluxtream-export-from-%d-to-%d.csv?channels=%s' % 
           (fluxtream_server, guest_id, int(start_time), int(end_time), ch_spec_str))
    try:
        req = urllib2.Request(url, headers = fluxtream_headers())
        result_str = urllib2.urlopen(req).read()
    except urllib2.HTTPError, e:
        raise Exception('Failed to authenticate (server says %s)' % e)

    # If the API call worked, result_str should be a CSV file
    # with the first line a header consisting of EpochTime, [dev_ch_names]

    # Create a CSV reader that iterates over the lines of the response
    csv_reader = csv.reader(result_str.splitlines(), delimiter=',')
    header = csv_reader.next()
    
    # Do some checks to make sure we got something reasonable
    if len(header) != len(dev_ch_names)+1:
        raise Exception("Expected header for CSV export of %s to contain %d columns, but only found %d.  Please double check that dev_ch_names are all valid" % (dev_ch_names, len(dev_ch_names)+1, len(header)))

    # Check the columns are what we expect
    for i in range(0,len(dev_ch_names)):
        if(dev_ch_names[i] != header[i+1]):
            raise Exception("Expected column %d of CSV header to be %s, but found %s instead.  Please double check that dev_ch_names are all valid" % (i+1, dev_ch_names[i], header[i+1]))
            
    # At this point, we can be confident that the columns map to Epoch, [dev_ch_names] as expected.
    # Return the csv reader.  Iterate over the rows using reader.next()
    return csv_reader

def fluxtream_login():
    return HTML("""
Fluxtream username: <input id="fluxtream_username" type="text"></input><br>
Fluxtream password: <input id="fluxtream_password" type="password"></input><br>
<button id = "fluxtream_authenticate">Authenticate</button> <span id="fluxtream_auth_result"></span>
<script>

// Send username and password to python
function fluxtream_authenticate(username, password, callback) {
  cmd =  'fluxtream_authenticate(' + JSON.stringify(username) + ',' +
         JSON.stringify(password) + ')'
  console.log(cmd);
  function cb(msg) {
    console.log(msg);
    console.log(msg.content.data['text/plain'] == 'True');
    callback(msg.content.data['text/plain'] == 'True')
  }
  IPython.notebook.kernel.execute(cmd, 
                                  {iopub: {output: cb}}, {silent: false});
}

$('#fluxtream_authenticate').click(function() {
    $('#fluxtream_auth_result').text('...');
    fluxtream_authenticate($('#fluxtream_username').val(),
                           $('#fluxtream_password').val(),
                           function(success) {
                             $('#fluxtream_auth_result').text(success ? 'Success' : 'Failed');
                           });
});
</script>
""")

In [ ]: