Hacking the Fitbit – Emulating a Pager for Twitter DMs!

Share this article

Hacking the Fitbit – Emulating a Pager for Twitter DMs!

I’ve been trying to wake up earlier in the morning. The trouble is that alarms wake everybody up, not just me. To get around this problem, I recently bought the cheapest Fitbit I could find, having learned that they have a neat silent alarm.

The truth is, if I had the cash I would rather have bought an Apple watch. When I got the Fitbit, my programmer brain immediately jumped to the question; “How can I hack this thing?”

Soldering iron vector image

I ended up learning a bit about Fitbit, OAuth and the Twitter API. I also learned that sometimes it’s better just to get an Apple watch…

The code for this tutorial can be found on Github.

Getting Started

I decided to try using the silent alarms as a notification system. I would check for new Twitter direct messages, and set an alarm as quickly as possible. That way, when I felt a silent alarm I wasn’t expecting, I could check Twitter…

It turns out both Fitbit and Twitter have JSON APIs, and both authenticate with OAuth. I began by creating a new Lumen app which would serve as the task scheduler and communicator for this project:

composer install laravel/lumen .

Lumen is really just a slimmed-down version of the Laravel framework. There are a few things disabled by default, and other things set to “light” alternative implementations. Everything you build in Lumen can be ported to Laravel, so I thought I would try it out and see if it was both fast enough and featured enough to handle the communication.

The communication needed to happen in two steps:

  1. Create links/buttons to connect the app to Twitter and Fitbit
  2. Schedule command-line tasks to check for new direct messages and set alarms.

I began by adding routes for the first step:

$app->get("/", function() {
    return view("dashboard");
});

$app->get("/auth/fitbit", function() {
    // begin auth with fitbit
});

$app->get("/auth/fitbit/callback", function() {
    // store oauth credentials
});

$app->get("/auth/twitter", function() {
    // begin auth with twitter
});

$app->get("/auth/twitter/callback", function() {
    // store oauth credentials
});

This happens in app/Http/routes.php.

I also had to create a dashboard view for these:

<a href="{{ url("auth/fitbit") }}">connect with fitbit</a>
<a href="{{ url("auth/twitter") }}">connect with twitter</a>

This happens in resources/views/dashboard.blade.php.

The artisan serve command has been removed from Lumen, but if you really want it back (like I do), you can install it with:

composer require mlntn/lumen-artisan-serve

You’ll also have to add \Mlntn\Console\Commands\Serve::class to the list of registered console commands, in app/Console/Kernel.php.

Registering Applications

Both Fitbit and Twitter required I register new applications before they would provide the OAuth keys I needed to authenticate.

Fitbit new app screen

I was developing this all locally, using the Artisan server, to get to routes like http://localhost:8080/auth/twitter. The Twitter app registration page wouldn’t allow callback URLs with localhost, IP address, or port numbers.

I was about to set up a redirect from https://assertchris.io/auth/twitter to http://localhost:8080/auth/twitter when I noticed that you can use different callback URLs in Twitter’s interface and the OAuth requests. If you come across this same problem, just use a fake URL (from a real domain) in the Twitter interface, and the local URL when you make the OAuth requests I’m about to show you…

Twitter new app screen

Making OAuth Requests

One of the reasons I chose Lumen was because of Socialite. Socialite abstracts most of the drudgery involved with OAuth and Guzzle abstracts the rest!

I added the public and secret keys for Fitbit and Twitter:

FITBIT_KEY=...
FITBIT_SECRET=...
FITBIT_REDIRECT_URI=...

TWITTER_KEY=...
TWITTER_SECRET=...
TWITTER_REDIRECT_URI=...

This happens in .env.

If I were building this app in Laravel, I would also need to modify app/config/services.php to reference these .env variables.

Then I installed Socialite and the Fitbit and Twitter providers from https://socialiteproviders.github.io:

composer require laravel/socialite
composer require socialiteproviders/fitbit
composer require socialiteproviders/twitter

Part of the Socialite installation instructions recommend adding the Socialite service provider to the list of service providers in config/app.php. The third-party Fitbit and Twitter providers have a different service provider which replaces the official one.

Lumen does away with the config folder, so this service provider needs to be registered in bootstrap/app.php. I also uncommented the EventServiceProvider class:

$app->register(
    App\Providers\EventServiceProvider::class
);

$app->register(
    SocialiteProviders\Manager\ServiceProvider::class
);

This happens in bootstrap/app.php.

Now that the EventServiceProvider class was back in play, I could add the events these OAuth providers required:

namespace App\Providers;

use Laravel\Lumen\Providers\EventServiceProvider as Base;
use SocialiteProviders\Manager\SocialiteWasCalled;
use SocialiteProviders\Fitbit\FitbitExtendSocialite;
use SocialiteProviders\Twitter\TwitterExtendSocialite;

class EventServiceProvider extends Base
{
    protected $listen = [
        SocialiteWasCalled::class => [
            FitbitExtendSocialite::class . "@handle",
            TwitterExtendSocialite::class . "@handle",
        ],
    ];
}

This happens in app/Providers/EventServiceProvider.php.

