node-miio
Version:
Control Mi Home devices, such as Mi Robot Vacuums, Mi Air Purifiers, Mi Smart Home Gateway (Aqara) and more
351 lines (322 loc) • 8.74 kB
JavaScript
'use strict';
const {ChargingState, AutonomousCharging} = require('abstract-things');
const {
Vacuum,
AdjustableFanSpeed,
AutonomousCleaning,
SpotCleaning,
} = require('abstract-things/climate');
const MiioApi = require('../../device');
const BatteryLevel = require('../capabilities/battery-level');
const checkResult = require('../../checkResult');
/**
* Implementation of the interface used by the Dreame Vacuum. This device
* doesn't use properties via get_prop but instead has a get_properties.
*/
module.exports = class extends (
Vacuum.with(
MiioApi,
BatteryLevel,
AutonomousCharging,
AutonomousCleaning,
SpotCleaning,
AdjustableFanSpeed,
ChargingState
)
) {
static get type() {
return 'miio:vacuum';
}
constructor(options) {
super(options);
this.defineProperty('device_fault', {
name: 'error',
siid: this.miioModel === 'dreame.vacuum.mc1808' ? 3 : 2,
piid: this.miioModel === 'dreame.vacuum.mc1808' ? 1 : 2,
mapper: (e) => {
let message;
switch (e) {
// https://python-miio.readthedocs.io/en/latest/api/miio.integrations.dreame.vacuum.dreamevacuum_miot.html#miio.integrations.dreame.vacuum.dreamevacuum_miot.FaultStatus
case 0:
return null;
default:
message = 'Unknown error ' + e;
}
return {
code: e,
message,
};
},
});
this.defineProperty('device_status', {
name: 'state',
siid: this.miioModel === 'dreame.vacuum.mc1808' ? 3 : 2,
piid: this.miioModel === 'dreame.vacuum.mc1808' ? 2 : 1,
mapper: (s) => {
// https://python-miio.readthedocs.io/en/latest/api/miio.integrations.dreame.vacuum.dreamevacuum_miot.html#miio.integrations.dreame.vacuum.dreamevacuum_miot.DeviceStatus
switch (s) {
case 1:
return 'sweeping';
case 2:
return 'idle';
case 3:
return 'paused';
case 4:
return 'error';
case 5:
return 'returning';
case 6:
return 'charging';
case 7:
return 'mopping';
case 8:
return 'drying';
case 9:
return 'washing';
case 10:
return 'returning-washing';
case 11:
return 'building';
case 12:
return 'sweeping-and-mopping';
case 13:
return 'fully-charged';
case 14:
return 'updating';
}
return 'unknown-' + s;
}
});
// Define the batteryLevel property for monitoring battery
this.defineProperty('battery_level', {
name: 'batteryLevel',
siid: this.miioModel === 'dreame.vacuum.mc1808' ? 2 : 3,
piid: 1,
});
this.defineProperty('charging_state', {
name: 'charging',
siid: this.miioModel === 'dreame.vacuum.mc1808' ? 2 : 3,
piid: 2,
mapper: (s) => {
switch (s) {
case 1: // charging
return true;
case 2: // discharging
return false;
case 4: // charging2
return true;
case 5: // go-charging
return false;
}
}
});
this.defineProperty('cleaning_mode', {
name: 'fanSpeed',
siid: this.miioModel === 'dreame.vacuum.mc1808' ? 18 : 4,
piid: this.miioModel === 'dreame.vacuum.mc1808' ? 6 : 4,
});
this.defineProperty('operating_mode', {
name: 'cleaningMode',
siid: this.miioModel === 'dreame.vacuum.mc1808' ? 18 : 4,
piid: 1,
mapper: (v) => {
switch (v) {
case 1:
return 'paused';
case 2:
return 'cleaning';
case 3:
return 'returning';
case 6:
return 'charging';
case 13:
return 'manual-cleaning';
case 14:
return 'idle';
case 17:
return 'manual-paused';
case 19:
return 'zone-cleaning';
}
return 'unknown-' + v;
}
});
this.defineProperty('water_flow', {
name: 'waterBoxMode',
siid: 4,
piid: 5,
});
this._monitorInterval = 60000;
}
propertyUpdated(key, value, oldValue) {
if (key === 'state') {
// Update charging state
this.updateCharging(value === 'charging');
switch (value) {
case 'cleaning':
case 'spot-cleaning':
case 'zone-cleaning':
case 'room-cleaning':
// The vacuum is cleaning
this.updateCleaning(true);
break;
case 'paused':
// Cleaning has been paused, do nothing special
break;
case 'error':
// An error has occurred, rely on error mapping
this.updateError(this.property('error'));
break;
case 'charging-error':
// Charging error, trigger an error
this.updateError({
code: 'charging-error',
message: 'Error during charging',
});
break;
case 'charger-offline':
// Charger is offline, trigger an error
this.updateError({
code: 'charger-offline',
message: 'Charger is offline',
});
break;
default:
// The vacuum is not cleaning
this.updateCleaning(false);
break;
}
} else if (key === 'fanSpeed') {
this.updateFanSpeed(value);
}
super.propertyUpdated(key, value, oldValue);
}
getDeviceInfo() {
return this.call('miIO.info');
}
async getSerialNumber() {
return 'unknown';
}
getRoomMap() {
return [];
}
getTimer() {
return [];
}
/**
* Start a cleaning session.
*/
activateCleaning() {
return this.call('action', {
did: 'start_clean',
siid: this.miioModel === 'dreame.vacuum.mc1808' ? 3 : 4,
aiid: 1,
in: []
}, {
refresh: ['state'],
refreshDelay: 1000,
}).then(checkResult);
}
/**
* Pause the current cleaning session.
*/
pause() {
return this.deactivateCleaning();
}
/**
* Stop the current cleaning session.
*/
deactivateCleaning() {
return this.call('action', {
did: 'stop_clean',
siid: this.miioModel === 'dreame.vacuum.mc1808' ? 3 : 4,
aiid: 2,
in: []
}, {
refresh: ['state'],
refreshDelay: 1000,
}).then(checkResult);
}
/**
* Stop the current cleaning session and return to charge.
*/
activateCharging() {
return (
this.pause()
.catch(() => this.deactivateCleaning())
// Wait 1 second
.then(() => new Promise((resolve) => setTimeout(resolve, 1000)))
.then(() =>
this.call('action', {
did: 'home',
siid: this.miioModel === 'dreame.vacuum.mc1808' ? 2 : 3,
aiid: 1,
in: []
}, {
refresh: ['state'],
refreshDelay: 1000,
})
)
.then(checkResult)
);
}
/**
* Set the power of the fan. Usually 38, 60 or 77.
*/
changeFanSpeed(speed) {
return this.call('set_properties', [{
did: 'cleaning_mode',
siid: 18,
piid: 6,
value: speed,
}], {
refresh: ['fanSpeed'],
}).then(checkResult);
}
setWaterBoxMode(mode) {
// From https://github.com/marcelrv/XiaomiRobotVacuumProtocol/blob/master/water_box_custom_mode.md
return this.call('set_properties', [{
did: 'water_flow',
siid: 4,
piid: 5,
value: mode,
}], {
refresh: ['waterBoxMode'],
}).then(checkResult);
}
/**
* Activate the find function, will make the device give off a sound.
*/
find() {
return this.call('action', {
did: 'locate',
siid: this.miioModel === 'dreame.vacuum.mc1808' ? 17 : 7,
aiid: 1,
in: []
}).then(() => null);
}
loadProperties(props) {
// We override loadProperties to use get_properties
props = props.map((key) => this._reversePropertyDefinitions[key] || key);
const properties = props.map((prop) => {
const definition = this._propertyDefinitions[prop];
if (definition) {
return {
did: prop,
siid: definition.siid,
piid: definition.piid,
};
}
}).filter(Boolean);
return this.call('get_properties', properties)
.then((result) => {
const mapped = {};
result.forEach((prop) => {
if (prop.code === 0) {
this._pushProperty(mapped, prop.did, prop.value);
}
});
return mapped;
});
}
};