Skip to content

Instantly share code, notes, and snippets.

@rduplain
Last active Apr 22, 2020
Embed
What would you like to do?
Amazon Dash→LIFX Power
#!/usr/bin/env python
# Detect Amazon Dash button network broadcast, toggle LIFX power based on MAC.
#
# Usage, with Amazon Dash buttons connected to LAN WiFi (see below for setup):
#
# 0. Configure `BUTTONS` with MAC to LIFX selector & `LIFX` with token.
# 1. Configure network firewall to block WAN out from dash MAC.
# 2. Install dependencies with `pip install requests scapy` on Python 3.
# 3. Run as root.
#
# Alternatively, to run Scapy without root:
#
# setcap cap_net_raw=eip /path/to/python
# setcap cap_net_raw=eip `which tcpdump`
#
# Note that this `setcap` usage opens up raw socket access to all users.
#
# This packet-sniffing program can run anywhere on the LAN from a non-WiFi
# hard-wired host. On button press, the dash button connects to WiFi and issues
# a DHCP request in order to get its IP and DNS settings. This DHCP request
# (with related ARP packets) is broadcast on the network. The following program
# detects the DHCP request by sniffing LAN traffic and dispatches the connected
# Python function.
#
# A simple debounce method ensures that only one dispatch is made within a few
# seconds, to avoid dispatch on multiple DHCP packets for the same button
# press. The connection is passive in that no response is sent to the dash
# button. The observed behavior of the dash button is a blinking white LED that
# eventually times out. While the white LED is lit, additional button presses
# do nothing, as no further DHCP request is made. The button then powers down
# about half a minute later, and a button press during an unlit LED then starts
# the process again, issuing a new DHCP request, to be picked up by this
# packet-sniffing program.
#
# Meaning, after pressing the dash button, wait until the white LED is unlit
# before attempting to press the button again.
#
# Altogether, this process is slow and takes several seconds.
#
# To get an Amazon Dash button onto local WiFI, noting that a button may need
# an attempted setting of amzn_ssid via `curl` (which fails) before it accepts
# the .wav payload:
#
# blog.christophermullins.com/2019/12/20/rescue-your-amazon-dash-buttons/
#
# LIFX personal access token:
#
# cloud.lifx.com/settings
import datetime
import requests
from scapy.all import Ether, sniff
LIFX = '0123456789abcdef01234567890abcdef01234567890abcdef01234567890abc'
BUTTONS = {
'01:23:45:67:89:AB': 'd0123456789a',
'12:34:56:78:9A:BC': 'group_id:0123456789abcdef0123456789abcdef',
}
LAST_SEEN = {}
DEBOUNCE_SECONDS = 25
def toggle(selector):
requests.post(
f'https://api.lifx.com/v1/lights/{selector}/toggle',
headers={'Authorization': f'Bearer {LIFX}'})
def detect_button(pkt):
MAC = pkt[Ether].src.upper()
if MAC in BUTTONS:
now = datetime.datetime.now()
timestamp = int(now.timestamp())
if timestamp > LAST_SEEN.get(MAC, 0) + DEBOUNCE_SECONDS:
LAST_SEEN[MAC] = timestamp
print(now.strftime(f'%Y-%m-%d %H:%M:%S - {MAC}'))
toggle(BUTTONS[MAC])
def main():
# Performance Note:
#
# Filter on ARP, not DHCP, as Scapy provides a simple means to specify
# `filter='arp'`. Otherwise, filtering on DHCP requires filter syntax with
# UDP packets and additional processing to detect DHCP layer.
sniff(prn=detect_button, filter='arp', store=0)
if __name__ == '__main__':
main()
@rduplain
Copy link
Author

rduplain commented Apr 22, 2020

Tested with:

  • Raspberry Pi 3 with fully updated Raspian "Buster" OS as of 2020-04-18.
  • Python 3.7.3, Scapy 2.4.3, Requests 2.23.0.
  • Amazon Dash buttons model JK29LP.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment