After quite a bit of iteration, I’m mostly happy with the way I’ve integrated Dash buttons into my home automation setup. Here’s a demo:

My goals were:

Make the buttons as responsive as possible. Make it robust. Setup should survive reboots and power outages without manual intervention. Integrate with SmartThings.

In a previous post, I outlined two different approaches. I went with the approach that had the lowest latency (<1s). This one is quite a bit more work — mostly because it requires a dedicated wireless card.

Here’s the equipment I used:

Raspberry Pi 2 (I used the CanaKit starter kit. If you’re buying now, you’d probably want the Pi 3 edition). Edimax EW-7811Un USB WiFi dongle.

Important! to use this approach, you need at least one WiFi dongle that supports monitor mode. The Edimax dongle I suggested doesn’t support monitor mode, but the one that comes with the CanaKit 2 does. Note that the Pi 3’s onboard WiFi device does not support monitor mode, so you’ll want to buy a dongle that does (you can buy the CanaKit dongle separately for $9).

Set up dash buttons

This approach will work with the normal setup process, but with a slight modification, you can ensure that the dash buttons don’t contact Amazon when pressed.

The only thing you need to do differently is set up the buttons on a network you can delete later. I have dd-wrt on my router, so I used a virtual interface. If your router supports a “guest network” or something to that effect, it’s the same thing.

Create the network, set up the dash buttons on it, delete the network. The buttons will still attempt to connect when pressed, but won’t be able to because it doesn’t exist.

Install required packages

sudo apt-get install iw tcpdump libpcap-dev wpasupplicant ifrename 1 sudo apt-get install iw tcpdump libpcap-dev wpasupplicant ifrename

Set up the network

If you’re using ethernet + a WiFi dongle, you shouldn’t need to do much of anything. If you’re using two WiFi devices, it’s a little trickier. In order for this to work consistently across reboots, you’ll have to:

Make sure that the interfaces (wlan0, etc.) are named consistently. They seemed to randomly swap by default, which obviously caused some problems. Tell the OS which device should be connecting to the network.

(1) is easy enough with ifrename. There’s probably a way to do it with udev, but this is way easier. It allows you to assign names to interfaces based on hardware (MAC) addresses. Open up /etc/iftab in your favorite editor (just create it if it doesn’t exist). Mine looks like this:

monitorwan mac 00:00:00:00:00:00 mainwan mac 00:00:00:00:00:01 1 2 monitorwan mac 00:00:00:00:00:00 mainwan mac 00:00:00:00:00:01

After a reboot, you should see that the devices are named appropriately:

$ ifconfig | grep wan mainwan Link encap:Ethernet HWaddr 00:00:00:00:00:01 monitorwan Link encap:Ethernet HWaddr 00:00:00:00:00:00 1 2 3 $ ifconfig | grep wan mainwan Link encap:Ethernet HWaddr 00:00:00:00:00:01 monitorwan Link encap:Ethernet HWaddr 00:00:00:00:00:00

Notice you can name the interfaces whatever you want. monitorwan and mainwan seemed more informative than wlan0 and wlan1. 🙂

(2) is also pretty straightforward. There might be an easier way to do this, but I just did it by editing /etc/network/interfaces to my liking:

auto lo iface lo inet loopback auto eth0 allow-hotplug eth0 iface eth0 inet manual allow-hotplug monitorwan auto monitorwan allow-hotplug mainwan auto mainwan iface mainwan inet dhcp wpa-ssid "MyNetworkName" wpa-psk 7d3102a8c6159bb6486d54951805a719e8b4a2878a8015b1942a5097a5f85195 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 auto lo iface lo inet loopback auto eth0 allow-hotplug eth0 iface eth0 inet manual allow-hotplug monitorwan auto monitorwan allow-hotplug mainwan auto mainwan iface mainwan inet dhcp wpa-ssid "MyNetworkName" wpa-psk 7d3102a8c6159bb6486d54951805a719e8b4a2878a8015b1942a5097a5f85195

The wpa-psk field is a pre-shared key generated from your network SSID and passphrase. You can generate it with the wpa_passphrase tool (from the wpasupplicant package):

