homebridge-valetudo-xiaomi-vacuum
Version:
A Homebridge plugin for Xiaomi Vacuum Cleaner 1st gen (Mi Robot) and 2nd gen (Roborock S50), with Valetudo installed.
373 lines (314 loc) • 10.8 kB
JavaScript
const { VacuumRe } = require('./vacuumre');
const { VacuumValetudo } = require('./vacuumvaletudo');
const types = require('./types');
let Service; let Characteristic;
class ValetudoXiaomiVacuum {
statusCallback(status) {
this.device.getBatteryLevel((error, level) => {
if (error) { return; }
this.batteryService.updateCharacteristic(
Characteristic.BatteryLevel, level,
);
this.batteryService.updateCharacteristic(
Characteristic.StatusLowBattery, level < this.lowBatteryThreshold
? Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW
: Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL,
);
});
this.device.getChargingState((error, state) => {
if (error) { return; }
this.batteryService.updateCharacteristic(
Characteristic.ChargingState,
state === types.CHARGING_STATE.CHARGING
? Characteristic.ChargingState.CHARGING
: Characteristic.ChargingState.NOT_CHARGING,
);
});
this.device.isCleaning((error, state) => {
if (error) { return; }
this.cleanService.updateCharacteristic(Characteristic.On,
state); // cleaning
});
this.device.isGoingHome((error, state) => {
if (error) { return; }
this.goHomeService.updateCharacteristic(Characteristic.On,
state); // driving home
});
this.device.isSpotCleaning((error, state) => {
if (error) { return; }
this.spotCleanService.updateCharacteristic(Characteristic.On,
state); // spot cleaning
});
if (this.device.powerControl) {
if (this.device.powerControl.highSpeed) {
this.device.isHighSpeedMode((error, highSpeedMode) => {
if (error) {
return;
}
this.highSpeedService.updateCharacteristic(Characteristic.On, highSpeedMode);
});
}
if (this.device.powerControl.mop) {
this.device.isMopMode((error, mopMode) => {
if (error) {
return;
}
this.mopService.updateCharacteristic(Characteristic.On, mopMode);
});
}
}
}
getHighSpeedMode(callback) {
this.device.isHighSpeedMode((error, status) => {
if (error) {
callback(error);
} else {
callback(null, status);
}
});
}
getMopMode(callback) {
this.device.isMopMode((error, mopMode) => {
if (error) {
callback(error);
} else {
callback(null, mopMode);
}
});
}
async setFanSpeedRequest(value) {
try {
await this.device.setFanSpeedRequest(value);
} catch (e) {
this.log.error(`Failed to change fan power: ${e}`);
throw (e);
}
}
async setFanSpeed(value, callback) {
this.log.debug(`Setting fan power to ${value}`);
try {
await this.device.setFanSpeed(value);
callback();
} catch (e) {
this.log.error(`Failed to change fan power: ${e}`);
callback();
}
}
setHighSpeedMode(on, callback) {
this.device.setHighSpeedMode(on, callback);
}
setMopMode(on, callback) {
this.device.setMopMode(on, callback);
}
getBattery(callback) {
this.device.getBatteryLevel((error, level) => {
if (error) {
callback(error);
} else {
callback(null, level);
}
});
}
getCharging(callback) {
this.device.getChargingState((error, state) => {
if (error) {
callback(error);
} else if (state === types.CHARGING_STATE.CHARGING) {
callback(null, Characteristic.ChargingState.CHARGING);
} else if (state === types.CHARGING_STATE.DISCHARGING) {
callback(null, Characteristic.ChargingState.NOT_CHARGEABLE);
} else {
callback(null, Characteristic.ChargingState.NOT_CHARGING);
}
});
}
getBatteryLow(callback) {
this.device.getBatteryLevel((error, level) => {
if (error) {
callback(error);
} else if (level < this.lowBatteryThreshold) {
callback(null, Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW);
} else {
callback(null, Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL);
}
});
}
getServices() {
return this.services;
}
async getVersion(callback) {
try {
this.device.version(callback);
} catch (e) {
callback(e);
}
}
async doFind(state, callback) {
this.log.debug('Finding');
if (state) {
this.device.doFind((error) => {
callback(error);
setTimeout(() => {
this.findService.updateCharacteristic(Characteristic.On, false);
}, 250);
});
} else {
callback(null);
}
}
identify(callback) {
this.log.debug('Identifying');
this.device.doFind((error) => {
callback(error);
});
}
async goHome(state, callback) {
const { log } = this;
if (state) {
log.debug('Executing go home');
try {
await this.device.goHome((error) => {
callback(error);
});
} catch (e) {
log.error(`Failed to execute go home: ${e}`);
}
} else {
callback(null);
}
}
isGoingHome(callback) {
this.device.isGoingHome((error, state) => {
this.log.debug(`Is going home? error: ${error}, state: ${state}`);
if (error) {
callback(error);
return;
}
callback(null, state);
});
}
async startCleaning(state, callback) {
if (state) {
this.log.debug('Executing cleaning');
try {
this.device.startCleaning((error) => {
callback(error);
});
} catch (e) {
this.log.error(`Failed to start cleaning: ${e}`);
}
} else {
this.device.stopCleaning((error) => {
callback(error);
});
}
}
isCleaning(callback) {
this.device.isCleaning((error, status) => {
if (error) {
callback(error);
return;
}
callback(null, status);
});
}
async startSpotCleaning(state, callback) {
const { log } = this;
if (state) {
log.debug('Executing spot cleaning');
this.device.startSpotCleaning((error) => {
callback(error);
});
} else {
this.device.stopCleaning(callback);
}
}
isSpotCleaning(callback) {
this.device.isSpotCleaning((error, status) => {
this.log.debug(`Is spot cleaning? error: ${error}, state: ${status}`);
if (error) {
callback(error);
return;
}
callback(null, status);
});
}
constructor(log, config) {
this.services = [];
this.log = log;
this.name = config.name || 'Vacuum';
this.commandFind = config.commandFind || `Find ${this.name}`;
this.commandGoHome = config.commandGoHome || `Go Home, ${this.name}`;
this.commandClean = config.commandClean || `Clean, ${this.name}`;
this.commandSpotClean = config.commandSpotClean || `Spot Clean, ${this.name}`;
this.commandHighSpeedMode = config.commandHighSpeedMode || `High speed mode ${this.name}`;
this.commandMoppingMode = config.commandMoppingMode || `Mopping mode ${this.name}`;
this.commandBattery = config.commandBattery || `${this.name} Battery`;
this.lowBatteryThreshold = 10;
const re = config['legacy-mode'] === true;
this.device = re
? new VacuumRe(this.log, config, (state) => { this.statusCallback(state); })
: new VacuumValetudo(this.log, config, (state) => { this.statusCallback(state); });
this.serviceInfo = new Service.AccessoryInformation();
this.serviceInfo
.setCharacteristic(Characteristic.Manufacturer, 'Xiaomi')
.setCharacteristic(Characteristic.Model, 'Roborock');
this.serviceInfo.getCharacteristic(Characteristic.FirmwareRevision)
.on('get', (callback) => { this.getVersion(callback); });
this.services.push(this.serviceInfo);
this.findService = new Service.Switch(this.commandFind, 'identify');
this.findService.getCharacteristic(Characteristic.On)
.on('set', (value, callback) => { this.doFind(value, callback); });
this.services.push(this.findService);
this.goHomeService = new Service.Switch(this.commandGoHome, 'home');
this.goHomeService.getCharacteristic(Characteristic.On)
.on('set', (value, callback) => {
this.goHome(value, callback);
})
.on('get', (callback) => { this.isGoingHome(callback); });
this.services.push(this.goHomeService);
this.cleanService = new Service.Switch(this.commandClean, 'clean');
this.cleanService.getCharacteristic(Characteristic.On)
.on('set', (value, callback) => { this.startCleaning(value, callback); })
.on('get', (callback) => { this.isCleaning(callback); });
this.services.push(this.cleanService);
this.spotCleanService = new Service.Switch(this.commandSpotClean, 'spotclean');
this.spotCleanService.getCharacteristic(Characteristic.On)
.on('set', (value, callback) => { this.startSpotCleaning(value, callback); })
.on('get', (callback) => { this.isSpotCleaning(callback); });
this.services.push(this.spotCleanService);
if (this.device.powerControl) {
if (this.device.powerControl.highSpeed) {
this.highSpeedService = new Service.Switch(this.commandHighSpeedMode, 'highspeed');
this.highSpeedService.getCharacteristic(Characteristic.On)
.on('set', (value, callback) => { this.setHighSpeedMode(value, callback); })
.on('get', (callback) => { this.getHighSpeedMode(callback); });
this.services.push(this.highSpeedService);
}
if (this.device.powerControl.mop) {
this.mopService = new Service.Switch(this.commandMoppingMode, 'mopspeed');
this.mopService.getCharacteristic(Characteristic.On)
.on('set', (value, callback) => { this.setMopMode(value, callback); })
.on('get', (callback) => { this.getMopMode(callback); });
this.services.push(this.mopService);
}
}
this.batteryService = new Service.BatteryService(this.commandBattery);
this.batteryService
.getCharacteristic(Characteristic.BatteryLevel)
.on('get', (callback) => { this.getBattery(callback); });
this.batteryService
.getCharacteristic(Characteristic.ChargingState)
.on('get', (callback) => { this.getCharging(callback); });
this.batteryService
.getCharacteristic(Characteristic.StatusLowBattery)
.on('get', (callback) => { this.getBatteryLow(callback); });
this.services.push(this.batteryService);
this.device.updateStatus(true);
}
}
module.exports = (api) => {
Service = api.hap.Service;
Characteristic = api.hap.Characteristic;
api.registerAccessory('homebridge-valetudo-xiaomi-vacuum', 'ValetudoXiaomiVacuum', ValetudoXiaomiVacuum);
};