[BETA] Homey Custom Tiles

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

1 Like

Thanks very much Josh. It’s so simple after exploring a variety of methods. Really appreciated.

1 Like

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>

1 Like

Thanks for sharing! I’ll have to give it a try. :star_struck:

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. :slight_smile:

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)

1 Like