With all these settings out the way, I could start to connect to Fitbit and Twitter:

use Laravel\Socialite\Contracts\Factory;

$manager = $app->make(Factory::class);

$fitbit = $manager->with("fitbit")->stateless();
$twitter = $manager->with("twitter");

$app->get("/auth/fitbit", function() use ($fitbit) {
    return $fitbit->redirect();
});

$app->get("/auth/fitbit/callback", function() use ($fitbit) {
    // use $fitbit->user()
});

$app->get("/auth/twitter", function() use ($twitter) {
    return $twitter->redirect();
});

$app->get("/auth/twitter/callback", function() use ($twitter) {
    // use $twitter->user()
});

This happens in app/Http/routes.php.

Something else that is disabled by default in Lumen is session management. It makes sense, since Lumen is intended mostly for stateless JSON web services, not actual websites. The trouble is that Socialite uses sessions by default for OAuth 2. I can disable this with the stateless() method…

Each Socialite provider provides a redirect() method, which I can return in a custom route. When users hit that route, they are redirected to the service I want them to authenticate with.

A simple dd($fitbit->user()) and dd($twitter->user()) show me that the details are arriving back on my local server as expected.

Now I can pull direct messages from Twitter:

use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Subscriber\Oauth\Oauth1;
use Illuminate\Support\Facades\Cache;

$app->get("/auth/twitter/callback", function() use ($twitter) {
    $user = $twitter->user();

    Cache::forever("TWITTER_TOKEN", $user->token);
    Cache::forever("TWITTER_TOKEN_SECRET", $user->tokenSecret);

    return redirect("/");
});

This happens in app/Http/routes.php.

Connecting to these services is only the first step. Once they send data back to my application, I need to attach it to future requests. It’s better to fetch the direct messages in another route, so for now I’m just going to store them in cache.

The default cache provider is Memcache. You can install it if it’s missing on your OS, or configure a different cache driver.

I’m also using a Laravel facade to get quick access to the underlying cache classes. These are disabled by default in Lumen, but you can uncomment $app->withFacades(); in boostrap/app.php to enable them again.

Fetching Direct Messages

Next I’ll add a link to the dashboard, and a new route, for fetching direct messages:

<a href="{{ url("twitter/fetch") }}">fetch direct messages</a>

This happens in resources/views/dashboard.blade.php.

$app->get("twitter/fetch", function() {
    $middleware = new Oauth1([
        "consumer_key" => getenv("TWITTER_KEY"),
        "consumer_secret" => getenv("TWITTER_SECRET"),
        "token" => Cache::get("TWITTER_TOKEN"),
        "token_secret" => Cache::get("TWITTER_TOKEN_SECRET"),
    ]);

    $stack = HandlerStack::create();
    $stack->push($middleware);

    $client = new Client([
        "base_uri" => "https://api.twitter.com/1.1/",
        "handler" => $stack,
    ]);

    $response = $client->get("direct_messages.json", [
        "auth" => "oauth",
    ]);

    header("content-type: application/json");
    print $response->getBody();
});

This happens in app/Http/routes.php.

Since I have the token and token secret (sent back to us from Twitter, as part of the OAuth process), we can use HandlerStack and Oauth1 classes to transparently add OAuth credentials to requests I make with Guzzle.

I can modify this slightly, to record current direct messages, so I can know when I’ve received something new:

$latest = Cache::get("TWITTER_LATEST", 0);

$response = $client->get("direct_messages.json", [
    "auth" => "oauth",
    "query" => [
        "since_id" => $latest,
    ],
]);

// header("content-type: application/json");
// print $response->getBody();

$response = (string) $response->getBody();
$response = json_decode($response, true);

foreach ($response as $message) {
    if ($message["id"] > $latest) {
        $latest = $message["id"];
    }
}

Cache::forever("TWITTER_LATEST", $latest);

This happens in app/Http/routes.php.

Twitter lets me filter out all direct messages since a specified direct message ID, so the idea is that I should loop over all direct messages and cache the newest (largest) ID. Then future requests will not include old direct messages and I can set alarms if there are any new messages.

Setting Silent Alarms

I want to store the Fitbit OAuth token details, in the same way I did for Twitter:

$app->get("/auth/fitbit/callback", function() use ($fitbit) {
    $user = $fitbit->user();

    Cache::forever("FITBIT_TOKEN", $user->token);
    Cache::forever("FITBIT_USER_ID", $user->id);

    return redirect("/");
});

This happens in app/Http/routes.php.

Now, assuming I’ve connected to Fitbit and Twitter, I can set alarms when there are new direct messages:

if (count($response)) {
    $token = Cache::get("FITBIT_TOKEN");
    $userId = Cache::get("FITBIT_USER_ID");

    $client = new Client([
        "base_uri" => "https://api.fitbit.com/1/",
    ]);

    $response = $client->get("user/-/devices.json", [
        "headers" => [
            "Authorization" => "Bearer {$token}"
        ]
    ]);

    $response = (string) $response->getBody();
    $response = json_decode($response, true);

    $id = $response[0]["id"];

    $endpoint =
        "user/{$userId}/devices/tracker/{$id}/alarms.json";

    $response = $client->post($endpoint, [
        "headers" => [
            "Authorization" => "Bearer {$token}"
        ],
        "form_params" => [
            "time" => date("H:iP", strtotime("+1 minute")),
            "enabled" => "true",
            "recurring" => "false",
            "weekDays" => [strtoupper(date("l"))],
        ],
    ]);

    header("content-type: application/json");
    print $response->getBody();
}

This happens in app/Http/routes.php.

Notice how I need to use a different kind of authentication in Fitbit? Fitbit uses OAuth 2, and allows Bearer headers for the token. It’s a lot easier than Twitter’s OAuth 1 requirements.

The first request I make is to fetch the devices I have in my Fitbit account. Given a bit more time I could create a list, and some sort of selection so that it’s a bit more dynamic.

Once I have the device ID (Fitbit assigned), I can create a new silent alarm. It requires a special date format, and the day needs to be what ever today is, in upper case. That bit of debug info tells me whether a new alarm has been set or not. If there are no direct messages, I see nothing.

Conclusion

This was a really fun project for me. I got to learn a bit about the constraints placed on new Lumen applications, connecting to OAuth services using Socialite, and how to interact with the Fitbit and Twitter APIs.

My Fitbit synchronizes every 15 minutes (at minimum) as well as every time I open the iPhone app. Perhaps newer Fitbit models sync more frequently, or have some kind of server-initiated push. It’s just a limitation I’m going to have to live with. Or I could buy an Apple Watch.

Frequently Asked Questions (FAQs) about Hacking the Fitbit

How can I emulate a pager for Twitter DMs using Fitbit?

Emulating a pager for Twitter DMs using Fitbit involves a series of steps. First, you need to set up a Twitter application and get your API keys. Then, you need to set up a server to listen for incoming DMs. This server will then send the DMs to your Fitbit device. You can use Node.js and Express to set up the server. Once the server is set up, you can use the Fitbit SDK to create a Fitbit app that will receive the DMs from the server and display them on your Fitbit device.

What are the prerequisites for hacking the Fitbit?

Before you start hacking the Fitbit, you need to have a few things in place. First, you need a Fitbit device that supports the Fitbit SDK. You also need a computer with Node.js and npm installed. Additionally, you need to have a basic understanding of JavaScript and how to use the command line. Lastly, you need a Twitter account and the ability to create a Twitter application.

Why is my Fitbit app crashing?

There could be several reasons why your Fitbit app is crashing. It could be due to a software bug, an issue with your device’s operating system, or a problem with the app itself. Try updating the app, restarting your device, or reinstalling the app to see if that fixes the problem. If the problem persists, you may need to contact Fitbit support for further assistance.

How can I troubleshoot Fitbit app crashes?

If your Fitbit app is crashing, there are a few things you can try. First, make sure you have the latest version of the app. If not, update it. Second, try restarting your device. If the app is still crashing, try uninstalling and reinstalling the app. If none of these steps work, you may need to contact Fitbit support.

How can I check the status of Fitbit?

You can check the status of Fitbit by visiting the Fitbit status page. This page provides real-time information about any issues or outages that Fitbit may be experiencing. You can also check the Fitbit community forums for any reports of issues from other users.

How can I report a problem with my Fitbit app?

If you’re experiencing problems with your Fitbit app, you can report them to Fitbit support. You can do this through the Fitbit app itself, or by visiting the Fitbit support website. Be sure to provide as much detail as possible about the problem you’re experiencing to help the support team diagnose and resolve the issue.

How can I set up a Twitter application for use with Fitbit?

Setting up a Twitter application for use with Fitbit involves a few steps. First, you need to create a new Twitter application through the Twitter Developer portal. Once you’ve created the application, you’ll need to get your API keys, which you’ll use to authenticate your application with Twitter. Then, you’ll need to set up a server to listen for incoming DMs and send them to your Fitbit device.

How can I use the Fitbit SDK to create a Fitbit app?

The Fitbit SDK allows you to create custom apps for Fitbit devices. You can use JavaScript to write your app, and the SDK provides a range of APIs that you can use to interact with the device’s hardware and software. You’ll need to install the Fitbit SDK on your computer, and you’ll also need a Fitbit device that supports the SDK.

Can I use Fitbit to receive notifications from other apps?

Yes, Fitbit devices can receive notifications from other apps. You can set up your Fitbit device to receive notifications from your phone, including calls, texts, and app notifications. You can also create custom apps using the Fitbit SDK that can receive notifications from other services, such as Twitter.

How can I get help with my Fitbit device?

If you need help with your Fitbit device, you can visit the Fitbit support website. There, you’ll find a range of resources, including user guides, troubleshooting tips, and a community forum where you can ask questions and get help from other Fitbit users. You can also contact Fitbit support directly for further assistance.

Christopher PittChristopher Pitt
View Author

Christopher is a writer and coder, working at Over. He usually works on application architecture, though sometimes you'll find him building compilers or robots.

BrunoShackhackinghackslaravellumenoauthoauth2oddballtwitter
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week