mirror of
https://github.com/Hypfer/Valetudo.git
synced 2025-10-26 11:27:27 +00:00
feat(vendor.dreame): Introduce water hookup test quirk
This commit is contained in:
parent
8e336f6f93
commit
cf20143b75
97
backend/lib/robots/dreame/DreameConst.js
Normal file
97
backend/lib/robots/dreame/DreameConst.js
Normal file
@ -0,0 +1,97 @@
|
||||
|
||||
// It seems that everything the AI models can detect shares the same ID space
|
||||
const AI_CLASSIFIER_IDS = Object.freeze({
|
||||
"0": "Unknown",
|
||||
"1": "Bedside Table",
|
||||
"2": "Wall Air Conditioner",
|
||||
"3": "Chair",
|
||||
"4": "Unknown",
|
||||
"5": "End Table",
|
||||
"6": "TV Monitor",
|
||||
"7": "TV Cabinet",
|
||||
"8": "Dining Table",
|
||||
"9": "Range Hood",
|
||||
"10": "Cupboard",
|
||||
"11": "Gas Water Heater",
|
||||
"12": "Toilet",
|
||||
"13": "Shower Head",
|
||||
"14": "Electric Water Heater",
|
||||
"15": "Refrigerator",
|
||||
"16": "Washer Dryer",
|
||||
|
||||
"32": "Unknown",
|
||||
|
||||
"40": "Unknown",
|
||||
"41": "Balcony",
|
||||
"42": "Bathroom",
|
||||
"43": "Bedroom",
|
||||
"44": "Corridor",
|
||||
"45": "Dining Room",
|
||||
"46": "Kitchen",
|
||||
"47": "Living Room",
|
||||
|
||||
|
||||
"128": "Pedestal",
|
||||
"129": "Bathroom Scale",
|
||||
"130": "Power Strip",
|
||||
"131": "Unknown",
|
||||
"132": "Toy",
|
||||
"133": "Shoes",
|
||||
"134": "Sock",
|
||||
"135": "Feces",
|
||||
"136": "Trash Can",
|
||||
"137": "Fabric",
|
||||
"138": "Cable", // Thread?
|
||||
"139": "Stain",
|
||||
"140": "Dock Entrance",
|
||||
"141": "Dock",
|
||||
"142": "Obstacle", // Ambiguous obstacle
|
||||
"143": "Black Desk Leg",
|
||||
|
||||
"144": "Roller",
|
||||
"145": "Sweeper",
|
||||
"146": "Cleaning Robot",
|
||||
|
||||
"147": "Ambiguous Other",
|
||||
"148": "Carpet Segment",
|
||||
"149": "Ground",
|
||||
"150": "Carpet",
|
||||
"151": "Unknown",
|
||||
"152": "Ceramic",
|
||||
|
||||
"157": "Human",
|
||||
"158": "Pet",
|
||||
|
||||
"159": "Furniture Leg",
|
||||
"160": "Furniture Leg Black",
|
||||
"161": "Wheel",
|
||||
"162": "Robot Cleaner",
|
||||
"163": "Cleaner",
|
||||
"164": "Unknown",
|
||||
"165": "Bottle",
|
||||
"166": "Unknown",
|
||||
"167": "Pet Bowl",
|
||||
"168": "Mirror",
|
||||
|
||||
"169": "Stain need filter", //??
|
||||
|
||||
"170": "Hand Gesture Unknown",
|
||||
"171": "Hand Gesture Stop",
|
||||
// Various body key points I'm not going to document here because they'll certainly only be used within the robots
|
||||
// firmware for some kind of gesture control
|
||||
|
||||
"201": "Bar Stool", // Might also be any kind of furniture that can act as a prison for the robot that can be entered but not left
|
||||
});
|
||||
|
||||
const WATER_HOOKUP_ERRORS = Object.freeze({
|
||||
1: "Tank replacements not installed",
|
||||
2: "External control box not installed",
|
||||
3: "Freshwater input abnormal. Check anything affecting water pressure",
|
||||
4: "Wastewater output abnormal. Check the pump",
|
||||
5: "Wastewater output abnormal. Check the filter"
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
WATER_HOOKUP_ERRORS: WATER_HOOKUP_ERRORS,
|
||||
AI_CLASSIFIER_IDS: AI_CLASSIFIER_IDS
|
||||
};
|
||||
@ -1,10 +1,12 @@
|
||||
const capabilities = require("./capabilities");
|
||||
|
||||
const ConsumableMonitoringCapability = require("../../core/capabilities/ConsumableMonitoringCapability");
|
||||
const DreameConst = require("./DreameConst");
|
||||
const DreameMiotServices = require("./DreameMiotServices");
|
||||
const DreameUtils = require("./DreameUtils");
|
||||
const DreameValetudoRobot = require("./DreameValetudoRobot");
|
||||
const entities = require("../../entities");
|
||||
const ErrorStateValetudoEvent = require("../../valetudo_events/events/ErrorStateValetudoEvent");
|
||||
const LinuxTools = require("../../utils/LinuxTools");
|
||||
const Logger = require("../../Logger");
|
||||
const MopAttachmentReminderValetudoEvent = require("../../valetudo_events/events/MopAttachmentReminderValetudoEvent");
|
||||
@ -568,6 +570,45 @@ class DreameGen2ValetudoRobot extends DreameValetudoRobot {
|
||||
}));
|
||||
break;
|
||||
}
|
||||
|
||||
case DreameGen2ValetudoRobot.MIOT_SERVICES.VACUUM_2.PROPERTIES.MISC_TUNABLES.PIID: {
|
||||
const deserializedTunables = DreameUtils.DESERIALIZE_MISC_TUNABLES(elem.value);
|
||||
|
||||
if (deserializedTunables.SmartHost > 0) {
|
||||
Logger.info("Disabling CleanGenius");
|
||||
// CleanGenius breaks most controls in Valetudo without any user feedback
|
||||
// Thus, we just automatically disable it instead of making every functionality aware of it
|
||||
|
||||
this.helper.writeProperty(
|
||||
DreameGen2ValetudoRobot.MIOT_SERVICES.VACUUM_2.SIID,
|
||||
DreameGen2ValetudoRobot.MIOT_SERVICES.VACUUM_2.PROPERTIES.MISC_TUNABLES.PIID,
|
||||
DreameUtils.SERIALIZE_MISC_TUNABLES_SINGLE_TUNABLE({
|
||||
SmartHost: 0
|
||||
})
|
||||
).catch(e => {
|
||||
Logger.warn("Error while disabling CleanGenius", e);
|
||||
});
|
||||
}
|
||||
|
||||
if (deserializedTunables.FluctuationConfirmResult > 0) {
|
||||
const errorString = DreameConst.WATER_HOOKUP_ERRORS[deserializedTunables.FluctuationTestResult];
|
||||
this.valetudoEventStore.raise(new ErrorStateValetudoEvent({
|
||||
message: `Water Hookup Error. ${errorString ?? "Unknown error " + deserializedTunables.FluctuationTestResult}`
|
||||
}));
|
||||
|
||||
this.helper.writeProperty(
|
||||
DreameGen2ValetudoRobot.MIOT_SERVICES.VACUUM_2.SIID,
|
||||
DreameGen2ValetudoRobot.MIOT_SERVICES.VACUUM_2.PROPERTIES.MISC_TUNABLES.PIID,
|
||||
DreameUtils.SERIALIZE_MISC_TUNABLES_SINGLE_TUNABLE({
|
||||
FluctuationConfirmResult: 0
|
||||
})
|
||||
).catch(e => {
|
||||
Logger.warn("Error while confirming water hookup test result", e);
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
const DreameGen2LidarValetudoRobot = require("./DreameGen2LidarValetudoRobot");
|
||||
const DreameGen2ValetudoRobot = require("./DreameGen2ValetudoRobot");
|
||||
const DreameMiotHelper = require("./DreameMiotHelper");
|
||||
const DreameUtils = require("./DreameUtils");
|
||||
const fs = require("fs");
|
||||
const Logger = require("../../Logger");
|
||||
|
||||
class DreameGen4ValetudoRobot extends DreameGen2LidarValetudoRobot {
|
||||
constructor(options) {
|
||||
@ -12,52 +10,13 @@ class DreameGen4ValetudoRobot extends DreameGen2LidarValetudoRobot {
|
||||
this.helper = new DreameMiotHelper({robot: this});
|
||||
}
|
||||
|
||||
parseAndUpdateState(data) {
|
||||
if (!Array.isArray(data)) {
|
||||
Logger.error("Received non-array state", data);
|
||||
return;
|
||||
}
|
||||
|
||||
data.forEach(elem => {
|
||||
switch (elem.siid) {
|
||||
case DreameGen2ValetudoRobot.MIOT_SERVICES.VACUUM_2.SIID: {
|
||||
switch (elem.piid) {
|
||||
case DreameGen2ValetudoRobot.MIOT_SERVICES.VACUUM_2.PROPERTIES.MISC_TUNABLES.PIID: {
|
||||
const deserializedTunables = DreameUtils.DESERIALIZE_MISC_TUNABLES(elem.value);
|
||||
|
||||
if (deserializedTunables.SmartHost > 0) {
|
||||
Logger.info("Disabling CleanGenius");
|
||||
// CleanGenius breaks most controls in Valetudo without any user feedback
|
||||
// Thus, we just automatically disable it instead of making every functionality aware of it
|
||||
|
||||
this.helper.writeProperty(
|
||||
DreameGen2ValetudoRobot.MIOT_SERVICES.VACUUM_2.SIID,
|
||||
DreameGen2ValetudoRobot.MIOT_SERVICES.VACUUM_2.PROPERTIES.MISC_TUNABLES.PIID,
|
||||
DreameUtils.SERIALIZE_MISC_TUNABLES_SINGLE_TUNABLE({
|
||||
SmartHost: 0
|
||||
})
|
||||
).catch(e => {
|
||||
Logger.warn("Error while disabling CleanGenius", e);
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return super.parseAndUpdateState(data);
|
||||
}
|
||||
|
||||
getStatePropertiesToPoll() {
|
||||
const superProps = super.getStatePropertiesToPoll();
|
||||
|
||||
return [
|
||||
...superProps,
|
||||
{ // Required so that we can automatically disable CleanGenius
|
||||
// + the water hookup test quirk
|
||||
siid: DreameGen2ValetudoRobot.MIOT_SERVICES.VACUUM_2.SIID,
|
||||
piid: DreameGen2ValetudoRobot.MIOT_SERVICES.VACUUM_2.PROPERTIES.MISC_TUNABLES.PIID
|
||||
}
|
||||
|
||||
@ -176,6 +176,7 @@ class DreameL10SProUltraHeatValetudoRobot extends DreameGen4ValetudoRobot {
|
||||
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.CARPET_DETECTION_AUTO_DEEP_CLEANING),
|
||||
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.MOP_DOCK_WATER_USAGE),
|
||||
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.MOP_DOCK_CLEANING_PROCESS_TRIGGER),
|
||||
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.WATER_HOOKUP_TEST_TRIGGER),
|
||||
]
|
||||
}));
|
||||
|
||||
|
||||
@ -177,6 +177,7 @@ class DreameL10SUltraValetudoRobot extends DreameGen2LidarValetudoRobot {
|
||||
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.MOP_DOCK_AUTO_DRYING),
|
||||
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.EDGE_MOPPING),
|
||||
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.DRAIN_INTERNAL_WATER_TANK),
|
||||
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.WATER_HOOKUP_TEST_TRIGGER),
|
||||
]
|
||||
}));
|
||||
|
||||
@ -202,6 +203,10 @@ class DreameL10SUltraValetudoRobot extends DreameGen2LidarValetudoRobot {
|
||||
{
|
||||
siid: DreameGen2ValetudoRobot.MIOT_SERVICES.VACUUM_2.SIID,
|
||||
piid: DreameGen2ValetudoRobot.MIOT_SERVICES.VACUUM_2.PROPERTIES.MOP_DOCK_SETTINGS.PIID
|
||||
},
|
||||
{ // Required for the water hookup test quirk
|
||||
siid: DreameGen2ValetudoRobot.MIOT_SERVICES.VACUUM_2.SIID,
|
||||
piid: DreameGen2ValetudoRobot.MIOT_SERVICES.VACUUM_2.PROPERTIES.MISC_TUNABLES.PIID
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
const DreameUtils = require("./DreameUtils");
|
||||
const DreameConst = require("./DreameConst");
|
||||
const Logger = require("../../Logger");
|
||||
const mapEntities = require("../../entities/map");
|
||||
const uuid = require("uuid");
|
||||
@ -268,7 +268,7 @@ class DreameMapParser {
|
||||
parseFloat(obstacle[0]),
|
||||
parseFloat(obstacle[1])
|
||||
);
|
||||
const type = DreameUtils.AI_CLASSIFIER_IDS[obstacle[2]] ?? `Unknown ID ${obstacle[2]}`;
|
||||
const type = DreameConst.AI_CLASSIFIER_IDS[obstacle[2]] ?? `Unknown ID ${obstacle[2]}`;
|
||||
const confidence = `${Math.round(parseFloat(obstacle[3])*100)}%`;
|
||||
const image = obstacle[5] !== undefined ? obstacle[5] : undefined;
|
||||
|
||||
|
||||
@ -446,6 +446,26 @@ class DreameQuirkFactory {
|
||||
}
|
||||
}
|
||||
});
|
||||
case DreameQuirkFactory.KNOWN_QUIRKS.WATER_HOOKUP_TEST_TRIGGER:
|
||||
return new Quirk({
|
||||
id: id,
|
||||
title: "Water Hookup Test",
|
||||
description: "Test if the permanent water hookup has been installed correctly. " +
|
||||
"Listen to the robots' voice prompts for status updates. If errors should occur, they will be raised as ValetudoEvents.",
|
||||
options: ["select_to_trigger", "trigger"],
|
||||
getter: async () => {
|
||||
return "select_to_trigger";
|
||||
},
|
||||
setter: async (value) => {
|
||||
if (value === "trigger") {
|
||||
return this.helper.writeProperty(
|
||||
99,
|
||||
8,
|
||||
JSON.stringify({ "bittest": [20, 0] })
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
case DreameQuirkFactory.KNOWN_QUIRKS.MOP_DOCK_AUTO_DRYING:
|
||||
return new Quirk({
|
||||
id: id,
|
||||
@ -1190,7 +1210,8 @@ DreameQuirkFactory.KNOWN_QUIRKS = {
|
||||
EDGE_EXTENSION_FREQUENCY: "8f6a7013-794e-40d9-9bbe-8fdeed7c0b9d",
|
||||
CAMERA_LIGHT: "bba079c2-293b-4ad5-99b8-4102a1220b12",
|
||||
DETACH_MOPS: "4a52e16b-3c73-479d-b308-7f0bbdde0884",
|
||||
MOP_DOCK_CLEANING_PROCESS_TRIGGER: "42c7db4b-2cad-4801-a526-44de8944a41f"
|
||||
MOP_DOCK_CLEANING_PROCESS_TRIGGER: "42c7db4b-2cad-4801-a526-44de8944a41f",
|
||||
WATER_HOOKUP_TEST_TRIGGER: "86094736-d66e-40c3-807c-3f5ef33cbf09"
|
||||
};
|
||||
|
||||
module.exports = DreameQuirkFactory;
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
const Logger = require("../../Logger");
|
||||
|
||||
const UINT8_MASK = 0b00000000000000000000000011111111;
|
||||
|
||||
/**
|
||||
@ -53,7 +55,19 @@ class DreameUtils {
|
||||
* @return {MISC_TUNABLES}
|
||||
*/
|
||||
static DESERIALIZE_MISC_TUNABLES(str) {
|
||||
const arr = JSON.parse(str);
|
||||
let arr = [];
|
||||
try {
|
||||
arr = JSON.parse(str);
|
||||
|
||||
if (!Array.isArray(arr)) {
|
||||
Logger.warn("Deserialized dreame misc tunables are not an array");
|
||||
|
||||
arr = [];
|
||||
}
|
||||
} catch (e) {
|
||||
Logger.warn("Failed to deserialize dreame misc tunables.");
|
||||
}
|
||||
|
||||
const result = {};
|
||||
|
||||
arr.forEach(elem => {
|
||||
@ -106,90 +120,6 @@ class DreameUtils {
|
||||
}
|
||||
}
|
||||
|
||||
// It seems that everything the AI models can detect shares the same ID space
|
||||
DreameUtils.AI_CLASSIFIER_IDS = Object.freeze({
|
||||
"0": "Unknown",
|
||||
"1": "Bedside Table",
|
||||
"2": "Wall Air Conditioner",
|
||||
"3": "Chair",
|
||||
"4": "Unknown",
|
||||
"5": "End Table",
|
||||
"6": "TV Monitor",
|
||||
"7": "TV Cabinet",
|
||||
"8": "Dining Table",
|
||||
"9": "Range Hood",
|
||||
"10": "Cupboard",
|
||||
"11": "Gas Water Heater",
|
||||
"12": "Toilet",
|
||||
"13": "Shower Head",
|
||||
"14": "Electric Water Heater",
|
||||
"15": "Refrigerator",
|
||||
"16": "Washer Dryer",
|
||||
|
||||
"32": "Unknown",
|
||||
|
||||
"40": "Unknown",
|
||||
"41": "Balcony",
|
||||
"42": "Bathroom",
|
||||
"43": "Bedroom",
|
||||
"44": "Corridor",
|
||||
"45": "Dining Room",
|
||||
"46": "Kitchen",
|
||||
"47": "Living Room",
|
||||
|
||||
|
||||
"128": "Pedestal",
|
||||
"129": "Bathroom Scale",
|
||||
"130": "Power Strip",
|
||||
"131": "Unknown",
|
||||
"132": "Toy",
|
||||
"133": "Shoes",
|
||||
"134": "Sock",
|
||||
"135": "Feces",
|
||||
"136": "Trash Can",
|
||||
"137": "Fabric",
|
||||
"138": "Cable", // Thread?
|
||||
"139": "Stain",
|
||||
"140": "Dock Entrance",
|
||||
"141": "Dock",
|
||||
"142": "Obstacle", // Ambiguous obstacle
|
||||
"143": "Black Desk Leg",
|
||||
|
||||
"144": "Roller",
|
||||
"145": "Sweeper",
|
||||
"146": "Cleaning Robot",
|
||||
|
||||
"147": "Ambiguous Other",
|
||||
"148": "Carpet Segment",
|
||||
"149": "Ground",
|
||||
"150": "Carpet",
|
||||
"151": "Unknown",
|
||||
"152": "Ceramic",
|
||||
|
||||
"157": "Human",
|
||||
"158": "Pet",
|
||||
|
||||
"159": "Furniture Leg",
|
||||
"160": "Furniture Leg Black",
|
||||
"161": "Wheel",
|
||||
"162": "Robot Cleaner",
|
||||
"163": "Cleaner",
|
||||
"164": "Unknown",
|
||||
"165": "Bottle",
|
||||
"166": "Unknown",
|
||||
"167": "Pet Bowl",
|
||||
"168": "Mirror",
|
||||
|
||||
"169": "Stain need filter", //??
|
||||
|
||||
"170": "Hand Gesture Unknown",
|
||||
"171": "Hand Gesture Stop",
|
||||
// Various body key points I'm not going to document here because they'll certainly only be used within the robots
|
||||
// firmware for some kind of gesture control
|
||||
|
||||
"201": "Bar Stool", // Might also be any kind of furniture that can act as a prison for the robot that can be entered but not left
|
||||
});
|
||||
|
||||
/**
|
||||
* @typedef {object} AI_SETTINGS
|
||||
* @property {boolean} obstacleDetection
|
||||
|
||||
@ -182,6 +182,7 @@ class DreameX40UltraValetudoRobot extends DreameGen4ValetudoRobot {
|
||||
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.CAMERA_LIGHT),
|
||||
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.DETACH_MOPS),
|
||||
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.MOP_DOCK_CLEANING_PROCESS_TRIGGER),
|
||||
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.WATER_HOOKUP_TEST_TRIGGER),
|
||||
]
|
||||
}));
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user