Tinkers and Scriveners, 2015

Tinkering is, historically, the repair of tinware, usually by itinerant craftsmen.  Modern usage extends the term to any kind of more or less informal modification or adaptation of something.  If the object is intended to be useful or repurposed, we say it has been “tinkered with.”  If the purpose is to make an art statement, we resort to French, and call it “bricolage.”

A scrivener is traditionally a transcriptionist, someone who writes down what an illiterate person needs to have in print for legal or financial purposes.  Scriveners are still fairly common in Third-World countries, and in the First World, we use professionals to help us with legal and other formal paperwork.   In the modern age, reading and writing is more or less a universal skill, but computer coding is not, so we commonly also employ a scrivener to write down what we want a computer to do.   I think the term “scrivener” is more descriptive than “programmer.” As someone who revises more code than writing original code, we often use the combination, “Tinkers and Scriveners” as our informal motto.

In our last episode, we described a “just for fun” practice, describing (a word derived from “scribe”)  to our computers how to take pictures of our driveway, turn them into a timelapse video, and make them available on the Web.  To extend and refine this concept, we tinkered with the scripts this week, to automate the process and add an audio track, plus fix a few issues.

Try it out:  http://www.parkins.org/webcam

As part of the allure of having a video surveillance system, we had included a weather report along with the “real time” view.  The process is simple:  “Go get the weather report, take a picture, and display the picture, time, and current weather report in a web page.”   That worked fine when it was just me running it.  But, as soon as I posted the URL (Uniform Resource Locater, or web page name) to Facebook, that World-wide Eater of Time, I immediately got a “nastygram” from the weather service informing me that I had abused my (free) automated query privilege by asking for many weather reports all at once.   I don’t have all that many “fans” eagerly awaiting my next post, so I presume it was the WordPress.org  and Facebook robots burrowing into the link to see where it went–many, many times.  The search engines do this to score your site/post and the spammers also are looking for paths that lead to posts with open comment access.

So, the first order of business was to rethink the weather report.  The service only updates their reports a few times an hour, so there is no need to ask for a fresh report every time the page is accessed.  So, we told the computer, “Remember the weather report, and only get a new one if the one you have is more than 10 minutes old.”  We don’t expect a lot of traffic, but this ensures at least 10 minutes between weather updates, no matter how many visitors we get at once.

Here’s what that revision looks like, in patch form:

> import os.path
< f = urllib2.urlopen('http://api.wunderground.com/api/...key.../geolookup/conditions/q/WA/Shelton.json')
< json_string = f.read()
> stale = time.time() - 600
> if ( os.path.getmtime('weather.dat') < stale ):
> f = urllib2.urlopen('http://api.wunderground.com/api/...key.../geolookup/conditions/q/WA/Shelton.json')
> json_string = f.read()
> w = open('weather.dat','w')
> w.write(json_string)
> w.close()
> w = open('weather.dat','r')
> json_string = w.read()

A minor change, but one that prevents breaking the application programming interface (API) agreement with the weather site. We can’t control how many hits we get on our web site, but we can control how often we hit theirs.

Since we are now taking a picture every 15 seconds, and the process runs all day, taking exclusive control over the camera hardware, we no longer can take snapshots in “real time” like we did originally. However, 15 seconds isn’t a long time, so we simply copy the picture into the “current” view every time we take one, and skip the “snap the shutter” part of the weather report page.  That part we had in the last post.  What we did this time, though, was add some code to update the time displayed on the screen and refresh the picture every 15 seconds.  This requires some browser code, in Javascript,  We also added some code to show when the weather report was last updated by the weather service.  The patch file looks something like this (we also cleaned up some other code that is no longer necessary):

< <h1>The view from Chaos Central %s</h1>
> <h1>The view from Chaos Central <span id="nowTime">%s</span></h1>

> updated = parsed_json['current_observation']['observation_time']
< print "<img src=\"/images/webcam.jpg\" />"
< print "<p>The current temperature in %s is: %s F / %s C<br />" % (location, temp_f, temp_c)
> print """
> <table><tr>
> <td>
> <img src="/images/current.png" id="currentImage"/>
> <script language="Javascript">
> setInterval(function() {
> var currentImageElement = document.getElementById('currentImage');
> currentImageElement.src = '/images/current.png?rand=' + Math.random();
> var now = new Date();
> var h = now.getHours();
> var m = now.getMinutes();
> var s = now.getSeconds();
> formatnum = function( num ) {
> if ( num < 10 ) { return '0' + num; }
> else { return '' + num; }
> }
> var time = formatnum(h) + ':' + formatnum(m) + ':' + formatnum(s);
> var nowTimeElement = document.getElementById("nowTime");
> nowTimeElement.innerHTML = time;
> }, 15000);
> </script>
> """
> print "<p>The temperature in %s is: %s F / %s C<br />" % (location, temp_f, temp_c)
> print "Weather Data %s<br />" % updated 
> print "Reload page to update weather: image updates automatically every 15 seconds<br />"
> print """
> </td><td>
> <ul>
> <li><a href="/timelapse0.html">Timelapse video of Monday</a></li>
> <li><a href="/timelapse1.html">Timelapse video of Tuesday</a></li>
> <li><a href="/timelapse2.html">Timelapse video of Wednesday</a></li>
> <li><a href="/timelapse3.html">Timelapse video of Thursday</a></li>
> <li><a href="/timelapse4.html">Timelapse video of Friday</a></li>
> <li><a href="/timelapse5.html">Timelapse video of Saturday</a></li>
> <li><a href="/timelapse6.html">Timelapse video of Sunday</a></li>
> </ul>
> </td></tr></table>
> """

Those of you familiar with HTML and web page layout will also notice that we converted the page into a table and added a column with a list of links to the timelapse videos, which weren’t really visible before, since we were testing them.

Now, to make all this useful and automated, we told the computers (plural, remember–the video production takes place in another, bigger machine) to make a new video every hour on the hour and add background music to the video.  For the first part, making a video of the images captured so far, we simply add the makevideo.sh script to a cron job.  “cron” is a daemon in the Unix/Linux system that runs programs at specified times, periodically. (A daemon is not to be confused with ‘demon.’  A demon is an evil force, a daemon is a helper or servant –the term comes from the Greek, adopted by the geek culture in the 1970s.)  Because our little Raspberry Pi runs on Universal Time (UTC, what used to be called Greenwich Mean Time), and daylight on the North American West Coast, eight time zones later, spans two days under UTC,  we have to put in two jobs for each time lapse image set, one for the morning and early afternoon, and one for late afternoon and evening:

