Changed calibration process to rely on points defined by users.

Breaking changes: recalibration required (guide provided)
This commit is contained in:
Piotr Machowski
2019-09-16 23:50:56 +02:00
parent 59f187ce33
commit 6a3e3d9bf3
4 changed files with 273 additions and 123 deletions

View File

@@ -20,8 +20,7 @@ This card enables you to specify target or start zoned cleanup using map, just l
| --- | --- | --- | --- | --- |
| `entity` | `string` | `True` | - | ID of Xiaomi vacuum entity |
| `map_image` | `string` | `True` | - | Path to image of map |
| `base_position` | `string` | `True` | - | Coordinates of pixel corresponding to base position (25500, 25500) on map image |
| `reference_point` | `string` | `True` | - | Coordinates of pixel corresponding to reference point (26500, 26500) on map image |
| `calibration_points` | `List` | `True` | - | Pairs of coordinates: in vacuum system and on map image. See: [Calibration](#calibration) |
| `zones` | `List` | `False` | Empty | List of predefined zones |
| `modes` | `List` | `False` | `[go_to_target, zoned_cleanup, predefined_zones]` | List of displayed modes. Possible values: `go_to_target`, `zoned_cleanup`, `predefined_zones` |
| `default_mode` | `string` | `False` | - | Default selected mode. Possible values: `go_to_target`, `zoned_cleanup`, `predefined_zones` |
@@ -36,12 +35,25 @@ views:
- type: custom:xiaomi-vacuum-map-card
entity: vacuum.xiaomi_vacuum
map_image: '/local/custom_lovelace/xiaomi_vacuum_map_card/map.png'
base_position:
x: 1889
y: 1600
reference_point:
x: 1625
y: 1336
calibration_points:
- vacuum:
x: 25500
y: 25500
map:
x: 466
y: 1889
- vacuum:
x: 26500
y: 26500
map:
x: 730
y: 1625
- vacuum:
x: 25500
y: 26500
map:
x: 466
y: 1625
zones:
- [[25500, 25500, 26500, 26500]]
- [[24215, 28125, 29465, 32175]]
@@ -50,11 +62,12 @@ views:
```
## Installation
1. Download [*xiaomi-vacuum-map-card.js*](https://github.com/PiotrMachowski/Home-Assistant-Lovelace-Xiaomi-Vacuum-Map-card/raw/master/dist/xiaomi-vacuum-map-card.js), [*texts.js*](https://github.com/PiotrMachowski/Home-Assistant-Lovelace-Xiaomi-Vacuum-Map-card/raw/master/dist/texts.js) and [style.js](Home-Assistant-Lovelace-Xiaomi-Vacuum-Map-card/raw/master/dist/style.js) to `/www/custom_lovelace/xiaomi_vacuum_map_card` directory:
1. Download [*xiaomi-vacuum-map-card.js*](https://github.com/PiotrMachowski/Home-Assistant-Lovelace-Xiaomi-Vacuum-Map-card/raw/master/dist/xiaomi-vacuum-map-card.js), [*coordinates-converter.js*](https://github.com/PiotrMachowski/Home-Assistant-Lovelace-Xiaomi-Vacuum-Map-card/raw/master/dist/coordinates-converter.js), [*texts.js*](https://github.com/PiotrMachowski/Home-Assistant-Lovelace-Xiaomi-Vacuum-Map-card/raw/master/dist/texts.js) and [style.js](Home-Assistant-Lovelace-Xiaomi-Vacuum-Map-card/raw/master/dist/style.js) to `/www/custom_lovelace/xiaomi_vacuum_map_card` directory:
```bash
mkdir -p www/custom_lovelace/xiaomi_vacuum_map_card
cd www/custom_lovelace/xiaomi_vacuum_map_card/
wget https://github.com/PiotrMachowski/Home-Assistant-Lovelace-Xiaomi-Vacuum-Map-card/raw/master/dist/xiaomi-vacuum-map-card.js
wget https://github.com/PiotrMachowski/Home-Assistant-Lovelace-Xiaomi-Vacuum-Map-card/raw/master/dist/coordinates-converter.js
wget https://github.com/PiotrMachowski/Home-Assistant-Lovelace-Xiaomi-Vacuum-Map-card/raw/master/dist/texts.js
wget https://github.com/PiotrMachowski/Home-Assistant-Lovelace-Xiaomi-Vacuum-Map-card/raw/master/dist/style.js
```
@@ -65,7 +78,18 @@ views:
type: module
```
### Defining service
## Calibration
To calibrate this card you have to provide exactly 3 pairs of coordinates.
Each pair must consist of:
* coordinates of point in vacuum system (extracted by FloleVac or just by sending vacuum to a desired point)
* coordinates of matching point on a map image
For the best outcome calibration points should form a right triangle.
If you have used this card before a migration guide will appear instead of actual card.
## Defining service
You can use `service` parameter for example to run a script instead of starting a vacuum directly. Provided service will be run with following parameters:
* `entity_id` - id of a vacuum
@@ -80,24 +104,15 @@ You can use `service` parameter for example to run a script instead of starting
Example HA script that can be used with this card is available [*here*](https://github.com/PiotrMachowski/Home-Assistant-Lovelace-Xiaomi-Vacuum-Map-card/raw/master/examples/vacuum_send_command.yaml).
### Hints
* To find out values for `base_position` and `reference_point` use service `vacuum.send_command` with data:
* `base_postion`:
```json
{
"entity_id": "vacuum.xiaomi_vacuum",
"command": "app_goto_target",
"params": [25500, 25500]
}
```
* `reference_point`:
```json
{
"entity_id": "vacuum.xiaomi_vacuum",
"command": "app_goto_target",
"params": [26500, 26500]
}
```
## Hints
* To find out values for `calibration_points` you can use service `vacuum.send_command` with data:
```json
{
"entity_id": "vacuum.xiaomi_vacuum",
"command": "app_goto_target",
"params": [25500, 25500]
}
```
Alternatively you can use `vacuum.xiaomi_clean_zone`:
```json
{
@@ -123,7 +138,4 @@ Example HA script that can be used with this card is available [*here*](https://
* **How to create map?**
You can use any image you want, the easisest way is to use screenshot from Mi Home/FloleVac.
## Community
Thread on [community.home-assistant.io](https://community.home-assistant.io/): [Xiaomi Vacuum Interactive Map Card](https://community.home-assistant.io/t/xiaomi-vacuum-interactive-map-card/)
You can use any image you want, the easiest way is to use screenshot from Mi Home/FloleVac.

70
dist/coordinates-converter.js vendored Normal file
View File

@@ -0,0 +1,70 @@
export default class CoordinatesConverter {
constructor(p1, p2, p3) {
this.ABMatrix = this.conversionMatrixAB(p1, p2, p3);
this.BAMatrix = this.conversionMatrixBA(p1, p2, p3);
}
conversionMatrixAB(p1, p2, p3) {
const p1p2ax = p1.a.x - p2.a.x;
const p1p3ax = p1.a.x - p3.a.x;
const p1p2ay = p1.a.y - p2.a.y;
const p1p3ay = p1.a.y - p3.a.y;
const p1p2bx = p1.b.x - p2.b.x;
const p1p3by = p1.b.y - p3.b.y;
const p1p3bx = p1.b.x - p3.b.x;
const p1p2by = p1.b.y - p2.b.y;
const divAD = p1p2ax * p1p3ay - p1p3ax * p1p2ay;
const dibBE = p1p2ay * p1p3ax - p1p3ay * p1p2ax;
const A = (p1p2bx * p1p3ay - p1p3bx * p1p2ay) / divAD;
const B = (p1p2bx * p1p3ax - p1p3bx * p1p2ax) / dibBE;
const C = p1.b.x - A * p1.a.x - B * p1.a.y;
const D = (p1p2by * p1p3ay - p1p3by * p1p2ay) / divAD;
const E = (p1p2by * p1p3ax - p1p3by * p1p2ax) / dibBE;
const F = p1.b.y - D * p1.a.x - E * p1.a.y;
return {A, B, C, D, E, F};
}
conversionMatrixBA(p1, p2, p3) {
const p1p2ax = p1.a.x - p2.a.x;
const p1p3ax = p1.a.x - p3.a.x;
const p1p2ay = p1.a.y - p2.a.y;
const p1p3ay = p1.a.y - p3.a.y;
const p1p2bx = p1.b.x - p2.b.x;
const p1p3by = p1.b.y - p3.b.y;
const p1p3bx = p1.b.x - p3.b.x;
const p1p2by = p1.b.y - p2.b.y;
const divAD = p1p2bx * p1p3by - p1p3bx * p1p2by;
const dibBE = p1p2by * p1p3bx - p1p3by * p1p2bx;
const A = (p1p2ax * p1p3by - p1p3ax * p1p2by) / divAD;
const B = (p1p2ax * p1p3bx - p1p3ax * p1p2bx) / dibBE;
const C = p1.a.x - A * p1.b.x - B * p1.b.y;
const D = (p1p2ay * p1p3by - p1p3ay * p1p2by) / divAD;
const E = (p1p2ay * p1p3bx - p1p3ay * p1p2bx) / dibBE;
const F = p1.a.y - D * p1.b.x - E * p1.b.y;
return {A, B, C, D, E, F};
}
convertAB(x, y) {
return this.convert(x, y, this.ABMatrix);
}
convertBA(x, y) {
return this.convert(x, y, this.BAMatrix);
}
convert(oldX, oldY, matrix) {
const {A, B, C, D, E, F} = matrix;
const x = A * oldX + B * oldY + C;
const y = D * oldX + E * oldY + F;
return {x, y};
}
}

View File

@@ -1,3 +1,4 @@
import CoordinatesConverter from './coordinates-converter.js';
import style from './style.js';
import {
textMode,
@@ -26,8 +27,7 @@ class XiaomiVacuumMapCard extends LitElement {
this.mode = 0;
this.vacuumZonedCleanupRepeats = 1;
this.currPoint = {x: null, y: null};
this.unit = {x: null, y: null};
this.shouldSwapAxis = false;
this.outdatedConfig = false;
}
static get properties() {
@@ -62,24 +62,42 @@ class XiaomiVacuumMapCard extends LitElement {
if (!config.map_image) {
throw new Error("Missing configuration: map_image");
}
if (!config.base_position) {
throw new Error("Missing configuration: base_position");
if (config.base_position || config.reference_point) {
this.outdatedConfig = true;
this.config = config;
return;
}
if (!config.base_position.x) {
throw new Error("Missing configuration: base_position.x");
if (!config.calibration_points || !Array.isArray(config.calibration_points)) {
throw new Error("Missing configuration: calibration_points");
}
if (!config.base_position.y) {
throw new Error("Missing configuration: base_position.y");
if (config.calibration_points.length !== 3) {
throw new Error("Exactly 3 calibration_points required");
}
if (!config.reference_point) {
throw new Error("Missing configuration: reference_point");
}
if (!config.reference_point.x) {
throw new Error("Missing configuration: reference_point.x");
}
if (!config.reference_point.y) {
throw new Error("Missing configuration: reference_point.y");
for (const calibration_point of config.calibration_points) {
if (calibration_point.map === null) {
throw new Error("Missing configuration: calibration_points.map");
}
if (calibration_point.map.x === null) {
throw new Error("Missing configuration: calibration_points.map.x");
}
if (calibration_point.map.y === null) {
throw new Error("Missing configuration: calibration_points.map.y");
}
if (calibration_point.vacuum === null) {
throw new Error("Missing configuration: calibration_points.vacuum");
}
if (calibration_point.vacuum.x === null) {
throw new Error("Missing configuration: calibration_points.vacuum.x");
}
if (calibration_point.vacuum.y === null) {
throw new Error("Missing configuration: calibration_points.vacuum.y");
}
}
const p1 = this.getCalibrationPoint(config, 0);
const p2 = this.getCalibrationPoint(config, 1);
const p3 = this.getCalibrationPoint(config, 2);
this.coordinatesConverter = new CoordinatesConverter(p1, p2, p3);
if (config.modes) {
if (!Array.isArray(config.modes) || config.modes.length < 1 || config.modes.length > 3) {
throw new Error("Invalid configuration: modes");
@@ -113,33 +131,77 @@ class XiaomiVacuumMapCard extends LitElement {
this.service_method = "send_command";
}
this.config = config;
}
getConfigurationMigration(config) {
const diffX = config.reference_point.x - config.base_position.x;
const diffY = config.reference_point.y - config.base_position.y;
this.shouldSwapAxis = diffX * diffY > 0;
if (this.shouldSwapAxis) {
this.unit = {
x: diffY,
y: diffX
};
const shouldSwapAxis = diffX * diffY > 0;
let unit = shouldSwapAxis ? diffX : diffY;
if (shouldSwapAxis) {
const temp = config.base_position.x;
config.base_position.x = config.base_position.y;
config.base_position.y = temp;
} else {
this.unit = {
x: diffX,
y: diffY
};
}
const canvasX = config.base_position.x;
const canvasY = unit + config.base_position.y;
let x = Math.round(canvasX);
let y = Math.round(canvasY);
if (shouldSwapAxis) {
x = Math.round(canvasY);
y = Math.round(canvasX);
}
return html`
<ha-card id="xiaomiCard" style="padding: 16px">
<div class="card-header" style="padding: 8px 0 16px 0;"><div class="name">Xiaomi Vacuum Map card</div></div>
<h3>Your configuration is outdated</h3>
<p>Migrate it using following calibration settings:</p>
<pre><textarea style="width: 100%; height: 22em">calibration_points:
- vacuum:
x: 25500
y: 25500
map:
x: ${config.base_position.x}
y: ${config.base_position.y}
- vacuum:
x: 26500
y: 26500
map:
x: ${config.reference_point.x}
y: ${config.reference_point.y}
- vacuum:
x: 25500
y: 26500
map:
x: ${x}
y: ${y}</textarea></pre>
</ha-card>`
}
getCalibrationPoint(config, index) {
return {
a: {
x: config.calibration_points[index].map.x,
y: config.calibration_points[index].map.y
},
b: {
x: config.calibration_points[index].vacuum.x,
y: config.calibration_points[index].vacuum.y
}
};
}
render() {
if (this.outdatedConfig) {
return this.getConfigurationMigration(this.config);
}
const modesDropdown = this.modes.map(m => html`<paper-item>${m}</paper-item>`);
const rendered = html`
${style}
<ha-card id="xiaomiCard">
<div id="mapWrapper">
<div id="map">
<img id="mapBackground" @load="${() => this.mapOnLoad()}" src="${this.config.map_image}">
<img id="mapBackground" @load="${() => this.calculateScale()}" src="${this.config.map_image}">
<canvas id="mapDrawing" style="${this.getCanvasStyle()}"
@mousemove="${e => this.onMouseMove(e)}"
@mousedown="${e => this.onMouseDown(e)}"
@@ -164,12 +226,12 @@ class XiaomiVacuumMapCard extends LitElement {
</ha-card>
`;
if (this.getMapImage()) {
this.mapOnLoad();
this.calculateScale();
}
return rendered;
}
mapOnLoad() {
calculateScale() {
const img = this.getMapImage();
const canvas = this.getCanvas();
this.imageScale = img.width / img.naturalWidth;
@@ -312,10 +374,10 @@ class XiaomiVacuumMapCard extends LitElement {
context.clearRect(0, 0, canvas.width, canvas.height);
context.translate(0.5, 0.5);
if (this.config.debug) {
const p1 = this.convertRealToCanvasCoordinates(25500, 25500);
this.drawCircle(context, p1.x, p1.y, 4, 'red', 1);
const p2 = this.convertRealToCanvasCoordinates(26500, 26500);
this.drawCircle(context, p2.x, p2.y, 4, 'red', 1);
for (const calibration_point of this.config.calibration_points) {
const {x, y} = this.convertVacuumToMapCoordinates(calibration_point.vacuum.x, calibration_point.vacuum.y);
this.drawCircle(context, x, y, 4, 'red', 1);
}
}
if (this.mode === 1 && this.currPoint.x != null) {
this.drawCircle(context, this.currPoint.x, this.currPoint.y, 4, 'yellow', 1);
@@ -350,7 +412,7 @@ class XiaomiVacuumMapCard extends LitElement {
for (let i = 0; i < this.config.zones.length; i++) {
const zone = this.config.zones[i];
for (const rect of zone) {
const {x, y, w, h} = this.convertRealToCanvasZone(rect[0], rect[1], rect[2], rect[3]);
const {x, y, w, h} = this.convertVacuumToMapZone(rect[0], rect[1], rect[2], rect[3]);
context.beginPath();
context.setLineDash([]);
if (!this.selectedZones.includes(i)) {
@@ -434,7 +496,7 @@ class XiaomiVacuumMapCard extends LitElement {
for (let i = 0; i < this.config.zones.length && selected === -1; i++) {
const zone = this.config.zones[i];
for (const rect of zone) {
const {x, y, w, h} = this.convertRealToCanvasZone(rect[0], rect[1], rect[2], rect[3]);
const {x, y, w, h} = this.convertVacuumToMapZone(rect[0], rect[1], rect[2], rect[3]);
if (mx >= x && my >= y && mx <= x + w && my <= y + h) {
selected = i;
break;
@@ -450,7 +512,7 @@ class XiaomiVacuumMapCard extends LitElement {
}
vacuumGoToPoint(debug) {
const mapPos = this.convertCanvasToRealCoordinates(this.currPoint.x, this.currPoint.y);
const mapPos = this.convertMapToVacuumCoordinates(this.currPoint.x, this.currPoint.y);
if (debug && this.config.debug) {
alert(JSON.stringify([mapPos.x, mapPos.y]));
} else {
@@ -458,14 +520,14 @@ class XiaomiVacuumMapCard extends LitElement {
entity_id: this.config.entity,
command: "app_goto_target",
params: [mapPos.x, mapPos.y]
}).then(() => this.launch_toast());
}).then(() => this.showToast());
}
}
vacuumStartZonedCleanup(debug) {
const zone = [];
for (const rect of this.rectangles) {
zone.push(this.convertCanvasToRealRect(rect, this.vacuumZonedCleanupRepeats));
zone.push(this.convertMapToVacuumRect(rect, this.vacuumZonedCleanupRepeats));
}
if (debug && this.config.debug) {
alert(JSON.stringify(zone));
@@ -474,7 +536,7 @@ class XiaomiVacuumMapCard extends LitElement {
entity_id: this.config.entity,
command: "app_zoned_clean",
params: zone
}).then(() => this.launch_toast());
}).then(() => this.showToast());
}
}
@@ -494,7 +556,7 @@ class XiaomiVacuumMapCard extends LitElement {
entity_id: this.config.entity,
command: "app_zoned_clean",
params: zone
}).then(() => this.launch_toast());
}).then(() => this.showToast());
}
}
@@ -502,9 +564,9 @@ class XiaomiVacuumMapCard extends LitElement {
return 3;
}
convertCanvasToRealRect(rect, repeats) {
const xy1 = this.convertCanvasToRealCoordinates(rect.x, rect.y);
const xy2 = this.convertCanvasToRealCoordinates(rect.x + rect.w, rect.y + rect.h);
convertMapToVacuumRect(rect, repeats) {
const xy1 = this.convertMapToVacuumCoordinates(rect.x, rect.y);
const xy2 = this.convertMapToVacuumCoordinates(rect.x + rect.w, rect.y + rect.h);
const x1 = Math.min(xy1.x, xy2.x);
const y1 = Math.min(xy1.y, xy2.y);
const x2 = Math.max(xy1.x, xy2.x);
@@ -512,16 +574,14 @@ class XiaomiVacuumMapCard extends LitElement {
return [x1, y1, x2, y2, repeats];
}
convertCanvasToRealCoordinates(canvasX, canvasY) {
const {x, y} = this.swapAxisIfNeeded(canvasX, canvasY);
const mapX = 25500 + (x / this.imageScale - this.config.base_position.x) / this.unit.x * 1000;
const mapY = 25500 + (y / this.imageScale - this.config.base_position.y) / this.unit.y * 1000;
return {x: Math.round(mapX), y: Math.round(mapY)};
convertMapToVacuumCoordinates(mapX, mapY) {
const {x, y} = this.coordinatesConverter.convertAB(mapX / this.imageScale, mapY / this.imageScale);
return {x: Math.round(x), y: Math.round(y)};
}
convertRealToCanvasZone(mapX1, mapY1, mapX2, mapY2) {
const {x: x1, y: y1} = this.convertRealToCanvasCoordinates(mapX1, mapY1);
const {x: x2, y: y2} = this.convertRealToCanvasCoordinates(mapX2, mapY2);
convertVacuumToMapZone(vacuumX1, vacuumY1, vacuumX2, vacuumY2) {
const {x: x1, y: y1} = this.convertVacuumToMapCoordinates(vacuumX1, vacuumY1);
const {x: x2, y: y2} = this.convertVacuumToMapCoordinates(vacuumX2, vacuumY2);
let x = Math.min(x1, x2);
let y = Math.min(y1, y2);
let w = Math.abs(x2 - x1);
@@ -529,17 +589,11 @@ class XiaomiVacuumMapCard extends LitElement {
return {x, y, w, h};
}
convertRealToCanvasCoordinates(mapX, mapY) {
const canvasX = ((mapX - 25500) / 1000 * this.unit.x + this.config.base_position.x) * this.imageScale;
const canvasY = ((mapY - 25500) / 1000 * this.unit.y + this.config.base_position.y) * this.imageScale;
return this.swapAxisIfNeeded(Math.round(canvasX), Math.round(canvasY));
}
swapAxisIfNeeded(x, y) {
if (this.shouldSwapAxis) {
return {x: y, y: x};
}
return {x: x, y: y};
convertVacuumToMapCoordinates(vacuumX, vacuumY) {
const {x: vX, y: vY} = this.coordinatesConverter.convertBA(vacuumX, vacuumY);
const x = Math.round(vX * this.imageScale);
const y = Math.round(vY * this.imageScale);
return {x, y};
}
getMapImage() {
@@ -574,7 +628,7 @@ class XiaomiVacuumMapCard extends LitElement {
};
}
launch_toast() {
showToast() {
const x = this.shadowRoot.getElementById("toast");
x.className = "show";
setTimeout(function () {

68
info.md
View File

@@ -13,8 +13,7 @@
| --- | --- | --- | --- | --- |
| `entity` | `string` | `True` | - | ID of Xiaomi vacuum entity |
| `map_image` | `string` | `True` | - | Path to image of map |
| `base_position` | `string` | `True` | - | Coordinates of pixel corresponding to base position (25500, 25500) on map image |
| `reference_point` | `string` | `True` | - | Coordinates of pixel corresponding to reference point (26500, 26500) on map image |
| `calibration_points` | `List` | `True` | - | Pairs of coordinates: in vacuum system and on map image. See: [Calibration](#calibration) |
| `zones` | `List` | `False` | Empty | List of predefined zones |
| `modes` | `List` | `False` | `[go_to_target, zoned_cleanup, predefined_zones]` | List of displayed modes. Possible values: `go_to_target`, `zoned_cleanup`, `predefined_zones` |
| `default_mode` | `string` | `False` | - | Default selected mode. Possible values: `go_to_target`, `zoned_cleanup`, `predefined_zones` |
@@ -29,20 +28,44 @@ views:
- type: custom:xiaomi-vacuum-map-card
entity: vacuum.xiaomi_vacuum
map_image: '/local/custom_lovelace/xiaomi_vacuum_map_card/map.png'
base_position:
x: 1889
y: 1600
reference_point:
x: 1625
y: 1336
calibration_points:
- vacuum:
x: 25500
y: 25500
map:
x: 466
y: 1889
- vacuum:
x: 26500
y: 26500
map:
x: 730
y: 1625
- vacuum:
x: 25500
y: 26500
map:
x: 466
y: 1625
zones:
- [[25500, 25500, 26500, 26500]]
- [[24215, 28125, 29465, 32175]]
- [[24245, 25190, 27495, 27940], [27492, 26789, 28942, 27889]]
- [[28972, 26715, 31072, 27915], [29457, 27903, 31107, 29203], [30198, 29215, 31498, 31215], [29461, 31228, 31511, 32478]]
```
## Calibration
### Defining service
To calibrate this card you have to provide exactly 3 pairs of coordinates.
Each pair must consist of:
* coordinates of point in vacuum system (extracted by FloleVac or just by sending vacuum to a desired point)
* coordinates of matching point on a map image
For the best outcome calibration points should form a right triangle.
If you have used this card before a migration guide will appear instead of actual card.
## Defining service
You can use `service` parameter for example to run a script instead of starting a vacuum directly. Provided service will be run with following parameters:
* `entity_id` - id of a vacuum
@@ -59,23 +82,14 @@ To overcome this issue you can use a [*python script*](https://github.com/PiotrM
Example HA script that can be used with this card is available [*here*](https://github.com/PiotrMachowski/Home-Assistant-Lovelace-Xiaomi-Vacuum-Map-card/raw/master/examples/vacuum_send_command.yaml).
## Hints
* To find out values for `base_position` and `reference_point` use service `vacuum.send_command` with data:
* `base_postion`:
```json
{
"entity_id": "vacuum.xiaomi_vacuum",
"command": "app_goto_target",
"params": [25500, 25500]
}
```
* `reference_point`:
```json
{
"entity_id": "vacuum.xiaomi_vacuum",
"command": "app_goto_target",
"params": [26500, 26500]
}
```
* To find out values for `calibration_points` you can use service `vacuum.send_command` with data:
```json
{
"entity_id": "vacuum.xiaomi_vacuum",
"command": "app_goto_target",
"params": [25500, 25500]
}
```
Alternatively you can use `vacuum.xiaomi_clean_zone`:
```json
{
@@ -87,7 +101,7 @@ Example HA script that can be used with this card is available [*here*](https://
* You can find out coordinates for zones using two methods:
* Enabling `debug` in settings, drawing zone in `Zoned cleanup` mode and holding `Start` button. Note: this method also works for other modes.
* Android App [*FloleVac*](https://play.google.com/store/apps/details?id=de.flole.xiaomi)
* For Polish version download [*textsPL.js*](https://github.com/PiotrMachowski/Home-Assistant-Lovelace-Xiaomi-Vacuum-Map-card/raw/master/dist/textsPL.js) and change filename to `texts.js`
## FAQ