Skip to content

Instantly share code, notes, and snippets.

@m33x
Last active April 27, 2024 22:35
Show Gist options
  • Star 70 You must be signed in to star a gist
  • Fork 12 You must be signed in to fork a gist
  • 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;
}
@m33x
Copy link
Author

m33x commented Feb 24, 2021

IMG_2122
Scriptable can be found at https://scriptable.app
You need to host the images and change sensor names and other URLs accordingly.

@johntdyer
Copy link

Could you post the icons you used?

@m33x
Copy link
Author

m33x commented Dec 15, 2021

hass-favicon.png

But I use the one hosted on my own HASS instance, e.g., https:///static/icons/favicon-192x192.png
https://raw.githubusercontent.com/home-assistant/frontend/dev/public/static/icons/favicon-192x192.png

humidity.png

But I changed the color from #ffffff to #d3e9fb
http://bemakoha.com.ar/assets/humedad.faf5dde2fdcb6366ead6b3415aca8471.png

temperature.png

But I cropped the left and right side of the image to make it narrower:
https://uploads-ssl.webflow.com/5c504ac89bf84cb501f4750a/5f3fa61aba10b4edb6d060f0_temperature.png

@kangapi
Copy link

kangapi commented Dec 31, 2021

Hello I am bad at Javascript and I would like to make a widget for my 3D printer from my printer information in home-assistant. The widget I started looks like this widget but unfortunately I can only display numbers like what can I do ? Thanks

@m33x
Copy link
Author

m33x commented Dec 31, 2021

Your sentence is hard to understand.

However, Scriptable widgets can only display information. There are no buttons or similar. You can only view, but not interact with your 3D printer.

That’s unrelated to Home Assistant and Scriptable, it’s a limitation that comes with iOS 15.

@kangapi
Copy link

kangapi commented Dec 31, 2021

Sorry i am very bad at english i just would like to know how to display strings instead of numbers with your code.

@m33x
Copy link
Author

m33x commented Jan 8, 2022

You would need to edit Line 203 and 206.
room.temp = Math.round(json[i]['state']);
-->
room.temp = json[i]['state'];

@kangapi
Copy link

kangapi commented Jan 19, 2022

Thank you very much !

@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