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

Add Shelly1 as GarageDoorOpener - actually Garage Door Switch #239

Open
wants to merge 13 commits 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
190 changes: 190 additions & 0 deletions abilities/garage-door-switch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
const { handleFailedRequest } = require('../util/error-handlers')

module.exports = homebridge => {
const { Ability } = require('./base')(homebridge)
const Characteristic = homebridge.hap.Characteristic
const Service = homebridge.hap.Service

class GarageDoorSwitchAbility extends Ability {
/**
* @param {string} switchProperty - The device property to trigger the
* garage door to open or close.
* @param {string} stateProperty - The device property used to indicate the
* garage door is closed.
* @param {function} setSwitch - A function that updates the device's switch
* state. Must return a Promise.
*/
constructor(switchProperty, stateProperty, setSwitch) {
super()
this._switchProperty = switchProperty
MichaelVdheeren marked this conversation as resolved.
Show resolved Hide resolved
this._stateProperty = stateProperty
this._setSwitch = setSwitch
this._targetState = null
}

get service() {
return this.platformAccessory.getService(Service.GarageDoorOpener)
}

get state() {
return this.device[this._stateProperty] || 0
}

get currentState() {
const CDS = Characteristic.CurrentDoorState

if (this.state > 0) {
return CDS.CLOSED
}

return CDS.OPEN
}

get targetState() {
if (this._targetState !== null) {
return this._targetState
}

const CDS = Characteristic.CurrentDoorState
const TDS = Characteristic.TargetDoorState
const cs = this.currentState
return cs === CDS.OPEN || cs === CDS.OPENING ? TDS.OPEN : TDS.CLOSED
}

_createService() {
const CDS = Characteristic.CurrentDoorState
const TDS = Characteristic.TargetDoorState

return new Service.GarageDoorOpener()
.setCharacteristic(CDS, this.currentState)
.setCharacteristic(TDS, TDS.CLOSED)
.setCharacteristic(Characteristic.ObstructionDetected, false)
}

_setupEventHandlers() {
super._setupEventHandlers()

// This is the handler to catch HomeKit events
this.service
.getCharacteristic(Characteristic.TargetDoorState)
.on('set', this._targetDoorStateSetHandler.bind(this))

// This is the handler to catch device events
// This one is always correct!
this.device
.on(
'change:' + this._stateProperty,
this._stateChangeHandler,
this
)
}

/**
* Handles changes from HomeKit to the TargetDoorState characteristic.
*/
async _targetDoorStateSetHandler(newValue, callback, context) {
const d = this.device

// If the context is shelly then this is an internal update
// to ensure that homekit is in sync with the current status
// If not, we really trigger the switch
if (context === 'shelly') {
callback()
return
}

this._targetState = newValue

this.log.debug(
'Target homekit state is',
newValue
)

this.log.debug(
'Setting',
this._switchProperty,
'of device',
d.type,
d.id,
'to',
true
)

try {
await this._setSwitch(true)
callback()
} catch (e) {
handleFailedRequest(
this.log,
d,
e,
'Failed to set ' + this._switchProperty
)
callback(e)
}
}

/**
* Handles changes from the device to the state property.
* This means either the garage door just closed or it has started to open.
*/
_stateChangeHandler(newValue) {
this.log.debug(
this._stateProperty,
'of device',
this.device.type,
this.device.id,
'changed to',
newValue
)

this.updateGarageDoorState()
}

updateGarageDoorState() {
const CDS = Characteristic.CurrentDoorState
const TDS = Characteristic.TargetDoorState

if (this.currentState === CDS.CLOSED) {
this.service
.getCharacteristic(TDS)
.setValue(TDS.CLOSED, null, 'shelly')
this.service
.setCharacteristic(CDS, CDS.CLOSING)

setTimeout(() => {
this.service
.setCharacteristic(CDS, CDS.CLOSED)
}, 1000)
} else {
this.service
.getCharacteristic(TDS)
.setValue(TDS.OPEN, null, 'shelly')
this.service
.setCharacteristic(CDS, CDS.OPENING)

setTimeout(() => {
this.service
.setCharacteristic(CDS, CDS.OPEN)
}, 1000)
}
}

getState(callback) {
callback(null, this.currentState)
}

detach() {
this.device
.removeListener(
'change:' + this._stateProperty,
this._stateChangeHandler,
this
)

super.detach()
}
}

return GarageDoorSwitchAbility
}
10 changes: 10 additions & 0 deletions accessories/factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module.exports = homebridge => {
} = require('./doors')(homebridge)

const {
Shelly1GarageDoorSwitchAccessory,
Shelly2GarageDoorOpenerAccessory,
} = require('./garage-door-openers')(homebridge)

Expand Down Expand Up @@ -165,6 +166,7 @@ module.exports = homebridge => {
} else if (accessoryType === 'valve') {
return new ShellyRelayValveAccessory(this.device, ...opts)
}

return new ShellyRelaySwitchAccessory(this.device, ...opts)
}
}
Expand Down Expand Up @@ -427,6 +429,14 @@ module.exports = homebridge => {
get numberOfPowerMeters() {
return 0
}

_createAccessory(accessoryType, ...opts) {
if (accessoryType === 'garageDoorOpener') {
return new Shelly1GarageDoorSwitchAccessory(this.device, ...opts)
}

return super._createAccessory(accessoryType, ...opts)
}
}
FACTORIES.set('SHSW-1', Shelly1Factory)

Expand Down
26 changes: 24 additions & 2 deletions accessories/garage-door-openers.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,30 @@

module.exports = homebridge => {
const Accessory = homebridge.hap.Accessory
const GarageDoorSwitchAbility =
require('../abilities/garage-door-switch')(homebridge)
const GarageDoorOpenerAbility =
require('../abilities/garage-door-opener')(homebridge)
const { ShellyAccessory } = require('./base')(homebridge)
const {
ShellyAccessory,
ShellyRelayAccessory
} = require('./base')(homebridge)

class Shelly1GarageDoorSwitchAccessory extends ShellyRelayAccessory {
constructor(device, index, config, log) {
super('garageDoorOpener', device, index, config, log)

this.abilities.push(new GarageDoorSwitchAbility(
'relay' + index,
'input' + index,
this.setRelay.bind(this)
))
}

get category() {
return Accessory.Categories.GARAGE_DOOR_OPENER
}
}

class Shelly2GarageDoorOpenerAccessory extends ShellyAccessory {
constructor(device, index, config, log) {
Expand Down Expand Up @@ -31,6 +52,7 @@ module.exports = homebridge => {
}

return {
Shelly2GarageDoorOpenerAccessory,
Shelly1GarageDoorSwitchAccessory,
Shelly2GarageDoorOpenerAccessory
}
}