Custom UI with Buttons - Fan Control

I submitted this for @andrey (right user?) over at: https://github.com/andrey-git/home-assistant-custom-ui/issues/3

Would be great to expose “Low | Med | High” to the front main UI like has been done with brightness.

The user is right :slight_smile:
I don’t have a fan, so I won’t be implementing a custom UI for it.

You are welcome to send a PR however!

Bummer. No problem.

I may try to take a stab at it but don’t have any experience with Polymer so it may be scary, lol.

Note that if you want to make something simple and not user-customizable, maybe you should try to submit it as a proper component into the main polymer repo.

Do you know of any components or examples that use radio buttons in the UI Cards?

I’ve got somethings started but having trouble getting all the pieces there (state toggle) and getting the radio buttons themed and functioning.

Making progress:

2 Likes

Use paper-button for buttons. You probably need either a toggle or an “off” button.

Not sure which approach I like better (toggle or off button) but here is latest:

right now, clicking one of the buttons opens the ‘detailed card’ - I can’t seem to find where that is controlled… Any guidance?

EDIT: I think I prefer the toggle for consistency.

1 Like

```

Yeah, that part is working. Each button has an on-tap set and is updating the speed. But when clicking a button, it executes the on-tap function but also launches the “more info” or “detail” modal, here:

In the event handler call stopPropagation

func(ev) {

ev.stopPropagation()
}

1 Like

Awesome, thanks!

Any way to limit what bits show up in the “more info” modal without creating a custom version?

bits? If you mean buttons - you can use the inDialog param. It is true in more-info mode and false otherwise.

1 Like

Would you mind sharing your progress with the code ?
Thinking about quick-select buttons for my media_player,source

Sure - I attempted to follow @andrey format and my plan is to eventually add customization like has been done for the light/brightness slider. Some of the UI also isn’t as responsive as it should be… so it needs work. But I like it so far. Also of note, on a mobile device, when selecting a button the “more info” dialog still opens (doesn’t on a desktop) and the information in the dialog still needs limiting/adjusting.

here it is:

fan-model.html

<script>
(function () {
  window.FanEntity = function (hass, stateObj) {
    this.hass = hass;
    this.stateObj = stateObj;
  };

  function addGetter(name, getter) {
    Object.defineProperty(window.FanEntity.prototype, name,
                          { get: getter });
  }

  addGetter('isOff', function () {
      return this.stateObj.state === 'off';
    
  });
  
  addGetter('getSpeed', function () {
    if (this.stateObj.attributes.speed !== undefined && this.stateObj.state != 'off') {
      return this.stateObj.attributes.speed;
    }

    return 'off';
    
  });

  addGetter('isLowSpeed', function () {
    if (this.stateObj.attributes.speed !== undefined && this.stateObj.state != 'off') {
      return this.stateObj.attributes.speed === 'low';
    }
    
  });

  addGetter('isMedSpeed', function () {
    if (this.stateObj.attributes.speed !== undefined && this.stateObj.state != 'off') {
      return this.stateObj.attributes.speed === 'medium';
    }
    
  });

  addGetter('isHighSpeed', function () {
    if (this.stateObj.attributes.speed !== undefined && this.stateObj.state != 'off') {
      return this.stateObj.attributes.speed === 'high';
    }
    
  });


  /* eslint-enable no-bitwise */

    Object.assign(window.FanEntity.prototype, {
        setSpeed(speedVal, ev) {

            if (speedVal == 'off')
            {
                 this.callService('turn_off');
            }
            else
            {
                if (this.stateObj.state != 'on'){
                    this.callService('turn_on');
                }
                this.callService('set_speed', {speed: speedVal});
            }

            ev.stopPropagation();
        },


    // helper method

        callService(service, data) {
            var serviceData = data || {};
            serviceData.entity_id = this.stateObj.entity_id;
            this.hass.callService('fan', service, serviceData);
            
        },
  });
}());
</script>

state-card-with-speed.html


<link rel="import" href="fan-model.html">

<dom-module id="state-card-with-speed">
  <template>
    <style is="custom-style"></style>
    <style>
      
      .state {
        white-space: nowrap;
      }
      
      .speed {
        min-width: 0;
      }
    

      ha-entity-toggle {
        margin-left: 16px;
      }
    </style>

    <div class='state'>

        <paper-button-group 
          in-dialog='[[inDialog]]'
          selected='[[entityObj.getSpeed]]'>
            <!--<paper-button class='speed'
              toggles name="off" 
              on-tap='onOffTap'
              disabled='[[entityObj.isOff]]'>Off</paper-button>-->
            <paper-button 
              in-dialog='[[inDialog]]'
              class='speed'
              toggles name="low" 
              on-tap='onLowTap'
              disabled='[[entityObj.isLowSpeed]]'>Low</paper-button>
            <paper-button 
              in-dialog='[[inDialog]]'
              class='speed'
              toggles name="medium" 
              on-tap='onMedTap'
              disabled='[[entityObj.isMedSpeed]]'>Med</paper-button>
            <paper-button 
              in-dialog='[[inDialog]]'
              class='speed'
              toggles name="high" 
              on-tap='onHighTap'
              disabled='[[entityObj.isHighSpeed]]'>High</paper-button>
        </paper-button-group> 

    </div>

  </template>
</dom-module>

<script>
Polymer({
  is: 'state-card-with-speed',
  properties: {
    hass: {
      type: Object,
    },
    inDialog: {
      type: Boolean,
      value: false,
    },
    stateObj: {
      type: Object,
    },
    entityObj: {
      type: Object,
      computed: 'computeEntityObj(hass, stateObj)',
    },
  },
  computeEntityObj: function (hass, stateObj) {
    return new window.FanEntity(hass, stateObj);
  },
  onOffTap: function (ev) {
    this.entityObj.setSpeed('off', ev);
  },
  onLowTap: function (ev) {
    this.entityObj.setSpeed('low', ev);
  },
  onMedTap: function (ev) {
    this.entityObj.setSpeed('medium', ev);
  },
  onHighTap: function (ev) {
    this.entityObj.setSpeed('high', ev);
  },
});
</script>

state-card-custom_fan.html

<link rel="import" href="state-card-with-speed.html">

<dom-module id="state-card-custom_fan">
  <template>
    <style is="custom-style" include="iron-flex iron-flex-alignment"></style>
    <style>
      :host {
        line-height: 1.5;
      }
    </style>

    <div class='horizontal justified layout'>
      <state-info state-obj="[[stateObj]]" in-dialog='[[inDialog]]'></state-info>
      <state-card-with-speed hass="[[hass]]" state-obj="[[stateObj]]" in-dialog='[[inDialog]]'></state-card-with-speed>
      <ha-entity-toggle state-obj='[[stateObj]]' hass='[[hass]]' in-dialog='[[inDialog]]'></ha-entity-toggle>
    </div>
  </template>
</dom-module>

<script>
Polymer({
  is: 'state-card-custom_fan',

  properties: {
    hass: {
      type: Object,
    },

    inDialog: {
      type: Boolean,
      value: false,
    },

    stateObj: {
      type: Object,
    },
  },
});
</script>

and last, configuration.yaml

  homeassistant:
    customize:
 
      fan.master_bedroom_fan:
        custom_ui_state_card: custom_fan 

Which should give you:

5 Likes

thx, let me take a stab at this and see how it goes…

@kylerw, I sent a link to this thread to @balloob and he blessed the concept, so it would be great if you could make a PR agains the main repo with this UI.

@kylerw, With regards to your query on responsiveness , i don’t see any issues with the buttons here:

Here goes the code - i’ve put everything into one file.

state-card-custom_light.html

<dom-module id="state-card-custom_light">
  <template>
    <style is="custom-style" include="iron-flex iron-flex-alignment"></style>
    <style>
      :host {
        line-height: 1.5;
      }
      paper-button {
        min-width: 30px;
        height: 20px;
        margin: 10px 2px 10px 2px;
        padding: 0 0 0 0;
      }
    </style>

<div class='horizontal justified layout'>
  <state-info state-obj="[[stateObj]]"></state-info>
  <paper-button-group>
      <paper-button style="background-color:#74e7ff" on-tap="btntap1"></paper-button>
      <paper-button style="background-color:#74ffc2" on-tap="btntap2"></paper-button>
      <paper-button style="background-color:#ffd574" on-tap="btntap3"></paper-button>
      <paper-button style="background-color:#eaeae1" on-tap="btntap4"></paper-button>
  </paper-button-group>
  <ha-entity-toggle state-obj="[[stateObj]]" hass="[[hass]]"></ha-entity-toggle>
</div>
</template>
</dom-module>
<script>
        Polymer({
          is: 'state-card-custom_light',
          properties: {
            hass: {
              type: Object,
            },
            stateObj: {
              type: Object,
            },
          },

          btntap1: function (ev) {
            this.setColor(116, 231, 255, 200, ev);
          },
          btntap2: function (ev) {
            this.setColor(116, 255, 194, 200, ev);
          },
          btntap3: function (ev) {
            this.setColor(255, 213, 116, 200, ev);
          },
          btntap4: function (ev) {
            this.setColor(255, 225, 255, 255, ev);
          },
         
          setColor(r,g,b,br,ev) {
            var serviceData = {entity_id: this.stateObj.entity_id, rgb_color: [r, g, b], brightness: br, transition: 1} || {};
            this.hass.callService('light', 'turn_on', serviceData);
            ev.stopPropagation();
  },
  
});
</script>

configuration.yaml

 homeassistant:
   customize:
     light.entrance:
       custom_ui_state_card: custom_light
     light.bedroom:                
       custom_ui_state_card: custom_light
5 Likes

By responsive I meant adjustments with the UI. In certain states, the buttons push the toggle off the card. Are you seeing that here?

Not really, it has been stable with my usage so far.