homebridge-miot
Version:
Homebridge plugin for devices supporting the miot protocol
992 lines (843 loc) • 36.7 kB
JavaScript
let Service, Characteristic, Accessory, HapStatusError, HAPStatus;
const Constants = require('../constants/Constants.js');
const DevTypes = require('../constants/DevTypes.js');
const Properties = require('../constants/Properties.js');
const PropValueListWrapper = require('../wrappers/PropValueListWrapper.js');
class BaseAccessory {
constructor(name, miotDevice, uuid, config, api, logger) {
this.api = api;
this.logger = logger;
// init the hap constants
// for myself
Service = this.api.hap.Service;
Characteristic = this.api.hap.Characteristic;
Accessory = this.api.platformAccessory;
HapStatusError = this.api.hap.HapStatusError;
HAPStatus = this.api.hap.HAPStatus;
// for superclasses
this.initHapConstants();
// check if we have mandatory device info
try {
if (!miotDevice) throw new Error(`Missing miot device for ${config.name}!`);
if (!uuid) throw new Error(`Missing uuid for ${config.name}!`);
if (this.getAccessoryType() !== miotDevice.getType()) throw new Error(`Accessory type ${this.getAccessoryType()} cannot be used for device type ${miotDevice.getType()}!`);
} catch (error) {
this.logger.error(error);
this.logger.error(`Something went wrong!`);
this.logger.error(`Failed to create accessory, missing mandatory information!`);
return;
}
// configuration
this.initConfigProperties(config);
// variables
this.name = name;
this.uuid = uuid;
this.miotDevice = miotDevice;
this.accessory = null;
this.customServices = [];
// init
this.accessory = this.initAccessory();
if (this.accessory) {
this.setupMainAccessoryService();
this.setupAdditionalAccessoryServices();
} else {
this.logger.warn(`Something went wrong! Could initialize the accessory!`);
}
}
/*----------========== SETUP ACCESSORY ==========----------*/
initHapConstants() {
//implemented by superclasses
}
initConfigProperties(config) {
//implemented by superclasses
}
getAccessoryType() {
return DevTypes.UNKNOWN;
}
/*----------========== SETUP SERVICES ==========----------*/
initAccessory() {
//implemented by superclasses
}
setupMainAccessoryService() {
//implemented by superclasses
}
setupAdditionalAccessoryServices() {
//implemented by superclasses
}
/*----------========== CREATE ADDITIONAL SERVICES ==========----------*/
prepareBuzzerControlService() {
if (this.getDevice().supportsBuzzerControl()) {
this.buzzerService = this.createStatefulSwitch('Buzzer', 'buzzerService', this.isBuzzerOn, this.setBuzzerOn);
this.addAccessoryService(this.buzzerService);
}
}
prepareLedControlService() {
if (this.getDevice().supportsLedControl()) {
if (this.getDevice().supportsLedControlList()) {
this.ledLevelControlServices = new Array();
this.getDevice().ledControlList().forEach((level, i) => {
let levelVal = level.value;
let levelDesc = level.description;
let tmpLedLevelSwitch = this.createStatefulSwitch('LED - ' + levelDesc, 'ledLevelControlService' + levelVal, () => {
return this.isLedLevelSwitchOn(levelVal);
}, (value) => {
this.setLedLevelSwitchOn(value, levelVal);
});
this.addAccessoryService(tmpLedLevelSwitch);
this.ledLevelControlServices.push(tmpLedLevelSwitch);
});
} else if (this.getDevice().supportsLedControlRange()) {
// if brightness supported then add a lightbulb for controlling
this.ledBrightnessService = new Service.Lightbulb('LED', 'ledBrightnessService');
this.ledBrightnessService
.getCharacteristic(Characteristic.On)
.onGet(this.isLedOn.bind(this))
.onSet(this.setLedOn.bind(this));
this.ledBrightnessService
.addCharacteristic(new Characteristic.Brightness())
.onGet(this.getLedBrightness.bind(this))
.onSet(this.setLedBrightness.bind(this));
this.addAccessoryService(this.ledBrightnessService);
} else {
this.ledService = this.createStatefulSwitch('LED', 'ledService', this.isLedOn, this.setLedOn);
this.addAccessoryService(this.ledService);
}
}
}
prepareScreenControlService() {
if (this.getDevice().supportsScreenControl()) {
if (this.getDevice().supportsScreenBrightnessList()) {
this.screenLevelControlServices = new Array();
this.getDevice().screenBrightnessList().forEach((level, i) => {
let levelVal = level.value;
let levelDesc = level.description;
let tmpScreenLevelSwitch = this.createStatefulSwitch('Screen - ' + levelDesc, 'screenLevelControlService' + levelVal, () => {
return this.isScreenLevelSwitchOn(levelVal);
}, (value) => {
this.setScreenLevelSwitchOn(value, levelVal);
});
this.addAccessoryService(tmpScreenLevelSwitch);
this.screenLevelControlServices.push(tmpScreenLevelSwitch);
});
} else if (this.getDevice().supportsScreenBrightnessRange()) {
this.screenBrightnessControlService = new Service.Lightbulb('Screen', 'screenBrightnessControlService');
this.screenBrightnessControlService
.getCharacteristic(Characteristic.On)
.onGet(this.isScreenOn.bind(this))
.onSet(this.setScreenOn.bind(this));
this.screenBrightnessControlService
.addCharacteristic(new Characteristic.Brightness())
.onGet(this.getScreenBrightness.bind(this))
.onSet(this.setScreenBrightness.bind(this));
this.addAccessoryService(this.screenBrightnessControlService);
} else {
this.screenService = this.createStatefulSwitch('Screen', 'screenService', this.isScreenOn, this.setScreenOn);
this.addAccessoryService(this.screenService);
}
}
}
prepareShutdownTimerService() {
if (this.getDevice().supportsPowerOffTimer()) {
this.shutdownTimerService = new Service.Lightbulb('Shutdown timer', 'shutdownTimerService');
this.shutdownTimerService
.getCharacteristic(Characteristic.On)
.onGet(this.isShutdownTimerOn.bind(this))
.onSet(this.setShutdownTimerOn.bind(this));
this.shutdownTimerService
.addCharacteristic(new Characteristic.Brightness())
.onGet(this.getShutdownTimerBrightness.bind(this))
.onSet(this.setShutdownTimerBrightness.bind(this));
this.addAccessoryService(this.shutdownTimerService);
}
}
prepareModeControlServices() {
if (this.getDevice().supportsModes()) {
this.addPropValueListWrapper('Mode', Properties.MODE, Properties.POWER);
}
}
prepareFanLevelControlServices() {
if (this.getDevice().supportsFanLevels()) {
this.addPropValueListWrapper('Fan Level', Properties.FAN_LEVEL, Properties.POWER);
}
}
prepareTemperatureService() {
if (this.getDevice().supportsTemperatureReporting()) {
this.temperatureService = new Service.TemperatureSensor('Temperature', 'temperatureService');
this.temperatureService
.setCharacteristic(Characteristic.StatusFault, Characteristic.StatusFault.NO_FAULT)
.setCharacteristic(Characteristic.StatusTampered, Characteristic.StatusTampered.NOT_TAMPERED)
.setCharacteristic(Characteristic.StatusLowBattery, Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL);
this.temperatureService
.getCharacteristic(Characteristic.CurrentTemperature)
.onGet(this.getCurrentTemperature.bind(this));
this.addAccessoryService(this.temperatureService);
}
}
prepareRelativeHumidityService() {
if (this.getDevice().supportsRelativeHumidityReporting()) {
this.relativeHumidityService = new Service.HumiditySensor('Humidity', 'relativeHumidityService');
this.relativeHumidityService
.setCharacteristic(Characteristic.StatusFault, Characteristic.StatusFault.NO_FAULT)
.setCharacteristic(Characteristic.StatusTampered, Characteristic.StatusTampered.NOT_TAMPERED)
.setCharacteristic(Characteristic.StatusLowBattery, Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL);
this.relativeHumidityService
.getCharacteristic(Characteristic.CurrentRelativeHumidity)
.onGet(this.getCurrentRelativeHumidity.bind(this));
this.addAccessoryService(this.relativeHumidityService);
}
}
prepareBatteryService() {
if (this.getDevice().supportsBatteryLevelReporting() || this.getDevice().supportsBatteryPowerReporting() || this.getDevice().supportsChargingStateReporting()) {
this.batteryService = new Service.BatteryService('Battery', 'batteryService');
this.batteryService
.getCharacteristic(Characteristic.StatusLowBattery)
.onGet(this.getStatusLowBatteryState.bind(this));
this.batteryService
.getCharacteristic(Characteristic.ChargingState)
.onGet(this.getBatteryChargingState.bind(this));
if (this.getDevice().supportsBatteryLevelReporting()) {
this.batteryService
.getCharacteristic(Characteristic.BatteryLevel)
.onGet(this.getBatteryLevel.bind(this));
}
this.addAccessoryService(this.batteryService);
}
}
prepareAirQualityService(pm25Breakpoints) {
if (this.getDevice().supportsPm25DensityReporting()) {
// setup the breakpoints
this.preparePm25Breakpoints(pm25Breakpoints);
this.airQualityService = new Service.AirQualitySensor('Air Quality', 'airQualityService');
this.airQualityService
.setCharacteristic(Characteristic.StatusFault, Characteristic.StatusFault.NO_FAULT)
.setCharacteristic(Characteristic.StatusTampered, Characteristic.StatusTampered.NOT_TAMPERED)
.setCharacteristic(Characteristic.StatusLowBattery, Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL);
this.airQualityService
.getCharacteristic(Characteristic.StatusActive)
.onGet(this.getAirQualityStatusActive.bind(this));
this.airQualityService
.getCharacteristic(Characteristic.AirQuality)
.onGet(this.getAirQuality.bind(this));
this.airQualityService
.getCharacteristic(Characteristic.PM2_5Density)
.onGet(this.getPM25Density.bind(this));
if (this.getDevice().supportsAirQualityReporting()) {
this.airQualityService
.getCharacteristic(Characteristic.VOCDensity)
.onGet(this.getVOCDensity.bind(this));
}
this.addAccessoryService(this.airQualityService);
}
}
prepareCarbonDioxideService(co2AbnormalThreshold) {
if (this.getDevice().supportsCo2DensityReporting()) {
// setup the threshold
this.prepareCo2AbnormalThreshold(co2AbnormalThreshold);
this.carbonDioxideService = new Service.CarbonDioxideSensor('Carbon Dioxide', 'carbonDioxideService');
this.carbonDioxideService
.setCharacteristic(Characteristic.StatusFault, Characteristic.StatusFault.NO_FAULT)
.setCharacteristic(Characteristic.StatusTampered, Characteristic.StatusTampered.NOT_TAMPERED)
.setCharacteristic(Characteristic.StatusLowBattery, Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL);
this.carbonDioxideService
.getCharacteristic(Characteristic.StatusActive)
.onGet(this.getCarbonDioxideStatusActive.bind(this));
this.carbonDioxideService
.getCharacteristic(Characteristic.CarbonDioxideDetected)
.onGet(this.getCarbonDioxideDetected.bind(this));
this.carbonDioxideService
.getCharacteristic(Characteristic.CarbonDioxideLevel)
.onGet(this.getCarbonDioxideLevel.bind(this));
this.addAccessoryService(this.carbonDioxideService);
}
}
// ----- characteristics
addChildLockCharacteristic(service) {
if (this.getDevice().supportsChildLock() && service) {
this.childLockCharacteristic = service.addCharacteristic(Characteristic.LockPhysicalControls);
this.childLockCharacteristic
.onGet(this.getLockPhysicalControlsState.bind(this))
.onSet(this.setLockPhysicalControlsState.bind(this));
}
}
addRotationSpeedCharacteristic(service) {
if (this.getDevice().supportsRotationSpeed() && service) {
this.rotationSpeedCharacteristic = service.addCharacteristic(Characteristic.RotationSpeed);
this.rotationSpeedCharacteristic
.onGet(this.getRotationSpeed.bind(this))
.onSet(this.setRotationSpeed.bind(this));
}
}
addBrightnessCharacteristic(service) {
if (this.getDevice().supportsBrightness() && service) {
this.brightnessCharacteristic = service.addCharacteristic(new Characteristic.Brightness());
this.brightnessCharacteristic
.onGet(this.getBrightness.bind(this))
.onSet(this.setBrightness.bind(this));
}
}
addColorTemperatureCharacteristic(service) {
if (this.getDevice().supportsColorTemp() && service) {
this.colorTemperatureCharacteristic = service.addCharacteristic(new Characteristic.ColorTemperature());
this.colorTemperatureCharacteristic
.onGet(this.getColorTemperature.bind(this))
.onSet(this.setColorTemperature.bind(this))
.setProps({
minValue: this.getDevice().getMinColorTempValue(),
maxValue: this.getDevice().getMaxColorTempValue()
});
}
}
addColorCharacteristic(service) {
if (this.getDevice().supportsColor() && service) {
this.hueCharacteristic = service.addCharacteristic(new Characteristic.Hue());
this.hueCharacteristic
.onGet(this.getColorHue.bind(this))
.onSet(this.setColorHue.bind(this));
this.saturationCharacteristic = service.addCharacteristic(new Characteristic.Saturation());
this.saturationCharacteristic
.onGet(this.getColorSaturation.bind(this))
.onSet(this.setColorSaturation.bind(this));
}
}
// ----- actions
prepareActionButtonServices(actionButtonsUserConfig) {
if (this.getDevice().hasActions()) {
this.actionSwitchServices = [];
let actionNames = Object.keys(this.getDevice().getAllActions());
// if user specified an array (object or strings) then filter them to those which actaully exist on the device
if (actionButtonsUserConfig && Array.isArray(actionButtonsUserConfig) && actionButtonsUserConfig.length > 0) {
let userActionKeys = actionButtonsUserConfig;
actionNames = userActionKeys.filter(tmpActionName => actionNames.includes(tmpActionName.action || tmpActionName)); // get only the action keys which actaully exist!
}
// create the action buttons
actionNames.forEach((item, i) => {
let actionName = item.action || item; // get the action name, if array of objects then get "action" else use the item
let actionDisplayName = item.name || this.getDevice().getActionFriendlyName(actionName); // get name from "name" else get friendly name
let actionId = actionName + 'ActionService' + i; // generate action id
let paramValues = item.params || []; // get params from "params" or leave empty
let tmpActionSwitch = this.createStatlessSwitch(actionDisplayName, actionId, (value) => {
this.setActionSwitchOn(value, actionName, paramValues);
});
this.actionSwitchServices[i] = tmpActionSwitch;
this.addAccessoryService(tmpActionSwitch);
});
}
}
/*----------========== HOMEBRIDGE STATE SETTERS/GETTERS ==========----------*/
// buzzer
isBuzzerOn() {
if (this.isMiotDeviceConnected()) {
return this.getDevice().isBuzzerEnabled();
}
return false;
}
setBuzzerOn(value) {
if (this.isMiotDeviceConnected()) {
this.getDevice().setBuzzerEnabled(value);
} else {
throw new HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
}
}
// led
isLedLevelSwitchOn(level) {
if (this.isMiotDeviceConnected() && this.getDevice().isPowerOn()) {
return this.getDevice().getLedValue() === level;
}
return false;
}
setLedLevelSwitchOn(state, level) {
if (this.isMiotDeviceConnected()) {
if (state) {
this.getDevice().setLedValue(level);
}
setTimeout(() => {
this.updateLedLevelSwitches();
}, Constants.BUTTON_RESET_TIMEOUT);
} else {
throw new HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
}
}
isLedOn() {
if (this.isMiotDeviceConnected()) {
return this.getDevice().isLedEnabled();
}
return false;
}
setLedOn(value) {
if (this.isMiotDeviceConnected()) {
if (!value || this.getDevice().isLedEnabled() === false) {
this.getDevice().setLedEnabled(value);
}
} else {
throw new HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
}
}
getLedBrightness() {
if (this.isMiotDeviceConnected()) {
return this.getDevice().getLedPercentage();
}
return 0;
}
setLedBrightness(value) {
if (this.isMiotDeviceConnected()) {
// use debounce to limit the number of calls when the user slides the slider
if (this.ledBrightnessTimeout) clearTimeout(this.ledBrightnessTimeout);
this.ledBrightnessTimeout = setTimeout(() => this.getDevice().setLedPercentage(value), 500);
} else {
throw new HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
}
}
//screen
isScreenLevelSwitchOn(level) {
if (this.isMiotDeviceConnected() && this.getDevice().isPowerOn()) {
return this.getDevice().getScreenBrightnessValue() === level;
}
return false;
}
setScreenLevelSwitchOn(state, level) {
if (this.isMiotDeviceConnected()) {
if (state) {
this.getDevice().setScreenBrightnessValue(level);
}
setTimeout(() => {
this.updateScreenLevelSwitches();
}, Constants.BUTTON_RESET_TIMEOUT);
} else {
throw new HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
}
}
isScreenOn() {
if (this.isMiotDeviceConnected()) {
return this.getDevice().isScreenEnabled();
}
return false;
}
setScreenOn(value) {
if (this.isMiotDeviceConnected()) {
if (!value || this.getDevice().isScreenEnabled() === false) {
this.getDevice().setScreenEnabled(value);
}
} else {
throw new HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
}
}
getScreenBrightness() {
if (this.isMiotDeviceConnected()) {
return this.getDevice().getScreenBrightnessPercentage();
}
return 0;
}
setScreenBrightness(value) {
if (this.isMiotDeviceConnected()) {
// use debounce to limit the number of calls when the user slides the slider
if (this.screenBrightnessTimeout) clearTimeout(this.screenBrightnessTimeout);
this.screenBrightnessTimeout = setTimeout(() => this.getDevice().setScreenBrightnessPercentage(value), 500);
} else {
throw new HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
}
}
// shutdown timer
isShutdownTimerOn() {
if (this.isMiotDeviceConnected()) {
return this.getDevice().isShutdownTimerEnabled();
}
return false;
}
setShutdownTimerOn(value) {
if (this.isMiotDeviceConnected()) {
if (value === false) { // only if disabling, enabling will automatically set it to 100%
this.getDevice().setShutdownTimer(0);
}
} else {
throw new HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
}
}
getShutdownTimerBrightness() {
if (this.isMiotDeviceConnected()) {
return Math.min(this.getDevice().getShutdownTimer(), 100);
}
return 0;
}
setShutdownTimerBrightness(value) {
if (this.isMiotDeviceConnected()) {
this.getDevice().setShutdownTimer(value);
} else {
throw new HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
}
}
// temperature
getCurrentTemperature() {
if (this.isMiotDeviceConnected()) {
return this.getDevice().getTemperature();
}
return 0;
}
// humidity
getCurrentRelativeHumidity() {
if (this.isMiotDeviceConnected()) {
return this.getDevice().getRelativeHumidity();
}
return 0;
}
// battery
getStatusLowBatteryState() {
if (this.getDevice().supportsBatteryLevelReporting()) {
if (this.isMiotDeviceConnected()) {
return this.getDevice().getBatteryLevel() > Constants.BATTERY_LOW_THRESHOLD ? Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL : Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW;
}
}
return Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL;
}
getBatteryChargingState() {
if (this.isMiotDeviceConnected()) {
if (this.getDevice().supportsChargingStateReporting()) {
return this.getDevice().isCharging() ? Characteristic.ChargingState.CHARGING : Characteristic.ChargingState.NOT_CHARGING;
} else if (this.getDevice().supportsBatteryPowerReporting()) {
return this.getDevice().isOnBatteryPower() ? Characteristic.ChargingState.NOT_CHARGING : Characteristic.ChargingState.CHARGING;
}
}
return Characteristic.ChargingState.NOT_CHARGEABLE;
}
getBatteryLevel() {
if (this.isMiotDeviceConnected()) {
return this.getDevice().getBatteryLevel();
}
return 0;
}
// air quality
getAirQualityStatusActive() {
if (this.isMiotDeviceConnected()) {
return true
}
return false;
}
getAirQuality() {
if (this.isMiotDeviceConnected()) {
let pm25Density = this.getDevice().getPm25Density();
if (pm25Density <= this.excellentBreakpoint) {
return Characteristic.AirQuality.EXCELLENT;
} else if (pm25Density > this.excellentBreakpoint && pm25Density <= this.goodBreakpoint) {
return Characteristic.AirQuality.GOOD;
} else if (pm25Density > this.goodBreakpoint && pm25Density <= this.fairBreakpoint) {
return Characteristic.AirQuality.FAIR;
} else if (pm25Density > this.fairBreakpoint && pm25Density <= this.inferiorBreakpoint) {
return Characteristic.AirQuality.INFERIOR;
} else if (pm25Density > this.inferiorBreakpoint) {
return Characteristic.AirQuality.POOR;
}
}
return Characteristic.AirQuality.UNKNOWN;
}
getPM25Density() {
if (this.isMiotDeviceConnected()) {
return this.getDevice().getPm25Density();
}
return 0;
}
getVOCDensity() {
if (this.isMiotDeviceConnected()) {
return this.getDevice().getAirQuality();
}
return 0;
}
// carbon dioxide
getCarbonDioxideStatusActive() {
if (this.isMiotDeviceConnected()) {
return true
}
return false;
}
getCarbonDioxideDetected() {
if (this.isMiotDeviceConnected()) {
let co2Density = this.getDevice().getCo2Density();
if (co2Density > this.co2AbnormalThreshold) {
return Characteristic.CarbonDioxideDetected.CO2_LEVELS_ABNORMAL;
} else {
return Characteristic.CarbonDioxideDetected.CO2_LEVELS_NORMAL;
}
}
return Characteristic.CarbonDioxideDetected.CO2_LEVELS_NORMAL;
}
getCarbonDioxideLevel() {
if (this.isMiotDeviceConnected()) {
return this.getDevice().getCo2Density();
}
return 0;
}
// ----- characteristics
getLockPhysicalControlsState() {
if (this.isMiotDeviceConnected()) {
return this.getDevice().isChildLockActive() ? Characteristic.LockPhysicalControls.CONTROL_LOCK_ENABLED : Characteristic.LockPhysicalControls.CONTROL_LOCK_DISABLED;
}
return Characteristic.LockPhysicalControls.CONTROL_LOCK_DISABLED;
}
setLockPhysicalControlsState(state) {
if (this.isMiotDeviceConnected()) {
let isChildLockActive = state === Characteristic.LockPhysicalControls.CONTROL_LOCK_ENABLED;
this.getDevice().setChildLock(isChildLockActive);
} else {
throw new HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
}
}
getRotationSpeed() {
if (this.isMiotDeviceConnected()) {
return this.getDevice().getFanSpeedPercentage();
}
return 0;
}
setRotationSpeed(value) {
if (this.isMiotDeviceConnected()) {
// use debounce to limit the number of calls when the user slides the rotation slider
if (this.rotationSpeedTimeout) clearTimeout(this.rotationSpeedTimeout);
this.rotationSpeedTimeout = setTimeout(() => this.getDevice().setFanSpeedPercentage(value), 500);
} else {
throw new HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
}
}
getBrightness() {
if (this.isMiotDeviceConnected()) {
return this.getDevice().getBrightness();
}
return 0;
}
setBrightness(brightness) {
if (this.isMiotDeviceConnected()) {
this.getDevice().setBrightness(brightness);
} else {
throw new HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
}
}
getColorTemperature() {
if (this.isMiotDeviceConnected()) {
return this.getDevice().getColorTemp();
}
return this.getDevice().getMinColorTempValue();
}
setColorTemperature(colorTemp) {
if (this.isMiotDeviceConnected()) {
this.getDevice().setColorTemp(colorTemp);
} else {
throw new HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
}
}
getColorHue() {
if (this.isMiotDeviceConnected()) {
return this.getDevice().getHue();
}
return 0;
}
setColorHue(hue) {
if (this.isMiotDeviceConnected()) {
this.getDevice().setHue(hue);
} else {
throw new HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
}
}
getColorSaturation() {
if (this.isMiotDeviceConnected()) {
return this.getDevice().getSaturation();
}
return 0;
}
setColorSaturation(saturation) {
if (this.isMiotDeviceConnected()) {
this.getDevice().setSaturation(saturation);
} else {
throw new HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
}
}
// ----- actions
setActionSwitchOn(state, actionName, paramValues = []) {
if (this.isMiotDeviceConnected()) {
this.getDevice().fireAction(actionName, paramValues);
this.resetActionSwitches();
} else {
throw new HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
}
}
/*----------========== SERVICES STATUS ==========----------*/
// called by index.js on device status update
updateDeviceStatus() {
if (this.buzzerService) this.buzzerService.getCharacteristic(Characteristic.On).updateValue(this.isBuzzerOn());
if (this.ledService) this.ledService.getCharacteristic(Characteristic.On).updateValue(this.isLedOn());
if (this.ledBrightnessService) this.ledBrightnessService.getCharacteristic(Characteristic.On).updateValue(this.isLedOn());
if (this.ledBrightnessService) this.ledBrightnessService.getCharacteristic(Characteristic.Brightness).updateValue(this.getLedBrightness());
if (this.screenService) this.screenService.getCharacteristic(Characteristic.On).updateValue(this.isScreenOn());
if (this.screenBrightnessControlService) this.screenBrightnessControlService.getCharacteristic(Characteristic.On).updateValue(this.isScreenOn());
if (this.screenBrightnessControlService) this.screenBrightnessControlService.getCharacteristic(Characteristic.Brightness).updateValue(this.getScreenBrightness());
if (this.shutdownTimerService) this.shutdownTimerService.getCharacteristic(Characteristic.On).updateValue(this.isShutdownTimerOn());
if (this.shutdownTimerService) this.shutdownTimerService.getCharacteristic(Characteristic.Brightness).updateValue(this.getShutdownTimerBrightness());
if (this.temperatureService) this.temperatureService.getCharacteristic(Characteristic.CurrentTemperature).updateValue(this.getCurrentTemperature());
if (this.relativeHumidityService) this.relativeHumidityService.getCharacteristic(Characteristic.CurrentRelativeHumidity).updateValue(this.getCurrentRelativeHumidity());
if (this.batteryService) this.batteryService.getCharacteristic(Characteristic.StatusLowBattery).updateValue(this.getStatusLowBatteryState());
if (this.batteryService) this.batteryService.getCharacteristic(Characteristic.ChargingState).updateValue(this.getBatteryChargingState());
if (this.batteryService && this.getDevice().supportsBatteryLevelReporting()) this.batteryService.getCharacteristic(Characteristic.BatteryLevel).updateValue(this.getBatteryLevel());
if (this.airQualityService) this.airQualityService.getCharacteristic(Characteristic.StatusActive).updateValue(this.getAirQualityStatusActive());
if (this.airQualityService) this.airQualityService.getCharacteristic(Characteristic.AirQuality).updateValue(this.getAirQuality());
if (this.airQualityService) this.airQualityService.getCharacteristic(Characteristic.PM2_5Density).updateValue(this.getPM25Density());
if (this.airQualityService && this.getDevice().supportsAirQualityReporting()) this.airQualityService.getCharacteristic(Characteristic.VOCDensity).updateValue(this.getVOCDensity());
if (this.carbonDioxideService) this.carbonDioxideService.getCharacteristic(Characteristic.StatusActive).updateValue(this.getCarbonDioxideStatusActive());
if (this.carbonDioxideService) this.carbonDioxideService.getCharacteristic(Characteristic.CarbonDioxideDetected).updateValue(this.getCarbonDioxideDetected());
if (this.carbonDioxideService) this.carbonDioxideService.getCharacteristic(Characteristic.CarbonDioxideLevel).updateValue(this.getCarbonDioxideLevel());
this.updateLedLevelSwitches();
this.updateScreenLevelSwitches();
if (this.childLockCharacteristic) this.childLockCharacteristic.updateValue(this.getLockPhysicalControlsState());
if (this.rotationSpeedCharacteristic) this.rotationSpeedCharacteristic.updateValue(this.getRotationSpeed());
if (this.brightnessCharacteristic) this.brightnessCharacteristic.updateValue(this.getBrightness());
if (this.colorTemperatureCharacteristic) this.colorTemperatureCharacteristic.updateValue(this.getColorTemperature());
if (this.hueCharacteristic) this.hueCharacteristic.updateValue(this.getColorHue());
if (this.saturationCharacteristic) this.saturationCharacteristic.updateValue(this.getColorSaturation());
this.updateCustomServices();
}
/*----------========== MULTI-SWITCH SERVICE HELPERS ==========----------*/
updateLedLevelSwitches() {
if (this.ledLevelControlServices) {
let currentLevel = this.getDevice().getLedValue();
this.ledLevelControlServices.forEach((tmpLedLevelSwitch, i) => {
let level = this.getDevice().ledControlList()[i];
let levelVal = level.value;
let isSwitchOn = (currentLevel === levelVal) && this.getDevice().isPowerOn();
tmpLedLevelSwitch.getCharacteristic(Characteristic.On).updateValue(isSwitchOn);
});
}
}
updateScreenLevelSwitches() {
if (this.screenLevelControlServices) {
let currentLevel = this.getDevice().getScreenBrightnessValue();
this.screenLevelControlServices.forEach((tmpScreenLevelSwitch, i) => {
let level = this.getDevice().screenBrightnessList()[i];
let levelVal = level.value;
let isSwitchOn = (currentLevel === levelVal) && this.getDevice().isPowerOn();
tmpScreenLevelSwitch.getCharacteristic(Characteristic.On).updateValue(isSwitchOn);
});
}
}
resetActionSwitches() {
if (this.actionSwitchServices && this.actionSwitchServices.length > 0) {
setTimeout(() => {
this.actionSwitchServices.forEach((tmpActionSwitch, i) => {
tmpActionSwitch.getCharacteristic(Characteristic.On).updateValue(false);
});
}, Constants.BUTTON_RESET_TIMEOUT);
}
}
/*----------========== SWITCH SERVICE HELPERS ==========----------*/
createStatefulSwitch(name, id, getterFn, setterFn) {
let newStatefulSwitch = new Service.Switch(name, id);
newStatefulSwitch
.getCharacteristic(Characteristic.On)
.onGet(getterFn.bind(this))
.onSet(setterFn.bind(this));
return newStatefulSwitch;
}
/*----------========== STATELESS SWITCH SERVICES HELPERS ==========----------*/
createStatlessSwitch(name, id, setterFn) {
let newStatelessSwitch = new Service.Switch(name, id);
newStatelessSwitch
.getCharacteristic(Characteristic.On)
.onGet(this.isStatelessSwitchOn.bind(this))
.onSet(setterFn.bind(this));
return newStatelessSwitch;
}
isStatelessSwitchOn() {
return false;
}
/*----------========== GETTERS ==========----------*/
getName() {
return this.name;
}
getUuid() {
return this.uuid;
}
getDevice() {
return this.miotDevice;
}
getAccessory() {
return this.accessory;
}
getLogger() {
return this.logger;
}
/*----------========== INFORMATION SERVICE ==========----------*/
updateInformationService(name, manufacturer, model, deviceId, pluginVer) {
if (this.getAccessory()) {
// remove the preconstructed information service, since i will be adding my own
this.getAccessory().removeService(this.getAccessory().getService(Service.AccessoryInformation));
this.informationService = new Service.AccessoryInformation();
this.informationService
.setCharacteristic(Characteristic.Name, name)
.setCharacteristic(Characteristic.Manufacturer, manufacturer)
.setCharacteristic(Characteristic.Model, model)
.setCharacteristic(Characteristic.SerialNumber, deviceId)
.setCharacteristic(Characteristic.FirmwareRevision, pluginVer);
this.addAccessoryService(this.informationService);
}
}
/*----------========== CUSTOM SERVICES ==========----------*/
addPropWrapper(wrapperClass, wrapperName, propName, linkedPropName, configuration) {
if (wrapperClass) {
let prop = this.getDevice().getProperty(propName);
let linkedProp = this.getDevice().hasProperty(linkedPropName) ? this.getDevice().getProperty(linkedPropName) : null;
if (prop) {
let newWrapper = new wrapperClass(wrapperName, this, prop, linkedProp, configuration, this.api, this.logger);
if (newWrapper && newWrapper.isWrapperValid()) {
this.logger.debug(`Adding property wrapper ${wrapperName} of type ${newWrapper.getWrapperType()} for property ${prop.getName()}!`);
this.customServices.push(newWrapper);
} else {
this.logger.debug(`Cannot add ${wrapperName} property wrapper! Invalid property!`);
}
} else {
this.logger.debug(`Cannot add ${wrapperName} property wrapper! Property not found on the device!`);
}
} else {
this.logger.debug(`Cannot add ${wrapperName} property wrapper! Wrong or missing property wrapper class!`);
}
}
addPropValueListWrapper(wrapperName, propName, linkedPropName) {
this.addPropWrapper(PropValueListWrapper, wrapperName, propName, linkedPropName, null);
}
updateCustomServices() {
this.customServices.forEach((custService, i) => {
custService.updateWrapperStatus();
});
}
/*----------========== PROPERTY HELPERS ==========----------*/
preparePm25Breakpoints(pm25Breakpoints) {
// make sure that the provided array is valid
if (pm25Breakpoints) {
if (!Array.isArray(pm25Breakpoints) || pm25Breakpoints.length != 4 ||
pm25Breakpoints[0] >= pm25Breakpoints[1] || pm25Breakpoints[1] >= pm25Breakpoints[2] || pm25Breakpoints[2] >= pm25Breakpoints[3]) {
this.getLogger().warn(`The value specified for the 'pm25Breakpoints' property is invalid! Reverting to default!`);
pm25Breakpoints = [7, 15, 30, 55];
}
} else {
pm25Breakpoints = [7, 15, 30, 55];
}
this.excellentBreakpoint = parseInt(pm25Breakpoints[0]);
this.goodBreakpoint = parseInt(pm25Breakpoints[1]);
this.fairBreakpoint = parseInt(pm25Breakpoints[2]);
this.inferiorBreakpoint = parseInt(pm25Breakpoints[3]);
}
prepareCo2AbnormalThreshold(co2AbnormalThreshold) {
// make sure that the value is valid
if (!Number.isFinite(co2AbnormalThreshold)) {
this.getLogger().warn(`The value specified for the 'co2AbnormalThreshold' property is invalid! Reverting to default!`);
co2AbnormalThreshold = 1000;
}
this.co2AbnormalThreshold = co2AbnormalThreshold;
}
/*----------========== HELPERS ==========----------*/
getPropValue(prop, defaultValue) {
if (prop == undefined) {
return defaultValue;
}
return prop;
}
addAccessoryService(service) {
this.accessory.addService(service);
}
isMiotDeviceConnected() {
return this.getDevice() && this.getDevice().isConnected();
}
}
module.exports = BaseAccessory;