Skip to content

Instantly share code, notes, and snippets.

@m33x
Last active September 17, 2024 12:21
Show Gist options
  • Save m33x/62f6e8f6eab546e4b3a854695ea8c3a8 to your computer and use it in GitHub Desktop.
Save m33x/62f6e8f6eab546e4b3a854695ea8c3a8 to your computer and use it in GitHub Desktop.
Simple Home Assistant (HASS) iOS Widget via Scriptable App
let widget = await createWidget();
if (!config.runsInWidget) {
await widget.presentSmall();
}
Script.setWidget(widget);
Script.complete();
async function createWidget(items) {
/* Get data from API */
const tempImg = await getImage('temperature.png');
const humidImg = await getImage('humidity.png');
const logoImg = await getImage('hass-favicon.png');
let req = new Request("https://<HASS IP>/api/states")
req.headers = { "Authorization": "Bearer <HASS Long-Lived Access Token at https://<HASS IP>/profile", "content-type": "application/json" }
let json = await req.loadJSON();
/* Parse data received from API */
let data = {outdoor: {}, child: {}, office: {}, bedroom: {}, livingroom: {}, kitchen: {}, hallway: {}, bathroom: {}, wc: {}}
data.outdoor = addData(json, data.outdoor, ['sensor.ble_temperature_aabbccddeeff', 'sensor.ble_humidity_aabbccddeeff']);
data.child = addData(json, data.child, ['sensor.ble_temperature_aabbccddeeff', 'sensor.ble_humidity_aabbccddeeff']);
data.office = addData(json, data.office, ['sensor.ble_temperature_aabbccddeeff', 'sensor.ble_humidity_aabbccddeeff']);
data.bedroom = addData(json, data.bedroom, ['sensor.ble_temperature_aabbccddeeff', 'sensor.ble_humidity_aabbccddeeff']);
data.livingroom = addData(json, data.livingroom, ['sensor.ble_temperature_aabbccddeeff', 'sensor.ble_humidity_aabbccddeeff']);
data.kitchen = addData(json, data.kitchen, ['sensor.ble_temperature_aabbccddeeff', 'sensor.ble_humidity_aabbccddeeff']);
data.hallway = addData(json, data.hallway, ['sensor.ble_temperature_aabbccddeeff', 'sensor.ble_humidity_aabbccddeeff']);
data.bathroom = addData(json, data.bathroom, ['sensor.ble_temperature_aabbccddeeff', 'sensor.ble_humidity_aabbccddeeff']);
data.wc = addData(json, data.wc, ['sensor.ble_temperature_aabbccddeeff', 'sensor.ble_humidity_aabbccddeeff']);
/* Create the widget */
const widget = new ListWidget();
widget.backgroundColor = new Color("#03a9f4", 1.0);
/* Design the widget header */
let headerStack = widget.addStack();
const logoStack = headerStack.addStack();
headerStack.addSpacer(2);
const titleStack = headerStack.addStack();
headerStack.addSpacer(7);
const tempImageStack = headerStack.addStack();
headerStack.addSpacer(14);
const humidImageStack = headerStack.addStack();
/* Add a logo icon */
logoStack.backgroundColor = new Color("#03a9f4", 1.0)
logoStack.cornerRadius = 1
const wimgLogo = logoStack.addImage(logoImg)
wimgLogo.imageSize = new Size(20, 20)
wimgLogo.rightAlignImage()
/* Add the name of this Home Assistant */
const titleLabel = titleStack.addText("HA42");
titleStack.setPadding(2, 0, 0, 0);
titleLabel.font = Font.heavyMonospacedSystemFont(12);
titleLabel.textColor = Color.black();
/* Add a temperature icon */
tempImageStack.backgroundColor = new Color("#03a9f4", 1.0)
tempImageStack.cornerRadius = 1
const wimgTemp = tempImageStack.addImage(tempImg)
wimgTemp.imageSize = new Size(20, 20)
wimgTemp.rightAlignImage()
/* Add a humid icon */
humidImageStack.backgroundColor = new Color("#03a9f4", 1.0)
humidImageStack.cornerRadius = 1
const wimgHumid = humidImageStack.addImage(humidImg)
wimgHumid.imageSize = new Size(20, 20)
wimgHumid.rightAlignImage()
widget.addSpacer(5)
/* Add the sensor entries */
const bodyStack = widget.addStack();
/* First, the label column */
const labelStack = bodyStack.addStack();
labelStack.setPadding(0, 0, 0, 0);
labelStack.borderWidth = 0;
labelStack.layoutVertically();
addLabel(labelStack, " Outdoor:")
addLabel(labelStack, " Child:")
addLabel(labelStack, " Office:")
addLabel(labelStack, " Bedroom:")
addLabel(labelStack, " Living:")
addLabel(labelStack, " Kitchen:")
addLabel(labelStack, " Hallway:")
addLabel(labelStack, " Bathroom:")
addLabel(labelStack, " WC:")
/* Second, the temperature column */
const tempStack = bodyStack.addStack();
tempStack.setPadding(0, 3, 0, 0);
tempStack.borderWidth = 0;
tempStack.layoutVertically();
addTemp(tempStack, data.outdoor)
addTemp(tempStack, data.child)
addTemp(tempStack, data.office)
addTemp(tempStack, data.bedroom)
addTemp(tempStack, data.livingroom)
addTemp(tempStack, data.kitchen)
addTemp(tempStack, data.hallway)
addTemp(tempStack, data.bathroom)
addTemp(tempStack, data.wc)
/* Third, the humidity column */
const humidStack = bodyStack.addStack();
humidStack.setPadding(0, 5, 0, 0);
humidStack.borderWidth = 0;
humidStack.layoutVertically();
addHumid(humidStack, data.outdoor)
addHumid(humidStack, data.child)
addHumid(humidStack, data.office)
addHumid(humidStack, data.bedroom)
addHumid(humidStack, data.livingroom)
addHumid(humidStack, data.kitchen)
addHumid(humidStack, data.hallway)
addHumid(humidStack, data.bathroom)
addHumid(humidStack, data.wc)
/* Done: Widget is now ready to be displayed */
return widget;
}
/* Adds the entries to the label column */
async function addLabel(labelStack, label) {
const mytext = labelStack.addText(label);
mytext.font = Font.semiboldSystemFont(10);
mytext.textColor = Color.black();
}
/* Adds the entries to the temperature column */
async function addTemp(tempStack, data) {
const mytext = tempStack.addText(data.temp + "°C");
mytext.font = Font.heavyMonospacedSystemFont(10);
mytext.textColor = Color.white();
}
/* Adds the entries to the humidity column */
async function addHumid(humidStack, data) {
const mytext = humidStack.addText("(" + data.humid + "%)");
mytext.font = Font.mediumMonospacedSystemFont(10);
mytext.textColor = Color.white();
mytext.textOpacity = 0.8;
}
/*
The following function is "borrowed" from:
https://gist.github.com/marco79cgn/23ce08fd8711ee893a3be12d4543f2d2
Retrieves the image from the local file store or downloads it once
*/
async function getImage(image) {
let fm = FileManager.local()
let dir = fm.documentsDirectory()
let path = fm.joinPath(dir, image)
if (fm.fileExists(path)) {
return fm.readImage(path)
} else {
// download once
let imageUrl
switch (image) {
case 'temperature.png':
imageUrl = "https://<YOU NEED TO HOST THIS>/temperature.png"
break
case 'humidity.png':
imageUrl = "https://<YOU NEED TO HOST THIS>/humidity.png"
break
case 'hass-favicon.png':
imageUrl = "https://<HASS IP>/static/icons/favicon-192x192.png"
break
default:
console.log(`Sorry, couldn't find ${image}.`);
}
let iconImage = await loadImage(imageUrl)
fm.writeImage(path, iconImage)
return iconImage
}
}
/*
The following function is "borrowed" from:
https://gist.github.com/marco79cgn/23ce08fd8711ee893a3be12d4543f2d2
Downloads an image from a given URL
*/
async function loadImage(imgUrl) {
const req = new Request(imgUrl)
return await req.loadImage()
}
/* Searches for the respective sensor values ('state') in the API response of Home Assistant */
function addData(json, room, sensors) {
room.temp = "N/A";
room.humid = "N/A";
var i;
for (i = 0; i < json.length; i++) {
if (json[i]['entity_id'] == sensors[0]) {
room.temp = Math.round(json[i]['state']);
}
if (json[i]['entity_id'] == sensors[1]) {
room.humid = Math.round(json[i]['state']);
}
}
return room;
}
@jacobhallgren
Copy link

