I’ve been using the iOS Scriptable app and the fantastic Weather Cal widget to have an information rich homescreen for a while now. The built-in COVID widget only had data to the country level but I wanted to see the New York Times’ data at the US county level.
OK, so “New York City” isn’t technically a county but the Times makes an exception and groups the boroughs together—call it a hometown bias.
This also serves as a decent example of how to make a custom Weather Cal component that loads, caches, and displays data from a custom API.
The New York Times doesn’t present a REST api but they do publish their data to Github about 3 times a day!
#!/bin/bash
# Live moving averages
curl https://raw.githubusercontent.com/nytimes/covid-19-data/master/rolling-averages/us-counties-recent.csv -o data.csv
# Example of the CSV data:
# date,geoid,county,state,cases,cases_avg,cases_avg_per_100k,deaths,deaths_avg,deaths_avg_per_100k
# 2021-12-21,USA-36998,New York City,New York,17958,10049.14,120.54,33,15.43,0.19
# Data.csv holds the last month of data for every US county sorted in ascending date order.
# I only care about the most recent date so we can search it backwards using tac and grep
tac data.csv | grep -m 1 "New York City" > current_cases_avg.txt
# Grab the 6th column
awk -F "\"*,\"*" '{print $6}' current_cases_avg.txt > current_cases_avg_only.txt
# Copy the text file to my webserver
cp current_cases_avg_only.txt /var/www/FAKE/PATH/current_cases_avg_only.txt
This bash script can live in cron and run every hour.
15 * * * * ~/update.sh >/dev/null 2>&1
In Scriptable, weather-cal.js
in the existing custom = {}
variable add the following:
async setupDanielC19() {
const danielC19Path = code.fm.joinPath(code.fm.libraryDirectory(), "weather-cal-danielC19")
// getCache(path REQUIRED, minTime OPTIONAL, maxTime OPTIONAL)
let danielC19Data = code.getCache(danielC19Path, 20, 60)
if (!danielC19Data || danielC19Data.length == 0 || danielC19Data.cacheExpired)
try {
//
// NOTE: Be sure to put in a real URL here!
//
let rawRequest = new Request("https://danielbeadle.net/FAKE/PATH/current_cases_avg_only.txt")
let rawData = await rawRequest.loadString()
if (!rawData || rawData.length == 0) { throw 0 }
danielC19Data = Number(rawData)
// Write Data to cache
code.fm.writeString(danielC19Path, JSON.stringify(danielC19Data, null, 2))
} catch (err) {
danielC19Data = code.getCache(danielC19Path, 20, 60)
}
return danielC19Data
},
async danielC19(column) {
if (!this.danielC19Data) {
danielC19Data = await custom.setupDanielC19()
}
// Set up the stack
let danielC19Stack = code.align(column)
danielC19Stack.layoutHorizontally()
danielC19Stack.centerAlignContent()
danielC19Stack.setPadding(code.padding/2, code.padding*2, code.padding/2, code.padding)
// Round to an integer and add commas
const c19rkiLine = code.provideText(`NYC 7-Day Avg: ${Number.parseInt(danielC19Data).toLocaleString('en-US')}`, danielC19Stack)
},
And finally at the top of weather-cal.js
add the new component
const layout = `
row
column
date
sunset
battery
danielC19
space
events
`