-
-
Save Jwerfel/ae0b3197ef47aa3662d0af75e423a4b7 to your computer and use it in GitHub Desktop.
Scriptable widget for displaying device status from SmartThings
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Variables used by Scriptable. | |
// These must be at the very top of the file. Do not edit. | |
// icon-color: blue; icon-glyph: magic; | |
let devicesModule = importModule('devices') | |
let deviceConfig = devicesModule.devices() | |
const keychainKey = "house_bearer"; | |
var bearerTokenId = ""; | |
if (Keychain.contains(keychainKey)) { | |
bearerTokenId = Keychain.get(keychainKey); | |
} else { | |
// Alerts aren't supported in widgets, so you need to run this script manually to set this up | |
alert = new Alert(); | |
alert.addTextField("Bearer Token ID", ""); | |
await alert.presentAlert(); | |
bearerTokenId = alert.textFieldValue(0); | |
Keychain.set(keychainKey, bearerTokenId); | |
} | |
let devices = await getDeviceStatuses(deviceConfig); | |
let widget = await createWidget(devices) | |
// Check if the script is running in | |
// a widget. If not, show a preview of | |
// the widget to easier debug it. | |
if (!config.runsInWidget) { | |
await widget.presentMedium() | |
} | |
// Tell the system to show the widget. | |
Script.setWidget(widget) | |
Script.complete() | |
async function createWidget(devices) { | |
let dateFormatter = new DateFormatter() | |
dateFormatter.useFullDateStyle() | |
dateFormatter.useShortTimeStyle() | |
dateFormatter.dateFormat = 'M/d h:mm a'; | |
let gradient = new LinearGradient() | |
gradient.locations = [0, 1] | |
gradient.colors = [ | |
new Color("#b00a0fe6"), | |
new Color("#b00a0fb3") | |
] | |
let w = new ListWidget() | |
// w.backgroundGradient = gradient | |
// Add spacer above content to center it vertically. | |
w.addSpacer() | |
let row = w.addStack(); | |
let anyOpen = false; | |
for (let i = 0; i < devices.length; i++) { | |
let device = devices[i]; | |
let stack = row.addStack(); | |
stack.layoutVertically(); | |
stack.centerAlignContent() | |
// stack.borderWidth=2 | |
let text = stack.addText(device.name); | |
text.centerAlignText(); | |
stack.addSpacer(4); | |
let image = stack.addImage(device.image); | |
image.centerAlignImage(); | |
stack.addSpacer(4); | |
let statusDate = new Date(Date.parse(device.lastUpdate)); | |
let statusDateString = dateFormatter.string(statusDate); | |
let dateText = stack.addText(statusDateString); | |
dateText.font = Font.mediumSystemFont(12) | |
if (i < (devices.length - 1)) { | |
row.addSpacer(20); | |
} | |
if (device.open) | |
anyOpen = true; | |
} | |
if (anyOpen) { | |
w.backgroundColor = Color.red();// new Color("#b00a0f") // red | |
} | |
else { | |
w.backgroundColor = Color.green(); | |
} | |
w.addSpacer(); | |
let dateRow = w.addStack(); | |
dateRow.layoutHorizontally(); | |
dateFormatter.dateFormat = 'h:mm a'; | |
let wdate = dateRow.addDate(new Date()) | |
wdate.applyRelativeStyle() | |
wdate.font = Font.mediumSystemFont(12); | |
if (anyOpen) | |
wdate.textColor = Color.yellow(); | |
else | |
wdate.textColor = Color.darkGray() | |
wdate.textOpacity = 0.9; | |
wdate.leftAlignText() | |
dateRow.addSpacer() | |
let nowString = dateFormatter.string(new Date()); | |
let nowTxt = dateRow.addText('Updated: ' + nowString); | |
nowTxt.font = Font.mediumSystemFont(12); | |
if (anyOpen) | |
nowTxt.textColor = Color.yellow(); | |
else | |
nowTxt.textColor = Color.darkGray() | |
nowTxt.textOpacity = 0.9; | |
nowTxt.rightAlignText(); | |
w.addSpacer() | |
return w | |
} | |
async function getImage(status, deviceType) { | |
let filename = ''; | |
switch (deviceType) { | |
case 'lock': | |
if (status == 'locked') { | |
return SFSymbol.named('lock').image; | |
// filename = 'lock_locked.png'; | |
} | |
else { | |
return SFSymbol.named('lock.open').image; | |
// filename = 'lock_unlocked.png'; | |
} | |
break; | |
case 'contact': | |
if (status == 'closed') { | |
filename = 'garagedoor_closed.png'; | |
} | |
else { | |
filename = 'garagedoor_open.png'; | |
} | |
break; | |
} | |
let fm = FileManager.iCloud(); | |
let dir = fm.documentsDirectory(); | |
let path = fm.joinPath(dir, "images"); | |
path = fm.joinPath(path, filename); | |
await fm.downloadFileFromiCloud(path); | |
return await fm.readImage(path); | |
} | |
async function getDeviceStatuses(deviceConfig) { | |
return new Promise((resolve, reject) => { | |
var promises = []; | |
for (let i = 0; i < deviceConfig.length; i++) { | |
let d = deviceConfig[i]; | |
promises.push(getDevice(d.name, d.id, d.type)); | |
} | |
console.log('promise length: ' + promises.length); | |
Promise.all(promises).then(values => { | |
console.log('promise.all is done, values: ' + values.length); | |
resolve(values); | |
}); | |
}); | |
} | |
async function getDevice(name, deviceId, deviceType) { | |
return new Promise(async (resolve, reject) => { | |
let json = await getDeviceStatusJson(deviceId); | |
let open = false; | |
let lastUpdate = null; | |
let value = null; | |
switch (deviceType) { | |
case 'lock': | |
value = json.lock.lock.value; | |
lastUpdate = json.lock.lock.timestamp; | |
open = (value != 'locked'); | |
break; | |
case 'contact': | |
value = json.contactSensor.contact.value; | |
lastUpdate = json.contactSensor.contact.timestamp; | |
open = (value != 'closed'); | |
break; | |
} | |
var image = await getImage(value, deviceType); | |
json.image = image; | |
json.name = name; | |
json.open = open; | |
json.lastUpdate = lastUpdate; | |
resolve(json); | |
}); | |
} | |
async function getDeviceStatusJson(deviceId) { | |
let url = "https://api.smartthings.com/v1/devices/" + deviceId + "/components/main/status"; | |
let req = new Request(url); | |
req.headers = { "Authorization": "Bearer " + bearerTokenId }; | |
return req.loadJSON(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
To make this work, you will need a SmartThings API key and to create a new second module called devices.js that contains the devices to load. Here is the format:
Currently only contact and lock is supported.