Custom UI: Tiles

Ok, I will take a look at this.

I tried the min-height: xxpx but it didn’t make any difference on my Ubuntu Phone. On the desktop, the tiles have looked right all the time.

sure, only use if you need it. @eddi89 made the default smaller, and maybe fit the bill just like that.

My setup still uses a small button for on/off all buttons below. Might take it up 5 px (now 25) and another bit of code can be taken out :wink:

05
10

Oh yes I see what you mean, a great update by eddi89, especially with explaining the options too.

Here’s an interesting use of tiles that I’ve come up with - a visual representation of windows that shows when they’re open or closed (that little blue glitch on the top window only occurs the first time after a HA restart, and is ok on subsequent open/closes).
window2

The colour change is instant and works really well! I use Xiaomi door/window sensors which are cheap and integrate very well with HA (links here: https://smarthomehobby.com/xiaomi-gateway-sensors/)

I basically have a 3-row tile with 0px gap. Each tile has an on and off picture, all of which I drew myself in GIMP at the pixel level by creating a 75x50 pixel image and turning on “show grid”.

You can combine multiple tiles to create larger windows too:
21

Here is an example of how I created the pics in GIMP:
35

The code for the single window together is:

    input_text.single_window:
      custom_ui_state_card: state-card-tiles
      config:
        columns: 1
        row_height: 50px
        column_width: 75px
        gap: 0px
        entities:
          #Lights
          - entity: binary_sensor.door_window_sensor_158d0001ab38a0
            label_template: "if (state === 'on') return ''; else return '';"
            style_template: "if (state === 'on') return 'background-image: url(\"/local/top-open.png\");'; else return 'background-image: url(\"/local/top.png\");'"
            row: 1
          - entity: binary_sensor.door_window_sensor_158d0001d68293
            label_template: "if (state === 'on') return ''; else return '';"
            style_template: "if (state === 'on') return 'background-image: url(\"/local/middle-open.png\");'; else return 'background-image: url(\"/local/middle.png\");'"
            row: 2
          - entity: binary_sensor.door_window_sensor_158d0001d68293
            label_template: "if (state === 'on') return ''; else return '';"
            style_template: "if (state === 'on') return 'background-image: url(\"/local/bottom-open.png\");'; else return 'background-image: url(\"/local/bottom.png\");'"
            row: 3

Note the label_template - I had to include this because text was appearing on each tile even if no label was set (It was showing on/off).

Hope this inspires other interesting uses of tiles too. :slight_smile:

4 Likes

cool, thanks!

i set up this today, using transparant images for buttons:

homeassistant:
  customize:
    input_text.tiles_activity:
      custom_ui_state_card: state-card-tiles
      config:
        columns: 5
        column_width: 50px
        row_height: 50px
        color: "var(--paper-card-background-color)"    

default theme:
50

night time darkred theme:

08

floating buttons!

1 Like

I can’t thank @eddi89 enough for this idea. Truly great work.

I have been using Custom UI: Tiles to compress our UI as efficiently as possible. We have nearly three hundred entities in HA, but most are just used for logging or automation triggers. Grouping wasn’t terribly efficient, and view grouping wasn’t very elegant for mobile access.

Here is our tile setup, designed specifically for primarily one-page mobile access (iOS):

Clicking any of the sensors (temperature, humidity, brightness) loads more_info views that show charts dividing sensors into individual floors of our home:

The final button on the bottom shows/hides detailed controls (light color, brightness, fixtures, switches, scripts, etc.). After 45 seconds, an automation re-hides the detailed groups to shrink the UI again.

A guest mode indicator shows only when our guest mode (no automations) is enabled:

I can post my config if someone is interested, just thought I’d share our setup and thank @eddi89 for the awesome work.

19 Likes

Very nice indeed @andrewjfreyer ! I’d love to see your config please!

Thanks! I’ll post tomorrow morning. I’ll also post my modified UI Tiles files.

2 Likes

It looks WOW, I also would like to see your config please :slight_smile:

Wow! That looks great! Would love to see your configuration files.

looking forward to that indeed. Don’t forget to post the code for that cool temp graph of the 3 sensors ;-)). Is that the new sensor in Hassio you;re using or was that already there before 0.65

