Home Assistant Vacuum Card for Synthwave theme

May 2, 2020 ยท View on GitHub

import { LitElement, html } from 'https://unpkg.com/@polymer/lit-element@0.6.5/lit-element.js?module';

const styles = html`

`

class VacuumCard extends LitElement { static get properties() { return { hass: Object, config: Object, mapUrl: String } }

get entity() { return this.hass.states[this.config.entity] }

get map() { return this.hass.states[this.config.map] }

firstUpdated() { this.refresh() }

refresh() { const cleaning = this.entity.state !== 'docked'

const url = this.map.attributes.entity_picture + `&t=${(new Date()).getTime()}`
const img = new Image(); 
img.onload = () => {
  this.mapUrl = url
  this.requestUpdate();
  setTimeout(() => this.refresh(), cleaning ? 1000 : 5000)
}
img.src = url

}

handleMore() { const e = new Event('hass-more-info', { bubbles: true, composed: true }) e.detail = { entityId: this.entity.entity_id } this.dispatchEvent(e); }

handleSpeed(e) { const fan_speed = e.target.getAttribute('value'); this.callService('set_fan_speed', { fan_speed }); }

callService(service, options = {}) { this.hass.callService('vacuum', service, { entity_id: this.config.entity, ...options }); }

renderSource() { const { attributes: { fan_speed: source, fan_speed_list: sources } } = this.entity;

const selected = sources.indexOf(source);
return html`
  <paper-menu-button class='source-menu' slot='dropdown-trigger'
    .horizontalAlign=${'right'} .verticalAlign=${'top'}
    .verticalOffset=${40} .noAnimations=${true}
    @click='${(e) => e.stopPropagation()}'>
    <paper-button class='source-menu__button' slot='dropdown-trigger'>
      <span class='source-menu__source' show=${true}>
        ${source}
      </span>
      <ha-icon icon="mdi:fan"></ha-icon>
    </paper-button>
    <paper-listbox slot='dropdown-content' selected=${selected}
      @click='${(e) => this.handleSpeed(e)}'>
      ${sources.map(item => html`<paper-item value=${item}>${item}</paper-item>`)}
    </paper-listbox>
  </paper-menu-button>`;

}

renderVacuumStats() { const { attributes: { main_brush_left, side_brush_left, filter_left, sensor_dirty_left } } = this.entity

return html`
  <div class="stats-block">
    <span class="stats-hours">${filter_left}</span> <sup>hours</sup>
    <div class="stats-subtitle">Filter</div>
  </div>
  <div class="stats-block">
    <span class="stats-hours">${side_brush_left}</span> <sup>hours</sup>
    <div class="stats-subtitle">Side brush</div>
  </div>
  <div class="stats-block">
    <span class="stats-hours">${main_brush_left}</span> <sup>hours</sup>
    <div class="stats-subtitle">Main brush</div>
  </div>
  <div class="stats-block">
    <span class="stats-hours">${sensor_dirty_left}</span> <sup>hours</sup>
    <div class="stats-subtitle">Sensors</div>
  </div>
`

}

renderCleaningStats() { const { attributes: { cleaned_area, cleaning_time } } = this.entity

return html`
  <div class="stats-block">
    <span class="stats-hours">${cleaned_area}</span> m<sup>2</sup>
    <div class="stats-subtitle">Cleaning area</div>
  </div>
  <div class="stats-block">
    <span class="stats-hours">${cleaning_time}</span> minutes
    <div class="stats-subtitle">Cleaning time</div>
  </div>
`

}

renderCleaningToolbar() { return html <div class="toolbar"> <paper-button @click='${(e) => this.callService('return_to_base')}'> <ha-icon icon="hass:home-map-marker" ></ha-icon> Return to dock </paper-button> </div> }

renderDockedToolbar() { const { actions } = this.config const buttons = actions.map(({ name, service, icon }) => { const execute = () => { const args = service.split('.') this.hass.callService(args[0], args[1]); } return html<paper-icon-button icon="${icon}" title="${name}" @click='${execute}'></paper-icon-button> })

return html`
  <div class="toolbar">
    <paper-icon-button  icon="hass:play" 
                        title="Clean" class="toolbar-icon"
                        @click='${(e) => this.callService('start')}'>
    </paper-icon-button>
    <paper-icon-button  icon="mdi:crosshairs-gps" 
                        title="Locate vacuum" class="toolbar-split"
                        @click='${(e) => this.callService('locate')}'>
    </paper-icon-button>
    <div class="fill-gap"></div>
    ${buttons}
  </div>
`

}

render() { const { state, attributes: { status, battery_level, battery_icon, } } = this.entity

const cleaning = state !== 'docked'

return html`
  ${styles}
  <ha-card>
    <div class="preview" @click='${(e) => this.handleMore()}' ?more-info=true>
      <div class="header">
        <div class="status">${status}</div>
        <div>
          ${this.renderSource()}
        </div>
        <div class="battery">
          ${battery_level}% <ha-icon icon="${battery_icon}"></ha-icon>
        </div>
      </div>

      <img class="vacuum" src="${this.mapUrl}" />

      <div class="stats">
        ${cleaning ? this.renderCleaningStats() : this.renderVacuumStats()}
      </div>
    </div>
    ${cleaning ? this.renderCleaningToolbar() : this.renderDockedToolbar()}
  </ha-card>
`

}

setConfig(config) { this.config = config; }

getCardSize() { return 2; } }

customElements.define('vacuum-card', VacuumCard);