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

Six months ago, I wrote about my thoughts on the difficulties of teaching kids to write code. There were a few iPad apps that seemed to have potential to introduce kids to programming.

Unfortunately I've had a hard time finding a way to introduce my sons to these apps. After all, it's kind of a slippery slope. I want this to be fun, and I'm afraid if it seems like too much of a chore right off the bat it might be a while before I can try again. Also, when you open up the iPad there are potential distractions just two clicks away. If the app comes off as boring or too educational I'll get a "can I play Minecraft instead?" within minutes.

So, I've been biding my time and waiting for the right moment. Then, lo and behold, we were signing my oldest son up for supper activities this Spring when I saw an appealing option: "Coding with Kodable". Ooh, that sounded interesting. After reading the description I learned that Kodable is another "learn to code" app in the same vein as the ones I mentioned in my blog post last December. Well, of course we signed up for it and my son will be finishing up his "camp" today.

It has gone really well. He did it with other kids his age, so it was fun, and he really did learn core concepts in programming. Today was the first time I dropped him off at his class, so I hung around for a few minutes and watched my son work through the app's puzzles. I was pretty impressed with what I saw.

Now, keep in mind that we're talking about 7 year olds here. They're still learning how to write and spell, so typing in complicated blocks of code is out of the question. The goal is to start with the basics.

What is a computer program? It's a sequential list of instructions that tell a computer what to do. So, that's where Kodables starts. In this case, the instructions are geared towards guiding a "Fuzz" through a maze.  (The optometrist in me can't help but notice this is also a great exercise for laterality / directionality, spatial reasoning, etc). The child simply drags a series of arrows onto the screen to guide their character though its course. Just like in programming, the order of the instructions counts. Also, there can be more than one way to accomplish a task, but there is often a way that involves the least amount of instructions or takes a better approach (in Kodables, the better approach is the one that leads the Fuzz past the most coins). 

The next major concept is conditional statements. A program needs accept input then make a decision as to what to do next. In most programming languages this is done in an IF/THEN statement, eg) IF (weight > 200) THEN diet. In codables the condition the child can apply a color to their arrows that essentially says "if your Fuzz encounters a red square turn down".

Conditional statements on Kodable. The instructions are the arrows in the top left side of the screen. Note the arrow with the red background - this is a conditional statement.

Conditional statements on Kodable. The instructions are the arrows in the top left side of the screen. Note the arrow with the red background - this is a conditional statement.

 

As people improve at coding they learn an important lesson: Don't work any harder than you have to. You oftentimes find yourself writing out the same sets of instructions multiple times. Whenever you find yourself in this situation you have to ask yourself if there's an easier way. Working smart,  not just hard, can result in huge time savings. In programming, two fundamental concepts that can help with this are loops and functions.

Loops can be used when repeating the same task two or more times. Consider the following maze on Kodables:

Loops on Kodable.&nbsp;

Loops on Kodable. 

The instructions would go like this:

  1. go right
  2. go up
  3. go right
  4. go up
  5. go right

That wasn't too bad to type out, but what if we had to do it ten more times? Or a hundred more times? This is where loops come in handy. A lop would essentially say:

  1. go right
  2. go up
  3. repeat the above steps 100 times

Functions are very similar in a lot of ways, except we don't necessarily duplicate the same instructions repeatedly. Instead, we save a set of instructions as a block, or function, that we can use, or "call", at any time. Consider this Kodable maze:

Functions in Kodable. Note the three steps that are included in the function (seen to the right of the curly brackets { } on the right hand side)

Functions in Kodable. Note the three steps that are included in the function (seen to the right of the curly brackets { } on the right hand side)

To get through this maze the instructions would look like this:

  1. go right
  2. at the pink box, turn down
  3. go right
  4. go up 
  5. go right
  6. at the pink box, turn down
  7. go right

Notice any patterns here? Steps 1-3 are identical to steps 5-7. The "programmer" astutely noticed this and made a function. The function is located on the right side by the curly brackets { }, and contains the same block of instructions as steps 1-3 above. So, the new instructions will look like this:

  1.  run the function
  2. go up
  3. run the function

