def render(self, request): logging.info("Fitbit App, request args: {0}".format(request.args)) # if "init" in request.args: # self.indx = IndxClient("http://{0}".format(request.args['host'][0]), request.args['box'], request.args['username'], request.args["password"], "FitbitConnector") # print self.indx # logging.info("Fitbit App, connected to the box {0}".format(box)) # self.return_ok(request, data = {"init": "ok"}) if "gotourl" in request.args: gotourl = self.fitbit.get_token_url() logging.info("Fitbit App, the gotourl is {0}".format(gotourl)) self.return_ok(request, data = {"url": gotourl}) elif "pin" in request.args: pin = request.args['pin'][0] logging.info("Fitbit App, the pin is {0}".format(pin)) token = self.fitbit.get_token_with_pin(pin) self.return_ok(request, data = {"token": json.dumps({"token_key": "{0}".format(token.key), "token_secret": "{0}".format(token.secret)})}) elif "token" in request.args: token = json.loads(request.args["token"][0]) self.fitbit = Fitbit(self.consumer_key, self.consumer_secret, token['token_key'], token['token_secret']) self.return_ok(request, data={}) elif "download" in request.args: self.fitbit_min = FitbitIntraDay(self.fitbit) start = None if ("start" in request.args): start = datetime.fromtimestamp(int(request.args["start"][0])/1000) response = self.download_data(start) self.return_ok(request, data = response) else: logging.info("Fitbit App, returning 404") self.return_not_found(request) return NOT_DONE_YET
def work(self, server_url): self.load_configuration() self.logger.debug("Loaded configuration: box: {0}, user: {1}, pass: {2}, overwrite: {3}, start: {4}".format(self.config_box, self.config_indx_user, self.config_indx_pass, self.config_overwrite, self.config_start.isoformat())) self.fitbit_intraday = FitbitIntraDay(self.fitbit) self.logger.debug("Created FitbitIntraDay.") def indx_cb(indx): self.logger.debug("Created INDXClient.") prep_d = Deferred() def objects_cb(harvester, indx=indx): # self.logger.debug("Results for object creation: {0}".format(objs)) self.logger.debug("Found or created all 5 time series.") self.logger.debug("Found or created harvester. {0}".format(harvester)) def wait(x): self.logger.debug("Harvested! Suspending execution for 6 hours at {0}.".format(datetime.now().isoformat())) sleep(21600) prep_d.callback(None) # harvester = objs[5] if harvester: if "zeros_from" in harvester : self.config_zeros_from = datetime.strptime(harvester["zeros_from"][0]["@value"], "%Y-%m-%dT%H:%M:%S") if self.config_start < self.config_zeros_from : if self.config_overwrite : self.config_zeros_from = self.today() self.config_overwrite = False; # will only overwrite the first time if the flag is set else: self.config_start = stored_zeros_from+timedelta(days=-1) if "fetched_days" in harvester : self.config_fetched_days = self.parse_list(harvester["fetched_days"]) self.harvest(indx, harvester).addCallbacks(wait, prep_d.errback) self.find_create(indx, self.steps_ts_id, {"http://www.w3.org/2000/01/rdf-schema#label":"Fitbit Steps Time Series", "http://www.w3.org/1999/02/22-rdf-syntax-ns#type":"http://purl.org/linked-data/cube#Dataset"}).addCallbacks( lambda x: self.find_create(indx, self.calories_ts_id, {"http://www.w3.org/2000/01/rdf-schema#label":"Fitbit Calories Time Series", "http://www.w3.org/1999/02/22-rdf-syntax-ns#type":"http://purl.org/linked-data/cube#Dataset"}), prep_d.errback).addCallbacks( lambda x: self.find_create(indx, self.distance_ts_id, {"http://www.w3.org/2000/01/rdf-schema#label":"Fitbit Distance Time Series", "http://www.w3.org/1999/02/22-rdf-syntax-ns#type":"http://purl.org/linked-data/cube#Dataset"}), prep_d.errback).addCallbacks( lambda x: self.find_create(indx, self.floors_ts_id, {"http://www.w3.org/2000/01/rdf-schema#label":"Fitbit Floors Time Series", "http://www.w3.org/1999/02/22-rdf-syntax-ns#type":"http://purl.org/linked-data/cube#Dataset"}), prep_d.errback).addCallbacks( lambda x: self.find_create(indx, self.elevation_ts_id, {"http://www.w3.org/2000/01/rdf-schema#label":"Fitbit Elevation Time Series", "http://www.w3.org/1999/02/22-rdf-syntax-ns#type":"http://purl.org/linked-data/cube#Dataset"}), prep_d.errback).addCallbacks( lambda x: self.find_create(indx, self.harvester_id, {"http://www.w3.org/2000/01/rdf-schema#label":"INDX Fitbit Harvester extra info"}), prep_d.errback).addCallbacks(objects_cb, prep_d.errback) return prep_d def indx_fail_cb(f): self.logger.error("Error getting indx! Exiting... {0}".format(f)) sys.exit(1) def loop_fail_cb(f): self.logger.error("Error in indx_cb! Exiting... {0}".format(f)) sys.exit(1) def loop(x=None): self.get_indx(server_url).addCallbacks(indx_cb, indx_fail_cb).addCallbacks(loop, loop_fail_cb) threads.deferToThread(loop)
class FitbitHarvester: def __init__(self): log_handler = logging.FileHandler("fitbit_harvester.log", "a") log_handler.setLevel(logging.DEBUG) formatter = logging.Formatter('%(name)s\t%(levelname)s\t%(asctime)s\t%(message)s') log_handler.setFormatter(formatter) self.logger = logging.getLogger() self.logger.setLevel(logging.DEBUG) for handler in self.logger.handlers: # remove default handler self.logger.removeHandler(handler) self.logger.addHandler(log_handler) data_root = keyring.util.platform_.data_root() if not os.path.exists(data_root): os.mkdir(data_root) keyring.set_keyring(PlaintextKeyring()) self.parser = argparse.ArgumentParser(prog="run") self.parser.add_argument('--config', help="Set config (input requires JSON) and exit.") self.parser.add_argument('--get-config', action="store_true", help="Output current config as JSON and exit.") self.parser.add_argument('--server', help="The server URL to connect to.") # init fitbit consumer_key = "9cc7928d03fa4e1a92eda0d01ede2297" consumer_secret = "340ea36a974e47738a335c0cccfe1fcf" self.fitbit = Fitbit(consumer_key, consumer_secret) self.fitbit_intraday = None self.box_version = 0 self.config_overwrite = False; self.config_start = self.today() self.config_box = None self.config_indx_user = None self.config_indx_pass = None self.config_fetched_days = [] self.config_zeros_from = self.today() self.harvester_id = "fitbit_harvester" self.steps_ts_id = "fitbit_steps_ts" self.calories_ts_id = "fitbit_calories_ts" self.distance_ts_id = "fitbit_distance_ts" self.floors_ts_id = "fitbit_floors_ts" self.elevation_ts_id = "fitbit_elevation_ts" self.ts_count = 0 self.ts_error = None def set_config(self, args): stored_config_harvester = keyring.get_password("INDX", "INDX_Fitbit_Harvester") if stored_config_harvester is not None: stored_config_harvester = json.loads(stored_config_harvester) stored_config_fitbit = keyring.get_password("Fitbit.com", "Fitbit") if stored_config_fitbit is not None: stored_config_fitbit = json.loads(stored_config_fitbit) received_config = json.loads(args['config']) if (type(received_config) != dict): received_config = json.loads(received_config) self.logger.debug("Received config ({0}): {1}".format(type(received_config), received_config)) config = {} if 'fitbit' in received_config: fitbit_config = received_config['fitbit'] if fitbit_config and ('pin' in fitbit_config) and ('req_token' in fitbit_config): # this should check for the req_token in the stored config! self.logger.debug("Received pin: {0}".format(fitbit_config['pin'])) try: token = self.fitbit.get_token_with_pin(fitbit_config['pin'], fitbit_config['req_token']) self.logger.debug("Got auth token {0}, of type {1}".format(token, type(token))) if token: config['token']=token keyring.set_password("Fitbit.com", "Fitbit", json.dumps(config)) except Exception as exc: self.logger.error("Could not authorise to fitbit, with pin {0}, error: {1}".format(fitbit_config['pin'], exc)) if 'harvester' in received_config: harvester_config = received_config['harvester'] if harvester_config != stored_config_harvester: keyring.set_password("INDX", "INDX_Fitbit_Harvester", json.dumps(harvester_config)) def get_config(self, args): stored_config_harvester = keyring.get_password("INDX", "INDX_Fitbit_Harvester") stored_config_fitbit = keyring.get_password("Fitbit.com", "Fitbit") self.logger.debug("Loaded harvester config from keyring: {0}".format(stored_config_harvester)) self.logger.debug("Loaded fitbit config from keyring: {0}".format(stored_config_fitbit)) if stored_config_fitbit is None : token_url = self.fitbit.get_token_url() config_fitbit = {} config_fitbit["url"] = token_url['url'] config_fitbit["req_token"] = token_url['req_token'] else : if (type(stored_config_fitbit) != dict): config_fitbit = json.loads(stored_config_fitbit) # if (type(config_fitbit) != dict): # config_fitbit = json.loads(config_fitbit) if 'token' not in config_fitbit : token_url = self.fitbit.get_token_url() config_fitbit["url"] = token_url['url'] config_fitbit["req_token"] = token_url['req_token'] keyring.set_password("Fitbit.com", "Fitbit", json.dumps(config_fitbit)) if stored_config_harvester is None: return json.dumps({"fitbit":config_fitbit}) # don't send the req_token return json.dumps({"fitbit":config_fitbit, "harvester":json.loads(stored_config_harvester)}) # don't send the req_token def load_configuration(self): stored_config_harvester = keyring.get_password("INDX", "INDX_Fitbit_Harvester") self.logger.debug("Loaded harvester config from keyring: {0}".format(stored_config_harvester)) if stored_config_harvester is None : self.logger.error("Harvester not configured. Please configure before use.") return if (type(stored_config_harvester) != dict): stored_config_harvester = json.loads(stored_config_harvester) if self.fitbit.get_token() is None: stored_config_fitbit = keyring.get_password("Fitbit.com", "Fitbit") self.logger.debug("Loaded fitbit config from keyring: {0}".format(stored_config_fitbit)) token = None if stored_config_fitbit is None : self.logger.error("Not authenticated to Fitbit.com. Please configure before use.") return else : if (type(stored_config_fitbit) != dict): stored_config_fitbit = json.loads(stored_config_fitbit) if 'token' not in stored_config_fitbit : self.logger.debug("Could not find Fitbit auth token in keyring"); if ('pin' in stored_config_fitbit) and ('req_token' in stored_config_fitbit): self.logger.debug("Found pin {0} and req_token in keyring, attempting authorization to Fitbit.com".format(stored_config_fitbit['pin'])) try: fitbit_token_config = {} token = self.fitbit.get_token_with_pin(stored_fitbit_config['pin'], stored_fitbit_config['req_token']) if token: self.logger.debug("Got auth token {0}".format(token)) # self.logger.debug("Got auth token of type {0}".format(type(token))) fitbit_token_config['token']=token keyring.set_password("Fitbit.com", "Fitbit", json.dumps(fitbit_token_config)) except Exception as exc: self.logger.error("Could not authorise to fitbit, error: {1}".format(exc)) return else : self.logger.debug("Could not find pin or req_token in keyring. Cannot attempt authorization to Fitbit.com.") return else: token = stored_config_fitbit['token'] if token is not None : self.fitbit.set_token(token) self.config_start = datetime.strptime(stored_config_harvester['start'], "%Y-%m-%d") self.config_box = stored_config_harvester['box'] self.config_indx_user = stored_config_harvester['user'] self.config_indx_pass = stored_config_harvester['password'] if 'overwrite' in stored_config_harvester : self.config_overwrite = stored_config_harvester['overwrite'] def run(self): args = vars(self.parser.parse_args()) self.logger.debug("Received arguments: {0}".format(args)) if args['config']: self.set_config(args) elif args['get_config']: print self.get_config(args) else: self.logger.debug("Starting the harvester. ") self.work(args['server']) reactor.run() def yesterday(self): return datetime.combine((datetime.now()+timedelta(days=-1)).date(), time(00,00,00)) def today(self): return datetime.combine(datetime.now().date(), time(00,00,00)) def get_indx(self, server_url): indx_d = Deferred() def authed_cb(): def token_cb(token): indx = IndxClient(server_url, self.config_box, "INDX_Fitbit_Harvester", token = token, client = authclient.client) indx_d.callback(indx) authclient.get_token(self.config_box).addCallbacks(token_cb, indx_d.errback) authclient = IndxClientAuth(server_url, "INDX_Fitbit_Harvester") authclient.auth_plain(self.config_indx_user, self.config_indx_pass).addCallbacks(lambda response: authed_cb(), indx_d.errback) return indx_d def work(self, server_url): self.load_configuration() self.logger.debug("Loaded configuration: box: {0}, user: {1}, pass: {2}, overwrite: {3}, start: {4}".format(self.config_box, self.config_indx_user, self.config_indx_pass, self.config_overwrite, self.config_start.isoformat())) self.fitbit_intraday = FitbitIntraDay(self.fitbit) self.logger.debug("Created FitbitIntraDay.") def indx_cb(indx): self.logger.debug("Created INDXClient.") prep_d = Deferred() def objects_cb(harvester, indx=indx): # self.logger.debug("Results for object creation: {0}".format(objs)) self.logger.debug("Found or created all 5 time series.") self.logger.debug("Found or created harvester. {0}".format(harvester)) def wait(x): self.logger.debug("Harvested! Suspending execution for 6 hours at {0}.".format(datetime.now().isoformat())) sleep(21600) prep_d.callback(None) # harvester = objs[5] if harvester: if "zeros_from" in harvester : self.config_zeros_from = datetime.strptime(harvester["zeros_from"][0]["@value"], "%Y-%m-%dT%H:%M:%S") if self.config_start < self.config_zeros_from : if self.config_overwrite : self.config_zeros_from = self.today() self.config_overwrite = False; # will only overwrite the first time if the flag is set else: self.config_start = stored_zeros_from+timedelta(days=-1) if "fetched_days" in harvester : self.config_fetched_days = self.parse_list(harvester["fetched_days"]) self.harvest(indx, harvester).addCallbacks(wait, prep_d.errback) self.find_create(indx, self.steps_ts_id, {"http://www.w3.org/2000/01/rdf-schema#label":"Fitbit Steps Time Series", "http://www.w3.org/1999/02/22-rdf-syntax-ns#type":"http://purl.org/linked-data/cube#Dataset"}).addCallbacks( lambda x: self.find_create(indx, self.calories_ts_id, {"http://www.w3.org/2000/01/rdf-schema#label":"Fitbit Calories Time Series", "http://www.w3.org/1999/02/22-rdf-syntax-ns#type":"http://purl.org/linked-data/cube#Dataset"}), prep_d.errback).addCallbacks( lambda x: self.find_create(indx, self.distance_ts_id, {"http://www.w3.org/2000/01/rdf-schema#label":"Fitbit Distance Time Series", "http://www.w3.org/1999/02/22-rdf-syntax-ns#type":"http://purl.org/linked-data/cube#Dataset"}), prep_d.errback).addCallbacks( lambda x: self.find_create(indx, self.floors_ts_id, {"http://www.w3.org/2000/01/rdf-schema#label":"Fitbit Floors Time Series", "http://www.w3.org/1999/02/22-rdf-syntax-ns#type":"http://purl.org/linked-data/cube#Dataset"}), prep_d.errback).addCallbacks( lambda x: self.find_create(indx, self.elevation_ts_id, {"http://www.w3.org/2000/01/rdf-schema#label":"Fitbit Elevation Time Series", "http://www.w3.org/1999/02/22-rdf-syntax-ns#type":"http://purl.org/linked-data/cube#Dataset"}), prep_d.errback).addCallbacks( lambda x: self.find_create(indx, self.harvester_id, {"http://www.w3.org/2000/01/rdf-schema#label":"INDX Fitbit Harvester extra info"}), prep_d.errback).addCallbacks(objects_cb, prep_d.errback) return prep_d def indx_fail_cb(f): self.logger.error("Error getting indx! Exiting... {0}".format(f)) sys.exit(1) def loop_fail_cb(f): self.logger.error("Error in indx_cb! Exiting... {0}".format(f)) sys.exit(1) def loop(x=None): self.get_indx(server_url).addCallbacks(indx_cb, indx_fail_cb).addCallbacks(loop, loop_fail_cb) threads.deferToThread(loop) def harvest(self, indx, harvester): self.logger.debug("Starting to harvest!") harvest_d = Deferred() def process_cb(day_points, day): self.logger.debug("Process day {0}".format(day.isoformat())) process_d = Deferred() # processing steps steps = self.download_steps(day) zeros = False zeros = self.check_all_zero(steps) if zeros : self.logger.debug("Step data points are all 0. ") if self.config_zeros_from > day: self.config_zeros_from = day else : self.config_zeros_from = self.today() steps_points = self.prepare_points(steps, day_points, day, self.steps_ts_id, "http://sociam.org/ontology/health/StepCount") # processing calories calories = self.download_calories(day) calories_points = self.prepare_points(calories, day_points, day, self.calories_ts_id, "http://sociam.org/ontology/health/CaloriesBurned") # processing distance distance = self.download_distance(day) distance_points = self.prepare_points(distance, day_points, day, self.distance_ts_id, "http://sociam.org/ontology/health/Distance") # processing floors floors = self.download_floors(day) floors_points = self.prepare_points(floors, day_points, day, self.floors_ts_id, "http://sociam.org/ontology/health/FloorsClimbed") # processing elevation elevation = self.download_elevation(day) elevation_points = self.prepare_points(elevation, day_points, day, self.elevation_ts_id, "http://sociam.org/ontology/health/Elevation") self.safe_update(indx, steps_points).addCallbacks( lambda x: self.safe_update(indx, calories_points), process_d.errback).addCallbacks( lambda x: self.safe_update(indx, distance_points), process_d.errback).addCallbacks( lambda x: self.safe_update(indx, floors_points), process_d.errback).addCallbacks( lambda x: self.safe_update(indx, elevation_points), process_d.errback).addCallbacks(process_d.callback, process_d.errback) return process_d def step_day(x, day, indx=indx, harvester=harvester): self.logger.debug("Current day: {0}".format(day)) if day < self.today(): next_day = day + timedelta(days=+1) self.get_day_points(indx, day).addCallbacks(process_cb, harvest_d.errback, callbackArgs=[day]).addCallbacks(step_day, harvest_d.errback, callbackArgs=[next_day, indx]) else: self.logger.debug("Finished harvesting round. Saving harvester data .. ") harvester["fetched_days"] = self.config_fetched_days harvester["zeros_from"] = self.config_zeros_from.isoformat() self.safe_update(indx, harvester).addCallbacks(harvest_d.callback, harvest_d.errback) self.logger.debug("Fetched days : {0}, Start from : {1}, Zeros from : {2}".format(self.config_fetched_days, self.config_start.isoformat(), self.config_zeros_from.isoformat())) step_day(None, self.config_start, indx) return harvest_d def prepare_points(self, raw_points, day_points, day, ts_id, rdf_type=None): points = [] if day_points and ts_id in day_points and day_points[ts_id] : points = self.replace_data_points(day, raw_points, day_points[ts_id], ts_id, rdf_type) else: points = self.create_data_points(day, raw_points, ts_id, rdf_type) return points def create_data_points(self, day, data, ts_id, rdf_type=None): self.logger.debug("Started creating data points.") data_points = [] for key in data.keys(): if key.endswith('-intraday'): for point in data[key]["dataset"]: interval_start = datetime.combine(day, datetime.strptime(point["time"], "%H:%M:%S").time()) interval_end = interval_start+timedelta(minutes=1) value = point["value"] data_point = { "@id": "fitbit_dp_{0}".format(uuid.uuid4()), "tstart": interval_start.isoformat(), "tend": interval_end.isoformat(), "value": value, "timeseries": { "@id": ts_id } } if rdf_type: data_point["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"] = [ { "@id": rdf_type } ] data_points.append(data_point) self.logger.debug("Finished creating data points.") return data_points def replace_data_points(self, day, new_data, old_data, ts_id, rdf_type=None): self.logger.debug("Started replacing values in data points.") data_points = [] replaced = 0 kept = 0 created = 0 for key in new_data.keys(): if key.endswith('-intraday'): for point in new_data[key]["dataset"]: data_point = None interval_start = datetime.combine(day, datetime.strptime(point["time"], "%H:%M:%S").time()) interval_end = interval_start+timedelta(minutes=1) value = point["value"] if interval_start in old_data: if len(old_data[interval_start]) > 1 : self.logger.error("There are {0} points for the same start time {1} in the same dataset {2}!!".format(len(old_data[interval_start]), interval_start, ts_id)) old_point = old_data[interval_start][0] # there shouldn't be more than 1 point here!!! if old_point['value'][0]['@value'] == str(value): kept = kept+1 else: replaced = replaced+1 # if all_other_fields_match: data_point = { "@id": old_point['@id'] } else: created = created+1 data_point = { "@id": "fitbit_dp_{0}".format(uuid.uuid4()) } if data_point : self.logger.debug("Making a data point for {0}".format(interval_start.isoformat())) data_point["tstart"] = interval_start.isoformat() data_point["tend"] = interval_end.isoformat() data_point["value"] = value data_point["timeseries"] = { "@id": ts_id } if rdf_type: data_point["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"] = [ { "@id": rdf_type } ] data_points.append(data_point) self.logger.debug("Finished replacing values in data points - replaced: {0}, kept: {1}, created: {2}".format(replaced, kept, created)) return data_points def download_steps(self, day): if (day == None): self.logger.error("No date given for download.") return [] steps = self.fitbit_intraday.get_steps(day)[0] self.logger.debug("Retrieved steps data for {0}.".format(day.date())) return steps def download_calories(self, day): if (day == None): self.logger.error("No date given for download.") return [] calories = self.fitbit_intraday.get_calories(day)[0] self.logger.debug("Retrieved calories data for {0}.".format(day.date())) return calories def download_distance(self, day): if (day == None): self.logger.error("No date given for download.") return [] distance = self.fitbit_intraday.get_distance(day)[0] self.logger.debug("Retrieved distance data for {0}.".format(day.date())) return distance def download_floors(self, day): if (day == None): self.logger.error("No date given for download.") return [] floors = self.fitbit_intraday.get_floors(day)[0] self.logger.debug("Retrieved floors data for {0}.".format(day.date())) return floors def download_elevation(self, day): if (day == None): self.logger.error("No date given for download.") return [] elevation = self.fitbit_intraday.get_elevation(day)[0] self.logger.debug("Retrieved elevation data for {0}.".format(day.date())) return elevation def get_day_points(self, indx, day): self.logger.debug("getting day points for {0}".format(day.isoformat())) points_d = Deferred() d = day.date() def points_cb(pts): points_d.callback(pts) if d.isoformat() in self.config_fetched_days : self.logger.debug("Data for {0} was already fetched, rechecking!".format(d.isoformat())) self.find_day_points(indx, day).addCallbacks(points_cb, points_d.errback) else: self.logger.debug("Data for {0} was not yet fetched. Getting it now.".format(d.isoformat())) self.config_fetched_days.append(d.isoformat()) points_d.callback(None) return points_d def find_day_points(self, indx, day) : self.logger.debug("Find day data points from {0}".format(day.date().isoformat())) points_d = Deferred() def found_cb(results): out = {self.steps_ts_id:{}, self.calories_ts_id:{}, self.distance_ts_id:{}, self.floors_ts_id:{}, self.elevation_ts_id:{}} for resp in results: if resp and 'code' in resp and resp['code']==200 and 'data' in resp: for pt in resp['data'] : obj = resp['data'][pt] if 'timeseries' in obj and obj['timeseries'][0]['@id'] in [self.steps_ts_id, self.calories_ts_id, self.distance_ts_id, self.floors_ts_id, self.elevation_ts_id] : objs_date_hash = out[obj['timeseries'][0]['@id']] if find_start in objs_date_hash: objs_list = objs_date_hash[find_start] else: objs_list = [] objs_list.append(obj) objs_date_hash[find_start] = objs_list out[obj['timeseries'][0]['@id']] = objs_date_hash self.logger.debug("Found points with start time {0}: {1}".format(find_start.isoformat(),objs_list)) else: self.logger.error("Didn't find any time points: {0}".format(resp)) points_d.callback(out) deferreds = [] for h in range(24): for m in range(60): find_start = datetime.combine(day.date(), time(h,m,0)) self.logger.debug("Searching data points with start time: {0}".format(find_start.isoformat())) deferreds.append(indx.query(json.dumps({"tstart":find_start.isoformat()}))) dl = defer.gatherResults(deferreds) dl.addCallbacks(found_cb, points_d.errback) return points_d def safe_update(self, indx, obj) : self.logger.debug("Updating objects at box version {0}".format(self.box_version)) update_d = Deferred() def update_cb(resp): self.logger.debug("safe_update: received response: {0}".format(resp)) if resp and "code" in resp and "data" in resp: if resp["code"] == 201 or resp["code"] == 200: self.box_version = resp["data"]["@version"] self.logger.debug("Updated objects! new box version: {0}".format(self.box_version)) update_d.callback(self.box_version) else: self.logger.debug("Received unknown response code {0}".format(resp)) update_d.errback(resp) else: self.logger.debug("Received unknown or no response {0}".format(resp)) update_d.errback(resp) def exception_cb(e, obj=obj, indx=indx): self.logger.error("Exception in safe update: {0}".format(e)) if isinstance(e.value, urllib2.HTTPError): # handle a version incorrect error, and update the version if e.value.code == 409: # 409 Obsolete response = e.value.read() json_response = json.loads(response) self.box_version = json_response['@version'] # self.safe_update(indx, obj).addCallbacks(reupdate_cb, update_d.errback) # try updating again now the version is correct indx.update(self.box_version, obj).addCallbacks(update_cb, exception_cb) else: self.logger.error("HTTPError updating INDX: {0}".format(e.value)) update_d.errback(e.value) else: self.logger.error("Error updating INDX: {0}".format(e.value)) update_d.errback(e.value) indx.update(self.box_version, obj).addCallbacks(update_cb, exception_cb) return update_d def find_by_id(self, indx, oid) : self.logger.debug("Searching object by id {0}".format(oid)) find_d = Deferred() def find_cb(resp): self.logger.debug("find_by_id: received response: {0}".format(resp)) if resp and "code" in resp and resp["code"] == 200 and "data" in resp: resp_data = resp["data"] if oid in resp_data: self.logger.debug("Object {0} found!".format(oid)) find_d.callback(resp_data[oid]) else: self.logger.debug("Object {0} not found!".format(oid)) find_d.callback(None) else: self.logger.debug("Response code is not 200! respose: {0}".format(resp)) find_d.errback(resp) # put a useful error here! indx.query(json.dumps({"@id": oid})).addCallbacks(find_cb, find_d.errback) return find_d def find_create(self, indx, oid, attrs={}): self.logger.debug("Searching for id: {0} and attrs: {1}".format(oid, attrs)) create_d = Deferred() def find_cb(obj): if obj is None: self.logger.debug("Object {0} not found! Creating it.".format(oid)) attrs.update({"@id":oid}) self.safe_update(indx, attrs).addCallbacks(lambda x: create_d.callback(attrs), create_d.errback) else: create_d.callback(obj) self.find_by_id(indx, oid).addCallbacks(find_cb, create_d.errback) return create_d def check_all_zero(self, data): for key in data.keys(): if key.endswith('-intraday'): for pt in data[key]["dataset"]: if pt["value"] > 0 : return False return True def parse_list(self, vlist): out = [x['@value'] for x in vlist] self.logger.debug("Parsed value list: {0}".format(out)) return out
class FitbitHarvester: def __init__(self): log_handler = logging.FileHandler("fitbit_harvester.log", "a") log_handler.setLevel(logging.DEBUG) formatter = logging.Formatter( '%(name)s\t%(levelname)s\t%(asctime)s\t%(message)s') log_handler.setFormatter(formatter) self.logger = logging.getLogger() self.logger.setLevel(logging.DEBUG) for handler in self.logger.handlers: # remove default handler self.logger.removeHandler(handler) self.logger.addHandler(log_handler) data_root = keyring.util.platform_.data_root() if not os.path.exists(data_root): os.mkdir(data_root) keyring.set_keyring(PlaintextKeyring()) self.parser = argparse.ArgumentParser(prog="run") self.parser.add_argument( '--config', help="Set config (input requires JSON) and exit.") self.parser.add_argument( '--get-config', action="store_true", help="Output current config as JSON and exit.") self.parser.add_argument('--server', help="The server URL to connect to.") # init fitbit consumer_key = "9cc7928d03fa4e1a92eda0d01ede2297" consumer_secret = "340ea36a974e47738a335c0cccfe1fcf" self.fitbit = Fitbit(consumer_key, consumer_secret) self.fitbit_intraday = None self.box_version = 0 self.config_overwrite = False self.config_start = self.today() self.config_box = None self.config_indx_user = None self.config_indx_pass = None self.config_fetched_days = [] self.config_zeros_from = self.today() self.harvester_id = "fitbit_harvester" self.steps_ts_id = "fitbit_steps_ts" self.calories_ts_id = "fitbit_calories_ts" self.distance_ts_id = "fitbit_distance_ts" self.floors_ts_id = "fitbit_floors_ts" self.elevation_ts_id = "fitbit_elevation_ts" self.ts_count = 0 self.ts_error = None def set_config(self, args): stored_config_harvester = keyring.get_password( "INDX", "INDX_Fitbit_Harvester") if stored_config_harvester is not None: stored_config_harvester = json.loads(stored_config_harvester) stored_config_fitbit = keyring.get_password("Fitbit.com", "Fitbit") if stored_config_fitbit is not None: stored_config_fitbit = json.loads(stored_config_fitbit) received_config = json.loads(args['config']) if (type(received_config) != dict): received_config = json.loads(received_config) self.logger.debug("Received config ({0}): {1}".format( type(received_config), received_config)) config = {} if 'fitbit' in received_config: fitbit_config = received_config['fitbit'] if fitbit_config and ('pin' in fitbit_config) and ( 'req_token' in fitbit_config ): # this should check for the req_token in the stored config! self.logger.debug("Received pin: {0}".format( fitbit_config['pin'])) try: token = self.fitbit.get_token_with_pin( fitbit_config['pin'], fitbit_config['req_token']) self.logger.debug("Got auth token {0}, of type {1}".format( token, type(token))) if token: config['token'] = token keyring.set_password("Fitbit.com", "Fitbit", json.dumps(config)) except Exception as exc: self.logger.error( "Could not authorise to fitbit, with pin {0}, error: {1}" .format(fitbit_config['pin'], exc)) if 'harvester' in received_config: harvester_config = received_config['harvester'] if harvester_config != stored_config_harvester: keyring.set_password("INDX", "INDX_Fitbit_Harvester", json.dumps(harvester_config)) def get_config(self, args): stored_config_harvester = keyring.get_password( "INDX", "INDX_Fitbit_Harvester") stored_config_fitbit = keyring.get_password("Fitbit.com", "Fitbit") self.logger.debug("Loaded harvester config from keyring: {0}".format( stored_config_harvester)) self.logger.debug("Loaded fitbit config from keyring: {0}".format( stored_config_fitbit)) if stored_config_fitbit is None: token_url = self.fitbit.get_token_url() config_fitbit = {} config_fitbit["url"] = token_url['url'] config_fitbit["req_token"] = token_url['req_token'] else: if (type(stored_config_fitbit) != dict): config_fitbit = json.loads(stored_config_fitbit) # if (type(config_fitbit) != dict): # config_fitbit = json.loads(config_fitbit) if 'token' not in config_fitbit: token_url = self.fitbit.get_token_url() config_fitbit["url"] = token_url['url'] config_fitbit["req_token"] = token_url['req_token'] keyring.set_password("Fitbit.com", "Fitbit", json.dumps(config_fitbit)) if stored_config_harvester is None: return json.dumps({"fitbit": config_fitbit}) # don't send the req_token return json.dumps({ "fitbit": config_fitbit, "harvester": json.loads(stored_config_harvester) }) # don't send the req_token def load_configuration(self): stored_config_harvester = keyring.get_password( "INDX", "INDX_Fitbit_Harvester") self.logger.debug("Loaded harvester config from keyring: {0}".format( stored_config_harvester)) if stored_config_harvester is None: self.logger.error( "Harvester not configured. Please configure before use.") return if (type(stored_config_harvester) != dict): stored_config_harvester = json.loads(stored_config_harvester) if self.fitbit.get_token() is None: stored_config_fitbit = keyring.get_password("Fitbit.com", "Fitbit") self.logger.debug("Loaded fitbit config from keyring: {0}".format( stored_config_fitbit)) token = None if stored_config_fitbit is None: self.logger.error( "Not authenticated to Fitbit.com. Please configure before use." ) return else: if (type(stored_config_fitbit) != dict): stored_config_fitbit = json.loads(stored_config_fitbit) if 'token' not in stored_config_fitbit: self.logger.debug( "Could not find Fitbit auth token in keyring") if ('pin' in stored_config_fitbit) and ( 'req_token' in stored_config_fitbit): self.logger.debug( "Found pin {0} and req_token in keyring, attempting authorization to Fitbit.com" .format(stored_config_fitbit['pin'])) try: fitbit_token_config = {} token = self.fitbit.get_token_with_pin( stored_fitbit_config['pin'], stored_fitbit_config['req_token']) if token: self.logger.debug( "Got auth token {0}".format(token)) # self.logger.debug("Got auth token of type {0}".format(type(token))) fitbit_token_config['token'] = token keyring.set_password( "Fitbit.com", "Fitbit", json.dumps(fitbit_token_config)) except Exception as exc: self.logger.error( "Could not authorise to fitbit, error: {1}". format(exc)) return else: self.logger.debug( "Could not find pin or req_token in keyring. Cannot attempt authorization to Fitbit.com." ) return else: token = stored_config_fitbit['token'] if token is not None: self.fitbit.set_token(token) self.config_start = datetime.strptime(stored_config_harvester['start'], "%Y-%m-%d") self.config_box = stored_config_harvester['box'] self.config_indx_user = stored_config_harvester['user'] self.config_indx_pass = stored_config_harvester['password'] if 'overwrite' in stored_config_harvester: self.config_overwrite = stored_config_harvester['overwrite'] def run(self): args = vars(self.parser.parse_args()) self.logger.debug("Received arguments: {0}".format(args)) if args['config']: self.set_config(args) elif args['get_config']: print self.get_config(args) else: self.logger.debug("Starting the harvester. ") self.work(args['server']) reactor.run() def yesterday(self): return datetime.combine((datetime.now() + timedelta(days=-1)).date(), time(00, 00, 00)) def today(self): return datetime.combine(datetime.now().date(), time(00, 00, 00)) def get_indx(self, server_url): indx_d = Deferred() def authed_cb(): def token_cb(token): indx = IndxClient(server_url, self.config_box, "INDX_Fitbit_Harvester", token=token, client=authclient.client) indx_d.callback(indx) authclient.get_token(self.config_box).addCallbacks( token_cb, indx_d.errback) authclient = IndxClientAuth(server_url, "INDX_Fitbit_Harvester") authclient.auth_plain(self.config_indx_user, self.config_indx_pass).addCallbacks( lambda response: authed_cb(), indx_d.errback) return indx_d def work(self, server_url): self.load_configuration() self.logger.debug( "Loaded configuration: box: {0}, user: {1}, pass: {2}, overwrite: {3}, start: {4}" .format(self.config_box, self.config_indx_user, self.config_indx_pass, self.config_overwrite, self.config_start.isoformat())) self.fitbit_intraday = FitbitIntraDay(self.fitbit) self.logger.debug("Created FitbitIntraDay.") def indx_cb(indx): self.logger.debug("Created INDXClient.") prep_d = Deferred() def objects_cb(harvester, indx=indx): # self.logger.debug("Results for object creation: {0}".format(objs)) self.logger.debug("Found or created all 5 time series.") self.logger.debug( "Found or created harvester. {0}".format(harvester)) def wait(x): self.logger.debug( "Harvested! Suspending execution for 6 hours at {0}.". format(datetime.now().isoformat())) sleep(21600) prep_d.callback(None) # harvester = objs[5] if harvester: if "zeros_from" in harvester: self.config_zeros_from = datetime.strptime( harvester["zeros_from"][0]["@value"], "%Y-%m-%dT%H:%M:%S") if self.config_start < self.config_zeros_from: if self.config_overwrite: self.config_zeros_from = self.today() self.config_overwrite = False # will only overwrite the first time if the flag is set else: self.config_start = stored_zeros_from + timedelta( days=-1) if "fetched_days" in harvester: self.config_fetched_days = self.parse_list( harvester["fetched_days"]) self.harvest(indx, harvester).addCallbacks(wait, prep_d.errback) self.find_create( indx, self.steps_ts_id, { "http://www.w3.org/2000/01/rdf-schema#label": "Fitbit Steps Time Series", "http://www.w3.org/1999/02/22-rdf-syntax-ns#type": "http://purl.org/linked-data/cube#Dataset" }).addCallbacks( lambda x: self.find_create( indx, self.calories_ts_id, { "http://www.w3.org/2000/01/rdf-schema#label": "Fitbit Calories Time Series", "http://www.w3.org/1999/02/22-rdf-syntax-ns#type": "http://purl.org/linked-data/cube#Dataset" }), prep_d.errback ).addCallbacks( lambda x: self.find_create( indx, self.distance_ts_id, { "http://www.w3.org/2000/01/rdf-schema#label": "Fitbit Distance Time Series", "http://www.w3.org/1999/02/22-rdf-syntax-ns#type": "http://purl.org/linked-data/cube#Dataset" }), prep_d.errback ).addCallbacks( lambda x: self.find_create( indx, self.floors_ts_id, { "http://www.w3.org/2000/01/rdf-schema#label": "Fitbit Floors Time Series", "http://www.w3.org/1999/02/22-rdf-syntax-ns#type": "http://purl.org/linked-data/cube#Dataset" }), prep_d.errback ).addCallbacks( lambda x: self.find_create( indx, self.elevation_ts_id, { "http://www.w3.org/2000/01/rdf-schema#label": "Fitbit Elevation Time Series", "http://www.w3.org/1999/02/22-rdf-syntax-ns#type": "http://purl.org/linked-data/cube#Dataset" }), prep_d.errback).addCallbacks( lambda x: self.find_create( indx, self.harvester_id, { "http://www.w3.org/2000/01/rdf-schema#label": "INDX Fitbit Harvester extra info" }), prep_d.errback).addCallbacks(objects_cb, prep_d.errback) return prep_d def indx_fail_cb(f): self.logger.error("Error getting indx! Exiting... {0}".format(f)) sys.exit(1) def loop_fail_cb(f): self.logger.error("Error in indx_cb! Exiting... {0}".format(f)) sys.exit(1) def loop(x=None): self.get_indx(server_url).addCallbacks(indx_cb, indx_fail_cb).addCallbacks( loop, loop_fail_cb) threads.deferToThread(loop) def harvest(self, indx, harvester): self.logger.debug("Starting to harvest!") harvest_d = Deferred() def process_cb(day_points, day): self.logger.debug("Process day {0}".format(day.isoformat())) process_d = Deferred() # processing steps steps = self.download_steps(day) zeros = False zeros = self.check_all_zero(steps) if zeros: self.logger.debug("Step data points are all 0. ") if self.config_zeros_from > day: self.config_zeros_from = day else: self.config_zeros_from = self.today() steps_points = self.prepare_points( steps, day_points, day, self.steps_ts_id, "http://sociam.org/ontology/health/StepCount") # processing calories calories = self.download_calories(day) calories_points = self.prepare_points( calories, day_points, day, self.calories_ts_id, "http://sociam.org/ontology/health/CaloriesBurned") # processing distance distance = self.download_distance(day) distance_points = self.prepare_points( distance, day_points, day, self.distance_ts_id, "http://sociam.org/ontology/health/Distance") # processing floors floors = self.download_floors(day) floors_points = self.prepare_points( floors, day_points, day, self.floors_ts_id, "http://sociam.org/ontology/health/FloorsClimbed") # processing elevation elevation = self.download_elevation(day) elevation_points = self.prepare_points( elevation, day_points, day, self.elevation_ts_id, "http://sociam.org/ontology/health/Elevation") self.safe_update(indx, steps_points).addCallbacks( lambda x: self.safe_update(indx, calories_points), process_d.errback).addCallbacks( lambda x: self.safe_update(indx, distance_points), process_d.errback).addCallbacks( lambda x: self.safe_update(indx, floors_points), process_d.errback).addCallbacks( lambda x: self.safe_update(indx, elevation_points), process_d.errback).addCallbacks( process_d.callback, process_d.errback) return process_d def step_day(x, day, indx=indx, harvester=harvester): self.logger.debug("Current day: {0}".format(day)) if day < self.today(): next_day = day + timedelta(days=+1) self.get_day_points( indx, day).addCallbacks(process_cb, harvest_d.errback, callbackArgs=[day]).addCallbacks( step_day, harvest_d.errback, callbackArgs=[next_day, indx]) else: self.logger.debug( "Finished harvesting round. Saving harvester data .. ") harvester["fetched_days"] = self.config_fetched_days harvester["zeros_from"] = self.config_zeros_from.isoformat() self.safe_update(indx, harvester).addCallbacks( harvest_d.callback, harvest_d.errback) self.logger.debug( "Fetched days : {0}, Start from : {1}, Zeros from : {2}".format( self.config_fetched_days, self.config_start.isoformat(), self.config_zeros_from.isoformat())) step_day(None, self.config_start, indx) return harvest_d def prepare_points(self, raw_points, day_points, day, ts_id, rdf_type=None): points = [] if day_points and ts_id in day_points and day_points[ts_id]: points = self.replace_data_points(day, raw_points, day_points[ts_id], ts_id, rdf_type) else: points = self.create_data_points(day, raw_points, ts_id, rdf_type) return points def create_data_points(self, day, data, ts_id, rdf_type=None): self.logger.debug("Started creating data points.") data_points = [] for key in data.keys(): if key.endswith('-intraday'): for point in data[key]["dataset"]: interval_start = datetime.combine( day, datetime.strptime(point["time"], "%H:%M:%S").time()) interval_end = interval_start + timedelta(minutes=1) value = point["value"] data_point = { "@id": "fitbit_dp_{0}".format(uuid.uuid4()), "tstart": interval_start.isoformat(), "tend": interval_end.isoformat(), "value": value, "timeseries": { "@id": ts_id } } if rdf_type: data_point[ "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"] = [ { "@id": rdf_type } ] data_points.append(data_point) self.logger.debug("Finished creating data points.") return data_points def replace_data_points(self, day, new_data, old_data, ts_id, rdf_type=None): self.logger.debug("Started replacing values in data points.") data_points = [] replaced = 0 kept = 0 created = 0 for key in new_data.keys(): if key.endswith('-intraday'): for point in new_data[key]["dataset"]: data_point = None interval_start = datetime.combine( day, datetime.strptime(point["time"], "%H:%M:%S").time()) interval_end = interval_start + timedelta(minutes=1) value = point["value"] if interval_start in old_data: if len(old_data[interval_start]) > 1: self.logger.error( "There are {0} points for the same start time {1} in the same dataset {2}!!" .format(len(old_data[interval_start]), interval_start, ts_id)) old_point = old_data[interval_start][ 0] # there shouldn't be more than 1 point here!!! if old_point['value'][0]['@value'] == str(value): kept = kept + 1 else: replaced = replaced + 1 # if all_other_fields_match: data_point = {"@id": old_point['@id']} else: created = created + 1 data_point = { "@id": "fitbit_dp_{0}".format(uuid.uuid4()) } if data_point: self.logger.debug("Making a data point for {0}".format( interval_start.isoformat())) data_point["tstart"] = interval_start.isoformat() data_point["tend"] = interval_end.isoformat() data_point["value"] = value data_point["timeseries"] = {"@id": ts_id} if rdf_type: data_point[ "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"] = [ { "@id": rdf_type } ] data_points.append(data_point) self.logger.debug( "Finished replacing values in data points - replaced: {0}, kept: {1}, created: {2}" .format(replaced, kept, created)) return data_points def download_steps(self, day): if (day == None): self.logger.error("No date given for download.") return [] steps = self.fitbit_intraday.get_steps(day)[0] self.logger.debug("Retrieved steps data for {0}.".format(day.date())) return steps def download_calories(self, day): if (day == None): self.logger.error("No date given for download.") return [] calories = self.fitbit_intraday.get_calories(day)[0] self.logger.debug("Retrieved calories data for {0}.".format( day.date())) return calories def download_distance(self, day): if (day == None): self.logger.error("No date given for download.") return [] distance = self.fitbit_intraday.get_distance(day)[0] self.logger.debug("Retrieved distance data for {0}.".format( day.date())) return distance def download_floors(self, day): if (day == None): self.logger.error("No date given for download.") return [] floors = self.fitbit_intraday.get_floors(day)[0] self.logger.debug("Retrieved floors data for {0}.".format(day.date())) return floors def download_elevation(self, day): if (day == None): self.logger.error("No date given for download.") return [] elevation = self.fitbit_intraday.get_elevation(day)[0] self.logger.debug("Retrieved elevation data for {0}.".format( day.date())) return elevation def get_day_points(self, indx, day): self.logger.debug("getting day points for {0}".format(day.isoformat())) points_d = Deferred() d = day.date() def points_cb(pts): points_d.callback(pts) if d.isoformat() in self.config_fetched_days: self.logger.debug( "Data for {0} was already fetched, rechecking!".format( d.isoformat())) self.find_day_points(indx, day).addCallbacks(points_cb, points_d.errback) else: self.logger.debug( "Data for {0} was not yet fetched. Getting it now.".format( d.isoformat())) self.config_fetched_days.append(d.isoformat()) points_d.callback(None) return points_d def find_day_points(self, indx, day): self.logger.debug("Find day data points from {0}".format( day.date().isoformat())) points_d = Deferred() def found_cb(results): out = { self.steps_ts_id: {}, self.calories_ts_id: {}, self.distance_ts_id: {}, self.floors_ts_id: {}, self.elevation_ts_id: {} } for resp in results: if resp and 'code' in resp and resp[ 'code'] == 200 and 'data' in resp: for pt in resp['data']: obj = resp['data'][pt] if 'timeseries' in obj and obj['timeseries'][0][ '@id'] in [ self.steps_ts_id, self.calories_ts_id, self.distance_ts_id, self.floors_ts_id, self.elevation_ts_id ]: objs_date_hash = out[obj['timeseries'][0]['@id']] if find_start in objs_date_hash: objs_list = objs_date_hash[find_start] else: objs_list = [] objs_list.append(obj) objs_date_hash[find_start] = objs_list out[obj['timeseries'][0]['@id']] = objs_date_hash self.logger.debug( "Found points with start time {0}: {1}".format( find_start.isoformat(), objs_list)) else: self.logger.error( "Didn't find any time points: {0}".format(resp)) points_d.callback(out) deferreds = [] for h in range(24): for m in range(60): find_start = datetime.combine(day.date(), time(h, m, 0)) self.logger.debug( "Searching data points with start time: {0}".format( find_start.isoformat())) deferreds.append( indx.query(json.dumps({"tstart": find_start.isoformat()}))) dl = defer.gatherResults(deferreds) dl.addCallbacks(found_cb, points_d.errback) return points_d def safe_update(self, indx, obj): self.logger.debug("Updating objects at box version {0}".format( self.box_version)) update_d = Deferred() def update_cb(resp): self.logger.debug( "safe_update: received response: {0}".format(resp)) if resp and "code" in resp and "data" in resp: if resp["code"] == 201 or resp["code"] == 200: self.box_version = resp["data"]["@version"] self.logger.debug( "Updated objects! new box version: {0}".format( self.box_version)) update_d.callback(self.box_version) else: self.logger.debug( "Received unknown response code {0}".format(resp)) update_d.errback(resp) else: self.logger.debug( "Received unknown or no response {0}".format(resp)) update_d.errback(resp) def exception_cb(e, obj=obj, indx=indx): self.logger.error("Exception in safe update: {0}".format(e)) if isinstance( e.value, urllib2.HTTPError ): # handle a version incorrect error, and update the version if e.value.code == 409: # 409 Obsolete response = e.value.read() json_response = json.loads(response) self.box_version = json_response['@version'] # self.safe_update(indx, obj).addCallbacks(reupdate_cb, update_d.errback) # try updating again now the version is correct indx.update(self.box_version, obj).addCallbacks(update_cb, exception_cb) else: self.logger.error("HTTPError updating INDX: {0}".format( e.value)) update_d.errback(e.value) else: self.logger.error("Error updating INDX: {0}".format(e.value)) update_d.errback(e.value) indx.update(self.box_version, obj).addCallbacks(update_cb, exception_cb) return update_d def find_by_id(self, indx, oid): self.logger.debug("Searching object by id {0}".format(oid)) find_d = Deferred() def find_cb(resp): self.logger.debug( "find_by_id: received response: {0}".format(resp)) if resp and "code" in resp and resp[ "code"] == 200 and "data" in resp: resp_data = resp["data"] if oid in resp_data: self.logger.debug("Object {0} found!".format(oid)) find_d.callback(resp_data[oid]) else: self.logger.debug("Object {0} not found!".format(oid)) find_d.callback(None) else: self.logger.debug( "Response code is not 200! respose: {0}".format(resp)) find_d.errback(resp) # put a useful error here! indx.query(json.dumps({"@id": oid})).addCallbacks(find_cb, find_d.errback) return find_d def find_create(self, indx, oid, attrs={}): self.logger.debug("Searching for id: {0} and attrs: {1}".format( oid, attrs)) create_d = Deferred() def find_cb(obj): if obj is None: self.logger.debug( "Object {0} not found! Creating it.".format(oid)) attrs.update({"@id": oid}) self.safe_update(indx, attrs).addCallbacks( lambda x: create_d.callback(attrs), create_d.errback) else: create_d.callback(obj) self.find_by_id(indx, oid).addCallbacks(find_cb, create_d.errback) return create_d def check_all_zero(self, data): for key in data.keys(): if key.endswith('-intraday'): for pt in data[key]["dataset"]: if pt["value"] > 0: return False return True def parse_list(self, vlist): out = [x['@value'] for x in vlist] self.logger.debug("Parsed value list: {0}".format(out)) return out
# set up connection to INDX # password = getpass.getpass() # indx = IndxClient(args['address'], args['box'], args['user'], args['passwd'], "Fitbit Connector") # set up connection to Fitbit consumer_key = "9cc7928d03fa4e1a92eda0d01ede2297" consumer_secret = "340ea36a974e47738a335c0cccfe1fcf" # fitbit = Fitbit(consumer_key, consumer_secret, access_token_key, access_token_secret) fitbit = Fitbit(consumer_key, consumer_secret) if fitbit.token == None: gotourl = fitbit.get_token_url() pin = raw_input("Please input you PIN: ") fitbit.get_token_with_pin(pin) fitbit_min = FitbitIntraDay(fitbit) # def get_fitbit_data(): # from_date = args['from_date'] # to_date = args['to_date'] # print '\nget activities tracker steps:\n' # return fitbit_ts.get_activities_tracker_steps(to_date, from_date) def transform_fitbit_response(response): aggr = response["activities-steps"] if len(aggr) > 1: # add this later print "several days worth of data, only processing the first day" day = datetime.strptime(aggr[0]["dateTime"], "%Y-%m-%d").date() mins_data = response["activities-steps-intraday"]["dataset"]
class FitbitApp(BaseHandler): def __init__(self, server): BaseHandler.__init__(self, server) self.isLeaf = True self.consumer_key = "9cc7928d03fa4e1a92eda0d01ede2297" self.consumer_secret = "340ea36a974e47738a335c0cccfe1fcf" self.fitbit = Fitbit(self.consumer_key, self.consumer_secret) def render(self, request): logging.info("Fitbit App, request args: {0}".format(request.args)) # if "init" in request.args: # self.indx = IndxClient("http://{0}".format(request.args['host'][0]), request.args['box'], request.args['username'], request.args["password"], "FitbitConnector") # print self.indx # logging.info("Fitbit App, connected to the box {0}".format(box)) # self.return_ok(request, data = {"init": "ok"}) if "gotourl" in request.args: gotourl = self.fitbit.get_token_url() logging.info("Fitbit App, the gotourl is {0}".format(gotourl)) self.return_ok(request, data = {"url": gotourl}) elif "pin" in request.args: pin = request.args['pin'][0] logging.info("Fitbit App, the pin is {0}".format(pin)) token = self.fitbit.get_token_with_pin(pin) self.return_ok(request, data = {"token": json.dumps({"token_key": "{0}".format(token.key), "token_secret": "{0}".format(token.secret)})}) elif "token" in request.args: token = json.loads(request.args["token"][0]) self.fitbit = Fitbit(self.consumer_key, self.consumer_secret, token['token_key'], token['token_secret']) self.return_ok(request, data={}) elif "download" in request.args: self.fitbit_min = FitbitIntraDay(self.fitbit) start = None if ("start" in request.args): start = datetime.fromtimestamp(int(request.args["start"][0])/1000) response = self.download_data(start) self.return_ok(request, data = response) else: logging.info("Fitbit App, returning 404") self.return_not_found(request) return NOT_DONE_YET def download_data(self, start): # end time is end of yesterday end = datetime.combine((datetime.now()+timedelta(days=-1)).date(), time(23,59,59)) response = {} if (start == None): d = timedelta(days=0) start = datetime.combine(end.date()+d, time(0,0,0)) response["from_date"] = start.isoformat() steps = self.fitbit_min.get_steps(start, end) calories = self.fitbit_min.get_calories(start, end) distance = self.fitbit_min.get_distance(start, end) floors = self.fitbit_min.get_floors(start, end) elevation = self.fitbit_min.get_elevation(start, end) compact_steps = self.compact_data(steps) compact_calories = self.compact_data(calories) compact_distance = self.compact_data(distance) compact_floors = self.compact_data(floors) compact_elevation = self.compact_data(elevation) observations = self.create_observation_points({"step_count":compact_steps, "calories_burned": compact_calories, "distance": compact_distance, "floors_climbed": compact_floors, "elevation": compact_elevation}) response["up_to_date"] = end.isoformat() response["observations"] = "{0}".format(json.dumps(observations)) return response def compact_data(self, observations): out = {} for day_data in observations: out = dict(out.items() + self.compact_day_data(day_data).items()) return out def compact_day_data(self, observations): out = {} day = None for key in observations.keys(): if (not key.endswith('-intraday')): day = datetime.strptime(observations[key][0]["dateTime"], "%Y-%m-%d").date() for key in observations.keys(): if (key.endswith('-intraday')): for obs in observations[key]["dataset"]: if (obs["value"] != 0): t = datetime.strptime(obs["time"], "%H:%M:%S").time() out[datetime.combine(day, t)] = obs["value"] return out def create_observation_points(self, lists): data_points = {} for key in lists.keys(): lst = lists[key] for d in lst.keys(): if (d in data_points): data_points[d] = dict(data_points[d].items() + {key: lst[d]}.items()) else: data_points[d] = {key: lst[d]} observations = [] for data_point in data_points.items(): obs = self.create_observation_point(data_point[0], data_point[1]) observations.append(obs) return observations def create_observation_point(self, ts, data): obs = {} # obs['@id'] = 'fitbit_{0}'.format(ts.strftime('%Y_%m_%d_%H_%M')) # put the id and type in the js code to transfer less data # obs['@type'] = 'http://purl.org/linked-data/cube#Observation' obs['start'] = ts.isoformat() obs['end'] = (ts+timedelta(seconds=59)).isoformat() # obs['device'] = [ { '@type': 'http://www.w3.org/2001/XMLSchema#string', '@value': 'Fitbit Connector' } ] # put the device in the js code to transfer less data for key in data.keys(): obs[key] = '{0}'.format(data[key]) return obs
# set up connection to INDX # password = getpass.getpass() # indx = IndxClient(args['address'], args['box'], args['user'], args['passwd'], "Fitbit Connector") # set up connection to Fitbit consumer_key = "9cc7928d03fa4e1a92eda0d01ede2297" consumer_secret = "340ea36a974e47738a335c0cccfe1fcf" # fitbit = Fitbit(consumer_key, consumer_secret, access_token_key, access_token_secret) fitbit = Fitbit(consumer_key, consumer_secret) if (fitbit.token == None): gotourl = fitbit.get_token_url() pin = raw_input("Please input you PIN: ") fitbit.get_token_with_pin(pin) fitbit_min = FitbitIntraDay(fitbit) # def get_fitbit_data(): # from_date = args['from_date'] # to_date = args['to_date'] # print '\nget activities tracker steps:\n' # return fitbit_ts.get_activities_tracker_steps(to_date, from_date) def transform_fitbit_response(response): aggr = response['activities-steps'] if (len(aggr) > 1): # add this later print 'several days worth of data, only processing the first day' day = datetime.strptime(aggr[0]['dateTime'], '%Y-%m-%d').date() mins_data = response['activities-steps-intraday']['dataset']