Pages

Thursday, December 22, 2016

Playing with my Raspberry Pi

It's been a while, and a few updates:  I got a pair of Raspberry Pi 3's on my desk and a Foscam exterior dome camera hanging over my garage now.  One of the Pi's is acting as a server to store the images for me on a custom built attached storage array (I've been busy!).  This post is not about those four projects though....it's about a piece of scripting that I found myself writing today.  See, the camera just dumps the files into a very flat directory structure, resulting in thousands of images and video files per day.  As you can imagine, this can build up to tens or hundreds of thousands if I'm not proactively moving the files into a structured directory tree, which until today, has been a manual process.  I got tired of that, and knew I could write a bash script to do it for me.  It only took me four hours, and here is the result:

#!/bin/bash
### Script to move output files from a Foscam exterior camera into a structured directory tree instead of storing them all in only the two default folders
### Adapted from a script found on the web at http://unix.stackexchange.com/questions/261888/help-creating-script-to-move-files-based-on-date-or-filename
### Modifed with information from http://stackoverflow.com/questions/26881104/extract-date-from-a-file-name-in-unix-using-shell-scripting
### and with information from http://stackoverflow.com/questions/12230762/insert-characters-into-a-string-in-bash
### Written by:
### Christopher M Goodrich, CISSP, ISSEP, ISSMP
### phaldor@comcast.net

### Lets set some variables to the base directories.  These should be changed to the base directories that you are using for the Foscam recording location
### and the top level folder where you want to store the organized files
BASEDIR=[insert folder here]   ### Top level storage location (we are going to make directories under this with this script)
RECDIR=[insert folder here]   ### Location where your Foscam is saving the recorded content

cd ${RECDIR}/record       ### Start with the motion recording files

for file in alarm_*.mkv       ### we are going to loop for .mkv files produced by the motion alarm
do
 ### Foscam uses alarm motion recording file format of alarm_YYYYMMDD-HHMMSS.mkv so
 ### get the year, month, and day out of filename
 rawdate=$(echo ${file} | awk -F'[_-]' '{print $2}')  ### sets variable rawdate to just the 8 digit date from the filename
 date=$(echo ${rawdate} | sed 's/^\(.\{4\}\)\(.\{2\}\)/\1-\2-/') ### sets variable date to the format 'YYYY-MM-DD'
# date=${date}-       ### adds ending '-' (ending hyphen is important)
 year=$(echo ${date} | cut -d"-" -f1)    ### sets varialbe year to the first four digits in variable date
 month=$(echo ${date} | cut -d"-" -f2)    ### sets variable month to the next two digits in variable date
 day=$(echo ${date} | cut -d"-" -f3)    ### sets variable day to the last two digits in variable date

 PARENTDIR=${BASEDIR}/${year}-${month}
 MIDDIR=${PARENTDIR}/${day}     ### create the variable for the parent folder (year and month) and storage folder (day)
 STOREDIR=${MIDDIR}/Motion-record

 if [ -d ${PARENTDIR} ]      ### if the parent directory exists
 then
  if [ -d ${MIDDIR} ]     ### if the storage directory exists
  then
   if [ -d ${STOREDIR} ]    ### if the motion-record subfolder exists
   then
    mv ${file} ${STOREDIR}   ### move the file
   else      ### the sub-folder doesn't exist
    mkdir ${STOREDIR}   ### create it
    mv ${file} ${STOREDIR}   ### move the file
   fi      ### close the if statement
  else       ### the storeage directory doesn't exist
   mkdir -p ${STOREDIR}    ### create the storage directory and the sub-folder that the file will reside in
   mv ${file} ${STOREDIR}    ### then move the file
  fi       ### close the if statement
 else        ### the parent directory doesn't exist
  mkdir -p ${STOREDIR}     ### create the directory tree to store motion recorded media
  mv ${file} ${STOREDIR}     ### then move the file
 fi        ### close the if statement
done         ### close the for loop

for file in schedule_*.mkv      ### we are going to loop for .mkv files produced by the scheduled recording feature
do
 ### Foscam uses scheduled recording file format of schedule_YYYYMMDD-HHMMSS.mkv so
 ### get the year, month, and day out of the filename
 rawdate=$(echo ${file} | awk -F'[_-]' '{print $2}')  ### sets variable rawdate to just the 8 digit date from the filename
 date=$(echo ${rawdate} | sed 's/^\(.\{4\}\)\(.\{2\}\)/\1-\2-/') ### sets variable date to the format 'YYYY-MM-DD'
# date=${date}-       ### adds ending '-' (ending hyphen is important)
# rawdate=$(echo ${file} | grep -Eo '[[:digit:]]{8}')  ### sets variable rawdate to just the 8 digit date from the filename
# date=$(echo ${rawdate:0:4}-${rawdate:4:2}-${rawdate:6:2}-) ### sets variable date to the format 'YYYY-MM-DD-" (ending hyphen is important)
 year=$(echo ${date} | cut -d"-" -f1)    ### sets varialbe year to the first four digits in variable date
 month=$(echo ${date} | cut -d"-" -f2)    ### sets variable month to the next two digits in variable date
 day=$(echo ${date} | cut -d"-" -f3)    ### sets variable day to the last two digits in variable date

 PARENTDIR=${BASEDIR}/${year}-${month}
 MIDDIR=${PARENTDIR}/${day}     ### create the variable for the parent folder (year and month) and storage folder (day)
 STOREDIR=${MIDDIR}/Schedule-record     ### create the variables for the parent folder (year and month) and storage folder (day)

 if [ -d ${PARENTDIR} ]      ### if the parent directory exists
 then
  if [ -d ${MIDDIR} ]     ### if the storage directory exists
  then
   if [ -d ${STOREDIR} ]    ### if the schedule-record subfolder exists
   then
    mv ${file} ${STOREDIR}   ### move the file
   else      ### the sub-folder doesn't exist
    mkdir ${STOREDIR}   ### create it
    mv ${file} ${STOREDIR}   ### move the file
   fi      ### close the if statement
  else       ### the storeage directory doesn't exist
   mkdir -p ${STOREDIR}    ### create the storage directory and the sub-folder that the file will reside in
   mv ${file} ${STOREDIR}    ### then move the file
  fi       ### close the if statement
 else        ### the parent directory doesn't exist
  mkdir -p ${STOREDIR}     ### create the directory tree to store scheduled recorded media
  mv ${file} ${STOREDIR}     ### then move the file
 fi        ### close the if statement
done         ### close the for loop


cd ${RECDIR}/snap       ### switch directory of focus to the snap folder

for file in MDAlarm_*.jpg      ### we are goinig to loop for .jpg files produced by the snapshot feature during motion events
do
 ### Foscam uses a file format of MDAlarm_YYYYMMDD-HHMMSS.jpg for taking snapshots during a motion alarm event so
 ### get the year, month, and day out of the filename
 rawdate=$(echo ${file} | awk -F'[_-]' '{print $2}')  ### sets variable rawdate to just the 8 digit date from the filename
 date=$(echo ${rawdate} | sed 's/^\(.\{4\}\)\(.\{2\}\)/\1-\2-/') ### sets variable date to the format 'YYYY-MM-DD'
# date=${date}-       ### adds ending '-' (ending hyphen is important)
# rawdate=$(echo ${file} | grep -Eo '[[:digit:]]{8}')  ### sets variable rawdate to just the 8 digit date from the filename
# date=$(echo ${rawdate:0:4}-${rawdate:4:2}-${rawdate:6:2}-) ### sets variable date to the format 'YYYY-MM-DD-" (ending hyphen is important)
 year=$(echo ${date} | cut -d"-" -f1)    ### sets varialbe year to the first four digits in variable date
 month=$(echo ${date} | cut -d"-" -f2)    ### sets variable month to the next two digits in variable date
 day=$(echo ${date} | cut -d"-" -f3)    ### sets variable day to the last two digits in variable date

 PARENTDIR=${BASEDIR}/${year}-${month}
 MIDDIR=${PARENTDIR}/${day}     ### create the variable for the parent folder (year and month) and storage folder (day)
 STOREDIR=${MIDDIR}/Motion-snap     ### create the variable for the parent folder (year and month) and storage folder (day)

 if [ -d ${PARENTDIR} ]      ### if the parent directory exists
 then
  if [ -d ${MIDDIR} ]     ### if the storage directory exists
  then
   if [ -d ${STOREDIR} ]    ### if the motion-snap subfolder exists
   then
    mv ${file} ${STOREDIR}   ### move the file
   else      ### the sub-folder doesn't exist
    mkdir ${STOREDIR}   ### create it
    mv ${file} ${STOREDIR}   ### move the file
   fi      ### close the if statement
  else       ### the storeage directory doesn't exist
   mkdir -p ${STOREDIR}    ### create the storage directory and the sub-folder that the file will reside in
   mv ${file} ${STOREDIR}    ### then move the file
  fi       ### close the if statement
 else        ### the parent directory doesn't exist
  mkdir -p ${STOREDIR}     ### create the directory tree to store motion recorded media
  mv ${file} ${STOREDIR}     ### then move the file
 fi        ### close the if statement
done         ### close the for loop


for file in Schedule_*.jpg      ### we are goinig to loop for .jpg files produced by the scheduled snapshot feature
do
 ### Foscam uses a file format of Schedule_YYYYMMDD-HHMMSS.jpg for taking regularly scheduled snapshots so
 ### get the year, month, and day out of the filename
 rawdate=$(echo ${file} | awk -F'[_-]' '{print $2}')  ### sets variable rawdate to just the 8 digit date from the filename
 date=$(echo ${rawdate} | sed 's/^\(.\{4\}\)\(.\{2\}\)/\1-\2-/') ### sets variable date to the format 'YYYY-MM-DD'
# date=${date}-       ### adds ending '-' (ending hyphen is important)
# rawdate=$(echo ${file} | grep -Eo '[[:digit:]]{8}')  ### sets variable rawdate to just the 8 digit date from the filename
# date=$(echo ${rawdate:0:4}-${rawdate:4:2}-${rawdate:6:2}-) ### sets variable date to the format 'YYYY-MM-DD-" (ending hyphen is important)
 year=$(echo ${date} | cut -d"-" -f1)    ### sets varialbe year to the first four digits in variable date
 month=$(echo ${date} | cut -d"-" -f2)    ### sets variable month to the next two digits in variable date
 day=$(echo ${date} | cut -d"-" -f3)    ### sets variable day to the last two digits in variable date

 PARENTDIR=${BASEDIR}/${year}-${month}
 MIDDIR=${PARENTDIR}/${day}     ### create the variable for the parent folder (year and month) and storage folder (day)
 STOREDIR=${MIDDIR}/Schedule-snap     ### create the variable for the parent folder (year and month) and storage folder (day)

 if [ -d ${PARENTDIR} ]      ### if the parent directory exists
 then
  if [ -d ${MIDDIR} ]     ### if the storage directory exists
  then
   if [ -d ${STOREDIR} ]    ### if the schedule-snap subfolder exists
   then
    mv ${file} ${STOREDIR}   ### move the file
   else      ### the sub-folder doesn't exist
    mkdir ${STOREDIR}   ### create it
    mv ${file} ${STOREDIR}   ### move the file
   fi      ### close the if statement
  else       ### the storeage directory doesn't exist
   mkdir -p ${STOREDIR}    ### create the storage directory and the sub-folder that the file will reside in
   mv ${file} ${STOREDIR}    ### then move the file
  fi       ### close the if statement
 else        ### the parent directory doesn't exist
  mkdir -p ${STOREDIR}     ### create the directory tree to store motion recorded media
  mv ${file} ${STOREDIR}     ### then move the file
 fi        ### close the if statement
done         ### close the for loop


Pretty handy little script there, and it processed the 26k files I had lying in the directories with a fair amount of speed!  You will notice that I have some extra lines in there that are commented out.  I tried a couple different methods before I found one that worked, and I failed to remove the extra lines before posting this, I was just that excited!  #HappyDance

Thursday, July 21, 2016

Pokemon Go

Have you caught the fever yet?  Well, if you haven't, perhaps you are under the impression that this is just a fad, that it's childish, or maybe you just don't quite "get" the concept of Augmented Reality (AR) games.  While this post is just about my own experience and why I got into the game, not meant to make an argument for or against it, if this post does convince you to get involved, you have my respect and support.

As a middle-aged man with teenage children, I tried to ignore the original Pokemon movement, but it sure didn't go unnoticed at the time.  Pokemon was big....HUGE, back in the day.  Unless you lived in a cave and didn't talk to anyone for years, you recognized the name, the main Pokemon creature (Pikachu), and the ball icon at the very least.  So when it hit the app store just a couple of weeks ago, I thought to myself "here we go again."  Then the news brought to my attention the staggering numbers, so I checked out what in the world was going on.  This led me to articles about Augmented Reality, city planning, and a few other stories that were only ancillary to the game itself.  Being a bit round around the middle (so to speak), and given that I saw there was a huge benefit to contributing to the data generated by the game, I decided to give it a try.  Another motivator was the teenage children in my household either already playing, or wanting to play the game.  Because I try to monitor the younger one's use of gaming and the internet, it gave me another reason to check it out.

So I downloaded it.  Keep in mind that other than a slight familiarity with the fact that it existed in the past, I had no knowledge of what Pokemon was all about, so I found myself at a disadvantage from the start.  Because I'm an intellectual, I went and did a lot of research on the game mechanics and the general world of Pokemon.  After doing all this, and playing the initial few levels in the game, I brought my younger teenagers aboard, giving me another experience on the back side of the game through the website.  So, here are my thoughts:

The game is dead simple.  At it's heart, you have a classic scavenger hunt where you run around looking for hidden things.  In this case, it's the literal truth as the game is played on the stage of the entire planet.  Think of it this way, Google has been mapping the planet Earth now for quite some time, and in doing so, has created enough public data that a game can be modeled to use it accurately through the use of Global Positioning System (GPS) receivers.  Since most smart phones come with a GPS receiver as well as the ability to download an app, the concept of adding digital data tied to GPS coordinates is not only not new, it's now old hat.  We actually see this every day without thinking of it in navigation apps.  Waze actually was a game first before it became the popular navigator that it is today.  Those that used the original version may remember your car gobbling up candy on the road.  Pokemon Go is the exact same concept, except you don't automatically get the candy once your real life coordinates intersect with it.  Add to that basic concept a simple idea.  What if, once you get within range of the object that you are attempting to get through the coordinates, you could actually see it in the context of the real world?  Well, open up that camera to this app, and your wish is granted.  Welcome to Augmented Reality.  We now have a digital object that is tied to a real world location that you can see through the camera lens.  Not only that, but you can interact with the digital object AND the real world at the same time.

This is, of course, the entire purpose of the game, collecting these objects.  Pokemon Go accomplishes this by setting up a ball throwing game overlaid on the AR experience.  If you throw straight, and take your time, you are more likely to hit your target and capture the Pokemon you are seeing.  I can vouch for what this looks like to an outsider, not much different than what everyone else not playing the game is doing already, keeping their head into their phones, and then raising it to take a picture.  Kudos to the game makers for this, other than the walking around aimlessly, these "trainers" as they are called, look like a normal person out in the world.  It could have been worse, making the game require an unusual movement which would look completely out of place or inappropriate.  Here is where anonymity in reality depart company though.  Whether you catch the Pokemon or not, something compels you to exclaim with joy or despair (as the case may be), making the players of this game extremely easy to spot.  Fortunately for them, they usually have company when they are caught in this situation.  I've experienced this first hand and in realizing that I've probably embarrassed myself, looked around and been met with sympathetic eyes rather than not.  By far the most beneficial aspect here is you are getting exercise that you may not normally be getting.

