A digital picture frame with weather forecast

Reuse and Recycle

© Lead Image © Christos Georghiou, 123RF.com

© Lead Image © Christos Georghiou, 123RF.com

Author(s):

A digital picture frame displays photographs and a current weather forecast with just a few hundred lines of Bash and a Raspberry Pi.

Not all that long ago I was thinking it would be pretty neat to buy the small-form-factor SheevaPlug computer  [1]. However, it was pretty pricey for a computer for which I had no specific use, so I never bought it. All of that went out the window when the pretty powerful and super affordable Raspberry Pi came out in 2012. Although I didn't have any immediate project in mind, I lined up with everyone else to purchase one.

Once I received my Raspberry Pi, I set out to create projects (e.g., a proxy server, LED cube [2], and networked music device [3]) just to get started. I also experimented with the I2C and SPI protocols and played with devices previously in the domain of the Arduino.

Before I knew it, I had a small collection of these neat little devices. Over time, however, they fell out of use. These classic Raspberry Pis still work, but they aren't as fast and as capable as the most recent Raspberry Pi. While visiting my parents last summer, I saw their old unused TFT monitor in the corner, and it occurred to me how it could be reused. A 15-inch monitor is nothing special when connected to a PC, but it is pretty fabulous when turned into an electronic picture frame.

Most electronic picture frames, usually 8 to 10 inches measured diagonally, simply cycle through photos either in internal memory or on an SD card. The task is well within the capabilities of the Raspberry Pi, but I had another twist that would make this a premium solution. I don't visit my folks as often as I would like, but I would like pictures of their grandchildren always displayed prominently.

What I envisioned was as picture frame connected to common data storage that could be maintained remotely and to which photos could be downloaded automatically. Yet considering the non-technical nature of my parents, the picture frame would need to be self-maintaining.

To justify the counter space for a 15-inch monitor, I would really need to convince my folks of the extra utility this solution would bring. My mom keeps a pretty close eye on the weather forecasts, so I thought the picture frame could also retrieve current weather information.

The picture frame functionality can be summarized in a few words, but implementing the solution would require a fair number of "moving parts":

  • LCD monitor
  • Raspberry Pi
  • SD card
  • USB stick
  • HDMI-to-VGA adapter cable
  • WiFi dongle/Ethernet cable
  • Weather data
  • Shared Internet data source

The hardware in my case was an old Raspberry Pi B with a WiFi dongle. The setup of the operating system was just a matter of imaging an SD card [4] with an image from the Raspberry Pi site. For this project, it was important that the Raspberry Pi boot automatically and log in to the desktop to display pictures. Although not a requirement, SSH is useful during development and perhaps later for support.

Helper Tools

All functionality would be controlled from a few Bash scripts, with a few utilities to perform some of the tasks:

apt-get install curl jq feh

curl retrieves data with URLs. More specifically, it will be used to retrieve weather data. Information found on the Internet comes in a number of formats, but one of the easiest data formats to use for most web pages is JSON, because it is not so wordy as XML. Even so, it is a bit unwieldy when used from the command line without some additional help, which comes in the form of the command-line utility jq, a lightweight JSON processor. This utility allows individual values to be selected out of a JSON structure without a lot of complicated syntax:

data.json
{
  "temp": 14.15,
  "pressure": 1009,
  "humidity": 87,
  "temp_min": 12.22,
  "temp_max": 15.56
}

Like other command-line tools, the jq command can process either a file or an input stream:

cat data.json | jq -r .pressure
>1009

A graphics program, preferably one that can be run from the command line, will display the photos. The feh program does all of this, and the -F option ensures the menu and taskbar are hidden. The feh image viewer can display a complete slideshow, or it can display a single image. The single-image display makes it the perfect tool and gives complete yet granular control over which images are displayed and for how long:

feh -F -x <picture fn>

The Raspberry Pi can have a large SD card or even an additional USB pen drive for picture storage, but a mutually accessible attached drive requires the Internet. A number of cloud solutions could make external storage available, but I wanted a solution that also had a friendly web interface.

