Skip to content

Commit

Permalink
feat: QuirksCapability
Browse files Browse the repository at this point in the history
  • Loading branch information
Hypfer committed Jan 10, 2022
1 parent a568c03 commit 79deeb1
Show file tree
Hide file tree
Showing 19 changed files with 1,145 additions and 522 deletions.
49 changes: 49 additions & 0 deletions backend/lib/core/Quirk.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
Quirks are Vendor-specific settings that don't (yet) fit into a generic capability
due to them being something that only one vendor/model does.
They are basic toggles/selects and may change at any time.
Availability of quirks may also depend on the firmware version of the robot.
It is not recommended using them in automations etc. They're just here for the Valetudo UI
If there are multiple similar quirks of different vendors, they shall be merged into a capability so
that we don't undermine the core idea of Valetudo being a generic abstraction.
One could probably also consider this a staging area for new stuff
*/

class Quirk {
/**
*
* @param {object} options
* @param {string} options.id
* @param {Array<string>} options.options
* @param {string} options.title
* @param {string} options.description
* @param {() => Promise<string>} options.getter
* @param {(value: string) => Promise<void>} options.setter
*/
constructor(options) {
this.id = options.id;
this.options = options.options;
this.title = options.title;
this.description = options.description;
this.getter = options.getter;
this.setter = options.setter;
}

/**
*
* @return {Promise<{options: Array<string>, description: string, id: string, title: string, value: string}>}
*/
async serialize() {
return {
id: this.id,
options: this.options,
title: this.title,
description: this.description,
value: await this.getter()
};
}
}

module.exports = Quirk;
93 changes: 93 additions & 0 deletions backend/lib/core/capabilities/QuirksCapability.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
const Capability = require("./Capability");
const Logger = require("../../Logger");

/**
*
* @template {import("../ValetudoRobot")} T
* @extends Capability<T>
*/
class QuirksCapability extends Capability {
/**
*
* @param {object} options
* @param {T} options.robot
* @param {Array<import("../Quirk")>} [options.quirks]
* @class
*/
constructor(options) {
super(options);

this.quirks = options.quirks;
}

/**
* @returns {Promise<Array<{options: Array<string>, description: string, id: string, title: string, value: string}>>}
*/
async getQuirks() {
let quirkFetchTimeout;
let serializedQuirks = [];

await Promise.race([
Promise.all(this.quirks.map(q => {
return new Promise((resolve, reject) => {
q.serialize().then((serializedQuirk) => {
serializedQuirks.push(serializedQuirk);

resolve();
}).catch(err => {
Logger.warn(`Error while serializing quirk with ID ${q.id}`, err);

//We're swallowing this error so that quirks that do work are still available
//in the UI. Still, if this message appears in the logs, it will need investigation
resolve();
});
});
})),
new Promise((resolve, reject) => {
quirkFetchTimeout = setTimeout(() => {
reject(new Error("Timeout while fetching quirks"));
}, 8000);
})
]);
clearTimeout(quirkFetchTimeout);

return serializedQuirks;
}

/**
*
* @param {string} id
* @param {string} value
* @returns {Promise<void>}
*/
async setQuirkValue(id, value) {
const quirk = this.quirks.find(q => {
return q.id === id;
});

if (!quirk) {
throw new Error(`No quirk with ID ${id}`);
} else {
return quirk.setter(value);
}
}

/**
* @returns {QuirksCapabilityProperties}
*/
getProperties() {
return {};
}

getType() {
return QuirksCapability.TYPE;
}
}

QuirksCapability.TYPE = "QuirksCapability";

module.exports = QuirksCapability;

