├── .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 |
--------------------------------------------------------------------------------