-
-
Save malosaa/c2611c8e490d6e0af1b5366577b5e2ba to your computer and use it in GitHub Desktop.
Simple Home Assistant (HASS) iOS Gauge Lock Screen Widget via Scriptable App
This file contains hidden or 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
// produce test ` | |
const widget = new ListWidget(); | |
// Get data from HASS | |
let result = await loadValues(); | |
// Check data quality | |
let value = result.value; | |
let isValid = !Number.isNaN(value); | |
if (!isValid) { | |
value = 0; | |
} | |
// Build widget | |
let progressStack = await progressCircle(widget, value); | |
// Tiny house or triangle | |
const mainIconName = isValid ? "house.fill" : "exclamationmark.triangle"; | |
const mainIconSize = 30; | |
let mainStack = progressStack.addStack(); | |
mainStack.layoutVertically(); | |
mainStack.centerAlignContent(); | |
let mainIcon = SFSymbol.named(mainIconName); | |
mainIcon.applyFont(Font.regularSystemFont(mainIconSize)); | |
mainIcon = mainStack.addImage(mainIcon.image); | |
mainIcon.imageSize = new Size(mainIconSize, mainIconSize); | |
mainIcon.tintColor = new Color("#fafafa"); | |
widget.addSpacer(); // Add spacer to push the valueStack to the right | |
let valueStack = widget.addStack(); | |
valueStack.layoutVertically(); | |
valueStack.centerAlignContent(); | |
let valueLabel = mainStack.addText(`${result.value}W`); | |
valueLabel.font = Font.mediumSystemFont(14); | |
valueLabel.textColor = new Color("#fafafa"); | |
valueLabel.textOpacity = 0.8; | |
valueLabel.leftAlignText(); | |
valueLabel.minimumScaleFactor = 0.5; | |
valueLabel.lineLimit = 1; | |
// Tiny bolt (lightning strike) icon | |
const badgeName = "bolt.fill"; | |
let badgeIcon = SFSymbol.named(badgeName); | |
badgeIcon.applyFont(Font.regularSystemFont(26)); | |
badgeIcon = progressStack.addImage(badgeIcon.image); | |
badgeIcon.imageSize = new Size(12, 12); | |
badgeIcon.tintColor = new Color("#fafafa"); | |
// iOS 16 gauge widget on lock screen | |
widget.presentAccessoryCircular(); | |
// or classical widget? You need to decide. | |
widget.backgroundColor = new Color("#7c7c7c", 1.0); | |
//widget.presentSmall(); | |
Script.setWidget(widget); | |
Script.complete(); | |
async function progressCircle( | |
on, | |
value = 50, | |
size = 60, | |
background = "hsl(1200, 0%, 10%)", | |
barWidth = 5.5 | |
) { | |
if (value > 1000) { | |
color = "hsl(120, 100%, 50%)"; // green | |
} else if (value >= 500 && value <= 1000) { | |
color = "hsl(54, 100%, 50%)"; // yellow | |
} else { | |
color = "hsl(0, 100%, 50%)"; // red | |
} | |
// Change colors in dark mode | |
async function isUsingDarkAppearance() { | |
return !Color.dynamic(Color.white(), Color.black()).red; | |
} | |
let isDark = await isUsingDarkAppearance(); | |
let background = isDark ? "hsl(0, 0%, 10%)" : "hsl(0, 0%, 100%)"; | |
let w = new WebView() | |
await w.loadHTML('<canvas id="c"></canvas>'); | |
// The magic gauge, filled with 'value' | |
let base64 = await w.evaluateJavaScript(` | |
let color = "${color}", | |
background = "${background}", | |
size = ${size}*3, | |
lineWidth = ${barWidth}*3, | |
percent = ${value * 100}; | |
let canvas = document.getElementById('c'), | |
c = canvas.getContext('2d'); | |
canvas.width = size; | |
canvas.height = size; | |
let posX = canvas.width / 2, | |
posY = canvas.height / 2, | |
onePercent = 360 / 100, | |
result = onePercent * percent; | |
c.lineCap = 'round'; | |
c.beginPath(); | |
c.arc( | |
posX, | |
posY, | |
(size - lineWidth - 1) / 2, | |
(Math.PI / 180) * 270, | |
(Math.PI / 180) * (270 + 360) | |
); | |
c.strokeStyle = background; | |
c.lineWidth = lineWidth; | |
c.stroke(); | |
c.beginPath(); | |
c.strokeStyle = color; | |
c.lineWidth = lineWidth; | |
c.arc( | |
posX, | |
posY, | |
(size - lineWidth - 1) / 2, | |
(Math.PI / 180) * 270, | |
(Math.PI / 180) * (270 + result) | |
); | |
c.stroke(); | |
completion(canvas.toDataURL().replace("data:image/png;base64,", "")); | |
`); | |
const image = Image.fromData(Data.fromBase64String(base64)); | |
// Add gauge to widget | |
let stack = on.addStack(); | |
stack.size = new Size(size, size); | |
stack.backgroundImage = image; | |
stack.centerAlignContent(); | |
let padding = barWidth * 2; | |
stack.setPadding(padding, padding, padding, padding); | |
return stack; | |
} | |
async function loadValues() { | |
// Replace the URL and headers with your HASS details | |
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(); | |
// Edit this, add your sensor | |
let result = { name: 'sensor.your_sensor_of_interest', value: -1 }; | |
// Search through HASS data find sensor and its current value | |
for (let i = 0; i < json.length; i++) { | |
if (json[i]['entity_id'] === result.name) { | |
result.value = parseFloat(json[i]['state']); | |
break; | |
} | |
} | |
// Testing / Debugging | |
//result.value = 30; | |
console.log(result); | |
return result; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment