eufy-robovac
Version:
NodeJS library to control Eufy RoboVac
320 lines (277 loc) • 8.96 kB
text/typescript
import TuyAPI from 'tuyapi';
export enum CleanSpeed {
STANDARD = 'Standard',
BOOST_IQ = 'Boost_IQ',
MAX = 'Max',
NO_SUCTION = 'No_suction'
}
export enum ErrorCode {
NO_ERROR = 'no_error',
STUCK_5_MIN = 'Stuck_5_min',
CRASH_BAR_STUCK = 'Crash_bar_stuck',
SENSOR_DIRTY = 'sensor_dirty',
NOT_ENOUGH_POWER = 'N_enough_pow',
WHEEL_STUCK = 'Wheel_stuck',
S_BRUSH_STUCK = 'S_brush_stuck',
FAN_STUCK = 'Fan_stuck',
R_BRUSH_STUCK = 'R_brush_stuck'
}
export enum WorkStatus {
RUNNING = 'Running', // Cleaning
STAND_BY = 'standby', // Not in the dock, paused
SLEEPING = 'Sleeping', // Not in the dock - goes into this state after being paused for a while
CHARGING = 'Charging', // In the dock, charging
COMPLETED = 'completed', // In the dock, full charged
RECHARGE_NEEDED = 'Recharge' // Going home because battery is depleted or home was pressed
}
export enum Direction {
FORWARD = 'forward',
BACKWARD = 'backward',
LEFT = 'left',
RIGHT = 'right'
}
export enum WorkMode {
AUTO = 'auto',
SMALL_ROOM = 'SmallRoom',
SPOT = 'Spot',
EDGE = 'Edge',
NO_SWEEP = 'Nosweep'
}
export interface StatusResponse {
devId: string,
dps: {
"1": boolean,
"2": boolean,
"3": string,
"5": string,
"15": string,
"101": boolean,
"102": string,
"103": boolean,
"104": number,
"106": string,
}
}
export interface RoboVacConfig {
deviceId: string
localKey: string
ip?: string
port?: number
}
export class RoboVac {
api: any;
PLAY_PAUSE = '2';
DIRECTION = '3';
WORK_MODE = '5';
WORK_STATUS = '15';
GO_HOME = '101';
CLEAN_SPEED = '102';
FIND_ROBOT = '103';
BATTERY_LEVEL = '104';
ERROR_CODE = '106';
connected: boolean = false;
debugLog: boolean;
statuses: StatusResponse = null;
lastStatusUpdate: number = null;
maxStatusUpdateAge: number = 1000 * (1 * 30); //30 Seconds
timeoutDuration: number;
constructor(config: RoboVacConfig, debugLog: boolean = false, timeoutDuration = 2) {
this.debugLog = debugLog;
if (!config.deviceId) {
throw new Error('You must pass through deviceId');
}
this.api = new TuyAPI(
{
id: config.deviceId,
key: config.localKey,
ip: config.ip,
port: config.port || 6668,
version: '3.3',
issueRefreshOnConnect: true
}
);
this.timeoutDuration = timeoutDuration;
this.api.on('error', (error: any) => {
if (debugLog) {
console.error('Robovac Error', JSON.stringify(error, null, 4));
}
});
this.api.on('connected', () => {
this.connected = true;
if (debugLog) {
console.log("Connected!");
}
});
this.api.on('disconnected', () => {
this.connected = false;
if (debugLog) {
console.log('Disconnected!');
}
});
this.api.on('dp-refresh', data => {
if (debugLog) {
console.log('DP_REFRESH data from device: ', data);
console.log('Status Updated!');
}
});
this.api.on('data', (data: StatusResponse) => {
if (debugLog) {
console.log('Data from device:', data);
console.log('Status Updated!');
}
});
}
async connect() {
if (!this.connected) {
if (this.debugLog) {
console.log('Connecting...');
}
await this.api.connect();
}
}
async disconnect() {
if (this.debugLog) {
console.log('Disconnecting...');
}
await this.api.disconnect();
}
async doWork(work: () => Promise<any>): Promise<any> {
if (!this.api.device.id || !this.api.device.ip) {
if (this.debugLog) {
console.log('Looking for device...');
}
try {
await this.api.find({ timeout: this.timeoutDuration });
if (this.debugLog) {
console.log(`Found device ${this.api.device.id} at ${this.api.device.ip}`);
}
} catch (err) {
console.log(err);
}
}
await this.connect();
return await work();
}
async getStatuses(force: boolean = false): Promise<{ devId: string, dps: { [key: string]: string | boolean | number } }> {
if (force || (new Date()).getTime() - this.lastStatusUpdate > this.maxStatusUpdateAge) {
return await this.doWork(async () => {
this.statuses = await this.api.get({ schema: true });
this.lastStatusUpdate = (new Date()).getTime();
return this.statuses;
});
} else {
return this.statuses;
}
}
async getCleanSpeed(force: boolean = false): Promise<CleanSpeed> {
let statuses = await this.getStatuses(force);
return <CleanSpeed>statuses.dps[this.CLEAN_SPEED];
}
async setCleanSpeed(cleanSpeed: CleanSpeed) {
await this.doWork(async () => {
await this.set({
[this.CLEAN_SPEED]: cleanSpeed
})
});
}
async getPlayPause(force: boolean = false): Promise<boolean> {
let statuses = await this.getStatuses(force);
return <boolean>statuses.dps[this.PLAY_PAUSE];
}
async setPlayPause(state: boolean) {
await this.doWork(async () => {
await this.set({
[this.PLAY_PAUSE]: state
})
});
}
async play() {
await this.setPlayPause(true);
}
async pause() {
await this.setPlayPause(true);
}
async getWorkMode(force: boolean = false): Promise<WorkMode> {
let statuses = await this.getStatuses(force);
return <WorkMode>statuses.dps[this.WORK_MODE];
}
async setWorkMode(workMode: WorkMode) {
await this.doWork(async () => {
if (this.debugLog) {
console.log(`Setting WorkMode to ${workMode}`);
}
await this.set({
[this.WORK_MODE]: workMode
})
});
}
async startCleaning(force: boolean = false) {
if (this.debugLog) {
console.log('Starting Cleaning', JSON.stringify(await this.getStatuses(force), null, 4));
}
await this.setWorkMode(WorkMode.AUTO);
if (this.debugLog) {
console.log('Cleaning Started!');
}
}
async getWorkStatus(force: boolean = false): Promise<WorkStatus> {
let statuses = await this.getStatuses(force);
return <WorkStatus>statuses.dps[this.WORK_STATUS];
}
async setWorkStatus(workStatus: WorkStatus) {
await this.doWork(async () => {
await this.set({
[this.WORK_STATUS]: workStatus
})
});
}
async goHome() {
await this.doWork(async () => {
await this.set({
[this.GO_HOME]: true
})
});
}
async setFindRobot(state: boolean) {
return await this.doWork(async () => {
await this.set({
[this.FIND_ROBOT]: state
})
});
}
async getFindRobot(force: boolean = false) {
let statuses = await this.getStatuses(force);
return <boolean>statuses.dps[this.FIND_ROBOT];
}
async getBatteyLevel(force: boolean = false) {
let statuses = await this.getStatuses(force);
return <number>statuses.dps[this.BATTERY_LEVEL];
}
async getErrorCode(force: boolean = false): Promise<ErrorCode> {
let statuses = await this.getStatuses(force);
return <ErrorCode>statuses.dps[this.ERROR_CODE];
}
async set(data: { [key: string]: string | number | boolean }) {
if (this.debugLog) {
console.log(`Setting: ${JSON.stringify(data, null, 4)}`);
}
return await this.api.set({
multiple: true,
data: data
});
}
formatStatus() {
console.log(`
-- Status Start --
- Play/Pause: ${(this.statuses.dps as any)[this.PLAY_PAUSE]}
- Direction: ${(this.statuses.dps as any)[this.DIRECTION]}
- Work Mode: ${(this.statuses.dps as any)[this.WORK_MODE]}
- Go Home: ${(this.statuses.dps as any)[this.GO_HOME]}
- Clean Speed: ${(this.statuses.dps as any)[this.CLEAN_SPEED]}
- Find Robot: ${(this.statuses.dps as any)[this.FIND_ROBOT]}
- Battery Level: ${(this.statuses.dps as any)[this.BATTERY_LEVEL]}
- Error Code: ${(this.statuses.dps as any)[this.ERROR_CODE]}
-- Status End --
`);
}
}