Data Storage

Dropbox, one of the original data drives on the Internet, provides free disk space (2GB) and a web interface for access. Although 2GB is not a lot of space for many tasks, it should be plenty for holding a few photographs. (See also the "Google Drive" box.)

Google Drive

My initial Internet data drive was not Dropbox, but Google Drive. Google has a much larger storage space of 15GB associated with each existing email account and has the community-supported gdrive client on GitHub [5] that performs much like the Dropbox command-line tool. One slight difference between the two clients is that the Google Drive client operates on file IDs, not file names.

When I started this project, I used the gdrive client, but while re-verifying all of the steps, I discovered that the client no longer worked for new email addresses. It turns out the original developer embedded their application ID into the client and either the ID was revoked, or it hit some magical limit at Google and was disabled.

If you prefer to use Google Drive, the source code does exist. It is simply a matter of registering with Google and recompiling the client. If you are comfortable with the Go programming language or would like to learn a bit about it, this might be an interesting project.

The dbxcli command-line tool [6] lets you access Dropbox as a drive. This client is available for a few platforms other than Linux, such as macOS and BSD. Yet before you can use this client, you must connect it to the Dropbox account with your credentials. The first time you run the client you are prompted for verification (Figure 1).

Figure 1: First time running the dbxcli client.

Entering the URL into your browser in response brings up the login dialog. When you enter your credentials, you will be asked if it is acceptable for this tool to have access to your Dropbox account (Figure 2). Pressing the Allow button brings up your verification code in a new dialog. Simply copy and paste the displayed code into your terminal session to finish setting up the dbxcli client.

Figure 2: Enabling Dropbox access.

Although it isn't important to know how this client works, it creates a .config/dbxcli folder in your home directory that contains an access code to be used as necessary by the dbxcli client. Once the code has been given, the client is set up, allowing control of Dropbox files from the command line. Client operations are somewhat limited but provide more than enough functionality to access Dropbox as an Internet-connected disk. The only operations needed for the picture frame are directory listings (ls), uploading files (put), downloading files (get), and deleting files (rm).

Weather Data

The weather functionality doesn't have very complex requirements: I simply want to get the current temperature and display a brief forecast. Initially I thought about opening up a web page and trying to capture the information there; however, that seemed a bit underhanded as well as difficult. I did find a number of sites that provide free weather information, but OpenWeather [7] was the easiest site to use.

OpenWeather provides different levels of service for retrieving data. Depending on the level of service you select, the data can be as old as a few hours or as recent as 10 minutes. It is possible to get weather forecasts, weather maps, the UV index, weather alerts, or just the current weather. The pricing model depends on the amount of weather or type of historical information needed.

The picture frame will only need at most a few calls per hour, which is considerably less than the 60 calls per second you can make with the free account. OpenWeather is affordable but it also makes its data available over REST API calls, thus eliminating the need to download libraries and compile programs to retrieve data. The one catch is that you have to sign up to use their service.

Once you sign up [8], you receive a key (Figure 3); with this token you are able to download your weather data by the API. You can even create additional keys for specific purposes.

Figure 3: The OpenWeather key maintenance screen.

OpenWeather has quite a number of REST calls, each of which fetch different types of weather data. Each API accepts a number of parameters, which makes it easy to customize the results. What is really nice is the consistency of parameters between different REST calls, making it easier to understand when you are looking at new REST calls.

When signing up, you receive an API key, or APPID (Figure 3), which is required for OpenWeather to associate your requests with a valid user for the premium function calls. However, if you do not provide your APPID parameter, you cannot even call their free services.

OpenWeather provides quite a number of ways to define for which city or location you want to retrieve weather. The most unambiguous method would be to find your desired city in the list of cities. You can get a list of all weather stations and their unique IDs, as well as their locations as longitude, latitude, city name, and country [9]. The API allows you to pass in the city name and country, although this doesn't work quite as well for the United States.