How often does it update the values?

@m33x
Copy link
Author

m33x commented Feb 18, 2022

I could not find any constant refresh interval, looks like, it is updated on-demand and depends on how often you look at your home screen.

10.0.0.110 - - [18/Feb/2022:09:36:42 +0100] "GET /api/states HTTP/1.1" 200 27597 "-" "ScriptableWidgetExtension/187 CFNetwork/1329 Darwin/21.3.0"
10.0.0.110 - - [18/Feb/2022:09:41:50 +0100] "GET /api/states HTTP/1.1" 200 27591 "-" "ScriptableWidgetExtension/187 CFNetwork/1329 Darwin/21.3.0"
10.0.0.110 - - [18/Feb/2022:09:47:00 +0100] "GET /api/states HTTP/1.1" 200 27567 "-" "ScriptableWidgetExtension/187 CFNetwork/1329 Darwin/21.3.0"
10.0.0.110 - - [18/Feb/2022:10:00:02 +0100] "GET /api/states HTTP/1.1" 200 27618 "-" "ScriptableWidgetExtension/187 CFNetwork/1329 Darwin/21.3.0"
10.0.0.110 - - [18/Feb/2022:10:15:20 +0100] "GET /api/states HTTP/1.1" 200 27590 "-" "ScriptableWidgetExtension/187 CFNetwork/1329 Darwin/21.3.0"
10.0.0.110 - - [18/Feb/2022:10:47:44 +0100] "GET /api/states HTTP/1.1" 200 27607 "-" "ScriptableWidgetExtension/187 CFNetwork/1329 Darwin/21.3.0"
10.0.0.110 - - [18/Feb/2022:10:53:14 +0100] "GET /api/states HTTP/1.1" 200 27644 "-" "ScriptableWidgetExtension/187 CFNetwork/1329 Darwin/21.3.0"
10.0.0.110 - - [18/Feb/2022:11:14:55 +0100] "GET /api/states HTTP/1.1" 200 27766 "-" "ScriptableWidgetExtension/187 CFNetwork/1329 Darwin/21.3.0"
10.0.0.110 - - [18/Feb/2022:11:29:51 +0100] "GET /api/states HTTP/1.1" 200 27811 "-" "ScriptableWidgetExtension/187 CFNetwork/1329 Darwin/21.3.0"
10.0.0.110 - - [18/Feb/2022:11:35:19 +0100] "GET /api/states HTTP/1.1" 200 27767 "-" "ScriptableWidgetExtension/187 CFNetwork/1329 Darwin/21.3.0"
10.0.0.110 - - [18/Feb/2022:12:00:06 +0100] "GET /api/states HTTP/1.1" 200 27762 "-" "ScriptableWidgetExtension/187 CFNetwork/1329 Darwin/21.3.0"
10.0.0.110 - - [18/Feb/2022:12:15:31 +0100] "GET /api/states HTTP/1.1" 200 27740 "-" "ScriptableWidgetExtension/187 CFNetwork/1329 Darwin/21.3.0"
10.0.0.110 - - [18/Feb/2022:12:45:03 +0100] "GET /api/states HTTP/1.1" 200 27764 "-" "ScriptableWidgetExtension/187 CFNetwork/1329 Darwin/21.3.0"
10.0.0.110 - - [18/Feb/2022:13:00:05 +0100] "GET /api/states HTTP/1.1" 200 27731 "-" "ScriptableWidgetExtension/187 CFNetwork/1329 Darwin/21.3.0"
10.0.0.110 - - [18/Feb/2022:13:14:53 +0100] "GET /api/states HTTP/1.1" 200 27742 "-" "ScriptableWidgetExtension/187 CFNetwork/1329 Darwin/21.3.0"

@FreelandA
Copy link

@jonibaer
Copy link

How do I add individual text like „km“ or „min“ after each sensor data? I do not want to add „%“ to the whole column.

@malosaa
Copy link

malosaa commented Jul 13, 2023

ext like „km“ or „min“ after each sensor data? I do not want to add „%“ to the whole column.

for example i wanted to return the produce of my solar panels.
let valueLabel = mainStack.addText(${result.value}W);
This is based on the gauge script on this gist.

the "W" is like you want or km or min.

see my full script an example of a forked (edited) version from gauge script m33x
https://gist.github.com/malosaa/c2611c8e490d6e0af1b5366577b5e2ba

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