Skip to content
This repository has been archived by the owner on Jun 23, 2022. It is now read-only.

Commit

Permalink
Refactor cover, fixing some bugs (#73)
Browse files Browse the repository at this point in the history
Cover devices may or may not support setting their position (ie, an arbitrary
value between 0 and 100) instead of just opening or closing.

Before this change, it was assumed that rollershutters could have their
set, but garage doors could not. This is not correct.

This refactors the cover accessory to make this separation more clear. Instead
of if statements scattered through the code, we build a class hierarchy to
clearly separate garage doors and rollershutters - of the "binary" type, and
the type we previously assumed all were.

At the same time, I moved all of the logic for choosing the cover type into
the cover factory, instead of doing it all in index.js
  • Loading branch information
rcloran authored and robbiet480 committed Dec 15, 2016
1 parent 30eac06 commit 4cd3926
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 107 deletions.
247 changes: 153 additions & 94 deletions accessories/cover.js
Original file line number Diff line number Diff line change
@@ -1,93 +1,154 @@
"use strict";
var Service, Characteristic, communicationError;

module.exports = function (oService, oCharacteristic, oCommunicationError) {
Service = oService;
Characteristic = oCharacteristic;
communicationError = oCommunicationError;

return HomeAssistantCover;
return HomeAssistantCoverFactory;
};
module.exports.HomeAssistantCover = HomeAssistantCover;

function HomeAssistantCover(log, data, client, cover_type) {
this.client = client
this.log = log;
// device info
this.domain = "cover"
this.data = data
this.entity_id = data.entity_id
this.uuid_base = data.entity_id
if (data.attributes && data.attributes.friendly_name) {
this.name = data.attributes.friendly_name
}else{
this.name = data.entity_id.split(".").pop().replace(/_/g, " ")

function HomeAssistantCoverFactory(log, data, client) {
if (!data.attributes) {
return null;
}

if (data.attributes.homebridge_cover_type === "garage_door") {
return new HomeAssistantGarageDoor(log, data, client);
} else if (data.attributes.homebridge_cover_type === "rollershutter") {
if (data.attributes.current_position !== undefined) {
return new HomeAssistantRollershutter(log, data, client);
} else {
return new HomeAssistantRollershutterBinary(log, data, client);
}
} else {
log.error("'"+data.entity_id+"' is a cover but does not have a 'homebridge_cover_type' property set. " +
"You must set it to either 'rollershutter' or 'garage_door' in the customize section " +
"of your Home Assistant configuration. It will not be available to Homebridge until you do. " +
"See the README.md for more information. " +
"The attributes that were found are:", JSON.stringify(data.attributes));
}
};

class HomeAssistantCover {
constructor(log, data, client) {
this.client = client
this.log = log;
// device info
this.domain = "cover"
this.data = data
this.entity_id = data.entity_id
this.uuid_base = data.entity_id
if (data.attributes && data.attributes.friendly_name) {
this.name = data.attributes.friendly_name
}else{
this.name = data.entity_id.split(".").pop().replace(/_/g, " ")
}
}
this.cover_type = cover_type;
}

HomeAssistantCover.prototype = {
onEvent: function(old_state, new_state) {
var coverState = new_state.attributes.current_position == 100 ? 0 : 1;
this.coverService.getCharacteristic(Characteristic.CurrentDoorState)
.setValue(coverState, null, "internal");
this.coverService.getCharacteristic(Characteristic.TargetDoorState)
.setValue(coverState, null, "internal");
},
getCoverState: function(callback){
this.client.fetchState(this.entity_id, function(data){
onEvent(old_state, new_state) {
var state = this.transformData(new_state);

this.service.getCharacteristic(this.stateCharacteristic)
.setValue(state, null, "internal");
this.service.getCharacteristic(this.targetCharacteristic)
.setValue(state, null, "internal");
}

getState(callback){
this.client.fetchState(this.entity_id, function(data) {
if (data) {
coverState = data.state == "closed"
callback(null, coverState)
}else{
callback(null, this.transformData(data))
} else {
callback(communicationError)
}
}.bind(this))
},
setCoverState: function(coverOn, callback, context) {
if (context == "internal") {
callback();
return;
}
}

getServices() {
var informationService = new Service.AccessoryInformation();
informationService
.setCharacteristic(Characteristic.Manufacturer, "Home Assistant")
.setCharacteristic(Characteristic.SerialNumber, this.entity_id)
.setCharacteristic(Characteristic.Model, this.model);

this.service
.getCharacteristic(this.stateCharacteristic)
.on("get", this.getState.bind(this));

this.service
.getCharacteristic(this.targetCharacteristic)
.on("get", this.getState.bind(this))
.on("set", this.setTargetState.bind(this));

return [informationService, this.service];
}

doChangeState(service, callback) {
var that = this;
var service_data = {
entity_id: this.entity_id
}

if (coverOn) {
this.log("Setting cover state on the "+this.name+" to closed");

this.client.callService(this.domain, "close_cover", service_data, function(data){
if (data) {
that.log("Successfully set cover state on the "+that.name+" to closed");
callback()
}else{
callback(communicationError)
}
}.bind(this))
}else{
this.log("Setting cover state on the "+this.name+" to open");

this.client.callService(this.domain, "open_cover", service_data, function(data){
if (data) {
that.log("Successfully set cover state on the "+that.name+" to open");
callback()
}else{
callback(communicationError)
}
}.bind(this))
}
},
getPosition: function(callback){
this.client.fetchState(this.entity_id, function(data){
if (data && data.attributes) {
callback(null, data.attributes.current_position)
}else{
this.log("Calling service "+service+" on "+this.name);

this.client.callService(this.domain, service, service_data, function(data) {
if (data) {
callback()
} else {
callback(communicationError)
}
}.bind(this))
},
setPosition: function(position, callback, context) {
}
}

class HomeAssistantGarageDoor extends HomeAssistantCover {
constructor(log, data, client) {
super(log, data, client)
this.model = "Garage Door" ;
this.service = new Service.GarageDoorOpener();
this.stateCharacteristic = Characteristic.CurrentDoorState;
this.targetCharacteristic = Characteristic.TargetDoorState;
}

transformData(data) {
return data.state === "closed" ? Characteristic.CurrentDoorState.CLOSED : Characteristic.CurrentDoorState.OPEN;
}

setTargetState(targetState, callback, context) {
if (context == "internal") {
callback();
return;
}

this.doChangeState(targetState === Characteristic.TargetDoorState.CLOSED ? "close_cover" : "open_cover", callback)
}
}

class HomeAssistantRollershutter extends HomeAssistantCover {
constructor(log, data, client) {
super(log, data, client)
this.model = "Rollershutter";
this.service = new Service.WindowCovering();
this.stateCharacteristic = Characteristic.CurrentPosition;
this.targetCharacteristic = Characteristic.TargetPosition;
}

transformData(data) {
if (data && data.attributes) {
return data.attributes.current_position;
} else {
return null;
}
}

setTargetState(position, callback, context) {
if (context == "internal") {
callback();
return;
}

var that = this;
var payload = {
entity_id: this.entity_id,
Expand All @@ -96,40 +157,38 @@ HomeAssistantCover.prototype = {

this.log("Setting the state of the "+this.name+" to "+ payload.position);

this.client.callService(this.domain, "set_cover_position", payload, function(data){
this.client.callService(this.domain, "set_cover_position", payload, function(data) {
if (data) {
that.log("Successfully set position of "+that.name+" to "+ payload.position);
callback()
}else{
} else {
callback(communicationError)
}
}.bind(this))
},
getServices: function() {
this.coverService = (this.cover_type === "garage_door") ? new Service.GarageDoorOpener() : new Service.WindowCovering();
this.model = (this.cover_type === "garage_door") ? "Garage Door" : "Rollershutter";
this.stateCharacteristic = (this.cover_type === "garage_door") ? Characteristic.CurrentDoorState : Characteristic.CurrentPosition;
this.targetCharacteristic = (this.cover_type === "garage_door") ? Characteristic.TargetDoorState : Characteristic.TargetPosition;
this.stateCharacteristicGetFunction = (this.cover_type === "garage_door") ? this.getCoverState : this.getPosition;
this.targetCharacteristicGetFunction = (this.cover_type === "garage_door") ? this.getCoverState : this.getPosition;
this.targetCharacteristicSetFunction = (this.cover_type === "garage_door") ? this.setCoverState : this.setPosition;

var informationService = new Service.AccessoryInformation();
informationService
.setCharacteristic(Characteristic.Manufacturer, "Home Assistant")
.setCharacteristic(Characteristic.SerialNumber, this.entity_id)
.setCharacteristic(Characteristic.Model, this.model);
}
}

this.coverService
.getCharacteristic(this.stateCharacteristic)
.on("get", this.stateCharacteristicGetFunction.bind(this));
class HomeAssistantRollershutterBinary extends HomeAssistantRollershutter {
transformData(data) {
if (data && data.state) {
return (data.state == "open") * 100;
} else {
return null;
}
}

this.coverService
.getCharacteristic(this.targetCharacteristic)
.on("get", this.targetCharacteristicGetFunction.bind(this))
.on("set", this.targetCharacteristicSetFunction.bind(this));
setTargetState(position, callback, context) {
if (context == "internal") {
callback();
return;
}

return [informationService, this.coverService];
if (!(position == 100 || position == 0)) {
this.log("Cannot set this cover to positions other than 0 or 100")
callback(communicationError) // TODO
} else {
this.doChangeState(position == "100" ? "open_cover" : "close_cover", callback)
}
}

}

module.exports.HomeAssistantCoverFactory = HomeAssistantCoverFactory;
16 changes: 3 additions & 13 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ module.exports = function(homebridge) {
HomeAssistantLock = require('./accessories/lock')(Service, Characteristic, communicationError);
HomeAssistantMediaPlayer = require('./accessories/media_player')(Service, Characteristic, communicationError);
HomeAssistantFan = require('./accessories/fan')(Service, Characteristic, communicationError);
HomeAssistantCover = require('./accessories/cover')(Service, Characteristic, communicationError);
HomeAssistantCoverFactory = require('./accessories/cover')(Service, Characteristic, communicationError);
HomeAssistantSensorFactory = require('./accessories/sensor')(Service, Characteristic, communicationError);
HomeAssistantBinarySensorFactory = require('./accessories/binary_sensor')(Service, Characteristic, communicationError);

Expand All @@ -46,6 +46,7 @@ function HomeAssistantPlatform(log, config, api){

var es = new EventSource(config.host + '/api/stream?api_password=' + encodeURIComponent(this.password));
es.addEventListener('message', function(e) {
this.log("Received event: " + e.data)
if (e.data == 'ping')
return;

Expand Down Expand Up @@ -176,18 +177,7 @@ HomeAssistantPlatform.prototype = {
}else if (entity_type == 'fan'){
accessory = new HomeAssistantFan(that.log, entity, that)
}else if (entity_type == 'cover'){
if (entity.attributes && entity.attributes.homebridge_cover_type && (
entity.attributes.homebridge_cover_type === 'rollershutter' ||
entity.attributes.homebridge_cover_type === 'garage_door'
)) {
accessory = new HomeAssistantCover(that.log, entity, that, entity.attributes.homebridge_cover_type)
} else {
that.log.error("'"+entity.entity_id+"' is a cover but does not have a 'homebridge_cover_type' property set. "+
"You must set it to either 'rollershutter' or 'garage_door' in the customize section " +
"of your Home Assistant configuration. It will not be available to Homebridge until you do. " +
"See the README.md for more information. " +
"The attributes that were found are:", JSON.stringify(entity.attributes));
}
accessory = HomeAssistantCoverFactory(that.log, entity, that);
}else if (entity_type == 'sensor'){
accessory = HomeAssistantSensorFactory(that.log, entity, that)
}else if (entity_type == 'binary_sensor' && entity.attributes && entity.attributes.sensor_class) {
Expand Down

0 comments on commit 4cd3926

Please sign in to comment.