Created
November 4, 2022 13:41
-
-
Save mrnovalles/08c3c2d72d164076d052d115f5d3447d to your computer and use it in GitHub Desktop.
An extension of the popular iterm widget for Scriptable to include energy costs in Spain
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: deep-gray; icon-glyph: magic; | |
// Change these to your usernames! | |
const user = "mrnovalles"; | |
// API PARAMETERS !important | |
// WEATHER_API_KEY, you need an Open Weather API Key | |
// You can get one for free at: https://home.openweathermap.org/api_keys (account needed). | |
const WEATHER_API_KEY = "REDACTED"; | |
const DEFAULT_LOCATION = { | |
latitude: 41.4, | |
longitude: 2.2 | |
}; | |
const Cache = importModule('cache'); | |
const cache = new Cache("termiWidgetCache"); | |
const data = await w(); | |
const widget = createWidget(data); | |
Script.setWidget(widget); | |
Script.complete(); | |
function createWidget(data) { | |
const w = new ListWidget() | |
const bgColor = new LinearGradient() | |
bgColor.colors = [new Color("#29323c"), new Color("#1c1c1c")] | |
bgColor.locations = [0.0, 1.0] | |
w.backgroundGradient = bgColor | |
w.setPadding(12, 15, 15, 12) | |
const stack = w.addStack(); | |
stack.layoutHorizontally(); | |
const leftStack = stack.addStack(); | |
leftStack.layoutVertically(); | |
leftStack.spacing = 6; | |
leftStack.size = new Size(200, 0); | |
const time = new Date() | |
const dfTime = new DateFormatter(); | |
dfTime.locale = "en"; | |
dfTime.useMediumDateStyle(); | |
dfTime.useNoTimeStyle(); | |
const firstLine = leftStack.addText(`[👨🎤] ${user} ~$ now`) | |
firstLine.textColor = Color.white() | |
firstLine.textOpacity = 0.7 | |
firstLine.font = new Font("Menlo", 11) | |
const timeLine = leftStack.addText(`[🗓] ${dfTime.string(time)}`) | |
timeLine.textColor = Color.white() | |
timeLine.font = new Font("Menlo", 11) | |
const batteryLine = leftStack.addText(`[🔋] ${renderBattery()}`) | |
batteryLine.textColor = new Color("#6ef2ae") | |
batteryLine.font = new Font("Menlo", 11) | |
const locationLine = leftStack.addText(`[️️📍] Location: ${data.weather.location}`) | |
locationLine.textColor = new Color("#7dbbae") | |
locationLine.font = new Font("Menlo", 11) | |
// Add energyLines from preciodelaluz.org | |
addEnergyLines(leftStack, data); | |
stack.addSpacer(); | |
const rightStack = stack.addStack(); | |
rightStack.spacing = 2; | |
rightStack.layoutVertically(); | |
rightStack.bottomAlignContent(); | |
addWeatherLine(rightStack, data.weather.icon, 32); | |
addWeatherLine(rightStack, `${data.weather.description}, ${data.weather.temperature}°`, 12, true); | |
addWeatherLine(rightStack, `High: ${data.weather.high}°`); | |
addWeatherLine(rightStack, `Low: ${data.weather.low}°`); | |
addWeatherLine(rightStack, `Wind: ${data.weather.wind} kmh`); | |
addWeatherLine(rightStack, `🌅: ${data.weather.sunrise}`); | |
addWeatherLine(rightStack, `🌇: ${data.weather.sunset}`); | |
return w | |
} | |
function addEnergyLines(leftStack, data) { | |
const sortedData = sortObj(data.energyAll); | |
const firstPrice = Object.values(sortedData)[0]["price"]; | |
let cheapestPrices = [firstPrice]; | |
const cheapestPriceAmount = 5; | |
let minPriceValue = firstPrice, | |
minPriceAt = 0, | |
maxPriceValue = firstPrice, | |
maxPriceAt = 0; | |
for (let [hourRange, value] of Object.entries(sortedData)) { | |
price = value["price"] | |
if (price >= maxPriceValue) { | |
maxPriceValue = value["price"]; | |
maxPriceAt = hourRange; | |
} | |
if (price < minPriceValue) { | |
minPriceValue = value["price"]; | |
minPriceAt = hourRange; | |
} | |
currentCheapest = Math.min(...cheapestPrices); | |
if (price < currentCheapest) { | |
if (cheapestPrices.length == cheapestPriceAmount) { | |
index = cheapestPrices.indexOf(currentCheapest); | |
cheapestPrices[index] = price; | |
} else { | |
cheapestPrices.push(price); | |
} | |
} | |
} | |
const minEnergyPrice = leftStack.addText(`[️️⚡️]Lo:${minPriceValue} at ${minPriceAt}`); | |
minEnergyPrice.textColor = new Color("#7dbbae"); | |
minEnergyPrice.font = new Font("Menlo", 11); | |
const maxEnergyPrice = leftStack.addText(`[️️⚡️]Hi:${maxPriceValue} at ${maxPriceAt}`); | |
maxEnergyPrice.textColor = new Color("#7dbbae"); | |
maxEnergyPrice.font = new Font("Menlo", 11); | |
let colorArr = []; | |
let hourArr = []; | |
for (let [key, timePeriod] of Object.entries(sortedData)) { | |
const hour = key.split("-")[0]; | |
if (hour % 4 == 0) { | |
hourArr.push(key.split("-")[0]); | |
} else { | |
hourArr.push(" "); | |
} | |
if (cheapestPrices.includes(timePeriod["price"])) { | |
colorArr.push("🟦"); | |
} | |
else if (timePeriod["is-cheap"] == true && timePeriod["is-under-avg"] == true) { | |
colorArr.push("🟩"); | |
} else if (timePeriod["is-cheap"] == false && timePeriod["is-under-avg"] == true) { | |
colorArr.push("🟨"); | |
} else { | |
colorArr.push("🟥"); | |
} | |
}; | |
const hourLine = leftStack.addText(hourArr.join("")); | |
hourLine.font = new Font("Menlo", 6); | |
hourLine.textColor = new Color("#ffffff"); | |
const energyColorLine = leftStack.addText(colorArr.join("")); | |
energyColorLine.font = new Font("Menlo", 6) | |
energyColorLine.url = "https://preciodelaluz.org" | |
} | |
function addWeatherLine(w, text, size, bold) { | |
const stack = w.addStack(); | |
stack.setPadding(0, 0, 0, 0); | |
stack.layoutHorizontally(); | |
stack.addSpacer(); | |
const line = stack.addText(text); | |
line.textColor = new Color("#ffcc66"); | |
line.font = new Font("Menlo" + (bold ? "-Bold" : ""), size || 11); | |
} | |
async function w() { | |
const weather = await fetchWeather(); | |
const energyAll = await fetchDailyEnergyAllPrice(); | |
return { | |
weather, | |
energyAll, | |
} | |
} | |
function renderBattery() { | |
const batteryLevel = Device.batteryLevel() | |
const juice = "#".repeat(Math.floor(batteryLevel * 8)) | |
const used = ".".repeat(8 - juice.length) | |
const batteryAscii = `[${juice}${used}] ${Math.round(batteryLevel * 100)}%` | |
return batteryAscii | |
} | |
function sortObj(obj) { | |
return Object.keys(obj).sort().reduce(function (result, key) { | |
result[key] = obj[key]; | |
return result; | |
}, {}); | |
} | |
async function fetchDailyEnergyAllPrice() { | |
const allPriceUrl = "https://api.preciodelaluz.org/v1/prices/all?zone=PCB" | |
const data = await fetchJson('all_energy_price', allPriceUrl); | |
return data; | |
} | |
async function fetchWeather() { | |
let location = await cache.read('location'); | |
if (!location) { | |
try { | |
Location.setAccuracyToThreeKilometers(); | |
location = await Location.current(); | |
} catch (error) { | |
location = await cache.read('location'); | |
} | |
} | |
if (!location) { | |
location = DEFAULT_LOCATION; | |
} | |
const address = await Location.reverseGeocode(location.latitude, location.longitude); | |
const url = "https://api.openweathermap.org/data/2.5/onecall?lat=" + location.latitude + "&lon=" + location.longitude + "&exclude=minutely,hourly,alerts&units=metric&lang=en&appid=" + WEATHER_API_KEY; | |
const data = await fetchJson(`weather_${address[0].locality}`, url); | |
const dfTime = new DateFormatter(); | |
dfTime.locale = "en"; | |
dfTime.useNoDateStyle(); | |
dfTime.useShortTimeStyle(); | |
return { | |
location: address[0].locality, | |
icon: getWeatherEmoji(data.current.weather[0].id, ((new Date()).getTime() / 1000) >= data.current.sunset), | |
description: data.current.weather[0].main, | |
temperature: Math.round(data.current.temp), | |
wind: Math.round(data.current.wind_speed), | |
high: Math.round(data.daily[0].temp.max), | |
low: Math.round(data.daily[0].temp.min), | |
sunrise: dfTime.string(new Date(data.current.sunrise * 1000)), | |
sunset: dfTime.string(new Date(data.current.sunset * 1000)) | |
} | |
} | |
async function fetchJson(key, url, headers) { | |
const cached = await cache.read(key, 5); | |
if (cached) { | |
return cached; | |
} | |
try { | |
console.log(`Fetching url: ${url}`); | |
const req = new Request(url); | |
req.headers = headers; | |
const resp = await req.loadJSON(); | |
cache.write(key, resp); | |
return resp; | |
} catch (error) { | |
try { | |
return cache.read(key, 5); | |
} catch (error) { | |
console.log(`Couldn't fetch ${url}`); | |
} | |
} | |
} | |
function getWeatherEmoji(code, isNight) { | |
if (code >= 200 && code < 300 || code == 960 || code == 961) { | |
return "⛈" | |
} else if ((code >= 300 && code < 600) || code == 701) { | |
return "🌧" | |
} else if (code >= 600 && code < 700) { | |
return "❄️" | |
} else if (code == 711) { | |
return "🔥" | |
} else if (code == 800) { | |
return isNight ? "🌕" : "☀️" | |
} else if (code == 801) { | |
return isNight ? "☁️" : "🌤" | |
} else if (code == 802) { | |
return isNight ? "☁️" : "⛅️" | |
} else if (code == 803) { | |
return isNight ? "☁️" : "🌥" | |
} else if (code == 804) { | |
return "☁️" | |
} else if (code == 900 || code == 962 || code == 781) { | |
return "🌪" | |
} else if (code >= 700 && code < 800) { | |
return "🌫" | |
} else if (code == 903) { | |
return "🥶" | |
} else if (code == 904) { | |
return "🥵" | |
} else if (code == 905 || code == 957) { | |
return "💨" | |
} else if (code == 906 || code == 958 || code == 959) { | |
return "🧊" | |
} else { | |
return "❓" | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment