Lovelace Custom Fan Card Example

Any examples of custom Lovelace state cards? Not card cards, but state cards.

Like:
https://developers.home-assistant.io/docs/en/frontend_creating_custom_ui.html

I have something working, but really it is just wrapping around the old style way of doing things. As such, things like override the name doesn’t work.

I am not compiling, so I’m not sure I can import things from HA. If I can, I don’t know the proper path. So I can’t import computeStateName, etc.

This “works” but ignores the name I pass in from ui-lovelace.yaml. Probably because state-info only gets stateObj which doesn’t have the correct name in it.

const UNAVAILABLE = 'Unavailable';

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: 0px;
                    margin-right: 0px;
                }
                ha-entity-toggle {
                    margin-left: 16px;
                }
            </style>
            <div class='horizontal justified layout'>
                <state-info state-obj="[[stateObj]]" in-dialog='[[inDialog]]'></state-info>
                <div class='horizontal justified layout' on-click="stopPropagation">
                    <paper-button
                        in-dialog='[[inDialog]]'
                        hidden$="[[inDialog]]"
                        class='speed'
                        toggles name="low"
                        on-tap='setSpeed'
                        disabled='[[isLowSpeed]]'>L</paper-button>
                    <paper-button
                        in-dialog='[[inDialog]]'
                        hidden$="[[inDialog]]"
                        class='speed'
                        toggles name="medium"
                        on-tap='setSpeed'
                        disabled='[[isMedSpeed]]'>M</paper-button>
                    <paper-button
                        in-dialog='[[inDialog]]'
                        hidden$="[[inDialog]]"
                        class='speed'
                        toggles name="high"
                        on-tap='setSpeed'
                        disabled='[[isHighSpeed]]'>H</paper-button>
                    <ha-entity-toggle state-obj='[[stateObj]]' hass='[[hass]]' in-dialog='[[inDialog]]'></ha-entity-toggle>
                </div>
            </div>
        `;
    }

    static get properties() {
        return {
            hass: {
                type: Object,
                observer: 'hassChanged'
            },
            name: String,
            inDialog: Boolean,
            config: Object,
            stateObj: Object,
            isLowSpeed: Boolean,
            isMedSpeed: Boolean,
            isHighSpeed: Boolean
        }
    }

    set hass(hass) {
    }

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

    hassChanged(hass) {

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

        // Check if this entity changed
        if (!stateObj && this.previousSpeed === UNAVAILABLE)
            return;

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

        if (speed === this.previousSpeed) {
            return;
        }

        //let name;

        //if (stateObj) {
        //    name = config.name || computeStateName(stateObj);
        //}
        //else {
        //    name = config.name || entityId;
        //}

        //console.log(name);

        this.setProperties({
            //name: name,
            stateObj: stateObj,
            previousState: speed,
            isLowSpeed: speed === 'low',
            isMedSpeed: speed === 'medium',
            isHighSpeed: speed === 'high',
        });
    }

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

    setSpeed(e) {

        const entityId = this.config.entity;
        const speed = e.currentTarget.getAttribute('name');

        if (speed === 'off') {
            this.hass.callService('fan', 'turn_off', { entity_id: entityId });
        }
        else {
            this.hass.callService('fan', 'set_speed', { entity_id: entityId, speed: speed });
        }
    }

}

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

I found that if I use hui-generic-entity-row and not state-info, I get closer to what I want. The name override works, but when I click and bring up the more info, it has undefined as the last activated time.

I’ll keep digging and post a complete example when I get it working.

Not sure I 100% understand what you’re after, but this is a great reference site for lovelace-ui:
https://home-assistant-lovelace-gallery.netlify.com/#

Update: I figured it out, most of the post below can be ignored.

I had a custom component using the state_card_custom_ui_secondary: state-card-fan-custom. It looks like what I have there, what I consider the “old way”, was the cause of the problem. Once I removed that customization, my control, source below, is working as expected.

Figured this out while waiting for a 504 gateway error to go away, anyway, what I was originally going to post…

Original Post:

I am recreating the state-info card for a fan that has l/m/h buttons on the entity card without using the custom_ui stuff (Andrey home-assistant-custom-ui)

Most examples are for the big cards, not the individual entities or state-info.

2018-09-13%2008_41_50-Home%20Assistant

I got most of it working, including respecting the name specified in the lovelace configuration file:

  - type: entities
    title: Master Bedroom
    show_header_toggle: false
    entities:
      - entity: sensor.wirelesstag_bedroom_master_temperature
        name: Temperature
      - entity: sensor.wirelesstag_bedroom_master_humidity
        name: Humidity
      - entity: fan.master_bedroom_fan
        type: custom:custom-fan-card
        name: Fan
      - entity: light.master_bedroom_light
        name: Light
      - entity: light.master_bedroom_nook
        name: Nook Light

What I don’t have, however, is the last changed time in the more info:

2018-09-13%2008_45_41-Home%20Assistant

At some point the future, I’d like to figure out how to override the controls in the more-info dialog, but that is for the future.

Here is the code I have so far for custom:custom-fan-card:

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: 0px;
                    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'
                        toggles name="low"
                        on-tap='setSpeed'
                        disabled='[[_isLowSpeed]]'>L</paper-button>
                    <paper-button
                        class='speed'
                        toggles name="medium"
                        on-tap='setSpeed'
                        disabled='[[_isMedSpeed]]'>M</paper-button>
                    <paper-button
                        class='speed'
                        toggles name="high"
                        on-tap='setSpeed'
                        disabled='[[_isHghSpeed]]'>H</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);
5 Likes

have you hosted the lovelace fan card code anywhere?

awesome card. :+1:

Screenshot_2018-10-15_12-58-29

1 Like

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

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.