For you device tracker tiles you want to make the images the exact height you set the tile to be but leave enough width to take care of scaling on devices as this only alters width. No more cut off heads then :slight_smile:

Ha! The cutoff is actually intentional. These are pulled from social media in real time, and so size may vary. If we fix the height, the width leaves white gaps which look much worse than cutoff height. Yes, I could be a bit more sophisticated about where and how it crops, but I’ve grown to like the cropped images!

1 Like

@Mariusthvdb Hass.io, from version 0.64 on (I think)

not sure what you’re using, since I’ve never seen it before in Hassio. this it:https://home-assistant.io/components/sensor.filter/?

Im looking for a way to have the outdoor temp mapped against my heaters. Show 3 lines: outdoor temp, heating 1, heating 2. Maybe even a 4th, inside humidity.

Could this be done, if yes, would this be the sensor to do so?

@Mariusthvdb, it’s a history_graph, introduced in 0.55. The engine changed recently (in the 60s somewhere, I think 0.64), so we get richer graphics now:

history_graph:
  temperature_graph:
    name: Temperature
    entities:
      - sensor.1fl_average_temperature
      - sensor.2fl_average_temperature
      - sensor.3fl_average_temperature 

The history graphs are what I use for my more_info flags in the configuration:

input_text.dummy_tiles_information:
  custom_ui_state_card: state-card-tiles
  config:
    columns: 8           
    row_height: 35px      
    gap: 0px              
    color: 'hsl(192, 96%, 48%)'
    text_size: '0.85em'
    entities:
      - entity: sensor.average_temperature
        label_template: "return parseInt(state) + 'ºF'"
        icon: mdi:thermometer
        more_info: history_graph.temperature
        column: 1
        column_span: 2
        row: 1            
        row_span: 1          
        color: 'hsla(0, 0%, 98%, 0)'    #whitetransparent
        color_on: 'hsla(0, 0%, 98%, 0)'    #whitetransparent
        color_off: 'hsla(0, 0%, 98%, 0)'    #whitetransparent
        text_color: 'hsl(0, 0%, 0%)'    #black
        text_color_on: 'hsl(0, 0%, 0%)'    #black
        text_color_off: 'hsl(0, 0%, 0%)'    #black
        icon_size: 12px
2 Likes

ok thanks! that great. We’ve been having issues showing these kind of graphs, even on single entities in Safari, so i didn’t recognize. Thanks for the example, will build mine asap!

Cheers,
Marius

@jarrah @Karolis_Kontrimas @thmry @Mariusthvdb

Here are the configurations I use currently. There are five separate tile groups that are shown in a two grousp on the default_view. One group shows four of the tile groups, and another shows the fifth. By default, the fifth tile group is hidden from view; visibility is toggled by an input_boolean linked to the lowermost tile of the default group.


