Lovelace Custom Fan Card Example

Hello, I add the “off” button to the card, but seems not working. I click the “Off” button is only can trigger the on-tap event, but cannot disable the button. Anything i doing wrong. Please help!!!

image

class CustomFanCard extends Polymer.Element {

    static get template() {
        return Polymer.html`
            <style is="custom-style" include="iron-flex iron-flex-alignment"></style>
            <style>
                :host {
                    line-height: 1.5;
                }
                .speed {
                    min-width: 40px;
                    max-width: 40px;
                    margin-left: 5px;
                    margin-right: 2px;
                }
                ha-entity-toggle {
                    margin-left: 16px;
                }
            </style>
            <hui-generic-entity-row hass="[[hass]]" config="[[_config]]">
                <div class='horizontal justified layout' on-click="stopPropagation">
                
                    <paper-button
                        class='speed'
                        raised noink name="low"
                        on-tap='setSpeed'
                        disabled='[[_isLowSpeed]]'>Lo</paper-button>
                    <paper-button
                        class='speed'
                        raised noink name="medium"
                        on-tap='setSpeed'
                        disabled='[[_isMedSpeed]]'>Med</paper-button>
                    <paper-button
                        class='speed'
                        raised noink name="high"
                        on-tap='setSpeed'
                        disabled='[[_isHghSpeed]]'>Hi</paper-button>
                    <paper-button
                        class='speed'
                        raised noink name="off"
                        on-tap='turnOff'
                        disabled='[[_isOff]]'>Off</paper-button>
                        
                    <ha-entity-toggle hass="[[hass]]" state-obj="[[_stateObj]]"></ha-entity-toggle>
                    
                </div>
            </hui-generic-entity-row>
        `;
    }

    static get properties() {
        return {
            hass: {
                type: Object,
                observer: 'hassChanged'
            },
            _config: Object,
            _stateObj: Object,
            _isOff: Boolean,
            _isLowSpeed: Boolean,
            _isMedSpeed: Boolean,
            _isHghSpeed: Boolean,
        }
    }

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

    hassChanged(hass) {

        const config = this._config;
        const stateObj = hass.states[config.entity];

        let speed;
        if (stateObj && stateObj.attributes) {
            speed = stateObj.attributes.speed || 'off';
        }

        this.setProperties({
            _stateObj: stateObj,
            _isOff: speed === 'off',
            _isLowSpeed: speed === 'low',
            _isMedSpeed: speed === 'medium',
            _isHghSpeed: speed === 'high',
        });
    }

    stopPropagation(e) {
        e.stopPropagation();
    }
    
    turnOff(e) {
        const speed = e.currentTarget.getAttribute('name');    
        this.hass.callService('script', 'fan_control', {
		    mqtt_topic: 'ha/dining room fan/cmd/switch',
		    command: speed
        });
        
    }

    setSpeed(e) {
        const speed = e.currentTarget.getAttribute('name');
        this.hass.callService('script', 'fan_control', {
		    mqtt_topic: 'ha/dining room fan/cmd/level',
		    command: speed
        });
        
    }

}

customElements.define('custom-fan-card', CustomFanCard);

Managed to fix it.

class CustomFanCard extends Polymer.Element {

    static get template() {
        return Polymer.html`
            <style is="custom-style" include="iron-flex iron-flex-alignment"></style>
            <style>
                :host {
                    line-height: 1.5;
                }
                .speed {
                    min-width: 35px;
                    max-width: 35px;
                    margin-left: 5px;
                    margin-right: 0px;
                }
                ha-entity-toggle {
                    margin-left: 16px;
                }
            </style>
            <hui-generic-entity-row hass="[[hass]]" config="[[_config]]">
                <div class='horizontal justified layout' on-click="stopPropagation">
                    <paper-button
                        class='speed'
                        raised noink name="off"
                        on-tap='setSpeed'
                        disabled='[[_isOff]]'><ha-icon icon="mdi:numeric-0-box"></ha-icon></paper-button>
                    <paper-button
                        class='speed'
                        raised noink name="low"
                        on-tap='setSpeed'
                        disabled='[[_isLowSpeed]]'><ha-icon icon="mdi:numeric-1-box"></ha-icon></paper-button>
                    <paper-button
                        class='speed'
                        raised noink name="medium"
                        on-tap='setSpeed'
                        disabled='[[_isMedSpeed]]'><ha-icon icon="mdi:numeric-2-box"></ha-icon></paper-button>
                    <paper-button
                        class='speed'
                        raised noink name="high"
                        on-tap='setSpeed'
                        disabled='[[_isHghSpeed]]'><ha-icon icon="mdi:numeric-3-box"></ha-icon></paper-button>
                    <ha-entity-toggle hass="[[hass]]" state-obj="[[_stateObj]]"></ha-entity-toggle>
                </div>
            </hui-generic-entity-row>
        `;
    }

    static get properties() {
        return {
            hass: {
                type: Object,
                observer: 'hassChanged'
            },
            _config: Object,
            _stateObj: Object,
            _isOff: Boolean,
            _isLowSpeed: Boolean,
            _isMedSpeed: Boolean,
            _isHghSpeed: Boolean
        }
    }

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

    hassChanged(hass) {

        const config = this._config;
        const stateObj = hass.states[config.entity];

        let speed;
        if (stateObj && stateObj.attributes) {
            speed = stateObj.attributes.speed || 'off';
        }

        this.setProperties({
            _stateObj: stateObj,
            _isOff: stateObj.state  === 'off',
            _isLowSpeed: speed === 'low' && stateObj.state === 'on',
            _isMedSpeed: speed === 'medium' && stateObj.state === 'on',
            _isHghSpeed: speed == 'high' && stateObj.state === 'on'
        });

    stopPropagation(e) {
        e.stopPropagation();
    }

    setSpeed(e) {
        const speed = e.currentTarget.getAttribute('name');
        this.hass.callService('fan', 'set_speed', {
            entity_id: this._config.entity, speed: speed
        });
    }

}

customElements.define('custom-fan-card', CustomFanCard);
1 Like

Yes, work now. Thanks a lot!!

1 Like

I’m fairly close to making this work just like the other state_card_custom_fanspeed interface did but I’m stuck on two things:

  1. Does anyone know how the syntax to use logical operators to set the state of a boolean in the code? I’ve tried “_boolean3: (_boolean1) && (_boolean2)” which gives errors. I tried it without the (). I’ve tried with {}. I’ve tried with {()}. etc…

  2. what is the way to set the background color if the button dynamically with the state of a boolean?

I’ve done searches for every iteration of those search terms I can think of and can’t find the answers. Everything I’ve found has been very basic or unrelated to the questions I have. Comprehensive Polymer documentation is pretty hard to find.

Is there a reason the card is shown with no border or box on my UI?

3 Likes

If anyone is interested I’ve been working on tweaking this card to make it look and act like the modifications I made to the state-card-custom-fanspeed from https://community.home-assistant.io/t/fan-control-set-speed-working-quick-setup/57520.

The only things different from that are the color for the “off” state and the button order. But if you want it the other way then it’s trivial to make the change.

Here are the screenshots with a dark theme and with the default theme:

ex

ex2

ex3

ex4

Here is the code:

class CustomFanCard extends Polymer.Element {

    static get template() {
        return Polymer.html`
            <style is="custom-style" include="iron-flex iron-flex-alignment"></style>
            <style>
                :host {
                    line-height: 1.5;
                }
                .speed {
                    min-width: 30px;
                    max-width: 30px;
					margin-left: 2px;
                    margin-right: 2px;
    	            //background-color:#546E7A;
					background-color:#759aaa;
	                border: 1px solid lightgrey; 
	                font-size: 10px !important;
	                float: right !important;   
                }
               </style>
            <hui-generic-entity-row hass="[[hass]]" config="[[_config]]">
                <div class='horizontal justified layout' on-click="stopPropagation">
                    <paper-button
                        class='speed'
						style='[[_lowOnColor]]'
                        toggles name="low"
                        on-tap='setSpeed'
                        disabled='[[_isOnLow]]'>LOW</paper-button>
                    <paper-button
                        class='speed'
						style='[[_medOnColor]]'
                        toggles name="medium"
                        on-tap='setSpeed'
                        disabled='[[_isOnMed]]'>MED</paper-button>
                    <paper-button
                        class='speed'
						style='[[_highOnColor]]'
                        toggles name="high"
                        on-tap='setSpeed'
                        disabled='[[_isOnHigh]]'>HIGH</paper-button>
				    <paper-button
                        class='speed'
						style='[[_offColor]]'
                        toggles name="off"
                        on-tap='setSpeed'
                        disabled='[[_isOffState]]'>OFF</paper-button>
                </div>
            </hui-generic-entity-row>
        `;
    }

    static get properties() {
        return {
            hass: {
                type: Object,
                observer: 'hassChanged'
            },
            _config: Object,
            _stateObj: Object,
			_lowOnColor: String,
			_medOnColor: String,
			_highOnColor: String,
			_offColor: String,
            _isOffState: Boolean,
            _isOnState: Boolean,
            _isOnLow: Boolean,
            _isOnMed: Boolean,
            _isOnHigh: Boolean
        }
    }

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

    hassChanged(hass) {

        const config = this._config;
        const stateObj = hass.states[config.entity];

        let speed;
        if (stateObj && stateObj.attributes) {
            speed = stateObj.attributes.speed || 'off';
        }
		
		let low;
		let med;
		let high;
		let offstate;
		
		if (stateObj && stateObj.attributes) {
		    if (stateObj.state == 'on' && stateObj.attributes.speed == 'low') {
			    low = 'on';
		    } else if (stateObj.state == 'on' && stateObj.attributes.speed == 'medium') {
			    med = 'on';
		    } else if (stateObj.state == 'on' && stateObj.attributes.speed == 'high') {
			    high = 'on';
		    } else {
				offstate = 'on';
			}
		}
		
        let lowcolor;
		let medcolor;
		let hicolor;
		let offcolor;
		
		if (low == 'on') {
			lowcolor = 'background-color: #43A047';
		} else {
			lowcolor = '';
		}
		
		if (med == 'on') {
			medcolor = 'background-color: #43A047';
		} else {
			medcolor = '';
		}
		
		if (high == 'on') {
			hicolor = 'background-color: #43A047';
		} else {
			hicolor = '';
		}
		
		if (offstate == 'on') {
			//offcolor = 'background-color: #43A047';
			offcolor = 'background-color: #f44c09';
		} else {
			offcolor = '';
		}
		
		this.setProperties({
            _stateObj: stateObj,
			_isOffState: stateObj.state == 'off',
            _isOnLow: low === 'on',
			_isOnMed: med === 'on',
			_isOnHigh: high === 'on',
			_lowOnColor: lowcolor,
			_medOnColor: medcolor,
			_highOnColor: hicolor,
			_offColor: offcolor
        });
    }

    stopPropagation(e) {
        e.stopPropagation();
    }

    setSpeed(e) {
        const speed = e.currentTarget.getAttribute('name');
        this.hass.callService('fan', 'set_speed', {
            entity_id: this._config.entity, speed: speed
        });
    }

}

customElements.define('custom-fan-card', CustomFanCard);

the configuration is the same as the original card:

- entity: fan.master_bedroom_fan
  type: custom:custom-fan-card
  name: MBR Fan

If you don’t like the colors you can simply go into the code and change the hex values for the “background-color:”.

And @rhodges since this thread has transformed into a thread specific to a custom fan card I’m changing the title of the thread to reflect that. I hope that’s OK. It will help more people find it if that’s the kind of information they need.

And thanks for getting the ball rolling on this. I’m OK at modifying cards (after digging around trying to find the info I need!) but there’s no way I would have known how to even get started without your initial work.

6 Likes

Thanks a lot finity I waited for someone to do the conversion to Lovelace.
Great Job!

Thank you! :slightly_smiling_face:

Hello
I like your fan card wery much. Great job.
But I can’t make it work
Can you please write step by step instruction how to implement in lovelace
Thanks

Thanks a bunch for this; it was the last holdback from the custom_ui days in my UI. Now Lovelace has replaced it all completely with feature parity (or better).

Thanks a bunch for this!

I may modify it to use for some lights to give logical dimming levels like 25%, 50%, 75%, 100% to make it easier on a small iPhone versus dragging a slider.

1 Like

First you need to copy the entire contents of the code above and store it in a file called “custom-fan-card.js” and locate that file in your www directory in your config directory.

then in your lovelace config you need to add the card in the “resources” section. How you do that depends on the mode of lovelace config you use. I use the yaml mode so my entry there looks like this:

resources:
  - url: /local/custom-fan-card.js
    type: js

then in your lovelace main config section you need to add the card config wherever you want to show it.

title: Home Assistant
views:
  title: Main
  cards:
    - type: entities
      title: Master Bedroom
      show_header_toggle: false
      entities:
        - entity: fan.master_bedroom_fan
          type: custom:custom-fan-card
          name: MBR Fan

You will obviously need to have a fan configured first and you will change the entity_id above to your fan id.

Does the above work with the raw conifg editor on .87?

I keep getting Entity not available config.entity Any help?

Thanks

I got it.

type: entities
title: Master Bedroom
show_header_toggle: false
entities:
  - entity: fan.sonoff_fan_1
    type: 'custom:custom-fan-card'
    name:Fan

Got message: Your config is not supported by the UI editor:
Expected a value of type {entity,name,icon} | entity-id for entities.0.type but received "custom:custom-fan-card".
Falling back to YAML editor.

you need to post your code properly formatted for the forum.

But at first glance trying to see thru the bad formatting it looks like in the “name:” section you don’t have a space after it and before “Fan”.

Thank you. Sorry for the bad code entry. I was excited that I got it to work. I know…lame excuse, but hey I learned something today!!! :slight_smile:

Ok, so you are saying that it is working for you?

ok finity thanks for the help.
Now I get to work this to.
But I only copy the code in to \hassio\config\www\custom_ui in costum-fan-card.js file
add this to fans.yaml

  - platform: mqtt  
    name: "Delavnica Ventilator"
    command_topic: "cmnd/delavnica_ventilator/FanSpeed"
    speed_command_topic: "cmnd/delavnica_ventilator/FanSpeed"    
    state_topic: "stat/delavnica_ventilator/RESULT"
    speed_state_topic: "stat/delavnica_ventilator/RESULT"
    #state_value_template: "{% if value_json.FanSpeed == 0 -%}0{%- elif value_json.FanSpeed > 0 -%}4{%- endif %}"
    state_value_template: >
      {% if value_json.FanSpeed is defined %}
        {% if value_json.FanSpeed == 0 -%}0{%- elif value_json.FanSpeed > 0 -%}4{%- endif %}
      {% else %}
        {% if states.delavnica_ventilator.state == 'off' -%}0{%- elif states.delavnica_ventilator.state == 'on' -%}4{%- endif %}
      {% endif %}
    speed_value_template: "{{ value_json.FanSpeed }}"
    #speed_value_template: "{% if value_json.FanSpeed == 0 -%}off{%- elif value_json.FanSpeed > 0 -%}{{ value_json.FanSpeed }}{%- endif %}"
    availability_topic: tele/delavnica_ventilator/LWT
    payload_off: "0"
    payload_on: "4"
    payload_low_speed: "1"
    payload_medium_speed: "2"
    payload_high_speed: "3"
    payload_available: Online
    payload_not_available: Offline
    speeds:
      - off
      - low
      - medium
      - high

and this in to lovelace entity card

  - entity: fan.delavnica_ventilator
    name: Ventilator
    type: 'custom:custom-fan-card'

and it works great.
I hope that can help some one in the future.

This needs to be updated to support the mwc-button if upgrading to 0.88

Note for Lovelace custom card developers: if you relied on the availability of <paper-button> in your code, you will have to update it to <mwc-button> to get a similar component.

1 Like

Is that just a search/replace or more involved?

Mostly. Also replace any on-tap attribute of the button with on-click. More changes might be needed.

Replace paper-button with mwc-bottom and on-tap with on-click will get you working. The style will be different, so more changes will need to be made to get it looking exactly like it was before, if that is what you want.

1 Like