It doesn't look like much, but this is one of hallmarks of efficient coding and can save tons of lines of code, not to mention time,  as programs get longer and longer.

A Kodable bug

A Kodable bug

Another very important feature kids will learn from Kodables is debugging. Coders rarely write a perfect program on the first try. Programming is very much a trial and error process: Write some code, run it, see where it screwed up, rewrite some code, and repeat. Just like real coding, you can run your program and watch each step as it executes to localize where the problem is coming fro. In Kodables there's segments where it purposely gives you a program with errors in it, and actually places a cartoon bug on the maze at the location of the problem. When you fix the programming "bug", your Fuzz rolls over the cartoon bug and squashes it. I know from experience there's nothing more satisfying than squashing those bugs.

In summary, Kodables seems to be a great way to get kids started in programming. It teaches them to look at a problem and break it into little parts, then give the computer stepwise instructions to solve the problem. It teaches concepts such as conditionals, loops, and functions. It also teaches debugging and, as a result, the acceptance of the inevitability of errors and the dedication needed to troubleshoot and fix your bugs.

In the future, kids that want to continue with coding will need to learn a programming language and its syntax. However, in my opinion, learning the basics of programming is much more important. And, after learning one programming language, picking up others is relatively simple once you master the fundamentals.

 

PS My 5 year old came along with me when we dropped my 7 year old off at Kodables camp today. After he saw his big brother solving puzzles on an iPad he wanted to do the same thing. We went to a coffee shop, downloaded the app, and spent an hour and a half "programming". He picked it up very quickly and we had a lot of fun together.



Posted
AuthorTodd Zarwell
CategoriesTech

I like Siri. It's far, far from perfect, and I think people hold it to a very high standard (it's easy to do when your basis for comparison is human intelligence). But there is a limited set of things that it does do well, and I find those things extremely helpful.

IMG_0088.jpg

Set a timer for 10 minutes (so I can kick the kids off my iPad).

Remind me to bring Drew's snowpants to school on Monday morning at 6:30.

Remind to place that order when I get to work.

Basically, it supplements my brain and helps me remember things I'm almost certain to forget.

Perhaps my biggest use of Siri, however, is managing grocery lists. This helps me in innumerable ways, which I will now outline. In a numbered list.

  1. I never forget to add something to a list. I use the last of the peanut butter, I pull out my iPhone, hold down the home button, and say "add peanut butter to my grocery list". Bam. Not gonna forget the PB.
  2. I always know what I need when I'm at the grocery store. I never have that nagging feeling that I'm forgetting something, and I never get home and kick myself for neglecting to pick something up. And my six year old never has to reprimand me for forgetting to pick up his apples.
  3. I always know what my wife needs at the grocery store, and she knows what I need. I might just stop in to buy donuts on a Sunday morning, but a quick look at my phone let's me know that Lisa just used the last of the garlic powder and I might as well grab that too.

Now, there's a few things that make this easier.

First of all, I have a list that's named "grocery". To do this, open the reminders and hit the "+" button to make a new list. If you open the reminders app and don't see the "+" button you might need to tap the area at the bottom of the screen that looks like a bunch of stacked index cards.

If you're looking at a list, click the bottom of the screen to see all your lists -&nbsp;and get the option to create more lists.

If you're looking at a list, click the bottom of the screen to see all your lists - and get the option to create more lists.

 

To share with your spouse, go into your newly created grocery list and click the edit button. Then click the Sharing opinion, then Add person.., and find the person upon whom you'll bestow your list sharing privileges.

 

 

 

 

This all works very well, but we decided to take it to another level with a third party app called AnyList.  AnyList has a number of nice features, including:

  1. It separates your grocery list into groups such as "produce" or "meat" or "dairy". That way, when you walk through the grocery store, you can get everything you need in each section without having to repeatedly zigzag  back and forth throughout the whole store.
  2. It also has the option to share with a spouse and sync with desktop and browser apps.
  3. And, for my favorite, it can import lists from the built in Reminders app. In my case, from my Grocery list. This has the awesome result of letting you use Siri to populate your AnyList list. To import from Reminders, go to AnyList's settings and turn the "Reminder's Import" option on. Just make sure the name of your list in AnyList matches the name in Reminders (in my case they're both named "Grocery")

 

By the way, the things I'm describing here don't have to be limited to groceries. I've followed this exact same process to make a Target list, a Home Depot list, and a Costco list.  

Here's a short video on how I use the lists. The one thing that I don't include is perhaps the most useful feature of all: The fact that all these additions are almost immediately present on my wife's phone as well.

 

In my next post I'd like to talk about something very closely related to groceries: recipes. Stay tuned.

Posted
AuthorTodd Zarwell
CategoriesTech
9 CommentsPost a comment

I just finished Walter Isaacson's book, The Innovators. You might remember Mr. Isaacson as the the man Steve Jobs approached and ultimately convinced to write his biography, but he also wrote a couple excellent memoirs about Benjamin Franklin and Albert Einstein

The goal of The Innovators  is to walk us through the major advances in computer science that brought us to the technology that we enjoy and love today. 

In my opinion, the most fascinating part of this evolution begins in the 1840's, when Ada Lovelace essentially mapped out the steps that are used to write a program computer program over 100 years before engineers could actually build a machine capable of running a program.

The book then moves on to discuss vacuum tube computers such as Eniac, the development of the transistor, the development of the microchip, the invention and popularization of the mouse and GUIs, Bill Gate's software commoditizing hardware, and the explosion of the Internet. And, of course, the collaboration of countless innovators that fueled this revolution.

I've read a number of books (Steve Jobs, iWozNerds 2.0.1, Eniac, Weaving the Web: The Original Design and Ultimate Destiny of the World Wide Web) that discussed many of these individual topics, but The Innovators is a great summary of the age of computing from it's humble beginnings to the present day.

 

 

Posted
AuthorTodd Zarwell
CategoriesTech
Tagstech

I was very disappointed to hear that Google Reader is being shut down on ​on July 1st of this year. RSS feeds are a great way to keep up with all your favorite blogs and news sources, and Google's product made this really easy. In fact, when I open a browser window, the second thing I open is Reader (the first being Gmail).

Sure, there are other RSS clients, but most of them use Reader as a backbone. This works well because it helps keep your feeds organized and in sync. Fortunately, it sounds like some of these third party apps are preparing for life after Google pulls the plug.

I didin't get fully immersed into RSS until a few years ago, but it sounds like there were some good RSS clients that were pretty much destroyed when Google Reader came along. Hopefully we'll see some innovation now that there's room for these apps again.

I think that, as a rule of thumb, it's good to pay for things you like. Free web services are great, but in the end it only works if companies can sell you to advertisers. In a lot of cases it ends up swashing the competition and then, if it's not profitable enough, it goes away. And consumers are left with nothing.

Posted
AuthorTodd Zarwell
CategoriesTech
Tagstech

I don't like to repeat myself too​ much, so I won't write much here. I think the video is pretty self explanatory, and you can read more here.

Introducing the newest version of the oblique crossed cylinders calculator on eyedock.com.

Posted
AuthorTodd Zarwell
CategoriesTech

I've written a couple posts about using online services to automate tasks like creating RSS feeds from a site that doesn't provide them and using IFTTT to keep a journal.  Another popular tool for web automation is Yahoo Pipes.  From the Pipe's homepage:

Pipes is a powerful composition tool to aggregate, manipulate, and mashup content from around the web.

It may be an over-simplification, but Yahoo Pipes essentially grabs content from the web, usually in the form of RSS, and manipulates it in ways that you tell it to.

The problem to solve

I want to find out when new iPhone apps come into the ​iTunes store that might be of interest to me as an optometrist.  I sometimes read about new ones in my journals or, more likely, on my social networks.  However, maybe I want to be the FIRST person to hear about the latest and greatest eye care app.​

