Custom UI with Buttons - Fan Control

My custom bit is just the fan, all other customizations come from @andrey at the github you posted. Basically you put your files in the www/custom_ui folder inside your config folder, then customize the entity/domain you want to use it on. @andrey has good example and tutorial at his github.

Thanks for the response,

I’ll hassle @andrey for a maybe more example.

Rob.

I’ve followed some of the examples in this thread to develop a custom UI for an alarm panel, however I’m seeing some odd behavior differences between Chrome and iOS Safari.

My UI’s html consists of the following bit of code:

<div class='horizontal justified layout'>
<div class='horizontal start-justified layout'>
  <div class="badge">
    <state-badge state-obj="[[stateObj]]" style="color: [[statusColor]] !important;"></state-badge>
  </div>
  <div class="info">
    <div class="name" in-dialog>Status:</div>
    <div class="time-ago" style="color: [[statusColor]] !important;">[[statusValue]]</div>
  </div>
</div>
<paper-button-group>
    <paper-button
    style="background-color:#4CAF50; color: #FFFFFF;"
    on-tap='handleDisarmTap'
    hidden$='[[!disarmButtonVisible]]'>Disarm</paper-button>
  <paper-button
  style="background-color:#673AB7; color: #FFFFFF;"
    on-tap='handleStayTap'
    hidden$='[[!armStayButtonVisible]]'> Arm Stay </paper-button>
  <paper-button
  style="background-color:#F44336; color: #FFFFFF;"
    on-tap='handleAwayTap'
    hidden$='[[!armAwayButtonVisible]]'>Arm Away</paper-button>
 </paper-button-group>
</div>

Everything works great in Chrome:

However, in Safari on my iPhone the colors don’t take:

Anyone have any thoughts on why the color style takes in Chrome but not in Safari?

2 Likes

Pretty sure safari is known to have issues but @andrey may be able to confirm.

Use Safari on Mac to debug

I’ve figured this out so I will post my code here for anyone else who may find it useful.

First, I have a template sensor which has 4 states, disarmed, disarmed_stay, armed_away, and armed_stay. This template sensor is based off a combination of input_booleans that change with various automatons.

I followed the approach of @Airpal and put the following code in a single file in /www/custom_ui:

<dom-module id="state-card-custom_alarm">
  <template>
    <style is="custom-style" include="iron-flex iron-flex-alignment"></style>
    <style>
      :host {
        line-height: 1.5;
      }
      paper-button {
        min-width: 30px;
        height: 30px;
        margin: 0px 0px 0px 0px;
        padding: 5 5 5 5;
      }
      .info {
        font-family: var(--paper-font-body1_-_font-family); -webkit-font-smoothing: var(--paper-font-body1_-_-webkit-font-smoothing); font-size: var(--paper-font-body1_-_font-size); font-weight: var(--paper-font-body1_-_font-weight); line-height: var(--paper-font-body1_-_line-height);min-width:10px;white-space:nowrap;float:left
      }
      .info {
        margin-left:16px
      }
      .name {
        white-space: var(--paper-font-common-nowrap_-_white-space); overflow: var(--paper-font-common-nowrap_-_overflow); text-overflow: var(--paper-font-common-nowrap_-_text-overflow);color:var(--primary-text-color);line-height:40px
      }
      .name[in-dialog],:host([secondary-line]) .name {
        line-height:20px
      }
      .status,::content> * {
        white-space: var(--paper-font-common-nowrap_-_white-space); overflow: var(--paper-font-common-nowrap_-_overflow); text-overflow: var(--paper-font-common-nowrap_-_text-overflow);color:var(--status-text-color);
      }
      .badge {
        position:relative;display:inline-block;width:40px;color:var(--status-text-color);border-radius:50%;height:40px;text-align:center;background-size:cover;line-height:40px
      }
    </style>
<div class='horizontal justified layout'>
    <div class='horizontal start-justified layout'>
      <div class="badge">
        <state-badge class="badge" state-obj="[[stateObj]]"></state-badge>
      </div>
      <div class="info">
        <div class="name" in-dialog>Status:</div>
        <div class="status">[[statusValue]]</div>
      </div>
    </div>
  <paper-button-group>
        <paper-button
        style="background-color:#4CAF50; color: #FFFFFF;"
        on-tap='handleDisarmTap'
        hidden$='[[!disarmButtonVisible]]'>Disarm</paper-button>
      <paper-button
      style="background-color:#673AB7; color: #FFFFFF;"
        on-tap='handleStayTap'
        hidden$='[[!armStayButtonVisible]]'> Arm Stay </paper-button>
      <paper-button
      style="background-color:#F44336; color: #FFFFFF;"
        on-tap='handleAwayTap'
        hidden$='[[!armAwayButtonVisible]]'>Arm Away</paper-button>
  </paper-button-group>
</div>
</template>
</dom-module>
<script>
        Polymer({
          is: 'state-card-custom_alarm',
          properties: {
            hass: {
              type: Object,
            },
            stateObj: {
              type: Object,
              observer: 'stateObjChanged',
            },
            disarmButtonVisible: {
              type: Boolean,
              value: false,
            },
            armStayButtonVisible: {
              type: Boolean,
              value: false,
            },
            armAwayButtonVisible: {
              type: Boolean,
              value: false,
            },
            statusValue: {
              type: String,
              value: 'unknown',
            },
          },
          stateObjChanged: function (newVal) {
            if (newVal) {
              if (newVal.state == 'disarmed_stay') {
                this.disarmButtonVisible = false;
                this.armStayButtonVisible = true;
                this.armAwayButtonVisible = false;
                this.statusValue = 'disarmed';
                this.updateStyles({'--status-text-color': '#4CAF50'});
              } else if (newVal.state == 'disarmed') {
                 this.disarmButtonVisible = false;
                 this.armStayButtonVisible = true;
                 this.armAwayButtonVisible = true;
                 this.statusValue = 'disarmed';     
                this.updateStyles({'--status-text-color': '#4CAF50'});       
              } else if (newVal.state == 'armed_stay') {
                this.disarmButtonVisible = true;
                this.armStayButtonVisible = false;
                this.armAwayButtonVisible = false;
                this.statusValue = 'armed stay';
                this.updateStyles({'--status-text-color': '#673AB7'});
              } else if (newVal.state == 'armed_away') {
                this.disarmButtonVisible = true;
                this.armStayButtonVisible = false;
                this.armAwayButtonVisible = false;
                this.statusValue = 'armed away';
                this.updateStyles({'--status-text-color': '#F44336'});
              }              
            }
          },
          handleDisarmTap: function (ev) {
            var serviceData = {entity_id: "input_boolean.home_switch"}
            this.hass.callService('input_boolean', 'turn_on', serviceData);
            this.stopProp(ev);
          },
          handleStayTap: function (ev) {
            var serviceData = {entity_id: "input_boolean.stay_switch"}
            this.hass.callService('input_boolean', 'turn_on', serviceData);
            this.stopProp(ev);
          },
          handleAwayTap: function (ev) {
            var serviceData = {entity_id: "input_boolean.away_switch"}
            this.hass.callService('input_boolean', 'turn_on', serviceData);
            this.stopProp(ev);
          },
          stopProp(ev) {
            ev.stopPropagation();
          },
});
</script>

So far this is working great in both Chrome and Safari. Next, I’m considering extending this idea to lights and other switches to replace the toggle with a button.

Here is my complete alarm panel:

7 Likes

This is awesome. Would love to see a generic “button” custom ui for these types of use cases in the future. May try taking bits from your code to enhance my fan.

2 Likes

For switch/light if you set “assumed state” it will replace the toggle with buttons. However those are buttons with icons, which is not what jamesdawson3 wanted.

Looks awesome! A question though, why are you using 4 input_boolean's? A input_select would save you three entries and if you would get/set the data straight from/to the alarm component that would provide the same functionality right?

1 Like

That’s a great point, I easily could condense the Booleans and template sensor down to a single input select.

Makes it a little easier to maintain :slight_smile:
How did you manage to get the doors/motion details like this?

1 Like

Just some template sensors. So motion for example:

motion_summary:
  value_template: >
    {%-for state in states.binary_sensor 
      if(state.attributes.device_class=="motion" and state.state=="on")-%}
        {%-if loop.last-%}
          {%-if loop.index > 0-%}
            {%-for state in states.binary_sensor if(state.attributes.device_class=="motion" and state.state =="on")-%}
              {{state.name}}{%-if not loop.last -%}{%-if(loop.index+1==loop.length)%} & {%else%}, {%endif-%}{%-else%} active{%endif-%}
            {%-endfor-%}
          {%-endif-%}
        {%-endif-%}
      {%-else-%}
        all clear
    {%-endfor-%}
  icon_template: >
    {%-for state in states.binary_sensor 
      if(state.attributes.device_class=="motion" and state.state=="on")-%}
        {%-if loop.last-%}
          {%-if loop.index > 0-%}
              mdi:alert
          {%-endif-%}
        {%-endif-%}
      {%-else-%}
        mdi:check
    {%-endfor-%}
4 Likes

Any way to get this and retain the new features that have been added to custom-ui card? Like the group naming? Maybe andrey needs a way to plug into his overrides so the wheel doesn’t need to be recreated?

With the new release 20170903 my CustomUI supports specifying other HTML elements as by using state_card_custom_ui_secondary attribute.

I’ve made a custom button for my TV Volume if anyone is interested… I have the custom ui in one file and its very simple. Big thanks to @Airpal and @kylerw for their inital examples!

It looks like this.
image

And

This is the code which is in a file called ‘state-card-custom_volume.html’

<dom-module id="state-card-custom_volume">
  <template>
    <style is="custom-style" include="iron-flex iron-flex-alignment"></style>
    <style>
      :host {
        line-height: 1.5;
      }
      paper-button {
        min-width: 60px;
        background-color:#f3f3f3
      }
    </style>

<div class='horizontal justified layout'>
  <state-info state-obj="[[stateObj]]"></state-info>
  <paper-button-group>
      <paper-button on-tap="btntap2" on-down="btndown2" on-up="btnup2"><svg style="width:24px;height:24px" viewBox="0 0 24 24"><path fill="#848484" d="M3,9H7L12,4V20L7,15H3V9M14,11H22V13H14V11Z" /></svg></paper-button>
      <paper-button on-tap="btntap1" on-down="btndown1" on-up="btnup1"><svg style="width:24px;height:24px" viewBox="0 0 24 24"><path fill="#848484" d="M3,9H7L12,4V20L7,15H3V9M14,11H17V8H19V11H22V13H19V16H17V13H14V11Z" /></svg></paper-button>
  </paper-button-group>
</div>
</template>
</dom-module>
<script>
        Polymer({
          is: 'state-card-custom_volume',
          properties: {
            hass: {
              type: Object,
            },
            stateObj: {
              type: Object,
            },
          },

          btndown1: function (ev) {
            this.setVol('tvvolumeup', ev);
          },
          btndown2: function (ev) {
            this.setVol('tvvolumedown', ev);
          },

          btnup1: function (ev) {
            this.setVol('tvvolumeup', ev);
          },
          btnup2: function (ev) {
            this.setVol('tvvolumedown', ev);
          },





          btntap1: function (ev) {
            this.setVol('tvvolumeup', ev);
          },
          btntap2: function (ev) {
            this.setVol('tvvolumedown', ev);
          },

          setVol(vol,ev) {
            var serviceData = {entity_id:('script.'+vol)};
            this.hass.callService('script', 'turn_on', serviceData);
            ev.stopPropagation();
  },

});
</script>

I have the actions calling a script I made in Hassio’s editor is its really easy to edit.
Some of you will see I have left in hooks for button press and release… I am currently working on a way to have the script repeat as you hold the button down. But I haven’t managed it yet… If anyone has any suggestions I’d love to hear!

In regards to responsiveness. Its ok. It only breaks when the card is less than 320px. Which for me is when the cards go to a 4 column layout.

7 Likes

Hi morrelli43, thank you for posting this. I’d like to implement your card for a fan controlled via broadlink (remote). I have limited knowledge, so would you mind sharing your entire “process”: the volume script, configuration file entry. Thank you.

Hello Airpal, the custom_light works on HA 0.53+ ?

look my yaml

customize:
light.sala03:
friendly_name: RGB Central
custom_ui_state_card: custom_light

frontend:
extra_html_url:
- https://raw.githubusercontent.com/andrey-git/home-assistant-custom-ui/master/state-card-custom-ui.html
- /home/homeassistant/.homeassistant/www/custom_ui/state-card-custom_light.html

but dont work :confused:

You would need the full name in customize:
custom_ui_state_card:state_card-custom_light

Hi guys, can you please tell me how have you hidden the toggle controls?
I have created a script which controls the pan / tilt function of my security cams.The technical part works great, but the toggle is useless and takes space.

Can you please help me with this one?

That is what i put in my “customization.yaml”

script.script_cam_control:
custom_ui_state_card: state-card-custom_camcontrol
hide_control: true

And thats my “state-card-custom_camcontrol.html”

<link rel="import" href="state-card-custom-ui.html">
<dom-module id="state-card-custom_camcontrol">
  <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 on-tap="btntap1">
	  <svg style="width:24px;height:24px" viewBox="0 0 24 24">
	    <path d="M0.5 8l7.5 7.5v-4.5h8v-6h-8v-4.5z"></path>
	  </svg>
	</paper-button>
	<paper-button on-tap="btntap2">
	  <svg style="width:24px;height:24px" viewBox="0 0 24 24">
	    <path d="M15.5 8l-7.5-7.5v4.5h-8v6h8v4.5z"></path>
	  </svg>
	</paper-button>
	<paper-button on-tap="btntap3">
	  <svg style="width:24px;height:24px" viewBox="0 0 24 24">
	    <path d="M8 0.5l-7.5 7.5h4.5v8h6v-8h4.5z"></path>
	  </svg>
	</paper-button>
	<paper-button on-tap="btntap4">
	  <svg style="width:24px;height:24px" viewBox="0 0 24 24">
	    <path d="M8 15.5l7.5-7.5h-4.5v-8h-6v8h-4.5z"></path>
	  </svg>
	</paper-button>
	<paper-button on-tap="btntap5">
	  <svg style="width:24px;height:24px" viewBox="0 0 24 24">
	    <path d="M8 0c-4.418 0-8 3.582-8 8s3.582 8 8 8 8-3.582 8-8-3.582-8-8-8zM8 14c-3.314 0-6-2.686-6-6s2.686-6 6-6c3.314 0 6 2.686 6 6s-2.686 6-6 6zM5 8c0-1.657 1.343-3 3-3s3 1.343 3 3c0 1.657-1.343 3-3 3s-3-1.343-3-3z">
</path>
	  </svg>
    </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_camcontrol',
      properties: {
        hass: {
          type: Object,
        },
        stateObj: {
          type: Object,
        },
      },

      btntap1: function (ev) {
        this.setColor(4, 3, ev);
      },
      btntap2: function (ev) {
        this.setColor(6, 3, ev);
      },
      btntap3: function (ev) {
        this.setColor(0, 3, ev);
      },
      btntap4: function (ev) {
        this.setColor(2, 3, ev);
      },
      btntap5: function (ev) {
        this.setColor(25, 3, ev);
      },
      setColor(com, ipaddr,ev) {
        var serviceData = {entity_id: this.stateObj.entity_id, command: com, ip: ipaddr} || {};
        this.hass.callService('shell_command', 'camcontrol', serviceData);
        ev.stopPropagation();
  },

});
</script>

Thats what i have now:
is

And thats what i want to achieve:
need

If you never what the toggle - just remove the <ha-entity-toggle> element.