The problem I had with their list was trying to distinguish between which entry was Minneapolis, Minnesota, and which was Minneapolis, Kansas. OpenWeather does not appear to make any distinction between the US states, so passing in a popular city name might not return the city data you are expecting. To resolve this problem, I had to use a reverse lookup site on the Internet [10].

To select the current temperature or retrieve a weather forecast, you would use:

http://api.openweathermap.org/data/2.5/weather
http://api.openweathermap.org/data/2.5/forecast

The parameters in Table 1 can be used with your requests, as well. If you want to keep your network requests to a minimum, you can use the group call. Simply pass in a comma-separated list of city IDs to receive a JSON structure for each:

http://api.openweathermap.org/data/2.5/group?id=2925533,5037649

Table 1

REST API Parameters

Parameter

Description

appid

The value associated with your OpenWeather user account.

q

The city name, which should include the country for clarity (i.e., London, UK) but is not mandatory.

id

The weather site.

units

The default is Kelvin, which isn't so friendly for normal people; however, you can change the units to metric or imperial (i.e., units=metric).

mode

The data returned is in JSON format by default. If you would prefer either XML or HTML, you can select that format with the mode parameter.

lang

Most data returned by OpenWeather does not need internationalization; however, a small description field gives a hint about the weather, such as light rain or sunny. By assigning the lang parameter, you can see these weather hints in the language of your choice.

Although not a comprehensive look at all the calls you can use to retrieve data from this service, it does show all of the important methods I used to generate my weather forecast.

Creating PNG Files

The data from the weather service is returned in JSON format, which is pretty convenient if you are parsing this information by JavaScript but somewhat less convenient for a graphics application.

With the use of jq, I easily extracted the values I needed, but that still left the problem of how to display the data. The only data the picture frame displays now is photographs. Although I could probably overlay information to the screen a number of different ways, I chose to create an image from the data with the use of a familiar technology: Apache FOP, a print formatter that can be used in document processing to convert XML data into a PDF file by XSL-FO (extensible stylesheet language – formatting objects). Also, you can use XSL-FO to convert the data into other output formats, including rich text (RTF) or PostScript (PS), but it is just as easy to create a TIFF or a PNG from the same stylesheet.

Because FOP makes it possible to create PNG output files, all of my data on the picture frame will be simple graphic files, simplifying my processing. Apache FOP is open source, so I just downloaded the latest version [11]. With a working Java environment, I could create PNG files.

Raspbian comes preinstalled with OpenJDK, the version of which depends on which Raspbian image you use. My problems appeared the first time I tried to run Java; I kept receiving an error about initialization:

Error occurred during initialization of VM

Server VM is only supported on ARMv7+ VFP

This error turned out to occur because OpenJDK 11 is compiled for a processor chip in the newer Raspberry Pis. Although the processor is backward compatible for older code, the older chip cannot run the OpenJDK 11 compiled for the new processor. The solution to this problem was to remove the current version of the JDK and install OpenJDK 8:

sudo apt-get purge openjdk*
sudo apt autoremove
sudo apt-get install -y galternatives openjdk-8-jdk

Formatting Objects Template

The Apache FOP print formatter XSL-FO combines quite a few technologies from the extensible stylesheet language and XPath. The main difference between Apache FOP and other markup languages such as HTML is separation between the data and the formatting instructions. HTML does not always separate the data to be formatted from the formatting instructions, which makes it difficult to have multiple views of a single source of data.

The XSL-FO template describes in detail how exactly the data should be formatted. The description has the physical output size, margins, and orientation of text. Other supported types of formatting are not dissimilar to those in a normal word processor, which allows you to change fonts, text size, justification, and colors of text or background. Also, you can create lists, import graphics, create watermarks, and make tables of all types.

Listing 1 shows the template necessary to create a small 7cm square document with 2cm margins, in the middle of which appears Hello World.

Listing 1

XSL-FO Hello World