/**
* @typedef {object} QuirksCapabilityProperties
*/
1 change: 1 addition & 0 deletions backend/lib/core/capabilities/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ module.exports = {
PendingMapChangeHandlingCapability: require("./PendingMapChangeHandlingCapability"),
PersistentMapControlCapability: require("./PersistentMapControlCapability"),
PresetSelectionCapability: require("./PresetSelectionCapability"),
QuirksCapability: require("./QuirksCapability"),
SensorCalibrationCapability: require("./SensorCalibrationCapability"),
SpeakerTestCapability: require("./SpeakerTestCapability"),
SpeakerVolumeControlCapability: require("./SpeakerVolumeControlCapability"),
Expand Down
226 changes: 2 additions & 224 deletions backend/lib/robots/dreame/Dreame1CValetudoRobot.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const capabilities = require("./capabilities");

const DreameMiotServices = require("./DreameMiotServices");
const DreameValetudoRobot = require("./DreameValetudoRobot");
const entities = require("../../entities");
const Logger = require("../../Logger");
Expand All @@ -8,231 +9,8 @@ const ValetudoSelectionPreset = require("../../entities/core/ValetudoSelectionPr

const stateAttrs = entities.state.attributes;

//https://miot-spec.org/miot-spec-v2/instance?type=urn:miot-spec-v2:device:vacuum:0000A006:dreame-mc1808:1
const MIOT_SERVICES = Object.freeze({
ERROR: {
SIID: 22,
PROPERTIES: {
CODE: {
PIID: 1
}
}
},
VACUUM_2: {
SIID: 18,
PROPERTIES: {
MODE: {
PIID: 1
},
FAN_SPEED: {
PIID: 6
},
WATER_USAGE: {
PIID: 20
},
WATER_TANK_ATTACHMENT: {
PIID: 9
},
TASK_STATUS: {
PIID: 18 // if robot has a task: value = 0
},
ADDITIONAL_CLEANUP_PROPERTIES: {
PIID: 21
},
PERSISTENT_MAPS: {
PIID: 23
},

CURRENT_STATISTICS_TIME: {
PIID: 2
},
CURRENT_STATISTICS_AREA: {
PIID: 3
},

TOTAL_STATISTICS_TIME: {
PIID: 13
},
TOTAL_STATISTICS_COUNT: {
PIID: 14
},
TOTAL_STATISTICS_AREA: {
PIID: 15
}
},
ACTIONS: {
START: {
AIID: 1
},
PAUSE: {
AIID: 2
}
}
},
MANUAL_CONTROL: {
SIID: 21,
PROPERTIES: {
ANGLE: {
PIID: 1
},
VELOCITY: {
PIID: 2
}
},
ACTIONS: {
MOVE: { // first MOVE action will "start" manual control
AIID: 1
},
STOP: {
AIID: 2
}
}
},
BATTERY: {
SIID: 2,
PROPERTIES: {
LEVEL: {
PIID: 1
},
CHARGING: {
PIID: 2
}
},
ACTIONS: {
START_CHARGE: {
AIID: 1
}
}
},
LOCATE: {
SIID: 17,
ACTIONS: {
LOCATE: {
AIID: 1
},
VOLUME_TEST: {
AIID: 3
}
}
},
VOICE: {
SIID: 24,
PROPERTIES: {
VOLUME: {
PIID: 1
},
ACTIVE_VOICEPACK: {
PIID: 3
},
URL: {
PIID: 4
},
HASH: {
PIID: 5
},
SIZE: {
PIID: 6
}
},
ACTIONS: {
DOWNLOAD_VOICEPACK: {
AIID: 2
}
}
},
AUDIO: {
SIID: 7,
PROPERTIES: {
VOLUME: {
PIID: 1
}
},
ACTIONS: {
VOLUME_TEST: {
AIID: 3
}
}
},
MAIN_BRUSH: {
SIID: 26,
PROPERTIES: {
TIME_LEFT: { //Hours
PIID: 1
},
PERCENT_LEFT: {
PIID: 2
}
},
ACTIONS: {
RESET: {
AIID: 1
}
}
},
SIDE_BRUSH: {
SIID: 28,
PROPERTIES: {
TIME_LEFT: { //Hours
PIID: 1
},
PERCENT_LEFT: {
PIID: 2
}
},
ACTIONS: {
RESET: {
AIID: 1
}
}
},
FILTER: {
SIID: 27,
PROPERTIES: {
TIME_LEFT: { //Hours
PIID: 2
},
PERCENT_LEFT: {
PIID: 1 //It's only swapped for the filter for some reason..
}
},
ACTIONS: {
RESET: {
AIID: 1
}
}
},


MAP: {
SIID: 23,
PROPERTIES: {
MAP_DATA: {
PIID: 1
},
FRAME_TYPE: { //Can be char I or P (numbers)
PIID: 2
},
CLOUD_FILE_NAME: {
PIID: 3
},
MAP_DETAILS: {
PIID: 4
},

ACTION_RESULT: {
PIID: 6 //TODO: validate
}
},
ACTIONS: {
POLL: {
AIID: 1
},
EDIT: {
AIID: 2
}
}
}
});
const MIOT_SERVICES = DreameMiotServices["1C"];



Expand Down
Loading

0 comments on commit 79deeb1

Please sign in to comment.