homebridge-miot
Version:
Homebridge plugin for devices supporting the miot protocol
355 lines (258 loc) • 9.06 kB
JavaScript
let Service, Characteristic, Accessory, HapStatusError, HAPStatus;
const Constants = require('../constants/Constants.js');
const Events = require('../constants/Events.js');
const PropFormat = require('../constants/PropFormat.js');
const PropUnit = require('../constants/PropUnit.js');
const PropAccess = require('../constants/PropAccess.js');
const ValueOperator = require('../constants/ValueOperator.js');
class AbstractPropertyWrapper {
constructor(wrapperName, prop, device, accessory, api, logger) {
if (new.target === AbstractPropertyWrapper) {
throw new Error('Cannot instantiate AbstractPropertyWrapper directly!')
}
this.wrapperName = wrapperName;
this.device = device;
this.accessory = accessory;
this.prop = prop;
this.api = api;
this.logger = logger;
Service = this.api.hap.Service;
Characteristic = this.api.hap.Characteristic;
Accessory = this.api.platformAccessory;
HapStatusError = this.api.hap.HapStatusError;
HAPStatus = this.api.hap.HAPStatus;
//variables
this.isValid = false;
this.configuration = {};
this.fixedValueOperator = ValueOperator.EQUAL;
// check if we have mandatory stuff
try {
if (!prop) throw new Error(`Missing property for ${wrapperName}!`);
if (!device) throw new Error(`Missing device for ${wrapperName}!`);
if (!accessory) throw new Error(`Missing accessory for ${wrapperName}!`);
} catch (error) {
this.logger.warn(error);
this.logger.warn(`Cannot create a property wrapper!`);
return;
}
this.logger.deepDebug(`<-W-> Creating ${this.getWrapperName()} wrapper of type ${this.getWrapperType()} for property ${this.getProp().getName()}`);
}
/*----------========== INIT ==========----------*/
initWrapper() {
this.logger.deepDebug(`<-W-> Initializing ${this.getWrapperName()} wrapper of type ${this.getWrapperType()} for property ${this.getProp().getName()}`);
try {
// prepare the custom service
this.isValid = this.prepareWrapper();
if (this.isWrapperValid()) {
this.getDevice().addPropertyToMonitor(this.getProp());
}
} catch (err) {
this.isValid = false;
this.logger.warn(`Failed to create property wrapper with name ${this.getWrapperName()}! Error during property creation! Skipping...`);
this.logger.debug(err);
}
}
/*----------========== PROPERTY WRAPPER INFO ==========----------*/
getWrapperType() {
return 'Generic';
}
/*----------========== SETUP WRAPPER ==========----------*/
prepareWrapper() {
//implemented by superclasses
return false;
}
/*----------========== STATE SETTERS/GETTERS ==========----------*/
/*----------========== WRAPPER PROTOCOL ==========----------*/
updateWrapperStatus() {
//implemented by superclasses
}
/*----------========== STATE HELPERS ==========----------*/
/*----------========== SETTERS ==========----------*/
setFixedValue(fixedValue) {
this.fixedValue = fixedValue;
}
setFixedValueOperator(fixedValueOperator) {
this.fixedValueOperator = ValueOperator.lookupByName(fixedValueOperator);
}
setLinkedProp(linkedProp) {
this.linkedProp = linkedProp;
}
setLinkedPropFixedValue(linkedPropFixedValue) {
this.linkedPropFixedValue = linkedPropFixedValue;
}
setConfiguration(configuration) {
this.configuration = configuration;
}
/*----------========== GETTERS ==========----------*/
getDevice() {
return this.device;
}
getAccessory() {
return this.accessory;
}
getWrapperName() {
return this.wrapperName;
}
getProp() {
return this.prop;
}
getFixedValue() {
return this.fixedValue;
}
getFixedValueOperator() {
return this.fixedValueOperator;
}
getLinkedProp() {
return this.linkedProp;
}
getLinkedPropFixedValue() {
return this.linkedPropFixedValue;
}
getConfiguration() {
return this.configuration;
}
isWrapperValid() {
return this.isValid;
}
getLogger() {
return this.logger;
}
/*----------========== CONVENIENCE ==========----------*/
createStatefulSwitch(name, id, getterFn, setterFn) {
let newStatefulSwitch = new Service.Switch(name, id);
newStatefulSwitch.addOptionalCharacteristic(Characteristic.ConfiguredName);
newStatefulSwitch.setCharacteristic(Characteristic.ConfiguredName, name);
newStatefulSwitch
.getCharacteristic(Characteristic.On)
.onGet(getterFn.bind(this))
.onSet(setterFn.bind(this));
return newStatefulSwitch;
}
createStatlessSwitch(name, id, setterFn) {
let newStatelessSwitch = new Service.Switch(name, id);
newStatelessSwitch.addOptionalCharacteristic(Characteristic.ConfiguredName);
newStatelessSwitch.setCharacteristic(Characteristic.ConfiguredName, name);
newStatelessSwitch
.getCharacteristic(Characteristic.On)
.onGet(this.isStatelessSwitchOn.bind(this))
.onSet((value) => {
setterFn.bind(this)(value);
setTimeout(() => {
if (newStatelessSwitch) newStatelessSwitch.getCharacteristic(Characteristic.On).updateValue(false);
}, Constants.BUTTON_RESET_TIMEOUT);
});
return newStatelessSwitch;
}
isStatelessSwitchOn() {
return false;
}
/*----------========== HELPERS ==========----------*/
generateServiceId(suffix) {
let serviceId = this.getProp().getName();
serviceId = serviceId.replace(/-./g, x => x[1].toUpperCase()); // kebap case to camel case
serviceId = serviceId.replace(/:./g, x => x[1].toUpperCase()); // : to camel case
serviceId = serviceId + 'Service';
if (this.getFixedValue()) {
serviceId = serviceId + this.getFixedValue();
}
if (suffix) {
serviceId = serviceId + suffix;
}
// make sure we do not have duplicate service names
let counter = 0;
let finalServiceId = serviceId;
while (this.hasAccessoryServiceById(finalServiceId)) {
counter = counter + 1;
finalServiceId = serviceId + counter;
}
return finalServiceId;
}
setPropValue(value) {
this.getDevice().setPropertyValue(this.getProp(), value);
}
getPropValue() {
return this.getProp().getValue();
}
isBoolean() {
return this.getProp() && this.getProp().getFormat() === PropFormat.BOOL;
}
isString() {
return this.getProp() && this.getProp().getFormat() === PropFormat.STRING;
}
hasValueRange() {
return this.getProp() && this.getProp().hasValueRange();
}
valueRange() {
return this.getProp().getValueRange();
}
hasValueList() {
return this.getProp() && this.getProp().hasValueList();
}
valueList() {
return this.getProp().getValueList();
}
isWritable() {
return this.getProp() && this.getProp().isWritable();
}
isWriteOnly() {
return this.getProp() && this.getProp().isWriteOnly();
}
getUnit() {
return this.getProp() ? this.getProp().getUnit() : PropUnit.NONE;
}
hasFixedValue() {
return typeof this.getFixedValue() !== 'undefined' && this.getFixedValue() !== null;
}
/*----------========== LINKED PROP HELPERS ==========----------*/
hasLinkedPropFixedValue() {
return typeof this.getLinkedPropFixedValue() !== 'undefined' && this.getLinkedPropFixedValue() !== null;
}
isLinkedPropBoolean() {
return this.getLinkedProp() && this.getLinkedProp().getFormat() === PropFormat.BOOL;
}
checkLinkedPropStatus() {
//if no linked prop specified then just ignore it and allow
if (!this.getLinkedProp()) {
return true;
}
// if the linked property value is set then check whether the prop value is qeual to the linked prop value
if (this.hasLinkedPropFixedValue()) {
return this.getLinkedProp().getValue() === this.getLinkedPropFixedValue();
}
// if boolean then return the value as status check
if (this.isLinkedPropBoolean()) {
return this.getLinkedProp().getValue();
}
return true
}
enableLinkedPropIfNecessary() {
if (!this.getLinkedProp() || !this.isLinkedPropBoolean()) {
return;
}
// enable the linked prop if it was disabled
if (this.getLinkedProp().getValue() === false) {
this.getDevice().setPropertyValue(this.getLinkedProp(), true);
}
}
disableLinkedPropIfNecessary() {
if (!this.getLinkedProp() || !this.isLinkedPropBoolean()) {
return;
}
// disable the linked prop if it was enabled
if (this.getLinkedProp().getValue() === true) {
this.getDevice().setPropertyValue(this.getLinkedProp(), false);
}
}
/*----------========== PROXY CAllS on ACCESSORY OBJ ==========----------*/
addAccessoryService(service) {
this.getAccessory().addService(service);
}
hasAccessoryServiceById(serviceId) {
return !!this.getAccessory().getService(serviceId);
}
/*----------========== PROXY CAllS on DEVICE ==========----------*/
isMiotDeviceConnected() {
return this.getDevice().isConnected();
}
}
module.exports = AbstractPropertyWrapper;