TileBoard - New dashboard for Homeassistant

You’re absolutely right. That was not my intent at all, at the time I just didn’t want anyone else to have to waste their time on a response to my question. I am still fine-tuning my solution but have every intention of sharing the code here when it’s complete.

1 Like

I updated my post above with the code to make an HTTP request when TileBoard loads. However, one thing to note is that my testing indicated that if you are not using a long-lived access token, the request will be triggered when the page first loads and kicks you to the authentication prompt, and then again when the page actually loads once you’ve authenticated. So, if you are attempting to process something based on your HTTP call, you’ll want to add logic on the other side to filter the duplicates.

I have completed an AppDaemon script that will allow for auto-generation of the screensaver, as well as auto-randomization. The tileboard.py script is in my github repository, along with instructions on how to set it up: https://github.com/apop880/AppDaemon - let me know if you have any questions!

Hi. Great work on this. I was unfamiliar with js before but am getting the hang on it now. I just wanted to ask if you would consider implementing the icons: functionality for input_select tiles? It would be nice to have a graphical representation of the current state of the option selected and there is space on the tile to show an icon in compact form.

Ditto, maybe you could add the option to show a compact icon in a normal sensor tile, just to add a visual indicator as to what you’re looking at. In my case, I am showing sunrise and sunset on sensors, and it would be nice to show an icon to represent each above the sensor value in the available space.

Thanks again for your work on this! :+1:

I can’t guarantee it won’t break anything but I can tell you the way I fixed this.

In the styles folder, make a backup copy of main.css.
Then open main.css with a text editor.
Find the section that starts .page-align
Then change the value for margin-left: to, say 80px.
Save and refresh. That worked for me.

I would highly recommend breaking that section out into a custom.css file instead. TileBoard loads custom.css last so any customizations can be put in that file and will supersede what’s in main.css. That way, you can stay up to date on the latest files from the repository without overwriting any of your own changes. Config.js and custom.css should be the only files you touch.

Good idea! I’ll check the docs on adding a custom css file. I’m kinda new at this :grinning:

EDIT: that was pretty straightforward. Thanks.

No problem, I got burned on this when I first started and started making changes to main.css before I realized there was a custom.css file. So, I had to compare my main.css with the original copy and move all my changes to custom.css last week so I could update to the latest release. Happy to save someone else the trouble. :smile:

1 Like

apop: Your screensaver AppDaemon App and hass.io additions is exactly what I’ve been looking to achieve (random screensaver with support for images that can be changed easily frequently). I followed the directions as per the README, added the event called ss_update but I don’t think its grabbing the list of URLS to use for the screensaver because when calling ss_update I get the following console error (I’m assuming the the length of the list is 0?):

main.js:1813 
TypeError: Cannot read property 'length' of undefined
        at Object.action (config.js:67)
        at callFunction (main.js:1674)
        at main.js:1790
        at Array.forEach (<anonymous>)
        at triggerEvents (main.js:1786)
        at handleEvent (main.js:1810)
        at handleMessage (main.js:1796)
        at main.js:1634
        at api.js:187

Here is the bottom of my config.js - is this correct?

				}	// driveway camera end
              ] //item end
           },
        ] //group end
     }

   ], //pages end
}

     if (CONFIG.screensaver && CONFIG.flickrApiKey) {
     var url = 'https://api.flickr.com/services/rest/?method=flickr.photosets.getPhotos'
            + '&photoset_id=' + CONFIG.flickrAlbum + '&api_key=' + CONFIG.flickrApiKey + '&format=json&nojsoncallback=1';
            
    var req = new XMLHttpRequest();
    req.open('GET', url, true);
    req.onreadystatechange = function() {
        if (req.readyState === 4) {
            var res = JSON.parse(req.responseText);
            if (res.photoset) res.photoset.photo.forEach(function(p) {
                CONFIG.screensaver.slides.push({
                    bg: 'https://farm' + p.farm + '.staticflickr.com/' + p.server + '/' + p.id + '_' + p.secret + '_h.jpg'
                });
            });
        }
    };
    req.send(null);

/* start #via https://github.com/apop880/AppDaemon */
	var webhook_endpoint = CONFIG.serverUrl + "/api/webhook/tb_update";

	var xhttp = new XMLHttpRequest;
	xhttp.open("POST", webhook_endpoint, true);
	xhttp.send();

	function ss_update_func() {
		var xhttp = new XMLHttpRequest;
		xhttp.open("POST", webhook_endpoint, true);
		xhttp.send();
  }
/* end #via https://github.com/apop880/AppDaemon**/
}

I modified the path in the AppDaemon script to suite my setup, and there are definitely JPEG images there (96 total):

a0d7b954-ssh:~/config/www# ls /config/www/images/screensaver/*.jpg | wc -l
96

The part I can’t figure out is when Tileboard loads it makes a call to /api/webhook/tb_update and that has an automation to handle the request but what actually forwards that request to my AppDaemon instance?

Nothing jumps out as me as being off with your code here. The only thing I see is that maybe your Flickr section is in conflict with my process. You could try commenting that part of config.js out. Otherwise, do you mind sending your full anonymized config file and your customized AppDaemon script?

The YAML automation gets to AppDaemon by firing an event, which the AppDaemon script has a listener set up for.

automations.yaml:

action:
    event: tb_update

and the relevant portion of tileboard.py:

self.listen_event(self.tb_throttle, "tb_update")

Edit: I tried removing the flicker code & the screensaver images array but I still have the same issue.
config.js: https://pastebin.com/4SJ5hv1h
tileboard.py: https://pastebin.com/k6JKA8YC
apps.yaml: https://pastebin.com/k6JKA8YC
automations.yaml below (relevant section)

- id: '6542345908765'
  alias: 'Tileboard Webhook'   
  trigger:
    platform: webhook
    webhook_id: tb_update
  action:   
    event: tb_update

The thing I can’t understand is how does the hass instance interface with the AppDaemon script? I get what the event webhook is doing but how does it actually communicate with the AppDaemon process? I don’t see a URL or hostname at all and my AppDaemon runs on a different port & IP address.

I’m a little confused on your pastebin links, it looks like you accidentally gave me the apps.yaml link twice, and config.js looks to maybe be an old version (it’s got the flickr portion at the bottom but not the webhook call, and it’s missing the ss_update code in the events portion of the config.

In terms of the webhook, this code that needs to go at the bottom is what drives that. Defining your YAML automation creates an endpoint at [server_url]/api/webhook/tb_update and then we hit it here, initially when the page loads, and then inside ss_update_func() which should be called each time the end of the screensaver is reached (defined inside the ss_update event)

var webhook_endpoint = CONFIG.serverUrl + "/api/webhook/tb_update";

var xhttp = new XMLHttpRequest;
xhttp.open("POST", webhook_endpoint, true);
xhttp.send();

function ss_update_func() {
	var xhttp = new XMLHttpRequest;
	xhttp.open("POST", webhook_endpoint, true);
	xhttp.send();
}

I just reset everything and tried again and it works perfectly - I must have had an error in the config preventing the last block from running. Thank you for your project!

No problem, glad you’ve got it working!

Guys, is it possible to apply customStyles after a page is loaded? i.e. I have some critical sensors I’d like to update the css styles for, without refreshing the page…

Yes, you can map customStyles to a function. The following posts give a couple of examples of how to do so:

I do this already, but I need to refresh for the css properties to update it seems…

I’m using keyframes to flash items dependant on state, example:

/* Red */
@keyframes alert
{
  0%   {background-color:rgba(255, 0, 0, 1);}
  30%   {background-color:rgba(255, 0, 0, 1);}
  50%   {background-color:rgba(255, 0, 0, 0.5);}
  70%   {background-color:rgba(255, 0, 0, 1);}
  100%   {background-color:rgba(255, 0, 0, 1);}
}

I’m not convinced that should stop updates without a refresh however…

It should be updating automatically, I’d have to defer to @resoai for ideas if it’s not.

EDIT Where is that keyframes section, is that in your config.js or your custom.css? What does the customStyles portion of your config.js look like?

As long as the state changes, you should be able to control this entirely with css and a new class. The HTML element will refresh on state change of the tile.

An example which doesn’t work without refresh:

		}, {
			position: [2, 0],
			type: TYPES.SENSOR_ICON,
			title: 'Coop Door',
			id: 'binary_sensor.duck_coop_door_open',
			states: function(item, entity) {
				var timeOfDay = this.states['sensor.day_night'].state;
				if (entity.state == 'on' && timeOfDay == 'night') {
					return item.state = 'Open & Night';
				}
				if (entity.state == 'on') {
					return item.state = 'Open';
				}
				if (entity.state == 'off') {
					return item.state = 'Closed';
				}
			},
			icons: {
				on: 'mdi-door-open',
				off: 'mdi-door-closed'
			},
			customStyles: function(item, entity) {
				// Show alert if door open after sun down
				if (entity.state == 'on' && this.states['sensor.day_night'].state == 'night') {
					return {
						'animation': 'alert 4s linear infinite',
					}
				}
			}
		}, {

Guessing it’s something about the animation specifically it doesn’t like. I’m not very familiar with CSS animations so I’m probably not going to be much help to you there.