I made this illustration for my nephew, an 8 year old that is obsessed with Godzilla trivia and vintage video games. I hope he loves it, although I worry that my Godzilla will not be historically acurrate.

godzilla playing nintendo
Posted
AuthorTodd Zarwell

I think most people who work in medical professions can appreciate this one. For medical-legal reasons, health care providers are expected to document everything. Occasionally, during our efforts to help and educate our patients, we’ll forget to record a finding or a test result.
It’s almost never a worrisome finding or a scary risk factor, but usually something that we consider to be a normal variation, something we weren’t very concerned about in the first place.
Still, it bugs me when I realize I’ve done something like this - even if my realization hits me in the middle of the night.

Posted
AuthorTodd Zarwell

I had the honor of being interviewed by Dr. Darryl Glover on the Defocus live stream last night. I was nervous and probably blabbered on a bit too much, but it was a fantastic experience and I really appreciated the opportunity to talk about my work on EyeDock and Eye Scholar.

Posted
AuthorTodd Zarwell

We were taking some family photos and my son picked up a long branch and lifted it over his head while screaming like a banshee. He looked so much like a crazy power lifter I just had to draw a barbell and an insane amount of weights.

C1F395D3-8CC4-4FCD-9ADB-976B4FA53DCC.jpeg
Posted
AuthorTodd Zarwell

Apple has released iOS 14, and perhaps one of their most exciting - albeit long overdue - new features is the introduction of widgets.

I’ve added some photo, news, calendar, and weather widgets to my home screen and I'm pretty happy with this change. Who knew having more control over your device would be so fun? Well, Android users knew, but that’s a topic for another day.

2020-10-04_09-47-33.png

Although I’m liking my widgets, I wish they were more exciting. I already have a lot of ways to check the weather, see photos and review my day’s activities, etc.

However, what I really care about is ice cream.

Wisconsin is the home of a Midwestern restaurant chain called Culver’s. Culvers has really good burgers and other entrée items, but what they’re really known for is their frozen custard.

Culver’s has a new Flavor of the Day (FOD) every, er, well, every day. While we don’t eat frozen custard every day, we do like to indulge from time to time, especially when we see a FOD that we like. The second most common question in our house (after “Dad, why are you such a nerd?") is “Dad, what’s the Flavor of the Day?”.

Well, I could look up their website and find out that information. Like an animal. But c’mon, this is 2020. This is the year of the iPhone Widget.

This sounded like a job for Scriptable, an iPhone app that lets you create custom applets using Javascript. When iOS widgets were introduced Scriptable added a simple example script (called “News in Widget”) for creating and adding a custom widget to your iPhone’s home screen.

In the provided example, the script calls an API to download articles from a news site, then displays the headlines in a widget.

Unfortunately Culver’s does not have a Flavor of the Day API, so to accomplish this we’re going to have to roll our own, and to do this we’re going to need to do some good old-fashioned web scraping. Also unfortunately, this is probably where I’m going to lose people, because this is going to require writing some server-side code and having a place to host it.

I’m going to use PHP to write my server-side script. This language has a built-in cURL function that makes it easy to grab the contents of a website.

However, once we’ve grabbed the contents of the website, we need to find the information we need. Fortunately, other - smarter - people have tackled this issue and we don’t have to reinvent the wheel. I’m going to use a PHP library called simplehtmldom. Simplehtmldom is really simple: You just extract data from your captured HTML using regular CSS selectors.

So, the next step is to figure out where the data we want is located in our HTML. This can be done by going to the website we want to scrape, opening up the developer tools, and inspecting the HTML elements we’re interested in.

In this case, we see the name of the Flavor of the Day embedded in a <strong> tag that is a child of a div with a specific class. In addition, it would be cool if we could display an image in our widget, so let’s capture the source of the FOD’s image while we’re at it.

culvers_code.png

My flavorScraper.php script is shown below. In the end, it’s pretty simple. It

  1. Includes the simplehtmldom script

  2. Identifies the website we want to scrape

  3. Gets the website’s HTML using function that uses cURL

  4. Parses the HTML using CSS selectors to grab our data

  5. Echos the data as a JSON object

This script was uploaded to a server so it can be called from my Scriptable script.

flavorScraper.php

<?php
// we'll use simple_html_dom.php to parse the contets of our website
// documentation: http://simplehtmldom.sourceforge.net/
include ("path_to_file/simple_html_dom.php");

// the website I want to scrape for data
// this is specific to the location nearest me
$url = "https://www.culvers.com/restaurants/my_town";

$html = new simple_html_dom();
$str = curl($url);
$html->load($str);

$flavorOfTheDay = [];
// the css selector for the name of the flavor of the day
$flavorOfTheDay['name'] =  $html->find("div.ModuleRestaurantDetail-fotd h2 strong")[0]->plaintext;
// the css selector for the image, and this case we're getting the URL from the src attribute. In this case it did not include the "https:" so we need to prepend it to the URL
$flavorOfTheDay['image']= "https:" . $html->find("div.ModuleRestaurantDetail-fotd img")[0]->src;

// return the data in JSON format
echo json_encode($fod);

// use curl to grab the contents of the the website
 function curl($url) {
     // Assigning cURL options to an array
     $options = Array(
         CURLOPT_RETURNTRANSFER => TRUE,  // Setting cURL's option to return the webpage data
         CURLOPT_FOLLOWLOCATION => TRUE,  // Setting cURL to follow 'location' HTTP headers
         CURLOPT_AUTOREFERER => TRUE, // Automatically set the referer where following 'location' HTTP headers
         CURLOPT_CONNECTTIMEOUT => 120,   // Setting the amount of time (in seconds) before the request times out
         CURLOPT_TIMEOUT => 120,  // Setting the maximum amount of time for cURL to execute queries
         CURLOPT_MAXREDIRS => 10, // Setting the maximum number of redirections to follow
         CURLOPT_USERAGENT => "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1a2pre) Gecko/2008073000 Shredder/3.0a2pre ThunderBrowse/3.2.1.8",  // Setting the useragent
         CURLOPT_URL => $url, // Setting cURL's URL option with the $url variable passed into the function
         CURLOPT_HTTPHEADER=>["Cookie: RoadblockSayCheeseCurds=0"]
     );

     $ch = curl_init();  // Initialising cURL
     curl_setopt_array($ch, $options);   // Setting cURL's options using the previously assigned array data in $options
     $data = curl_exec($ch); // Executing the cURL request and assigning the returned data to the $data variable
     //echo "<br><br>data: " . $data;
     curl_close($ch);    // Closing cURL
     return $data;   // Returning the data from the function
 }

Now we just need the Scriptable script.

To create this script I copied their “News in Widget” example and modified it for my needs. It simply:

  1. Calls the flavorScraper.php script from my server

  2. Creates a widget

  3. Takes the JSON returned from flavorScraper.php

  4. Extracts the Flavor of the Day text and image and displays them

FlavorScraper (Scriptable App):

let item = await loadItem()
let widget = await createWidget(item)
// Check if the script is running in
// a widget. If not, show a preview of
// the widget to easier debug it.
if (!config.runsInWidget) {
  await widget.presentMedium()
}
// Tell the system to show the widget.
Script.setWidget(widget)
Script.complete()

async function createWidget(item) {
 
  let imgURL = item.image;
  let w = new ListWidget()
  if (imgURL != null) {
    let imgReq = new Request(imgURL)
    let img = await imgReq.loadImage()
    w.backgroundImage = img
   
  }

  let titleTxt = w.addText(item.name)
  titleTxt.font = Font.boldSystemFont(32)
  titleTxt.textColor = Color.blue()
  titleTxt.centerAlignText()
  // Add spacing below headline.
   w.addSpacer(12);
  // Add spacing below content to center it vertically.
  w.addSpacer()
  return w
}
  
async function loadItem() {
  // the url to the server where I'm hosting flavorScraper.php
  let url = "https://www.myServerSite.com/flavorScraper.php"
  let req = new Request(url)
  let json = await req.loadJSON()
  return json
}

Now, we just need to add a new Scriptable widget to our home screen and tell the widget we want to display the widget from this flavorScraper script.

IMG_5148.png

And, voilà! We now can see the Flavor of the Day at a moment’s notice!

It’s a good start. I should probably play with the font color (I needed something that was visible in night mode - where the background is black - and in daytime mode).

If I find the ambition to improve on this, I might make a short list of my family's favorites, and, if the FOD of the day is a favorite, add something attention-getting such as a different colored background, some additional text, or maybe a few emojis.

Posted
AuthorTodd Zarwell
CategoriesTech