From top to bottom, this is the first ‘group’ of tiles:

   input_text.dummy_tiles_main:
      custom_ui_state_card: state-card-tiles
      config:
        columns: 6           
        row_height: 54px      
        gap: 8px              
        color: 'hsl(192, 96%, 48%)'                  
        entities:
          - entity: sensor.andrew_status
            label_state: sensor.andrew_status
            image: !secret ajf_facebook
            column: 1              
            column_span: 3         
            row: 1
            row_span: 2          
            color: 'hsl(0, 100%, 100%)'    #white
            color_on: 'hsl(0, 100%, 100%)'    #white
            color_off: 'hsl(0, 100%, 100%)'    #white
            text_size: '1.2em'
            #secondary label
            label_sec_state: sensor.andrew_occupancy_template
            text_sec_color: "#eee"
            text_sec_size: '0.7em'
            more_info: sensor.andrew_status

          - entity: sensor.wife_status
            label_state: sensor.wife_status
            image: !secret wife_facebook
            column: 4              
            column_span: 3         
            row: 1             
            row_span: 2       
            color: 'hsl(0, 100%, 100%)'    #white
            color_on: 'hsl(0, 100%, 100%)'    #white
            color_off: 'hsl(0, 100%, 100%)'    #white
            text_size: '1.2em'
            #secondary label
            label_sec_state: sensor.wife_occupancy_template
            text_sec_color: "#eee"
            text_sec_size: '0.7em'
            more_info: sensor.wife_status

          - entity: switch.1fl_fireplace
            label: Fireplace
            column: 1              
            column_span: 3         
            row: 3             
            row_span: 1          
            color: 'hsl(192, 96%, 48%)'    #blue
            color_on: 'hsl(37, 96%, 48%)'  #orange
            color_off: 'hsl(192, 96%, 48%)'    #blue     
            #secondary label
            label_sec_template: "return state_time;"
            text_sec_color: "#eee"
            text_sec_size: '0.7em'
            icon_size: 18px

          - entity: vacuum.charlie
            label: Vacuum
            column: 4         
            column_span: 3     
            row: 3              
            row_span: 1
            color: 'hsl(192, 96%, 48%)'    #blue
            color_on: 'hsl(37, 96%, 48%)'  #orange
            color_off: 'hsl(192, 96%, 48%)'    #blue 
            #secondary label
            label_sec_template: "return entities['vacuum.charlie'].attributes.status;"
            text_sec_color: "#eee"
            text_sec_size: '0.7em'
            text_sec_align: right  

          - entity: light.first_floor_lights
            label: First
            icon: mdi:lightbulb-outline  
            more_info: light.first_floor_lights
            column: 1              
            column_span: 2      
            row: 4      
            row_span: 1         
            color: 'hsl(0, 0%, 80%)'    #grey        
            color_on: 'hsl(37, 96%, 48%)'  #orange
            color_off: 'hsl(0, 0%, 80%)'    #grey 
            icon_size: 18px
            label_sec_template: "return state_time;"
            text_sec_color: "#eee"
            text_sec_size: '0.7em'

          - entity: light.second_floor_lights
            label: Second
            icon: mdi:lightbulb-outline
            column: 3              
            column_span: 2        
            row: 4             
            row_span: 1          
            color: 'hsl(0, 0%, 80%)'    #grey        
            color_on: 'hsl(37, 96%, 48%)'  #orange
            color_off: 'hsl(0, 0%, 80%)'    #grey 
            icon_size: 18px  
            label_sec_template: "return state_time;"
            text_sec_color: "#eee"
            text_sec_size: '0.7em'  

          - entity: light.third_floor_lights
            label: Third
            icon: mdi:lightbulb-outline
            column: 5              
            column_span: 2        
            row: 4            
            row_span: 1      
            color: 'hsl(0, 0%, 80%)'    #grey        
            color_on: 'hsl(37, 96%, 48%)'  #orange
            color_off: 'hsl(0, 0%, 80%)'    #grey  
            icon_size: 18px
            label_sec_template: "return state_time;"
            text_sec_color: "#eee"
            text_sec_size: '0.7em'

          - entity: input_boolean.approved_disarm
            label: Guest Mode Enabled
            column: 1              
            column_span: 6         
            row: 5             
            row_span: 1        
            color: 'hsla(0, 100%, 100%, 0)'    #white
            color_on: 'hsla(0, 100%, 100%, 0)'    #white
            color_off: 'hsla(0, 100%, 100%, 0)'    #white 
            text_color_on: 'hsl(192, 96%, 48%)'    #blue
            text_color_off: 'hsl(192, 96%, 48%)'    #blue
            hide_when_off: 'true'
            label_sec_template: "return state_time;"
            text_sec_color: "#aaa"
            text_sec_size: '0.7em'
            more_info: input_boolean.approved_disarm

In this example, I have a more_info dialog set for the first floor lights because these are the lights that we most commonly dim or adjust colors. Far more convenient for us to have a more_info dialog pop up than it is to have to scroll or find another control for the brightness or color of the lights.

There are more two things to note about this. First, I have added hide_when_off and a variable accessible int he javascript template called state_time.

  • Hide when off does what you’d expect, although it seems that this works best with Polymer when the hide_when_off element is the last element in a group. Having it within a group works too, but spacing is inconsistent when hiding and showing multiple times in quick succession. For me, it wasn’t a big deal since I only wanted to hide a single tile.

    That said, this can be accomplished by @eddi89’s option of style_template. I added hide_when_off on my forked version a bit before style_template was released. Instead of switching my configuration when updating @eddi89’s master, I left it in so that we can configure both the style and whether it is hidden. Duplicative, for sure, but it was easier to update.

  • state_time is a variable that mimics relative_time(N) in the Jinja. It returns the most significant time that has elapsed since the relevant entity has changed. This is what I use for my secondary labels in the configuration.

Yes, I know I have more columns than necessary for this layout. That’s an artifact of a previous design that I haven’t updated yet.


This is the second ‘group’ of tiles. I think this is mostly self-explanatory. The weather information comes from the DarkSky sensor component:

    input_text.dummy_tiles_information:
      custom_ui_state_card: state-card-tiles
      config:
        columns: 8           
        row_height: 35px      
        gap: 0px              
        color: 'hsl(192, 96%, 48%)'
        text_size: '0.85em'
        entities:
          - entity: sensor.average_temperature
            label_template: "return parseInt(state) + 'ºF'"
            icon: mdi:thermometer
            more_info: history_graph.temperature
            column: 1
            column_span: 2
            row: 1            
            row_span: 1          
            color: 'hsla(0, 0%, 98%, 0)'    #whitetransparent
            color_on: 'hsla(0, 0%, 98%, 0)'    #whitetransparent
            color_off: 'hsla(0, 0%, 98%, 0)'    #whitetransparent
            text_color: 'hsl(0, 0%, 0%)'    #black
            text_color_on: 'hsl(0, 0%, 0%)'    #black
            text_color_off: 'hsl(0, 0%, 0%)'    #black
            icon_size: 12px

          - entity: sensor.average_humidity
            label_template: "return parseInt(state) + '%'"
            icon: mdi:weather-rainy
            more_info: history_graph.humidity
            column: 3
            column_span: 2
            row: 1              
            row_span: 1          
            color: 'hsla(0, 0%, 98%, 0)'    #whitetransparent
            color_on: 'hsla(0, 0%, 98%, 0)'    #whitetransparent
            color_off: 'hsla(0, 0%, 98%, 0)'    #whitetransparent
            text_color: 'hsl(0, 0%, 0%)'    #black
            text_color_on: 'hsl(0, 0%, 0%)'    #black
            text_color_off: 'hsl(0, 0%, 0%)'    #black
            icon_size: 12px

          - entity: sensor.average_illuminance
            label_template: "return parseInt(state) + '%'"
            icon: mdi:weather-sunny
            more_info: history_graph.brightness
            column: 5
            column_span: 2
            row: 1             
            row_span: 1          
            color: 'hsla(0, 0%, 98%, 0)'    #whitetransparent
            color_on: 'hsla(0, 0%, 98%, 0)'    #whitetransparent
            color_off: 'hsla(0, 0%, 98%, 0)'    #whitetransparent
            text_color: 'hsl(0, 0%, 0%)'    #black
            text_color_on: 'hsl(0, 0%, 0%)'    #black
            text_color_off: 'hsl(0, 0%, 0%)'    #black
            icon_size: 12px

          - entity: binary_sensor.hallway_thermostat_fan  
            label_state: climate.hallway
            icon: mdi:fan
            column: 7              
            column_span: 2
            row: 1              
            row_span: 1
            color: 'hsla(0, 0%, 98%, 0)'    #whitetransparent
            color_on: 'hsla(0, 0%, 98%, 0)'    #whitetransparent
            color_off: 'hsla(0, 0%, 98%, 0)'    #whitetransparent
            text_color: 'hsl(0, 0%, 0%)'    #black
            text_color_on: 'hsl(0, 0%, 0%)'    #black
            text_color_off: 'hsl(0, 0%, 0%)'    #black
            icon_size: 12px

          - entity: sensor.weather_hourly_summary
            label_state: sensor.weather_hourly_summary
            icon_template: "return 'mdi:' + entities['sensor.weather_icon'].state;"
            label_sec_template: "return parseInt(entities['sensor.weather_daily_low_temperature'].state) + 'ºF to ' + parseInt(entities['sensor.weather_daily_high_temperature'].state) + 'ºF'"
            column: 1
            column_span: 8
            row: 2            
            row_span: 3          
            color: 'hsla(0, 0%, 98%, 0)'    #whitetransparent
            color_on: 'hsla(0, 0%, 98%, 0)'    #whitetransparent
            color_off: 'hsla(0, 0%, 98%, 0)'    #whitetransparent
            text_color: 'hsl(0, 0%, 0%)'    #black
            text_color_on: 'hsl(0, 0%, 0%)'    #black
            text_color_off: 'hsl(0, 0%, 0%)'    #black
            text_sec_color: 'hsl(0, 0%, 0%)'    #black
            icon_size: 12px

This is the third ‘group’ of tiles, related to lock and garage security. I think this is mostly self-explanatory.

input_text.dummy_tiles_security:
  custom_ui_state_card: state-card-tiles
  config:
    columns: 6           
    row_height: 54px      
    gap: 8px              
    entities:
      - entity: lock.1fl_deadbolt_door_main
        label: Front
        icon: mdi:door
        column: 1
        column_span: 2
        row: 1             
        row_span: 1          
        color:  'hsl(0, 0%, 80%)'    #grey    
        color_off: 'hsl(37, 96%, 48%)'  #orange
        color_on:  'hsl(0, 0%, 80%)'    #grey 

      - entity: lock.1fl_deadbolt_door_kitchen
        label: Kitchen
        icon: mdi:door
        column: 3
        column_span: 2
        row: 1            
        row_span: 1          
        color:  'hsl(0, 0%, 80%)'    #grey    
        color_off: 'hsl(37, 96%, 48%)'  #orange
        color_on:  'hsl(0, 0%, 80%)'    #grey  

      - entity: lock.1fl_deadbolt_door_garage
        label: Garage
        icon: mdi:door
        column: 5
        column_span: 2
        row: 1             
        row_span: 1          
        color:  'hsl(0, 0%, 80%)'    #grey    
        color_off: 'hsl(37, 96%, 48%)'  #orange
        color_on:  'hsl(0, 0%, 80%)'    #grey 

      - entity: cover.garage
        label: "Garage Lift"
        icon: mdi:garage
        more_info: cover.garage
        column: 1              
        column_span: 6     
        row: 2            
        row_span: 1  
        color: 'hsl(0, 0%, 80%)'    #grey        
        color_on: 'hsl(37, 96%, 48%)'  #orange
        color_off: 'hsl(0, 0%, 80%)'    #gr
        label_sec_template: "return state + ' for ' + state_time;"
        text_sec_color: "#eee"
        text_sec_size: '0.7em'

In this example, I have a more_info dialog set for the garage so that we don’t accidentally open the garage by haphazardly clicking about the UI.


This is the fourth ‘group’ of tiles. I think this is mostly self-explanatory. It plugs into an automation that shows/hides the more detailed groups:

input_text.dummy_toggle_lights:
  custom_ui_state_card: state-card-tiles
  config:
    columns: 1          
    row_height: 54px      
    gap: 8px              
    color: 'hsl(192, 96%, 48%)'                  
    entities:
      - entity: input_boolean.gui_toggle_lights
        label_template: "if (state == 'on'){return 'Hide Detailed Controls'}else{return 'Show Detailed Controls'}"
        column: 1              
        column_span: 1 
        icon: mdi:settings    
        row: 1            
        row_span: 1           
        color: 'hsl(192, 96%, 48% )'    #blue
        color_on: 'hsl(37, 96%, 48%)'  #orange
        color_off: 'hsl(192, 96%, 48%)'    #blue
        icon_size: 14px

As an example, here’s a portion of the associated automation. Adding additional service entries will toggle visibility of other things too. This can be used for groups or views, but I’m just using it for groups. I have another automation that automatically hides the “more detailed” groups on boot and, additionally, hides the “more detailed” groups after 45 seconds of being shown:

- alias: Display Automations (Lights - Toggle UI)
  trigger:
    - platform: state
      entity_id: input_boolean.gui_toggle_lights
      to: 'on'
    - platform: state
      entity_id: input_boolean.gui_toggle_lights
      to: 'off'
  action:
    - service: group.set_visibility
      data_template:
        entity_id: group.floor_lights
        visible: "{{not(is_state('input_boolean.gui_toggle_lights','on'))}}"

The last ‘group’ of tiles is within a hidden group. I don’t think the configuration there is particularly interesting, since it’s just a set of buttons similar to the default configuration of Custom UI: Tiles. I won’t add it here unless people are interested in that section in particular.

Now, for adjustments to the custom_ui files. For state_time, we change the template function:

computeFromTemplate(hass, entity, template) {
	if ( hass.states[entity.entity] ) {
		const state = hass.states[entity.entity].state;
		const attributes = hass.states[entity.entity].attributes;
		const entities = hass.states;
		
		var sec_diff = (Date.now() - Date.parse(entities[entity.entity].last_updated))/1000;
		var int_diff = 0; var append = ''; 
		if(sec_diff < 60){int_diff = parseInt(sec_diff); append='second'; }else if(sec_diff < 3600){ int_diff = parseInt(sec_diff/60); append='minute'; }else if(sec_diff <= 86400){ int_diff = parseInt(sec_diff/3600);append='hour'; } if (int_diff > 1){append += 's';}
		var state_time = int_diff + ' ' + append;

		return Function('state', 'attributes', 'entities', 'state_time', entity[template])(state, attributes, entities, state_time);
	}
}

Note that we also have to pass the hass object to this function in the original polymer template:

<template is='dom-if' if='[[entity.icon_template]]'>
   <iron-icon icon='[[computeFromTemplate(hass, entity, "icon_template")]]'></iron-icon>
</template>

For hide_when_off, to the compute_style methods, add this line to the top of the if:

if (entity.hide_when_off == 'true' && isOff ){style += 'display:none;';}

And these lines to the top of the function:

const state = hass.states[entity.entity] ? hass.states[entity.entity].state : 'off';
const isOff = window.hassUtil.OFF_STATES.includes(state);

These additional lines prevent an error/crash when an expected entity does not exist or a state is unknown.

I’ll commit these to my github repo soon; an old version of these files is there already. Apologies for the block quoting here.

12 Likes

@Mariusthvdb There was a UI bug for these graphs in Safari (mobile and desktop) for me as well - until very recently. As in the past few weeks. Now, all works like a charm!

Having the charts is actually pretty cool. We can clearly see what time we go to bed (and turn on the showers) and get up in the morning (and turn on the showers) based on humidity. It’s also interesting to see that our exhaust fan appears to be effective!

I’ll be adding filters to these very soon.

3 Likes

Amazing stuff, thank you for being very detailed, it makes a big difference when following code! I’m looking forward to trying out the graphs too!