Wednesday 16 November 2016

Raspberry pi + Amazon Dash button

Amazon has launched its Dash button which allows you to order something (e.g. toothpaste) with a single click through Amazon Prime. You can put these buttons anywhere you like (e.g. in the bathroom in case of toothpaste emergency during the upcoming zombie apocalypse). What's great about them is that you can hack them.

First things first, you need to set it up. Follow the instructions, but don't select a product to order at the end. Just exit the app. This will setup the wifi but leave the button unable to order anything.

Next, find out the IP of the button. Mk1 versions of the button used to do an ARP request when switched on, which could be intercepted so tell when it was activated. With Mk2 this has been changed, and it now makes a bootp request to get an IP first. So get to your router and grab that IP. Personnally, I've reserved a small block for these buttons and cut off their internet access (you never know; they have no business chatting outside the house).

Now your button is ready to play with.

You now have 2 options: intercept the bootp from the button (using its MAC address) or just ping it until it replies. Option #1 has the advantage of not generating additional network traffic. However, when you have a slightly weird network that combines wifi, CPL, and ethernet, bootp packets don't always travel well (for example some wifi routers don't like to route bootp to machines behind them, e.g. some old d-link ones). So for me, ping is the best option.

The concept is simple: ping your dash buttons until one answers. When it does, trigger an action. Then disable that button for 10 seconds or so to avoid dupes or double triggers. Loop. I wrote a small PHP script for that (it's rough but trivial):



$lastaction = Array();

function ping($host, $timeout = 1) {
            /* ICMP ping packet with a pre-calculated checksum */
            $package = "\x08\x00\x7d\x4b\x00\x00\x00\x00PingHost";
            $socket  = socket_create(AF_INET, SOCK_RAW, 1);
            socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, array('sec' => $timeout, 'usec' => 0));
            socket_connect($socket, $host, null);

            $ts = microtime(true);
            socket_send($socket, $package, strLen($package), 0);
            if (socket_read($socket, 255))
                    $result = microtime(true) - $ts;
            else    $result = false;

            return $result;

function processbutton($ip) {
        global $buttons;

        $action = $buttons[$ip];
        if ($action!="") {
        } else {
                debug("No action found for ".$ip);

while(true) {
        foreach($buttons as $ip => $action) {
                debug("Testing ".$ip);
                $time = ping($ip);
                if ($time>0) {
                        debug($ip." is up");
                        if (($time>0) && (($lastaction[$ip]+$delay) < time())) {
                                debug("Processing ".$ip);
                                $lastaction[$ip] = time();
                                processbutton($ip, $action);
                        } else {
                                debug("Activity on ".$ip." is too recent");


The config declares the buttons and the action to trigger (in my case hit a page that switches the heating on/off using the radpi).


$buttons = Array(
        "" => "",
        "" => "",

$delay = 10;
$debug = true;


Now run the script as a daemon at startup and you're good to go for as many buttons as you want with the caveat that the more buttons you have, the more of a delay you might have between push and action up to the point actions could be missed. If you have that many buttons you should switch to a parallel pinging of the buttons or fall back on the interception of the bootp packets.

No comments:

Post a Comment