Skip to content

Instantly share code, notes, and snippets.

@joshualyon
Last active August 18, 2022 18:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joshualyon/e17ca4b80b3a82c5730d12ee337de12f to your computer and use it in GitHub Desktop.
Save joshualyon/e17ca4b80b3a82c5730d12ee337de12f to your computer and use it in GitHub Desktop.
Double Hubitat Custom Tile
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.21.1/axios.min.js"></script>
<script src="//cdn.sharptools.io/js/custom-tiles.js"></script>
<script>
//stub a variable to hold the settings at a higher scope
// var settings; //apiSample, deviceId, attribute
var tileSettings = {
apiSample: "XXXXXXXX",
deviceId1: "2374",
attribute1: "chart",
deviceId2: "2146",
attribute2: "chart",
}
var apiSettings = {};
var timerId;
// var REFRESH_INTERVAL = 60 * 60 * 1000; //every 60 minutes?
var REFRESH_INTERVAL = 3 * 60 * 1000; // every 3 minutes
//get the content element so we can update it later
var contentEl = document.getElementById("content1");
var contentE2 = document.getElementById("content2");
//when the tile is ready
stio.ready(function(data){
//get the settings from the callback
// tileSettings = data.settings; //token, deviceId, attribute
//and initialize the tile
init();
});
var tapState = 0
function init(){
//parse the relevant settings out of the apiSample
parseApiSample();
//make a call to the Hubitat API to get some data
refresh().then(function(){
//if the first refresh is successful, schedule the periodic refreshes
timerId = setInterval(refresh, REFRESH_INTERVAL);
});
}
//helper method for logging an error to console, showing a toast, and updating the tile to display 'Error'
function showError(message){
console.error(message);
stio.showToast(message, "red");
contentEl.innerText = "Error";
contentE2.innerText = "Error";
}
//parse out the various components from a provided Maker API URL
function parseApiSample(sampleUrl){
//if we weren't passed in an explicit URL to parse
if(sampleUrl == null){
//then try to use the sample API URL from the tile settings
sampleUrl = tileSettings.apiSample;
}
//if no URL was provided, let the user know
if(sampleUrl == null || sampleUrl === "") showError("No API URL was provided. Please configure the tile.");
//if the api isn't the cloud API, let the user know
if(sampleUrl.indexOf("cloud.hubitat.com") < 0) showError("Please use the Hubitat Maker API CLOUD URI.")
//try to parse out the various parts of the URL we need with a regular expression
var re = /https:\/\/cloud\.hubitat\.com\/api\/([^\/]+)\/apps\/([\d]+)\/[^\?]+\?access_token\=([^\&]+)/;
var match = sampleUrl.match(re); //array of the various regex matches (0: full string, 1: hub id, 2: app id, 3: token)
//pass the parsed settings back into the top-level variable
apiSettings = {
"hubId": match[1],
"appId": match[2],
"token": match[3]
};
}
//helper function to format the parsed data back into a base URI
function getBaseUrl(){
return `https://cloud.hubitat.com/api/${apiSettings.hubId}/apps/${apiSettings.appId}`
}
//helper function to format the token into an axios 'data' object to attach the token as a parameter
function getAxiosConfig(){
return {params: {access_token: apiSettings.token}};
}
function refresh(){
let promise1 = getThing(1)
let promise2 = getThing(2)
return Promise.all([promise1, promise2]).then(result=> {
//try to find the desired parameter
let attribute1 = result[0].attributes.find(function(attr){ return attr.name === tileSettings.attribute1});
let attribute2 = result[1].attributes.find(function(attr){ return attr.name === tileSettings.attribute2});
//if we didn't get the attribute, bail out
if(attribute1 == null || attribute2 == null) return showError("Could not find desired attribute");
console.log('got attribute1', attribute1);
console.log('got attribute2', attribute2);
//showError(attribute.currentValue)
//otherwise inject the attribute value as HTML
var contentEl = document.getElementById("content1");
contentEl.innerHTML = attribute1.currentValue
var contentE2 = document.getElementById("content2");
contentE2.innerHTML = attribute2.currentValue
event.preventDefault()
})
}
function getThing(num){
var deviceId = tileSettings.deviceId1
if (num == 2) deviceId = tileSettings.deviceId2
let url = getBaseUrl() + `/devices/${deviceId}` + `?access_token=` + apiSettings.token
let config = getAxiosConfig();
//make the API call
return axios.get(url).then(function(response){
//if we got a response with the expected base data
if(response.data && response.data.attributes){
//return the response
return response.data; //TODO: we could parse it into a more helpful "Thing" object format
}
}).catch(function(error){
showError("Error communicating with Maker API with url of:" + url)
})
}
</script>
<style>
html,body {height: 100%;margin:0;}
/*
.main-content {
display: flex;
height:100%;
align-items: center;
justify-content: center;
}
*/
/*
.main-content #content {
text-align: center; /* OPTIONAL center align any text that gets injected */
}
/*
.main-content #content img {
max-width: 100%; /* OPTIONAL scale any inner images if they're too big */
}
*/
*/
/* OPTIONAL APPROACH FOR KNOWN CONTENT FORMATS
(Uncomment below CSS to use)
Another vertical auto-scale approach
where the expected inner content from
the attribute is known.
Makes the 'content' holder flex so
we can center and forces the content
into a column.
Then restricts the to a maximum height
and hides the br since it shouldn't
display in flex layout.
*/
/*
#content {
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
align-items: center;
justify-content: center;
}
#content img {
max-height: 70%;
}
#content br { display: none; }
*/
</style>
<div class="main-content" id="main-content">
<span id="content1"></span>
<span id="content2"></span>
</div>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment