Lovelace Custom Fan Card Example

could you share how you got this working?

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

not sure what i’m missing (created the file above using the code shown along with the card referenced):

  - title: Upstairs
    icon: mdi:arrow-up-bold
    id: upstairs
    cards:
      - type: vertical-stack
        cards:
          - type: entities
            show_header_toggle: false
            entities:
              - entity: fan.bedroom_fan
                type: custom:custom-fan-card
                name: Bedroom Fan

not sure what else to try? thanks in advance for any help :smiley:

Looks ok to me.
I used the last src code he published. Also change type js to type: module

After that, in chrome press f12, go to refresh icon right click “Empty cache and hard reload”

3 Likes

I haven’t. I might create a repo and post things as I play with them. I’ll let the thread know. What I’m running now is here in this thread so I haven’t bothered with repo yet.

Hello, can you explain a little bit more of what you actually did to make this work?

Awesome card.

3 Likes

Please share your code. This on lovelace?

Sure. I used @rhodges’s code with some modify. This is my custom:

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="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,
            _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,
            _isLowSpeed: speed === 'low',
            _isMedSpeed: speed === 'medium',
            _isHghSpeed: speed == 'high'
        });
    }

    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);
3 Likes

Big thanks for posting this custom card code, I use lovelace for a control panel and even thought it’s a 10inch tablet the icons are to small, this is perfect to make my icons bigger but keep the interface compact.

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: 85px;
                margin-left: 5px;
                margin-right: 0px;
                padding: 0!important;
            }
            ha-entity-toggle {
                margin-left: 16px;
            }
            .large-icon{
                width:65px!important;
                height:65px!important;
            }
        </style>
        <hui-generic-entity-row hass="[[hass]]" config="[[_config]]">
            <div class='horizontal justified layout' on-click="stopPropagation">
                <paper-button
                    class='speed'
                    raised noink name="up"
                    on-tap='setSpeed'
                    disabled='[[_isLowSpeed]]'><ha-icon class="large-icon" icon="mdi:arrow-up-thick"></ha-icon></paper-button>
                <paper-button
                    class='speed'
                    raised noink name="stop"
                    on-tap='setSpeed'
                    disabled='[[_isMedSpeed]]'><ha-icon class="large-icon" icon="mdi:stop"></ha-icon></paper-button>
                <paper-button
                    class='speed'
                    raised noink name="down"
                    on-tap='setSpeed'
                    disabled='[[_isHghSpeed]]'><ha-icon class="large-icon" icon="mdi:arrow-down-thick"></ha-icon></paper-button>
            </div>
        </hui-generic-entity-row>
    `;
}

static get properties() {
    return {
        hass: {
            type: Object,
            observer: 'hassChanged'
        },
        _config: Object,
        _stateObj: Object,
        _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,
        _isLowSpeed: speed === 'up',
        _isMedSpeed: speed === 'stop',
        _isHghSpeed: speed === 'down'
    });
}

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

setSpeed(e) {
    const speed = e.currentTarget.getAttribute('name');
    if (speed === 'up'){
        this.hass.callService('script', 'blind_lr_sheer_up', {
            entity_id: this._config.entity
        });
    }
    if (speed === 'stop'){
        this.hass.callService('script', 'blind_lr_sheer_stop', {
            entity_id: this._config.entity
        });
    }
    if (speed === 'down'){
        this.hass.callService('script', 'blind_lr_sheer_down', {
            entity_id: this._config.entity
        });
    }
}

}
customElements.define(‘custom-fan-card’, CustomFanCard);

love lace config

- type: custom:custom-fan-card
  entity: cover.blind_b1_block
  name: Sheer

and I get this, show comparison to normal entitiy garage cover icons

Big thanks again, I how need to modify code to handle all the different shutters, which I’ll have a go at and let you know/ask for help.

What error/issue are you getting? using type: js worked for me

1 Like

This works well but the only problem is the last fan speed stays selected when you turn the fan off . Seems like the card does not reset the state when the fan is off. You have to choose another fan speed to start it again.

A similar problem was fixed in the old interface --> Fan Control. Has anyone got this working in lovelace?

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