$ wpa_passphrase MyNetworkName mysuperdupergoodpassword network={ ssid="MyNetworkName" #psk="mysuperdupergoodpassword" psk=7d3102a8c6159bb6486d54951805a719e8b4a2878a8015b1942a5097a5f85195 } 1 2 3 4 5 6 $ wpa_passphrase MyNetworkName mysuperdupergoodpassword network={ ssid="MyNetworkName" #psk="mysuperdupergoodpassword" psk=7d3102a8c6159bb6486d54951805a719e8b4a2878a8015b1942a5097a5f85195 }

You can apply these settings with a sudo service networking restart . Probably good to reboot to make sure it works as expected.

Download ha_gateway

This setup uses ha_gateway, which is a small REST gateway I use to bridge a bunch of custom hackery with the rest of my home automation setup (mostly SmartThings). To install it, just check out the project from Github:

sudo mkdir -p /apps && \ sudo chown $USER /apps && \ cd /apps && \ git clone https://github.com/sidoh/ha_gateway.git && \ cd /apps/ha_gateway && \ bundle install 1 2 3 4 5 6 sudo mkdir -p /apps && \ sudo chown $USER /apps && \ cd /apps && \ git clone https://github.com/sidoh/ha_gateway.git && \ cd /apps/ha_gateway && \ bundle install

While I haven’t tested ha_gateway with anything but ruby 2.3.1, it probably works with 1.9+. If you’re getting errors when running bundle install , post a comment and I’ll help debug.

Create the monitor interface

In order to use monitor mode, we create a virtual monitor interface. We can do this with the iw tool, but I stuffed all of the setup into a script shipped with ha_gateway. It takes two arguments: the interface you’re using for monitor mode, and what you want to name the virtual interface

sudo /apps/ha_gateway/bin/create_monitor_interface monitorwan DashMonitor 1 sudo /apps/ha_gateway/bin/create_monitor_interface monitorwan DashMonitor

This should create an interface called DashMonitor :

$ ifconfig DashMonitor DashMonitor Link encap:UNSPEC HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:3219880 errors:0 dropped:5137 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:571027641 (544.5 MiB) TX bytes:0 (0.0 B) 1 2 3 4 5 6 7 $ ifconfig DashMonitor DashMonitor Link encap:UNSPEC HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:3219880 errors:0 dropped:5137 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:571027641 (544.5 MiB) TX bytes:0 (0.0 B)

To test if it’s working, you can try a tcpdump:

$ sudo tcpdump -i DashMonitor -c 50 tcpdump: WARNING: DashMonitor: no IPv4 address assigned tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on DashMonitor, link-type IEEE802_11_RADIO (802.11 plus radiotap header), capture size 65535 bytes 15:46:08.905143 2412 MHz 11g -17dB signal antenna 1 39.0 Mb/s MCS 4 20 MHz lon GI CF +QoS Data IV:1319 Pad 20 KeyID 0 15:46:08.905424 24.0 Mb/s 2412 MHz 11g -33dB signal antenna 1 Acknowledgment RA:00:00:00:00:00:00 (oui Unknown) 15:46:08.905540 24.0 Mb/s 2412 MHz 11g -17dB signal antenna 1 15:46:08.905767 24.0 Mb/s 2412 MHz 11g -33dB signal antenna 1 Acknowledgment RA:00:00:00:00:00:00 (oui Unknown) 15:46:08.905909 2412 MHz 11g -17dB signal antenna 1 39.0 Mb/s MCS 4 20 MHz lon GI CF +QoS Data IV:131a Pad 20 KeyID 0 5 packets captured 50 packets received by filter 14 packets dropped by kernel 1 2 3 4 5 6 7 8 9 10 11 12 $ sudo tcpdump -i DashMonitor -c 50 tcpdump: WARNING: DashMonitor: no IPv4 address assigned tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on DashMonitor, link-type IEEE802_11_RADIO (802.11 plus radiotap header), capture size 65535 bytes 15:46:08.905143 2412 MHz 11g -17dB signal antenna 1 39.0 Mb/s MCS 4 20 MHz lon GI CF +QoS Data IV:1319 Pad 20 KeyID 0 15:46:08.905424 24.0 Mb/s 2412 MHz 11g -33dB signal antenna 1 Acknowledgment RA:00:00:00:00:00:00 (oui Unknown) 15:46:08.905540 24.0 Mb/s 2412 MHz 11g -17dB signal antenna 1 15:46:08.905767 24.0 Mb/s 2412 MHz 11g -33dB signal antenna 1 Acknowledgment RA:00:00:00:00:00:00 (oui Unknown) 15:46:08.905909 2412 MHz 11g -17dB signal antenna 1 39.0 Mb/s MCS 4 20 MHz lon GI CF +QoS Data IV:131a Pad 20 KeyID 0 5 packets captured 50 packets received by filter 14 packets dropped by kernel

If you have basically any WiFi traffic around you, you should see packets pretty much immediately. If you don’t, it either means the monitor device isn’t working, or you’re legitimately not seeing traffic on whatever channel the NIC is tuned to.

To make sure the monitor device survives reboots, you can invoke the same script from /etc/rc.local :

#!/bin/sh -e # # rc.local # # This script is executed at the end of each multiuser runlevel. # Make sure that the script will "exit 0" on success or any other # value on error. # # In order to enable or disable this script just change the execution # bits. # # By default this script does nothing. # Print the IP address _IP=$(hostname -I) || true if [ "$_IP" ]; then printf "My IP address is %s

" "$_IP" fi /apps/ha_gateway/bin/create_monitor_interface monitorwan DashMonitor || echo "Failed to create monitor interface" exit 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #!/bin/sh -e # # rc.local # # This script is executed at the end of each multiuser runlevel. # Make sure that the script will "exit 0" on success or any other # value on error. # # In order to enable or disable this script just change the execution # bits. # # By default this script does nothing. # Print the IP address _IP = $ ( hostname - I ) || true if [ "$_IP" ] ; then printf "My IP address is %s

" "$_IP" fi / apps / ha_gateway / bin / create_monitor_interface monitorwan DashMonitor || echo "Failed to create monitor interface" exit 0

Figure out MAC addresses of your dash button(s)

The easiest way I’ve found to do this is to use the monitor mode NIC and search for packets associated with the network you set them up on. I set my dash buttons up on a network called CMDashButton:

$ sudo tcpdump -i DashMonitor -e | grep CMDashButton tcpdump: WARNING: DashMonitor: no IPv4 address assigned tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on DashMonitor, link-type IEEE802_11_RADIO (802.11 plus radiotap header), capture size 65535 bytes 16:24:28.680003 1.0 Mb/s 2412 MHz 11b -43dB signal antenna 1 BSSID:Broadcast DA:Broadcast SA:00:00:00:00:00:00 (oui Unknown) Probe Request (CMDashButton) [1.0* 2.0* 5.5* 6.0* 11.0* 12.0* 24.0* 9.0 Mbit] [...clipped...] 2475 packets captured 2475 packets received by filter 0 packets dropped by kernel 1 2 3 4 5 6 7 8 9 $ sudo tcpdump -i DashMonitor -e | grep CMDashButton tcpdump: WARNING: DashMonitor: no IPv4 address assigned tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on DashMonitor, link-type IEEE802_11_RADIO (802.11 plus radiotap header), capture size 65535 bytes 16:24:28.680003 1.0 Mb/s 2412 MHz 11b -43dB signal antenna 1 BSSID:Broadcast DA:Broadcast SA:00:00:00:00:00:00 (oui Unknown) Probe Request (CMDashButton) [1.0* 2.0* 5.5* 6.0* 11.0* 12.0* 24.0* 9.0 Mbit] [...clipped...] 2475 packets captured 2475 packets received by filter 0 packets dropped by kernel

The hardware address is shown after “SA:” prefix.

Configure ha_gateway

ha_gateway is configured using a central YAML config file. You can just copy from the example:

cd /apps/ha_gateway cp config/ha_gateway.yml.example config/ha_gateway.yml 1 2 cd /apps/ha_gateway cp config/ha_gateway.yml.example config/ha_gateway.yml

Ignore the stuff at the beginning and skip down to the listeners: key. You’ll create a listener for each dash button you want to use:

listeners: clorox_wipes_button: driver: tcpdump_monitor params: # Put the MAC address of your button here hw_addr: 00:00:00:00:00:00 # Change this if you named your monitor interface something different. interface: DashMonitor # There will always be 5-10 packets each time you press the button. # This prevents actions from repeating more than once every 5000ms # (5 seconds). dedup_threshold: 5000 events: probe_received: http: method: PUT url: http://google.com/some/path params: some_param1: 'some_value' some_param2: 'some_other_value' 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 listeners : clorox_wipes_button : driver : tcpdump_monitor params : # Put the MAC address of your button here hw_addr : 00 :00 :00 :00 :00 :00 # Change this if you named your monitor interface something different. interface : DashMonitor # There will always be 5-10 packets each time you press the button. # This prevents actions from repeating more than once every 5000ms # (5 seconds). dedup_threshold : 5000 events : probe_received : http : method : PUT url : http ://google.com/some/path params : some_param1 : 'some_value' some_param2 : 'some_other_value'

This will fire an HTTP PUT request to http://google.com/some/path with the specified params every time the button is pressed. We can worry about making it do something useful later. First, let’s verify the button presses are getting picked up.

Use the run_listeners.sh script to fire up the ha_gateway listener process. Note you’ll have to run it with sudo — it won’t be able to listen on the monitor interface otherwise:

cd /apps/ha_gateway sudo bin/run_listeners.sh 1 2 cd /apps/ha_gateway sudo bin/run_listeners.sh

After waiting 10-20 seconds, press your dash button. You should see a log message that looks like this:

D, [2016-12-04T16:51:45.906202 #4177] DEBUG -- : Read line from tcpdump: 16:51:45.842083 1.0 Mb/s 2412 MHz 11b -45dB signal antenna 1 Probe Request (CMDashButton) [1.0* 2.0* 5.5* 6.0* 11.0* 12.0* 24.0* 9.0 Mbit] 1 D, [2016-12-04T16:51:45.906202 #4177] DEBUG -- : Read line from tcpdump: 16:51:45.842083 1.0 Mb/s 2412 MHz 11b -45dB signal antenna 1 Probe Request (CMDashButton) [1.0* 2.0* 5.5* 6.0* 11.0* 12.0* 24.0* 9.0 Mbit]

This means ha_gateway is successfully detecting dash button presses! Now let’s make it do something useful.

Integrating with Smart Things

ha_gateway integrates with SmartThings. We’ll be able to control your existing ST devices and routines with the dash button. Getting this working is a little complicated because SmartThings requires clients to oauth with it. Let’s get that out of the way first.

First, you’ll have to install ha_gateway’s SmartApp. Log into your ST account (https://graph.api.smartthings.com/) and click on “My Smart Apps”. Click on the green “New SmartApp” button on the right near the top. Click on the “From Code” tab and paste in this code:

This should take you to an editor page. Couple of things to do to finalize setup:

Publish the newly created app – click on “Publish”, then “For Me” Click on the “App Settings” button, then click on the “OAuth” section. Click on the “Enable OAuth for this SmartApp” button. You should see two text fields containing a “Client ID” and a “Client Secret”. Make note of ’em. Click on “Update” near the bottom. OAuth settings won’t persist if you skip this!

Copy the client ID and client secret into ha_gateway’s config YAML:

require_hmac_signatures: false smartthings: client_id: 00000000-0000-0000-0000-000000000000 client_secret: 00000000-0000-0000-0000-000000000000 site_location: 'http://ip-of-your-pi:8000' 1 2 3 4 5 6 7 require_hmac_signatures: false smartthings: client_id: 00000000-0000-0000-0000-000000000000 client_secret: 00000000-0000-0000-0000-000000000000 site_location: 'http://ip-of-your-pi:8000'

Setting site_location: is important so that the OAuth redirect ends up hitting the Pi again. For now, also make sure that require_hmac_signatures: is set to false. It’ll make it easier to go through the OAuth process.

Now fire up the ha_gateway web server by running bin/run.sh . Now navigate to:

http://ip-of-your-pi:8000/smartthings/authorize

This should direct you to an OAuth page on ST’s site. Select a hub, check the switches you want to allow control of, and click “Authorize”. You’ll be redirected to and endpoint that outputs a JSON blob containing information about the devices you authorized, which might look something like this:

{"00000000-0000-0000-0000-000000000000":{"name":"Xmas Tree","status":"on"}} 1 {"00000000-0000-0000-0000-000000000000":{"name":"Xmas Tree","status":"on"}}

You can control each device via RESTful PUT requests. For example:

$ curl -X PUT -d'command=toggle' http://ip-of-your-pi:8000/smartthings/switches/00000000-0000-0000-0000-000000000000 1 $ curl -X PUT -d'command=toggle' http://ip-of-your-pi:8000/smartthings/switches/00000000-0000-0000-0000-000000000000

This would send the “toggle” command to “Xmas Tree”, which would turn it off since its previous status was on.

If you wanted to configure a dash button to switch on and off your Christmas Tree, you’d edit the listener config like so:

clorox_wipes_button: driver: tcpdump_monitor params: # Put the MAC address of your button here hw_addr: 00:00:00:00:00:00 # Change this if you named your monitor interface something different. interface: DashMonitor # There will always be 5-10 packets each time you press the button. # This prevents actions from repeating more than once every 5000ms # (5 seconds). dedup_threshold: 5000 events: probe_received: http: method: PUT url: '/smartthings/switches/00000000-0000-0000-0000-000000000000' params: command: toggle 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 clorox_wipes_button : driver : tcpdump_monitor params : # Put the MAC address of your button here hw_addr : 00 :00 :00 :00 :00 :00 # Change this if you named your monitor interface something different. interface : DashMonitor # There will always be 5-10 packets each time you press the button. # This prevents actions from repeating more than once every 5000ms # (5 seconds). dedup_threshold : 5000 events : probe_received : http : method : PUT url : '/smartthings/switches/00000000-0000-0000-0000-000000000000' params : command : toggle

Notice we don’t need to provide the full URL, just the path. ha_gateway will assume we want to send the request to its REST server. It’ll fill in the URL specified in the site_location: key.

You can start both the REST server and the listener process with the included start script. It’ll run the listener process as root, so make sure you’ve got an active sudo session (i.e., make sure it’s not prompting for a password):

sudo echo hi && \ cd /apps/ha_gateway && \ bin/start 1 2 3 sudo echo hi && \ cd / apps / ha_gateway && \ bin / start

Logs are in logs/ha_gateway.log and logs/listeners.log .

You can also run routines. You can access /smartthings/routines to get a list of routines. To run a routine, send a GET request to /smartthings/routines/<routine_name>. Normalize routine_name to be all lowercase, remove non-alphanumeric characters, and replace spaces with underscores (e.g., “Good Night!” -> “good_night”).

Starting ha_gateway at boot

Obviously we want the REST server and the listener process to survive a reboot. This is pretty easy. I use monit because I already had it set up, but it’s probably more straightforward to just add this line to /etc/rc.local :

/apps/ha_gateway/bin/start || echo 'failed to start ha_gateway' 1 /apps/ha_gateway/bin/start || echo 'failed to start ha_gateway'

Make sure it appears above the exit 0 at the end of the script.

Securing it

If you don’t mind anyone on your network being able to access ha_gateway (and therefore turn off your Christmas Cheer), you can enable HMAC signatures. This will require anyone making a request to sign the request with a shared secret. Just edit the config file:

require_hmac_signatures: true hmac_secret: <some random secret> 1 2 require_hmac_signatures: true hmac_secret: <some random secret>

Conclusions

This works really well for me. It was way more work than I expected when I decided to look into hacking the dash buttons. I have five dash buttons for various uses, and they work very reliably. Adding new buttons is really straightforward.