For Tileboard, you can use a custom tile, where customHtml can be anything you want
This includes javascript and all that, I do not know how far you want and can dive into the rabbithole
This is my home page:
var CONTROLS =
{
title: 'Controls',
bg: 'images/bg2.png',
hidden: false,
icon: 'mdi-home',
groups: [
{
width: 4,
height: 4,
groupMarginCss: '7px 0px',
items: [
{
position: [0, 0],
width: 1,
height: 1,
title: 'woonkamer',
type: TYPES.INPUT_BOOLEAN,
state: false,
id: 'input_boolean.avond',
icon: 'mdi-lightbulb'
},
{
position: [0, 1],
width: 0.5,
height: 0.5,
classes: [CLASS_SMALL],
title: 'bank',
type: TYPES.LIGHT,
id: 'light.stekker',
state: false,
icon: "mdi-lightbulb"
},
{
position: [0.5, 1],
width: 0.5,
height: 0.5,
classes: [CLASS_SMALL],
title: 'spot',
type: TYPES.LIGHT,
id: 'light.spot',
state: false,
icon: "mdi-lightbulb"
},
{
position: [0, 1.5],
width: 3,
height: 2,
title: '',
type: TYPES.CAMERA_THUMBNAIL,
id: 'camera.buienradar_loop',
state: false,
refresh: function () {
return 1000
}
},
{
position: [0, 3.5],
type: TYPES.CUSTOM,
id: 'input_boolean.buienradar_regenmeter',
title: '',
width: 3,
height: 1,
state: false,
customStyles: {'background-color': '#4682B4'},
customHtml: function(item, entity) {
var d = new Date(entity.last_changed);
d.setSeconds(d.getSeconds() + Math.round(this.$scope.states['input_number.buienradar_regenmeter_refresh'].state));
if (entity.state == 'on' && Date.now() > d) {
//Turn off and on to update the last_changed property
this.api.callService('input_boolean', 'turn_off', {'entity_id' : entity.entity_id});
this.api.callService('input_boolean', 'turn_on', {'entity_id' : entity.entity_id});
//console.log(d);
//implemented a setTimeout, otherwise filling of the field did not work on iOS 9.3.6
setTimeout(function(){
var labels = new Array();
var values = new Array();
$http = angular.injector(["ng"]).get("$http");
$http.get("https://gpsgadget.buienradar.nl/data/raintext/?lat=53.03&lon=6.22").then(function(data) {
var lines = data.data.split("\n");
for (i=0;i<lines.length;i++) {
line = lines[i];
if (line.length > 0) {
var a = line.split('|');
values.push(a[0]);
labels.push(a[1]);
}
};
//console.log(labels);
if (labels.length == 0) {
var coeff = 1000 * 60 * 5;
now = new Date();
var rounded = new Date(Math.round(now / coeff) * coeff)
labels.push(rounded.getHours() + ':' + rounded.getMinutes());
for(i=0;i<23;i++) {
rounded.setMinutes(rounded.getMinutes() + 5);
labels.push(rounded.getHours() + ':' + rounded.getMinutes());
}
}
this.chart1 = new Chart('br_forecast', {
type: 'line',
data : {
labels: labels,
datasets: [
{
spanGaps: true,
pointStyle: 'circle',
data: values,
borderColor: '#5F9EA0',
backgroundColor: '#5F9EA0',
}
]
},
options: {
legend: {
display: false
},
tooltips: {
enabled:true
},
scales: {
xAxes: [{
type: 'category',
distribution: 'linear',
time: {
parser: 'h:m'
},
gridLines: {
drawTicks: false,
},
ticks: {
padding: 5,
fontSize: 10,
fontColor: '#FFF',
maxRotation: 0,
callback: function(value, index, values) {
if (index == 0) {
return "Nu";
} else {
if (index % 2 != 0) {return ' ';}
var date = new Date();
date.setHours(value.split(':')[0]);
date.setMinutes(value.split(':')[1]);
//padStart does not work in iOS 9.3.6
var dateString = String(date.getHours()).length < 2 ? "0" + String(date.getHours()) : String(date.getHours());
dateString += ":";
dateString += String(date.getMinutes()).length < 2 ? "0" + String(date.getMinutes()) : String(date.getMinutes());
return dateString;
}
}
}
}],
yAxes: [{
gridLines: {
drawTicks: false,
},
position: 'right',
ticks: {
fontSize: 10,
fontColor: '#FFF',
padding: 5,
beginAtZero: true,
suggestedMax: 255,
callback: function(value, index, values) {
return index == 0 ? 'Zwaar' : (index == values.length-1) ? 'Licht' : '';
}
}
}]
},
elements: {
line: {
stepped: false
},
},
}
});
});
}, 1000);
}
return '<div class="item-history"><div class="item-history-container"><canvas class="chart chart-line ng-isolate-scope chartjs-render-monitor" id="br_forecast"></canvas></div></div>';
},
},
{
position: [3, 0],
type: TYPES.WEATHER_LIST,
width: 4,
height: 1.5,
title: '',
id: {},
state: false,
hideHeader: false,
primaryTitle: 'min-max',
secondaryTitle: 'regenkans',
dateTitle: 'dag',
iconTitle: '',
list: [1,2,3,4,5].map(function (id) {
var regen = "&sensor.br_rainchance_" + id + "d.state";
regen += "&sensor.br_rainchance_" + id + "d.attributes.unit_of_measurement";
var temperature = "&sensor.br_minimum_temperature_" + id + "d.state - ";
temperature += "&sensor.br_temperature_" + id + "d.state";
temperature += "&sensor.br_temperature_" + id + "d.attributes.unit_of_measurement";
return {
date: function () {
var d = new Date(Date.now() + id * 24 * 60 * 60 * 1000);
//toLocaleString with localization will not work on iPad
var day = d.getDay();
switch (day) {
case 0: return 'zo';
case 1: return 'ma';
case 2: return 'di';
case 3: return 'wo';
case 4: return 'do';
case 5: return 'vr';
case 6: return 'za';
}
},
iconImage: "&sensor.br_symbol_" + id + "d.attributes.entity_picture",
primary: temperature,
secondary: regen
}
})
},
{
position: [3, 1.5],
type: TYPES.CUSTOM,
id: 'input_boolean.buienradar_rssfeed',
title: '',
width: 4,
height: 3,
state: false,
customStyles: {'background-color': '#4682B4'},
customHtml: function(item, entity) {
var d = new Date(entity.last_changed);
d.setMinutes(d.getMinutes() + Math.round(this.$scope.states['input_number.buienradar_rssfeed_refresh'].state));
if (entity.state == 'on' && Date.now() > d) {
// this.api.callService('input_number', 'set_value', {'entity_id' : 'input_number.buienradar_rssfeed_refresh', 'value': Math.round(this.$scope.states['input_number.buienradar_rssfeed_refresh'].state)});
this.api.callService('input_boolean', 'turn_on', {'entity_id' : entity.entity_id});
this.$scope.states[entity.entity_id].last_changed = Date.now();
//implemented a setTimeout, otherwise filling of the field did not work on iOS 9.3.6
setTimeout(function(){
$http = angular.injector(["ng"]).get("$http");
$http.get("https://data.buienradar.nl/1.0/feed/xml/rssbuienradar").then(function(data) {
var item = new window.DOMParser()
.parseFromString(data.data, "text/xml")
.querySelectorAll("item")[0];
var d = new Date(item.querySelector("pubDate").firstChild.textContent);
//padStart does not work in iOS 9.3.6
var dateString = String(d.getDate()).length < 2 ? "0" + String(d.getDate()) : String(d.getDate());
dateString += "-";
dateString += String(d.getMonth()+1).length < 2 ? "0" + String(d.getMonth()+1) : String(d.getMonth()+1);
dateString += "-" + d.getFullYear();
var i = item.querySelector("pubDate").firstChild.textContent.split(' ');
i.pop();
i = i.slice(1);
document.getElementById('rssTitle').innerHTML = i.join(' ');
document.getElementById('rssText').innerHTML = item.querySelector("description").firstChild.textContent;
});
}, 1000);
}
return '<div id="rssFeed"><div id="rssTitle"></div><div id="rssText"></div></div>';
}
},
]
},
]
}
With my config like this:
function loadJS(url) {
var xhttp = new XMLHttpRequest();
var script = document.createElement( "script" );
xhttp.open("GET", url, false);
xhttp.send();
script.text = xhttp.responseText;
document.head.appendChild( script ).parentNode.removeChild( script );
}
loadJS('pages/cameras.js');
//loadJS('pages/weer.js');
loadJS('pages/controls.js');
//loadJS('pages/system.js');
//loadJS('pages/test.js');
var CONFIG = {
customTheme: CUSTOM_THEMES.COMPACT,
transition: TRANSITIONS.ANIMATED_GPU,
entitySize: ENTITY_SIZES.BIG,
tileSize: 131,
tileMargin: 5,
serverUrl: 'http://192.168.43.54:8123',
wsUrl: 'ws://192.168.43.54:8123/api/websocket',
authToken: 'longlivetokenhomeassistant',
debug: false,
pingConnection: true,
// next fields are optional
events: [],
timeFormat: 24,
menuPosition: MENU_POSITIONS.LEFT,
hideScrollbar: true,
groupsAlign: GROUP_ALIGNS.HORIZONTALLY,
onReady: function () {
this.$scope.isPanEnabled = false;
},
screensaver: {
timeout: 300,
slidesTimeout: 10,
styles: { fontSize: '40px' },
leftBottom: [
{
type: SCREENSAVER_ITEMS.DATETIME,
dateFormat: 'EEEE dd LLLL',
}
],
slides: [
{
bg: 'images/bg2.png',
rightTop: [ // put text to the 2nd slide
{
type: SCREENSAVER_ITEMS.CUSTOM_HTML,
html: 'Tik om te beginnen',
styles: { fontSize: '40px' }
}
]
},
{
bg: 'images/bg2.png',
leftTop: [
{
type: SCREENSAVER_ITEMS.CUSTOM_HTML,
html: 'Tik om te beginnen',
styles: { fontSize: '40px' }
}
]
},
{
bg: 'images/bg2.png',
rightBottom: [
{
type: SCREENSAVER_ITEMS.CUSTOM_HTML,
html: 'Tik om te beginnen',
styles: { fontSize: '40px' }
}
]
}
]
},
header: {
styles: {
margin: '10px 110px 0',
fontSize: '28px'
},
right: [
{
type: HEADER_ITEMS.WEATHER,
styles: {
fontSize: '20px'
},
fields: {
summary: function(item,entity) {
return this.$scope.states['sensor.br_symbol'].state;//.substring(0, 25);
},
temperature: '&sensor.br_temperature.state &sensor.br_temperature.attributes.unit_of_measurement (voelt als &sensor.br_feel_temperature.state)',
}
}
],
left: [
{
type: HEADER_ITEMS.DATETIME,
dateFormat: 'EEEE dd LLLL',
}
]
},
pages: [
// TEST,
CONTROLS,
// WEER,
CAMERAS,
// SYSTEM
],
}
It produces this page: