diff --git a/README.md b/README.md index fced68e78846358a791f45e9f2837a321c20126d..87bbe1f24218b58f879d12f72a69a1272409897f 100644 --- a/README.md +++ b/README.md @@ -30,11 +30,11 @@ If `flicd` and this script are not run from the same device, ensure that `flicd` **Can I run `flicd` as a daemon/at startup?** -Yes. See https://ethtiter.com/ for a `systemd` service file. +Yes. See https://ethitter.com/p/9738/ for a `systemd` service file. **How is the `buttons` property of `config.json` structured?** -Each object within the `buttons` property is structured according to how the button should respond to the entity's current state. For example, if the light is off, what should a button press do. +Each object within the `buttons` property is structured according what effect the click should have on an entity in its current state. For example, if the light is off, what should a single-click do. "mac": { "mac": "mac", @@ -42,15 +42,17 @@ Each object within the `buttons` property is structured according to how the but "status": { "entity_id": "switch.lamp" }, - "off": { - "entity": "switch", - "entity_id": "switch.lamp", - "entity_action": "turn_on" - }, - "on": { - "entity": "switch", - "entity_id": "switch.lamp", - "entity_action": "turn_off" + "single": { + "on": { + "entity": "switch", + "entity_id": "switch.lamp", + "entity_action": "turn_off" + }, + "off": { + "entity": "switch", + "entity_id": "switch.lamp", + "entity_action": "turn_on" + } } } @@ -62,21 +64,71 @@ The above example simply switches a light on and off, depending on its current s "status": { "entity_id": "alarm_control_panel.shm" }, - "disarmed": { - "entity": "alarm_control_panel", - "entity_id": "alarm_control_panel.shm", - "entity_action": "alarm_arm_home" + "single": { + "disarmed": { + "entity": "alarm_control_panel", + "entity_id": "alarm_control_panel.shm", + "entity_action": "alarm_arm_home" + }, + "armed_home": { + "entity": "alarm_control_panel", + "entity_id": "alarm_control_panel.shm", + "entity_action": "alarm_disarm" + }, + "armed_away": { + "entity": "switch", + "entity_id": "switch.lamp", + "entity_action": "turn_on" + } + } + } + +In this example, if the alarm isn't active, or is set to the "armed home" state, its Flic button will toggle between those states. If, however, one tried to use the button to disable the alarm when no one is home (motion sensors are active, among other changes), the button will simply turn on a light; it won't disarm the alarm, as that's something I only allow for those who have access to Home Assistant or SmartThings directly. + +As three click types--`single`, `double`, and `hold`--are supported, adding responses to the new click types is a matter of updating `config.json` to define how those clicks are handled. + + "mac": { + "mac": "mac", + "label": "Lamp", + "status": { + "entity_id": "switch.lamp" + }, + "single": { + "on": { + "entity": "switch", + "entity_id": "switch.lamp", + "entity_action": "turn_off" + }, + "off": { + "entity": "switch", + "entity_id": "switch.lamp", + "entity_action": "turn_on" + } }, - "armed_home": { - "entity": "alarm_control_panel", - "entity_id": "alarm_control_panel.shm", - "entity_action": "alarm_disarm" + "double": { + "on": { + "entity": "switch", + "entity_id": "switch.lamp", + "entity_action": "turn_off" + }, + "off": { + "entity": "switch", + "entity_id": "switch.lamp", + "entity_action": "turn_on" + } }, - "armed_away": { - "entity": "switch", - "entity_id": "switch.lamp", - "entity_action": "turn_on" + "hold": { + "on": { + "entity": "switch", + "entity_id": "switch.lamp", + "entity_action": "turn_off" + }, + "off": { + "entity": "switch", + "entity_id": "switch.lamp", + "entity_action": "turn_on" + } } } -In this example, if the alarm isn't active, or is set to the "armed home" state, its Flic button will toggle between those states. If, however, one tried to use the button to disable the alarm when no one is home (motion sensors are active, among other changes), the button will simply turn on a light; it won't disarm the alarm, as that's something I only allow for those who have access to Home Assistant or SmartThings directly. +This isn't a particularly interesting example, as all three click types perform the same action, but it demonstrates what's possible. diff --git a/config-sample.json b/config-sample.json index 012739239400410c3d94b1360be4c1f8f95b0749..686299c118f6aade19ca53c59bea8262323615c7 100644 --- a/config-sample.json +++ b/config-sample.json @@ -12,17 +12,43 @@ "status": { "entity_id": "" }, - "on": { - "entity": "", - "entity_id": "", - "entity_action": "" + "single": { + "on": { + "entity": "", + "entity_id": "", + "entity_action": "" + }, + "off": { + "entity": "", + "entity_id": "", + "entity_action": "" + } }, - "off": { - "entity": "", - "entity_id": "", - "entity_action": "" + "double": { + "on": { + "entity": "", + "entity_id": "", + "entity_action": "" + }, + "off": { + "entity": "", + "entity_id": "", + "entity_action": "" + } + }, + "hold": { + "on": { + "entity": "", + "entity_id": "", + "entity_action": "" + }, + "off": { + "entity": "", + "entity_id": "", + "entity_action": "" + } } } }, - "config_version": 2 + "config_version": 3 } diff --git a/index.js b/index.js index 68d1b553d1eead852537cbae88180e287d601d6a..6779a7378867316ae55d058011cb531eaa4ac87c 100644 --- a/index.js +++ b/index.js @@ -13,8 +13,8 @@ var request = require( 'request' ); */ var config = require( './config.json' ); -if ( 'undefined' === typeof config.config_version || config.config_version < 2 ) { - console.error( 'Configuration is for v0.1.0 and must be updated to work with this release. Exiting!' ); +if ( 'undefined' === typeof config.config_version || config.config_version < 3 ) { + console.error( 'Configuration is for a release prior to v0.2.5 and must be updated to work with this release. Exiting!' ); process.exit(); } @@ -40,24 +40,53 @@ client.on( 'newVerifiedButton', function( bdAddr ) { * BUTTON REACTIONS */ function listenToButton( bdAddr ) { + // Not much to do if the button isn't configured if ( "object" !== typeof config.buttons[bdAddr] ) { console.error( 'No configuration for button ' + bdAddr ); return; } + // Establish button connection var cc = new FlicConnectionChannel( bdAddr ); client.addConnectionChannel( cc ); - cc.on( 'buttonUpOrDown', function( clickType, wasQueued, timeDiff ) { - if ( 'ButtonDown' !== clickType ) { + // Bind to our desired click type; see ProtocolDocumentation.md in fliclib-linux-hci + cc.on( 'buttonSingleOrDoubleClickOrHold', function( clickType, wasQueued, timeDiff ) { + // Don't handle long-queued events, to avoid excessive toggling of HA entities + if ( wasQueued && timeDiff > 2 ) { return; } - if ( wasQueued && timeDiff > 2 ) { + // Convert the verbose click name to something convenient + // Also validates click type + var parsedClickType = ''; + + switch ( clickType ) { + case 'ButtonSingleClick': + parsedClickType = 'single'; + break; + + case 'ButtonDoubleClick': + parsedClickType = 'double'; + break; + + case 'ButtonHold': + parsedClickType = 'hold'; + break; + + default: + // Do nothing, this type isn't supported + break; + } + + // Click type is unsupported + if ( 0 === parsedClickType.length ) { + console.error( 'Unsupported click type ' + clickType ); return; } - buttonActivated( bdAddr ); + // Time to pass the click to HA + buttonActivated( bdAddr, parsedClickType ); } ); } @@ -68,9 +97,24 @@ function listenToButton( bdAddr ) { /** * Make HTTP(s) requests to Home Assistant to toggle status of device assigned to pushed button */ -function buttonActivated( mac ) { - var buttonConfig = config.buttons[mac]; +function buttonActivated( mac, clickType ) { + // Check for a button config + if ( 'undefined' === typeof config.buttons[ mac ] ) { + console.error( 'No configuration for button ' + mac ); + return; + } + + var buttonConfig = config.buttons[ mac ]; + + // Check for a config for this click type + if ( 'undefined' === typeof buttonConfig[ clickType ] ) { + console.error( 'No configuration for click type `' + clickType + '` on button "' + buttonConfig.label + '"' ); + return; + } + + var buttonClickConfig = buttonConfig[ clickType ]; + // Check current state of button's entity var req = { url: homeAssistantApiBase + 'states/' + buttonConfig.status.entity_id, headers: { @@ -95,12 +139,13 @@ function buttonActivated( mac ) { // HA only deals with JSON body = JSON.parse( body ); + // Use entity's current state to determine new state, per config var service = 'services/', postBody = {}; - if ( 'object' === typeof buttonConfig[ body.state ] ) { - service += buttonConfig[ body.state ].entity + '/' + buttonConfig[ body.state ].entity_action; - postBody.entity_id = buttonConfig[ body.state ].entity_id; + if ( 'object' === typeof buttonClickConfig[ body.state ] ) { + service += buttonClickConfig[ body.state ].entity + '/' + buttonClickConfig[ body.state ].entity_action; + postBody.entity_id = buttonClickConfig[ body.state ].entity_id; } else { console.error( 'Unhandled state: ' + body.state ); return; @@ -111,7 +156,7 @@ function buttonActivated( mac ) { req.method = 'POST'; req.body = JSON.stringify( postBody ); - // Make request to change alarm status + // Make request to update entity status request( req, function( err, res, body ) { // Handle error states if ( err ) { diff --git a/package.json b/package.json index 38b71f74f20276dbce3ac74028f8a80cef892709..c3bd5e4751b3717c77b4925d34b31861f9bdc8d1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "flic-button-home-assistant-controller", - "version": "0.2.0", + "version": "0.2.5", "description": "Control Home Assistant entities with Flic buttons", "author": "Erick Hitter <contact@ethitter.com>", "license": "GPL-2.0+",