I’m unsure if I have done something wrong or there is a limitation with the Homey Image Tile? I have tested the API’s using a gauge and they work and am trying to display a Homey Insight chart. I have set up a camera (advanced virtual device) using Device Capabilities app. When I click on the camera (device) it shows the insights image in Homey. I use the device url in Homey Image Tile but no luck - is blank. Can Homey Image Tile display an image from the advanced virtual device?
If you are referring to the Insights Graphs app from the Homey community, I believe displaying the image directly from the Insights Graphs apps would be better in this case. I wrote up some instructions in the original post:
In theory, if the Advanced Virtual Device exposes the Insights Graph image like a camera, you should be able to use it with the Homey Image Viewer custom tile… I would need more information on how you have things configured and how the AVD device is exposing the image though… still, the alternative direct approach linked above is more streamlined
Thanks very much Josh. It’s so simple after exploring a variety of methods. Really appreciated.
Updated the code for flows to also get advanced flows:
<script type="application/json" id="tile-settings">
{
"schema": "0.1.0",
"settings": [
{"name": "token", "label": "Token", "type": "STRING"},
{"name": "homeyCloudId", "label": "Homey Cloud ID", "type": "STRING"},
{
"name": "isFavoritesOnly",
"label": "Only Show Favorites",
"type": "BOOLEAN",
"default": false
}
],
"name": "Homey Flows"
}
</script>
<!-- Do not edit above -->
<script src="https://cdn.sharptools.io/js/custom-tiles.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.21.1/axios.min.js">
</script>
<div class="main-content" id="mainContent">Initializing...</div>
<style>
html, body { margin: 0; height:100%; }
.main-content {
width: 100%; height: 100%;
padding: 0; margin: 0;
display: flex;
align-items: center; justify-content: center;
text-align: center;
font-size: min(20vh, 20vw);
}
</style>
<script>
var getAxiosConfig = function getAxiosConfig() {
return {
"headers": {
"Authorization": "Bearer " + patToken
}
};
};
var onClick = function onClick() {
if (flows.length === 1) {
//execute the one flow we have
executeFlow(flows[0].flowId);
} else {
var list = {
title: "Flows",
items: []
};
//build the list from the flows array
flows.forEach(function(flow) {
list.items.push({
label: flow.name,
value: flow.id
});
});
//show the list
stio.showList(list).then(function(picked) {
//if the user picked something, then execute that flow
if (picked) {
executeFlow(picked);
}
});
}
};
var getData = function getData() {
var promises = [
getFlowsAndAdvancedFlows()
];
if (isFavoritesOnly) {
promises.push(getUserMe());
}
Promise.all(promises).then(function(promiseResults) {
if (isFavoritesOnly) {
flows = favoriteFlowIds.map(function(id) {
return rawFlows[id];
}).filter(function(f) {
return f != null;
});
} else {
flows = Object.values(rawFlows).sort(function(a, b) {
var _b, _a_name, _a;
return (_a = a) === null || _a === void 0 ? void 0 : (_a_name = _a.name) === null || _a_name === void 0 ? void 0 : _a_name.localeCompare((_b = b) === null || _b === void 0 ? void 0 : _b.name, undefined, {
sensitivity: "base"
});
});
}
//formatting
if (flows.length === 1) {
content.innerText = flows[0].name;
} else {
content.innerText = "Flows";
}
}).catch(function(error) {
console.warn("Error in getData. Probably caught earlier", error.message);
});
};
var getFlowsAndAdvancedFlows = function getFlowsAndAdvancedFlows() {
var urlFlows = URL_TEMPLATE.replace("{{cloudId}}", cloudId) + "flow/flow";
var urlAdvancedFlows = URL_TEMPLATE.replace("{{cloudId}}", cloudId) + "flow/advancedflow";
var promiseFlows = axios.get(urlFlows, getAxiosConfig());
var promiseAdvancedFlows = axios.get(urlAdvancedFlows, getAxiosConfig());
return Promise.all([promiseFlows, promiseAdvancedFlows]).then(function(responses) {
var flowsData = responses[0].data || {};
var advancedFlowsData = responses[1].data || {};
// Merge flows and advanced flows
rawFlows = Object.assign({}, flowsData, advancedFlowsData);
}).catch(function(error) {
console.error("Error fetching flows and advanced flows", error);
content.innerText = "Error fetching flows and advanced flows";
throw error;
});
};
var getUserMe = function getUserMe() {
var url = URL_TEMPLATE.replace("{{cloudId}}", cloudId) + "users/user/me";
return axios.get(url, getAxiosConfig()).then(function(response) {
if (!response.data) return;
userProfile = response.data;
var flowIds = userProfile && userProfile.properties && userProfile.properties.favoriteFlows;
if (!Array.isArray(flowIds)) return;
favoriteFlowIds = flowIds;
}).catch(function(error) {
console.error("Error fetching favorites", error);
content.innerText = "Error fetching favorites";
throw error; //rethrow the error so the caller doesn't continue
});
};
var executeFlow = function executeFlow(flowId) {
var url = URL_TEMPLATE.replace("{{cloudId}}", cloudId) + "flow/advancedflow/";
url = url + flowId + "/trigger"; // Use advancedflow instead of flow
axios.post(url, null, getAxiosConfig()).then(function(response) {
console.log("Response Status", response.status);
});
};
var content = document.getElementById("mainContent");
var patToken = null;
var cloudId = null;
var flows = [];
var rawFlows = {};
var userProfile;
var favoriteFlowIds = [];
var isFavoritesOnly = false;
var URL_TEMPLATE = "https://{{cloudId}}.connect.athom.com/api/manager/";
content.onclick = onClick; //setup the click handler
stio.ready(function(data) {
console.log("stio library is ready with token", data.settings.token);
if (data.settings.token == null) {
content.innerText = "Please configure the tile";
return;
} else {
patToken = data.settings.token;
cloudId = data.settings.homeyCloudId;
isFavoritesOnly = !!data.settings.isFavoritesOnly;
}
content.innerText = "Loading...";
getData();
});
</script>
Thanks for sharing! I’ll have to give it a try.
From a quick glance, one thing I noticed is that the executeFlow()
always calls the /advancedflow
path. Does that actually work for the regular flows too? Otherwise it would likely need some logic to determine what type of flow was clicked and which type it needs to execute so executing the regular flows would work too.
To be honest, I didn´t try to execute any regular flows… I don´t have any at the moment to try directly. I will look in to it though. Also, if you do it before me, feel free to edit the code and update! (I´m very new to this and self learned so it might not be perfect)
I have now used Homey Image Viewer to display images from my Ring camera in Homey 2023 - and it works!
But I can’t get the tile to update. If I refresh the screen manually (ie switch between two dashboards), I get a new image, but I can’t get it to happen automatically. Is there something I’m overlooking?
That particular tile refreshes the displayed image every 5 minutes. Per the original post.
It was originally written more as a proof-of-concept than a fully polished tile. If anyone wants to try updating the tile to be more realtime in nature, I would be happy to incorporate their changes in the version in the original post.
Hi Josh … It would be great if you could assign each flow to a tile instead of getting a list of all flows. Can you implement that? VG Uwe
Isn’t there a built-in Logic Card for “A webhook is received” that you could add to a Flow if you wanted to trigger it directly? Then you could copy that URL into a Hyperlink tile.
You could even use the special hyperlink syntax to avoid the Hyperlink Tile opening a new window if you wanted to:
Alternatively, the Homey Flows custom tile is open source, so if anyone wants to modify it to support directly controlling a flow, I’m open to that.