Ok, great, so collecting Pokemon is the intent here, but to do so comes with the true benefit of the game.  In order to do this, you have to walk....a lot.  The whole game revolves around this simple fact.  There are location based interactive objects other than Pokemon in the game, in the form of rest stops (Pokestops) and community centers (Gyms).  These offer game enhancing items or a place to put your collected Pokemon against other trainer's Pokemon in battle.  The former are static locations where in the real world, you will find other players standing around.  This facilitates interaction with those people, solving another real world problem, that of the increased isolation of the individual.  Because you have a common interest, there is something to talk about, facilitating a social aspect.  While some may say this can bring about a negative encounter, it's far more likely to be a positive one.  One other aspect to these locations is they base themselves on real world points of interest.  Maybe it's a sign, a mural, or a museum piece, but the point is that it exists in the real world.  Why this is important is lost on most people, but not to city planners.  Using the data points generated from this game, they can now plan activities, track visits, and promote locations.

My last point is that I am getting to interact with my children as well.  Sure, not all parents will want to run around their neighborhood playing a game while dealing with kids, but it's nice to relate and build on the relationship with your kids while seeing a few things you may not know about your area and meeting others from your local area.  If this simply isn't for you, you can purchase in game items and not have to walk around to catch the Pokemon, but that will get expensive after a while.  Worried about your data plan?  T-Mobile is now offering an incentive to not count the data from playing the game against your limits.

That about does it for this post, so to all those out there playing, happy hunting!

Wednesday, April 13, 2016

The quest for a better workspace

     Anyone who works from home may relate to this story.  I began working from home last year, and I wasn't really prepared to do so from a workspace perspective.  I started with a writing desk that my wife had rescued some time ago and slowly added surfaces to handle the monitors and personal laptop that I really did need.  The problem with this setup was that the writing desk was never meant to handle computers, especially more than one laptop, so it was very cramped and I couldn't use the CD/DVD drive without moving the laptop towards the front.  Below is a picture of the setup after I moved into a spare bedroom (not the original setup in the master bedroom, which was even more cramped than what this picture displays.

     Moving into the spare bedroom did afford me more surface area, but didn't relieve the basic problem of a surface that I could actually lay out documents or do any other work on, and I still couldn't open the drive bays on the work or the personal laptop.  Perhaps this may need a bit of explanation;  although many people get by with setups like the one I show above, I am very comfortable with a normal office environment, work in the Information Technology field, and for the last 15 years have had at least two monitors for every computer on my desk.  Needless to say, I almost feel physical pain when I don't have that second monitor, thus you see two laptops and two monitors on the setup above.  The size of the second monitor doesn't bug me too much (those are 9 year old Dell 17" ones in the picture) and actually find the larger monitors more of a problem than a benefit.

     Well, it happened one day that I was browsing online and got the thought to look at prices for desk mounted monitor stands.  Don't ask why, because I couldn't tell you what exactly I was thinking at the time since a desk mounted stand wouldn't possibly work with the setup I had, but sometimes I just get a thought into my head and I have to go with it.  As it happens, I found a very reasonably priced hex monitor stand (under $100 is reasonable given it could handle six 24" monitors at once).  True to my nature, I whipped out the card and two minutes later, the stand was bought.  Great, now what?  As I stated before, the desk setup I had wasn't built to secure a desk mounted stand off the back.  It didn't take me very long to come up with the beginnings of an idea though.  The two cabinet units on either side of the writing desk were of the same manufacturer, solidly built, and had the capability to put a surface on top to accommodate the monitor stand.  Next out of the drawer came the tape measure and I found that if I didn't want to sacrifice any leg room between them, the surface would have to be over 7 and a half feet wide.  Try to find a desk that large, I dare you.

     People that don't know me should be informed that once an idea gets into my head, I bull forward solving the problems until I have a complete solution.  Everything else gets set aside, and even if I'm doing something else, my own family catches me with a particular facial expression; head cocked to one side, staring at the ceiling muttering to myself.  Fortunately, I've been around long enough that I've picked up some experience here and there, but it is still a process.  I think I spent the next day going to the salvage yards and consignment markets looking for a suitable surface.  I did find a conference table without legs that may have done the job, but it was too heavy for me to move by myself, was far deeper than I wanted, was oval, and was priced far too high for my thinking.  Craigslist didn't yield any better results, and a quick search online didn't leave me with much hope that I'd find a desk or any reasonable facsimile thereof that fit what was in my mind.

     From the start of the search, I had it in my mind that this may be the case, and I moved to plan B; yup, you guessed it, I'm a guy, that means the local hardware store.  What I wanted was a surface somewhere in the dimensions of 8'x3' that could support the monitor stand off the back edge, which meant at least 3/4" plywood.  Keep in mind, I hadn't stepped foot into the hardware store yet, all of the above was done within the confines of my own skull so far, and I wasn't done, but I wanted to see if they had a piece of wood that may fit.  For anyone familiar with plywood, this could be a tall order, it couldn't be construction grade, it had to be finished and hardwood, meant to be exposed, not rough framing lumber.  As it happens, the local Home Depot had a very nice piece of birchwood with a surface so smooth I couldn't find an imperfection.  What's more, I liked how it looked (I love woodgrain).

     Did I mention that I fancy myself somewhat of an engineer?  Well, I am analytical and have a passing knowledge about weight tolerances, which means I usually OVER-engineer any build (ask my wife about the porch I built for the doublewide when we first got married with the 3/4" carriage bolts through 8"x8" posts).  I wasn't sure 3/4" plywood would be enough, so decided to double the thickness of the desk, which meant two sheets of 4"x8" glued together.  Now I had an idea of the desktop, but I wasn't done yet.  Two pieces in those dimensions meant that I'd be cutting the plywood lengthwise and have a 1'x8' strip left over from each piece.  I'd already be buying the whole sheet, so what was I going to do with the extra wood?  This epiphany wasn't a stretch, I could built shelves on either side of the desk.  After about an hour wandering around the store, I had a very good idea of what I was going to be building, with what materials, and a few extra enhancements to boot.  I walked out empty handed.

     "Wait, empty handed?" you say?  Yup, not only do I have to approve the budget with the wife, but I wanted to be absolutely sure of the design I had in mind, which meant modeling it on a CAD program.  I went home and found a free program that I'd used in the past, but found it didn't have a feature to draw exact measurements (or at least adjust the objects after I had approximated them with the mouse).  The second free program I tried did have what I wanted, and I proceeded to look up the specs on the monitor stand and draw my concept.  Below is the final result.

     This took me about two days to get it to a level of sophistication I was happy with.  I didn't bother drawing the bottom tier of the monitor stand to show the laptop trays I had ordered as they weren't critical for the measurements I was after.  All I wanted out of this drawing was the confirmation that I had enough room between the shelves I had thought of for the monitor stand and mounted equipment.  It looked good, and that Saturday, I pulled the trigger on the purchase.



     As soon as I could, I setup a mock build to ensure I had the store employee cut the wood the right way (the guy that cut the dowels didn't impress me with his measuring skills).

     I didn't waste any time getting to work, I had checked the weather and knew I had about a week before any rain was due, and I had to space the project out to let glue dry as well as allow for the stain/polyurethane finish to set.  I started in on the most detailed piece, the top surface of the desktop, where I had to drill 16 holes for the shelf supports and cut an opening for the sunken power receptacle I was going to put in-between the sheets.  I've spent enough time under desks to know that if I brought power to the surface, and then put the strips on the desktop, life is SO much easier.  Besides, I knew I had plenty of room to play with, so the 6"x6" hole wasn't too large of a sacrifice of desktop space.  Yes, now I had an electrical wiring job as well as a carpentry project on my hands.

     It took an evening and part of the next day to get the holes in the two sheets because I ran out of power on the cordless drill and had to charge the battery, then I took a step back and looked at that sunken receptacle again.  It was meant to go into a wall and had a 1/4" lip on it.  My worry was it would create too much of a gap in-between the plywood and cause the two sheets to not bond properly.  So I dug the Dremel out of the shed and routed the surface of the bottom sheet to sink it enough to allow the two sheets to be perfectly flush.  I managed to save one of the cutouts for a lid to hide the hole on the top surface as well and spent some time with the Dremel getting that piece and the edges of the top hole as smooth as I could so the cords wouldn't catch or fray on sharp edges.

     The next day I glued the two sheets together and added the decorative edge that I picked out (thanks to my Dad for the mitre box!).



     The picture directly above showed my learning curve on drilling holes through hardwood.  A word to the wise, don't drill through all the way from one side to the other, the final surface will chip as shown above.  Instead, drill until the spade bit tip breaks through, then turn the piece over and drill from the other side to finish the hole.  There may still be some chipping, but not nearly as bad as that first hole looks like.  The idea on these holes is the shelf supports, which are 1" diameter dowel cut 12" long and held in place through only one of the two 3/4" plywood sheets.  If necessary, a little glue would make them virtually one piece of wood, but I didn't think that was going to be necessary and I wanted the option to disassemble the desk if I ever had to move it.

     A couple of days into the next work week, the monitor stand arrived, and I was surprised to actually see the size of the thing.  The specs online weren't all inclusive, and I had guessed on certain dimensions.  As it turned out, I had fallen short on a couple of them, so back to the CAD program I went to make sure it was still going to work.  About an hour later, I breathed a sigh of relief, it was still good.  I would have to articulate the arms a bit more, but now I had the right measurements and the monitor stand was truly impressive.
     It was time to paint it with the combo stain/polyurethane I had purchased and let it sit for a few days.  This was the step I didn't have a whole lot of faith in my ability to get it just right.  That, combined with my impatience, led to only a single thick coat on the surface, but it actually looked pretty decent and the brush marks gave the impression of woodgrain, so I lost the actual look of the wood under the stain in favor of a Southwest style finish job.  After a few days, I decided I didn't have a problem with it and grew to like it.

     Originally, I had planned on using the two cabinets for supports, but they weren't going to do the entire job.  I needed to support the back half of the desk, so went out to find a couple of simple bookshelves to use in the back.  I didn't care about color or function, as long as they were 29 1/2" tall (the same height as the two I had already).  Walmart this time, no reason to build some from scratch.  I bought two, but one had an open end, and was missing the hardware kit.  I called the customer support line and they promised to send me the kit out.  I built the one I could and found it was actually a tiny bit taller than the units I had.

     That Friday the forecast called for rain, so I had to bring the finished desktop and all the raw materials inside.  I had wanted to finish the shelves, but settled for setting up the desk and waiting for the weather to clear.  Since I still had not recieved the hardware kit for the second shelf, I used a tripod, which worked out in my favor since the desktop turned out to have a very slight curve and was bowed up in the middle.  When I got it upstairs, I also found that the second shelving unit wasn't necessary, this thing was solid enough for my 200 lbs weight to climb on top and assemble the monitor stand.

     So I've worked at the desk for almost a week now and a break in the weather has allowed me to at least assemble the shelves, but not to apply the stain yet.  Here is the desk as it sits today: