diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8bea6da --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +slider-entity-row.js binary diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d26a54b --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +package-lock.json +package.json +webpack.config.js diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5b9de40 --- /dev/null +++ b/Makefile @@ -0,0 +1,42 @@ +AUTHOR := Thomas Lovén +CARD_TOOLS := $(PWD)/../card-tools + +PACKAGE := $(shell basename $(CURDIR)) +PACKAGE := $(PACKAGE:lovelace-%=%) +DOCKER_CMD:=docker run --rm -v $(CARD_TOOLS):/card-tools:ro -v $(PWD):/usr/src/$(PACKAGE) -w="/usr/src/$(PACKAGE)" node:11 + +build: setup + $(DOCKER_CMD) npm run build + +dev: setup + $(DOCKER_CMD) npm run watch + +setup: package.json package-lock.json webpack.config.js + +clean: + rm package.json package-lock.json webpack.config.js + rm -r node_modules + rm $(PACKAGE).js + +define WEBPACK_CONFIG +const path = require('path'); + +module.exports = { + entry: './src/main.js', + mode: 'production', + output: { + filename: '$(PACKAGE).js', + path: path.resolve(__dirname) + } +}; +endef +export WEBPACK_CONFIG +webpack.config.js: + echo "$$WEBPACK_CONFIG" >> $@ + +package-lock.json: + $(DOCKER_CMD) npm install webpack webpack-cli --save-dev + +package.json: + $(DOCKER_CMD) /bin/bash -c "npm set init.license 'MIT' && npm set init.author.name '$(AUTHOR)' && npm init -y" + $(DOCKER_CMD) sed -E -i -e '/^ +"main"/d' -e '/^ +"scripts"/a\ "build": "webpack",' -e '/^ +"scripts"/a\ "watch": "webpack --watch --mode=development",' -e '2a\ "private": true,' $@ diff --git a/README.md b/README.md index bab5a80..abe8e4f 100644 --- a/README.md +++ b/README.md @@ -1,105 +1,80 @@ slider-entity-row ================= -Add a slider to rows in lovelace entity cards +Add a slider to rows in lovelace [entities](https://www.home-assistant.io/lovelace/entities/) cards. -This works for: +For installation instructions [see this guide](https://github.com/thomasloven/hass-config/wiki/Lovelace-Plugins). + +Install `slider-entity-row.js` as a `module`. + +## Usage +Add this to an entities card: + +```yaml +type: entities +entities: + - light.bed_light + - type: custom:slider-entity-row + entity: light.kitchen_lights +``` + +![slider-entity-row](https://user-images.githubusercontent.com/1299821/59467898-15b16600-8e31-11e9-9924-53b108572d3a.png) + +Currenly supported entity domains: - `light` - set brightness - `media_player` - set volume - `climate` - set temperature - `cover` - set position - `fan` - set speed (assumes first setting is `off`) -- `input_number` -- `input_select` +- `input_number` - set value (only if `mode: slider`) +- `input_select` - select option -![slider-entity-row](https://user-images.githubusercontent.com/1299821/48869222-b6303200-eddc-11e8-8b8c-7b4a9601df7a.png) +![domains](https://user-images.githubusercontent.com/1299821/59467899-1813c000-8e31-11e9-8abd-34c887a7db2a.png) + +### Options + +- `toggle: true` - Show a toggle instead of current state +- `hide_state: true` - Do not display current state +- `hide_when_off: true` - Hide the slider when state is `off` +- `full_row: true` - Hide icon and name and stretch slider to full width +- `min: ` - Set minimum value of slider +- `max: ` - Set maximum value of slider +- `step: ` - Set step size of slider ```yaml - - title: slider-entity-row - cards: - - type: entities - title: Domains - show_header_toggle: false - entities: - - input_number.slider - - - type: section - label: light - - type: custom:slider-entity-row - entity: light.bed_light - - type: custom:slider-entity-row - entity: light.ceiling_lights - - type: custom:slider-entity-row - entity: light.kitchen_lights - - - type: section - label: media_player - - type: custom:slider-entity-row - entity: media_player.bedroom - - type: custom:slider-entity-row - entity: media_player.living_room - - type: custom:slider-entity-row - entity: media_player.lounge_room - - type: custom:slider-entity-row - entity: media_player.walkman - - - type: section - label: cover - - type: custom:slider-entity-row - entity: cover.hall_window - - type: custom:slider-entity-row - entity: cover.garage_door - - type: custom:slider-entity-row - entity: cover.living_room_window - - - type: entities - title: Options - show_header_toggle: false - entities: - - type: section - label: default - - type: custom:slider-entity-row - entity: light.bed_light - - type: custom:slider-entity-row - entity: media_player.bedroom - - type: custom:slider-entity-row - entity: cover.hall_window - - - type: section - label: "toggle: true" - - type: custom:slider-entity-row - entity: light.bed_light - toggle: true - - - type: section - label: "full_row: true" - - entity: light.bed_light - - type: custom:slider-entity-row - entity: light.bed_light - full_row: true - - entity: media_player.bedroom - - type: custom:slider-entity-row - entity: media_player.bedroom - full_row: true - - entity: cover.hall_window - - type: custom:slider-entity-row - entity: cover.hall_window - full_row: true +type: entities +title: Options +entities: + - type: custom:slider-entity-row + entity: light.bed_light + name: Default + - type: custom:slider-entity-row + entity: light.bed_light + name: toggle + toggle: true + - type: custom:slider-entity-row + entity: light.bed_light + name: hide_state + hide_state: true + - type: custom:slider-entity-row + entity: light.ceiling_lights + name: hide_when_off + hide_when_off: true + - type: custom:slider-entity-row + entity: light.ceiling_lights + name: hide_when_off + toggle + hide_when_off: true + toggle: true + - type: section + label: full_row + - type: custom:slider-entity-row + entity: light.bed_light + name: hide_state + full_row: true ``` -### Extra options -`hide_state` - (default: false) Set to true to hide the percentage display. - -`min` - (default: 0) Minimum value of slider - -`max` - (default: 100) Maximum value of slider - -`step` - (default: 5) Step size of slider -Note that slider values are in percent and will be rescaled e.g. for lights which require a brightness setting between 0 and 255. - -`hide_when_off` - Hide the slider when entity is off. - +![options](https://user-images.githubusercontent.com/1299821/59467902-19dd8380-8e31-11e9-9173-97c9b6be3179.png) --- -Thanks to Gabe Cook (@gabe565) for help with fan and input_select support. +Buy Me A Coffee diff --git a/slider-entity-row.js b/slider-entity-row.js index a930f9d..fb64434 100644 --- a/slider-entity-row.js +++ b/slider-entity-row.js @@ -1,352 +1,53 @@ -class SliderEntityRow extends Polymer.Element { - - static get template() { - const style = Polymer.html` - - - `; - - const input = Polymer.html` -
-
- - -
-
- `; - - return Polymer.html` - ${style} - - - - `; - } - - setConfig(config) - { - const CONTROLLERS = { - light: { - set: (stateObj, value) => { - value = Math.ceil(value/100.0*255); - if (value) - this._hass.callService('light', 'turn_on', { - entity_id: stateObj.entity_id, - brightness: value - }); - else - this._hass.callService('light', 'turn_off', { - entity_id: stateObj.entity_id - }); - }, - get: (stateObj) => { - return (stateObj.state === 'on') ? - Math.ceil(stateObj.attributes.brightness*100.0/255) : - 0; - }, - supported: { - slider: (stateObj) => { - if(stateObj.state === 'off' && this._config.hide_when_off) return false; - if('brightness' in stateObj.attributes) return true; - if(('supported_features' in stateObj.attributes) && - (stateObj.attributes.supported_features & 1)) return true; - return false; - }, - toggle: () => true, - }, - string: (stateObj, l18n) => { - if(stateObj.state === 'off') return l18n['state.default.off']; - return `${this.controller.get(stateObj)} %`; - }, - min: () => 0, - max: () => 100, - step: () => 5, - }, - - media_player: { - set: (stateObj, value) => { - value = value/100.0; - this._hass.callService('media_player', 'volume_set', { - entity_id: stateObj.entity_id, - volume_level: value - }); - }, - get: (stateObj) => { - return (stateObj.attributes.is_volume_muted) ? - 0 : - Math.ceil(stateObj.attributes.volume_level*100.0); - }, - supported: { - slider: () => true, - toggle: () => false, - }, - string: (stateObj, l18n) => { - if (stateObj.attributes.is_volume_muted) return '-'; - return !!stateObj.attributes.volume_level ? `${this.controller.get(stateObj)} %` : l18n['state.media_player.off']; - }, - min: () => 0, - max: () => 100, - step: () => 5, - }, - - climate: { - set: (stateObj, value) => { - this._hass.callService('climate', 'set_temperature', { - entity_id: stateObj.entity_id, - temperature: value - }); - }, - get: (stateObj) => stateObj.attributes.temperature, - supported: { - slider: () => true, - toggle: () => true, - }, - string: (stateObj, l18n) => { - if (stateObj.attributes.operation_mode === 'off') return l18n['state.climate.off']; - return `${this.controller.get(stateObj)} ${this._hass.config.unit_system.temperature}`; - }, - min: (stateObj) => stateObj.attributes.min_temp, - max: (stateObj) => stateObj.attributes.max_temp, - step: () => 1, - }, - - cover: { - set: (stateObj, value) => { - if (value) - this._hass.callService('cover', 'set_cover_position', { - entity_id: stateObj.entity_id, - position: value - }); - else - this._hass.callService('cover', 'close_cover', { - entity_id: stateObj.entity_id - }); - }, - get: (stateObj) => { - return (stateObj.state === 'open') ? - Math.ceil(stateObj.attributes.current_position) : - 0; - }, - supported: { - slider: (stateObj) => { - if(stateObj.attributes.hasOwnProperty('current_position')) return true; - if((stateObj.attributes.hasOwnProperty('supported_features')) && - (stateObj.attributes.supported_features & 4)) return true; - return false; - }, - toggle: () => false, - }, - string: (stateObj, l18n) => { - if (!this.controller.supported.slider(stateObj)) return ''; - if (stateObj.state === 'closed') return l18n['state.cover.closed']; - return `${this.controller.get(stateObj)} %`; - }, - min: () => 0, - max: () => 100, - step: () => 5, - }, - - fan: { - set: (stateObj, value) => { - if (value in stateObj.attributes.speed_list) - this._hass.callService('fan', 'turn_on', { - entity_id: stateObj.entity_id, - speed: stateObj.attributes.speed_list[value] - }); - else - this._hass.callService('fan', 'turn_off', { - entity_id: stateObj.entity_id - }); - }, - get: (stateObj) => { - return (stateObj.state !== 'off') ? - stateObj.attributes.speed_list.indexOf(stateObj.attributes.speed) : - 0; - }, - supported: { - slider: (stateObj) => { - return (!(stateObj.state === 'off' && this._config.hide_when_off) && - stateObj.attributes.hasOwnProperty('speed')); - }, - toggle: () => true, - }, - string: (stateObj, l18n) => { - if(stateObj.state === 'off') return l18n['state.default.off']; - return stateObj.attributes.speed; - }, - min: (stateObj) => 0, - max: (stateObj) => stateObj.attributes.speed_list.length - 1, - step: () => 1, - }, - - input_number: { - set: (stateObj, value) => { - value = value || 0; - this._hass.callService('input_number', 'set_value', { - entity_id: stateObj.entity_id, - value: value - }); - }, - get: (stateObj) => { - return stateObj.state; - }, - supported: { - slider: (stateObj) => { - return (stateObj.attributes.mode === "slider"); - }, - toggle: () => false - }, - string: (stateObj, l18n) => { - return Math.floor(stateObj.state); - }, - min: (stateObj) => stateObj.attributes.min, - max: (stateObj) => stateObj.attributes.max, - step: (stateObj) => stateObj.attributes.step, - }, - - input_select: { - set: (stateObj, value) => { - if (value in stateObj.attributes.options) - this._hass.callService('input_select', 'select_option', { - entity_id: stateObj.entity_id, - option: stateObj.attributes.options[value] - }); - }, - get: (stateObj) => { - return stateObj.attributes.options.indexOf(stateObj.state); - }, - supported: { - slider: (stateObj) => { - return stateObj.attributes.hasOwnProperty('options') && (stateObj.attributes.options.length > 1); - }, - toggle: () => false, - }, - string: (stateObj, l18n) => stateObj.state, - min: () => 0, - max: (stateObj) => stateObj.attributes.options.length-1, - step: () => 1, - }, - }; - - - this._config = config; - this.stateObj = null; - const domain = config.entity.split('.')[0]; - this.controller = CONTROLLERS[domain]; - if(!this.controller) throw new Error('Unsupported entity domain: ' + domain); - - this.displayRow = !config.full_row; - this.displayToggle = config.toggle && this.controller.supported.toggle(); - this.displayValue = !this.displayToggle; - if(config.hide_state) this.displayValue = false; - this.displaySlider = false; - - this.min = config.min || 0; - this.max = config.max || 100; - this.step = config.step || 5; - - this.rtl = this._isRTL()? "rtl" : "ltr"; - - if(this._hass && this._config) { - this.stateObj = this._config.entity in this._hass.states ? this._hass.states[this._config.entity] : null; - if(this.stateObj) { - this.min = this._config.min || this.controller.min(this.stateObj); - this.max = this._config.max || this.controller.max(this.stateObj); - this.step = this._config.step || this.controller.step(this.stateObj); - this.value = this.controller.get(this.stateObj); - this.displaySlider = this.controller.supported.slider(this.stateObj); + `} + ${this._config.toggle&&t.hasToggle?s:""} + `} + + `;return this._config.full_row?i:a` + ${i} + `}static get styles(){return r` + .wrapper { + display: flex; + align-items: center; + height: 40px; } - } - } - - statusString(stateObj) { - let l18n = this._hass.resources[this._hass.language]; - if(!stateObj) return l18n['state.default.unavailable']; - return this.controller.string(stateObj, l18n); - } - - set hass(hass) { - this._hass = hass; - this.rtl = this._isRTL()? "rtl" : "ltr"; - - if(hass && this._config) { - this.stateObj = this._config.entity in hass.states ? hass.states[this._config.entity] : null; - if(this.stateObj) { - this.min = this._config.min || this.controller.min(this.stateObj); - this.max = this._config.max || this.controller.max(this.stateObj); - this.step = this._config.step || this.controller.step(this.stateObj); - this.value = this.controller.get(this.stateObj); - this.displaySlider = this.controller.supported.slider(this.stateObj); + .state { + min-width: 45px; + text-align: end; } - } - } - - _isRTL() { - if(!this._hass) return false; - const lang = this._hass.language || "en"; - if(!this._hass.translationMetadata.translations[lang]) return false; - const retval = this._hass.translationMetadata.translations[lang].isRTL || false; - return retval; - } - - selectedValue(ev) { - this.controller.set(this.stateObj, parseInt(this.value, 10)); - } - - stopPropagation(ev) { - ev.stopPropagation(); - } -} - -customElements.define('slider-entity-row', SliderEntityRow); + ha-entity-toggle { + margin-left: 8px; + } + ha-slider.full { + width: 100%; + } + `}})}]); \ No newline at end of file diff --git a/src/climate-controller.js b/src/climate-controller.js new file mode 100644 index 0000000..4179236 --- /dev/null +++ b/src/climate-controller.js @@ -0,0 +1,35 @@ +import {Controller} from "./controller.js"; + +export class ClimateController extends Controller { + + get _value() { + return this.stateObj.attributes.temperature; + } + + set _value(value) { + this._hass.callService("climate", "set_temperature", { + entity_id: this.stateObj.entity_id, + temperature: value, + }); + } + + get string() { + if (this.stateObj.attributes.operation_mode === "off") + return this._hass.localize("state.climate.off"); + return `${this.value} ${this._hass.config.unit_system.temperature}`; + } + + get isOff() { + return this.stateObj.attributes.operation_mode === "off"; + } + + get _min() { + return this.stateObj.attributes.min_temp; + } + get _max() { + return this.stateObj.attributes.max_temp; + } + get _step() { + return 1; + } +} diff --git a/src/controller.js b/src/controller.js new file mode 100644 index 0000000..3a9b31d --- /dev/null +++ b/src/controller.js @@ -0,0 +1,59 @@ +export class Controller { + constructor(config) { + this._config = config; + } + + set hass(hass) { + this._hass = hass; + this.stateObj = this._config.entity in hass.states ? hass.states[this._config.entity] : null; + } + + get value() { + if(this._value) + return Math.round(this._value/this.step)*this.step; + return 0; + } + set value(value) { + if (value !== this.value) + this._value = value; + } + + get string() { + return `${this.value}`; + } + get hidden() { + return false; + } + get hasSlider() { + return true; + } + get hasToggle() { + return true; + } + + get isOff() { + return this.value === 0; + } + + get min() { + if (this._config.min !== undefined) + return this._config.min; + if (this._min !== undefined) + return this._min; + return 0; + } + get max() { + if (this._config.max !== undefined) + return this._config.max; + if (this._max !== undefined) + return this._max; + return 100; + } + get step() { + if (this._config.step !== undefined) + return this._config.step; + if (this._step !== undefined) + return this._step; + return 5; + } +} diff --git a/src/cover-controller.js b/src/cover-controller.js new file mode 100644 index 0000000..5d204e2 --- /dev/null +++ b/src/cover-controller.js @@ -0,0 +1,40 @@ +import {Controller} from "./controller.js"; + +export class CoverController extends Controller { + + get _value() { + return this.stateObj.state === "open" + ? this.stateObj.attributes.current_position + : 0; + } + + set _value(value) { + this._hass.callService("cover", "set_cover_position", { + entity_id: this.stateObj.entity_id, + position: value, + }); + } + + get string() { + if (!this.hasSlider) + return ""; + if (this.stateObj.state === "closed") + return this._hass.localize("state.cover.closed"); + return `${this.value} %` + } + + get hasToggle() { + return false; + } + + get hasSlider() { + if ("current_position" in this.stateObj.attributes) return true; + if (("supported_features" in this.stateObj.attributes) && + (this.stateObj.attributes.supported_features & 4)) return true; + return false; + } + + get _step() { + return 10; + } +} diff --git a/src/fan-controller.js b/src/fan-controller.js new file mode 100644 index 0000000..05841a5 --- /dev/null +++ b/src/fan-controller.js @@ -0,0 +1,43 @@ +import {Controller} from "./controller.js"; + +export class FanController extends Controller { + + get _value() { + return (this.stateObj.state !== "off") + ? this.stateObj.attributes.speed_list.indexOf(this.stateObj.attributes.speed) + : 0; + } + + set _value(value) { + if (value in this.stateObj.attributes.speed_list) { + this._hass.callService("fan", "turn_on", { + entity_id: this.stateObj.entity_id, + speed: this.stateObj.attributes.speed_list[value], + }); + } else { + this._hass.callService("fan", "turn_off", { + entity_id: this.stateObj.entity_id, + }); + } + } + + get string() { + if (this.stateObj.state === "off") + return this._hass.localize("state.default.off"); + return this.stateObj.attributes.speed; + } + + get hasSlider() { + if ("speed" in this.stateObj.attributes) return true; + return false; + } + + get _max() { + return this.stateObj.attributes.speed_list.length -1; + } + + get _step() { + return 1; + } + +} diff --git a/src/input-number-controller.js b/src/input-number-controller.js new file mode 100644 index 0000000..0fd52a6 --- /dev/null +++ b/src/input-number-controller.js @@ -0,0 +1,44 @@ +import {Controller} from "./controller.js"; + +export class InputNumberController extends Controller { + + get _value() { + return this.stateObj.state; + } + + set _value(value) { + this._hass.callService("input_number", "set_value", { + entity_id: this.stateObj.entity_id, + value: value, + }); + } + + get string() { + return `${Math.round(this.stateObj.state)}` + } + + get isOff() { + return false; + } + + get hasToggle() { + return false; + } + + get hasSlider() { + return this.stateObj.attributes.mode === "slider"; + } + + get _min() { + return this.stateObj.attributes.min; + } + + get _max() { + return this.stateObj.attributes.max; + } + + get _step() { + return this.stateObj.attributes.step; + } + +} diff --git a/src/input-select-controller.js b/src/input-select-controller.js new file mode 100644 index 0000000..a066154 --- /dev/null +++ b/src/input-select-controller.js @@ -0,0 +1,41 @@ +import {Controller} from "./controller.js"; + +export class InputSelectController extends Controller { + + get _value() { + return this.stateObj.attributes.options.indexOf(this.stateObj.state); + } + + set _value(value) { + if (value in this.stateObj.attributes.options) + this._hass.callService("input_select", "select_option", { + entity_id: this.stateObj.entity_id, + option: this.stateObj.attributes.options[value], + }); + } + + get string() { + return this.stateObj.state; + } + + get isOff() { + return false; + } + + get hasToggle() { + return false; + } + + get hasSlider() { + return this.stateObj.attributes.options && this.stateObj.attributes.options.length > 0 + } + + get _max() { + return this.stateObj.attributes.options.length - 1; + } + + get _step() { + return 1; + } + +} diff --git a/src/light-controller.js b/src/light-controller.js new file mode 100644 index 0000000..d410632 --- /dev/null +++ b/src/light-controller.js @@ -0,0 +1,37 @@ +import {Controller} from "./controller.js"; + +export class LightController extends Controller { + + get _value() { + return (this.stateObj.state === "on") + ? Math.ceil(this.stateObj.attributes.brightness*100.0/255) + : 0; + } + + set _value(value) { + value = Math.ceil(value/100.0*255); + if (value) { + this._hass.callService("light", "turn_on", { + entity_id: this.stateObj.entity_id, + brightness: value, + }); + } else { + this._hass.callService("light", "turn_off", { + entity_id: this.stateObj.entity_id, + }); + } + } + + get string() { + if (this.stateObj.state === "off") + return this._hass.localize("state.default.off"); + return `${this.value} %`; + } + + get hasSlider() { + if ("brightness" in this.stateObj.attributes) return true; + if (("supported_features" in this.stateObj.attributes) && + (this.stateObj.attributes.supported_features & 1)) return true; + return false; + } +} diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..881748a --- /dev/null +++ b/src/main.js @@ -0,0 +1,119 @@ +import {LitElement, html, css} from "/card-tools/lit-element.js"; + +import { Controller } from "./controller.js"; +import { LightController } from "./light-controller.js"; +import { MediaPlayerController } from "./media-player-controller.js"; +import { ClimateController } from "./climate-controller.js"; +import { CoverController } from "./cover-controller.js"; +import { FanController } from "./fan-controller.js"; +import { InputNumberController } from "./input-number-controller.js"; +import { InputSelectController } from "./input-select-controller.js"; + +const controllers = { + light: LightController, + media_player: MediaPlayerController, + climate: ClimateController, + cover: CoverController, + fan: FanController, + input_number: InputNumberController, + input_select: InputSelectController, +}; + +class SliderEntityRow extends LitElement { + static get properties() { + return { + hass: {}, + }; + } + + setConfig(config) { + this._config = config; + const domain = config.entity.split('.')[0]; + const ctrlClass = controllers[domain]; + if(!ctrlClass) + throw new Error(`Unsupported entity type: ${domain}`); + this.ctrl = new ctrlClass(config); + } + + render() { + const c = this.ctrl; + c.hass = this.hass; + const slider = html` + c.value = this.shadowRoot.querySelector("ha-slider").value} + class=${this._config.full_row ? "full" : ""} + > + `; + const toggle = html` + + `; + + const content = html` +
ev.stopPropagation()}> + ${(c.stateObj.state === "unavailable") + ? html` + + unavailable + + ` + : html` + ${((this._config.hide_when_off && c.isOff) + || !c.hasSlider) + ? "" + : slider } + ${(this._config.hide_state || this._config.toggle) + ? "" + : html` + + ${c.string} + + `} + ${this._config.toggle + && c.hasToggle + ? toggle + : ""} + `} +
+ `; + + if (this._config.full_row) + return content; + + return html` + ${content} + `; + } + + static get styles() { + return css` + .wrapper { + display: flex; + align-items: center; + height: 40px; + } + .state { + min-width: 45px; + text-align: end; + } + ha-entity-toggle { + margin-left: 8px; + } + ha-slider.full { + width: 100%; + } + `; + } +} + +customElements.define('slider-entity-row', SliderEntityRow); diff --git a/src/media-player-controller.js b/src/media-player-controller.js new file mode 100644 index 0000000..533b321 --- /dev/null +++ b/src/media-player-controller.js @@ -0,0 +1,34 @@ +import {Controller} from "./controller.js"; + +export class MediaPlayerController extends Controller { + + get _value() { + return (this.stateObj.is_volume_muted === "on") + ? 0 + : Math.ceil(this.stateObj.attributes.volume_level*100.0); + } + + set _value(value) { + value = value/100.0; + this._hass.callService("media_player", "volume_set", { + entity_id: this.stateObj.entity_id, + volume_level: value, + }); + } + + get isOff() { + return this.stateObj.state === "off"; + } + + get string() { + if (this.stateObj.attributes.is_volume_muted) + return "-"; + return !!this.stateObj.attributes.volume_level + ? `${this.value} %` + : this._hass.localize("state.media_player.off"); + } + + get hasToggle() { + return false; + } +}