├── .gitignore ├── LICENSE ├── README.md ├── app.js ├── config ├── config.example.json └── org.dasher.plist ├── lib └── dasher.js ├── package.json └── script ├── bootstrap ├── find_button ├── install ├── restart ├── server ├── uninstall └── upgrade /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | config/config.json 3 | *.log 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jon Maddox 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dasher!! 2 | 3 | ## :warning: Dasher is sunsetted :warning: 4 | This project is no longer under active development. I neither use it, nor do I use Amazon Dash buttons in my home automation. I found their latency and general way of working to not be a great solution as a smart button for my household. 5 | 6 | [Nekmo/amazon-dash](https://github.com/Nekmo/amazon-dash) is a great alternative. It has support for all kinds of actions and is actively developed. It's way better than mine! 7 | 8 | ## What it is 9 | 10 | Dasher is a simple way to bridge your Amazon Dash buttons to HTTP services. 11 | 12 | Do you have a Home Automation service set up like [Home Assistant](https://home-assistant.io), [openHab](http://www.openhab.org), or 13 | maybe a SmartThings hub? Using Dasher, you can easily command them to do 14 | something whenever your Dash button is pressed. 15 | 16 | This of course goes for anything you can reach via HTTP. That includes IFTTT by 17 | way of the Maker channel :metal: 18 | 19 | ## How it works 20 | 21 | It's pretty simple. Press a button and an HTTP request is made or local command 22 | is ran. That's it. 23 | 24 | You configure your Dash button(s) via `config/config.json`. You add its network 25 | address and either a url, an http method, and optionally a content body and 26 | headers or a local command to execute. 27 | 28 | When Dasher starts, it will listen for your button being pressed. Once it sees 29 | it, it will then make the HTTP request or run the command that you defined for 30 | it in your config. 31 | 32 | ## Configuration 33 | 34 | You define your buttons via the `config/config.json` file. It's a simple JSON 35 | file that holds an array of buttons. 36 | 37 | Here's an example. 38 | 39 | ```json 40 | {"buttons":[ 41 | { 42 | "name": "Notify", 43 | "address": "43:02:dc:b2:ab:23", 44 | "interface": "en0", 45 | "timeout": "60000", 46 | "protocol": "udp", 47 | "url": "https://maker.ifttt.com/trigger/Notify/with/key/5212ssx2k23k2k", 48 | "method": "POST", 49 | "json": true, 50 | "body": {"value1": "any value", "value2": "another value", "value3": "wow, even more value"} 51 | }, 52 | { 53 | "name": "Party Time", 54 | "address": "d8:02:dc:98:63:49", 55 | "url": "http://192.168.1.55:8123/api/services/scene/turn_on", 56 | "method": "POST", 57 | "headers": {"authorization": "your_password"}, 58 | "json": true, 59 | "body": {"entity_id": "scene.party_time"}, 60 | "formData": { 61 | "var1":"val1", 62 | "var2":" val2" 63 | } 64 | }, 65 | { 66 | "name": "Start Cooking Playlist", 67 | "address": "66:a0:dc:98:d2:63", 68 | "url": "http://192.168.1.55:8181/playlists/cooking/play", 69 | "method": "PUT" 70 | }, 71 | { 72 | "name": "Debug Dash Button", 73 | "address": "41:02:dc:b2:ab:23", 74 | "debug": true 75 | }, 76 | { 77 | "name": "Command Exec Button", 78 | "address": "41:02:dc:b2:10:12", 79 | "cmd": "/home/user/dash_button.sh" 80 | } 81 | ]} 82 | ``` 83 | 84 | Buttons take up to 8 options. 85 | 86 | * `name` - Optionally give the button action a name. 87 | * `address` - The MAC address of the button. 88 | * `interface` - Optionally listen for the button on a specific network interface. (`enX` on OS X and `ethX` on Linux) 89 | * `timeout` - Optionally set the time required between button press detections (if multiple pressese are detected) in milliseconds. Default is 5000. 90 | * `protocol` - Optionally set the protocol for your Dash button. Options are udp, arp, and all. Default listens to arp. The "newer" JK29LP button from ~Q2 2016+ tends to use udp. 91 | * `url` - The URL that will be requested. 92 | * `method` - The HTTP method of the request. 93 | * `headers` - Optional headers to use in the request. 94 | * `json` - Optionally declare the content body as being JSON in the request. 95 | * `body` - Optionally provide a content-body that will be sent with the request. 96 | * `formData` - Optionally add formData that will be sent with the request. 97 | * `debug` - Used for testing button presses and will -not- perform a request. 98 | * `cmd` - Used to run a local command rather than an HTTP request. Setting this 99 | will override the url parameter. 100 | 101 | Setting and using these values should be enough to cover almost every kind of 102 | request you need to make. 103 | 104 | You can find more examples in the [example config](/config/config.example.json). 105 | 106 | ## Protips 107 | 108 | Here are few protips about Dash buttons that will help you plan how to use them. 109 | 110 | * Dash buttons take ~5 seconds to trigger your action. 111 | * Use DHCP Reservation on your Dash button to lower the latency from ~5s to ~1s. 112 | * Dash buttons are discrete buttons. There is no on or off. They just do a 113 | single command. 114 | * Dash buttons can not be used for another ~10 seconds after they've been pressed. 115 | * If your Dash button is using udp, specify it in the button config. 116 | * Listening over wifi is unreliable. I highly recommend using ethernet, especially on Raspberry Pi 117 | 118 | Dash buttons should be used to trigger specific things. I.E. a scene in 119 | your home automation, as a way to turn everything off in your house, or 120 | as a simple counter. 121 | 122 | ## Setup 123 | 124 | You'll want to set up your Dash buttons as well as Dasher. 125 | 126 | ### Dash button 127 | 128 | Setting up your Dash button is as simple as following the instructions provided 129 | by Amazon **EXCEPT FOR THE LAST STEP**. Just follow the instructions to set it 130 | up in their mobile app. When you get to the step where it asks you to pick which 131 | product you want to map it to, just quit the setup process. 132 | 133 | The button will be set up and available on your network. 134 | 135 | #### Find Dash Button 136 | 137 | Once your Dash button is set up and on your network, you need to determine its 138 | MAC address. Run this: 139 | 140 | script/find_button 141 | 142 | Click your Dash button and the script will listen for your device. Dash buttons should appear as manufactured by 'Amazon Technologies Inc.'. Once you have 143 | its MAC address you will be able to configure it in Dasher by modifying `config/config.json` after installing Dasher. 144 | 145 | ### Dasher app 146 | 147 | Simply **install the dependencies** and **clone the repository**. 148 | 149 | **note:** You might need to install `libpcap-dev` or `npm` on Linux first. 150 | 151 | sudo apt-get install libpcap-dev 152 | sudo apt-get install npm 153 | 154 | **note** Raspberry Pi users may need to update node arm which will automatically remove nodejs-legacy. Credit @legotheboss 155 | 156 | sudo apt-get install node 157 | 158 | wget http://node-arm.herokuapp.com/node_latest_armhf.deb 159 | sudo dpkg -i node_latest_armhf.deb 160 | 161 | **Clone and Set up Dasher** 162 | 163 | git clone https://github.com/maddox/dasher.git 164 | cd dasher 165 | npm install 166 | 167 | Then create a `config.json` in `/config` to set up your Dash buttons. Use the 168 | example to help you. If you just want to test the button press, use the debug button example with the MAC address you found running script/find_button. 169 | 170 | 171 | ## Running It 172 | 173 | Listening for Dash buttons requires root. So you need to launch Dasher with sudo. 174 | 175 | sudo npm run start 176 | 177 | ### Auto Start on OS X 178 | 179 | After setting it up with `script/bootstrap` just run `script/install` to load Dasher with `launchd`. Dasher will now start on boot. 180 | 181 | You can uninstall it with `script/uninstall` and restart it with `script/restart`. 182 | 183 | ### Raspberry Pi 184 | **Having problems running npm install?** 185 | 186 | Raspberry Pi users may need to update node arm which will automatically remove nodejs-legacy. One you are on node mainline, force the update to the latest version of node for arm. This may not be needed if you are running a first generation pi. If you are on a Pi and run into problems with npm install, try this. Credit @legotheboss 187 | 188 | Replace nodejs-legacy with node and manually update to the latest version of node arm. 189 | 190 | sudo apt-get install node 191 | wget http://node-arm.herokuapp.com/node_latest_armhf.deb 192 | sudo dpkg -i node_latest_armhf.deb 193 | 194 | **Quick Start** Works as of (1/27/17) 195 | 196 | Starting from a fresh Raspberry Pi Build? 197 | 198 | sudo apt-get install libpcap-dev 199 | sudo apt-get install npm 200 | 201 | sudo apt-get install node 202 | wget http://node-arm.herokuapp.com/node_latest_armhf.deb 203 | sudo dpkg -i node_latest_armhf.deb 204 | 205 | git clone https://github.com/maddox/dasher.git 206 | cd dasher 207 | sudo npm install 208 | 209 | sudo ./script/find_button 210 | # update /config/config.json with mac address of your button 211 | sudo npm run start 212 | 213 | **Auto Starting** 214 | 215 | Advanced information on autostarting Dasher on your Raspberry Pi can be found [here](https://github.com/maddox/dasher/wiki/Running-Dasher-on-a-Raspberry-Pi-at-startup). 216 | 217 | ## Contributions 218 | 219 | * fork 220 | * create a feature branch 221 | * open a Pull Request 222 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var DasherButton = require('./lib/dasher') 2 | var config = require('./config/config.json') 3 | 4 | var buttons = [] 5 | 6 | for (var i = 0; i < config.buttons.length; i++) { 7 | button = config.buttons[i] 8 | buttons.push(new DasherButton(button)) 9 | } 10 | -------------------------------------------------------------------------------- /config/config.example.json: -------------------------------------------------------------------------------- 1 | {"buttons":[ 2 | { 3 | "name": "Debug Dash Button", 4 | "address": "d8:02:dc:98:63:49", 5 | "debug": true 6 | }, 7 | { 8 | "name": "Start Party Time", 9 | "address": "d8:02:dc:98:63:49", 10 | "interface": "en0", 11 | "timeout": "60000", 12 | "protocol": "udp", 13 | "url": "http://192.168.1.55:8123/api/services/scene/turn_on", 14 | "method": "POST", 15 | "headers": {"x-ha-access": "your_password"}, 16 | "json": true, 17 | "body": {"entity_id": "scene.party_time"} 18 | }, 19 | { 20 | "name": "Start Cooking Playlist", 21 | "address": "66:a0:dc:98:d2:63", 22 | "protocol": "arp", 23 | "url": "http://192.168.1.55:8181/playlists/cooking/play", 24 | "method": "PUT" 25 | }, 26 | { 27 | "address": "63:c9:dc:4a:b1:49", 28 | "url": "http://media.local:8888/button_presses/increment", 29 | "method": "POST", 30 | "body": "button_name=diaper_button", 31 | "formData": { 32 | "var1":"val1", 33 | "var2":" val2" 34 | } 35 | }, 36 | { 37 | "address": "55:a6:dc:4a:53:d4", 38 | "url": "https://maker.ifttt.com/trigger/Notify/with/key/4uY5SD3IQcsLKK1xUWbcV", 39 | "method": "POST" 40 | }, 41 | { 42 | "address": "43:02:dc:b2:ab:23", 43 | "url": "https://maker.ifttt.com/trigger/Notify/with/key/4uY5SD3IQcsLKK1xUWbcV", 44 | "method": "POST", 45 | "json": true, 46 | "body": {"value1": "any value", "value2": "another value", "value3": "wow, even more value"} 47 | }, 48 | { 49 | "name": "Command Exec Button", 50 | "address": "41:02:dc:b2:10:12", 51 | "cmd": "/home/user/dash_button.sh" 52 | } 53 | ]} 54 | -------------------------------------------------------------------------------- /config/org.dasher.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Label 6 | org.dasher 7 | 8 | EnvironmentVariables 9 | 10 | PATH 11 | /usr/local/bin/:$PATH 12 | NODE_ENV 13 | production 14 | 15 | 16 | ProgramArguments 17 | 18 | script/server 19 | 20 | 21 | AbandonProcessGroup 22 | 23 | 24 | RunAtLoad 25 | 26 | 27 | KeepAlive 28 | 29 | SuccessfulExit 30 | 31 | 32 | 33 | WorkingDirectory 34 | %PATH% 35 | 36 | StandardErrorPath 37 | /Library/Logs/dasher.log 38 | 39 | StandardOutPath 40 | /Library/Logs/dasher.log 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /lib/dasher.js: -------------------------------------------------------------------------------- 1 | var url = require('url') 2 | var dashButton = require('node-dash-button'); 3 | var request = require('request') 4 | const execFile = require('child_process').execFile; 5 | process.title = "dasher" 6 | 7 | function doLog(message) { 8 | console.log('[' + (new Date().toISOString()) + '] ' + message); 9 | } 10 | 11 | function DasherButton(button) { 12 | var options = {headers: button.headers, body: button.body, json: button.json, formData: button.formData} 13 | var debug = button.debug || false; 14 | this.dashButton = dashButton(button.address, button.interface, button.timeout, button.protocol) 15 | var buttonList = {}; 16 | 17 | this.dashButton.on("detected", function() { 18 | 19 | if(!buttonList.hasOwnProperty(button.address)){ 20 | buttonList[button.address] = 1; 21 | } else { 22 | buttonList[button.address]++; 23 | } 24 | doLog(button.name + " pressed. Count: " + buttonList[button.address]); 25 | if (debug){ 26 | doLog("Debug mode, skipping request."); 27 | console.log(button); 28 | } else if (typeof button.cmd !== 'undefined' && button.cmd != "") { 29 | doCommand(button.cmd, button.name) 30 | } else { 31 | doRequest(button.url, button.method, options) 32 | } 33 | }) 34 | 35 | doLog(button.name + " added.") 36 | } 37 | 38 | function doCommand(command, name, callback) { 39 | const child = execFile(command, [name], (error, stdout, stderr) => { 40 | if (error) { 41 | // Stripping a trailing new line 42 | output = stderr.replace (/\s+$/g, ""); 43 | doLog(`There was an error: ${output}`); 44 | } 45 | 46 | if (stdout != "") { 47 | // Stripping a trailing new line 48 | output = stdout.replace (/\s+$/g, ""); 49 | doLog(`Command output: ${output}`); 50 | } 51 | }); 52 | } 53 | 54 | function doRequest(requestUrl, method, options, callback) { 55 | options = options || {} 56 | options.query = options.query || {} 57 | options.json = options.json || false 58 | options.headers = options.headers || {} 59 | 60 | var reqOpts = { 61 | url: url.parse(requestUrl), 62 | method: method || 'GET', 63 | qs: options.query, 64 | body: options.body, 65 | json: options.json, 66 | headers: options.headers, 67 | formData: options.formData 68 | } 69 | 70 | request(reqOpts, function onResponse(error, response, body) { 71 | if (error) { 72 | doLog("there was an error"); 73 | console.log(error); 74 | } else { 75 | if (response.statusCode === 401) { 76 | doLog("Not authenticated"); 77 | } 78 | if (response.statusCode < 200 || response.statusCode > 299) { 79 | doLog("Unsuccessful status code"); 80 | } 81 | } 82 | 83 | if (callback) { 84 | callback(error, response, body) 85 | } 86 | }) 87 | } 88 | 89 | module.exports = DasherButton 90 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dasher", 3 | "version": "1.4.1", 4 | "description": "A simple way to bridge your Amazon Dash buttons to HTTP services.", 5 | "scripts": { 6 | "start": "node app.js" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/maddox/dasher" 11 | }, 12 | "author": "Jon Maddox ", 13 | "license": "MIT", 14 | "bugs": { 15 | "url": "https://github.com/maddox/dasher/issues" 16 | }, 17 | "dependencies": { 18 | "node-dash-button": "^0.6.1", 19 | "request": "^2.60.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /script/bootstrap: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | if ! test $(which forever) 6 | then 7 | echo 8 | echo "!!!!" 9 | echo "You don't have forever installed. You need to install it first." 10 | echo 11 | echo "Just install it with this command: " 12 | echo 'sudo npm install forever -g' 13 | echo 14 | exit 1 15 | fi 16 | 17 | mkdir -p log 18 | 19 | npm install 20 | 21 | if [ ! -f config/config.json ] 22 | then 23 | echo 24 | echo "==> Please create and edit your configuration at config/config.json." 25 | echo 26 | exit 0 27 | fi 28 | 29 | echo "Finished setting up Dasher! run it with script/server or install it with script/install." 30 | -------------------------------------------------------------------------------- /script/find_button: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | sudo node node_modules/node-dash-button/bin/findbutton 4 | -------------------------------------------------------------------------------- /script/install: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | echo "Installing Dasher..." 6 | 7 | APP_PATH=`pwd` 8 | 9 | sudo mkdir -p /Library/LaunchDaemons 10 | sudo mkdir -p /root/.forever 11 | 12 | sudo cp config/org.dasher.plist /Library/LaunchDaemons/org.dasher.plist 13 | 14 | sudo sed -i '' -e "s#%PATH%#$APP_PATH#g" /Library/LaunchDaemons/org.dasher.plist 15 | 16 | sudo launchctl bootstrap system /Library/LaunchDaemons/org.dasher.plist 17 | -------------------------------------------------------------------------------- /script/restart: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | script/uninstall 4 | script/install 5 | -------------------------------------------------------------------------------- /script/server: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | test -z "$NODE_ENV" && 4 | export NODE_ENV='development' 5 | 6 | echo $NODE_ENV 7 | 8 | if [ "$NODE_ENV" = "development" ]; then 9 | sudo /usr/local/bin/forever -a -w app.js 10 | else 11 | node app.js 12 | fi 13 | -------------------------------------------------------------------------------- /script/uninstall: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Uninstalling Dasher..." 4 | forever stop dasher > /dev/null 2>&1 5 | 6 | laPath="/Library/LaunchAgents/org.dasher.plist" 7 | ldPath="/Library/LaunchDaemons/org.dasher.plist" 8 | 9 | if [ -f "$laPath" ] 10 | then 11 | sudo launchctl unload "$laPath" 12 | sudo rm "$laPath" 13 | fi 14 | 15 | if [ -f "$ldPath" ] 16 | then 17 | sudo launchctl unload "$ldPath" 18 | sudo rm "$ldPath" 19 | fi 20 | -------------------------------------------------------------------------------- /script/upgrade: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo "Updating from GitHub..." 3 | BRANCH=$(git rev-parse --abbrev-ref HEAD) 4 | git pull origin $BRANCH 5 | 6 | npm update 7 | 8 | script/restart 9 | --------------------------------------------------------------------------------