Skip to content

Commit

Permalink
Merge pull request #198 from davidh2075/garage-door-kogan
Browse files Browse the repository at this point in the history
Corrected some logic errors and added support for the Kogan opener.
  • Loading branch information
iRayanKhan authored Feb 15, 2021
2 parents e2937d9 + 82c6063 commit 8f7045d
Showing 1 changed file with 221 additions and 17 deletions.
238 changes: 221 additions & 17 deletions lib/GarageDoorAccessory.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
const BaseAccessory = require('./BaseAccessory');

// define constants for Kogan garage door.
// Action
const GARAGE_DOOR_OPEN = 'open';
const GARAGE_DOOR_CLOSE = 'close';
const GARAGE_DOOR_FOPEN = 'fopen';
const GARAGE_DOOR_FCLOSE = 'fclose';

// Status or state
// yes, 'openning' is not a mistake, that's the text from the Kogan opener
const GARAGE_DOOR_OPENED = 'opened';
const GARAGE_DOOR_CLOSED = 'closed';
const GARAGE_DOOR_OPENNING = 'openning';
const GARAGE_DOOR_OPENING =
'opening'; // 'opening' is not currently a valid value; added in case Kogan
// one day decides to correct the spelling
const GARAGE_DOOR_CLOSING = 'closing';
// Kogan garage door appears to have no stopped status

// Kogan manufacturer name
const GARAGE_DOOR_MANUFACTURER_KOGAN = 'Kogan';

// main code
class GarageDoorAccessory extends BaseAccessory {
static getCategory(Categories) {
return Categories.GARAGE_DOOR_OPENER;
Expand All @@ -17,18 +39,78 @@ class GarageDoorAccessory extends BaseAccessory {
super._registerPlatformAccessory();
}

// function to return a ID string for log messages
_logPrefix() {
return '[TuyaAccessory] ' +
(this.manufacturer ? this.manufacturer + ' ' : '') + 'GarageDoor';
}

// function to prefix a string ID and always log to console
_alwaysLog(...args) { console.log(this._logPrefix(), ...args); }

// function to log to console if debug is on
_debugLog(...args) {
if (this.debug) {
this._alwaysLog(...args);
}
}

// function to return true if the garage door manufacturer is Kogan and false
// otherwise
_isKogan() {
if (this.manufacturer === GARAGE_DOOR_MANUFACTURER_KOGAN.trim()) {
return true;
} else {
return false;
}
}

_registerCharacteristics(dps) {
const {Service, Characteristic} = this.hap;
const service = this.accessory.getService(Service.GarageDoorOpener);
this._checkServiceName(service, this.device.context.name);

this.dpAction = this._getCustomDP(this.device.context.dpAction) || '1';
this.dpStatus = this._getCustomDP(this.device.context.dpStatus) || '2';
// set the debug flag
if (this.device.context.debug) {
this.debug = true;
} else {
this.debug = false;
}

// test if this is a Kogan opener, i.e. has Kogan in upper or lower case in
// the manufacturer field and set the manufacturer property for later
// comparisons. If the manufacturer field is not Kogan and is not empty, set
// the manufacturer property to that value. Otherwise, set the manufacturer
// property to a blank string.
if (this.device.context.manufacturer.trim().toLowerCase() ===
GARAGE_DOOR_MANUFACTURER_KOGAN.trim().toLowerCase()) {
this.manufacturer = GARAGE_DOOR_MANUFACTURER_KOGAN.trim();
} else if (this.device.context.manufacturer) {
this.manufacturer = this.device.context.manufacturer.trim();
} else {
this.manufacturer = '';
}
// set the dpAction and dpStatus values based on the manufacturer
if (this._isKogan()) {
// Kogan SmarterHome Wireless Garage Door Opener
this._debugLog(
'_registerCharacteristics setting dpAction and dpStatus for ' +
GARAGE_DOOR_MANUFACTURER_KOGAN + ' door');
this.dpAction = this._getCustomDP(this.device.context.dpAction) || '101';
this.dpStatus = this._getCustomDP(this.device.context.dpStatus) || '102';
} else {
// the original garage door opener
this._debugLog(
'_registerCharacteristics setting dpAction and dpStatus for generic door');
this.dpAction = this._getCustomDP(this.device.context.dpAction) || '1';
this.dpStatus = this._getCustomDP(this.device.context.dpStatus) || '2';
}

this.currentOpen = Characteristic.CurrentDoorState.OPEN;
this.currentOpening = Characteristic.CurrentDoorState.OPENING;
this.currentClosing = Characteristic.CurrentDoorState.CLOSING;
this.currentClosed = Characteristic.CurrentDoorState.CLOSED;
this.currentStopped = Characteristic.CurrentDoorState.STOPPED;
this.targetOpen = Characteristic.TargetDoorState.OPEN;
this.targetClosed = Characteristic.TargetDoorState.CLOSED;
if (!!this.device.context.flipState) {
Expand All @@ -41,52 +123,174 @@ class GarageDoorAccessory extends BaseAccessory {
}

const characteristicTargetDoorState = service.getCharacteristic(Characteristic.TargetDoorState)
.updateValue(this._getTargetDoorState(dps[this.dpAction]))
.updateValue(this._getTargetDoorState(dps[this.dpStatus]))
.on('get', this.getTargetDoorState.bind(this))
.on('set', this.setTargetDoorState.bind(this));

const characteristicCurrentDoorState = service.getCharacteristic(Characteristic.CurrentDoorState)
.updateValue(this._getCurrentDoorState(dps))
.updateValue(this._getCurrentDoorState(dps[this.dpStatus]))
.on('get', this.getCurrentDoorState.bind(this));

this.device.on('change', (changes, state) => {
console.log('[TuyaAccessory] GarageDoor changed: ' + JSON.stringify(state));
this.device.on('change', changes => {
this._alwaysLog('changed:' + JSON.stringify(changes));

if (changes.hasOwnProperty(this.dpStatus)) {
const newCurrentDoorState =
this._getCurrentDoorState(changes[this.dpStatus]);
this._debugLog('on change new and old CurrentDoorState ' +
newCurrentDoorState + ' ' +
characteristicCurrentDoorState.value);
this._debugLog('on change old characteristicTargetDoorState ' +
characteristicTargetDoorState.value);

if (newCurrentDoorState == this.currentOpen &&
characteristicTargetDoorState.value !== this.targetOpen)
characteristicTargetDoorState.updateValue(this.targetOpen);

if (newCurrentDoorState == this.currentClosed &&
characteristicTargetDoorState.value !== this.targetClosed)
characteristicTargetDoorState.updateValue(this.targetClosed);

if (changes.hasOwnProperty(this.dpAction)) {
const newCurrentDoorState = this._getCurrentDoorState(state);
if (characteristicCurrentDoorState.value !== newCurrentDoorState) characteristicCurrentDoorState.updateValue(newCurrentDoorState);
}
});
}

getTargetDoorState(callback) {
this.getState(this.dpAction, (err, dp) => {
this.getState(this.dpStatus, (err, dp) => {
if (err) return callback(err);

this._debugLog('getTargetDoorState dp ' + JSON.stringify(dp));

callback(null, this._getTargetDoorState(dp));
});
}

_getTargetDoorState(dp) {
return dp ? this.targetOpen : this.targetClosed;
this._debugLog('_getTargetDoorState dp ' + JSON.stringify(dp));

if (this._isKogan()) {
// translate the Kogan strings to the enumerated status values
switch (dp) {
case GARAGE_DOOR_OPENED:
case GARAGE_DOOR_OPENNING:
case GARAGE_DOOR_OPENING:
return this.targetOpen;

case GARAGE_DOOR_CLOSED:
case GARAGE_DOOR_CLOSING:
return this.targetClosed;

default:
this._alwaysLog('_getTargetDoorState UNKNOWN STATE ' +
JSON.stringify(dp));
}
} else {
// Generic garage door uses true for the opened status and false for the
// closed status
if (dp === true) {
return this.targetOpen;
} else if (dp === false) {
return this.targetClosed;
} else {
this._alwaysLog('_getTargetDoorState UNKNOWN STATE ' +
JSON.stringify(dp));
}
}
}

setTargetDoorState(value, callback) {
this.setState(this.dpAction, value === this.targetOpen, callback);
var newValue = GARAGE_DOOR_CLOSE;
this._debugLog('setTargetDoorState value ' + value + ' targetOpen ' +
this.targetOpen + ' targetClosed ' + this.targetClosed);

if (this._isKogan()) {
// translate the the enumerated status values to Kogan strings
switch (value) {
case this.targetOpen:
newValue = GARAGE_DOOR_OPEN;
break;

case this.targetClosed:
newValue = GARAGE_DOOR_CLOSE;
break;

default:
this._alwaysLog('setTargetDoorState UNKNOWN STATE ' +
JSON.stringify(value));
}
} else {
// Generic garage door uses true for the open action and false for the
// close action
switch (value) {
case this.targetOpen:
newValue = true;
break;

case this.targetClosed:
newValue = false;
break;

default:
this._alwaysLog('setTargetDoorState UNKNOWN STATE ' +
JSON.stringify(value));
}
}

this.setState(this.dpAction, newValue, callback);
}

getCurrentDoorState(callback) {
this.getState([this.dpAction, this.dpStatus], (err, dps) => {
this.getState(this.dpStatus, (err, dpStatusValue) => {
if (err) return callback(err);

callback(null, this._getCurrentDoorState(dps));
callback(null, this._getCurrentDoorState(dpStatusValue));
});
}

_getCurrentDoorState(dps) {
// ToDo: Check other `dps` for opening and closing states
return dps[this.dpAction] ? this.currentOpen : this.currentClosed;
_getCurrentDoorState(dpStatusValue) {
this._debugLog('_getCurrentDoorState dpStatusValue ' +
JSON.stringify(dpStatusValue));

if (this._isKogan()) {
// translate the Kogan strings to the enumerated status values
switch (dpStatusValue) {
case GARAGE_DOOR_OPENED:
return this.currentOpen;

case GARAGE_DOOR_OPENNING:
case GARAGE_DOOR_OPENING:
return this.currentOpening;

case GARAGE_DOOR_CLOSING:
return this.currentClosing;

case GARAGE_DOOR_CLOSED:
return this.currentClosed;

default:
this._alwaysLog('_getCurrentDoorState UNKNOWN STATUS ' +
JSON.stringify(dpStatusValue));
}
} else {
// Generic garage door uses true for the open status and false for the
// close status. It doesn't seem to have other values for opening and
// closing. If the getState() function callback in BaseAccessory.js passed
// the dps object into this function, we may be able to infer opening and
// closing from the combined dpStatus and dpAction values. That would
// require mods to every accessory that used that callback. Not worth it.
if (dpStatusValue === true) {
// dpStatus true corresponds to an open door
return this.currentOpen;
} else if (dpStatusValue === false) {
// dpStatus false corresponds to a closed door, so assume "not open"
return this.currentClosed;
} else {
this._alwaysLog('_getCurrentDoorState UNKNOWN STATUS ' +
JSON.stringify(dps[this.dpStatus]));
}
}
}
}

module.exports = GarageDoorAccessory;
module.exports = GarageDoorAccessory;

0 comments on commit 8f7045d

Please sign in to comment.