├── img ├── vacuum.png └── vacuum_ecovacs.png ├── examples ├── default.png ├── no-title.png ├── no-buttons.png └── with-image.png ├── hacs.json ├── tracker.json ├── LICENSE ├── CHANGELOG.md ├── README.md └── xiaomi-vacuum-card.js /img/vacuum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benct/lovelace-xiaomi-vacuum-card/HEAD/img/vacuum.png -------------------------------------------------------------------------------- /examples/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benct/lovelace-xiaomi-vacuum-card/HEAD/examples/default.png -------------------------------------------------------------------------------- /examples/no-title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benct/lovelace-xiaomi-vacuum-card/HEAD/examples/no-title.png -------------------------------------------------------------------------------- /img/vacuum_ecovacs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benct/lovelace-xiaomi-vacuum-card/HEAD/img/vacuum_ecovacs.png -------------------------------------------------------------------------------- /examples/no-buttons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benct/lovelace-xiaomi-vacuum-card/HEAD/examples/no-buttons.png -------------------------------------------------------------------------------- /examples/with-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benct/lovelace-xiaomi-vacuum-card/HEAD/examples/with-image.png -------------------------------------------------------------------------------- /hacs.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Simple Vacuum Card", 3 | "filename": "xiaomi-vacuum-card.js", 4 | "render_readme": true 5 | } -------------------------------------------------------------------------------- /tracker.json: -------------------------------------------------------------------------------- 1 | { 2 | "xiaomi-vacuum-card": { 3 | "updated_at": "2022-03-04", 4 | "version": "4.5.0", 5 | "remote_location": "https://raw.githubusercontent.com/benct/lovelace-xiaomi-vacuum-card/master/xiaomi-vacuum-card.js", 6 | "visit_repo": "https://github.com/benct/lovelace-xiaomi-vacuum-card", 7 | "changelog": "https://github.com/benct/lovelace-xiaomi-vacuum-card/blob/master/CHANGELOG.md" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Ben Tomlin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | ## 4.5.0 5 | 6 | *Note:* HA version `2022.3.0` or higher required to support new dropdown elements. 7 | 8 | - **Fixed:** Replace unsupported paper elements with MWC dropdown menu (#99, #100) 9 | 10 | ## 4.4.0 11 | 12 | *Note:* HA version `2021.11.0` or higher may be required if you use the standard `xiamoi` vacuum integration. 13 | 14 | - **Added:** Support vacuum data from separate sensor entities (#72, #84) 15 | 16 | ## 4.3.0 17 | 18 | - **Added:** Support custom service on dropdown attributes (#71) 19 | - **Fixed:** Icons not showing after change to `ha-button-icon` (#86, #87) 20 | 21 | ## 4.2.0 22 | 23 | - **Added:** Support using any data values from vacuum entity (#69) 24 | - **Added:** Support any generic dropdown list attributes (#69) 25 | 26 | ## 4.1.0 27 | 28 | - **Added:** Function `shouldUpdate` to prevent unnecessary re-rendering (#61) 29 | - **Changed:** Use dashes instead of underscore in console info card name 30 | 31 | ## 4.0.1 32 | 33 | - **Fixed:** Compatibility issue with HA `0.116` (#56) 34 | 35 | ## 4.0.0 36 | 37 | Refactored most of the code and added several features and improvements. 38 | May contain **breaking changes** and require some **configuration changes**! 39 | See [README](https://github.com/benct/lovelace-xiaomi-vacuum-card) for more information. 40 | 41 | - **Added:** Dropdown menu for selecting operation mode/fan speed (#9, #48) 42 | - **Added:** Support additional buttons and custom service calls (#26, #41, #50, #51) 43 | - **Added:** Support hiding any state or attribute (#42, #47) 44 | - **Added:** Display icons with state values and support icons on all attributes 45 | - **Added:** Display vacuum entity's battery icon if available 46 | - **Added:** Vendor support for Neato vacuums (#16) 47 | - **Added:** Vendor support for Xiaomi Mi vacuums (#34) 48 | - **Added:** Vendor support for Deebot (slim) vacuums (#53) 49 | - **Changed:** Simplify several vendor integrations 50 | - **Changed:** Render proper icon buttons with optional labels 51 | - **Changed:** Make background image disabled by default 52 | - **Fixed:** Incorrect padding causing hidden text shadows under title 53 | 54 | ## 3.0.1 55 | 56 | - **Fixed:** Incorrect unit on `roomba` boolean attribute values (#24) 57 | 58 | ## 3.0.0 59 | 60 | - **Changed:** Major refactoring and cleanup of code 61 | - **Changed:** Use LitElement instead of Polymer 62 | - **Added:** Support for HA Cast [https://cast.home-assistant.io](https://cast.home-assistant.io) 63 | - **Added:** Support custom button icons 64 | - **Added:** Support hiding specific vacuum attributes (#27) 65 | - **Added:** Vendor support for iRobot Roomba vacuums (#24) 66 | 67 | ## 2.4.0 68 | 69 | - **Added:** Option to hide all labels/details (#20) 70 | - **Added:** Vendor support for Robovac vacuums (#23) 71 | 72 | ## 2.3.2 73 | 74 | - **Fixed:** Error on undefined state objects 75 | 76 | ## 2.3.1 77 | 78 | - **Changed:** Vendor `ecovacs_ozmo` changed to more accurate `deebot` (#17) 79 | - **Changed:** Round computed numbers for `deebot` values 80 | - **Fixed:** Main value units showing as undefined 81 | 82 | ## 2.3.0 83 | 84 | - **Added:** Support alternate attributes for Valetudo/Dustcloud firmware (#15) 85 | - **Added:** Support alternate attributes for Ecovacs Ozmo models (#17) 86 | - **Changed:** Improved general attribute and value handling 87 | 88 | ## 2.2.1 89 | 90 | - **Fixed:** Wrong button color on light themes (#11) 91 | 92 | ## 2.2.0 93 | 94 | - **Added:** Clean spot button and service call (#7) 95 | - **Added:** Options to show/hide individual buttons 96 | 97 | ## 2.1.0 98 | 99 | - **Added:** Customization/translation of labels (#6) 100 | - **Fixed:** Link to changelog in custom_updater json (#5) 101 | - **Fixed:** Incorrect option name in readme example 102 | 103 | ## 2.0.0 104 | 105 | - **Added:** Support for custom_updater component (#2) 106 | - **Added:** Vendor support for Ecovacs vacuums (#3) 107 | - **Changed:** Significant code improvements 108 | - **Changed:** Accommodate future vendor implementations 109 | - **Fixed:** Use standardized name and path for background images (#4) 110 | 111 | **Breaking** 112 | - Option `background` renamed to `image` 113 | - Custom image URLs must now include the `/local/` path prefix 114 | 115 | ## 1.1.1 116 | 117 | - **Fixed:** Unsupported function syntax for some browsers 118 | 119 | ## 1.1.0 120 | 121 | - **Added:** Support for `frienly_name` / custom name (#1) 122 | - **Added:** Version information 123 | 124 | ## 1.0.0 125 | 126 | - **Initial release** 127 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xiaomi-vacuum-card 2 | 3 | Simple card for various robot vacuums in Home Assistant's Lovelace UI 4 | 5 | [![GH-release](https://img.shields.io/github/v/release/benct/lovelace-xiaomi-vacuum-card.svg?style=flat-square)](https://github.com/benct/lovelace-xiaomi-vacuum-card/releases) 6 | [![GH-downloads](https://img.shields.io/github/downloads/benct/lovelace-xiaomi-vacuum-card/total?style=flat-square)](https://github.com/benct/lovelace-xiaomi-vacuum-card/releases) 7 | [![GH-last-commit](https://img.shields.io/github/last-commit/benct/lovelace-xiaomi-vacuum-card.svg?style=flat-square)](https://github.com/benct/lovelace-xiaomi-vacuum-card/commits/master) 8 | [![GH-code-size](https://img.shields.io/github/languages/code-size/benct/lovelace-xiaomi-vacuum-card.svg?color=red&style=flat-square)](https://github.com/benct/lovelace-xiaomi-vacuum-card) 9 | [![hacs_badge](https://img.shields.io/badge/HACS-Default-orange.svg?style=flat-square)](https://github.com/hacs) 10 | 11 | Integrated support for most vacuums from the following brands/models: 12 | Xiaomi, Roomba, Neato, Robovac, Valetudo, Ecovacs, Deebot 13 | 14 | ## Installation 15 | 16 | Manually add [xiaomi-vacuum-card.js](https://raw.githubusercontent.com/benct/lovelace-xiaomi-vacuum-card/master/xiaomi-vacuum-card.js) 17 | to your `/www/` folder and add the following to the `configuration.yaml` file: 18 | ```yaml 19 | lovelace: 20 | resources: 21 | - url: /local/xiaomi-vacuum-card.js?v=4.5.0 22 | type: module 23 | ``` 24 | 25 | _OR_ install using [HACS](https://hacs.xyz/) and add this (if in YAML mode): 26 | ```yaml 27 | lovelace: 28 | resources: 29 | - url: /hacsfiles/lovelace-xiaomi-vacuum-card/xiaomi-vacuum-card.js 30 | type: module 31 | ``` 32 | 33 | The above configuration can be managed directly in the Configuration -> Lovelace Dashboards -> Resources panel when not using YAML mode, 34 | or added by clicking the "Add to lovelace" button on the HACS dashboard after installing the plugin. 35 | 36 | If you want to use the vacuum background image, download and add 37 | [img/vacuum.png](https://raw.githubusercontent.com/benct/lovelace-xiaomi-vacuum-card/master/img/vacuum.png) 38 | to `/www/img/` or configure your own preferred path. 39 | 40 | ## Configuration 41 | 42 | | Name | Type | Default | Description 43 | | ---- | ---- | ------- | ----------- 44 | | type | string | **Required** | `custom:xiaomi-vacuum-card` 45 | | entity | string | **Required** | `vacuum.my_xiaomi_vacuum` 46 | | name | string/bool | `friendly_name` | Override friendly name (set to `false` to hide) 47 | | image | string/bool | `false` | Set path/filename of background image (i.e. `/local/img/vacuum.png`) 48 | | state | [Entity Data](#entity-data) | *(see below)* | Set to `false` to hide all states 49 | | attributes | [Entity Data](#entity-data) | *(see below)* | Set to `false` to hide all attributes 50 | | buttons | [Button Data](#button-data) | *(see below)* | Set to `false` to hide button row 51 | 52 | ### Entity Data 53 | 54 | Default vacuum attributes under each list: 55 | - `state` (**left list**) include `status`, `battery` and `mode`. 56 | - `attributes` (**right list**) include `main_brush`, `side_brush`, `filter` and `sensor`. 57 | 58 | See [examples](#examples) on how to customize, hide or add custom attributes. 59 | 60 | | Name | Type | Default | Description 61 | | ---- | ---- | ------- | ----------- 62 | | key | string | **Required** | Attribute/state key on vacuum entity 63 | | icon | string | | Optional icon 64 | | label | string | | Optional label text 65 | | unit | string | | Optional unit 66 | 67 | ### Button Data 68 | 69 | Default buttons include `start`, `pause`, `stop`, `spot` (hidden), `locate` and `return`. 70 | See [examples](#examples) on how to customize, hide or add custom buttons/actions. 71 | 72 | | Name | Type | Default | Description 73 | | ---- | ---- | ------- | ----------- 74 | | icon | string | **Required** | Show or hide stop button 75 | | service | string | **Required** | Service to call (i.e `vacuum.start`) 76 | | show | bool | `true` | Show or hide button 77 | | label | string | | Optional label on hover 78 | | service_data | object | | Data applied to the service call 79 | 80 | ### Other vendors 81 | 82 | This card was originally written for Xiaomi (Roborock) vacuum cleaners, but version `2.0` and later has added support for some other vendors too. 83 | If you want any other vendors to be added, feel free to open an issue or contribute directly with a PR. 84 | 85 | | Name | Type | Default | Description 86 | | ---- | ---- | ------- | ----------- 87 | | vendor | string | `xiaomi` | Supported vendors: `xiaomi`, `xiaomi_mi`, `valetudo`, `ecovacs`, `deebot`, `deebot_slim`, `robovac`, `roomba`, `neato` 88 | 89 | *Note: Default attributes and buttons may change for each vendor integration.* 90 | 91 | ## Screenshots 92 | 93 | ![xiaomi-vacuum-card](https://raw.githubusercontent.com/benct/lovelace-xiaomi-vacuum-card/master/examples/default.png) 94 | 95 | ![xiaomi-vacuum-card-no-title](https://raw.githubusercontent.com/benct/lovelace-xiaomi-vacuum-card/master/examples/no-title.png) 96 | 97 | ![xiaomi-vacuum-card-image](https://raw.githubusercontent.com/benct/lovelace-xiaomi-vacuum-card/master/examples/with-image.png) 98 | 99 | ![xiaomi-vacuum-card-no-buttons](https://raw.githubusercontent.com/benct/lovelace-xiaomi-vacuum-card/master/examples/no-buttons.png) 100 | 101 | ## Examples 102 | 103 | Basic configuration: 104 | ```yaml 105 | - type: custom:xiaomi-vacuum-card 106 | entity: vacuum.xiaomi_vacuum_cleaner 107 | ``` 108 | 109 | ```yaml 110 | - type: custom:xiaomi-vacuum-card 111 | entity: vacuum.xiaomi_vacuum_cleaner 112 | image: /local/custom/folder/background.png 113 | name: My Vacuum 114 | vendor: xiaomi 115 | ``` 116 | 117 | Hide state, attributes and/or buttons: 118 | ```yaml 119 | - type: custom:xiaomi-vacuum-card 120 | entity: vacuum.xiaomi_vacuum_cleaner 121 | state: false 122 | attributes: false 123 | buttons: false 124 | ``` 125 | 126 | Hide specific state values, attributes and/or buttons: 127 | ```yaml 128 | - type: custom:xiaomi-vacuum-card 129 | entity: vacuum.xiaomi_vacuum_cleaner 130 | state: 131 | mode: false 132 | attributes: 133 | main_brush: false 134 | side_brush: false 135 | buttons: 136 | pause: false 137 | locate: false 138 | ``` 139 | 140 | Customize specific state values, attributes and/or buttons: 141 | ```yaml 142 | - type: custom:xiaomi-vacuum-card 143 | entity: vacuum.xiaomi_vacuum_cleaner 144 | state: 145 | status: 146 | key: state 147 | mode: 148 | icon: mdi:robot-vacuum 149 | label: 'Fan speed: ' 150 | unit: 'percent' 151 | attributes: 152 | main_brush: 153 | key: component_main_brush 154 | side_brush: 155 | key: component_side_brush 156 | buttons: 157 | pause: 158 | icon: mdi:stop 159 | label: Hold 160 | service: vacuum.stop 161 | ``` 162 | 163 | Show default clean spot button: 164 | ```yaml 165 | - type: custom:xiaomi-vacuum-card 166 | entity: vacuum.xiaomi_vacuum_cleaner 167 | buttons: 168 | spot: 169 | show: true 170 | ``` 171 | 172 | Add custom attributes: 173 | ```yaml 174 | - type: custom:xiaomi-vacuum-card 175 | entity: vacuum.xiaomi_vacuum_cleaner 176 | attributes: 177 | clean_area: 178 | key: 'clean_area' 179 | label: 'Cleaned area: ' 180 | unit: ' m2' 181 | ``` 182 | 183 | Add custom buttons and service calls: 184 | ```yaml 185 | - type: custom:xiaomi-vacuum-card 186 | entity: vacuum.xiaomi_vacuum_cleaner 187 | buttons: 188 | new_button: 189 | icon: mdi:light-switch 190 | label: Custom button! 191 | service: light.turn_off 192 | service_data: 193 | entity_id: light.living_room 194 | ``` 195 | 196 | Translations: 197 | ```yaml 198 | - type: custom:xiaomi-vacuum-card 199 | entity: vacuum.xiaomi_vacuum_cleaner 200 | attributes: 201 | main_brush: 202 | label: 'Hovedkost: ' 203 | unit: ' timer' 204 | side_brush: 205 | label: 'Sidekost: ' 206 | unit: ' timer' 207 | filter: 208 | label: 'Filtere: ' 209 | sensor: 210 | label: 'Sensorer: ' 211 | buttons: 212 | start: 213 | label: Start! 214 | pause: 215 | label: Stopp! 216 | stop: 217 | label: Hammertime 218 | ``` 219 | 220 | ## Disclaimer 221 | 222 | This project is not affiliated, associated, authorized, endorsed by, or in any way officially connected with the Xiaomi Corporation, 223 | or any of its subsidiaries or its affiliates. The official Xiaomi website can be found at https://www.mi.com/global/. 224 | 225 | ## My cards 226 | 227 | [xiaomi-vacuum-card](https://github.com/benct/lovelace-xiaomi-vacuum-card) | 228 | [multiple-entity-row](https://github.com/benct/lovelace-multiple-entity-row) | 229 | [github-entity-row](https://github.com/benct/lovelace-github-entity-row) | 230 | [battery-entity-row](https://github.com/benct/lovelace-battery-entity-row) | 231 | [~~attribute-entity-row~~](https://github.com/benct/lovelace-attribute-entity-row) 232 | 233 | [![BMC](https://www.buymeacoffee.com/assets/img/custom_images/white_img.png)](https://www.buymeacoff.ee/benct) 234 | -------------------------------------------------------------------------------- /xiaomi-vacuum-card.js: -------------------------------------------------------------------------------- 1 | ((LitElement) => { 2 | console.info( 3 | '%c XIAOMI-VACUUM-CARD %c 4.5.0 ', 4 | 'color: cyan; background: black; font-weight: bold;', 5 | 'color: darkblue; background: white; font-weight: bold;', 6 | ); 7 | 8 | const state = { 9 | status: { 10 | key: 'status', 11 | icon: 'mdi:robot-vacuum', 12 | }, 13 | battery: { 14 | key: 'battery_level', 15 | unit: '%', 16 | icon: 'mdi:battery-charging-80', 17 | }, 18 | mode: { 19 | key: 'fan_speed', 20 | icon: 'mdi:fan', 21 | }, 22 | }; 23 | 24 | const attributes = { 25 | main_brush: { 26 | key: 'main_brush_left', 27 | label: 'Main Brush: ', 28 | unit: ' h', 29 | }, 30 | side_brush: { 31 | key: 'side_brush_left', 32 | label: 'Side Brush: ', 33 | unit: ' h', 34 | }, 35 | filter: { 36 | key: 'filter_left', 37 | label: 'Filter: ', 38 | unit: ' h', 39 | }, 40 | sensor: { 41 | key: 'sensor_dirty_left', 42 | label: 'Sensor: ', 43 | unit: ' h', 44 | }, 45 | }; 46 | 47 | const buttons = { 48 | start: { 49 | label: 'Start', 50 | icon: 'mdi:play', 51 | service: 'vacuum.start', 52 | }, 53 | pause: { 54 | label: 'Pause', 55 | icon: 'mdi:pause', 56 | service: 'vacuum.pause', 57 | }, 58 | stop: { 59 | label: 'Stop', 60 | icon: 'mdi:stop', 61 | service: 'vacuum.stop', 62 | }, 63 | spot: { 64 | show: false, 65 | label: 'Clean Spot', 66 | icon: 'mdi:broom', 67 | service: 'vacuum.clean_spot', 68 | }, 69 | locate: { 70 | label: 'Locate', 71 | icon: 'mdi:map-marker', 72 | service: 'vacuum.locate', 73 | }, 74 | return: { 75 | label: 'Return to Base', 76 | icon: 'mdi:home-map-marker', 77 | service: 'vacuum.return_to_base', 78 | }, 79 | }; 80 | 81 | const compute = { 82 | trueFalse: v => (v === true ? 'Yes' : (v === false ? 'No' : '-')), 83 | divide100: v => Math.round(Number(v) / 100), 84 | secToHour: v => Math.floor(Number(v) / 60 / 60), 85 | } 86 | 87 | const vendors = { 88 | xiaomi: { 89 | attributes: { 90 | main_brush: {compute: compute.secToHour}, 91 | side_brush: {compute: compute.secToHour}, 92 | filter: {compute: compute.secToHour}, 93 | sensor: {compute: compute.secToHour}, 94 | } 95 | }, 96 | xiaomi_mi: { 97 | attributes: { 98 | main_brush: {key: 'main_brush_hours'}, 99 | side_brush: {key: 'side_brush_hours'}, 100 | filter: {key: 'hypa_hours'}, 101 | sensor: { 102 | key: 'mop_hours', 103 | label: 'Mop: ', 104 | }, 105 | }, 106 | }, 107 | valetudo: { 108 | state: { 109 | status: {key: 'state'}, 110 | }, 111 | attributes: { 112 | main_brush: {key: 'mainBrush'}, 113 | side_brush: {key: 'sideBrush'}, 114 | filter: {key: 'filter'}, 115 | sensor: {key: 'sensor'}, 116 | }, 117 | }, 118 | roomba: { 119 | attributes: { 120 | main_brush: false, 121 | side_brush: false, 122 | filter: false, 123 | sensor: false, 124 | bin_present: { 125 | key: 'bin_present', 126 | label: 'Bin Present: ', 127 | compute: compute.trueFalse, 128 | }, 129 | bin_full: { 130 | key: 'bin_full', 131 | label: 'Bin Full: ', 132 | compute: compute.trueFalse, 133 | }, 134 | }, 135 | }, 136 | robovac: { 137 | attributes: false, 138 | buttons: { 139 | stop: {show: false}, 140 | spot: {show: true}, 141 | }, 142 | }, 143 | ecovacs: { 144 | attributes: false, 145 | buttons: { 146 | start: {service: 'vacuum.turn_on'}, 147 | pause: {service: 'vacuum.stop'}, 148 | stop: {service: 'vacuum.turn_off', show: false}, 149 | spot: {show: true}, 150 | }, 151 | }, 152 | deebot: { 153 | buttons: { 154 | start: {service: 'vacuum.turn_on'}, 155 | pause: {service: 'vacuum.stop'}, 156 | stop: {service: 'vacuum.turn_off'}, 157 | }, 158 | attributes: { 159 | main_brush: { 160 | key: 'component_main_brush', 161 | compute: compute.divide100, 162 | }, 163 | side_brush: { 164 | key: 'component_side_brush', 165 | compute: compute.divide100, 166 | }, 167 | filter: { 168 | key: 'component_filter', 169 | compute: compute.divide100, 170 | }, 171 | sensor: false, 172 | }, 173 | }, 174 | deebot_slim: { 175 | buttons: { 176 | start: {service: 'vacuum.turn_on'}, 177 | pause: {service: 'vacuum.stop'}, 178 | stop: {service: 'vacuum.turn_off'}, 179 | }, 180 | attributes: { 181 | main_brush: false, 182 | side_brush: {key: 'component_side_brush'}, 183 | filter: {key: 'component_filter'}, 184 | sensor: false, 185 | }, 186 | }, 187 | neato: { 188 | state: { 189 | mode: false, 190 | }, 191 | attributes: { 192 | main_brush: false, 193 | side_brush: false, 194 | filter: false, 195 | sensor: false, 196 | clean_area: { 197 | key: 'clean_area', 198 | label: 'Cleaned area: ', 199 | unit: ' m2', 200 | }, 201 | }, 202 | }, 203 | }; 204 | 205 | const html = LitElement.prototype.html; 206 | const css = LitElement.prototype.css; 207 | 208 | class XiaomiVacuumCard extends LitElement { 209 | 210 | static get properties() { 211 | return { 212 | _hass: {}, 213 | config: {}, 214 | stateObj: {}, 215 | } 216 | } 217 | 218 | static get styles() { 219 | return css` 220 | .background { 221 | background-repeat: no-repeat; 222 | background-position: center center; 223 | background-size: cover; 224 | } 225 | .title { 226 | font-size: 20px; 227 | padding: 12px 16px 8px; 228 | text-align: center; 229 | white-space: nowrap; 230 | text-overflow: ellipsis; 231 | overflow: hidden; 232 | } 233 | .flex { 234 | display: flex; 235 | align-items: center; 236 | justify-content: space-evenly; 237 | } 238 | .grid { 239 | display: grid; 240 | grid-template-columns: repeat(2, auto); 241 | cursor: pointer; 242 | } 243 | .grid-content { 244 | display: grid; 245 | align-content: space-between; 246 | grid-row-gap: 6px; 247 | } 248 | .grid-left { 249 | text-align: left; 250 | font-size: 110%; 251 | padding-left: 10px; 252 | border-left: 2px solid var(--primary-color); 253 | } 254 | .grid-right { 255 | text-align: right; 256 | padding-right: 10px; 257 | border-right: 2px solid var(--primary-color); 258 | }`; 259 | } 260 | 261 | render() { 262 | return this.stateObj ? html` 263 | 264 | ${this.config.show.name ? 265 | html`
${this.config.name || this.stateObj.attributes.friendly_name}
` 266 | : null} 267 | ${(this.config.show.state || this.config.show.attributes) ? html` 268 |
269 | ${this.config.show.state ? html` 270 |
271 | ${Object.values(this.config.state).filter(v => v).map(this.renderAttribute.bind(this))} 272 |
` : null} 273 | ${this.config.show.attributes ? html` 274 |
275 | ${Object.values(this.config.attributes).filter(v => v).map(this.renderAttribute.bind(this))} 276 |
` : null} 277 |
` : null} 278 | ${this.config.show.buttons ? html` 279 |
280 | ${Object.values(this.config.buttons).filter(v => v).map(this.renderButton.bind(this))} 281 |
` : null} 282 |
` : html`Entity '${this.config.entity}' not available...`; 283 | } 284 | 285 | renderAttribute(data) { 286 | const computeFunc = data.compute || (v => v); 287 | const isValidSensorData = data && `${this.config.sensorEntity}_${data.key}` in this._hass.states; 288 | const isValidAttribute = data && data.key in this.stateObj.attributes; 289 | const isValidEntityData = data && data.key in this.stateObj; 290 | 291 | const value = isValidSensorData 292 | ? computeFunc(this._hass.states[`${this.config.sensorEntity}_${data.key}`].state) + (data.unit || '') 293 | : isValidAttribute 294 | ? computeFunc(this.stateObj.attributes[data.key]) + (data.unit || '') 295 | : isValidEntityData 296 | ? computeFunc(this.stateObj[data.key]) + (data.unit || '') 297 | : null; 298 | const attribute = html`
299 | ${data.icon && this.renderIcon(data)} 300 | ${(data.label || '') + (value !== null ? value : this._hass.localize('state.default.unavailable'))} 301 |
`; 302 | 303 | const hasDropdown = `${data.key}_list` in this.stateObj.attributes; 304 | 305 | return (hasDropdown && value !== null) 306 | ? this.renderDropdown(attribute, data.key, data.service) 307 | : attribute; 308 | } 309 | 310 | renderIcon(data) { 311 | const icon = (data.key === 'battery_level' && 'battery_icon' in this.stateObj.attributes) 312 | ? this.stateObj.attributes.battery_icon 313 | : data.icon; 314 | return html``; 315 | } 316 | 317 | renderButton(data) { 318 | return data && data.show !== false 319 | ? html` 323 | 324 | ` 325 | : null; 326 | } 327 | 328 | renderDropdown(attribute, key, service) { 329 | const list = this.stateObj.attributes[`${key}_list`]; 330 | 331 | return html` 332 |
e.stopPropagation()}> 333 | this.toggleMenu(key)}> 334 | ${attribute} 335 | 336 | this.handleChange(list[e.detail.index], key, service)} 338 | id=${`xvc-menu-${key}`} 339 | activatable 340 | corner="BOTTOM_START"> 341 | ${list.map(item => html`${item}`)} 342 | 343 |
`; 344 | } 345 | 346 | toggleMenu(key) { 347 | const menu = this.shadowRoot.querySelector(`#xvc-menu-${key}`); 348 | menu.open = !menu.open; 349 | } 350 | 351 | getCardSize() { 352 | if (this.config.show.name && this.config.show.buttons) return 4; 353 | if (this.config.show.name || this.config.show.buttons) return 3; 354 | return 2; 355 | } 356 | 357 | shouldUpdate(changedProps) { 358 | return changedProps.has('stateObj'); 359 | } 360 | 361 | setConfig(config) { 362 | if (!config.entity) throw new Error('Please define an entity.'); 363 | if (config.entity.split('.')[0] !== 'vacuum') throw new Error('Please define a vacuum entity.'); 364 | if (config.vendor && !config.vendor in vendors) throw new Error('Please define a valid vendor.'); 365 | 366 | const vendor = vendors[config.vendor] || vendors.xiaomi; 367 | 368 | this.config = { 369 | name: config.name, 370 | entity: config.entity, 371 | sensorEntity: `sensor.${config.entity.split('.')[1]}`, 372 | show: { 373 | name: config.name !== false, 374 | state: config.state !== false, 375 | attributes: config.attributes !== false, 376 | buttons: config.buttons !== false, 377 | }, 378 | buttons: this.deepMerge(buttons, vendor.buttons, config.buttons), 379 | state: this.deepMerge(state, vendor.state, config.state), 380 | attributes: this.deepMerge(attributes, vendor.attributes, config.attributes), 381 | styles: { 382 | background: config.image ? `background-image: url('${config.image}'); color: white; text-shadow: 0 0 10px black;` : '', 383 | icon: `color: ${config.image ? 'white' : 'var(--paper-item-icon-color)'};`, 384 | content: `padding: ${config.name !== false ? '8px' : '16px'} 16px ${config.buttons !== false ? '8px' : '16px'};`, 385 | }, 386 | }; 387 | } 388 | 389 | set hass(hass) { 390 | if (hass && this.config) { 391 | this.stateObj = this.config.entity in hass.states ? hass.states[this.config.entity] : null; 392 | } 393 | this._hass = hass; 394 | } 395 | 396 | handleChange(mode, key, service) { 397 | this.callService(service || `vacuum.set_${key}`, {entity_id: this.stateObj.entity_id, [key]: mode}); 398 | } 399 | 400 | callService(service, data = {entity_id: this.stateObj.entity_id}) { 401 | const [domain, name] = service.split('.'); 402 | this._hass.callService(domain, name, data); 403 | } 404 | 405 | fireEvent(type, options = {}) { 406 | const event = new Event(type, { 407 | bubbles: options.bubbles || true, 408 | cancelable: options.cancelable || true, 409 | composed: options.composed || true, 410 | }); 411 | event.detail = {entityId: this.stateObj.entity_id}; 412 | this.dispatchEvent(event); 413 | } 414 | 415 | deepMerge(...sources) { 416 | const isObject = (obj) => obj && typeof obj === 'object'; 417 | const target = {}; 418 | 419 | sources.filter(source => isObject(source)).forEach(source => { 420 | Object.keys(source).forEach(key => { 421 | const targetValue = target[key]; 422 | const sourceValue = source[key]; 423 | 424 | if (Array.isArray(targetValue) && Array.isArray(sourceValue)) { 425 | target[key] = targetValue.concat(sourceValue); 426 | } else if (isObject(targetValue) && isObject(sourceValue)) { 427 | target[key] = this.deepMerge(Object.assign({}, targetValue), sourceValue); 428 | } else { 429 | target[key] = sourceValue; 430 | } 431 | }); 432 | }); 433 | 434 | return target; 435 | } 436 | } 437 | 438 | customElements.define('xiaomi-vacuum-card', XiaomiVacuumCard); 439 | })(window.LitElement || Object.getPrototypeOf(customElements.get("hui-masonry-view") || customElements.get("hui-view"))); 440 | --------------------------------------------------------------------------------