TileBoard - New dashboard for Homeassistant

Hello. I have been using HA for about a month now and happened upon TileBoard a week or so ago after looking for a good, lightweight front end to run on a wall mounted tablet. My whole reason for using HA was to find an easy way for my wife to control the various bulbs and switches (and future sensors and cameras) we have collected without needing several different apps on her phone. A wall mounted tablet gives her single place to control everything. BTW, I picked up an Amazon Fire 8 this past weekend (on sale for $49) and I am running Fully Kiosk on it.

@resoai this is awesome Alexey. I am a web dev but mostly backend stuff and the front end stuff I have done is React. I have not worked with angular before but am starting to get there now. However, I am amazed at how much can just be done with the config file. My goal is to do everything I need there or custom.css without touching the other things, but I am starting to need to branch beyond that with the things I am trying to do.

I have learned a ton both from the project readme and this thread, nearly everything I wanted to do was discussed. I hope to share some things I have learned.

I am looking forward to your take on splitting out some of the config into multiple files @resoai. I have tried a few things but none of them were very clean. I was able to separate out my tile definitions from their usage. I define all my tile entities above the setting of the config variable, something like this:

tiles = {};
tiles.family_room_table_lamp: {
      width: 1,
      type: TYPES.SWITCH,
      id: "light.table_lamp",
      states: {
         on: "On",
         off: "Off"
      },
      ... etc ...
}

along with this function:

function buildTile( position, tileDefinition ){
   var tile = angular.copy( tileDefinition );
   tile.position = position;
   return tile;
}

and then use them like this as items in a group:

groups: [ {
  // Primary Lights & Switches
  title: '',
  width: 2,
  height: 3,
  items: [ 
    buildTile( [0, 0], tiles.family_room_table_lamp ), 
    buildTile( [1, 0], tiles.room_basement_mediaroom ), 
    buildTile( [0, 1], tiles.room_basement_storage ),
    buildTile( [1, 1], tiles.familyroom_spaceheater )
  ]
},

This allows me to use the same tiles in multiple places but only have to type out the definition for them once. Currently I am only applying the position they are used it, but the same approach could be used for width or any other property for that matter.

Initially I tried using the spread operator (which is much cleaner), which worked great on any browser on my laptop but did not work on FireOS because the core browser component is uses is older. So I had to adopt the buildTile function. Basically, the spread operator just splats all the property from the object after the […]. Here is an example of that in case any of you all can use it:

groups: [ {
  // Primary Lights & Switches
  title: '',
  width: 2,
  height: 3,
  items: [ 
    { position: [0, 0], ...tiles.family_room_table_lamp }, 
    { position: [1, 0], ...tiles.room_basement_mediaroom }, 
    { position: [0, 1], ...tiles.room_basement_storage },
    { position: [1, 1], ...tiles.familyroom_spaceheater }
  ]
},

Hopefully that gives some of you ideas on how to clean up your config file. I hope to contribute more in the future.

4 Likes

Does anybody know why I keep getting logged out on tileboard?

Nice work, that’s a nice way to be able to reuse tiles as objects, I’ve been looking to clean up my config also as I have different tileboards for different devices. I’ll have to try making a tiles array.

Up until now I’ve had each tile as a static object, for example

const GARAGE_LIGHT_A = { 
  position: [0, 1],
  type: TYPES.SWITCH,
  id: 'light.garage_light_1',
  title: 'Light 1',
  subtitle: 'Garage',
  states: {
    on: 'On',
    off: 'Off'
  },
  icon: 'mdi-lightbulb'
}

then I just have GARAGE_LIGHT_A in my group object, but being able to add the positions in after is so much nicer. :slight_smile:

Finally, my camera stream popout seems to have stopped working, I have 2 camera streams and both camera thumbnails work in the tile but the popout has no image at all.

Is there an easy way to disable the camera popout? could I somehow overwrite the ng-click class for camera tiles only?

1 Like

I have everything in a tiles object but unlike the example I gave they are grouped like:

tiles: {
    lights: {
        family_room_table_lamp: { ... etc ... }
        family_room_standing_lamp: { etc }
    }, 
    switches: { etc },
    device_trackers: { etc },
    weather: { etc }
}

so I can reference them like tiles.weather.forecast

Nice, having objects within objects is a nice touch.

I’ve found you can expand your build tile function to also be able to adjust the size of the tile too:

function buildTileBig (position, tileDefinition, width, height) {
  var tile = angular.copy(tileDefinition);
  tile.position = position;
  tile.width = width;
  tile.height = height;
  return tile;
}

This was I’m able to re-use a camera tile for example to be different sizes on different pages, and to use it you just add the width and height values after the tile object name:

    buildTileBig( [0, 1], ALL_TILES.CAMS.BACK, 3, 2),

will make a 3x2 sizes camera tile at position 0,1

I still use buildTile for 1 x 1 sized tiles.

1 Like

You are on the right track! But what happens when you want to be able to set another property? You either create another function or add another property to the existing function’s arguments list.

This is a little more generic and future-proof:

function buildTile( tileDefinition, options ){
   var tile = angular.copy( tileDefinition );
   Object.keys( options ).forEach( k => {
      tile[ k ] = options[ k ]
   } );
   return tile;
}

Basically you just pass in an options object with whatever properties you want set on the tile. The function just loops through the properties on options and and then sets the value to a property of the same name on the tile created from the definition. The usage looks like:

buildTile( tiles.lights.room_mediaroom, { position: [0, 0] } ), 
buildTile( tiles.lights.room_kitchen, { position: [0, 1], title: "Hello World", width: 2 } ),
// or for a bit more readability 
buildTile( 
    tiles.lights.room_kitchen, 
    { 
        position: [0, 1], 
        title: "Hello World", 
        width: 2 
    } 
),

With this we are getting back to the crux of how tileboard works except now you can define your defaults for a tile.

Yes this is basically what it does already… if you have just one thing to reuse why not put that in a variable and reuse it in the default tile builder?

I understand what your trying to simplify but to be honest this does not :slight_smile:

1 Like
function $$(tileObject, merge) {
   if(merge instanceof Array) {
      merge = {position: merge};
   }

   var copy = mergeObjects({}, tileObject);

   return mergeObjects(copy, merge);
}
{
    width: 3,
    height: 3,
    title: 'Scenes',
    items: [
        $$(TILES.GOOD_MORNING_SCENE, [0, 0]),
        $$(TILES.GOOD_NIGHT_SCENE, [1, 0]),
        $$(TILES.LIGHTS_OUT_SCENE, [2, 0]),

        $$(TILES.MOVIE_SCENE, [0, 1]),
        $$(TILES.GAME_SCENE, [1, 1]),
        $$(TILES.RELAX_SCENE, [2, 1]),
    ]
}
1 Like

True, all fair points.

In my case I have several items, mostly lights and switches, that I use for tiles in multiple places in my dashboard, for instance in an overview section on my home screen, maybe on a specific room page, and then again on a page that lists all lights. In my case being able to simply make a copy of the tile definition that includes most of the options already set and just being able to override the position is enough. And since I treat some tiles like that I want to treat all of them the same way, I do not want some tiles defined inline and some tiles defined as custom tiles, etc.

It does simplify it for me, but what works for me does not work for everyone.

function loadScript(url) {
  var req = new XMLHttpRequest();
  req.open('GET', url + '?cache=' + Math.random(), false);
  req.onreadystatechange = function(){
     if (req.readyState === 4) {
        var s = document.createElement('script');
        s.appendChild(document.createTextNode(req.responseText));
        document.head.appendChild(s);
     }
  };
  req.send(null);
}

loadScript('includes/helpers/functions.js');
loadScript('includes/helpers/states.js');
loadScript('includes/helpers/common.js');
loadScript('includes/helpers/events.js');
loadScript('includes/helpers/tiles.js');

5 Likes

Thanks @resoai I’ll give that a try!

1 Like

If you have a lot of similar tiles on one page, you could could do something like this:

{
    title: 'Downstairs',
    width: 2,
    height: 3,
    items: [
        ['climate.hallway', 'sensor.hallway_heat_demand'],
        ['climate.av_room', 'sensor.av_room_heat_demand'],
        ['climate.lounge', 'sensor.lounge_heat_demand'],
        ['climate.kitchen', 'sensor.kitchen_heat_demand'],
        ['climate.dining_room', 'sensor.dining_room_heat_demand'],
        ['climate.utility_room']
    ].map(function(item, i) {
        var y = Math.floor(i / 2);

        return $$(TILES.CLIMATE, {
            position: [i - y * 2, y],
            subtitle: item[1] ? 'Valve at &' + item[1] + '.state' + '%' : false,
            id: item[0]
        });
    })
}

As you can see, I’m also reusing TILE.CLIMATE.

2 Likes

Nice that allows you to put any property in the array.

With this we are getting back to the crux of how tileboard works except now you can define your defaults for a tile.

I guess if you make after you add lots of changes to then sometimes it’s easier just to make a whole new one, but it’s really handy for me when I have a single camera tile that different sizes depending on which device I’m using Tileboard on.

Yes this is basically what it does already… if you have just one thing to reuse why not put that in a variable and reuse it in the default tile builder?

For me this help as I’ll re-use the tile on multiple tileboard layouts. Eg Phone has a tile layout that is different to my ipad, but they both use the same tiles. It’s great to have this flexability :slight_smile:

EDIT: Is anyone else running HA behind 2 proxies? If so do your camera feeds work in popout mode in tileboard?

First of all @resoai thank you for this masterpiece. I have one question: is there any way to implement floorplan to your work? It would be really nice and usefull.

1 Like

+1 for Floorplan support

No, this is not possible

You could include your Floorplan in an iframe tile

could you share how to made items pulsate please?
I’ve no idea how to implement this. custom css?

I am finally able to create the weather/forecast card that I want, so I am going to go with TileBoard vs HADashboard. Love it!

But how do I set each date to use two lines instead of one? I want to make it so each day has two lines that can display for the summary. Also, is there a way to have it display relative day (today, tomorrow, Thursday, etc)? Thanks.

This is my current config for my forecast card.

{
                    position: [0, 1],
                    type: TYPES.WEATHER_LIST,
                    width: 3,
                    height: 2,
                    title: 'Forecast',
                    id: {},
                    icons: {
                        'clear-day': 'clear',
                        'clear-night': 'nt-clear',
                        'cloudy': 'cloudy',
                        'rain': 'rain',
                        'sleet': 'sleet',
                        'snow': 'snow',
                        'wind': 'hazy',
                        'fog': 'fog',
                        'partly-cloudy-day': 'partlycloudy',
                        'partly-cloudy-night': 'nt-partlycloudy'
                    },
                    hideHeader: false,
                    secondaryTitle: 'Summary',
                    list: [1,2,3,4,5].map(function (id) {
                    var forecast = "&sensor.dark_sky_overnight_low_temperature_" + id + ".state - ";
                    forecast += "&sensor.dark_sky_daytime_high_temperature_" + id + ".state";
                    forecast += "&sensor.dark_sky_daytime_high_temperature_" + id + ".attributes.unit_of_measurement";

                    var wind = "&sensor.dark_sky_summary_" + id + ".state";

                    return {
                    date: function () {
                    var d = new Date(Date.now() + id * 24 * 60 * 60 * 1000);
                    return d.toString().split(' ').slice(1, 3).join(' ');
                    },
                    icon: "&sensor.dark_sky_icon_" + id + ".state",
                    //iconImage: null, replace icon with image
                    primary: forecast,
                    secondary: wind
                            }
                        })
                    }
               ]
            }

You’d have to use custom.css to make it 2 lines per entry