There have been several posts about Application Programming Interfaces (APIs), but little explanation or motivation.
Everyone knows that the world wide web is bursting with useful and interesting information. Generally we get this information in our browser.
Besides the countless websites that we enjoy with the browser, there are many more sources of information that are out there, on the web, but do not have a human-facing presentation. To consume these, you must have a program that uses their API.
But, how do you "have a program"? Readers of this blog know the answer: own/control a pico and use it.
Kinds of things on the web
Besides the web resources specifically designed for human consumption in the browser, there are two broad categories:
- Passive: databases of information, mostly rectangular in shape (having rows and columns (in which each row has the same columns as the other rows)).
- Active: events that occur, and are published, generally containing a collection of related data.
Picos can easily be programmed to fetch information from these databases, and also subscribe to and react to such events.
In addition, as a hybrid of the two types, a pico can be programmed to "wake up" at a specified time and go out and get a piece of data, storing it for later use or display.
Example: Astronomy Picture of the Day (APOD)
NASA holds a large variety of information, much of which is available for human consumption in the browser. One of its most popular websites collects pictures of the universe from voluntary contributors and selects one a day.
The same information is also available through an API, documented here, in the section APOD: Astronomy Picture of the Day.
A program to retrieve the APOD
Using the bazaar application*, we created a ruleset whose identifier (RID) is com.vcpnews.apod, whose name is photo, and whose meta name is Astronomy Picture of the Day. The boilerplate code is:
ruleset com.vcpnews.apod { meta { name "Astronomy Picture of the Day" use module io.picolabs.plan.apps alias app shares photo } global { photo = function(_headers){ app:html_page("manage Astronomy Picture of the Day", "", << <h1>Manage Astronomy Picture of the Day</h1> >>, _headers) } } }
To this, we add a rule to go to the NASA API and fetch the current picture, using the URL that they show in the documentation;
rule getAPOD { select when com_vcpnews_apod need_apod pre { api_url = "https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY" apod_response = http:get(api_url) apod = apod_response.get("content").decode() } fired { ent:apod := apod } }
In the prelude of this rule we name the URL from the documentation api_url, use HTTP to get the API's response, and finally extract the content of that response (a string, so we decode it into a KRL map).
In the rule's postlude (the rule is considered to have fired (because there is no conditional action in the rule)), we save that data, the map, into an entity variable, ent:apod.
Now, if we can just get this rule to be selected by an event, our pico will have a picture to display. A convenient event is one raised by the manage applications ruleset when we use it to install this ruleset.
rule checkAPOD { select when com_vcpnews_apod factory_reset pre { today = time:now().split("T").head() apod_date = ent:apod.get("date") } if apod_date.isnull() || apod_date < today then noop() fired { raise com_vcpnews_apod event "need_apod" } }
This event has an event domain based on our ruleset's RID (replacing dots and dashes with an underscore (so, com_vcpnews_apod)), and the event type is always factory_reset. So the above rule will be selected whenever we install the ruleset as an application.
The only essential part of this rule is in the postlude where we raise our com_vcpnews_apod:need_apod event, so that out getAPOD rule will be selected and react as described above.
Since we're going to be working on the ruleset and will need to install it many times, we decided to avoid using NASA's API more than once a day. To do that, in the prelude we get today's date (by using time:now() to get the current timestamp, split that string on a "T", and take the part before the "T" (which is the current date)), and name it (obviously enough) today.
If the ent:apod we have in hand (if we don't have one yet, it'll show as null) has a date before today's date, then we do need a new one, so the conditional action (nothing in this case, i.e. noop()) will occur (so that the rule is said to have fired), and our getAPOD rule will be selected.
Having the program display the APOD
With the two rules in place, and once we have installed our ruleset, we will have something to display. All we have to do now is expand the boilerplate function (named photo (as we specified when we created the boilerplate ruleset)):
photo = function(_headers){ title = ent:apod.get("title") app:html_page("manage Astronomy Picture of the Day", "", << <h1>Manage Astronomy Picture of the Day</h1> <img class="apod" src="#{ent:apod.get("url")}" alt="#{title}" title="#{title}"> <p class="explanation">#{ent:apod.get("explanation")}</p> <p class="explanation">#{ent:apod.get("date") || ""}</p> <br clear="all"> <p>Credits: <a href="https://apod.nasa.gov/apod/astropix.html">Astronomy Picture of the Day</a> </p> <hr> >>, _headers) }
We elected to show the picture itself (the img tag), the explanation (a paragraph provided by the picture's contributor), and the date.
For accessibility reasons we use the alt attribute (used by screen readers for visually impaired viewers) to show the picture's title, and use that same title as a tooltip (the title attribute) when the mouse is hovering over the picture.
Rather than dealing with all of the different ways the picture might need to be attributed, we also include a link to NASA's page so that they can display all of the credits in the right way.
Finally, we use some CSS to lay out our page in a more pleasing way.
styles = << <style type="text/css"> img.apod { width:25%; height:25%; float:left; } p.explanation { font-size:80%; padding:2em 400px; } </style> >> app:html_page("manage Astronomy Picture of the Day", styles, << … >>, _headers) }
Here we move subtly out of the realm of web programming into the realm of web design. We elected to display just a thumbnail (16 times smaller than the one in the url of the picture data), and place the explanation and date to the right of that thumbnail.
Having the program update the APOD
As the ruleset is written so far, on every visit it will display the picture our pico currently holds. If days go by between visits, we'll be looking at an older picture.
So, we'll add a mechanism to let us manually go out and get a newer picture. First, we'll display a gear icon that is clickable:
…
reload_url = app:event_url(meta:rid,"viewer_wants_apod")
…
a#reload { float:right; text-decoration:none; margin:0.5em; }
</style>
>>
app:html_page("manage Astronomy Picture of the Day", styles,
<<
<a id="reload" href="#{reload_url}" title="reload">⚙️</a>
<h1>Manage Astronomy Picture of the Day</h1>
…
Now, the viewer can trigger an event by clicking on the gear icon. We want our getAPOD rule to be selected when we install the ruleset or when the viewer has clicked on the gear icon. So, we declare this by modifying the rule:
rule getAPOD { select when com_vcpnews_apod need_apod or com_vcpnews_apod viewer_wants_apod … }
One final thing. When the getAPOD rule has been selected (and fetches the current APOD) we want the page the viewer is looking at to redisplay. So we add this rule:
rule redirectBack { select when com_vcpnews_apod viewer_wants_apod pre { url = app:query_url(meta:rid,"photo.html") } send_directive("_redirect",{"url":url}) }
Notes
* See the "Tutorial for a new application" for instructions to install the bazaar ruleset (and also the introspect ruleset). These applications assist you in programming your pico.
The purpose of this post is not to replace the human-facing page (because it's pretty great already), but to show how we can use an API to make a human-facing page of our own.
The finished com.vcpnews.apod ruleset is available. An alert reader may have noticed that we built the ruleset bit by bit as we thought of things. This is typical of programming. Each time we make a change, we install the ruleset (as an application) again and try out our changes. That cycle, change/install/test continues over and over again until we are satisfied and move on to something else. When an idea for an improvement occurs perhaps many days later, we enter that cycle again. Our ruleset lives and evolves in that way.
No comments:
Post a Comment