01 <?xml version="1.0" encoding="UTF-8"?>
02 <xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
03     xmlns:fo="http://www.w3.org/1999/XSL/Format" exclude-result-prefixes="fo">
04 <xsl:template match="weather">
05     <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
06       <fo:layout-master-set>
07         <fo:simple-page-master master-name="pnglayout" page-height="7cm"
08                 page-width="7cm" margin-top="2cm" margin-bottom="2cm"
09                 margin-left="2cm" margin-right="2cm" >
10           <fo:region-body />
11         </fo:simple-page-master>
12       </fo:layout-master-set>
13       <fo:page-sequence master-reference="pnglayout">
14         <fo:flow flow-name="xsl-region-body">
15           <fo:block>Hello World</fo:block>
16         </fo:flow>
17       </fo:page-sequence>
18      </fo:root>
19 </xsl:template>
20 </xsl:stylesheet>

Because of the general complexity of XSL-FO, it would take quite a few pages to describe a simple template properly. A number of books fully describe creating such templates if you are interested in learning more about this technology. You can see my favorite tutorial on the topic online [12].

Source Code

To make REST API calls, parse the resulting JSON output, and coordinate the processes, you could use any of a number of languages. The script or program will power a picture frame that displays new photos every few minutes, so you don't need sub-second response times. With this in mind, I created a Bash script to control and process both the image and weather data.

I enjoy writing Bash scripts, and I am also interested in how others solve various scripting problems; however, I suspect I am in the minority on this subject. Therefore, I will show a few small excerpts of the various scripts, and the rest of the scripts are available as a download [13]. The scripts must handle four tasks:

  • Update photos on the Raspberry Pi
  • Select and display a picture
  • Download the weather forecast
  • Generate an image from forecast

These tasks are interesting enough to take a deeper look.

Updating Photos

The simplest way to update the Raspberry Pi might be to wait for some special trigger file to be generated. In this case, you would just list the files in a directory on Dropbox, and when the trigger file exists, you would transfer the files.

Although I might use this method to coordinate a file transfer at work, this solution doesn't exactly scream user friendly. A different method would be to copy over all the files periodically to the Raspberry Pi. In this case, the synchronization is just a matter of removing all existing photos, gathering a list of all images on Dropbox, iterating through that list, and copying over each file. In the future, I might revise the synchronization mechanism, but for now, I will do this once a week.

The first step is to gather up a list of files to be copied over and then to iterate through that list. A rather cute way of doing this is to use both head and tail to get a specific entry from the list:

NAME=`head -${IDX} $WORKFILE | tail -1`

Downloading the file from Dropbox is then just a matter of running dbxcli with the name of the current file to retrieve.

Selecting and Displaying a Picture

Displaying a picture is as easy as running the feh program with the name of the file to be displayed over the desktop. The entire goal of the picture frame is to allow for dynamic updating of photos, but even keeping that in consideration, the photos will remain fairly static most of the time; therefore, I maintain the current list of files to display, which is generated each time the files are copied from Dropbox.

Displaying all photographs is just a matter of looping through the list of existing files. Each photo should be displayed for at least a few minutes. I decided that the script shouldn't run continuously; instead, from the list of pictures, it should choose the current image, display it, and exit. The script, then, is run by a crontab file every few minutes. For this to work, the script will need to maintain its state between runs.

The pickpicture method in Listing 2 retrieves the current item in the list to be displayed, advances the index, and then displays the current picture.

Listing 2

Display Bash Code

01 pickpicture()
02 {
03    IDXFILE=$PICDIR/index.dat
04    cd $PICDIR
05
06    IDX=`cat $IDXFILE`
07    CNT=`cat $PICLIST | wc -l`
08    NAME=`head -$IDX $PICLIST | tail -1`
09
10    IDX=$(($IDX + 1))
11
12    if [ $IDX -gt $CNT ]
13    then
14       echo 1 > $IDXFILE
15    else
16       echo $IDX > $IDXFILE
17    fi
18
19    showpicture $NAME
20
21    cd $INSTDIR
22 }

Downloading Weather Data

Retrieving the weather for a given city is quite easy. The OpenWeather site does the authentication once at the beginning and gives you a time-unlimited key, so you only need to pass the key in with each call:

CMD="http://api.openweathermap.org/data/2.5/weather?id=${CITYID}&appid=${APPID}&units=metric"
curl -s $CMD > $TODAY

The script simply makes a call to the OpenWeather URL with the appropriate parameters, and you receive a JSON object in return from the curl utility. The returned weather structure is not overly complicated: Simply select each field of interest.

Extracting the values was straightforward except for sunrise and sunset, because these values are stored as a Unix UTC value. The good news is that this can easily be converted to a more user-friendly value with the date command:

# parse out data from data file
sunset=`cat $TODAY | jq -r '.sys' |jq -r '.sunset'`
sunset=`date --date=@$sunset +'%H:%M:%S'`

Generating Forecast Image

Apache FOP was a convenient choice because I can define a template and merge it with a data file, although it glosses over the generation of the template and XML data file. The Bash script parses all weather data and simply creates an XML data file that matches the requirements of the template I have created. With FOP and OpenJDK installed, I generated a PNG file with:

fop -xml <xml input fn> -xsl <xsl template>-png <output fn>

In my weather template, I tried to include as much information as possible. The script examined the weather description and selected a matching graphic, which is friendlier than pure text. Unfortunately, the forecast has a lot of information (Figure 4).

Figure 4: Weather and forecast.

Miscellaneous Configuration

The standard default for the Raspberry Pi is for the screen to blank after a certain amount of inactivity. Inactivity is inevitable when you don't have a keyboard or mouse attached to the picture frame system. To control this behavior, go to the X server configuration file (i.e., /etc/lightdm/lightdm.conf for the Raspberry Pi) and add the line:

xserver-command=X -s 0 -dpms

This setup might already exist in the default section but will need to be uncommented. According to online sources, you will find this line under the [SeatDefaults] section, but in my case, the section was called [Seat:*].

Scheduling

The various scripts that perform the individual tasks are run by the cron daemon (Listing 3). Because only one thing can be displayed on the screen at a time, the crontab entries have been organized in such a way that twice an hour the weather and forecast are retrieved and displayed at times that normal photographs are not.

Listing 3

Crontab

#
# next image
0,10,15,20,25,30,40,45,50,55 * * * * /home/pi/screensaver/showpic.sh > /dev/null 2>&1
#
# get and show current weather
4,34 * * * * /home/pi/screensaver/getweather.sh > /dev/null 2>&1
#
# simply copy everything from Dropbox each week
1 1 1,7,14,21,28 * * /home/pi/screensaver/forcesync.sh > /dev/null 2>&1

Lessons Learned

Although creating something new with my old Raspberry Pi was really fun, I did encounter a few problems along the way. My first problem was mainly self-inflicted: I had hoped it would be possible to find an inexpensive, custom-made wooden frame online. Although I found a number of options, the real cost was shipping, which was almost as much as the cost of the frame itself.

In my last minute dress rehearsal, one difficulty turned out to be that an old USB cable I was going to bundle with the picture frame wasn't delivering enough power. Ensure you test the exact hardware you are planning to use.

The most unexpected problem was networking. My initial development used an Ethernet connection, and I never had any problems connecting. Only during my final testing with a WiFi dongle did I encountered networking problems when the network periodically stopped working. After some research, it seems that others had similar problems in the past and have solved this by restarting the networking component. I created a small script to check the Internet connection and restart it if necessary. Running this script once a minute from the root crontab solved my networking problems.

All the necessary logic for displaying the photographs and gathering the current weather forecast can be done with just a few hundred lines of Bash script (Figure 5). This new electronic picture frame should be useful for many years to come.

Figure 5: The monitor with its picture frame.

The Author

Christopher Dock is a senior consultant at T-Systems On Site Services GmbH. When not working on integration projects, Christopher likes to experiment with Raspberry Pi solutions and other electronics projects. You can read more of his work at http://blog.paranoidprofessor.com.