Skip to content

Commit

Permalink
0.9.0, Initial Version
Browse files Browse the repository at this point in the history
  • Loading branch information
mwittig committed Sep 5, 2016
1 parent 8d5474d commit 41330cf
Show file tree
Hide file tree
Showing 7 changed files with 293 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
language: node_js
node_js:
- "4"
5 changes: 5 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Release History


* 20160905, V0.9.0
* Initial Version
70 changes: 69 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,70 @@
# pimatic-amazing-dash-button
A pimatic plugin for Amazon's dash-buttons

[![Npm Version](https://badge.fury.io/js/pimatic-amazing-dash-button.svg)](http://badge.fury.io/js/pimatic-amazing-dash-button)
[![Build Status](https://travis-ci.org/mwittig/pimatic-amazing-dash-button.svg?branch=master)](https://travis-ci.org/mwittig/pimatic-amazing-dash-button)
[![Dependency Status](https://david-dm.org/mwittig/pimatic-amazing-dash-button.svg)](https://david-dm.org/mwittig/pimatic-amazing-dash-button)

A pimatic plugin for Amazon's dash-buttons. It is a pretty light-weight implementation which uses a `ContactSensor`
device abstraction for the dash-button. Auto-discovery of dash-buttons is supported.

The plugin sniffs for ARP probes which will be sent out by a dash-button when the
button is pressed. The plugin is based on [cap](https://www.npmjs.com/package/cap), a
cross-platform binding for performing packet capturing with node.js. It can be used on *nix and Windows systems.

## Installation

**This plugin requires libpcap to capture ARP requests on the network**. On Raspberry PI and comparable systems `libpcap`
must be installed, i.e. `sudo apt-get install libpcap-dev`.
On Windows, [WinPcap](http://www.winpcap.org/install/default.htm) most be installed.


## Plugin Configuration

{
"plugin": "amazing-dash-button",
"interfaceAddress": "192.168.1.15",
}

The plugin has the following configuration properties:

| Property | Default | Type | Description |
|:------------------|:---------|:--------|:--------------------------------------------|
| interfaceAddress | - | String | IP address associated with the network interface which shall be used to listen to ARP requests (optional) |



## Device Configuration

With pimatic v0.9 nad higher dash-button devices can be automatically discovered.

{
"id": "AmazingDashButton1",
"name": "AmazingDashButton1",
"class": "AmazingDashButton",
"macAddress": "AC:63:BE:B3:BE:78"
}

The plugin has the following configuration properties:

| Property | Default | Type | Description |
|:------------------|:---------|:--------|:--------------------------------------------|
| interfaceAddress | - | String | MAC address of the device |
| invert | false | String | If true, invert the contact state, i.e., contact is 'closed' if dash-button not pressed |
| holdTime | 1500 | Integer | The number of milliseconds the contact shall enter the state indicating button pressed (closed if not inverted) |

## Contributions and Donations

[![PayPal donate button](https://img.shields.io/paypal/donate.png?color=blue)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=E44SSB34CVXP2)

Contributions to the project are welcome. You can simply fork the project and create a pull request with
your contribution to start with. If you wish to support my work with a donation I'll highly appreciate this.

## History

See [Release History](https://github.com/mwittig/pimatic-amazing-dash-button/blob/master/HISTORY.md).

## License

Copyright (c) 2016, Marcus Wittig and contributors. All rights reserved.

[AGPL-3.0](https://github.com/mwittig/pimatic-amazing-dash-button/blob/master/LICENSE)
16 changes: 16 additions & 0 deletions amazing-dash-button-config-schema.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module.exports = {
title: "pimatic-amazing-dash-button plugin configuration config options"
type: "object"
properties:
debug:
description: "Debug mode. Writes debug messages to the pimatic log, if set to true."
type: "boolean"
default: false
interfaceAddress:
description: "
the IP address associated with the network interface which shall be used to listen to ARP requests.
If omitted the interface with a bound IP address will be used.
"
type: "string"
required: false
}
126 changes: 126 additions & 0 deletions amazing-dash-button.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# The amazing dash-button plugin
module.exports = (env) ->

Promise = env.require 'bluebird'
cap = require 'cap'
commons = require('pimatic-plugin-commons')(env)


# ###AmazingDashButtonPlugin class
class AmazingDashButtonPlugin extends env.plugins.Plugin

init: (app, @framework, @config) =>
@interfaceAddress = @config.interfaceAddress if @config.interfaceAddress?
@invert = @config.invert || false
@_contact = @invert
@debug = @config.debug || false
@base = commons.base @, 'Plugin'
@capture = new cap.Cap()
@buffer = new Buffer(65536)

process.on "SIGINT", @_stop
@_start()

# register devices
deviceConfigDef = require("./device-config-schema")
@framework.deviceManager.registerDeviceClass("AmazingDashButton",
configDef: deviceConfigDef.AmazingDashButton,
createCallback: (@config, lastState) =>
new AmazingDashButton(@config, @, lastState)
)

# auto-discovery
@framework.deviceManager.on('discover', (eventData) =>
@framework.deviceManager.discoverMessage 'pimatic-amazing-dash-button', 'Searching for dash-buttons. Press dash-button now!'
@candidatesSeen = []
@lastId = null

@arpPacketHandler = (arp) =>
candidateArp = arp.info.srcmac.toUpperCase()
strippedArpTrunc = candidateArp.replace(/:/g,'').substring(0,6)

# List of registered Mac addresses with IEEE as of 18 July 2016 for Amazon Technologies Inc.
# source: https://regauth.standards.ieee.org/standards-ra-web/pub/view.html#registries
amazonVendorIds = [
"747548", "F0D2F1", "8871E5", "74C246", "F0272D", "0C47C9",
"A002DC", "AC63BE", "44650D", "50F5DA", "84D6D0"
]
if strippedArpTrunc in amazonVendorIds and candidateArp not in @candidatesSeen
@base.debug 'Amazon device (possibly a dash-button) detected: ' + candidateArp
@candidatesSeen.push candidateArp
@lastId = @base.generateDeviceId @framework, "dash", @lastId

deviceConfig =
id: @lastId
name: @lastId
class: 'AmazingDashButton'
macAddress: candidateArp

@framework.deviceManager.discoveredDevice(
'pimatic-amazing-dash-button', "#{deviceConfig.name} (#{deviceConfig.macAddress})", deviceConfig
)

@on 'arpPacket', @arpPacketHandler
@timer = setTimeout( =>
@removeListener 'arpPacket', @arpPacketHandler
, eventData.time
)
)

_start: () ->
if @interfaceAddress?
device = cap.findDevice @interfaceAddress
else
device = cap.findDevice()
@base.debug "Sniffing for ARP requests on device", device

linkType = @capture.open device, 'arp', 10 * 1024 * 1024, @buffer
try
@capture.setMinBytes 0
catch e
@_base.debug e

@capture.on "packet", @_rawPacketHandler

_stop: () =>
@capture.removeListener "packet", @_rawPacketHandler
@capture.close();

_rawPacketHandler: () =>
ret = cap.decoders.Ethernet @buffer
@emit 'arpPacket', ret if ret.info.type is 2054

class AmazingDashButton extends env.devices.ContactSensor
# Initialize device by reading entity definition from middleware
constructor: (@config, @plugin, lastState) ->
@id = @config.id
@name = @config.name
@macAddress = @config.macAddress.toUpperCase()
@_invert = @config.invert || false
@_contact = @_invert
@debug = @plugin.debug || false
@base = commons.base @, @config.class
@arpPacketHandler = (arp) =>
if arp.info.srcmac.toUpperCase() is @macAddress
@_setContact not @_invert
clearTimeout @timer if @timer?
@timer = setTimeout( =>
@_setContact @_invert
@timer = null
, @config.holdTime
)
super()
@plugin.on 'arpPacket', @arpPacketHandler

destroy: () ->
clearTimeout @timer if @timer?
@plugin.removeListener 'arpPacket', @arpPacketHandler
super()

getContact: () -> Promise.resolve @_contact


# ###Finally
# Create a instance of my plugin
# and return it to the framework.
return new AmazingDashButtonPlugin
20 changes: 20 additions & 0 deletions device-config-schema.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module.exports = {
title: "pimatic-amazing-dash-button device config schemas"
AmazingDashButton:
title: "AmazingDashButton config"
type: "object"
extensions: ["xLink"]
properties: {
macAddress:
description: "MAC address of the dash-button"
type: "string"
invert:
description: "If true, invert the contact state, i.e., contact is 'closed' if dash-button not pressed."
type: "boolean"
default: false
holdTime:
description: "The number of milliseconds the contact shall enter the state indicating button pressed (closed if not inverted)."
type: "integer"
default: 1500
}
}
54 changes: 54 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{
"name": "pimatic-amazing-dash-button",
"version": "0.9.0",
"author": {
"name": "Marcus Wittig",
"url": "https://github.com/mwittig"
},
"main": "amazing-dash-button",
"files": [
"amazing-dash-button.coffee",
"amazing-dash-button-config-schema.coffee",
"device-config-schema.coffee",
"LICENSE",
"HISTORY.md",
"README.md"
],
"keywords": [
"amazon",
"amazon-dash",
"dash",
"button",
"arp",
"pimatic"
],
"homepage": "https://github.com/mwittig/pimatic-amazing-dash-button/tree/master",
"private": false,
"repository": {
"type": "git",
"url": "git://github.com/mwittig/pimatic-amazing-dash-button.git"
},
"bugs": {
"url": "https://github.com/mwittig/pimatic-amazing-dash-button/issues"
},
"license": "AGPL-3.0",
"maintainers": [
{
"name": "mwittig",
"url": "https://github.com/mwittig"
}
],
"configSchema": "amazing-dash-button-config-schema.coffee",
"dependencies": {
"cap": "^0.1.1",
"pimatic-plugin-commons": "^0.9.2"
},
"optionalDependencies": {},
"peerDependencies": {
"pimatic": ">=0.8.0 <1.0.0"
},
"engines": {
"node": ">0.8.x",
"npm": ">1.1.x"
}
}

0 comments on commit 41330cf

Please sign in to comment.