├── .gitignore ├── LICENSE ├── README.md ├── dashiot └── __init__.py ├── observer ├── README.md ├── discover.py └── listen.py ├── openwrt ├── README.md └── listen.py ├── rpi ├── README.md └── hostapd ├── test_http.py ├── test_mqtt.py └── test_view_mqtt.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject 90 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Amazon Dash Button Fun 2 | The aim of this project is to make the Amazon Dash button useful (sorry Amazon, but now it is quite useless). To accomplish that, we developed a simple IoT framework that can connect the button to any HTTP or MQTT server. 3 | 4 | ## About 5 | This repository provides two methods that can be used in three different ways to use any Amazon Dash button for your own purpose. 6 | 7 | * Quick Hack: Observing the traffic 8 | * Dash Endpoint 9 | * On an openWRT router 10 | * Using a Raspberry Pi 11 | 12 | Furthermore, the project provides a simple Python IoT framework, that allows to publish button presses as HTTP or MQTT messages. It supports all three ways of observing button presses. 13 | 14 | ## Listening to the Button 15 | 16 | For a quick test, you can try the [traffic observer](observer). This quick hack monitors the network traffic for the dash button's DHCP request. 17 | If you want to use the button for a real project, I suggest either setting up an [openWRT router](openwrt) or a [Raspberry Pi](rpi) as an endpoint. 18 | (Either click on the links or checkout the README.md in the respective folder) 19 | 20 | Both methods provide a Python script (*listen.py*) that allows to test the setup. It can also be used as base for own projecs. 21 | 22 | ## IoT Framework 23 | 24 | We provide a simple IoT framework that builds upon the *listen.py* scripts. The framework provides an abstraction from the used method and can be used with both methods. 25 | 26 | With the framework, a handler can be installed for each buttons. Whenever the button is pressed, either a MQTT or a HTTP message is published. The code is as simple as 27 | ```python 28 | from dashiot import DashIoT 29 | 30 | # Create an MQTT publisher that uses the network observer for detecting button presses 31 | mqtt = DashIoT(DashIoT.Connector.OBSERVER, DashIoT.Protocol.MQTT) 32 | 33 | # If button with mac address "ac63be5aea19" is pressed, send the mac address under the topic "dash/button1" 34 | mqtt.publish_handler("ac63be5aea19", lambda x: ("dash/button1", x)) 35 | 36 | # MQTT server that accepts our messages 37 | mqtt.connect("iot.eclipse.org", 1883) 38 | 39 | # Publish all detected button presses forever 40 | mqtt.run() 41 | ``` 42 | 43 | The `DashIoT` constructor takes the connector (either `DashIoT.Connector.OBSERVER` for the traffic observer or `DashIoT.Connector.SERVER` for the endpoint) and the protocol (either `DashIoT.Protocol.MQTT` or `DashIoT.Protocol.HTTP`). 44 | ### MQTT 45 | If MQTT is used, the Python package `paho-mqtt` has to be installed (e.g. using `sudo pip install paho-mqtt`). The sample (*test_mqtt.py*) uses Eclipse's public MQTT server ([iot.eclipse.org](http://iot.eclipse.org)). However, any other MQTT server can be used. 46 | 47 | For every button, a handler has to be configured using `publish_handler`. The first argument is the button's MAC address, the second parameter is a callback function that receives the MAC address and returns a tuple of topic and message. 48 | 49 | The script *test_view_mqtt.py* is a simple MQTT subscriber that displays the published messages. 50 | 51 | 52 | ### HTTP 53 | If HTTP is used, the framework issues an HTTP GET request to the given URL. As with the MQTT handler, the `publish_handler` function takes the MAC address of the button as the first parameter and a callback funtion as the second parameter. The callback function returns a tuple of GET parameter name and GET parameter value, e.g. if the GET request should be "?msg=data", the callback must return `("msg", "data")`. 54 | 55 | A simple HTTP test server can be started using `sudo python -m SimpleHTTPServer 80`. -------------------------------------------------------------------------------- /dashiot/__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import paho.mqtt.client as mqtt 3 | try: 4 | import urllib2 5 | except: 6 | pass 7 | try: 8 | import urllib.request 9 | except: 10 | pass 11 | 12 | class DashIoTConnector: 13 | OBSERVER = 1 14 | SERVER = 2 15 | 16 | class DashIoTProtocol: 17 | HTTP = 1 18 | MQTT = 2 19 | 20 | class DashIoT(object): 21 | Connector = DashIoTConnector() 22 | Protocol = DashIoTProtocol() 23 | 24 | def __init__(self, connector, protocol): 25 | if connector == self.Connector.OBSERVER: 26 | sys.path.insert(0, 'observer') 27 | elif connector == self.Connector.SERVER: 28 | sys.path.insert(0, 'openwrt') 29 | else: 30 | print("Error! Unknown connector") 31 | return 32 | 33 | if protocol == self.Protocol.MQTT: 34 | self.client = mqtt.Client() 35 | 36 | self.prot = protocol 37 | self.callbacks = {} 38 | 39 | 40 | def publish_handler(self, mac, handler): 41 | self.callbacks[mac.replace(":", "")] = handler 42 | 43 | 44 | def callback(self, mac): 45 | ret = self.callbacks[mac.replace(":", "")](mac) 46 | if isinstance(ret, tuple): 47 | topic = ret[0] 48 | msg = ret[1] 49 | else: 50 | topic = "dash" 51 | msg = ret 52 | 53 | if self.prot == self.Protocol.MQTT: 54 | self.client.publish(topic, msg, 1) 55 | 56 | if self.prot == self.Protocol.HTTP: 57 | try: 58 | urllib2.urlopen(self.host + "?" + topic + "=" + msg).read() 59 | except: 60 | pass 61 | try: 62 | urllib.request.urlopen(self.host + "?" + topic + "=" + msg).read() 63 | except: 64 | pass 65 | 66 | 67 | def connect(self, host, port = 1883): 68 | if self.prot == self.Protocol.MQTT: 69 | self.client.connect(host, port, 60) 70 | elif self.prot == self.Protocol.HTTP: 71 | self.host = host 72 | 73 | 74 | def run(self): 75 | import listen 76 | 77 | for cb in self.callbacks.keys(): 78 | listen.add_handler(cb, self.callback) 79 | listen.listen() 80 | 81 | -------------------------------------------------------------------------------- /observer/README.md: -------------------------------------------------------------------------------- 1 | # Amazon Dash Button Fun 2 | 3 | ## Quick Hack: Observing Traffic 4 | 5 | Observing the traffic is the easiest way to play with the dash button. 6 | 7 | When pressing the Dash button, it wakes up and connects to the WiFi. To start the communication with Amazon, it has to first get an IP address from the router using the DHCP protocol. We know that the button was pressed if we see such a packet in the observed traffic that has the dash button's MAC address as sender. 8 | 9 | **Advantages** 10 | 11 | + easy 12 | + no hardware required 13 | 14 | **Disadvantages** 15 | 16 | + has to observe the network traffic 17 | + still connects to Amazon 18 | + annoying notifications on the phone 19 | 20 | ## Configuration 21 | 22 | 1. You just have to activate the Dash button using the Amazon app. You can abort the activation at the point where you have to select a product. 23 | 24 | ## Getting the MAC address 25 | 26 | Run `discover.py` and follow the instructions. 27 | 28 | ## Listening for button presses 29 | 30 | Set the `BUTTON` variable at the top of the `listen.py` file to the button's MAC address. Run `listen.py` as root. 31 | 32 | Everytime the button is pressed, it's MAC address is displayed. 33 | -------------------------------------------------------------------------------- /observer/discover.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import struct 3 | import binascii 4 | import time 5 | 6 | runs = 3 7 | tolerance = 0 8 | 9 | filtered = [] 10 | rawSocket = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(0x0003)) 11 | 12 | while len(filtered) != 1: 13 | print("[!] Prepare to press the button in 3 seconds") 14 | time.sleep(1) 15 | print("[!] 2...") 16 | time.sleep(1) 17 | print("[!] 1...") 18 | time.sleep(1) 19 | 20 | filtered = [] 21 | candidates = [] 22 | 23 | for i in range(runs): 24 | print("[!] Press the button...\n") 25 | t_end = time.time() + 12 26 | 27 | macs = [] 28 | while time.time() < t_end: 29 | packet = rawSocket.recvfrom(2048) 30 | ethernet_header = struct.unpack("!6s6s2s", packet[0][0:14]) 31 | 32 | if ethernet_header[2] != '\x08\00': 33 | continue # not ip 34 | 35 | ip_header = struct.unpack("!1s1s2s2s1s1s1s1s2s4s4s", packet[0][14:34]) 36 | 37 | if ip_header[7] != '\x11': 38 | continue # not udp 39 | 40 | if len(packet[0][42:]) < 240: 41 | continue # not dhcp 42 | 43 | dhcp_header = struct.unpack("!1s1s1s1s4s2s2s4s4s4s4s6s10s64s128s4s", packet[0][42:282]) 44 | 45 | macs.append(binascii.hexlify(dhcp_header[11])) 46 | candidates.append(set(macs)) 47 | macs = [] 48 | 49 | macs = {} 50 | 51 | for run in range(len(candidates)): 52 | for mac in candidates[run]: 53 | if mac in macs: 54 | macs[mac] += 1 55 | else: 56 | macs[mac] = 1 57 | 58 | for m in macs: 59 | if macs[m] >= len(candidates) - tolerance: 60 | filtered.append(m) 61 | 62 | print("[!] Discovered button with MAC %s" % (filtered[0])) 63 | -------------------------------------------------------------------------------- /observer/listen.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import struct 3 | import binascii 4 | 5 | BUTTON = "ac63be5aea19" # change this to your mac for the demo 6 | 7 | 8 | 9 | buttons = {} 10 | 11 | def add_handler(mac, handler): 12 | buttons[mac.replace(":", "")] = handler 13 | 14 | def listen(): 15 | last_tid = {} 16 | rawSocket = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(0x0003)) 17 | while True: 18 | packet = rawSocket.recvfrom(2048) 19 | ethernet_header = struct.unpack("!6s6s2s", packet[0][0:14]) 20 | 21 | if ethernet_header[2] != '\x08\00': 22 | continue # not ip 23 | 24 | ip_header = struct.unpack("!1s1s2s2s1s1s1s1s2s4s4s", packet[0][14:34]) 25 | 26 | if ip_header[7] != '\x11': 27 | continue # not udp 28 | 29 | if len(packet[0][42:]) < 240: 30 | continue # not dhcp 31 | 32 | dhcp_header = struct.unpack("!1s1s1s1s4s2s2s4s4s4s4s6s10s64s128s4s", packet[0][42:282]) 33 | 34 | for b in buttons.keys(): 35 | if b in binascii.hexlify(ethernet_header[1]) and b in binascii.hexlify(dhcp_header[11]): 36 | # filter duplicate presses 37 | tid = binascii.hexlify(dhcp_header[4]) 38 | if b in last_tid and last_tid[b] == tid: 39 | continue 40 | last_tid[b] = tid 41 | buttons[b](":".join([b[i:i+2] for i in range(0, len(b), 2)])) 42 | 43 | 44 | 45 | def demo_print(mac): 46 | print("%s pressed" % (mac)) 47 | 48 | if __name__ == "__main__": 49 | add_handler(BUTTON, demo_print) 50 | listen() 51 | -------------------------------------------------------------------------------- /openwrt/README.md: -------------------------------------------------------------------------------- 1 | # Amazon Dash Button Fun 2 | 3 | ## Dash Endpoint: OpenWRT Router 4 | 5 | If you have a router running openWRT, only a few modifications are required to use it as endpoint for the Dash button. A custom endpoint prevents the button from connecting to Amazon. 6 | 7 | After the button has its IP address, it starts to resolve `parker-gw-eu.amazon.com`. By sending a fake DNS-entry, we can redirect the communication to our own server. 8 | 9 | **Advantages** 10 | 11 | + easy setup 12 | + does not connect to Amazon 13 | 14 | **Disadvantages** 15 | 16 | + router with custom firmware required 17 | 18 | ## Configuration 19 | 20 | 1. Activate your Dash button using the Amazon app. You can abort the activation at the point where you have to select a product. 21 | 2. Edit */etc/config/dhcp*, add the following line in the `config dnsmasq` section: 22 | 23 | list address '/parker-gw-eu.amazon.com/192.168.1.10` 24 | Here, `192.168.1.10` is the IP address of our Dash endpoint. This can be either a local IP or any IP address on the internet. 25 | 26 | 3. Restart dnsmasq: `/etc/init.d/dnsmasq restart` 27 | 28 | ## Listening for button presses 29 | 30 | Run the `listen.py` script as your Dash endpoint on the server specified in */etc/config/dhcp*. In this example this would be a local computer with the IP `192.168.1.10`. 31 | 32 | Everytime the button is pressed, it connects to our endpoint. The script outputs the button's MAC address on every press. 33 | -------------------------------------------------------------------------------- /openwrt/listen.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import subprocess 3 | import sys 4 | 5 | BUTTON = "ac63be5aea19" # change this to your mac for the demo 6 | 7 | 8 | 9 | buttons = {} 10 | 11 | def add_handler(mac, handler): 12 | buttons[mac.replace(":", "")] = handler 13 | 14 | def listen(): 15 | serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 16 | serversocket.bind(("0.0.0.0", 443)) 17 | serversocket.listen(5) 18 | 19 | while True: 20 | # wait for connection 21 | connection, addr = serversocket.accept() 22 | address = addr[0] 23 | # dash button closes it anyways, to close it to maybe save battery 24 | connection.close() 25 | 26 | # get dash button's mac address 27 | cmd="arp -a" 28 | mac = None 29 | p=subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE) 30 | output, errors = p.communicate() 31 | if output is not None : 32 | if sys.platform in ['linux','linux2']: 33 | for i in output.split("\n"): 34 | if address in i: 35 | for j in i.split(): 36 | if ":" in j: 37 | mac = j 38 | elif sys.platform in ['win32']: 39 | item = output.split("\n")[-2] 40 | if address in item: 41 | mac = item.split()[1] 42 | 43 | mac_raw = mac.replace(":", "") 44 | if mac_raw in buttons: 45 | buttons[mac_raw](mac) 46 | 47 | 48 | def demo_print(mac): 49 | print("%s pressed" % (mac)) 50 | 51 | if __name__ == "__main__": 52 | add_handler(BUTTON, demo_print) 53 | listen() -------------------------------------------------------------------------------- /rpi/README.md: -------------------------------------------------------------------------------- 1 | # Amazon Dash Button Fun 2 | 3 | ## Standalone: Raspberry Pi 4 | 5 | We will configure a Raspberry Pi to act as access point for the dash button. Similar to the openWRT solution, this gives us full control over the button's communication. The Raspberry Pi will be connected to the internet through the ethernet port and provide a WiFi access point through a WiFi adapter. 6 | 7 | All scripts run on the Raspberry Pi and no server/computer is needed to interact with the button. 8 | 9 | **Advantages** 10 | 11 | + no dependencies 12 | + no connection to Amazon 13 | + no router modifications needed 14 | 15 | **Disadvantages** 16 | 17 | + requires hardware (RPi + WiFi Dongle) 18 | 19 | 20 | ## Configuration 21 | 22 | 1. Install Raspbian Jessie Lite. 23 | 2. Install required packages `sudo apt-get install hostapd dnsmasq isc-dhcp-server` 24 | 3. Get a WiFi adapter that is compatible with the Raspberry Pi. I used a [Netgear WNA1000M N150 Micro Adapter](https://www.amazon.de/Netgear-WNA1000M-100FRS-WL-USB-WNA1000M-100GRS-150MBit/dp/B004URO9FG/). It has a Realtek RTL8192CU chipset. If your chipset is supported out-of-the-box, skip the next step. 25 | 4. Install the modified hostapd version from Realtek. Either take the binary [`hostapd`](hostapd) from this repository, or follow these steps to build it yourself. 26 | + Download the RTL8192CU driver from Realtek: [http://www.realtek.com/downloads/downloadsView.aspx?Langid=1&PNid=21&PFid=48&Level=5&Conn=4&DownTypeID=3&GetDown=false&Downloads=true#2772](http://www.realtek.com/downloads/downloadsView.aspx?Langid=1&PNid=21&PFid=48&Level=5&Conn=4&DownTypeID=3&GetDown=false&Downloads=true#2772) 27 | + Navigate to *RTL8188C_8192C_USB_linux_v4.0.2_9000.20130911/wpa_supplicant_hostapd/* and extract *wpa_supplicant_hostapd-0.8_rtw_r7475.20130812.tar.gz* 28 | + navigate to *wpa_supplicant_hostapd-0.8_rtw_r7475.20130812/hostapd/* and build with `make`. 29 | + remove the installed *hostapd* binary `sudo mv /usr/sbin/hostapd /usr/sbin/hostapd.orig` 30 | + install the new *hostapd* binary `sudo cp hostapd /usr/sbin/hostapd` 31 | 5. Configure *hostapd* by creating the file */etc/hostapd/hostapd.conf* with the following contents (if you don't have the RTL8192CU chipset, replace `rtl871xdrv` with `nl80211`) 32 | 33 | interface=wlan0 34 | driver=rtl871xdrv 35 | ssid=DashIoT 36 | channel=1 37 | auth_algs=1 38 | wmm_enabled=0 39 | wpa=1 40 | wpa_passphrase=yourpassword 41 | wpa_key_mgmt=WPA-PSK 42 | wpa_pairwise=CCMP 43 | macaddr_acl=0 44 | 45 | 6. Configure the WiFi adapter in */etc/network/interfaces* to use the 192.168.50.x network. It should look like this 46 | 47 | auto lo 48 | iface lo inet loopback 49 | 50 | iface eth0 inet manual 51 | 52 | iface wlan0 inet static 53 | address 192.168.50.1 54 | netmask 255.255.255.0 55 | 56 | hostapd /etc/hostapd/hostapd.conf 57 | 58 | 7. Configure the DHCP server in */etc/dhcp/dhcp.conf*. It should look like this 59 | 60 | default-lease-time 600; 61 | max-lease-time 7200; 62 | option routers 192.168.50.1; 63 | option domain-name-servers 192.168.50.1, 192.168.50.1; 64 | 65 | subnet 192.168.50.0 netmask 255.255.255.0 { 66 | pool { 67 | max-lease-time 600; 68 | range 192.168.50.10 192.168.50.50; 69 | option routers 192.168.50.1; 70 | option domain-name-servers 192.168.50.1, 192.168.50.1; 71 | allow unknown-clients; 72 | } 73 | } 74 | 75 | 8. Disable Google's DNS server 76 | 77 | sudo iptables -t nat -I PREROUTING -j DNAT --destination 8.8.8.8 --to 192.168.50.1 78 | sudo iptables -t nat -I PREROUTING -j DNAT --destination 8.8.4.4 --to 192.168.50.1 79 | 80 | 9. Enable NAT: 81 | 82 | sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE 83 | sudo iptables -A FORWARD -i eth0 -o wlan0 -j ACCEPT 84 | sudo iptables -A FORWARD -i wlan0 -o eth0 -j ACCEPT 85 | 86 | 10. Enable IPv4 forwarding. Add following line to */etc/sysctl.conf*: 87 | 88 | net.ipv4.ip_forward=1 89 | 90 | 11. Make the rules persistent by installing *iptables-persistent*: `sudo apt-get install iptables-persistent` 91 | 12. Reboot the Raspberry Pi (`sudo reboot`) 92 | 13. You should be able to connect to the access point with any wireless device. 93 | 14. Activate your Dash button using the Amazon app. You can abort the activation at the point where you have to select a product. 94 | 15. Redirect the Amazon endpoint to the Raspberry Pi by adding the following line to */etc/dnsmasq.conf*: 95 | 96 | address=/parker-gw-eu.amazon.com/192.168.50.1 97 | 98 | 16. Restart *dnsmasq*: `sudo service dnsmasq restart` 99 | 100 | ## Listening for button presses 101 | 102 | All the tools from the openWRT setup also work with the Raspberry Pi setup. Just run `sudo python openwrt/listen.py` to test the setup. Every time the button is pressed, the MAC address of the button is displayed. 103 | 104 | -------------------------------------------------------------------------------- /rpi/hostapd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/misc0110/dash-button/490ef380d667170833f38a349640ab40fe4ac76f/rpi/hostapd -------------------------------------------------------------------------------- /test_http.py: -------------------------------------------------------------------------------- 1 | from dashiot import DashIoT 2 | 3 | # Create an HTTP publisher that uses the network observer for detecting button presses 4 | http = DashIoT(DashIoT.Connector.OBSERVER, DashIoT.Protocol.HTTP) 5 | 6 | # If button with mac address "ac63be5aea19" is pressed, send the mac address as parameter "btn" 7 | http.publish_handler("ac63be5aea19", lambda x: ("btn", x)) 8 | 9 | # HTTP server to which we want to publish 10 | http.connect("http://localhost:8080/") 11 | 12 | # Publish all detected button presses forever 13 | http.run() 14 | 15 | -------------------------------------------------------------------------------- /test_mqtt.py: -------------------------------------------------------------------------------- 1 | from dashiot import DashIoT 2 | 3 | # Create an MQTT publisher that uses the network observer for detecting button presses 4 | mqtt = DashIoT(DashIoT.Connector.OBSERVER, DashIoT.Protocol.MQTT) 5 | 6 | # If button with mac address "ac63be5aea19" is pressed, send the mac address as under the topic "dash/button1" 7 | mqtt.publish_handler("ac63be5aea19", lambda x: ("dash/button1", x)) 8 | 9 | # MQTT server that accepts our messages 10 | mqtt.connect("iot.eclipse.org", 1883) 11 | 12 | # Publish all detected button presses forever 13 | mqtt.run() 14 | 15 | -------------------------------------------------------------------------------- /test_view_mqtt.py: -------------------------------------------------------------------------------- 1 | import paho.mqtt.client as mqtt 2 | 3 | def on_connect(client, userdata, flags, rc): 4 | client.subscribe("dash/#") 5 | 6 | def on_message(client, userdata, msg): 7 | print(msg.topic + " " + str(msg.payload)) 8 | 9 | client = mqtt.Client() 10 | client.on_connect = on_connect 11 | client.on_message = on_message 12 | 13 | client.connect("iot.eclipse.org", 1883, 60) 14 | client.loop_forever() 15 | 16 | --------------------------------------------------------------------------------