Skip to content

Commit

Permalink
feat: Add voice pack managemenet capability (#725)
Browse files Browse the repository at this point in the history
* Add voice pack managemenet capability

* Add hash parameter to voice pack capability download method

* Add forgotten lang parameter to voice pack capability enable stock

* Add default impl for voice pack download methods

* Use options object for voice pack download params

* Add voice pack capability router

* Add more default implementations for voice pack management capability

* getCurrentVoiceLanguage returns a string

* viomi: Implement ViomiVoicePackManagementCapability

* viomi: Return progress null if no operation in progress

* Simplify voice pack capability, only allow custom pack downloads

As discussed in #725

* Use new ValetudoVoicePackOperationStatus entity to describe status

* Voice pack: make getVoicePackOperationStatus abstract

* Voice pack: use "url" instead of "presignedUrl"
  • Loading branch information
depau authored Mar 4, 2021
1 parent a8a0b39 commit c953e2b
Show file tree
Hide file tree
Showing 9 changed files with 204 additions and 3 deletions.
51 changes: 51 additions & 0 deletions lib/core/capabilities/VoicePackManagementCapability.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
const NotImplementedError = require("../NotImplementedError");
const Capability = require("./Capability");

class VoicePackManagementCapability extends Capability {
/**
* Returns the current applied voice pack language.
*
* @abstract
* @returns {Promise<string>}
*/
async getCurrentVoiceLanguage() {
throw new NotImplementedError();
}

/**
* This method should instruct the vacuum to download a voice pack from `presignedUrl`.
* The actual specifications of what exactly is hosted behind presignedUrl depend on the specific vacuum model.
* The same goes for the hash, the user should provide a hash or signature as expected by the vacuum.
*
* @abstract
* @param {object} options
* @param {string} options.url
* @param {string} [options.language]
* @param {string} [options.hash]
* @returns {Promise<void>}
*/
async downloadVoicePack(options) {
throw new NotImplementedError();
}

/**
* This method should return the status of the current voice pack operation, if one is ongoing.
*
* @abstract
* @returns {Promise<import("../../entities/core/ValetudoVoicePackOperationStatus")>}
*/
async getVoicePackOperationStatus() {
throw new NotImplementedError();
}

/**
* @returns {string}
*/
getType() {
return VoicePackManagementCapability.TYPE;
}
}

VoicePackManagementCapability.TYPE = "VoicePackManagementCapability";

module.exports = VoicePackManagementCapability;
3 changes: 2 additions & 1 deletion lib/core/capabilities/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ module.exports = {
RawCommandCapability: require("./RawCommandCapability"),
DoNotDisturbCapability: require("./DoNotDisturbCapability"),
CarpetModeControlCapability: require("./CarpetModeControlCapability"),
SpeakerTestCapability: require("./SpeakerTestCapability")
SpeakerTestCapability: require("./SpeakerTestCapability"),
VoicePackManagementCapability: require("./VoicePackManagementCapability"),
};
39 changes: 39 additions & 0 deletions lib/entities/core/ValetudoVoicePackOperationStatus.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
const SerializableEntity = require("../SerializableEntity");

/**
* @class ValetudoVoicePackOperationStatus
* @property {ValetudoVoicePackOperationStatusType} type
* @property {number} [progress]
*/
class ValetudoVoicePackOperationStatus extends SerializableEntity {
/**
* This entity represents the status of a voice pack operation.
*
* @param {object} options
* @param {ValetudoVoicePackOperationStatusType} options.type
* @param {number} [options.progress] represents the download or installation progress in the range 0-100
* @param {object} [options.metaData]
* @class
*/
constructor(options) {
super(options);

this.type = options.type;
this.progress = options.progress;
}
}


/**
* @typedef {string} ValetudoVoicePackOperationStatusType
* @enum {string}
*
*/
ValetudoVoicePackOperationStatus.TYPE = Object.freeze({
IDLE: "idle",
DOWNLOADING: "downloading",
INSTALLING: "installing",
});


module.exports = ValetudoVoicePackOperationStatus;
4 changes: 4 additions & 0 deletions lib/robots/viomi/ViomiValetudoRobot.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ class ViomiValetudoRobot extends MiioValetudoRobot {
this.registerCapability(new capabilities.ViomiZoneCleaningCapability({
robot: this
}));

this.registerCapability(new capabilities.ViomiVoicePackManagementCapability({
robot: this
}));
}

setEmbeddedParameters() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
const VoicePackManagementCapability = require("../../../core/capabilities/VoicePackManagementCapability");
const ValetudoVoicePackOperationStatus = require("../../../entities/core/ValetudoVoicePackOperationStatus");

class ViomiVoicePackManagementCapability extends VoicePackManagementCapability {
/**
* Returns the current applied voice pack language.
*
* @abstract
* @returns {Promise<string>}
*/
async getCurrentVoiceLanguage() {
const res = await this.robot.sendCommand("get_downloadstatus", []);
// @ts-ignore
// noinspection JSUnresolvedVariable
return res.curVoice;
}

/**
* This method should instruct the vacuum to download a voice pack from `presignedUrl`.
* The actual specifications of what exactly is hosted behind presignedUrl depend on the specific vacuum model.
* The same goes for the hash, the user should provide a hash or signature as expected by the vacuum.
*
* @param {object} options
* @param {string} options.url
* @param {string} [options.language]
* @param {string} [options.hash]
* @returns {Promise<void>}
*/
async downloadVoicePack(options) {
let args = ["it", options.url, "viomi doesn't even bother blabla"];
if (options.language) {
args[0] = options.language;
}
if (options.hash) {
args[2] = options.hash;
}

await this.robot.sendCommand("download_voice", args);
}

/**
* This method should return the status of the current voice pack operation, if one is ongoing.
*
* @returns {Promise<ValetudoVoicePackOperationStatus>}
*/
async getVoicePackOperationStatus() {
let statusOptions = {
type: ValetudoVoicePackOperationStatus.TYPE.IDLE,
progress: undefined,
};
const res = await this.robot.sendCommand("get_downloadstatus", []);

// noinspection JSUnresolvedVariable
if (res.targetVoice !== "") {
// noinspection JSUnresolvedVariable
if (res.progress === 100 || res.targetVoice === "en" || res.targetVoice === "zh") {
// en and zh are built-in and won't be downloaded, even if a URL is provided.
statusOptions.type = ValetudoVoicePackOperationStatus.TYPE.INSTALLING;
} else {
statusOptions.type = ValetudoVoicePackOperationStatus.TYPE.DOWNLOADING;
}
// noinspection JSUnresolvedVariable
statusOptions.progress = res.progress;
}

// noinspection JSUnresolvedVariable
return new ValetudoVoicePackOperationStatus(statusOptions);
}
}

module.exports = ViomiVoicePackManagementCapability;
1 change: 1 addition & 0 deletions lib/robots/viomi/capabilities/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ module.exports = {
ViomiConsumableMonitoringCapability: require("./ViomiConsumableMonitoringCapability"),
ViomiZoneCleaningCapability: require("./ViomiZoneCleaningCapability"),
ViomiPersistentMapControlCapability: require("./ViomiPersistentMapControlCapability"),
ViomiVoicePackManagementCapability: require("./ViomiVoicePackManagementCapability"),
ViomiCombinedVirtualRestrictionsCapability: require("./ViomiCombinedVirtualRestrictionsCapability")
};
3 changes: 2 additions & 1 deletion lib/webserver/CapabilitiesRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ const CAPABILITY_TYPE_TO_ROUTER_MAPPING = {
[capabilities.MapSegmentationCapability.TYPE]: capabilityRouters.MapSegmentationCapabilityRouter,
[capabilities.DoNotDisturbCapability.TYPE]: capabilityRouters.DoNotDisturbCapabilityRouter,
[capabilities.CarpetModeControlCapability.TYPE]: capabilityRouters.CarpetModeControlCapabilityRouter,
[capabilities.SpeakerTestCapability.TYPE]: capabilityRouters.SpeakerTestCapabilityRouter
[capabilities.SpeakerTestCapability.TYPE]: capabilityRouters.SpeakerTestCapabilityRouter,
[capabilities.VoicePackManagementCapability.TYPE]: capabilityRouters.VoicePackManagementCapabilityRouter,
};

module.exports = CapabilitiesRouter;
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const CapabilityRouter = require("./CapabilityRouter");

class VoicePackManagementCapabilityRouter extends CapabilityRouter {

initRoutes() {
this.router.get("/", async (req, res) => {
res.json({
"currentLanguage": await this.capability.getCurrentVoiceLanguage(),
"operationStatus": await this.capability.getVoicePackOperationStatus()
});
});

this.router.put("/", async (req, res) => {
if (req.body && req.body.action === "download" && req.body.url) {
try {
await this.capability.downloadVoicePack({
url: req.body.url,
language: req.body.language,
hash: req.body.hash
});
res.sendStatus(200);
} catch (e) {
res.status(500).send(e.message);
}
} else {
res.status(400).send("Invalid request");
}
});
}
}

module.exports = VoicePackManagementCapabilityRouter;
3 changes: 2 additions & 1 deletion lib/webserver/capabilityRouters/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ module.exports = {
MapSegmentationCapabilityRouter: require("./MapSegmentationCapabilityRouter"),
DoNotDisturbCapabilityRouter: require("./DoNotDisturbCapabilityRouter"),
CarpetModeControlCapabilityRouter: require("./CarpetModeControlCapabilityRouter"),
SpeakerTestCapabilityRouter: require("./SpeakerTestCapabilityRouter")
SpeakerTestCapabilityRouter: require("./SpeakerTestCapabilityRouter"),
VoicePackManagementCapabilityRouter: require("./VoicePackManagementCapabilityRouter"),
};

0 comments on commit c953e2b

Please sign in to comment.