homebridge-xiaomi-roborock-vacuum
Version:
Xiaomi Vacuum Cleaner - 1st (Mi Robot), 2nd (Roborock S50 + S55), 3rd Generation (Roborock S6) and S5 Max - plugin for Homebridge.
148 lines • 6.56 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DeviceManager = void 0;
const rxjs_1 = require("rxjs");
const miio_1 = __importDefault(require("../miio"));
const constants_1 = require("../utils/constants");
const GET_STATE_INTERVAL_MS = 30000; // 30s
class DeviceManager {
hap;
log;
internalDevice$ = new rxjs_1.BehaviorSubject(undefined);
ip;
token;
internalErrorChanged$ = new rxjs_1.Subject();
internalStateChanged$ = new rxjs_1.Subject();
errorChanged$ = this.internalErrorChanged$.pipe((0, rxjs_1.distinct)());
stateChanged$ = this.internalStateChanged$.asObservable();
deviceConnected$ = this.internalDevice$.pipe((0, rxjs_1.filter)(Boolean));
connectingPromise = null;
connectRetry = setTimeout(() => void 0, 100); // Noop timeout only to initialise the property
constructor(hap, log, config) {
this.hap = hap;
this.log = log;
if (!config.ip) {
throw new Error("You must provide an ip address of the vacuum cleaner.");
}
this.ip = config.ip;
if (!config.token) {
throw new Error("You must provide a token of the vacuum cleaner.");
}
this.token = config.token;
this.connect().catch(() => {
// Do nothing in the catch because this function already logs the error internally and retries after 2 minutes.
});
}
get model() {
return this.internalDevice$.value?.miioModel || "unknown model";
}
get state() {
return this.property("state");
}
get isCleaning() {
return constants_1.cleaningStatuses.includes(this.state);
}
get isPaused() {
return this.state === "paused";
}
get device() {
if (!this.internalDevice$.value) {
throw new Error("Not connected yet");
}
return this.internalDevice$.value;
}
property(propertyName) {
return this.device.property(propertyName);
}
async ensureDevice(callingMethod) {
try {
if (!this.internalDevice$.value) {
const errMsg = `${callingMethod} | No vacuum cleaner is discovered yet.`;
this.log.error(errMsg);
throw new Error(errMsg);
}
// checking if the device has an open socket it will fail retrieving it if not
// https://github.com/aholstenson/miio/blob/master/lib/network.js#L227
const socket = this.internalDevice$.value.handle.api.parent.socket;
this.log.debug(`DEB ensureDevice | ${this.model} | The socket is still on. Reusing it.`);
}
catch (error) {
const err = error;
if (/destroyed/i.test(err.message) ||
/No vacuum cleaner is discovered yet/.test(err.message)) {
this.log.info(`INF ensureDevice | ${this.model} | The socket was destroyed or not initialised, initialising the device`);
await this.connect();
}
else {
this.log.error(err.message, err);
throw err;
}
}
}
async connect() {
if (this.connectingPromise === null) {
// if already trying to connect, don't trigger yet another one
this.connectingPromise = this.initializeDevice().catch((error) => {
this.log.error(`ERR connect | miio.device, next try in 2 minutes | ${error}`);
clearTimeout(this.connectRetry);
// Using setTimeout instead of holding the promise. This way we'll keep retrying but not holding the other actions
this.connectRetry = setTimeout(() => this.connect().catch(() => { }), 120000);
throw error;
});
}
try {
await this.connectingPromise;
clearTimeout(this.connectRetry);
}
finally {
this.connectingPromise = null;
}
}
async initializeDevice() {
this.log.debug("DEB getDevice | Discovering vacuum cleaner");
const device = await miio_1.default.device({ address: this.ip, token: this.token });
if (device.matches("type:vaccuum")) {
this.internalDevice$.next(device);
this.log.setModel(this.model);
this.log.info("STA getDevice | Connected to: %s", this.ip);
this.log.info("STA getDevice | Model: " + this.model);
this.log.info("STA getDevice | State: " + this.property("state"));
this.log.info("STA getDevice | FanSpeed: " + this.property("fanSpeed"));
this.log.info("STA getDevice | BatteryLevel: " + this.property("batteryLevel"));
this.device.on("errorChanged", (error) => this.internalErrorChanged$.next(error));
this.device.on("stateChanged", (state) => this.internalStateChanged$.next(state));
// Refresh the state every 30s so miio maintains a fresh connection (or recovers connection if lost until we fix https://github.com/homebridge-xiaomi-roborock-vacuum/homebridge-xiaomi-roborock-vacuum/issues/81)
(0, rxjs_1.timer)(0, GET_STATE_INTERVAL_MS).pipe((0, rxjs_1.exhaustMap)(() => this.getState()));
}
else {
const model = (device || {}).miioModel;
this.log.error(`Device "${model}" is not registered as a vacuum cleaner! If you think it should be, please open an issue at https://github.com/homebridge-xiaomi-roborock-vacuum/homebridge-xiaomi-roborock-vacuum/issues/new and provide this line.`);
this.log.debug(device);
device.destroy();
}
}
async getState() {
try {
await this.ensureDevice("getState");
await this.device.poll();
const state = await this.device.state();
this.log.debug(`DEB getState | ${this.model} | State %j | Props %j`, state, this.device.properties);
for (const key in state) {
if (key === "error") {
this.internalErrorChanged$.next(state[key]);
}
else {
this.internalStateChanged$.next({ key, value: state[key] });
}
}
}
catch (err) {
this.log.error(`getState | %j`, err);
}
}
}
exports.DeviceManager = DeviceManager;
//# sourceMappingURL=device_manager.js.map