Skip to content

Commit

Permalink
Corrected some logic errors and added support for the Kogan opener. T…
Browse files Browse the repository at this point in the history
…he changes made were as follows. Added test for Kogan manufacturer and changed log messages prefix to TuyaAccessory with manufacturer name. Set the dpAction and dpStatus values based on the manufacturer. Changed _getCurrentDoorState to use dps or changes as the argument and test for a Kogan opener then return manufacturer-specific values. Added a debug flag (name/value pair in the configuration) and debug log function _debugLog(). Replaced all console.log() calls with _debugLog() calls. The debug flag values are true and false and default to false if not present in the configuration. Added the Kogan fopen and fclose commands to _getTargetDoorState. Changed calls to _getCurrentDoorState() to use the dpStatus value either directly or via dps[this.dpStatus] or changes[this.dpStatus] instead of dps and state. In _getTargetDoorState(), changed from Action values to Status values. Added a status variant with the correct spelling for 'opening' in case Kogan to decide to correct the incorrect spelling 'openning’. Corrected generic garage door to use true and false for open/close and opened/closed. Removed the code that infers for generic doors the opening and closing states from dpStatus and dpAction, as the dpAction value is now unavailable in _getCurrentDoorState().
  • Loading branch information
davidh2075 committed Feb 9, 2021
1 parent e2937d9 commit 82c6063
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 82c6063

Please sign in to comment.