A solution

Apple has a page on their site that allows you to create RSS feeds to alert you when new media becomes available in iTunes.  It lets you do some customization, so I created an RSS feed that would be populated with new IOS apps.  I customized it like so:

​I decided to find apps in all genres.  An optometry app might be listed as medical, but when I submitted my apps I also considered listing them as reference or utility.  

Clicking the ​Generate button will give you an RSS feed URL:

http://itunes.apple.com/us/rss/newapplications/limit=300/xml

Copy this to your clipboard.​

However, there was some pretty severe limitations on how much we were able to customize this feed.  If you plug it into you favorite RSS reader (like Google Reader) you'll get probably get 250 listings for fart apps.  So, how can we customize this further?  Why, Yahoo Pipes, of course!

Go to Yahoo Pipes, log into your Yahoo account, and "Create a new pipe".  On the Pipes Editor page, drag the Fetch Feed ​resource from the left hand column into the workspace. Note:  If you're not seeing this column click the arrow button on the left hand side of the page (see circled item below).

​Paste the iTunes RSS URL into the text box in the Fetch Feed window.  To test the feed, click on the top of this window (it'll turn gold as it is in the image above) and the content of the feed will show up in the pane at the bottom of the page.  It's working, but there's a lot of apps unrelated to eye care!  We need to filter these results.

So, we go back to the tools in the left hand column and find filters - it's under the operators heading. Drag this tool under your Fetch Feed window.  We need to conned everything, so drag a  from the circle on the bottom of the Fetch Feed window to the circle on the top of the filters window - this will create a "pipe" connecting the two.  Do the same thing from the filters window to the pipe output window.  

​Now, let's add some filters. We'll have better results if we try permitting things that are eye care related than if we try to block all the things that are not eye care related, so we'll choose the permit option.  Make sure you choose the any option  - if you make a lot of rules it's unlikely that any of the feeds will meet all of them, and consequently nothing will pass through your filter. For my first rule I decided to permit items where the description contains the word optometry.  By clicking the (+) button I added similar filters for the words optometrist, ophthalmology, and ophthalmologist.

By clicking on the Pipe Output title ​you can see the results of your filter.  I did this and . . . got nothing.  Well, that's not totally unexpected - what are the odds that an app was released today with one of those keywords?  I decided to add a few more and came up with this:

I clicked the Pipe Output again and . . . got a hit!  The App For Learning Optics app passed through my filter.  Not only did something get through, it's fairly relevant!  Note: your results may vary depending on what is coming through the RSS feed when you test it.

Google Reader button​

Google Reader button​

So, what should we do with this?  Give it a title (Optometry Apps RSS), hit the save button​, then click the run pipe link.  This will take you to another page that shows the results of your pipe. It also gives you the ability to get the filtered data in multiple ways, including as an RSS feed.  I chose the Google Reader button and added it to, er, Google Reader.  When I go to Google Reader I see my Optometry Apps RSS feed complete with the result that made it through my filters.   Even better, it shows up in Flipboard on my iPad since I've connected that to my Google Reader account.

If you don't want to repeat this whole process, feel free to use the feed that I've created.

http://pipes.yahoo.com/pipes/pipe.run?_id=c7306982fb17d3464a8b00235179356b&_render=rss

​This was really meant to be an exercise in using Google Pipes and RSS (i'm not really desperate to immediately find out about new optometry apps).   But, is this practical or useful?  Only time will tell.   I'm not sure if my filters will catch the  IOS apps that I want it to.  It really depends on how well I set up my filters, which I'll probably have to experiment with for a while.  

Oh, for extra credit: If you really, really want to be the first person to find out about ​new IOS eye care apps, you could take this RSS feed and use IFTTT to alert yourself with a text message when a new app pops up in your feed. Or, get really creative and have a light turn on in your house.

Posted
AuthorTodd Zarwell
CategoriesTech