feat(core): MopDockMopWashTemperatureControlCapability

This commit is contained in:
Sören Beye 2025-09-06 14:13:27 +02:00
parent 0c6272f704
commit de62cbf812
10 changed files with 380 additions and 4 deletions

View File

@ -0,0 +1,65 @@
const Capability = require("./Capability");
const NotImplementedError = require("../NotImplementedError");
/**
* @template {import("../ValetudoRobot")} T
* @extends Capability<T>
*/
class MopDockMopWashTemperatureControlCapability extends Capability {
/**
*
* @param {object} options
* @param {T} options.robot
* @class
*/
constructor(options) {
super(options);
}
/**
* @returns {Promise<MopDockMopWashTemperatureControlCapabilityTemperature>}
*/
async getTemperature() {
throw new NotImplementedError();
}
/**
*
* @param {MopDockMopWashTemperatureControlCapabilityTemperature} newTemperature
* @returns {Promise<void>}
*/
async setTemperature(newTemperature) {
throw new NotImplementedError();
}
/**
* @returns {{supportedTemperatures: Array<MopDockMopWashTemperatureControlCapabilityTemperature>}}
*/
getProperties() {
return {
supportedTemperatures: []
};
}
getType() {
return MopDockMopWashTemperatureControlCapability.TYPE;
}
}
MopDockMopWashTemperatureControlCapability.TYPE = "MopDockMopWashTemperatureControlCapability";
/**
* @typedef {string} MopDockMopWashTemperatureControlCapabilityTemperature
* @enum {string}
*
*/
MopDockMopWashTemperatureControlCapability.TEMPERATURE = Object.freeze({
COLD: "cold",
WARM: "warm",
HOT: "hot",
SCALDING: "scalding",
BOILING: "boiling"
});
module.exports = MopDockMopWashTemperatureControlCapability;

View File

@ -25,6 +25,7 @@ module.exports = {
MappingPassCapability: require("./MappingPassCapability"),
MopDockCleanManualTriggerCapability: require("./MopDockCleanManualTriggerCapability"),
MopDockDryManualTriggerCapability: require("./MopDockDryManualTriggerCapability"),
MopDockMopWashTemperatureControlCapability: require("./MopDockMopWashTemperatureControlCapability"),
MopExtensionControlCapability: require("./MopExtensionControlCapability"),
ObstacleAvoidanceControlCapability: require("./ObstacleAvoidanceControlCapability"),
ObstacleImagesCapability: require("./ObstacleImagesCapability"),

View File

@ -92,7 +92,8 @@ const CAPABILITY_TYPE_TO_ROUTER_MAPPING = {
[capabilities.ObstacleImagesCapability.TYPE]: capabilityRouters.ObstacleImagesCapabilityRouter,
[capabilities.HighResolutionManualControlCapability.TYPE]: capabilityRouters.HighResolutionManualControlCapabilityRouter,
[capabilities.MopExtensionControlCapability.TYPE]: capabilityRouters.SimpleToggleCapabilityRouter,
[capabilities.CameraLightControlCapability.TYPE]: capabilityRouters.SimpleToggleCapabilityRouter
[capabilities.CameraLightControlCapability.TYPE]: capabilityRouters.SimpleToggleCapabilityRouter,
[capabilities.MopDockMopWashTemperatureControlCapability.TYPE]: capabilityRouters.MopDockMopWashTemperatureControlCapabilityRouter,
};
module.exports = CapabilitiesRouter;

View File

@ -0,0 +1,31 @@
const CapabilityRouter = require("./CapabilityRouter");
class MopDockMopWashTemperatureControlCapabilityRouter extends CapabilityRouter {
initRoutes() {
this.router.get("/", async (req, res) => {
try {
res.json({
temperature: await this.capability.getTemperature()
});
} catch (e) {
this.sendErrorResponse(req, res, e);
}
});
this.router.put("/", this.validator, async (req, res) => {
if (req.body.temperature) {
try {
await this.capability.setTemperature(req.body.temperature);
res.sendStatus(200);
} catch (e) {
this.sendErrorResponse(req, res, e);
}
} else {
res.sendStatus(400);
}
});
}
}
module.exports = MopDockMopWashTemperatureControlCapabilityRouter;

View File

@ -0,0 +1,104 @@
{
"/api/v2/robot/capabilities/MopDockMopWashTemperatureControlCapability": {
"get": {
"tags": [
"MopDockMopWashTemperatureControlCapability"
],
"summary": "Get current mop wash water temperature",
"responses": {
"200": {
"description": "Ok",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"temperature": {
"type": "string",
"enum": [
"cold",
"warm",
"hot",
"scalding",
"boiling"
]
}
}
}
}
}
}
}
},
"put": {
"tags": [
"MopDockMopWashTemperatureControlCapability"
],
"summary": "Set mop wash water temperature",
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"temperature": {
"type": "string",
"enum": [
"cold",
"warm",
"hot",
"scalding",
"boiling"
]
}
}
}
}
}
},
"responses": {
"200": {
"$ref": "#/components/responses/200"
},
"400": {
"$ref": "#/components/responses/400"
}
}
}
},
"/api/v2/robot/capabilities/MopDockMopWashTemperatureControlCapability/properties": {
"get": {
"tags": [
"MopDockMopWashTemperatureControlCapability"
],
"summary": "Get various capability-related properties",
"responses": {
"200": {
"description": "Ok",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"supportedTemperatures": {
"type": "array",
"items": {
"type": "string",
"enum": [
"cold",
"warm",
"hot",
"scalding",
"boiling"
]
}
}
}
}
}
}
}
}
}
}
}

View File

@ -18,6 +18,7 @@ module.exports = {
MappingPassCapabilityRouter: require("./MappingPassCapabilityRouter"),
MopDockCleanManualTriggerCapabilityRouter: require("./MopDockCleanManualTriggerCapabilityRouter"),
MopDockDryManualTriggerCapabilityRouter: require("./MopDockDryManualTriggerCapabilityRouter"),
MopDockMopWashTemperatureControlCapabilityRouter : require("./MopDockMopWashTemperatureControlCapabilityRouter"),
ObstacleImagesCapabilityRouter: require("./ObstacleImagesCapabilityRouter"),
PendingMapChangeHandlingCapabilityRouter: require("./PendingMapChangeHandlingCapabilityRouter"),
PresetSelectionCapabilityRouter: require("./PresetSelectionCapabilityRouter"),
@ -29,5 +30,5 @@ module.exports = {
VoicePackManagementCapabilityRouter: require("./VoicePackManagementCapabilityRouter"),
WifiConfigurationCapabilityRouter: require("./WifiConfigurationCapabilityRouter"),
WifiScanCapabilityRouter: require("./WifiScanCapabilityRouter"),
ZoneCleaningCapabilityRouter: require("./ZoneCleaningCapabilityRouter")
ZoneCleaningCapabilityRouter: require("./ZoneCleaningCapabilityRouter"),
};

View File

@ -25,6 +25,9 @@ import {
MapSegmentEditJoinRequestParameters,
MapSegmentEditSplitRequestParameters,
MapSegmentRenameRequestParameters,
MopDockMopWashTemperature,
MopDockMopWashTemperaturePayload,
MopDockMopWashTemperatureProperties,
MQTTConfiguration,
MQTTProperties,
MQTTStatus,
@ -1186,3 +1189,31 @@ export const fetchObstacleImagesProperties = async (): Promise<ObstacleImagesPro
return data;
});
};
export const sendMopDockMopWashTemperature = async (payload: MopDockMopWashTemperaturePayload): Promise<void> => {
return valetudoAPI
.put(`/robot/capabilities/${Capability.MopDockMopWashTemperatureControl}`, payload)
.then(({status}) => {
if (status !== 200) {
throw new Error("Could not send mop dock mop wash temperature");
}
});
};
export const fetchMopDockMopWashTemperature = async (): Promise<MopDockMopWashTemperature> => {
return valetudoAPI
.get<MopDockMopWashTemperaturePayload>(`/robot/capabilities/${Capability.MopDockMopWashTemperatureControl}`)
.then(({data}) => {
return data.temperature;
});
};
export const fetchMopDockMopWashTemperatureProperties = async (): Promise<MopDockMopWashTemperatureProperties> => {
return valetudoAPI
.get<MopDockMopWashTemperatureProperties>(
`/robot/capabilities/${Capability.MopDockMopWashTemperatureControl}/properties`
)
.then(({data}) => {
return data;
});
};

View File

@ -128,6 +128,9 @@ import {
sendMopExtensionControlState,
fetchCameraLightControlState,
sendCameraLightControlState,
fetchMopDockMopWashTemperature,
sendMopDockMopWashTemperature,
fetchMopDockMopWashTemperatureProperties,
} from "./client";
import {
PresetSelectionState,
@ -150,6 +153,7 @@ import {
MapSegmentEditJoinRequestParameters,
MapSegmentEditSplitRequestParameters,
MapSegmentRenameRequestParameters,
MopDockMopWashTemperature,
MQTTConfiguration,
NetworkAdvertisementConfiguration,
NTPClientConfiguration,
@ -228,7 +232,9 @@ enum QueryKey {
ObstacleImages = "obstacle_image",
ObstacleImagesProperties = "obstacle_image_properties",
MopExtensionControl = "mop_extension_control",
CameraLightControl = "camera_light_control"
CameraLightControl = "camera_light_control",
MopDockMopWashTemperature = "mop_dock_mop_wash_temperature",
MopDockMopWashTemperatureProperties = "mop_dock_mop_wash_temperature_properties",
}
const useOnCommandError = (capability: Capability | string): ((error: unknown) => void) => {
@ -1592,3 +1598,31 @@ export const prefetchObstacleImagesProperties = async (queryClient : QueryClient
});
}
};
export const useMopDockMopWashTemperatureQuery = () => {
return useQuery({
queryKey: [QueryKey.MopDockMopWashTemperature],
queryFn: fetchMopDockMopWashTemperature,
});
};
export const useMopDockMopWashTemperatureMutation = () => {
return useValetudoFetchingMutation({
queryKey: [QueryKey.MopDockMopWashTemperature],
mutationFn: (temperature: MopDockMopWashTemperature) => {
return sendMopDockMopWashTemperature({ temperature: temperature }).then(
fetchMopDockMopWashTemperature
);
},
onError: useOnCommandError(Capability.MopDockMopWashTemperatureControl),
});
};
export const useMopDockMopWashTemperaturePropertiesQuery = () => {
return useQuery({
queryKey: [QueryKey.MopDockMopWashTemperatureProperties],
queryFn: fetchMopDockMopWashTemperatureProperties,
staleTime: Infinity,
});
};

View File

@ -25,6 +25,7 @@ export enum Capability {
MapSegmentation = "MapSegmentationCapability",
MapSnapshot = "MapSnapshotCapability",
MappingPass = "MappingPassCapability",
MopDockMopWashTemperatureControl = "MopDockMopWashTemperatureControlCapability",
ObstacleAvoidanceControl = "ObstacleAvoidanceControlCapability",
PetObstacleAvoidanceControl = "PetObstacleAvoidanceControlCapability",
MopExtensionControl = "MopExtensionControlCapability",
@ -594,3 +595,13 @@ export interface ObstacleImagesProperties {
height: number
}
}
export type MopDockMopWashTemperature = "cold" | "warm" | "hot" | "scalding" | "boiling";
export interface MopDockMopWashTemperaturePayload {
temperature: MopDockMopWashTemperature;
}
export interface MopDockMopWashTemperatureProperties {
supportedTemperatures: Array<MopDockMopWashTemperature>;
}

View File

@ -5,6 +5,7 @@ import {
AutoEmptyDockAutoEmptyInterval,
Capability,
CarpetSensorMode,
MopDockMopWashTemperature,
useAutoEmptyDockAutoEmptyControlMutation,
useAutoEmptyDockAutoEmptyControlQuery,
useAutoEmptyDockAutoEmptyIntervalMutation,
@ -22,6 +23,9 @@ import {
useKeyLockStateMutation,
useKeyLockStateQuery,
useLocateMutation,
useMopDockMopWashTemperatureMutation,
useMopDockMopWashTemperaturePropertiesQuery,
useMopDockMopWashTemperatureQuery,
useMopExtensionControlMutation,
useMopExtensionControlQuery,
useObstacleAvoidanceControlMutation,
@ -48,6 +52,7 @@ import {
Sensors as CarpetModeIcon,
Star as QuirksIcon,
Waves as CarpetSensorModeIcon,
DeviceThermostat as MopDockMopWashTemperatureControlIcon,
} from "@mui/icons-material";
import {SpacerListMenuItem} from "../components/list_menu/SpacerListMenuItem";
import {LinkListMenuItem} from "../components/list_menu/LinkListMenuItem";
@ -477,6 +482,89 @@ const CameraLightControlCapabilitySwitchListMenuItem = () => {
);
};
const MopDockMopWashTemperatureControlCapabilitySelectListMenuItem = () => {
const SORT_ORDER: Record<MopDockMopWashTemperature, number> = {
"cold": 1,
"warm": 2,
"hot": 3,
"scalding": 4,
"boiling": 5,
};
const {
data: mopDockMopWashTemperatureProperties,
isPending: mopDockMopWashTemperaturePropertiesPending,
isError: mopDockMopWashTemperaturePropertiesError
} = useMopDockMopWashTemperaturePropertiesQuery();
const options: Array<SelectListMenuItemOption> = (
mopDockMopWashTemperatureProperties?.supportedTemperatures ?? []
).sort((a, b) => {
const aMapped = SORT_ORDER[a] ?? 10;
const bMapped = SORT_ORDER[b] ?? 10;
return aMapped - bMapped;
}).map((val: MopDockMopWashTemperature) => {
let label;
switch (val) {
case "cold":
label = "Cold";
break;
case "warm":
label = "Warm";
break;
case "hot":
label = "Hot";
break;
case "scalding":
label = "Scalding";
break;
case "boiling":
label = "Boiling";
break;
}
return {
value: val,
label: label
};
});
const {
data: data,
isPending: isPending,
isFetching: isFetching,
isError: isError,
} = useMopDockMopWashTemperatureQuery();
const {mutate: mutate, isPending: isChanging} = useMopDockMopWashTemperatureMutation();
const loading = isFetching || isChanging;
const disabled = loading || isChanging || isError;
const currentValue = options.find(mode => {
return mode.value === data;
}) ?? {value: "", label: ""};
return (
<SelectListMenuItem
options={options}
currentValue={currentValue}
setValue={(e) => {
mutate(e.value as MopDockMopWashTemperature);
}}
disabled={disabled}
loadingOptions={mopDockMopWashTemperaturePropertiesPending || isPending}
loadError={mopDockMopWashTemperaturePropertiesError}
primaryLabel="Mop Wash Temperature"
secondaryLabel="Select if and/or how much the dock should heat the water used to rinse the mop pads."
icon={<MopDockMopWashTemperatureControlIcon/>}
/>
);
};
const RobotOptions = (): React.ReactElement => {
const [
locateCapabilitySupported,
@ -490,6 +578,7 @@ const RobotOptions = (): React.ReactElement => {
carpetSensorModeControlCapabilitySupported,
mopExtensionControlCapabilitySupported,
mopDockMopWashTemperatureControlSupported,
autoEmptyDockAutoEmptyControlCapabilitySupported,
autoEmptyDockAutoEmptyIntervalControlCapabilitySupported,
@ -514,6 +603,7 @@ const RobotOptions = (): React.ReactElement => {
Capability.CarpetSensorModeControl,
Capability.MopExtensionControl,
Capability.MopDockMopWashTemperatureControl,
Capability.AutoEmptyDockAutoEmptyControl,
Capability.AutoEmptyDockAutoEmptyIntervalControl,
@ -591,6 +681,12 @@ const RobotOptions = (): React.ReactElement => {
);
}
if (mopDockMopWashTemperatureControlSupported) {
items.push(
<MopDockMopWashTemperatureControlCapabilitySelectListMenuItem key={"mopDockMopWashTemperatureControl"}/>
);
}
return items;
}, [
obstacleAvoidanceControlCapabilitySupported,
@ -600,7 +696,8 @@ const RobotOptions = (): React.ReactElement => {
collisionAvoidantNavigationControlCapabilitySupported,
carpetModeControlCapabilitySupported,
carpetSensorModeControlCapabilitySupported,
mopExtensionControlCapabilitySupported
mopExtensionControlCapabilitySupported,
mopDockMopWashTemperatureControlSupported
]);
const dockListItems = React.useMemo(() => {