A Fitbit Low Battery SMS Notification using AWS – Part 3

In Part 1, I introduced an app that sends an SMS notification when your Fitbit battery is low. Part 2 covered the Authentication Flow of the process. This post describes how the device status is checked and communicated to the user.

Fitbit-SMS-Sched2The flow is as follows:

  1. The CloudWatch Scheduler kicks off a Lambda job that pulls registered users from the DynamoDB table. This information is based to a SNS topic, which in turn…
  2. …kicks off the Lambda function that obtains the Fitbit device info. The Fitbit api is called, and the resulting message is processed by the Lambda function. The device information is stored in and retrieved from the DynamoDB table as needed for processing and history tracking.
  3. If the result was a success then the a message is sent to the SNS topic for the subscribed user – assuming that the battery status is low AND the user has not already been notified.
  4. If a “Token Expired” error is returned, then the Lambda function passes the required information to the Refresh User SNS topic. The Lambda function again calls a Fitbit API; this time to refresh the token.
  5. If the token is refreshed, then the information is saved to the DynamoDB table and passed to the Get Device Info Topic (starting the process all over again)
  6. If a failure occurs, then a message is sent to the user, directing them to re-authorize the application to access their Fitbit account.

By biggest challenge with this flow was determining the best method to pass and provide information to the lambda functions. In some places, I pass all the information in the sns message. In other cases, I only pass the item key, and retrieve the information in the lambda function (via a DynamoDB call).

My second challenge was with the CloudWatch scheduling component. I attempted to use Cron to limit the times that the job would run (i.e. – I didn’t want to send a notification at midnight). Ultimately, I just decided to skip using Cron, and schedule the job to run every 3 hours.

If I were to enhance the application, I’d do the following

  • Allow the notification time to be specific to each user, based on their preferences and timezone
  • Check to see if the user’s Fitbit was in sleep mode before sending a message

Code to retrieve the device info is below:


var aws = require('aws-sdk');  
var ddb = new aws.DynamoDB();
var sns = new aws.SNS();
var sns_message = '';

exports.handler = function(event, context) {
    
    var obj = JSON.parse(event.Records[0].Sns.Message);
        
    var access_token = obj.access_token;
    var token_type = obj.token_type;
    var refresh_token = obj.refresh_token;
    var mobile = obj.mobile;
    var topic_arn = obj.topic_arn;
    var previous_device_details = obj.device_details;
    var active = obj.active;
    

    var table ='Fitbit_Authorization';

    var https = require('https');
    var options = {
        host :  'api.fitbit.com',
        port : 443,
        path : '/1/user/-/devices.json',
        method : 'Post',
        headers: {'Authorization' : token_type + ' ' + access_token}
    };
 
    //making the https get call
    var getReq = https.request(options, function(res) {
        console.log("\nstatus code: ", res.statusCode);
        res.on('data', function(data) {

            var json_results = JSON.parse(data);

            if (json_results.success===false){
            //   if (json_results.success!==false){ 
                switch(res.statusCode){
                    
                    case 401:
                        //need to call auth
                   
                        sns.publish({
                            Message: mobile,
                            Subject: 'Refresh Token',
                            TopicArn: 'arn:aws:sns:us-east-1:039057814095:Refresh_Fitbit_Token'}, function(err, data) {
                            if (err) {console.log(err.stack);
                            }
                            else{
                               context.done(null, "there was a error - need to refresh the token");
                           }
                        });
                        break;
                    case 403:
                        context.done(null, "there was a error - user does not permit access");
                        break;
                    default:
                        context.done(null, "there was a error - not sure of the error");
                        break;
                }
                

                
            }
            else
            {
                
                var device_results = [];
                

                for (var i = 0; i <= json_results.length-1; i++) {
                
                    if (json_results[i].battery != 'Empty') {
                        console.log('device: ' + json_results[i].deviceVersion);
                    
                    device_results.push({ 
                        "device" : json_results[i].deviceVersion,
                        "battery"  : json_results[i].battery,
                        "datetime_checked" : Date.now() 
                    });
                    
                    
                    if (active == '0'){
                        
                                sns_message = "Your " + json_results[i].deviceVersion + " battery is " + json_results[i].battery + ". You will be notified when your battery is low.";
                                sns.publish({
                                    Message: sns_message,
                                    Subject: sns_message,
                                    TopicArn: topic_arn}, function(err, data) {
                                    if (err) {console.log(err.stack);
                                    }
                                    else{
                                        console.log("SNS:" + sns_message)
                                   }
                                });
                        
                    }
                    else{
                        
                        
                        var previous_device_details_array = eval(previous_device_details);
                        
                        for(var j = 0; j < previous_device_details_array.length; j++)

                            if(previous_device_details_array[j].device == json_results[i].deviceVersion)

                                if (previous_device_details_array[j].battery != json_results[i].battery && json_results[i].battery == 'Low'){
                                    sns_message = "Your " + json_results[i].deviceVersion + " battery is " + json_results[i].battery + ". Charge your battery as soon as possible.";
                                    console.log("here 4");
                                    sns.publish({
                                        Message: sns_message,
                                        Subject: sns_message,
                                        TopicArn: topic_arn}, function(err, data) {
                                        if (err) {console.log(err.stack);
                                        }
                                        else{
                                            console.log("SNS:" + sns_message);
                                       }
                                    });
                                } 
                            }   

                        }   

                    }

                }

            }
            
            var PutItem_params = {
                        Key: {
                             Mobile: {S: mobile}
                            },
                        AttributeUpdates: {
                            Device_Details: {
                                Action: 'PUT',
                                Value: {S: JSON.stringify(device_results)}
                                },
                            Active: {
                                Action: 'PUT',
                                Value: {S: '1'}
                                }
                            },
                        TableName: table
                    };

                            
                ddb.updateItem(PutItem_params, function(err, data) {
                    if (err){ console.log(err); console.log(item);}
                    else {
                        console.log('new array: ' + JSON.stringify(device_results));
                        context.done(null, JSON.stringify(device_results));
                    }
                });
                
                
            }
            
            
            
        });
    });
 
    //end the request
    getReq.end();
    getReq.on('error', function(err){
        console.log("Error: ", err);
    }); 
    
    

};

Related Posts

Leave a Reply