diff --git a/docs/templates/neato.md b/docs/templates/neato.md index 7a6da12d..bde00cec 100644 --- a/docs/templates/neato.md +++ b/docs/templates/neato.md @@ -4,14 +4,6 @@ This platform can be used to control vacuums connected to Home Assistant using built-in Neato integration. -## Calibration - -You can calibrate the map using following config: -```yaml -calibration_source: - identity: true -``` - ## Available templates * ### Room cleaning (`vacuum_clean_segment`) diff --git a/docs/templates/romedtinoSimpleWyze.md b/docs/templates/romedtinoSimpleWyze.md index 73656ac0..2c44d935 100644 --- a/docs/templates/romedtinoSimpleWyze.md +++ b/docs/templates/romedtinoSimpleWyze.md @@ -6,14 +6,6 @@ This platform can be used to control vacuums connected to Home Assistant using c To reset a value for a given consumable press and hold a matching tile. -## Calibration - -You can calibrate the map using following config: -```yaml -calibration_source: - identity: true -``` - ## Available templates * ### Room cleaning (`vacuum_clean_segment`) diff --git a/docs/templates/roomba.md b/docs/templates/roomba.md index 88f87aa1..f8374172 100644 --- a/docs/templates/roomba.md +++ b/docs/templates/roomba.md @@ -4,14 +4,6 @@ This platform can be used to control vacuums connected to Home Assistant using built-in Roomba integration. -## Calibration - -You can calibrate the map using following config: -```yaml -calibration_source: - identity: true -``` - ## Available templates * ### Room cleaning (`vacuum_clean_segment`) diff --git a/src/config-validators.ts b/src/config-validators.ts index 8fa87375..c989a71f 100644 --- a/src/config-validators.ts +++ b/src/config-validators.ts @@ -67,7 +67,7 @@ function validateIconConfig(config: IconActionConfig): TranslatableString[] { return ["validation.preset.icons.invalid"]; } const errors: TranslatableString[] = []; - if (!config.icon && config.type !== "menu") { + if (!config.icon && config.type !== "menu" && !config.replace_config) { errors.push("validation.preset.icons.icon.missing"); } return errors; @@ -239,11 +239,20 @@ function validateMapModeConfig( function validatePreset(config: CardPresetConfig, nameRequired: boolean, language: Language): TranslatableString[] { const errors: TranslatableString[] = []; + const platformsWithDefaultCalibration = [ + PlatformGenerator.DEEBOTUNIVERSE_DEEBOT_4_HOME_ASSISTANT_PLATFORM, + PlatformGenerator.NEATO_PLATFORM, + PlatformGenerator.ROMEDTINO_SIMPLE_WAZE_PLATFORM, + PlatformGenerator.ROOMBA_PLATFORM, + ]; const mandatoryFields = new Map([ ["entity", "validation.preset.entity.missing"], ["map_source", "validation.preset.map_source.missing"], - ["calibration_source", "validation.preset.calibration_source.missing"], ]); + const vacuumPlatform = PlatformGenerator.getPlatformName(config.vacuum_platform); + if (!platformsWithDefaultCalibration.includes(vacuumPlatform)) { + mandatoryFields.set("calibration_source", "validation.preset.calibration_source.missing"); + } const params = Object.keys(config); mandatoryFields.forEach((v: string, k: string) => { if (!params.includes(k)) { @@ -254,7 +263,6 @@ function validatePreset(config: CardPresetConfig, nameRequired: boolean, languag if (config.calibration_source) validateCalibrationSource(config.calibration_source).forEach(e => errors.push(e)); if (config.vacuum_platform && !PlatformGenerator.getPlatforms().includes(config.vacuum_platform)) errors.push(["validation.preset.platform.invalid", "{0}", config.vacuum_platform]); - const vacuumPlatform = config.vacuum_platform ?? "default"; (config.icons ?? []).flatMap(i => validateIconConfig(i)).forEach(e => errors.push(e)); (config.tiles ?? []).flatMap(i => validateTileConfig(i)).forEach(e => errors.push(e)); (config.map_modes ?? []) diff --git a/src/model/generators/platform-generator.ts b/src/model/generators/platform-generator.ts index 7763d2a7..d1321c0c 100644 --- a/src/model/generators/platform-generator.ts +++ b/src/model/generators/platform-generator.ts @@ -15,6 +15,7 @@ import * as simpleWyzeTemplate from "./platform_templates/romedtino_simple-wyze- import * as setupDecimalTemplate from "./platform_templates/setup_decimal.json"; import * as setupIntegerTemplate from "./platform_templates/setup_integer.json"; import { + CalibrationPoint, IconTemplate, MapModeConfig, PlatformTemplate, @@ -86,6 +87,10 @@ export class PlatformGenerator { return Array.from(PlatformGenerator.TEMPLATES.keys()); } + public static getPlatformName(platform: string | undefined): string { + return platform ?? PlatformGenerator.XIAOMI_MIIO_PLATFORM; + } + public static getPlatformsDocumentationUrl(platform: string): string { const file = PlatformGenerator.TEMPLATE_DOCUMENTATIONS_URLS.get(platform) ?? @@ -132,6 +137,10 @@ export class PlatformGenerator { return undefined; } + public static getCalibration(platform: string | undefined): CalibrationPoint[] | undefined { + return this.getPlatformTemplate(PlatformGenerator.getPlatformName(platform)).calibration_points; + } + private static getPlatformTemplate(platform: string): PlatformTemplate { return ( this.TEMPLATES.get(platform) ?? diff --git a/src/model/generators/platform_templates/DeebotUniverse_Deebot-4-Home-Assistant.json b/src/model/generators/platform_templates/DeebotUniverse_Deebot-4-Home-Assistant.json index f5fd600b..c5583d0d 100644 --- a/src/model/generators/platform_templates/DeebotUniverse_Deebot-4-Home-Assistant.json +++ b/src/model/generators/platform_templates/DeebotUniverse_Deebot-4-Home-Assistant.json @@ -27,7 +27,7 @@ "icon": "mdi:select-drag", "selection_type": "MANUAL_RECTANGLE", "coordinates_rounding": true, - "coordinates_to_meters_divider": 1000, + "coordinates_to_meters_divider": -1, "repeats_type": "NONE", "max_selections": 1, "service_call_schema": { @@ -47,7 +47,7 @@ "selection_type": "PREDEFINED_RECTANGLE", "max_selections": 1, "coordinates_rounding": true, - "coordinates_to_meters_divider": 1000, + "coordinates_to_meters_divider": -1, "repeats_type": "NONE", "service_call_schema": { "service": "xiaomi_miio.vacuum_clean_zone", @@ -141,5 +141,37 @@ } } } + ], + "calibration_points": [ + { + "vacuum": { + "x": 0, + "y": 0 + }, + "map": { + "x": 400, + "y": 400 + } + }, + { + "vacuum": { + "x": 6400, + "y": 0 + }, + "map": { + "x": 528, + "y": 400 + } + }, + { + "vacuum": { + "x": 0, + "y": 6400 + }, + "map": { + "x": 400, + "y": 528 + } + } ] } diff --git a/src/model/generators/platform_templates/neato.json b/src/model/generators/platform_templates/neato.json index ea0d538e..fbaf9e50 100644 --- a/src/model/generators/platform_templates/neato.json +++ b/src/model/generators/platform_templates/neato.json @@ -27,5 +27,37 @@ "unit": "unit.meter_squared_shortcut" } ] - } + }, + "calibration_points": [ + { + "vacuum": { + "x": 0, + "y": 0 + }, + "map": { + "x": 0, + "y": 0 + } + }, + { + "vacuum": { + "x": 1, + "y": 0 + }, + "map": { + "x": 1, + "y": 0 + } + }, + { + "vacuum": { + "x": 0, + "y": 1 + }, + "map": { + "x": 0, + "y": 1 + } + } + ] } diff --git a/src/model/generators/platform_templates/romedtino_simple-wyze-vac.json b/src/model/generators/platform_templates/romedtino_simple-wyze-vac.json index 69e785c9..13082a42 100644 --- a/src/model/generators/platform_templates/romedtino_simple-wyze-vac.json +++ b/src/model/generators/platform_templates/romedtino_simple-wyze-vac.json @@ -45,5 +45,37 @@ "unit": "unit.hour_shortcut" } ] - } + }, + "calibration_points": [ + { + "vacuum": { + "x": 0, + "y": 0 + }, + "map": { + "x": 0, + "y": 0 + } + }, + { + "vacuum": { + "x": 1, + "y": 0 + }, + "map": { + "x": 1, + "y": 0 + } + }, + { + "vacuum": { + "x": 0, + "y": 1 + }, + "map": { + "x": 0, + "y": 1 + } + } + ] } diff --git a/src/model/generators/platform_templates/roomba.json b/src/model/generators/platform_templates/roomba.json index 265b361e..c4a30a2b 100644 --- a/src/model/generators/platform_templates/roomba.json +++ b/src/model/generators/platform_templates/roomba.json @@ -46,5 +46,37 @@ ] } ] - } + }, + "calibration_points": [ + { + "vacuum": { + "x": 0, + "y": 0 + }, + "map": { + "x": 0, + "y": 0 + } + }, + { + "vacuum": { + "x": 1, + "y": 0 + }, + "map": { + "x": 1, + "y": 0 + } + }, + { + "vacuum": { + "x": 0, + "y": 1 + }, + "map": { + "x": 0, + "y": 1 + } + } + ] } diff --git a/src/model/map_objects/manual-rectangle.ts b/src/model/map_objects/manual-rectangle.ts index 99e1a2d9..8fd7b4fd 100644 --- a/src/model/map_objects/manual-rectangle.ts +++ b/src/model/map_objects/manual-rectangle.ts @@ -120,6 +120,8 @@ export class ManualRectangle extends MapObject { const width = Math.abs(x2 - x1); const height = Math.abs(y2 - y1); const divider = this._context.coordinatesToMetersDivider(); + if (divider === -1) + return ""; const rounder = (v: number): string => (v / divider).toFixed(1); return `${rounder(width)}${this.localize("unit.meter_shortcut")} x ${rounder(height)}${this.localize( "unit.meter_shortcut", diff --git a/src/types/types.ts b/src/types/types.ts index 14681d02..69694ec9 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -60,7 +60,7 @@ export interface CardPresetConfig extends ConditionalObjectConfig { readonly map_source: MapSourceConfig; readonly map_locked?: boolean; readonly two_finger_pan?: boolean; - readonly calibration_source: CalibrationSourceConfig; + readonly calibration_source?: CalibrationSourceConfig; readonly icons?: IconActionConfig[]; readonly append_icons?: boolean; readonly tiles?: TileConfig[]; @@ -112,6 +112,7 @@ export interface PlatformTemplate { readonly from_sensors?: TileFromSensorTemplate[]; }; readonly icons?: IconTemplate[]; + readonly calibration_points?: CalibrationPoint[]; } export interface TileTemplate extends TileConfig { diff --git a/src/utils.ts b/src/utils.ts index 9eb2dd8c..7dfdfa74 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -24,6 +24,7 @@ import { Modifier } from "./model/map_mode/modifier"; import { HomeAssistantFixed } from "./types/fixes"; import { ServiceCallSchema } from "./model/map_mode/service-call-schema"; import { TemplatableItemValue } from "./model/map_mode/templatable-value"; +import { PlatformGenerator } from "./model/generators/platform-generator"; export function stopEvent(event: MouseEvent | TouchEvent): void { event.preventDefault(); @@ -66,7 +67,7 @@ export function getWatchedEntitiesForPreset(config: CardPresetConfig, language: if (config.map_source.camera) { watchedEntities.add(config.map_source.camera); } - if (config.calibration_source.entity) { + if (config.calibration_source?.entity) { watchedEntities.add(config.calibration_source.entity); } (config.conditions ?? []) @@ -98,7 +99,7 @@ export function getWatchedEntitiesForPreset(config: CardPresetConfig, language: if (e) watchedEntities.add(e); }); (config.map_modes ?? []) - .map(m => new MapMode(config.vacuum_platform ?? "default", m, language)) + .map(m => new MapMode(PlatformGenerator.getPlatformName(config.vacuum_platform), m, language)) .forEach(m => getWatchedEntitiesForMapMode(m).forEach(e => watchedEntities.add(e))); return watchedEntities; } diff --git a/src/xiaomi-vacuum-map-card.ts b/src/xiaomi-vacuum-map-card.ts index df6443fd..5a1872bf 100644 --- a/src/xiaomi-vacuum-map-card.ts +++ b/src/xiaomi-vacuum-map-card.ts @@ -185,7 +185,7 @@ export class XiaomiVacuumMapCard extends LitElement { camera: true, }, entity: vacuums[0], - vacuum_platform: "default", + vacuum_platform: PlatformGenerator.XIAOMI_MIIO_PLATFORM, }; } @@ -414,7 +414,7 @@ export class XiaomiVacuumMapCard extends LitElement { } private _getCalibration(config: CardPresetConfig): CalibrationPoint[] | undefined { - if (config.calibration_source.identity) { + if (config.calibration_source?.identity) { return [ { map: { x: 0, y: 0 }, vacuum: { x: 0, y: 0 } }, { map: { x: 1, y: 0 }, vacuum: { x: 1, y: 0 } }, @@ -422,7 +422,7 @@ export class XiaomiVacuumMapCard extends LitElement { ]; } if ( - config.calibration_source.calibration_points && + config.calibration_source?.calibration_points && [3, 4].includes(config.calibration_source.calibration_points.length) ) { return config.calibration_source.calibration_points; @@ -430,15 +430,19 @@ export class XiaomiVacuumMapCard extends LitElement { if (!this.hass) { return undefined; } - if (config.calibration_source.entity && !config.calibration_source?.attribute) { + if (config.calibration_source?.entity && !config.calibration_source?.attribute) { return JSON.parse(this.hass.states[config.calibration_source.entity]?.state); } - if (config.calibration_source.entity && config.calibration_source?.attribute) { + if (config.calibration_source?.entity && config.calibration_source?.attribute) { return this.hass.states[config.calibration_source.entity]?.attributes[config.calibration_source.attribute]; } - if (config.calibration_source.camera) { + if (config.calibration_source?.camera) { return this.hass.states[config.map_source?.camera ?? ""]?.attributes["calibration_points"]; } + const platformCalibration = PlatformGenerator.getCalibration(config.vacuum_platform); + if (platformCalibration) { + return platformCalibration; + } return undefined; } @@ -516,7 +520,7 @@ export class XiaomiVacuumMapCard extends LitElement { this.mapX = 0; this.mapY = 0; if (this.hass) this._updateCalibration(config); - const vacuumPlatform = config.vacuum_platform ?? "default"; + const vacuumPlatform = PlatformGenerator.getPlatformName(config.vacuum_platform); this.modes = ( (config.map_modes?.length ?? -1) === -1 || vacuumPlatform.startsWith("Setup") ? PlatformGenerator.generateDefaultModes(vacuumPlatform) @@ -525,15 +529,6 @@ export class XiaomiVacuumMapCard extends LitElement { this.presetIndex = index; this.currentPreset = config; - // const icons = - // (config.icons?.length ?? -1) === -1 - // ? IconListGenerator.generate(this.hass, config.entity, this.config.language) - // : config.append_icons - // ? [ - // ...IconListGenerator.generate(this.hass, config.entity, this.config.language), - // ...(config.icons ?? []), - // ] - // : config.icons; const iconsPromise = GeneratorWrapper.generate(this.hass, config.icons, config.entity, vacuumPlatform, this.internalVariables, this.config.language, config.append_icons ?? false,