feat!: Remove ZonePresets and GoToLocationPresets

This commit is contained in:
Sören Beye 2022-04-15 21:43:13 +02:00
parent f7f3f0db6e
commit de8719012f
33 changed files with 128 additions and 1619 deletions

View File

@ -44,12 +44,6 @@
}
}
},
"zonePresets": {
"type": "object"
},
"goToLocationPresets": {
"type": "object"
},
"mqtt": {
"$ref": "#/components/schemas/MqttConfigDTO"
},
@ -387,9 +381,7 @@
"type": "string",
"enum": [
"full_cleanup",
"zone_cleanup",
"segment_cleanup",
"goto_location"
"segment_cleanup"
]
},
"params": {

View File

@ -1,29 +1,34 @@
const SerializableEntity = require("../SerializableEntity");
const uuid = require("uuid");
// noinspection JSCheckFunctionSignatures
class ValetudoGoToLocation extends SerializableEntity {
/**
*
* @param {object} options
* @param {string} options.name
* @param {object} options.coordinates
* @param {number} options.coordinates.x
* @param {number} options.coordinates.y
* @param {string} [options.id]
* @param {object} [options.metaData]
* @class
*/
constructor(options) {
super(options);
this.name = options.name;
this.coordinates = {
x: options.coordinates.x,
y: options.coordinates.y
};
this.id = options.id ?? uuid.v4();
if (
!(
this.coordinates &&
typeof this.coordinates.x === "number" &&
typeof this.coordinates.y === "number"
)
) {
throw new Error("Invalid coordinates");
}
}
}

View File

@ -15,9 +15,7 @@ class ValetudoTimer extends SerializableEntity {
* @param {object} options.action
* @param {ValetudoTimerActionType} options.action.type
* @param {object} options.action.params
* @param {string} [options.action.params.zone_id]
* @param {Array<string>} [options.action.params.segment_ids]
* @param {string} [options.action.params.goto_id]
* @param {number} [options.action.params.iterations]
* @param {boolean} [options.action.params.custom_order]
* @param {object} [options.metaData]
@ -42,9 +40,7 @@ class ValetudoTimer extends SerializableEntity {
*/
ValetudoTimer.ACTION_TYPE = Object.freeze({
FULL_CLEANUP: "full_cleanup",
ZONE_CLEANUP: "zone_cleanup",
SEGMENT_CLEANUP: "segment_cleanup",
GOTO_LOCATION: "goto_location"
SEGMENT_CLEANUP: "segment_cleanup"
});

View File

@ -32,6 +32,22 @@ class ValetudoZone extends SerializableEntity {
this.points = options.points;
this.iterations = options.iterations ? options.iterations : 1;
if (
!(
this.points &&
typeof this.points.pA?.x === "number" &&
typeof this.points.pA?.y === "number" &&
typeof this.points.pB?.x === "number" &&
typeof this.points.pB?.y === "number" &&
typeof this.points.pC?.x === "number" &&
typeof this.points.pC?.y === "number" &&
typeof this.points.pD?.x === "number" &&
typeof this.points.pD?.y === "number"
)
) {
throw new Error("Invalid Zone points data");
}
}
}

View File

@ -1,26 +0,0 @@
const SerializableEntity = require("../SerializableEntity");
const uuid = require("uuid");
// noinspection JSCheckFunctionSignatures
class ValetudoZonePreset extends SerializableEntity {
/**
* This is a named container which contains ValetudoZones
*
* @param {object} options
* @param {string} options.name
* @param {Array<import("./ValetudoZone")>} options.zones
* @param {string} [options.id]
* @param {object} [options.metaData]
* @class
*/
constructor(options) {
super(options);
this.name = options.name;
this.zones = options.zones;
this.id = options.id ?? uuid.v4();
}
}
module.exports = ValetudoZonePreset;

View File

@ -1,26 +0,0 @@
{
"components": {
"schemas": {
"ValetudoZonePreset": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"id": {
"type": "string"
},
"metaData": {
"type": "object"
},
"zones": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ValetudoZone"
}
}
}
}
}
}
}

View File

@ -1,7 +1,6 @@
module.exports = {
ValetudoDNDConfiguration: require("./ValetudoDNDConfiguration"),
ValetudoDataPoint: require("./ValetudoDataPoint"),
ValetudoGoToLocation: require("./ValetudoGoToLocation"),
ValetudoMapSegment: require("./ValetudoMapSegment"),
ValetudoMapSnapshot: require("./ValetudoMapSnapshot"),
ValetudoRestrictedZone: require("./ValetudoRestrictedZone"),
@ -15,6 +14,5 @@ module.exports = {
ValetudoWifiNetwork: require("./ValetudoWifiNetwork"),
ValetudoWifiStatus: require("./ValetudoWifiStatus"),
ValetudoZone: require("./ValetudoZone"),
ValetudoZonePreset: require("./ValetudoZonePreset"),
ntpClient: require("./ntpClient")
};

View File

@ -10,7 +10,7 @@ const ValetudoMapSegment = require("../core/ValetudoMapSegment");
*
* Everything is int. Coordinates and size are in cm
*
* The origin is found on the top-left corner
* The origin is found in the top-left corner
*
*/
class ValetudoMap extends SerializableEntity { //TODO: Current, Historic, Etc.

View File

@ -1,9 +1,7 @@
const CapabilityMqttHandle = require("./CapabilityMqttHandle");
const ComponentType = require("../homeassistant/ComponentType");
const DataType = require("../homie/DataType");
const HassAnchor = require("../homeassistant/HassAnchor");
const InLineHassComponent = require("../homeassistant/components/InLineHassComponent");
const PropertyMqttHandle = require("../handles/PropertyMqttHandle");
const ValetudoGoToLocation = require("../../entities/core/ValetudoGoToLocation");
class GoToLocationCapabilityMqttHandle extends CapabilityMqttHandle {
/**
@ -19,68 +17,42 @@ class GoToLocationCapabilityMqttHandle extends CapabilityMqttHandle {
}));
this.capability = options.capability;
this.registerChild(
new PropertyMqttHandle({
parent: this,
controller: this.controller,
topicName: "presets",
friendlyName: "Presets",
datatype: DataType.STRING,
format: "json",
getter: async () => {
const result = this.robot.config.get("goToLocationPresets") ?? {};
await HassAnchor.getAnchor(HassAnchor.ANCHOR.GOTO_PRESETS_LEN).post(Object.keys(result).length);
this.registerChild(new PropertyMqttHandle({
parent: this,
controller: this.controller,
topicName: "go",
friendlyName: "Go to location",
datatype: DataType.STRING,
format: "same json as the REST interface",
setter: async (value) => {
const reqGoToLocation = JSON.parse(value);
return result;
},
helpText: "This handle provides a set of configured Go-to-location presets as a JSON object."
}).also((prop) => {
HassAnchor.getTopicReference(HassAnchor.REFERENCE.GOTO_PRESETS).post(prop.getBaseTopic()).then();
})
);
this.registerChild(
new PropertyMqttHandle({
parent: this,
controller: this.controller,
topicName: "go",
friendlyName: "Go to location preset",
datatype: DataType.STRING,
setter: async (value) => {
const gotoPreset = this.robot.config.get("goToLocationPresets")[value];
if (gotoPreset === undefined) {
throw new Error("Invalid go to location preset ID found in go payload");
if (
reqGoToLocation && reqGoToLocation.coordinates &&
typeof reqGoToLocation.coordinates.x === "number" &&
typeof reqGoToLocation.coordinates.y === "number"
) {
await this.capability.goTo(new ValetudoGoToLocation({
coordinates: {
x: reqGoToLocation.coordinates.x,
y: reqGoToLocation.coordinates.y
}
}));
} else {
throw new Error("Invalid go to location payload");
}
},
helpText: "This handle accepts a JSON object identical to the one used by the REST API.\n\n" +
"Sample payload:\n\n" +
"```json\n" +
JSON.stringify({
coordinates: {
x: 50,
y: 50
}
await this.capability.goTo(gotoPreset);
},
helpText: "Use this handle to make the robot go to a configured preset location. It accepts one " +
"single preset UUID as a regular string."
})
);
this.controller.withHass((hass) => {
this.attachHomeAssistantComponent(
new InLineHassComponent({
hass: hass,
robot: this.robot,
name: this.capability.getType(),
friendlyName: "GoTo Locations",
componentType: ComponentType.SENSOR,
baseTopicReference: HassAnchor.getTopicReference(HassAnchor.REFERENCE.HASS_GOTO_LOCATION_STATE),
autoconf: {
state_topic: HassAnchor.getTopicReference(HassAnchor.REFERENCE.HASS_GOTO_LOCATION_STATE),
icon: "mdi:map-marker-outline",
json_attributes_topic: HassAnchor.getTopicReference(HassAnchor.REFERENCE.GOTO_PRESETS),
json_attributes_template: "{{ value }}"
},
topics: {
"": HassAnchor.getAnchor(HassAnchor.ANCHOR.GOTO_PRESETS_LEN)
}
})
);
});
}, null, 2) +
"\n```"
}));
}
}

View File

@ -58,16 +58,16 @@ class MapSegmentationCapabilityMqttHandle extends CapabilityMqttHandle {
helpText: "This handle accepts a JSON object identical to the one used by the REST API.\n\n" +
"Sample payload:\n\n" +
"```json\n" +
"{\n" +
" \"segment_ids\": [\n" +
" \"20\",\n"+
" \"18\",\n"+
" \"16\"\n"+
" ],\n"+
" \"iterations\": 2,\n"+
" \"customOrder\": true\n"+
"}\n" +
"```"
JSON.stringify({
segment_ids: [
"20",
"18",
"16"
],
iterations: 2,
customOrder: true
}, null, 2) +
"\n```"
}));
}
}

View File

@ -1,9 +1,7 @@
const CapabilityMqttHandle = require("./CapabilityMqttHandle");
const ComponentType = require("../homeassistant/ComponentType");
const DataType = require("../homie/DataType");
const HassAnchor = require("../homeassistant/HassAnchor");
const InLineHassComponent = require("../homeassistant/components/InLineHassComponent");
const PropertyMqttHandle = require("../handles/PropertyMqttHandle");
const ValetudoZone = require("../../entities/core/ValetudoZone");
class ZoneCleaningCapabilityMqttHandle extends CapabilityMqttHandle {
/**
@ -19,71 +17,57 @@ class ZoneCleaningCapabilityMqttHandle extends CapabilityMqttHandle {
}));
this.capability = options.capability;
this.registerChild(
new PropertyMqttHandle({
parent: this,
controller: this.controller,
topicName: "presets",
friendlyName: "Presets",
datatype: DataType.STRING,
format: "json",
getter: async () => {
const result = this.robot.config.get("zonePresets") ?? {};
await HassAnchor.getAnchor(HassAnchor.ANCHOR.ZONE_PRESETS_LEN).post(Object.keys(result).length);
return result;
},
helpText: "This handles provides the list of configured zone presets as a JSON object."
}).also((prop) => {
HassAnchor.getTopicReference(HassAnchor.REFERENCE.ZONE_PRESETS).post(prop.getBaseTopic()).then();
})
);
this.registerChild(new PropertyMqttHandle({
parent: this,
controller: this.controller,
topicName: "start",
friendlyName: "Start zoned cleaning",
datatype: DataType.STRING,
format: "same json as the REST interface",
setter: async (value) => {
const req = JSON.parse(value);
this.registerChild(
new PropertyMqttHandle({
parent: this,
controller: this.controller,
topicName: "start",
friendlyName: "Start zone preset",
datatype: DataType.STRING,
format: "json",
setter: async (value) => {
const loadedZone = this.robot.config.get("zonePresets")[value];
if (!loadedZone) {
throw new Error("Error while starting zone cleanup. There is no zone preset with id " + value);
}
try {
await this.capability.start(loadedZone.zones);
} catch (e) {
throw new Error(`Error while starting zone cleaning for zone_id ${value}: ${e}`);
}
},
helpText: "This handle accepts a zone preset **UUID** to start. You can retrieve them from the `/presets` handle.\n\n" +
"Sample value:\n" +
"`25f6b7fe-0a28-477d-a1af-937ad91b2df4`\n"
})
);
this.controller.withHass((hass) => {
this.attachHomeAssistantComponent(
new InLineHassComponent({
hass: hass,
robot: this.robot,
name: this.capability.getType(),
friendlyName: "Zone Presets",
componentType: ComponentType.SENSOR,
baseTopicReference: HassAnchor.getTopicReference(HassAnchor.REFERENCE.HAZZ_ZONE_CLEANING_STATE),
autoconf: {
state_topic: HassAnchor.getTopicReference(HassAnchor.REFERENCE.HAZZ_ZONE_CLEANING_STATE),
icon: "mdi:square-outline",
json_attributes_topic: HassAnchor.getTopicReference(HassAnchor.REFERENCE.ZONE_PRESETS),
json_attributes_template: "{{ value }}"
},
topics: {
"": HassAnchor.getAnchor(HassAnchor.ANCHOR.ZONE_PRESETS_LEN)
}
})
);
});
if (Array.isArray(req?.zones)) {
await this.capability.start(req.zones.map(z => {
return new ValetudoZone({
points: z.points,
iterations: z.iterations
});
}));
} else {
throw new Error("Invalid zone cleaning payload");
}
},
helpText: "This handle accepts a JSON object identical to the one used by the REST API.\n\n" +
"Sample payload:\n\n" +
"```json\n" +
JSON.stringify({
zones: [
{
iterations: 1,
points: {
pA: {
x: 50,
y: 50
},
pB: {
x: 100,
y: 50
},
pC: {
x: 100,
y: 100
},
pD: {
x: 50,
y: 100
}
}
}
]
}, null, 2) +
"\n```"
}));
}
}

View File

@ -209,14 +209,12 @@ HassAnchor.ANCHOR = Object.freeze({
CURRENT_STATISTICS_TIME: "current_statistics_time",
CURRENT_STATISTICS_AREA: "current_statistics_area",
FAN_SPEED: "fan_speed",
GOTO_PRESETS_LEN: "goto_presets_len",
MAP_SEGMENTS_LEN: "map_segments_len",
VACUUM_STATE: "vacuum_state",
WIFI_IPS: "wifi_ips",
WIFI_FREQUENCY: "wifi_freq",
WIFI_SIGNAL: "wifi_signal",
WIFI_SSID: "wifi_ssid",
ZONE_PRESETS_LEN: "zone_presets_len",
});
HassAnchor.REFERENCE = Object.freeze({
@ -224,14 +222,10 @@ HassAnchor.REFERENCE = Object.freeze({
BASIC_CONTROL_COMMAND: "basic_control_command",
FAN_SPEED_SET: "fan_speed_set",
FAN_SPEED_PRESETS: "fan_speed_presets", // Actually contains the presets, not a topic
GOTO_PRESETS: "goto_presets",
ZONE_PRESETS: "zone_presets",
HASS_CONSUMABLE_STATE: "hass_consumable_state_",
HASS_GOTO_LOCATION_STATE: "hass_goto_location_state",
HASS_MAP_SEGMENTS_STATE: "hass_map_segments_state",
HASS_WATER_GRADE_PRESETS: "hass_water_grade_presets",
HASS_WIFI_CONFIG_ATTRS: "hass_wifi_config_attrs",
HAZZ_ZONE_CLEANING_STATE: "hass_zone_cleaning_state",
});
module.exports = HassAnchor;

View File

@ -15,8 +15,6 @@
},
"blockExternalAccess": true
},
"zonePresets": {},
"goToLocationPresets": {},
"mqtt": {
"enabled": false,
"connection": {

View File

@ -1,11 +1,9 @@
const Logger = require("../Logger");
const ValetudoFullCleanupTimerAction = require("./actions/ValetudoFullCleanupTimerAction");
const ValetudoGoToTimerAction = require("./actions/ValetudoGoToTimerAction");
const ValetudoNTPClientDisabledState = require("../entities/core/ntpClient/ValetudoNTPClientDisabledState");
const ValetudoNTPClientSyncedState = require("../entities/core/ntpClient/ValetudoNTPClientSyncedState");
const ValetudoSegmentCleanupTimerAction = require("./actions/ValetudoSegmentCleanupTimerAction");
const ValetudoTimer = require("../entities/core/ValetudoTimer");
const ValetudoZoneCleanupTimerAction = require("./actions/ValetudoZoneCleanupTimerAction");
const MS_PER_MIN = 60 * 1000;
@ -91,12 +89,6 @@ class Scheduler {
case ValetudoTimer.ACTION_TYPE.FULL_CLEANUP:
action = new ValetudoFullCleanupTimerAction({robot: this.robot});
break;
case ValetudoTimer.ACTION_TYPE.ZONE_CLEANUP:
action = new ValetudoZoneCleanupTimerAction({
robot: this.robot,
zoneId: timerDefinition.action?.params?.zone_id
});
break;
case ValetudoTimer.ACTION_TYPE.SEGMENT_CLEANUP:
action = new ValetudoSegmentCleanupTimerAction({
robot: this.robot,
@ -105,12 +97,6 @@ class Scheduler {
customOrder: timerDefinition.action?.params?.custom_order
});
break;
case ValetudoTimer.ACTION_TYPE.GOTO_LOCATION:
action = new ValetudoGoToTimerAction({
robot: this.robot,
goToId: timerDefinition.action?.params?.goto_id
});
break;
}
if (action) {

View File

@ -1,36 +0,0 @@
const GoToLocationCapability = require("../../core/capabilities/GoToLocationCapability");
const ValetudoTimerAction = require("./ValetudoTimerAction");
class ValetudoGoToTimerAction extends ValetudoTimerAction {
/**
* @param {object} options
* @param {import("../../core/ValetudoRobot")} options.robot
* @param {string} options.goToId
*/
constructor(options) {
super(options);
this.goToId = options.goToId;
}
async run() {
if (!this.goToId) {
throw new Error("Missing goToId");
}
if (!this.robot.hasCapability(GoToLocationCapability.TYPE)) {
throw new Error("Robot is missing the GoToLocationCapability");
} else {
const capability = this.robot.capabilities[GoToLocationCapability.TYPE];
const goToLocationPreset = this.robot.config.get("goToLocationPresets")[this.goToId];
if (goToLocationPreset) {
return capability.goTo(goToLocationPreset);
} else {
throw new Error("There is no go to location preset with id " + this.goToId);
}
}
}
}
module.exports = ValetudoGoToTimerAction;

View File

@ -1,36 +0,0 @@
const ValetudoTimerAction = require("./ValetudoTimerAction");
const ZoneCleaningCapability = require("../../core/capabilities/ZoneCleaningCapability");
class ValetudoZoneCleanupTimerAction extends ValetudoTimerAction {
/**
* @param {object} options
* @param {import("../../core/ValetudoRobot")} options.robot
* @param {string} options.zoneId
*/
constructor(options) {
super(options);
this.zoneId = options.zoneId;
}
async run() {
if (!this.zoneId) {
throw new Error("Missing zoneId");
}
if (!this.robot.hasCapability(ZoneCleaningCapability.TYPE)) {
throw new Error("Robot is missing the ZoneCleaningCapability");
} else {
const capability = this.robot.capabilities[ZoneCleaningCapability.TYPE];
const zonePreset = this.robot.config.get("zonePresets")[this.zoneId];
if (zonePreset) {
return capability.start(zonePreset.zones);
} else {
throw new Error("There is no zone preset with id " + this.zoneId);
}
}
}
}
module.exports = ValetudoZoneCleanupTimerAction;

View File

@ -1,9 +1,7 @@
const BasicControlCapability = require("../core/capabilities/BasicControlCapability");
const express = require("express");
const GoToLocationCapability = require("../core/capabilities/GoToLocationCapability");
const MapSegmentationCapability = require("../core/capabilities/MapSegmentationCapability");
const ValetudoTimer = require("../entities/core/ValetudoTimer");
const ZoneCleaningCapability = require("../core/capabilities/ZoneCleaningCapability");
class TimerRouter {
/**
@ -42,14 +40,6 @@ class TimerRouter {
response.supportedActions.push(ValetudoTimer.ACTION_TYPE.SEGMENT_CLEANUP);
}
if (this.robot.hasCapability(ZoneCleaningCapability.TYPE)) {
response.supportedActions.push(ValetudoTimer.ACTION_TYPE.ZONE_CLEANUP);
}
if (this.robot.hasCapability(GoToLocationCapability.TYPE)) {
response.supportedActions.push(ValetudoTimer.ACTION_TYPE.GOTO_LOCATION);
}
res.json(response);
});

View File

@ -8,155 +8,11 @@ const ValetudoGoToLocation = require("../../entities/core/ValetudoGoToLocation")
class GoToLocationCapabilityRouter extends CapabilityRouter {
initRoutes() {
this.router.get("/presets", (req, res) => {
res.json(this.capability.robot.config.get("goToLocationPresets"));
});
this.router.get("/presets/:id", (req, res) => {
const locationPreset = this.capability.robot.config.get("goToLocationPresets")[req.params.id];
if (locationPreset) {
res.json(locationPreset);
} else {
res.sendStatus(404);
}
});
this.router.put("/presets/:id", async (req, res) => {
const locationPreset = this.capability.robot.config.get("goToLocationPresets")[req.params.id];
if (locationPreset && req.body && req.body.action === "goto") {
try {
await this.capability.goTo(locationPreset);
res.sendStatus(200);
} catch (e) {
Logger.warn("Error while going to goToLocationPreset for preset " + req.params.id, e);
res.status(500).json(e.message);
}
} else {
res.sendStatus(404);
}
});
this.router.delete("/presets/:id", (req, res) => {
const goToLocationPresets = this.capability.robot.config.get("goToLocationPresets");
if (goToLocationPresets[req.params.id]) {
delete(goToLocationPresets[req.params.id]);
this.capability.robot.config.set("goToLocationPresets", goToLocationPresets);
res.sendStatus(200);
} else {
res.sendStatus(404);
}
});
this.router.post("/presets/:id", (req, res) => {
const goToLocationPresets = this.capability.robot.config.get("goToLocationPresets");
if (goToLocationPresets[req.params.id]) {
if (req.body && req.body.name && req.body.coordinates && req.body.coordinates.x !== undefined && req.body.coordinates.y !== undefined) {
try {
const newPreset = new ValetudoGoToLocation({
name: req.body.name,
id: req.params.id,
coordinates: req.body.coordinates
});
goToLocationPresets[newPreset.id] = newPreset;
this.capability.robot.config.set("goToLocationPresets", goToLocationPresets);
res.sendStatus(200);
} catch (e) {
Logger.warn("Error while saving goToLocationPreset", req.body);
res.status(500).json(e.message);
}
}
} else {
res.sendStatus(404);
}
});
this.router.post("/presets", (req, res) => {
if (req.body && req.body.name && req.body.coordinates && req.body.coordinates.x !== undefined && req.body.coordinates.y !== undefined) {
try {
const goToLocationPresets = this.capability.robot.config.get("goToLocationPresets");
const newPreset = new ValetudoGoToLocation({
name: req.body.name,
id: req.body.id,
coordinates: req.body.coordinates
});
goToLocationPresets[newPreset.id] = newPreset;
this.capability.robot.config.set("goToLocationPresets", goToLocationPresets);
res.sendStatus(200);
} catch (e) {
Logger.warn("Error while saving new goToLocationPreset", req.body);
res.status(500).json(e.message);
}
} else {
res.sendStatus(400);
}
});
//TODO: Remove this after building a new webinterface
this.router.get("/presets_legacy", (req, res) => {
const presetsFromConfig = Object.values(this.capability.robot.config.get("goToLocationPresets"));
res.json(presetsFromConfig.map(preset => {
return {
name: preset.name,
id: preset.id,
coordinates: [preset.coordinates.x, preset.coordinates.y]
};
}));
});
this.router.post("/presets_legacy", (req, res) => {
if (Array.isArray(req.body)) {
const valid = req.body.every(p => {
return p && p.name && Array.isArray(p.coordinates);
});
if (valid) {
const presetsArr = req.body.map(preset => {
return new ValetudoGoToLocation({
name: preset.name,
id: preset.id,
coordinates: {
x: preset.coordinates[0],
y: preset.coordinates[1]
}
});
});
const presets = {};
presetsArr.forEach(z => {
presets[z.id] = z;
});
this.capability.robot.config.set("goToLocationPresets", presets);
res.sendStatus(200);
} else {
res.sendStatus(400);
}
}
});
this.router.put("/", async (req, res) => {
if (req.body && req.body.action) {
if (req.body.action === "goto" && req.body.coordinates && req.body.coordinates.x !== undefined && req.body.coordinates.y !== undefined) {
try {
await this.capability.goTo(new ValetudoGoToLocation({
name: "dynamic",
coordinates: req.body.coordinates
}));
res.sendStatus(200);

View File

@ -4,191 +4,10 @@ const CapabilityRouter = require("./CapabilityRouter");
const Logger = require("../../Logger");
const ValetudoZone = require("../../entities/core/ValetudoZone");
const ValetudoZonePreset = require("../../entities/core/ValetudoZonePreset");
class ZoneCleaningCapabilityRouter extends CapabilityRouter {
initRoutes() {
this.router.get("/presets", (req, res) => {
res.json(this.capability.robot.config.get("zonePresets"));
});
this.router.get("/presets/:id", (req, res) => {
const zone = this.capability.robot.config.get("zonePresets")[req.params.id];
if (zone) {
res.json(zone);
} else {
res.sendStatus(404);
}
});
this.router.put("/presets/:id", async (req, res) => {
const zone = this.capability.robot.config.get("zonePresets")[req.params.id];
if (zone && req.body && req.body.action === "clean") {
try {
await this.capability.start(zone.zones);
res.sendStatus(200);
} catch (e) {
Logger.warn("Error while starting zone cleaning for preset " + req.params.id, e);
res.status(500).json(e.message);
}
} else {
res.sendStatus(404);
}
});
this.router.delete("/presets/:id", (req, res) => {
const zoneSettings = this.capability.robot.config.get("zonePresets");
if (zoneSettings[req.params.id]) {
delete(zoneSettings[req.params.id]);
this.capability.robot.config.set("zonePresets", zoneSettings);
res.sendStatus(200);
} else {
res.sendStatus(404);
}
});
this.router.post("/presets", (req, res) => {
if (req.body && req.body.name && Array.isArray(req.body.zones) && req.body.zones.length > 0) {
try {
const zoneSettings = this.capability.robot.config.get("zonePresets");
const newPreset = new ValetudoZonePreset({
name: req.body.name,
id: req.body.id,
zones: req.body.zones.map(z => {
return new ValetudoZone({
points: z.points,
iterations: z.iterations
});
})
});
zoneSettings[newPreset.id] = newPreset;
this.capability.robot.config.set("zonePresets", zoneSettings);
res.sendStatus(200);
} catch (e) {
Logger.warn("Error while saving new zone", req.body);
res.status(500).json(e.message);
}
} else {
res.sendStatus(400);
}
});
//TODO: Remove this after building a new webinterface
this.router.get("/presets_legacy", (req, res) => {
const presetsFromConfig = Object.values(this.capability.robot.config.get("zonePresets"));
res.json(presetsFromConfig.map(preset => {
return {
name: preset.name,
id: preset.id,
areas: preset.zones.map(zone => {
return [
zone.points.pA.x,
zone.points.pA.y,
zone.points.pC.x,
zone.points.pC.y,
zone.iterations
];
})
};
}));
});
this.router.post("/presets_legacy", (req, res) => {
if (Array.isArray(req.body)) {
const valid = req.body.every(z => {
return z && z.name && Array.isArray(z.areas);
});
if (valid) {
const zonePresetsArr = req.body.map(preset => {
return new ValetudoZonePreset({
name: preset.name,
id: preset.id,
zones: preset.areas.map(zone => {
return new ValetudoZone({
points: {
pA: {
x: zone[0],
y: zone[1]
},
pB: {
x: zone[2],
y: zone[1]
},
pC: {
x: zone[2],
y: zone[3]
},
pD: {
x: zone[0],
y: zone[3]
},
},
iterations: zone[4]
});
})
});
});
const zonePresets = {};
zonePresetsArr.forEach(z => {
zonePresets[z.id] = z;
});
this.capability.robot.config.set("zonePresets", zonePresets);
res.sendStatus(200);
} else {
res.sendStatus(400);
}
}
});
this.router.post("/presets/:id", (req, res) => {
const zoneSettings = this.capability.robot.config.get("zonePresets");
if (zoneSettings[req.params.id]) {
if (req.body && req.body.name && Array.isArray(req.body.zones) && req.body.zones.length > 0) {
try {
const newPreset = new ValetudoZonePreset({
name: req.body.name,
id: req.params.id,
zones: req.body.zones.map(z => {
return new ValetudoZone({
points: z.points,
iterations: z.iterations
});
})
});
zoneSettings[newPreset.id] = newPreset;
this.capability.robot.config.set("zonePresets", zoneSettings);
res.sendStatus(200);
} catch (e) {
Logger.warn("Error while saving new zone", req.body);
res.status(500).json(e.message);
}
}
} else {
res.sendStatus(404);
}
});
this.router.put("/", async (req, res) => {
if (req.body && req.body.action) {
if (req.body.action === "clean" && Array.isArray(req.body.zones)) {

View File

@ -1,190 +1,4 @@
{
"/api/v2/robot/capabilities/GoToLocationCapability/presets": {
"get": {
"tags": [
"GoToLocationCapability"
],
"summary": "Get available go-to-location presets",
"responses": {
"200": {
"description": "Ok",
"content": {
"application/json": {
"schema": {
"type": "object",
"description": "Describing this structure requires OpenAPI 3.1 support in Swagger UI"
}
}
}
}
}
},
"post": {
"tags": [
"GoToLocationCapability"
],
"summary": "Add new preset",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValetudoGoToLocation"
}
}
}
},
"responses": {
"200": {
"$ref": "#/components/responses/200"
},
"400": {
"$ref": "#/components/responses/400"
}
}
}
},
"/api/v2/robot/capabilities/GoToLocationCapability/presets/{id}": {
"get": {
"tags": [
"GoToLocationCapability"
],
"summary": "Get go-to-location preset by ID",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"description": "Preset UUID",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Ok",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValetudoGoToLocation"
}
}
}
},
"404": {
"description": "The specified preset ID was not found"
}
}
},
"put": {
"tags": [
"GoToLocationCapability"
],
"summary": "Go to a go-to-location preset",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"description": "Preset UUID",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": false,
"required": [
"action"
],
"properties": {
"action": {
"type": "string",
"enum": [
"goto"
]
}
}
}
}
}
},
"responses": {
"200": {
"$ref": "#/components/responses/200"
},
"404": {
"description": "The specified preset ID was not found"
},
"500": {
"$ref": "#/components/responses/500"
}
}
},
"post": {
"tags": [
"GoToLocationCapability"
],
"summary": "Edit existing preset by ID",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"description": "Preset UUID",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValetudoGoToLocation"
}
}
}
},
"responses": {
"200": {
"$ref": "#/components/responses/200"
},
"404": {
"description": "The specified preset ID does not exist."
}
}
},
"delete": {
"tags": [
"GoToLocationCapability"
],
"summary": "Delete go-to-location preset by ID",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"description": "Preset UUID",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"$ref": "#/components/responses/200"
},
"404": {
"description": "The specified preset IDs was not found"
}
}
}
},
"/api/v2/robot/capabilities/GoToLocationCapability": {
"put": {
"tags": [

View File

@ -1,186 +1,4 @@
{
"/api/v2/robot/capabilities/ZoneCleaningCapability/presets": {
"get": {
"tags": [
"ZoneCleaningCapability"
],
"summary": "Get available zone presets",
"responses": {
"200": {
"description": "Ok",
"content": {
"application/json": {
"schema": {
"type": "object",
"description": "Describing this structure requires OpenAPI 3.1 support in Swagger UI"
}
}
}
}
}
},
"post": {
"tags": [
"ZoneCleaningCapability"
],
"summary": "Add new preset",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValetudoZonePreset"
}
}
}
},
"responses": {
"200": {
"$ref": "#/components/responses/200"
},
"400": {
"$ref": "#/components/responses/400"
}
}
}
},
"/api/v2/robot/capabilities/ZoneCleaningCapability/presets/{id}": {
"get": {
"tags": [
"ZoneCleaningCapability"
],
"summary": "Get zone preset by ID",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"description": "Preset UUID",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Ok",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValetudoZonePreset"
}
}
}
},
"404": {
"description": "One or more specified zone preset IDs were not found"
}
}
},
"put": {
"tags": [
"ZoneCleaningCapability"
],
"summary": "Clean zone preset by ID",
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"action": {
"type": "string",
"enum": [
"clean"
]
}
}
}
}
}
},
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"description": "Preset UUID",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"$ref": "#/components/responses/200"
},
"404": {
"description": "The specified zone preset IDs was not found"
}
}
},
"delete": {
"tags": [
"ZoneCleaningCapability"
],
"summary": "Delete zone preset by ID",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"description": "Preset UUID",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"$ref": "#/components/responses/200"
},
"404": {
"description": "The specified zone preset IDs was not found"
}
}
},
"post": {
"tags": [
"ZoneCleaningCapability"
],
"summary": "Edit existing preset by ID",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"description": "Preset UUID",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValetudoZonePreset"
}
}
}
},
"responses": {
"200": {
"$ref": "#/components/responses/200"
},
"400": {
"$ref": "#/components/responses/400"
},
"404": {
"description": "The specified zone ID does not exist."
}
}
}
},
"/api/v2/robot/capabilities/ZoneCleaningCapability": {
"put": {
"tags": [

View File

@ -63,36 +63,6 @@
}
}
}
},
"zone_cleanup": {
"description": "A timer scheduling a cleanup of a zone preset every sunday at 11:10 UTC",
"value": {
"enabled": true,
"dow": [0],
"hour": 11,
"minute": 10,
"action": {
"type": "zone_cleanup",
"params": {
"zone_id": "338b8c37-8ecd-48b4-b3d2-e03fd74d2e25"
}
}
}
},
"goto_location": {
"description": "A timer scheduling a goto preset every tuesday at 4AM UTC",
"value": {
"enabled": true,
"dow": [2],
"hour": 4,
"minute": 0,
"action": {
"type": "goto_location",
"params": {
"goto_id": "c04a24bd-67fc-4ca8-9dbe-2d867fb71147"
}
}
}
}
}
}
@ -225,9 +195,7 @@
"type": "string",
"enum": [
"full_cleanup",
"zone_cleanup",
"segment_cleanup",
"goto_location"
"segment_cleanup"
]
}
}

View File

@ -57,8 +57,6 @@ This capability enables you to send your robot to a location on your map. It wil
One common use-case of this is to send the robot to your bin.
Furthermore, this capability will enable you to define ValetudoGoToLocationPresets which are predefined spots that can be called via MQTT.
## KeyLockCapability <a id="KeyLockCapability"></a>
This capability enables you to disable control of the robot via the buttons on the devices.
@ -143,5 +141,3 @@ This capability enables you to get the current Wi-Fi connection details (includi
## ZoneCleaningCapability <a id="ZoneCleaningCapability"></a>
This capability enables you to send your robot to clean one or more (depending on the vendor) zones drawn onto the map.
Furthermore, this also enables you to define ValetudoZonePresets which are predefined zones that can be called via MQTT.

View File

@ -8,7 +8,6 @@ import {
ConsumableId,
ConsumableState,
DoNotDisturbConfiguration,
GoToLocation,
HTTPBasicAuthConfiguration,
LogLevelResponse,
ManualControlInteraction,
@ -51,7 +50,6 @@ import {
WifiConfiguration,
WifiStatus,
Zone,
ZonePreset,
ZoneProperties,
} from "./types";
import { floorObject } from "./utils";
@ -226,16 +224,6 @@ export const sendGoToCommand = async (point: Point): Promise<void> => {
);
};
export const fetchZonePresets = async (): Promise<ZonePreset[]> => {
return valetudoAPI
.get<Record<string, ZonePreset>>(
`/robot/capabilities/${Capability.ZoneCleaning}/presets`
)
.then(({data}) => {
return Object.values(data);
});
};
export const fetchZoneProperties = async (): Promise<ZoneProperties> => {
return valetudoAPI
.get<ZoneProperties>(
@ -246,15 +234,6 @@ export const fetchZoneProperties = async (): Promise<ZoneProperties> => {
});
};
export const sendCleanZonePresetCommand = async (id: string): Promise<void> => {
await valetudoAPI.put(
`/robot/capabilities/${Capability.ZoneCleaning}/presets/${id}`,
{
action: "clean",
}
);
};
export const sendCleanTemporaryZonesCommand = async (
zones: Zone[]
): Promise<void> => {
@ -339,27 +318,6 @@ export const sendRenameSegmentCommand = async (
);
};
export const fetchGoToLocationPresets = async (): Promise<Segment[]> => {
return valetudoAPI
.get<Record<string, GoToLocation>>(
`/robot/capabilities/${Capability.GoToLocation}/presets`
)
.then(({data}) => {
return Object.values(data);
});
};
export const sendGoToLocationPresetCommand = async (
id: string
): Promise<void> => {
await valetudoAPI.put(
`/robot/capabilities/${Capability.GoToLocation}/presets/${id}`,
{
action: "goto",
}
);
};
export const sendLocateCommand = async (): Promise<void> => {
await valetudoAPI.put(`/robot/capabilities/${Capability.Locate}`, {
action: "locate",

View File

@ -19,7 +19,6 @@ import {
fetchCurrentStatistics,
fetchCurrentStatisticsProperties,
fetchDoNotDisturbConfiguration,
fetchGoToLocationPresets,
fetchHTTPBasicAuthConfiguration,
fetchKeyLockState,
fetchManualControlProperties,
@ -49,7 +48,6 @@ import {
fetchValetudoLogLevel,
fetchVoicePackManagementState,
fetchWifiStatus,
fetchZonePresets,
fetchZoneProperties,
sendAutoEmptyDockAutoEmptyControlEnable,
sendAutoEmptyDockManualTriggerCommand,
@ -57,12 +55,10 @@ import {
sendCarpetModeEnable,
sendCleanSegmentsCommand,
sendCleanTemporaryZonesCommand,
sendCleanZonePresetCommand,
sendCombinedVirtualRestrictionsUpdate,
sendConsumableReset,
sendDoNotDisturbConfiguration,
sendGoToCommand,
sendGoToLocationPresetCommand,
sendHTTPBasicAuthConfiguration,
sendJoinSegmentsCommand,
sendKeyLockEnable,
@ -134,11 +130,9 @@ enum CacheKey {
Consumables = "consumables",
Attributes = "attributes",
PresetSelections = "preset_selections",
ZonePresets = "zone_presets",
ZoneProperties = "zone_properties",
Segments = "segments",
MapSegmentationProperties = "map_segmentation_properties",
GoToLocationPresets = "go_to_location_presets",
PersistentData = "persistent_data",
RobotInformation = "robot_information",
ValetudoInformation = "valetudo_information",
@ -386,39 +380,12 @@ export const useGoToMutation = (
);
};
export const useZonePresetsQuery = () => {
return useQuery(CacheKey.ZonePresets, fetchZonePresets);
};
export const useZonePropertiesQuery = () => {
return useQuery(CacheKey.ZoneProperties, fetchZoneProperties, {
staleTime: Infinity,
});
};
export const useCleanZonePresetMutation = (
options?: UseMutationOptions<RobotAttribute[], unknown, string>
) => {
const queryClient = useQueryClient();
const onError = useOnCommandError(Capability.ZoneCleaning);
return useMutation(
(id: string) => {
return sendCleanZonePresetCommand(id).then(fetchStateAttributes);
},
{
onError,
...options,
async onSuccess(data, ...args) {
queryClient.setQueryData<RobotAttribute[]>(CacheKey.Attributes, data, {
updatedAt: Date.now(),
});
await options?.onSuccess?.(data, ...args);
},
}
);
};
export const useCleanTemporaryZonesMutation = (
options?: UseMutationOptions<RobotAttribute[], unknown, Zone[]>
) => {
@ -547,33 +514,6 @@ export const useRenameSegmentMutation = (
);
};
export const useGoToLocationPresetsQuery = () => {
return useQuery(CacheKey.GoToLocationPresets, fetchGoToLocationPresets);
};
export const useGoToLocationPresetMutation = (
options?: UseMutationOptions<RobotAttribute[], unknown, string>
) => {
const queryClient = useQueryClient();
const onError = useOnCommandError(Capability.ZoneCleaning);
return useMutation(
(id: string) => {
return sendGoToLocationPresetCommand(id).then(fetchStateAttributes);
},
{
onError,
...options,
async onSuccess(data, ...args) {
queryClient.setQueryData<RobotAttribute[]>(CacheKey.Attributes, data, {
updatedAt: Date.now(),
});
await options?.onSuccess?.(data, ...args);
},
}
);
};
export const useLocateMutation = () => {
const onError = useOnCommandError(Capability.Locate);

View File

@ -46,12 +46,6 @@ export interface Zone {
iterations: number;
}
export interface ZonePreset {
id: string;
name: string;
zones: Zone[];
}
export interface ZoneProperties {
zoneCount: {
min: number;
@ -71,12 +65,6 @@ export interface MapSegmentationProperties {
customOrderSupport: boolean;
}
export interface GoToLocation {
id: string;
name: string;
coordinates: Point;
}
export interface Segment {
id: string;
name?: string;

View File

@ -3,10 +3,8 @@ import {Opacity as WaterUsageIcon,} from "@mui/icons-material";
import {Capability, useRobotInformationQuery} from "../api";
import {useCapabilitiesSupported} from "../CapabilitiesProvider";
import BasicControls from "./BasicControls";
import GoToLocationPresets from "./GoToLocationPresets";
import PresetSelectionControl from "./PresetSelection";
import RobotStatus from "./RobotStatus";
import ZonePresets from "./ZonePresets";
import Dock from "./Dock";
import CurrentStatistics from "./CurrentStatistics";
import Attachments from "./Attachments";
@ -19,16 +17,12 @@ const ControlsBody = (): JSX.Element => {
basicControls,
fanSpeed,
waterControl,
goToLocation,
zoneCleaning,
triggerEmptySupported,
currentStatistics,
] = useCapabilitiesSupported(
Capability.BasicControl,
Capability.FanSpeedControl,
Capability.WaterUsageControl,
Capability.GoToLocation,
Capability.ZoneCleaning,
Capability.AutoEmptyDockManualTrigger,
Capability.CurrentStatistics
);
@ -64,8 +58,6 @@ const ControlsBody = (): JSX.Element => {
)}
{triggerEmptySupported && <Dock/>}
{goToLocation && <GoToLocationPresets />}
{zoneCleaning && <ZonePresets />}
{
robotInformation &&

View File

@ -1,147 +0,0 @@
import {
Box,
Button,
CircularProgress,
FormControl,
FormHelperText,
Grid,
MenuItem,
Paper,
Select,
SelectChangeEvent,
styled,
Typography,
} from "@mui/material";
import React from "react";
import {
Capability,
useGoToLocationPresetMutation,
useGoToLocationPresetsQuery,
useRobotStatusQuery,
} from "../api";
const StyledFormControl = styled(FormControl)({
minWidth: 120,
});
const GoToLocationPresets = (): JSX.Element => {
const { data: status } = useRobotStatusQuery((status) => {
return status.value;
});
const {
data: goToLocations,
isLoading: goToLocationPresetsLoading,
isError: goToLocationPresetLoadError,
} = useGoToLocationPresetsQuery();
const {
isLoading: goToLocationPresetIsExecuting,
mutate: goToLocationPreset
} = useGoToLocationPresetMutation({
onSuccess() {
setSelected("");
},
});
const [selected, setSelected] = React.useState<string>("");
const handleChange = React.useCallback(
(event: SelectChangeEvent<string>) => {
setSelected(event.target.value);
},
[]
);
const canGo = status === "idle" || status === "docked";
const handleGo = React.useCallback(() => {
if (selected === "" || !canGo) {
return;
}
goToLocationPreset(selected);
}, [canGo, goToLocationPreset, selected]);
const body = React.useMemo(() => {
if (goToLocationPresetsLoading) {
return (
<Grid item>
<CircularProgress size={20} />
</Grid>
);
}
if (goToLocationPresetLoadError || goToLocations === undefined) {
return (
<Grid item>
<Typography color="error">
Error loading {Capability.GoToLocation}
</Typography>
</Grid>
);
}
return (
<>
<Grid item>
<StyledFormControl>
<Select
value={selected}
onChange={handleChange}
displayEmpty
variant="standard"
>
<MenuItem value="">
<em>Preset</em>
</MenuItem>
{goToLocations.map(({ name, id }) => {
return (
<MenuItem key={id} value={id}>
{name}
</MenuItem>
);
})}
</Select>
{!canGo && selected !== "" && (
<FormHelperText>Can only go to location when idle</FormHelperText>
)}
</StyledFormControl>
</Grid>
<Grid item xs>
<Box display="flex" justifyContent="flex-end">
<Button
disabled={!selected || goToLocationPresetIsExecuting || !canGo}
onClick={handleGo}
>
Go
</Button>
</Box>
</Grid>
</>
);
}, [
canGo,
handleChange,
handleGo,
goToLocationPresetIsExecuting,
goToLocationPresetLoadError,
goToLocationPresetsLoading,
goToLocations,
selected,
]);
return (
<Grid item>
<Paper>
<Box px={2} py={1}>
<Grid container direction="row" alignItems="center" spacing={1}>
<Grid item>
<Typography variant="subtitle1">Go to</Typography>
</Grid>
{body}
</Grid>
</Box>
</Paper>
</Grid>
);
};
export default GoToLocationPresets;

View File

@ -1,144 +0,0 @@
import {
Box,
Button,
CircularProgress,
FormControl,
FormHelperText,
Grid,
MenuItem,
Paper,
Select,
SelectChangeEvent,
styled,
Typography,
} from "@mui/material";
import React from "react";
import {
Capability,
useCleanZonePresetMutation,
useRobotStatusQuery,
useZonePresetsQuery,
} from "../api";
const StyledFormControl = styled(FormControl)({
minWidth: 120,
});
const ZonePresets = (): JSX.Element => {
const { data: status } = useRobotStatusQuery((status) => {
return status.value;
});
const {
data: zonePresets,
isLoading: zonePresetsLoading,
isError: errorLoadingZonePresets,
} = useZonePresetsQuery();
const {
isLoading: cleanZonePresetExecuting,
mutate: cleanZonePreset
} = useCleanZonePresetMutation({
onSuccess() {
setSelected("");
},
});
const [selected, setSelected] = React.useState<string>("");
const canClean = status === "idle" || status === "docked";
const handleChange = React.useCallback(
(event: SelectChangeEvent<string>) => {
setSelected(event.target.value as string);
},
[]
);
const handleClean = React.useCallback(() => {
if (selected === "" || !canClean) {
return;
}
cleanZonePreset(selected);
}, [canClean, cleanZonePreset, selected]);
const body = React.useMemo(() => {
if (zonePresetsLoading) {
return (
<Grid item>
<CircularProgress size={20} />
</Grid>
);
}
if (errorLoadingZonePresets || zonePresets === undefined) {
return (
<Grid item>
<Typography color="error">
Error loading {Capability.ZoneCleaning}
</Typography>
</Grid>
);
}
return (
<>
<Grid item>
<StyledFormControl>
<Select
value={selected}
onChange={handleChange}
displayEmpty
variant="standard"
>
<MenuItem value="">
<em>Preset</em>
</MenuItem>
{zonePresets.map(({ name, id }) => {
return (
<MenuItem key={id} value={id}>
{name}
</MenuItem>
);
})}
</Select>
{!canClean && selected !== "" && (
<FormHelperText>Can only start cleaning when idle</FormHelperText>
)}
</StyledFormControl>
</Grid>
<Grid item xs>
<Box display="flex" justifyContent="flex-end">
<Button
disabled={!selected || cleanZonePresetExecuting || !canClean}
onClick={handleClean}
>
Clean
</Button>
</Box>
</Grid>
</>
);
}, [
canClean,
handleChange,
handleClean,
cleanZonePresetExecuting,
errorLoadingZonePresets,
zonePresetsLoading,
selected,
zonePresets,
]);
return (
<Grid item>
<Paper>
<Box px={2} py={1}>
<Grid container direction="row" alignItems="center" spacing={1}>
<Grid item>
<Typography variant="subtitle1">Clean zone preset</Typography>
</Grid>
{body}
</Grid>
</Box>
</Paper>
</Grid>
);
};
export default ZonePresets;

View File

@ -2,10 +2,8 @@ import React, { FunctionComponent } from "react";
import { TimerActionControlProps } from "./types";
import {
Capability,
useGoToLocationPresetsQuery,
useMapSegmentationPropertiesQuery,
useSegmentsQuery,
useZonePresetsQuery,
} from "../../api";
import {
Box,
@ -34,15 +32,9 @@ export const validateParams: Record<
full_cleanup: () => {
return true;
},
zone_cleanup: (props) => {
return props.zone_id && props.zone_id !== "none";
},
segment_cleanup: (props) => {
return props.segment_ids?.length > 0 && (props.iterations ?? 1 > 0);
},
goto_location: (props) => {
return props.goto_id && props.goto_id !== "none";
},
};
export const FullCleanupControls: FunctionComponent<TimerActionControlProps> =
@ -51,65 +43,6 @@ export const FullCleanupControls: FunctionComponent<TimerActionControlProps> =
return null;
};
export const ZoneCleanupControls: FunctionComponent<TimerActionControlProps> =
({ disabled, params, setParams }) => {
const selectedZoneId = params.zone_id ?? "none";
const {
data: zonePresets,
isLoading: zonePresetsLoading,
isError: zonePresetsError,
} = useZonePresetsQuery();
const zoneMenuItems = React.useMemo(() => {
if (!zonePresets) {
return null;
}
return zonePresets.map(({ name, id }) => {
return (
<MenuItem key={id} value={id}>
{name || "Unnamed zone: " + id}
</MenuItem>
);
});
}, [zonePresets]);
if (zonePresetsLoading) {
return <CircularProgress />;
}
if (zonePresetsError) {
return (
<Typography color="error">
Error loading {Capability.ZoneCleaning}
</Typography>
);
}
return (
<FormControl>
<InputLabel id={"zone-label"}>Select zone</InputLabel>
<Select
labelId={"zone-label"}
id={"zone-select"}
value={selectedZoneId}
label="Select zone"
disabled={disabled}
onChange={(e) => {
setParams({
zone_id: e.target.value,
});
}}
>
<MenuItem value={"none"}>
<em>No zone selected</em>
</MenuItem>
{zoneMenuItems}
</Select>
</FormControl>
);
};
export const SegmentCleanupControls: FunctionComponent<TimerActionControlProps> =
({ disabled, params, setParams }) => {
const segmentIds: Array<string> = React.useMemo(() => {
@ -318,64 +251,3 @@ export const SegmentCleanupControls: FunctionComponent<TimerActionControlProps>
</>
);
};
export const GoToLocationControls: FunctionComponent<TimerActionControlProps> =
({ params, setParams, disabled }) => {
const selectedGoToId = params.goto_id ?? "none";
const {
data: goToLocations,
isLoading: goToLocationPresetsLoading,
isError: goToLocationPresetLoadError,
} = useGoToLocationPresetsQuery();
const goToMenuItems = React.useMemo(() => {
if (!goToLocations) {
return null;
}
return goToLocations.map(({ name, id }) => {
return (
<MenuItem key={id} value={id}>
{name || "Unnamed go to location: " + id}
</MenuItem>
);
});
}, [goToLocations]);
if (goToLocationPresetsLoading) {
return <CircularProgress />;
}
if (goToLocationPresetLoadError) {
return (
<Typography color="error">
Error loading {Capability.GoToLocation}
</Typography>
);
}
return (
<FormControl>
<InputLabel id={"go-to-location-label"}>
Select go to location
</InputLabel>
<Select
labelId={"go-to-location-label"}
id={"go-to-location-select"}
value={selectedGoToId}
label="Select go to location"
disabled={disabled}
onChange={(e) => {
setParams({
goto_id: e.target.value,
});
}}
>
<MenuItem value={"none"}>
<em>No go to location selected</em>
</MenuItem>
{goToMenuItems}
</Select>
</FormControl>
);
};

View File

@ -39,9 +39,7 @@ type TimerCardProps = {
export const timerActionLabels: Record<string, string> = {
full_cleanup: "Full cleanup",
zone_cleanup: "Zone cleanup",
segment_cleanup: "Segment cleanup",
goto_location: "Go to location",
};
function convertTime(hour: number, minute: number, offset: number) : { hour: number, minute: number } {

View File

@ -25,10 +25,8 @@ import { StaticTimePicker } from "@mui/lab";
import { TimerActionControlProps } from "./types";
import {
FullCleanupControls,
GoToLocationControls,
SegmentCleanupControls,
validateParams,
ZoneCleanupControls,
} from "./ActionControls";
const actionControls: Record<
@ -36,9 +34,7 @@ const actionControls: Record<
React.ComponentType<TimerActionControlProps>
> = {
full_cleanup: FullCleanupControls,
zone_cleanup: ZoneCleanupControls,
segment_cleanup: SegmentCleanupControls,
goto_location: GoToLocationControls,
};
type TimerDialogProps = {

View File

@ -86,28 +86,6 @@ const fakeConfig = {
onUpdate: (_) => {
},
get: key => fakeConfig[key],
"goToLocationPresets": {
"a9666386-7041-4bd4-a823-ebefa48665eb": {
"__class": "ValetudoGoToLocation",
"metaData": {},
"name": "SpotA",
"coordinates": {
"x": 2589,
"y": 2364
},
"id": "a9666386-7041-4bd4-a823-ebefa48665eb"
},
"6c74ac84-dfe9-4c4c-8bec-836ff268d630": {
"__class": "ValetudoGoToLocation",
"metaData": {},
"name": "SpotB",
"coordinates": {
"x": 2186,
"y": 2262
},
"id": "6c74ac84-dfe9-4c4c-8bec-836ff268d630"
}
},
};
const eventStore = new ValetudoEventStore()