Skip to content

Instantly share code, notes, and snippets.

@Jwerfel
Created October 5, 2020 16:24
Show Gist options
  • Save Jwerfel/ae0b3197ef47aa3662d0af75e423a4b7 to your computer and use it in GitHub Desktop.
Save Jwerfel/ae0b3197ef47aa3662d0af75e423a4b7 to your computer and use it in GitHub Desktop.
Scriptable widget for displaying device status from SmartThings
// 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();
}
@Jwerfel
Copy link
Author

Jwerfel commented Oct 5, 2020

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:

module.exports.devices = () => {
let deviceConfig = [
{name: 'Garage', id: '', type: 'contact'},
{name: 'Front Door', id: '', type: 'lock'}
];
return deviceConfig
}

Currently only contact and lock is supported.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment