Skip to content

Instantly share code, notes, and snippets.

@mrnovalles
Created November 4, 2022 13:41
Show Gist options
  • Save mrnovalles/08c3c2d72d164076d052d115f5d3447d to your computer and use it in GitHub Desktop.
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
// 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