Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose second button of Shelly 1L to Homebridge #427

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,16 @@ interface of a device, under *Settings -> Device info -> Device ID*.
types are `"contactSensor"`, `"motionSensor"`, `"occupancySensor"`,
`"outlet"`, `"switch"` (default) and `"valve"`.

#### Shelly 1L configurations
* `"type"` - sets the type of the secondary button (only useful when configured
to detached mode). Available types are:

* `"switch"` (default) disables the secondary button.
* `"statelessSwitch"` triggers short, double or long press (useful for push
buttons).
* `"statelessToggle"` always triggers a single press with each state change
(useful for toggle switches).

#### Shelly 2.5 configurations
* `"type"` - in roller mode, the device can be identified as either `"door"`,
`"garageDoorOpener"`, `"window"` or `"windowCovering"` (default).
Expand Down
69 changes: 68 additions & 1 deletion abilities/stateless-programmable-switch.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,5 +141,72 @@ module.exports = homebridge => {
}
}

return StatelessProgrammableSwitchAbility
class StatelessProgrammableToggleAbility extends StatelessProgrammableSwitchAbility {
/**
* @param {string} inputProperty - The device property used to indicate
* that the input has been triggered
* @param {string} inputCounterProperty - The device property used to
* indicate the current input event count.
* @param {number} index - The index of this switch.
*/
constructor(inputProperty, inputCounterProperty, index = 1) {
super(null, inputCounterProperty, index)

this.inputProperty = inputProperty
}

_setupEventHandlers() {
super._setupEventHandlers()

this.device
.on(
'change:' + this.inputProperty,
this._inputChangeHandler,
this
)
}

/**
* Handles changes from the device to the input property.
*/
_inputChangeHandler(newValue) {
this.log.debug(
this.inputProperty,
'of device',
this.device.type,
this.device.id,
'changed to',
newValue
)

this._triggerSwitchDebounced()
}

/**
* Triggers a new switch event.
*/
_triggerSwitch() {
const PSE = Characteristic.ProgrammableSwitchEvent

this.service
.getCharacteristic(PSE)
.updateValue(PSE.SINGLE_PRESS)
}

detach() {
this.device
.removeListener(
'change:' + this.inputProperty,
this._inputChangeHandler,
this
)

super.detach()
}
}

return {
StatelessProgrammableSwitchAbility,
StatelessProgrammableToggleAbility,
}
}
21 changes: 20 additions & 1 deletion accessories/factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ module.exports = homebridge => {
const {
ShellyButton1StatelessSwitchAccessory,
ShellyInputStatelessSwitchAccessory,
ShellyInputStatelessToggleAccessory,
} = require('./stateless-switches')(homebridge)

const {
Expand Down Expand Up @@ -515,7 +516,25 @@ module.exports = homebridge => {
/**
* Shelly 1L factory.
*/
class Shelly1LFactory extends RelayAccessoryFactory {}
class Shelly1LFactory extends RelayAccessoryFactory {
get numberOfAccessories() {
return 2
}

_createAccessory(accessoryType, index, config, log) {
if (index == 0) {
return this._createAccessoryForRelay(accessoryType, index, config, log)
}
else {
if (accessoryType === "statelessToggle") {
return new ShellyInputStatelessToggleAccessory(this.device, index, config, log, 1, 1)
}
else if (accessoryType === "statelessSwitch") {
return new ShellyInputStatelessSwitchAccessory(this.device, index, config, log, 1, 1)
}
}
}
}
FACTORIES.set('SHSW-L', Shelly1LFactory)

/**
Expand Down
40 changes: 33 additions & 7 deletions accessories/stateless-switches.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
module.exports = homebridge => {
const BatteryAbility = require('../abilities/battery')(homebridge)
const ServiceLabelAbility = require('../abilities/service-label')(homebridge)
const StatelessProgrammableSwitchAbility =
require('../abilities/stateless-programmable-switch')(homebridge)
const {
StatelessProgrammableSwitchAbility,
StatelessProgrammableToggleAbility,
} = require('../abilities/stateless-programmable-switch')(homebridge)
const { ShellyAccessory } = require('./base')(homebridge)

class ShellyButton1StatelessSwitchAccessory extends ShellyAccessory {
Expand All @@ -19,19 +21,42 @@ module.exports = homebridge => {
}

class ShellyInputStatelessSwitchAccessory extends ShellyAccessory {
constructor(device, index, config, log, numberOfInputs = 1) {
constructor(device, index, config, log, numberOfInputs = 1, numberOfFirstInput = 0) {
super('statelessSwitch', device, index, config, log)

if (numberOfInputs === 1) {
this.abilities.push(new StatelessProgrammableSwitchAbility(
'inputEvent0',
'inputEventCounter0'
'inputEvent' + numberOfFirstInput,
'inputEventCounter' + numberOfFirstInput
))
} else {
for (let i = 0; i < numberOfInputs; i++) {
this.abilities.push(new StatelessProgrammableSwitchAbility(
'inputEvent' + i,
'inputEventCounter' + i,
'inputEvent' + (i + numberOfFirstInput),
'inputEventCounter' + (i + numberOfFirstInput),
i + 1
))
}

this.abilities.push(new ServiceLabelAbility())
}
}
}

class ShellyInputStatelessToggleAccessory extends ShellyAccessory {
constructor(device, index, config, log, numberOfInputs = 1, numberOfFirstInput = 0) {
super('statelessToggle', device, index, config, log)

if (numberOfInputs === 1) {
this.abilities.push(new StatelessProgrammableToggleAbility(
'input' + numberOfFirstInput,
'inputEventCounter' + numberOfFirstInput
))
} else {
for (let i = 0; i < numberOfInputs; i++) {
this.abilities.push(new StatelessProgrammableToggleAbility(
'input' + (i + numberOfFirstInput),
'inputEventCounter' + (i + numberOfFirstInput),
i + 1
))
}
Expand All @@ -44,5 +69,6 @@ module.exports = homebridge => {
return {
ShellyButton1StatelessSwitchAccessory,
ShellyInputStatelessSwitchAccessory,
ShellyInputStatelessToggleAccessory,
}
}
1 change: 1 addition & 0 deletions config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
"outlet",
"sensor",
"statelessSwitch",
"statelessToggle",
"switch",
"valve",
"whiteLightbulb",
Expand Down