0 16,17,18,19,20,21,22,23 * * 0 /home/larye/TIMELAPSE/makevideo.sh 6
0 0,1,2,3 * * 1 /home/larye/TIMELAPSE/makevideo.sh 6
0 16,17,18,19,20,21,22,23 * * 1 /home/larye/TIMELAPSE/makevideo.sh 0
0 0,1,2,3 * * 2 /home/larye/TIMELAPSE/makevideo.sh 0
0 16,17,18,19,20,21,22,23 * * 2 /home/larye/TIMELAPSE/makevideo.sh 1
0 0,1,2,3 * * 3 /home/larye/TIMELAPSE/makevideo.sh 1
0 16,17,18,19,20,21,22,23 * * 3 /home/larye/TIMELAPSE/makevideo.sh 2
0 0,1,2,3 * * 4 /home/larye/TIMELAPSE/makevideo.sh 2
0 16,17,18,19,20,21,22,23 * * 4 /home/larye/TIMELAPSE/makevideo.sh 3
0 0,1,2,3 * * 5 /home/larye/TIMELAPSE/makevideo.sh 3
0 16,17,18,19,20,21,22,23 * * 5 /home/larye/TIMELAPSE/makevideo.sh 4
0 0,1,2,3 * * 6 /home/larye/TIMELAPSE/makevideo.sh 4
0 16,17,18,19,20,21,22,23 * * 6 /home/larye/TIMELAPSE/makevideo.sh 5
0 0,1,2,3 * * 0 /home/larye/TIMELAPSE/makevideo.sh 5

Now, the web server owns the scripts that generate the web page and take the photos, but we share the folder on the external disk drive that holds the videos and I own the video building scripts on the CentOS server, so I run this set of  cron jobs under my own user account.  The timelapse show for the current day starts at 8:00am PST (1600 UTC), and runs until well after dark ( i.e., 0300 UTC, or 7:00pm PST).  As the days grow longer, if I am still running this experiment, I will need to extend the times, or use the astronomical data to control whether or not the video needs to be generated.  At the current display rate, the timelapse video covers an hour every 30 seconds.  In mid-winter, daylight lasts less than 8 hours at this latitude, but nearly 18 hours at the summer solstice, so the video will stretch out from the current 4-minute running time to nearly 10 minutes.  The camera program starts at 4:00am and waits for daylight to start recording, shutting off automatically at dark.  The programs are written so that it would be trivially easy to change the frame rate to keep the length of the video shorter by advancing the time scale faster later in the day, as the days get longer.

The last bit of code added was for an audio track.  For this, I told the computer, “Find a song in the music library that is about the same length as the video, and add a sound track using it.”  I have enough tunes of varying length in the library so that there is a different one every hour, and hopefully enough in between so we get different ones each week as the days get longer.

Part of this exercise was to get some practice writing in the Python language.  However, the song-picker, I wrote in my old standby, Perl, just to get it done quickly.

#!/usr/bin/perl -w

%list = (
 108 => "Sintel/Jan_Morgenstern_-_01_-_Snow_Fight.mp3",
.  # lots more songs here, deleted for clarity...
 225 => "Kai_Engel/Kai_Engel_-_09_-_Embracing_The_Sunrise.mp3");

$frames = $ARGV[0];
$rate = $ARGV[1];
$runtime = $frames / $rate;

foreach $key (sort { $a <=> $b } keys %list) {
 print STDERR "check: " . $key . " against " . $runtime . "\n";
 if ( $key >= $runtime ) {
 print $list{$key};
print $list{$key};

This program is called by the shell script that runs the video:


frames=`ls ./images/${1}/img*png | wc -l`
# find a music track that is about the same length as the video
song=`./songs.pl $frames $rate`
rm surveil${1}.mp4
~/bin/ffmpeg -framerate $rate -i ./images/${1}/img%04d.png -i ./music/${song}
-c:a aac -strict experimental -strict -2 -r 30 -pix_fmt yuv420p surveil${1}.mp4
echo $song > surveil${1}.txt

This also records the name of the artist and the song (for the credits) in a file, the contents of which are displayed by the Javascript code on the web page that plays the video clip.  This is still crude, using a frame.  The other part of this learning exercise is to hone my Javascript skills and learn to use AJAX (Asynchronous Javascript And XML–actually more often JSON rather than XML)  to pull data from the server and format it so the page is well-formatted and updates seamlessly.  The time updates on the “real-time” page also needs work to format it the same as the original time statement.  One oddity in that is that the initial timestamp is the server time, but the updates are for the timezone of the browser, so the hour will change if you are in a different timezone.  This violates the “least surprise” principle of good web and program design.

Finally, here is one of the video display pages, showing the mechanism to update the current video file and song title.  I should rewrite this as a Python script, so it accepts the index of the video and creates the proper page, instead of repeating the HTML code seven times and having to edit all of them for each code change, which leads to errors and inconsistency.  The random number function added on to the file URL in the Javascript code is a hack to make the browser reload the file–if the URL doesn’t change, the browser will use the cached (old) version of the file, so that the video and song title (and photo in the ‘real-time’ page)  wouldn’t update until the cache expired (usually a couple of weeks–not very effective for an hourly or 15-second update).  That’s always been an issue when fixing customer web sites–they don’t always see the change unless they clear their browser cache.  If they are working through a proxy server, they don’t have control over the proxy caching.  A cache is  a neat way to speed up the Internet for sites you visit a lot, but it can be annoying if the browser or proxy doesn’t check the file modification time against your cached copy.  Inaccurate time-keeping on the user end can also mess up cache coherence and browser cookie management.   Fortunately, most computers now get their time updates from the Internet automatically, so this is less of a problem than it was in the old modem/dial-up days when we couldn’t afford the overhead of a lot of background processing over the link.

<head><title>Webcam Timelapse</title></head>
<table width="500px">
<tr><td align="left">
<a href="timelapse5.html">Saturday</a>
</td><td align="center">
<a href="/cgi-bin/webcam.cgi">Home</a>
</td><td align=right>
<a href="timelapse0.html">Monday</a>
<video width="320" height="240" controls>
<source src="/TIMELAPSE/videos/surveil6.mp4" type="video/mp4" id="vidFile6">
<p>Music track: <iframe id="audFile6" src="/TIMELAPSE/videos/surveil6.txt" height="55px" width="300px" onload="upDate()"></iframe></p>
<script language="Javascript">
function upDate() {
 var currentVidFile6 = document.getElementById('vidFile6');
 currentVidFile6.src = '/TIMELAPSE/videos/surveil6.mp4?rand=' + Math.random();

So, there we have it: a project that is realized in a variety of programming languages and data formats: Python, Bash (Bourne-Again SHell), Perl (Practical Extraction and Report Language or Perniciously Eclectic Rubbish Lister or just short for ‘Pearl,’ depending who you ask), HTML5 (HyperText Markup Language version 5), and JSON (JavaScript Object Notation), MPEG 4 video and MPEG 3 audio, H264 video codec and AAC audio coding, and runs on several different Linux distributions, Raspian (Debian 7 on Atom CPU) and CentOS7 (Red Hat unlicensed version on 64-bit Intel CPU, as a virtual machine running on a CentOS6 host), using the Apache2 web server software configured to use CGI (Computer Gateway Interface, not Computer Generated Images, as used in the movies) and directories aliased on a shared disk drive. We use Secure Shell (SSH) with a password-caching agent to transfer files between the web server and the video processor, and a API (Application Programming Interface) to retrieve weather and astronomical data from a remote web server. We also use a script that queries an external “whats-my-address” server to track the variable IP address of the web server and link to it by a redirection statement on our public web server, routing the request through a firewall rule and port mapping to avoid a lot of hacker traffic on the public HTTP (HyperText Transport Protocol) port (80). All of the software used is freely available, shared for the benefit of all who use it and contribute to the software community: no proprietary, hidden code, no expensive training classes, just relatively inexpensive books or free on-line documentation and how-to-do-it forums. This is literacy in the 21st century–the ability to express yourself through ubiquitous and free technology, with the freedom to tinker–if you don’t like the way the software works, change it, or write something completely different. Expand your mind. Share your code and write about your experience.

Christmas Vacation At Last

Apologies in advance for the technical detail, but I’ve been trying to get the grandchildren interested in learning to code–if not as a career, just to make the world less magical and more driven by thoughtful application of disciplined skill, like driving a car or cooking a meal.  And, above all, it can be fun.  You know who you are.  The rest of of you, sit down, hang on, and share with the next generation.  The other lesson here is that it is never too late to learn something new, nor too early.

Regular readers may know that I became more or less “full time” retired this fall, with the ending of the latest in a 14-year sequence of contracts supporting the NIH Rocky Mountain Laboratories.  Oh, I have a few commercial web clients left, with the occasional rewrite, addition, or help request when a data entry goes awry, and a few pro bono web sites to manage, but, for the first time in 50 years, I actually have only a few minor updates in this last week of the year.

Typically, the systems support folk perform maintenance on systems when the majority of the staff has time off, which, in the United States, tends to occur only between Christmas and New Years Day.  In the pre-Unix days, this often meant traveling from coast to coast to work on U.S. Navy ships in port for the holiday.  In the past 20 years, it meant working late on days when the rest of the staff got early dismissal, or telecommuting on holidays.

So, having a bit of free time, I’ve decided to “play,” which, for the thoroughly addicted Unix Curmudgeon, means writing programs for fun and wiring up systems just to learn something new (or practice rusty skills).  The geek toy of the year, or maybe decade, is the Raspberry Pi computer, the $35 Linux board (more like $50-$100, depending on what accessories are needed to set up and run it–and, once they are set up, you can run them “headless,” without keyboard, monitor, or mouse, and access them from your PC, Mac, iPad, or phone, with the proper app).  Normally fairly frugal, I have somehow managed to acquire five of these little beasties, the last one because I thought one died, but it was the SD card (like the one in your camera) that serves as the primary “disk drive,” so I bought an extra SD card and have an extra box.  I also bought one to get my hands on the new B+ model, the one with a 40-pin I/O jack and four USB ports instead of the old-style with 34-pins and two USB jacks.  I did refrain from buying another protective case, instead choosing to bolt the “replacement” board to a piece of scrap hardwood so it won’t short out.

A “crustless” Pi, mounted on a scrap of lumber. This one is a database server, running PostgreSQL.


The purpose of the Raspberry Pi, as conceived by its British designers, is to promote learning and experimenting.  Thus, the I/O port, with a number of digital and analog inputs and outputs, and a special port for a miniature CCD camera board the size of a postage stamp.  Of course, I had to have one of those, too, along with a “daughterboard” for another that provides convenient digital input/output lines with screw terminals and a couple of tiny relays.

This Raspberry Pi has a PiFace module plugged into the I/O jack, providing input/output terminals that can be connected to lights, other circuits, and switches. two relays are provided for switching incompatible voltage devices. When not wired to sensors, this unit is the head node of a compute cluster and a file server for a 1TB external disk drive.


The Raspberry Pi is, though small, a “real” Linux server, and can run almost any software.  Having been a Unix/Linux system administrator for the past 25 years, I find them  fun to play with and use to recreate small versions of big networks.  I have one that provides network information and internet name service to the local network, another that is a router and cluster head node, connecting two networks together and managing one of them, including disk sharing, one that is a gateway to the Internet (providing secure access into our network) and also a web server, one that is a print server, and another that is a database server.  And, of course, one that is a camera platform.  (In case you are counting, some do several things at once–that’s what Linux and Unix are good at, even in a small machine.)

Lacking interesting ideas, I merely pointed the camera out my office window, to provide a “window on the world,” for which I have started to experiment with different programming.  First, I simply put up a web site (using the Pi as the web server) that allows the user to take a picture of what the camera is looking at “right now.”  The Raspberry Pi promotes the use of the Python programming language (named by its author, Guido van Rossum, after the British comedy team, “Monte Python”), a language that I have heretofore avoided, since it uses “white space” as a block delimiter, and white space has been the bane of Unix shell programmers (of which I am one) since the beginning of the epoch (1/1/1970).  Nevertheless, it is a solid, well-constructed language, which is growing rapidly, in both academia as a beginning programming language and in industry and research as a powerful tool for rapid prototyping and robust permanent systems.  Python is, unlike most scripting languages, strongly typed (meaning variables are distinctly numbers, text, or other structures) and naturally object-oriented, which means data can inherit processing methods and structure, and details of structure can be hidden from other parts of the program, making programs easier to extend and debug, and making objects easily imported into other programs.

The PiCam, pointed out at the driveway, mounted in a case and held by a “third hand” workbench tool. The ribbon cable attaches the camera to the web server, below.


So, with Python as a de facto system programming language, the libraries that operate the external ports and the camera are, of course, written in Python.  Using the devices requires Python programming skills, incentive enough to finally learn the language.

The web camera project quickly evolved into a combined weather station and surveillance system, and, not surprisingly, expanded my Javascript programming skills as well.  Javascript is at the core of most interactive web applications, as it mostly runs in the user’s browser, providing dynamic interaction with the server without the need to reload the current web page, and capable of performing automatic functions.  Since part of the project involves sewing a sequence of still images into a timelapse video, it also involved building a command-line video editor from source code and learning to manipulate video with it.

The web page:  All this code does is display a photo, then update the time and the photo every 15 seconds (the rate at which the camera takes pictures).  The python code runs on the server, the Javascript code runs in the user’s browser.  The image acquisition happens in another program, which follows.

 #!/usr/bin/env python

import cgi
import time
import os
import sys
import urllib2
import json

os.environ['TZ'] = "PST8PDT"

print "Content-type: text/html"

Now = time.localtime(time.time())
DST = time.tzname[Now.tm_isdst]
Date = time.asctime(Now)

print """
<head><title>Raspberry PiCam</title>
<h1>The view from Chaos Central <span id="nowTime">%s</span></h1>
""" % Date

f = urllib2.urlopen('http://api.wunderground.com/api/79be9651d0e9e9fc/geolookup/conditions/q/WA/Shelton.json')
json_string = f.read()
parsed_json = json.loads(json_string)
location = parsed_json['location']['city']
title = parsed_json['current_observation']['image']['title']
link = parsed_json['current_observation']['image']['link']
weather = parsed_json['current_observation']['weather']
temp_f = parsed_json['current_observation']['temp_f']
temp_c = parsed_json['current_observation']['temp_c']
wind = parsed_json['current_observation']['wind_string']
updated = parsed_json['current_observation']['observation_time']

print """
<img src="/images/current.png" id="currentImage"/>
<script language="Javascript">
setInterval(function() {
 var currentImageElement = document.getElementById('currentImage');
 currentImageElement.src = '/images/current.png?rand=' + Math.random();
 var now = new Date();
 var h = now.getHours();
 var m = now.getMinutes();
 var s = now.getSeconds();
 formatnum = function( num ) {
 if ( num < 10 ) { return '0' + num; }
 else { return '' + num; }
 var time = formatnum(h) + ':' + formatnum(m) + ':' + formatnum(s);
 var nowTimeElement = document.getElementById("nowTime");
 nowTimeElement.innerHTML = time;
}, 15000);
print "<p>The temperature in %s is: %s F / %s C<br />" % (location, temp_f, temp_c)
print "and the weather is %s<br />" % weather
print "Wind: %s<br /><br />" % wind
print "Weather Data %s<br />" % updated
print "Reload page to update weather: image updates automatically every 15 seconds<br />"
print "Weather data provided by <a href=\"%s\">%s</a><br />" % (link, title)
print "Image realized on a <a href=\"http://www.raspberrypi.org\">Raspberry Pi</a></p>"

print """

So, confused yet? Below is the code that actually runs the camera. It takes a picture every 15 seconds, numbering the pictures so that a third program can sew them together into a timelapse video.  The timezone hack in the shebang line (the first line of the script) powers the timing of the script.  This script is started each day by the system before the earliest sunrise, then waits until sunrise (obtained from the weather programming interface), and runs until sunset.  We start 30 minutes early and stop 30 minutes later to start/stop in twilight.

#!/usr/bin/env TZ=PST8PDT python

import time
import picamera
import os
import urllib2
import json

riset = urllib2.urlopen('http://api.wunderground.com/api/...(api key here).../astronomy/q/WA/Shelton.json')
json_string = riset.read()

parsed_json = json.loads(json_string)
sunriseh = int(parsed_json['sun_phase']['sunrise']['hour'])
sunrisem = int(parsed_json['sun_phase']['sunrise']['minute'])
if ( sunrisem <= 30 ):
 sunrisem += 30
 sunriseh = sunriseh - 1
 sunrisem = sunrisem - 30

while (time.localtime().tm_hour < sunriseh):
 if ( time.localtime().tm_min < 30 ):
 time.sleep((sunrisem * 60) + 1 )
 while (time.localtime().tm_min < sunrisem):
 time.sleep(60 * (sunrisem - time.localtime().tm_min + 1))

sunseth = int(parsed_json['sun_phase']['sunset']['hour'])
sunsetm = int(parsed_json['sun_phase']['sunset']['minute'])
if ( sunsetm >= 30 ):
 sunsetm = sunsetm - 30
 sunseth += 1
 sunsetm += 30

print 'Sunrise: ' + str(sunriseh) + ':' + str(sunrisem) + ', Sunset: ' + str(sunseth) + ':' + str(sunsetm)


logdir = '/mnt/TIMELAPSE/' + str(wday)

# remove last week's files.
import shutil

# grab camera and start recording
with picamera.PiCamera() as camera:
 camera.resolution = (320,240)
# loop: capture method grabs camera device for duration.
 for filename in camera.capture_continuous(logdir + '/img{counter:04d}.png'):
 shutil.copyfile(filename, '/var/www/images/current.png')
 print('Captured %s' % filename)
 if ( time.localtime().tm_hour >= sunseth and time.localtime().tm_min <= sunsetm ):
 shutil.copyfile('/var/www/images/NoImage.png', '/var/www/images/current.png')

So, there it is, a program that finds the hours of daylight, day after day, and records four photos a minute, which should catch at least of glimpse of anyone entering the property. Each picture is copied to the web site as “current.png” so that the Javascript in the web page can update it to anyone currently watching (or at least who has a tab open to the site).

The next evolution is to make a timelapse movie, which, at 8 frames per second, displays an hour of observation every 30 seconds. At faster display would make the movie shorter, but moving objects won’t appear long enough for the eye to recognise them, and slower internet connections/browsers may drop frames, missing data altogether.

The Dell PowerEdge 110, configured as a virtual machine host. This currently looks like about nine different machines to the network. This provides our business with all of the various system configurations we support, including customer systems that may run on older software versions as well as the latest releases. One of the virtual machines assembles the timelapse videos for the PiCam system. Because it is very fast, and videos are hard work.

This code runs in a virtual server on our main virtual host, which contains system images for the various versions and releases of Linux, Windows, and FreeBSD that we support. This happens to run on CentOS7, the latest free Red Hat clone.  It could run on the Raspberry Pi, but requires a lot of time and processing power, so we chose to distribute parts of the process on a faster machine with more memory: the Pi has a 32-bit 700Mhz Atom processor and 512 MB of memory; the virtual host has a quad-core 64-bit 2.4Ghz  (3 times faster) Intel Xeon processor and 8GB  (16 times as much) memory.


~/bin/ffmpeg -framerate 8 -i ./images/${1}/img%04d.png -r 30 -pix_fmt yuv420p surveil${1}.mp4

Short, eh? A simple, essentially one-line shell script running the ffmpeg command with a lot of options set. But, there’s more to this. For one, the files are on an external hard drive attached to the Raspberry Pi. It takes much less processing power and time to copy files, and we can copy them incrementally through the day, so we have a driver script on the Raspberry Pi to send the files to the main server, run the remote video processing script, and retrieve the resulting video file.  In this case,  the included ‘my_agent’ script sets the environment needed to login to the remote machine using a security agent’s pre-authorized key .


. my_agent
rsync -av /mnt/TIMELAPSE/* centos7:TIMELAPSE/images/
ssh centos7 ./TIMELAPSE/makevideo.sh $1
rsync -av centos7:TIMELAPSE/surveil*.mp4 /mnt/TIMELAPSE/videos/

Lastly, a web interface is needed to display the video on the user’s browser: This is still under development as an integrated part of the webcam application, but relies on a snippet of HTML version 5 code, the latest version of the HyperText Markup Language that Tim Berners-Lee spun off as a subset/variant of the 1986 Standard Generalized Markup Language (SGML) to invent the World Wide Web 25 years ago, in 1989 (it didn’t get built until 1990). HTML 5 provides powerful tags to define multi-media constructs like video and audio that previously required specialized streaming server software and browser plugins to implement.  The code snippet below contains the Javascript hack needed to signal the browser to reload a new version of the file, rather than replay the cached version.  The final version will offer the option of displaying a timelapse video for the current day to current time (within the hour) or for any day in the past week (hence the use of an external disk drive on the Raspberry Pi, in order to store a week’s worth of surveillance video and the still pictures from which it is built).

<video width="320" height="240" controls>
<source src="/TIMELAPSE/videos/surveil0.mp4" type="video/mp4" id="vidFile">
<script language="Javascript">
 var currentVidFile = document.getElementById('vidFile');
 currentVidFile.src = '/TIMELAPSE/videos/surveil0.mp4?rand=' + Math.random();

And, so, that’s what old programmers do for fun during Christmas break. Meanwhile, I’ve developed some familiarity and skill with Python programming, honed Javascript skills, and refreshed my skills building software from source packages, and kept up to date on the latest system software from Red Hat. Jobs are out there… On the Internet, no one knows you’re a geezer, or simply doesn’t care, if you have the code skills they need.

If you want to see what’s outside our window, check on http://www.parkins.org/webcam during the day ( the camera is off at night).    The picture will update every 15 seconds until you close the browser window or tab (or go to another site from this window). Of course, it is a work in progress, and we have recently made changes to our router, so it might not work at any given time.

The Parkins Report: Events of 2014

Note: this is an expanded version of the one-page PDF we circulate.

“Entering Utah,” on Road Trip 2014, a January venture to visit relatives in New Mexico,Texas, and California.

This year was characterized by extreme medical adventures, interspersed with the usual auto tours and some slightly different activities. The year started fairly normally, with an auto tour to New Mexico and California, and a business trip to Montana, but then took a different tack.

The Southwest Loop tour began with the Bike Friday perched on top of the Jeep, with the intent of getting in some winter riding early, while visiting with kids, grandkids, and great-grandkids in Santa Fe, Las Cruces, and El Paso. In keeping with our advancing age and reluctance to let scenery pass by in the dark, we took several days enroute, stopping in eastern Oregon and Durango, Colorado, arriving in sunny Santa Fe to -11C temps, much too cold for riding.

Las Cruces was a bit more hospitable, weatherwise, and we did get in a few rides, one in the middle of a half-marathon, where we shared the trail with many runners for 2 km. The back and chest pain Larye had experienced on early-season rides for the past several years returned, but overall the ride was pleasant.

Crossing the Sonoran Desert, headed from Las Cruces to Anaheim.

Moving west, we visited relatives in Anaheim and Thousand Oaks. After a few days, we headed north, overnighting in Carmel-by-the-Sea before settling in for a few days vacation and riding at Clear Lake. The weather was a bit cold and Larye’s discomfort was more pronounced, through we did manage a 30-km ride on a mild day. Despite the drought, we drove US 101 the rest of the way north to Oregon in sometimes heavy rain, taking time to tour the scenic drives through the redwoods. In Oregon, our way was blocked by a large tree blown down across US101, with high winds when we finally reached our evening’s destination. Our tour culminated with a stop at the chilly air museum in the blimp hanger at Tillamook, then directly home after encountering snow at the 45th parallel.

A quick inspection of the exterior of our cabin: the snow was piled deep against the front door, so we didn’t go in.

In early March, we traveled to Montana, staying with nephew Rick rather than shoveling out our cabin, which was buried in several feet of snow. A business trip to Rocky Mountain Laboratory yielded a task to flesh out a web application Larye had written years before and package it for general distribution to other users of the instrumentation with which it was designed to work.

The login screen on Larye’s web app, a custom user interface to create plate definition files for the BD Biosciences FACS cell-counting instrument, originally designed for the Research Technology Section of the Research Technology Branch of the National Institute for Allergy and Infectious Disease, and soon to be released to Open Source as a Linux software package and virtual appliance, for all users of the instrument model.


At Lake Chelan, as the fruit trees were starting to bloom.

We visited with friends Gary and Char at a resort near Mt. Hood in the spring, and they stayed with us in May at McCall, Idaho. It’s always fun to share vacations.  Gary was the first to note that Larye’s exercise-related pain might be something other than reflux, having been through similar symptoms himself the year before.

A few local bike rides were cut short because of Larye’s recurrent pain when starting out. We spent a week at Lake Chelan in late April, with some riding around Manson, with minor starting-out pains. Memorial Day weekend, we rode the 30km around Payette Lake, from McCall, Idaho, with frequent stops for pain to subside and pushing through the loose sand and gravel on about a quarter of the route.

Ready to begin our circumnavigation of Payette Lake, at McCall, Idaho. The 30-km loop was fraught with frequent stops to let the angina pain subside. Judy grounds Larye for the duration of the week: three weeks later, he was in ICU recovering from cardiac bypass.

On returning home, Larye saw his physician and insisted on a cardiac stress test, “just to rule out any problems.” Well, the stress test lasted almost three minutes before blood pressure and pulse spiked over 200, and Larye was feeling pain down to his fingertips. This was on a Friday, and he was sent home with nitro pills and beta blockers, with a Monday cardiology appointment, which yielded an early Tuesday catheterization: the blockage was severe, and a full cardiac artery bypass graft was scheduled for the afternoon, as soon as the surgical team finished the morning surgery.

Waiting for lunch in the ICU, the morning after surgery.
Waiting for lunch in the ICU, the morning after surgery.

So, suddenly, the summer plan turned from training for a bicycle tour in Wisconsin to slowly regaining strength by walking back and forth on the porch, gradually extending to downtown sidewalks, then city and county parks, then regional trails, and an excursion into the Olympics and salt marshes, hiking trails we hadn’t visited in 20 years or more. By the time the Portland Knit,Quilt, and Stitch came around in August, Larye was ambulatory enough to drive to the Lacey Amtrak station and we attended the conference via public transit, after getting a clean bill of health from his cardiac surgeon, and later, a release from the cardiologist: no rehab program needed, since we were hiking up to 6km on the trails by then.

Rehab: a walk across the Tacoma Narrows Bridge, 6km round trip, before going in for 8-week checkup with the heart surgeon.

Labor Day weekend found us “on the road again,” with the weekend in Silverton, Oregon, touring the Oregon Gardens, with a brief tour of Silver Falls before heading east to the dry side for a week in the Bend area. The original plan had been for a bicycling holiday, but we continued to hike, visiting the Newberry Volcanic National Monument and hiking the trails around the resort, including an hour’s spin on a side-by-side one-speed tricycle just to prove we could still ride, albeit cautiously. Since then, Larye has set up his old Fuji touring bike on a wind trainer in the basement to get in some interval training without danger of crashing, something we don’t want to do: read on.

Workout from Larye Parkins on Vimeo.

Recovery was not without setbacks, however. A couple weeks after surgery, on July 4, Larye experienced a pulmonary embolism, which prompted another hospital stay, so he is on blood thinner for a year, which involved several weeks of daily painful injections into the stomach while building up the poison levels… Then, a few days before a planned long fall vacation trip to Montana, Idaho, eastern Washington, and British Columbia, the warfarin mistook Larye for a rat and he turned up with bleeding kidneys, for a few frightening days until the warfarin level was brought down and the flow stopped, plus some unpleasant tests to rule out bladder cancer: found a kidney stone, to be addressed later. We were able to join our vacation route “in progress,” with a trip to visit Judy’s brother and sister-in-law in eastern Washington before heading for Canada’s Okanagan Lake and a visit with Larye’s cousin, Becky.

Kelowna, BC, Canada. Where we stayed was exactly 75km each direction from cousin Becky’s house, on the opposite side of the lake. The lake is 135km long, with Kelowna and its floating bridge about halfway.

We had one more trip planned this year, at least, to spend another week at Lake Chelan, finishing out this year’s timeshare obligations, sans bicycle, but with hiking shoes. It is quiet time at the resort, with only three of the 24 units in our section occupied, including ourselves.

This was the year that Larye became more or less retired for real, after electing not to renew his contract for support of the NIH, which expired in September. He has hinted to his remaining clients that nothing is forever, so they should have a Plan B.

Being an official “retired person,” Larye didn’t have any excuses to put off completing the inside storm window project this fall, spurred on by an early cold snap in mid-November.

Our fourth year as Warm Showers hosts saw an early influx of bicycle tourists, with a trio of hardy souls in January on a Seattle-to-Los Angeles trip, and a scattering of early season tourists in between our own travels in the spring. The medical issues forced us to close for the summer as well as cancel a few reservations, but we had a flurry of guests between our Bend and Kelowna trips, and a late-November tourist who needed rescued from storms and steep hills that left him cold and wet, far short of his goal by dark, 60km from us and far from other hosts. We had to turn down yet another potential guest in early December, due to our schedules.  The guest count is close to 100, plus a number of cancellations and just requests for advice or assistance.

“Twilight in the Garden,” a quilted, discharged, appliqued, and bead-embellished piece from 2008, which now hangs in a classroom at the Lacey Senior Center

Judy continued as program director for the Olympia Weavers Guild, which is more or less a full-time job, if not a lifetime position, as few are willing to undertake the task. She also is now primarily a weaver, having sold her quilt fabric stash last year and, on the weekend before Larye’s surgery, her long-arm quilting machine. Fortunately, her health has been good this year.  Judy also sold an art piece this year, to the Lacey Senior Center, as a result of a call for entries for art to hang in the new center at Woodland Park.

Peace — Larye and Judy (and Delia)
For more photos and videos, find us on Facebook,Vimeo, or our personal blogs.  (Links to some of our videos below.)

18-year-old Delia runs the house, insisting on a lap near the fire, and her favorite quilt.

Appendix: Travels with Judy and Larye, a video notebook

Las Cruces – NMSU from Larye Parkins on Vimeo

Once past the half-marathon (2000 runners) with whom we shared the bike path, we continued on to the New Mexico State University campus, then back to our B&B on the normally busy El Paseo commercial strip, where there was no bike lane.

LakePort from Larye Parkins on Vimeo.

A ride around the north end of Clear Lake to Lakeport and back saw much heavy traffic, despite being “off season.”

Wapato Lake from Larye Parkins on Vimeo

On our spring trip to Lake Chelan, we rode up into the hills and around the lakes and apple and wine country north of Manson.

Payette Lake from Larye Parkins on Vimeo.

The Payette Lake ride was the ultimate wakeup call that no amount of diet and training was going to fix what turned out to be advanced heart disease. The lack of film footage on this ride around the beautiful high mountain lake was telling–Larye was too busy dealing with getting back to town alive to operate the camera.

Capital Lake from Larye Parkins on Vimeo.

One of our first long walks.  We also walked around the north basin of Capital Lake later, and made a number of walks on the 3-km Huff ‘n Puff trail park in Shelton, as well as other city trails and county park trails.

Staircase remix from Larye Parkins on Vimeo

Staircase is the southwest gateway to the interior of the Olympic National Park.  We last hiked this in 1985 on a weekend backpacking trip with Matt, Mark, and Jason.

Theler Wetland Nature Preserve Trail from Larye Parkins on Vimeo.

Another nostalgic visit: we hiked this tidal marsh trail when it first opened in 1994.

Amtrak Cascades – Olympia to Portland from Larye Parkins on Vimeo.

A train trip to the Quilt, Knit, and Stitch expo in Portland.  We did a lot of walking around the Lloyd Center area, where our Montana friends
were staying, as well as downtown Portland, taking the light rail and buses around the city, along with more walking.

Newberry caldera from Larye Parkins on Vimeo.

A trip to Bend, Oregon, led us to a hike around the east shore of West Paulina Lake, in the crater of the Newberry volcano south of Bend, in search of the hot springs at the north side of the crater.

ClineFalls from Larye Parkins on Vimeo.

We had intended to cycle the paths around Eagle Crest Resort and the roads and trails near Bend, but ended up hiking the trails instead, one of which led us down into the Deschutes River trail upstream from Cline Falls.

Trike from Larye Parkins on Vimeo.

Our first pedal outing, on a rented side-by-side trike at Eagle Crest Resort, near Bend.

Vacation as Mental Recreation


1. n. an extended period of recreation, especially one spent away from home or in traveling.

2. n. the action of leaving something one previously occupied.

The second definition just means “leaving,” without specific reference to reason or purpose…  As to the first, there are many different forms of recreation, which may or may not require extended travel.

Somehow, during the early 1990s, we talked ourselves into the time share mode of vacationing.  The scheme works like this:  you find a place you like to visit, with activities you like to do, and you buy a small share of a condominium at a resort near that location.  The purchase qualifies as a second home for tax purposes, so the interest bite isn’t quite so bad, and you own a tiny portion of [supposedly] prime real estate.  And, you get to use the property at designated times of the year, commensurate with the share you own.  Disregarding the purchase price, which is promoted as an “investment,” the cost of the vacation in an apartment with full kitchen, laundry, etc. and access to beach, tennis, pool, and proximity to other amenities such as golf and skiing (in season) is little more than or even less than a stay in a cramped and dark motel room nearby — provided, of course, that you use all the time allotted to you and that the property retains resale value.

Other than the fact that the amenities desirable to us, namely nice scenery and good places to ride our bicycle and hike, interesting shops, etc., make the venue less exclusive than the golfing, boating, skiing, etc., that attract most other resort guests,  our activities are exploratory, not conducive to repeat visits year after year, regardless of the season.

Not long after committing to this life-long enforced vacation plan, we found ourselves in jobs that

  1. didn’t have a lot of vacation time (as a troubleshooter and “cleaner” I tended, in the 1990s, to change jobs every year and a half, on average), and
  2. didn’t offer time off when we could schedule vacation, i.e., when not teaching night school, which was the other reason we became interested in the floating exchange system, besides the spirit of adventure: we could possibly coordinate our calendars, provided there was a vacancy in a place we wanted to visit at the time we desired.

The first reason made it difficult to use up the backlog of vacation credits, as we also needed time off to visit relatives, of which there were too many to have them visit us at the time share, and which option was impractical for a lot of reasons, such as wrong time of year for school vacations, travel cost, etc.

But, as often happened, I had jobs where I could telecommute from home, or, as it turned out, from anywhere with a phone connection (later, Internet connection).  Judy, with a more stable job, usually, had more vacation time accrued, but, due to commuting time, had little time for hobbies. Later, she was also self-employed, and vacation was a time to do her own sewing, weaving, and hand work, or portable projects for customers (we once pieced a guild raffle quilt at a time share condo, and were asked by the management to limit use of the sewing machine, as it vibrated the building).

We did have an option to get around the fixed vacation times, too:  for a few hundred dollars more a year, in membership fees and “exchange” fees, we could use our vacation credit to go to other comparable resorts instead of our own, if desired.  Which we did, for a number of years, a scheme that also allowed us to gain more vacation days by traveling exclusively in the “off season,” i.e., between the skiing and golfing/boating seasons, which are the best times for cycling and hiking, anyway.  The operative term being “comparable,” which means that all the other resorts have boating, skiing, and golfing as primary attractions, as well.

So, early on, we started using our resort time as a working vacation, hauling computers, sewing machines, looms, fax machines, and cell phones off to the resorts, spending part of the day working and the rest of the day exploring on foot or wheel.  This, then, has been our recreational plan for over 20 years.  And, the “24x7x365″ work mentality has carried through even on non-resort outings, carrying on remote Internet work while traveling, from coffee shops, motels, and campgrounds, and even sometimes phone consultations pulled over at a freeway exchange or shopping center parking lot, or, on the bike, standing in the middle of a country road in the rain.

Now, in semi-retirement, with only a few clients in maintenance mode and some volunteer work, one would expect we would be free at last to have a “normal” vacation.  Well, old habits are hard to break.  On our most recent trip, back to the original time share condo that we actually own a piece of, we loaded the car with our computers and looms, knitting needles, and balls of yarn.  This time, though, the objective was, on the fiber side, perfecting new skills and making items for relatives, and the computer efforts aimed at also learning and perfecting new skills, and working on blog items.

Judy finishes tying on the warp, ready to start weaving on the small 1930s Structo Artcraft metal loom we recently acquired.
Judy finishes tying on the warp, ready to start weaving on the small 1930s Structo Artcraft metal loom we recently acquired.

This past week’s effort was aimed at setting up an experimental compute cluster, using the popular Raspberry Pi single-board tiny Linux computers.  We have a collection of them at home, pressed into service as Internet gateway, network services, and print server, respectively, but had acquired a couple more to experiment with home automation controls and sensors.  These, we wired up at the resort as a local area network, connected to the Internet via the resort WiFi, and using dnsmasq and IP forwarding to route our other computers to the Internet.  Setting up a compute cluster also involves sharing storage resources, so we were busy installing packages for the services needed, such as NFS (Network File System) for disk sharing and PostgreSQL for a database server, with the intention of building a distributed software application server that is extensible by adding more low-cost nodes to the system.  Fortunately, it was the slow season, with few other tenants in residence, so our bandwidth hogging went unnoticed, and the service was nearly as fast as at home.

Two Raspberry Pi computers, plus power strip, Ethernet switch, WiFi dongle, USB hub, and smart phone comprise part of a makeshift local area network. Not shown: laptop computers connected to the switch and a USB hard drive added to the USB hub later. The extra cords are chargers for two iPads that complete the complement of “necessary” electronics.

This isn’t the first time we’ve wired up a router while on vacation — some resorts still charge for Internet, and only allow one or two devices on one account, so it is convenient to connect your own router and switch and run everyone’s personal devices off one login account.  In Canada, some hotels and resorts have wired Internet rather than WiFi, so having a router is the only way to share the connection among devices.  Almost all systems now have two network interfaces, ethernet and WiFi, so it is easy to make one device a router and connect the rest to it, either through the wired switch from one WiFi connection or reverse the flow (where there is a wired connection) to make the router a WiFi access point.

So it goes: you can take the system administrator out of the data center, but you can’t take the data center out of the sysadmin.  Because a lot of hotel WiFi systems have little or no security, we also use a web proxy server located in our home office–also on a Raspberry Pi and accessed through an encrypted “tunnel”–to browse sites that are also not secure.  This also hides our location from the Internet, so we don’t get a flood of local ads.  The home network gateway also provides access to a webcam to keep track of the house while we are gone.

As much as we take home with us just to have a different view out the window, the other side of time share vacationing is that we need to take time to visit relatives and time to explore and travel on our own, bicycle touring while we still can.  And, as retired folks, our income has declined (and, thanks to the 21st century economy and banking practices, so has our retirement nest egg:  we started out with nothing, and have very little of it left), leaving little discretionary income for unnecessary travel.  So, we’ve put the “fixed base” time share condo up for sale.  Never mind that it wasn’t a good investment: the current selling prices for our units are somewhat less than the real estate fees, and sales are slow, so we won’t get anything out of it, and after 20 years of declining value, our effective nightly cost has been much higher than we should be willing to pay, but we will cut the monthly maintenance fee expense.

We have a membership/owner share in another time share club that doesn’t involve ownership in a specific unit of a specific resort, but provides access to use any of their facilities, so we will still be able to enjoy condo vacationing (actually, obligated to go periodically, as the annual allocations expire within two years if not used).  The old type of time sharing a fixed location doesn’t quite work for us anymore, if it ever did, and it certainly doesn’t work for our children: none of them or even their extended families want to take on the responsibility, so we are letting it go–if it will sell. We had listed it once before, for two years, unsuccessfully, prior to converting it to the floating exchange program.  If it doesn’t sell this year, well,we will be back, toting bicycle, computers,looms, yarn, and whatever else we need to enjoy living at home away from home.

Yet Another Fruit-ful Computing Modality

Followers of the computing side of the Unix Curmudgeon blog will note that our 20-year dalliance with Linux has expanded from the server, workstation, and laptop incarnations to the appliance, namely Raspberry Pi, a tiny single-board card that runs Linux and uses an HDMI TV as a monitor. Of course, we’re familiar with the other fruity moniker, the Apple, and have even used Macs from time to time, since the advent of OS/X, the BSD-based operating environment introduced around the turn of the century:   use generally confined to command-line scripts in terminal windows, as opposed to the graphical desktop made popular with the original MacIntosh.


This summer, Judy–the Nice Person complement to the Unix Curmudgeon–who has patiently put up with the Linux-only network at Chaos Central until now–bought herself an iPad to replace the severely outclassed and underpowered Netbook road machine, which hadn’t fared well in the progressive upgrades over the years from Ubuntu 10.04 to Mint 17 (based on Ubuntu 14.04). Naturally, she has fallen in love with the tablet, the latest successor to “the computer for the rest of us.”

Meanwhile, the Curmudgeon has been making do between the Android phone and the Netbook during his limited-duty recovery from surgery earlier, akin to typing with boxing gloves while blindfolded.  So, the Nice Person, being a sentimental soul, designated a second iPad as a belated birthday present for the septuagenarian Curmudgeon.  OK, iOS, like Android, might have deep Unix roots, but, as “locked” appliances, aren’t multiuser and don’t have a command shell or root.  They also have “apps,” which aren’t “open,” though many of them are free (as in beer), and which, then aren’t customizable and it is less easy to roll your own custom apps.

So, the annoyances abound.  One of the first things of note is the way Apple deals with technologies they don’t like: they simply don’t support them.  Thus, none of the five dozen or so videos we’ve produced in the last couple of years will play on the iPad, with audio and/or video missing: we used the LAME MP3 coding for the audio track, and IOS only supports the newer Advanced Audio Coding format (OK, it’s been around for 17 years, but MP3 is still more common).  This in itself isn’t a huge problem, but it does mean re-generating all of the videos from project files, for which some of the source components have been moved, requiring hand-editing the text-based project files and searching for the missing component files.  Now, we do have some conversion utilities, but they inconveniently do not include the requisite audio coding.  The other issue is that we normally render videos at 25fps, even though the camera runs at 30, and Apple likes 30fps, period.  Fortunately, the video coding remains at H.264.  The real problem in all this was ferreting out the real cause of the problem, wading through the often less-than-helpful online help forums. The good news is that most other video software will accept the Apple set of protocols.

And so it goes: we have managed to find SSH apps that let us interact with the other *nix machines, with the exception of our bastion server, which requires host identification that we can’t generate or set on a rootless machine.  However, we can go through a third machine that is external to the network and registered with our server.

Some of the apps are just plain annoying or obtuse, with few clues as to how to get them to behave the way you expect ( no doubt some of the problem is unfamiliarity with the iOS gesture vocabulary that 4-year-olds seem to find intuitive).  As with all rapidly-changing technology today, the helpful hints found online only worked with the previous version of the system you just upgraded to.  But, yes, the slim tablet is much more portable, faster, brighter, and higher resolution than the old netbook, and the on-scene keyboard is much better than the tiny one on the phone… The apps put out by many of the sites we visit most make better use of the display than do the web browsers. I’d like to see some of the apps more generally available for Linux as well as the tablet OSes.

Oh, by the way, this post was composed, graphics and all, entirely on the iPad.  Who says old dogs can’t learn new tricks?

Musings on Unix, Bicycling, Quilting, Weaving, Old Houses, and other diversions

%d bloggers like this: