Lovelace Custom Fan Card Example

IMHO that is a great reason to utilize something like the custom updater. You never know what’s going to happen, especially with a developing platform like Home Assistant.

For people for whom HA isn’t their primary hobby it does make keeping track of things much simpler! Heck even my utterly technically illiterate wife can handle updating everything when it’s a one click solution. :joy:

I see what you’re saying. Maybe I’ll have to look into that too.

If you want to use @finity’s version with your theme colours try the code below. Credit to @undertoe. I’ve made the font and buttons slightly bigger as I prefer how it looks. I’ve also moved the “off” button to the start of the row.

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: inherit;
                }
                .speed {
                    min-width: 34px;
                    max-width: 34px;
                    height: 34px;
                    margin-left: 2px;
                    margin-right: 2px;
                    background-color:'var(--dark-accent-color)';
	                border: 1px var(--dark-theme-disabled-color);  
                    border-radius: 4px;
	                font-size: 11px !important;
                    text-align: center;
	                float: right !important;
                    padding: 1px;
                    font-family : inherit;
		    }
				
            </style>
            <hui-generic-entity-row hass="[[hass]]" config="[[_config]]">
                <div class='horizontal justified layout' on-click="stopPropagation">
                    <button
                            class='speed'
                            style='[[_offColor]]'
                            toggles name="off"
                            on-click='setSpeed'
                            disabled='[[_isOffState]]'>OFF</button>
				    <button
                            class='speed'
                            style='[[_lowOnColor]]'
                            toggles name="low"
                            on-click='setSpeed'
                            disabled='[[_isOnLow]]'>LOW</button>
                    <button
                            class='speed'
                            style='[[_medOnColor]]'
                            toggles name="medium"
                            on-click='setSpeed'
                            disabled='[[_isOnMed]]'>MED</button>
                    <button
                            class='speed'
                            style='[[_highOnColor]]'
                            toggles name="high"
                            on-click='setSpeed'
                            disabled='[[_isOnHigh]]'>HIGH</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: var(--dark-primary-color); color: white;';
    } else { 
        lowcolor = '';
    }

    if (med == 'on') {
        medcolor = 'background-color: var(--dark-primary-color); color: white;';
    } else {
        medcolor = '';
    }

    if (high == 'on') {
        hicolor = 'background-color: var(--dark-primary-color); color: white;';
    } else {
        hicolor = '';
    }
	
    if (offstate == 'on') {
        offcolor = 'background-color: var(--dark-primary-color); color: white;';
    } 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);

image
image

4 Likes

@slipx06 @undertoe

Finally had a chance to try this out. Exactly the behaviour I was hoping for. Thanks so much!

I just tried to convert an old non-smart fan that I’ve been controlling with a broadlink RM Pro from HA to a real fan component and I realized that the way my component works it would give an error on trying to turn that type of fan off.

I made a modification to my fan control and since it’s becoming kind of its own entity at this point I decided to create it’s own thread so I don’t keep mucking up this one.

Here is the new thread:

Ok, so…

I made a few more modifications to the component. The theme is now pretty much fully customizable (within the parameters of a standard 3 speed fan).

See my other thread for details if you’re interested.

Fan Control Entity Row

I’ve set everything up as per @slipx06 last code, when I click “LOW” “MED” or “HIGH” the fan goes to that setting, but the “OFF” remains highlighted in blue, and I cannot click “OFF” to turn off the fan. I can still change between speeds. Any reason why the card isn’t coming off of “OFF” in HA?

switches

ui-lovelace.yaml:

  - type: entities
    title: Switches
    show_header_toggle: false
    entities:
      - entity: switch.living_room_spot_lights
        secondary_info: last-changed
        icon: mdi:lightbulb        
      - entity: switch.fireplace_spot_lights
        secondary_info: last-changed
        icon: mdi:lightbulb
      - entity: switch.dining_room_plug
        name: "Dining Room Lamps"
        secondary_info: last-changed
        icon: mdi:lightbulb
      - entity: switch.living_room_fan_light
        name: "Living Room Fan Light"
        secondary_info: last-changed
        icon: mdi:lightbulb
      - entity: fan.living_room_fan
        name: "Living Room Fan"
        type: custom:custom-fan-card
        secondary_info: last-changed
        icon: mdi:fan 

configuration.yaml:

fan:

  • platform: mqtt
    name: “Living Room Fan”
    command_topic: “cmnd/sonoff_ifan02_1/FanSpeed”
    speed_command_topic: “cmnd/sonoff_ifan02_1/FanSpeed”
    state_topic: “stat/sonoff_ifan02_1/RESULT”
    speed_state_topic: “stat/sonoff_ifan02_1/RESULT”
    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.fan.living_room_fan.state == ‘off’ -%}0{%- elif states.fan.living_room_fan.state == ‘on’ -%}4{%- endif %}
    {% endif %}
    speed_value_template: “{{ value_json.FanSpeed }}”
    availability_topic: tele/sonoff_ifan02_1/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

custom-fan-card.js

class CustomFanCard extends Polymer.Element {

static get template() {
return Polymer.html`


:host {
line-height: inherit;
}
.speed {
min-width: 34px;
max-width: 34px;
height: 34px;
margin-left: 2px;
margin-right: 2px;
background-color:‘var(–dark-accent-color)’;
border: 1px var(–dark-theme-disabled-color);
border-radius: 4px;
font-size: 11px !important;
text-align: center;
float: right !important;
padding: 1px;
font-family : inherit;
}

      </style>
      <hui-generic-entity-row hass="[[hass]]" config="[[_config]]">
          <div class='horizontal justified layout' on-click="stopPropagation">
              <button
                      class='speed'
                      style='[[_offColor]]'
                      toggles name="off"
                      on-click='setSpeed'
                      disabled='[[_isOffState]]'>OFF</button>
              <button
                      class='speed'
                      style='[[_lowOnColor]]'
                      toggles name="low"
                      on-click='setSpeed'
                      disabled='[[_isOnLow]]'>LOW</button>
              <button
                      class='speed'
                      style='[[_medOnColor]]'
                      toggles name="medium"
                      on-click='setSpeed'
                      disabled='[[_isOnMed]]'>MED</button>
              <button
                      class='speed'
                      style='[[_highOnColor]]'
                      toggles name="high"
                      on-click='setSpeed'
                      disabled='[[_isOnHigh]]'>HIGH</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: var(–dark-primary-color); color: white;’;
} else {
lowcolor = ‘’;
}

if (med == ‘on’) {
medcolor = ‘background-color: var(–dark-primary-color); color: white;’;
} else {
medcolor = ‘’;
}

if (high == ‘on’) {
hicolor = ‘background-color: var(–dark-primary-color); color: white;’;
} else {
hicolor = ‘’;
}

if (offstate == ‘on’) {
offcolor = ‘background-color: var(–dark-primary-color); color: white;’;
} 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);

If I remember correctly I ran into some kind of issue using that code but I don’t know if that was it or not. But I’ve modified the code here and made it pretty customizable and it works really good.

If you’re interested see my post up above for a link to the thread about my control row which has the link to the code on github.

@finity Can you confirm whether your fan card works with 0.92. I’m fairly certain I have it set up correctly but I keep getting the dreaded Red Box of No Worky. Below is the code I have in my ui-lovelace.yaml. The card was RAW downloaded from github using wget and stored in the www/ folder.

resources
- url: /local/fan-control-entity-row.js
  type: js 

- type: entities
  title: Fans
  show_header_toggle: false
  entities:
  - entity: fan.master_bedroom_fan
    type: custom:fan-contol-entity-row
    name: Master Bedroom
    customTheme: false

i haven’t updated to v92 yet and likely won’t for a few days until the dust settles on the new release.

ill let you know what i find then.

did you have it working before on an older version and now its not or is this the first version you tried it on?

This is the first time. I hadn’t updated since 0.88 so I was still running old fan button code.

ok. i’ll try to remember to let you know what i find when i update.

Used your yaml code for the fan (with off in quotes and not in quotes [I noticed when not in quotes its blue instead of orange]) and the json code on your link https://community.home-assistant.io/t/lovelace-fan-control-entity-row-split-from-the-other-topics/102952/3. Still same issue, the OFF button stays highlighted and is not selectable in HA. The HI, MED, and LOW buttons all work correctly.

Funny thing, is I also have HomeKit installed and it works correctly on my iPhone and iPad (turns on and off). So just HA/Lovelace is the issue.

I’m going to answer you in my other thread to prevent from jumbling up this thread any more with off (this) topic posts…

And I’ll do the same for @daphatty as well…

thank you!

it’s usefull code!

i’m customize to Xiaomi Airpurifier
silent / auto / favorite & on/off toggle

%EC%9D%B4%EB%AF%B8%EC%A7%80%203

Looks nice ! Is that possible to have your code about this Air Purifier ? thanks!

For my Xiaomi Airpurifier 2S code here.
save file name : custom-fan-card-xiaomiair.js
pickpick_1

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: inherit;
            }
            .speed {
                min-width: 34px;
                max-width: 34px;
                height: 34px;
                margin-left: 2px;
                margin-right: 2px;
                background-color:'var(--dark-accent-color)';
	                border: 1px var(--dark-theme-disabled-color);  
                border-radius: 4px;
	                font-size: 11px !important;
                text-align: center;
	                float: right !important;
                padding: 1px;
                font-family : inherit;
		    }
				
        </style>
        <hui-generic-entity-row hass="[[hass]]" config="[[_config]]">
            <div class='horizontal justified layout' on-click="stopPropagation">
				    <button
                        class='speed'
                        style='[[_silOnColor]]'
                        toggles name="silent"
                        on-click='setSpeed'
                        disabled='[[_isOnSil]]'><ha-icon icon="mdi:power-sleep"></ha-icon></button>
                <button
                        class='speed'
                        style='[[_autoOnColor]]'
                        toggles name="auto"
                        on-click='setSpeed'
                        disabled='[[_isOnAuto]]'><ha-icon icon="mdi:brightness-auto"></ha-icon></button>
                <button
                        class='speed'
                        style='[[_favoOnColor]]'
                        toggles name="favorite"
                        on-click='setSpeed'
                        disabled='[[_isOnFavo]]'><ha-icon icon="mdi:fan"></ha-icon></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,
        _silOnColor: String,
        _autoOnColor: String,
        _favoOnColor: String,
        _offColor: String,
        _isOffState: Boolean,
        _isOnState: Boolean,
        _isOnSil: Boolean,
        _isOnAuto: Boolean,
        _isOnFavo: Boolean
    }
}

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

hassChanged(hass) {

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

    let speed;
    if (stateObj && stateObj) {
        speed = stateObj.state || 'off';
    }
		
    let sil;
	    let auto;
	    let favo;
	    let offstate;
		
	    if (stateObj && stateObj.attributes) {
	        if (stateObj.state == 'on' && stateObj.attributes.speed == 'Silent') {
		        sil = 'on';
		} else if (stateObj.state == 'on' && stateObj.attributes.speed == 'Auto') {
		        auto = 'on';
		} else if (stateObj.state == 'on' && stateObj.attributes.speed == 'Favorite') {
		        favo = 'on';
		} else {
			offstate = 'on';
		}
	}
		
    let silcolor;
    let autocolor;
	    let favocolor;
	    let offcolor;
		
	   if (sil == 'on') {
    silcolor = 'background-color: var(--dark-primary-color); color: white;';
} else { 
    silcolor = '';
}

if (auto == 'on') {
    autocolor = 'background-color: var(--dark-primary-color); color: white;';
} else {
    autocolor = '';
}

if (favo == 'on') {
    favocolor = 'background-color: var(--dark-primary-color); color: white;';
} else {
    favocolor = '';
}
	
if (offstate == 'on') {
    offcolor = 'background-color: var(--dark-primary-color); color: white;';
} else {
    offcolor = '';
}
		
	this.setProperties({
    _stateObj: stateObj,
	    _isOffState: stateObj.state == 'off',
    _isOnSil: sil === 'on',
	    _isOnAuto: auto === 'on',
	    _isOnFavo: favo === 'on',
	    _silOnColor: silcolor,
	    _autoOnColor: autocolor,
	    _favoOnColor: favocolor,
	    _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-xiaomiair', CustomFanCard);
2 Likes

Thanks I will try!

do u mind sharing how u got it done? I have 6 speed on my fan too and would like to achieve the same result

I have this

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">
                    <mwc-button
                        class='speed'
                        toggles name="1"
                        on-click='setSpeed'
                        disabled='[[_is1Speed]]'>1</mwc-button>
                    <mwc-button
                        class='speed'
                        toggles name="2"
                        on-click='setSpeed'
                        disabled='[[_is2Speed]]'>2</mwc-button>
                    <mwc-button
                        class='speed'
                        toggles name="3"
                        on-click='setSpeed'
                        disabled='[[_is3Speed]]'>3</mwc-button>
                    <mwc-button
                        class='speed'
                        toggles name="4"
                        on-click='setSpeed'
                        disabled='[[_is4Speed]]'>4</mwc-button>
                    <mwc-button
                        class='speed'
                        toggles name="5"
                        on-click='setSpeed'
                        disabled='[[_is5Speed]]'>5</mwc-button>
                    <mwc-button
                        class='speed'
                        toggles name="6"
                        on-click='setSpeed'
                        disabled='[[_is6Speed]]'>6</mwc-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,
            _is1Speed: Boolean,
            _is2Speed: Boolean,
            _is3Speed: Boolean,
            _is4Speed: Boolean,
            _is5Speed: Boolean,
            _is6Speed: 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,
            _is1Speed: speed === '1',
            _is2Speed: speed === '2',
            _is3Speed: speed == '3',
            _is4Speed: speed == '4',
            _is5Speed: speed == '5',
            _is6Speed: speed == '6'
        });
    }

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