mirror of
https://github.com/PiotrMachowski/lovelace-xiaomi-vacuum-map-card.git
synced 2021-06-25 09:14:01 +03:00
Changed calibration process to rely on points defined by users.
Breaking changes: recalibration required (guide provided)
This commit is contained in:
76
README.md
76
README.md
@@ -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
70
dist/coordinates-converter.js
vendored
Normal 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};
|
||||
}
|
||||
}
|
||||
182
dist/xiaomi-vacuum-map-card.js
vendored
182
dist/xiaomi-vacuum-map-card.js
vendored
@@ -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
68
info.md
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user