Setting-up an APRS iGate and Digipeater in Linux October 21, 2012 4 Comments

As a long time Amateur Radio Operator (callsign: KB8MON), I recently explored more into APRS, the Automatic Packet Reporting System.  This interest was driven by another interest in High Altitude Balloons (HAB), and how they might be tracked.  I live in downtown Detroit, and found that there is a vacuum here of what are called “APRS digipeaters” and “APRS igates” in the area.  APRS Digipeaters are stations that repeat the location beacons of other more mobile APRS stations (or balloons in my case), and igates, that serve a similar purpose toward relaying this digital APRS location information to the Internet via aprs2.net.

To get this going, you need:

  • An Amateur Radio (Ham) License
  • 2 Meter Radio
  • TNC Modem
  • Computer (i.e. Internet Gateway)

Here is what I came up with:

An Amateur Radio (Ham) License

I happened to already have this, but to get a new ham license, you will need to study up on the FCC requirements for getting one, but the test is not very hard.  Then you need to find a testing location in your area http://www.arrl.org/find-an-amateur-radio-license-exam-session

 2 Meter Radio

Pretty much any 2 meter ham radio will work for this.  You just need to find one that has a microphone and speaker jack that will be used to transmit and receive the radio beacons, and set it to the 144.390 frequency, which is generally used in the US for APRS.  I used an old-school Icom 32-AT transceiver.

TNC Modem

The TNC modem is a device that translates the audio from the radio into digital data.  Pretty much the only current manufacturer of these are from a company called Byonics.  I purchased their TinyTrak4 device that cost around $75 assembled, and it works great.

Computer

Although you can use any computer for this, you probably don’t want a giant desktop or laptop, as the CPU and memory required are rather small.  So, I went with a Raspberry Pi computer that runs on an ARM chip, and only costs around $35.  This computer runs an OS based on Debian called Raspian, and is connected to the TNC modem via a USB to Serial adapter, and to the Internet via a USB 802.11 WiFi adapter.  I set it up to automatically connect to my home’s WiFi, as well as to automatically start a program in Linux called APRX.

Results

So, after it is all setup and running, you can watch as you relay and record on the internet the location and tracking information of other APRS people around you.  The output looks like:

2012-10-21 21:16:56.243 KB8MON    R VE3KCR>APN383,MTCLMN*,KB8MON-1*,WIDE2*:!4226.14NS08206.23W#PHG7640/W2,SONTn,Chatham - ve3cih@rac.ca
2012-10-21 21:17:28.337 KB8MON    R KB8MON-1>KB8MON,WIDE1,WIDE2:/000000h4222.75N008305.76WI000/000Downtown Detroit iGate/Digi rob@wayne.edu/A=003281
2012-10-21 21:18:28.380 KB8MON    R KB8MON-1>KB8MON,WIDE1,WIDE2:/000000h4222.75N008305.76WI000/000Downtown Detroit iGate/Digi rob@wayne.edu/A=003281
2012-10-21 21:18:47.029 APRSIS    R BANCFT>APRX22,TCPIP*,qAC,W8FSM-JS:W8FSM
2012-10-21 21:19:28.424 KB8MON    R KB8MON-1>KB8MON,WIDE1,WIDE2:/000000h4222.75N008305.76WI000/000Downtown Detroit iGate/Digi rob@wayne.edu/A=003281
2012-10-21 21:19:28.990 APRSIS    R LANSNG>APRX21,TCPIP*,qAC,W8FSM-JS:!4245.28NS08431.53W#W2,MIn W8FSM Lansing Digi/IGATE
2012-10-21 21:20:18.134 KB8MON    R VA3OT>CQ,VE3KCR*,MTCLMN*,WIDE2*:> " Maple City Weather "
2012-10-21 21:20:20.504 KB8MON    R SHLBTP>APOT21,MTCLMN*,WIDE2*:!S8vEy9NF4# stPHG3170 W2,MIn Grid EN82lp WF5X
2012-10-21 21:20:27.560 APRSIS    R JAMTWP>APRX23,TCPIP*,qAC,W8FSM-JS:W8FSM
2012-10-21 21:20:28.468 KB8MON    R KB8MON-1>KB8MON,WIDE1,WIDE2:/000000h4222.75N008305.76WI000/000Downtown Detroit iGate/Digi rob@wayne.edu/A=003281
2012-10-21 21:20:35.070 KB8MON    R VA3OT>CQ,VE3KCR*,MTCLMN*,WIDE2*:@212122z4224.00N/08210.00W_208/001g008t059r000p000P000h00b10166.DsVP

In case you are interested in what settings/configuration I used for the Bionics TinyTrak 4 Device, here they are:

BANK is 0
P300 is FALSE
TXTDISP is FALSE
NODISP is FALSE
PPATHING is FALSE
DMSDISP is FALSE
MICETMV is FALSE
ENTS is FALSE
TELHIRES is FALSE
TELVOLT is TRUE
TELTEMP is TRUE
PREEMPT is TRUE
DIGIID is TRUE
WXPOS is TRUE
TELREAD is TRUE
FRAWDISP is FALSE
HRAWDISP is FALSE
WYPTXT is FALSE
PKTICOM is TRUE
PKTOCOM is TRUE
RPATHDISP is FALSE
LEDS is TRUE
PAVPEN is FALSE
DEC96 is FALSE
DDIST is FALSE
HEADERLN is FALSE
DMETRIC is FALSE
SOFTRST is TRUE
MSGCMD is TRUE
MSGCAP is FALSE
LRNTPS is FALSE
GPSCHK is FALSE
INTCLK is FALSE
TOSV is FALSE
TALT is TRUE
TSPEED is TRUE
TIMESTAMP is TRUE
TIMEHMS is TRUE
SBEN is FALSE
TSWPT is TRUE
AMODE is KISS
BMODE is GPS
ABAUD is 19200
BBAUD is 4800
BNKMODE is 0
SSIDROUTE is 0
ALTNET is KB8MON
MYCALL is KB8MON-1
PATH1 is WIDE1
PATH2 is WIDE2
PATH3 is
TSTAT is Downtown Detroit iGate/Digi rob@wayne.edu
BTEXT is >Downtown Detroit iGate/Digi rob@wayne.edu
BPERIOD is 900
TXD is 100
MTXD is 10
PERSIST is 0
SLOTTIME is 0
QUIET is 0
TRNKMODE is 0
CDMODE is TONES
CDLEVEL is 20
TXLEVEL is 90
TXTWIST is 50
RXAMP is 13
GWAYLEN is 232
GWAYMODE is NMEA
GRELAYBITS is 1
GRELAYRATE is 1
GKRELAY is 0
LOCATION is 4222.7500N 08305.7600W
GALT is 1000
TSYMCODE is I
TSYMTABLE is 0
STATUSRATE is 1
PPERIOD is 60
MPPERIOD is 0
SBSSPEED is 5
SBFSPEED is 60
SBSPERIOD is 1800
SBFPERIOD is 90
SBTANGLE is 27
SBTSLOPE is 255
SBTTIME is 5
MMSG is 2
TSOFFSET is 17
TDAO is 0
TPROTOCOL is APRS
TPSWITCH is 0
TPERIOD is 0
TVOLTTWK is 128
TTEMPTWK is 128
WPERIOD is 0
ALIAS1 is WIDE1
ALIAS2 is WIDE2
ALIAS3 is WIDE3
DUPETIME is 30
FILTERCALL is

Also, if you are interested in the configuration file (/etc/aprx.conf) for APRX, here it is:

mycall  KB8MON
<aprsis>
server  noam.aprs2.net  14580
</aprsis>
<logging>
pidfile /var/run/aprx.pid
rflog /var/log/aprx/aprx-rf.log
aprxlog /var/log/aprx/aprx.log
</logging>
<interface>
serial-device   /dev/ttyUSB0    19200   8n1     KISS
tx-ok   true
</interface>
<digipeater>
        transmitter $mycall
        <source>
        source  APRSIS
        relay-type      third-party
        via-path        WIDE1-1
        msg-path        WIDE1-1
        </source>
</digipeater>

Here is what the whole thing looks like closed in an Ikea box ;)

Windows Phone 7 and Zimbra September 28, 2011 3 Comments

I’ve been testing Windows Phone 7.   Specifically, its compatibility with Zimbra.   I am very impressed.   I have to admit that I am an entrenched critic of Microsoft, and was recently reminded by a colleague why this is: http://en.wikipedia.org/wiki/Embrace,_extend_and_extinguish.  Regardless of this, M$ has done a great job in implementing a mobile platform that works almost flawlessly with Zimbra and implements a standards-compliant version of ActiveSync.  Given that ActiveSync is a Microsoft product, and that their phone talks this protocol talks flawlessly with any ActiveSync service, should not be a surprise to me, but it is a wonderful feat of engineering that few beyond Microsoft have been able to perfect.

I have to say that I am strangely drawn toward the platform in its simplicity and basic function.  I miss the days of the non-graphical Blackberry OS with the text-based email and calendaring functions.  The Windows 7 platform reminds me of this basic and flawless function.  Android and iPhone seem to be riddled with a buggy glitz that is absent in the nearly perfectly executed Windows Phone 7 OS.   Also impressive is that the OS provides great integration with services such as Facebook, and provides great mash-ups with your contacts too.

What I can’t deal with is the lack of popular apps.  I am a hopelessly involved in the “app” culture of the iPhone and Android markets, and can’t give that up.  However, if you’re a person who only cares about their contacts and email, and primarily use an ActiveSync server such as Exchange or Zimbra, I couldn’t recommend a better OS than Windows Phone 7.  Hopefully, we don’t see the “extinguish” phase come too soon =).

Zimbra Backup Replication with RSYNC June 27, 2010 1 Comment

An Overview of Zimbra Backups

Creating a viable replica of a Zimbra backup can be a challenging task and the key to doing it right is to understand how Zimbra backups are structured and scheduled. The RSYNC tool is an excellent choice for an offsite replica of your Zimbra mailstore backup, as it provides options to deal with the structure. The first thing to consider is how you are backing up Zimbra in the first place. Here is a typical crontab entry detailing when and how zmbackup is called.

0 1 * * 1 /opt/zimbra/bin/zmbackup -f -a all
0 1 * * 0,2-6 /opt/zimbra/bin/zmbackup -i
0 0 * * * /opt/zimbra/bin/zmbackup -del 3m

Translated to English, this states that Zimbra is going to:

  • Line1: Do a full backup of all accounts at 1:00am on Monday
  • Line2: Do an incremental backup Sun, Tues, Weds, Thurs, Fri and Sat
  • Line3: Get rid of any backups that are older than 3 months, and check for this every day at midnight

Now, when Zimbra does its backup job, by default it puts the backup files in /opt/zimbra/backup. If you look in this directory, you will see something similar to this:

drwxr-xr-x   5 zimbra zimbra    4096 Jun 27 01:24 .
drwxr-xr-x  54 root   root      4096 Mar 28 02:23 ..
-rw-r-----   1 zimbra zimbra 1750611 Jun 27 01:24 accounts.xml
drwx------   2 root   root     16384 Apr 26  2009 lost+found
drwxr-x--- 148 zimbra zimbra   12288 Jun 27 01:24 sessions
drwxr-x---  13 zimbra zimbra    4096 Jun 27 01:24 tmp

The accounts.xml file contains vital information about the backups. Zimbra requires this information in the case of a restore and without this file, your backup will be useless. The information in this file includes:

  • The translation between the Zimbra UUID for a person and their account name
  • Where to look for the most recent full backup of an account

In your sessions directory, you should see the actual backup directories:

drwxr-x---   5 zimbra zimbra  4096 Jun 24 01:23 full-20100624.052333.282
drwxr-x---   5 zimbra zimbra  4096 Jun 25 01:20 full-20100625.052028.170
drwxr-x---   5 zimbra zimbra  4096 Jun 26 01:30 full-20100626.053024.490
drwxr-x---   5 zimbra zimbra  4096 Jun 27 01:24 full-20100627.052414.328
drwxr-x---   5 zimbra zimbra  4096 Mar 27 02:05 incr-20100327.050647.501
drwxr-x---   5 zimbra zimbra  4096 Mar 31 01:25 incr-20100331.050704.213
drwxr-x---   5 zimbra zimbra  4096 Apr  1 01:21 incr-20100401.050711.294
drwxr-x---   5 zimbra zimbra  4096 Apr  2 01:27 incr-20100402.050621.529
...

Zimbra automatically uses hard links to reference message files between backup sets that have not changed since the last backup. By only storing a message for a user once, this keeps the total size of your /opt/zimbra/backup/ directory to a minimum. However, you must consider these hard links when designing your backup solution. Otherwise, you can easily make this mistake:

  • If you blindly copy this data to another directory without considering the hard links, it will balloon in size at the target location, because the hard links between full backups will not be preserved and each full backup will be the full size of your mailstore. These backups should still be viable, but it would be a huge waste of space.

If you want true point-in-time recovery, you need to copy everything in /opt/zimbra/backup/ while preserving hard links. You should not pick and choose what to copy.

If you were hard-up to do something quick, a minimum emergency backup of Zimbra would include only taking the most recent full backup directory in /opt/zimbra/backup/sessions/, while preserving the /opt/zimbra/backup/ directory structure otherwise, along with the necessary accounts.xml file. However, this is not recommended. Zimbra intends for everything in /opt/zimbra/backup/ to be replicated and managed as a whole, and if you start picking it apart, you definitely will not have point-in-time recovery, and you run the risk of not getting everything that Zimbra needs to do a full restore. If you choose to pick apart the Zimbra backup scheme, make sure you understand entirely how it all works first. Also, if you use auto-group mode and if you replicate only the most recent full backup from a list of many available, a incomplete replica is all but guaranteed.

Replication with RSYNC

Zimbra stores individual messages in individual files. For small Zimbra installations, this is not a large management problem. For very large zimbra installs, this can be challenging to work with. In fact, it’s almost impossible to do this with any version of RSYNC before version 3. For more about this, see the post I did a few years ago. Since then, RSYNC has introduced a life-saving feature (“–recursive”). This is only available in RSYNC v3 and allows RSYNC to recursively traverse directories and start the initial transfer from sources with millions of files very quickly. Depending on the size of your backups, and with RSYNC v2, it could take days to even start transferring files from a Zimbra backup directory.

So, there are 2 important switches that you need to pass your RSYNC v3 process to create a fast, viable backup are:

  • –recursive This takes advantage of the recursive logic in RSYNC v3
  • -H This will preserve hard links, and keep the size of your /opt/zimbra/backup/ replica under control

And here it is put into an RSYNC command:

rsync -avzH --exclude="lost+found" --recursive --delete -e ssh zimbra@zimbra.server.com:/opt/zimbra/backup /my/local/replica/ > /my/local/replica.log

This assumes that you have an SSH key pair setup. Remember that RSYNC simply copies data from the source to the target. If you mistakingly delete your /opt/zimbra/backup/sessions/ directory or make some other horrible mistake, it will replicate that mistake. It is good practice to turn off your RSYNC replication before making any major changes as this replicate may be your only safeguard.

Now, about scheduling.. Run your rsync task after Zimbra typically completes its full backup and incremental backups, and leave enough time to ensure that the RSYNC completes before starting the next Zimbra backup. Depending on the size of your mailstore, this may mean staggering your Zimbra and RSYNC replica operations to each occur every other day.

Good luck with your Zimbra backup replication!

How To: 330 Gallon Rain Barrel June 6, 2010 5 Comments

Here is some information about my two 330 rain barrels. This might help you if you are interested in saving some money on your water bill and keeping a fair amount of the rainfall over your property off the watershed and from becoming just wasted runoff.

A lot of sites out there sell those 50 gallon drums for rain collections. I don’t recommend them. Regardless of the size house you have, you will easily overflow one of those things with a sprinkle of rain. I’ve even found this 330 gallon barrel to be too small, so I have installed 2, one in the front of the house and one in the back for a total of 660 gallons of rain collection.

Adding some fair size rain barrels/containers to your property is surprisingly easy and cheap. The first thing you need to do is to buy a few of the rain barrels off eBay. Search for liquid tote or tote container or the like. These containers are normally used for shipping large amounts of oils/liquids on trucks/trains. Many sellers will let you pay a little extra to have them cleaned out. Otherwise, just do your best with a hose and everything will be ok.

You can expect to pay between $70 and $120 a piece on eBay if you look around. You’re going to pay a premium for shipping as they will be delivered via truck, but that’s just the way it is.

First, find a place near your existing downspout and drain to place the barrel. Then, setup and level a pad of cinder blocks on the soil around where you want to lay the tote. Make sure that the soil is compact and level. Then, place the tote on top of the cinder blocks when it arrives.

Next, you are going to need to intercept your regular downspout with some PVC. This will likely involve getting some PVC elbows and some lengths of 4″ and 2″ pipe. Here is how I connected mine.

I simply put an elbow on the top part to angle some 4″ PVC toward the tote, then, on the overflow return, jammed some 2″ PVC down the existing aluminum hookup to the gutter drain.

The most complicated part is setting up the connector on the top of the tote so that when you get a big downpour that overflows tote, extra water will flow back into your sewer system. I’m not going to bore you with a 1000 words about this, as a picture is worth more than 1000 words. So here it is.

The key here is to cut a hole in the plastic cap that comes with your tote and glue/attach a 4″ PVC connector to it. Then, get one of the screw-in 2″ PVC connectors, drill out a 2″ hole in the side and connect the 2″ PVC in the side of the 4″ PVC. Then, get some aluminum flashing, adjustable strap some window screen and attach as shown. When the water overflows, it goes down the 2″ pipe to the sewer.

Some notes to remember about the whole thing.. First, the barrel will weigh more than 1 ton, or about 2700 pounds when full. If you don’t compact your soil before placing the cinder blocks, the barrel will list to one side or the other when weighed down. Next, don’t seal all of your PVC joints until the very end. You can probably even get away not sealing them at all. Also, you will likely need a few connectors from your hardware stores to connect the large pipe at the base of the tote to your garden hose. Use plenty of caulk/silicon on the joints.

Well, that’s it, and good luck!

Android SDK – $25 and an hour later January 1, 2010 1 Comment


If you’ve ever had an inkling to get into mobile software development, now is your time. Google has drastically narrowed the gap for wanna-be mobile developers to get into this arena with their opensource and free Android SDK (Software Development Kit). Exciting new phones such as the, HTC “Google Phone”, Motorola Droid, Motorola Cliq and MyTouch are making a splash and diverting attention away from the all-consuming iPhone market.

Android does have a bit of an identity crisis going on, no doubt because of the openness of the platform and the freedom it provides to developers hoping to capitalize on it. The marketing buzz surrounding different phones and software like Droid, MyTouch, G1, Google Phone, MOTOBLUR, Android, etc. seem to have a lot of people disoriented and on the surface, it is a bit confusing. All of these products are really only variously branded versions of a base Android OS running on a phone with some custom apps pre-installed. Android apps that you write will generally work on all of these. Here’s a breakdown:

Common Name Platform OS
Verizon Droid Motorola phone with some custom apps Android 2.0/2.1
Google Phone HTC Hero phone with some Google apps Android 2.0/2.1
T-Mobile MyTouch 3G HTC phone with some Google apps Android 1.5/1.6
T-Mobile Cliq Motorola M200 with some Motorola “MOTOBLUR” apps Android 1.5/1.6


Motorola has written “MOTOBLUR” as an application layer for Android 1.5/1.6 on the Cliq that allows corporate Exchange sync and other stuff that lets you consolidate your different accounts from Facebook, etc. Droid is a more generic version of Android 2.0 which has Exchange syncing built-in along with built-in apps for integration to Google services. It seems likely that Motorola will upgrade “MOTOBLUR” to be Android 2.0 and sideline MOTOBLUR in the future as there is a bit of overlap between what Android 2.0 and MOTOBLUR try to accomplish.

The Android SDK is available for free to download and so is Eclipse and Java. The Android OS is based on a Linux kernel and the Android GUI and API are written in Java. You can get the development environment running under MacOS X, Windows and Linux and the included documentation is excellent. Java is what enables the Android application packages (.apk files) to be installed on any architecture phone. A comprehensive development environment built on top of Eclipse allows you to visually draw the layout for your Android app, then fill in the blanks with a mix of more Java and XML. A variety of libraries built into the Android API such as Apache HTTP libs make interacting with web-based services over cell networks very easy. Also unlike Apple, a complete “service” infrastructure allows you to write daemons/services that run in the background with ease. Android incorporates the lightweight opensource database system SQLite to deal with local data storage and includes a basic library much like a registry for storing application preferences and restoring previous states of apps.

public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);	   	   
  TextView tv = new TextView(this);
 
  tv.setText("sun3.org says hello!");
  setContentView(tv);
 
  // Start my custom service, if it is not running already
  startService(new Intent(this, AndroidConnectService.class));
  }

Within the SDK, you first need to become familiar with the terminology. For example, the GUI that you write is called an “Activity”, “Intents” are actions along with data (calls to activities/services) and the presentation/screen is a “Layout”, etc. Once you get familiar with the basic terminology of the Android SDK and poke around a little, a design philosophy in Android emerges regarding the sharing of access to your interfaces and services. The entire platform is designed to be very modular and to allow other developers to use your Activities and Services.

You don’t even need an Android phone to start your development, as the SDK comes with a complete emulator that lets you test your apps without the pain of installing your software on actual Android hardware every time you make a change. Incidentally, this is also a great way to test-drive Android without buying a phone or borrowing your friend’s phone.

Once you are ready to test out your app on a real phone, you’ll find that the Android OS allows you to download and install your .apk file directly from a web URL. Unlike Apple, the philosophy around Android app development is one of un-restricted access. Apple has a more restrictive App Store, and hackers need to “jail break” their phones before they can install non Apple-approved apps. Apple is only making it harder to jail break their phones. Presumably, Apple is trying to save people from themselves and their own distaste. Google has a different philosophy for Android, whereby they make the SDK components, the OS and source for the whole platform freely available and open to tinkering. It’s surprisingly easy to get an app working, tested and installed on your off-the-shelf Android phone. I had their included “helloworld” example running within about an hour, including downloading the SDK/Java JRE, poking around, compiling, testing on their emulator and installing on my regular phone.

So now you’re wondering where the $25 dollars comes in? The Android Marketplace asks $25 for you to become an official developer. This includes the ability to both upload and sell applications in the official Android Marketplace, which is akin to the Apple AppStore. Unlike Apple, there is little to no review (read “censoring”) of the apps that you sell at the Google Android Marketplace. The $25 is just to keep the spammers away. So, what are you waiting for? =)

praux.com API October 28, 2009 1 Comment

praux.com
If you havn’t seen praux.com‘s fancy API, you should definitely have a look. Here’s an interface I put together that uses the API to display praux-hosted resumes on your own site. Praux is a new site that allows you to assemble, maintain and share your resume with others. The REST design and personal URLs at Praux allow you to reference, export and display your resume in many different ways. Here’s some code that uses the YAML feed of your praux.com resume, to render it on your own site. You will need the spyc YAML parser to use the below script. Have fun!

<?
require_once("spyc.php");
$data = file_get_contents("http://your.url.praux.com/yaml/");
 
$resume = spyc_load_file($data);
 
echo "<b>" . $resume[name] . "</b><br>";
echo "<b>" . $resume[email] . "</b><br>";
echo "<b>" . $resume[phone] . "</b><br><br>";
 
$sections = $resume[sections];
 
function recurse_child($child,$level=0,$lastout=false,$subsection=0) {
    $level++;   
    if ($lastout==false) {
        $level--;
        }
    if (is_array($child)) {
        foreach ($child as $itemid => $item) {
            if (is_array($item[content_items]))  {
                if ($item[content_items][0][body]) {
                    echo "<span style='display:inline;position:relative;left:120px;'>";
                    echo str_repeat("&nbsp;",$level*8);
                    echo "</span>";
                    echo "<div style='display:inline;position:relative;left:135px;'>";
                    echo "<span style='position:absolute;left:-15px;font-size:1.0em;'>";
                    echo "&bull;";
                    echo "</span>";
                    echo trim($item[content_items][0][body]);
                    echo "</div>";
                    echo "<br>\n";
                    $lastout=true;
                    }
                if ($item[content_items][0][date_range]) {
                    if ($subsection > 0) {
                        echo "<br>";                
                        }
                    $subsection++;
 
                    echo $item[content_items][0][date_range] . " &nbsp;&nbsp;&nbsp;";
                    echo "<span style='position:absolute;left:120px;'>";
                    if ($item[content_items][0][organization]) {
                        echo "<b><i>" . $item[content_items][0][organization] . "</i></b>, ";
                        }
                    else {
                        echo "<b><i>" . $item[content_items][0][title] . "</i></b>, ";                
                        }                
                    echo $item[content_items][0][locality];
                    echo "</span><br>";
                    echo "<span style='position:absolute;left:120px;'>";
                    echo $item[content_items][0][title];
                    echo "</span><br>";
                    }            
                if (is_array($item[children]))
                    {
                    recurse_child($item[children],$level,$lastout,$subsection);
                    }
                }
            }
        }    
    }
 
echo "<div style='position:absolute;width:600px;'>";
foreach ($resume[sections] as $sectionid => $section) {
    foreach ($section[content_blocks] as $blockid => $block) {
        echo "<span style='font-size:1.5em;'>" . $block[content_items][0][body] . "</span>";
        echo "<span style='position:relative;'><hr></span><br>\n";
            foreach ($block as $childid => $child) {
            recurse_child($child);
            }
        echo "<br>";
        }
    }
echo "</div>";
 
?>

Ubuntu (Dapper) on the PC Engines Alix 3D1 October 25, 2009 11 Comments

alix-3D1
The Alix 3D1 is a small PC with an LX700 AMD Geode cpu. It costs $75 USD and can, with enough patience, run just about any mainstream Linux OS that you can think of. That is thanks to the AMD Geode processor, which is i386/x86 compatible, so you won’t need any special ARM distributions or the like to get the thing going.

This is a tutorial on installing specifically the Ubuntu Dapper distro on the Alix 3D1 with the help of a Windows PC. 

For some reason mainstream Ubuntu installs after Dapper have issues completing the installation procedure and will likely cause problem for you. If you are able to install something released after Dapper, please let me know!

Where to buy an Alix 3D1

First of all, I’ll post some info about where to get the Alix 3D1. It is made by a company called PC Engines and there are 3 different versions of the board. The cheapest is the 3D1 ($75).

This tutorial also should apply to the 3D2 model. The 3D3 model has an Award BIOS and a VGA port, so things might be a little different with that one (and likely easier). Available distributors:

What you need to get with the Alix 3D1

The 3D1 comes in a cardboard box with nothing but the motherboard and some anti-static packaging, so you’re going to need to order a few accessories with it too:

  • A power supply
  • An enclosure. You can’t go wrong with the metal waterproof one, it’s very nice.
  • If you’re planning on doing 802.11, you’ll want the type-N coaxial pigtails for any NICs you are adding to the 3D1′s 2 miniPCI slots.
  • A Compact Flash (CF) card preferably about 4GB or larger

Total cost for the Alix 3D1, power supply, waterproof closure with mounts and 2 type-N 802.11 pigtails was only $146.90 USD.

Accessing the Serial Console

putty
First thing you will need to do is to get access to the console on the 3D1. I’m using Windows, so the best terminal program out there (AFAIK) is PuTTY. You can download PuTTY from:

After you have PuTTY installed, you will need a serial cable with a null-modem adapter. Connect the serial cable with null-modem to the 3D1 to your Windows PC and start up PuTTY. Select ‘Serial’ for connection type and put the baud rate at 38400 and type in the COM port (COM[1-X]). 38400 is the default baud rate of the 3D1.

PC Engines ALIX.3 v0.99
640 KB Base Memory
130048 KB Extended Memory
 
01F0 Master 848A SAMSUNG CF/ATA
Phys C/H/S 992/16/32 Log C/H/S 992/16/32

Booting Ubuntu (Dapper) via PXE

ubuntulittleThe best way I’ve found to load a Linux distro on the box is to use the built-in PXE boot functionality of the BIOS. When the BIOS on the 3D1 boots, it does a DHCP broadcast to find any available PXE boot servers out there. You don’t have to worry about other DHCP servers on your network responding unless they are especially setup to do PXE. PXE uses your specialized DHCP server + TFTP (setup below) to bootstrap the device. If everything is setup correctly below, your DHCP server responds and provides an image for the device to download and run (bootstrap). After the image is downloaded and executed, then the fun begins.

This seems like a lot of stuff to get configured to do this, but luckily, there is some software that makes this pretty straight foward. The best utility I’ve found to do this a Windows program called TFTPD32:

You will first need to prepare a directory on your Windows PC for the TFTP server to serve out a kernel and a boot image. Create a directory on your windows machine for the files. I called mine C:\tftpd.

Downloading and preparing Ubuntu Dapper netboot.tar.gz

Download the following package to your C:\tftpd directory:

tftp-settingsYou will need a program to unzip .gz and extrat .tar files. A great one for Windows is 7-Zip, which is available here:

After the archive is in the directory, extract the .gz, then the .tar underneath the c:\tftpd directory. After this is done, you’re ready to prepare your TFTP and DHCP servers.

Setting up TFTPD32

You will need to setup your TFTPD32 application as follows. You are going to need to enable ‘PXE Compatibility‘ as well as make sure that DHCP and TFTPD servers are enabled. Also, be sure to set the path correctly for your ‘base directory‘ as shown, as well as setup your new DHCP server to give normal addresses on your network. The DHCP server that you setup here will serve up the correct boot image when an address is given out to the 3D1. There is an area to to input what image you want to provide to new DHCP hosts. In this textbox under the DHCP tab of TFTPD32, enter ‘pxelinux.0‘. Make sure that your default gateway, actual DNS server, subnet and IP ranges all make sense in the configuration. If you have problems getting PXE booting to work, be sure to look at your logs tabs. They are invaluable for debugging what is wrong.
tftp6

Starting up the Alix 3D1!

When you first startup the 3D1, you are going to need to modify the BIOS config to allow PXE booting. To do this, type ‘S’ when the 3D1 is testing memory. This should bring up a menu of options. To toggle PXE booting, type ‘N’, then save the configuration to the BIOS.

PC Engines ALIX.3 v0.99
640 KB Base Memory
130048 KB Extended Memory
 
01F0 Master 848A SAMSUNG CF/ATA
Phys C/H/S 992/16/32 Log C/H/S 992/16/32
 
BIOS setup:
 
(9) 9600 baud (2) 19200 baud *3* 38400 baud (5) 57600 baud (1) 115200 baud
*C* CHS mode (L) LBA mode (W) HDD wait (V) HDD slave (U) UDMA enable
(M) MFGPT workaround
(P) late PCI init
*R* Serial console enable
*E* PXE boot enable
(X) Xmodem upload
(Q) Quit
 
Save changes Y/N ?
Writing setup to flash...

When the 3D1 boots back up it, should start checking for a DHCP server, see the TFTPD services you have running, grab an available IP address from your DHCP server and start to load the kernel.

PC Engines ALIX.3 v0.99
640 KB Base Memory
130048 KB Extended Memory
 
01F0 Master 848A SAMSUNG CF/ATA
Phys C/H/S 992/16/32 Log C/H/S 992/16/32
 
Intel UNDI, PXE-2.0 (build 082)
Copyright (C) 1997,1998,1999  Intel Corporation
VIA Rhine III Management Adapter v2.43 (2005/12/15)
 
CLIENT MAC ADDR: 00 0D B9 13 CD 7C
CLIENT IP: 192.169.0.20  MASK: 255.255.255.0  DHCP IP: 192.169.0.250
GATEWAY IP: 192.169.0.1

Monitoring the TFTPD32 Logs

You should now see some activity in the TFTPD32 logs. The following would be a good sign:

Read request for file <pxelinux.cfg/default>. Mode octet [25/10 17:20:35.078]
OACK: <tsize=1821,> [25/10 17:20:35.078]
Using local port 2360 [25/10 17:20:35.078]
<pxelinux.cfg\default>: sent 4 blks, 1821 bytes in 0 s. 0 blk resent [25/10 17:20:35.140]
Connection received from 192.169.0.20 on port 57099 [25/10 17:20:35.140]
Read request for file <ubuntu-installer/i386/boot-screens/boot.txt>. Mode octet [25/10 17:20:35.140]
OACK: <tsize=301,> [25/10 17:20:35.156]
Using local port 2361 [25/10 17:20:35.156]
Connection received from 192.169.0.20 on port 57100 [25/10 17:20:35.234]
<ubuntu-installer\i386\boot-screens\boot.txt>: sent 1 blk, 301 bytes in 0 s. 0 blk resent [25/10 17:20:35.234]
Read request for file <ubuntu-installer/i386/boot-screens/splash.rle>. Mode octet [25/10 17:20:35.250]
OACK: <tsize=8023,> [25/10 17:20:35.250]
Using local port 2362 [25/10 17:20:35.250]
TIMEOUT waiting for Ack block #0  [25/10 17:20:50.343]

Ok, here’s the tricky part

Ubuntu will by default try and send the console output to a VGA source. Since there is not a VGA source on the 3D1, you are going to have to do tell it to send the console to the serial port. Problem is, you cannot see that it is currently at a boot prompt asking you what to do. In the TFTPD server logs, you should be able to see that it has grabbed the pxelinux.0 file and is waiting on something. This is your queue!. Type into PuTTY the following (you will not be able to see the output of your typing):

linux console=ttyS0,38400

..and hit Enter.
ubuntu
Within about 10 seconds, you should see the console outputting to your PuTTY window and the kernel booting. Your 3C1 should now have a valid IP, Netmask, router and DNS server configuration and it should continue to install Ubuntu from the Ubuntu network archives server. The rest is a piece of cake!

Credits

The following site(s) were very helpful in creating this tutorial

GroupWise 6.5 to Zimbra AddressBook August 22, 2009 No Comments

Here is some code to translate a GroupWise 6.5 address book into a format that Zimbra can understand.

nab2zimbra.php

<?php
function get_value_by_mapi($zimbraid, $nabid,$nabvalues,$nabfields)
    {
    if ($nabid == "")
        {
        return  "";
        }
    for ($i=0;$i<sizeof($nabfields);$i++)
        {
        if ($nabfields[$i][mapitag] == $nabid)
            {
            return $nabvalues[$i];
            }
        }
    }
 
if (count($argv) < 2)
    {
    echo "Usage: php nab2zimbra.php <input_file.nab> \n";
    }
else
    {
    $inputfile = trim($argv[1]);
    if (file_exists($inputfile))
        {
        $row = 1;
        $handle = fopen($inputfile, "r");
 
        /* 
        :::TAGMAP:::0FFE0003:***,
        3001001E:Name,
        3A08001E:Office Phone Number,
        3A18001E:Department,
        3A23001E:Fax Number,
        3003001E:E-Mail Address,
        3A06001E:First Name,
        3A11001E:Last Name,
        3A17001E:Title,
        3A29001E:Address,
        3A27001E:City,
        3A28001E:State,
        3A26001E:Country,
        3A2A001E:ZIP Code,
        3002001E:E-Mail Type,
        3A19001E:Mailstop,
        3A09001E:Home Phone Number,
        3A1C001E:Cellular Phone Number,
        3A21001E:Pager Number,
        3A1A001E:Phone Number,
        600B001E:Greeting,
        600F001E:Owner,
        3A16001E:Organization,
        3004001E:Comments,
        3A00001E:User ID,
        6604001E:Domain,
        6609001E:Additional Routing,
        6605001E:Post Office,
        6603001E:GUID,
        6607001E:eDirectory Distinguished Name,
        6608001E:Network ID,
        660D001E:Internet Domain,
        660E001E:AIM/IM Screen Name,
        3A45001E:Prefix,
        3A44001E:Middle Name,
        3A05001E:Generation,
        3A5D001E:Home Address,
        3A59001E:Home City,
        3A5C001E:Home State,
        3A5B001E:Home ZIP,
        3A5A001E:Home Country,
        3A50001E:Personal Web Site,
        3A51001E:Office Web Site,
        6612001E:Resource Type,
        6615001E:Primary Contact Name
        */        
 
        // First go through the input file and load an associative array with mapped MAPI tags        
 
 
 
        $fields = array();
 
        while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) 
            {
            if ($row==1)
                {
                $num = count($data);
                $row++;
                // This should be the header line
                $fieldmap = array();
                for ($c=0; $c < $num; $c++) 
                    {
                    $thismapitag = "";
                    $exploded = explode(":",$data[$c]);
                    $thismapitag = $exploded[0];                     
                    $fieldmap[$c][value] = $exploded[1];
                    $fieldmap[$c][mapitag] = $thismapitag;
                    }
                }
            else
                {                
                $num = count($data);
                $row++;
                for ($c=0; $c < $num; $c++) 
                    {
                    // Zimbra mapping
                    /*
                    birthday	
                    company	= 3A16001E
                    department = 3A18001E
                    dlist	
                    email	= 3003001E
                    email2	
                    email3	
                    fileAs = 1,2,3	
                    firstName	= 3A06001E
                    fullName = 3001001E
                    homeCity = 3A59001E
                    homeCountry	= 3A5A001E
                    homeFax	= 
                    homePhone	= 3A09001E
                    homePhone2	
                    homePostalCode = 3A5B001E
                    homeState	= 3A5C001E
                    homeStreet = 3A5D001E
                    homeURL	= 3A50001E
                    initials	
                    jobTitle = 3A17001E
                    lastName = 3A11001E
                    middleName = 3A44001E
                    mobilePhone	= 3A1C001E
                    nickname	
                    notes	= 3004001E
                    pager	= 3A21001E
                    type	
                    workCity = 3A27001E
                    workFax	= 3A23001E
                    workPhone	= 3A08001E
                    workPhone2	
                    workPostalCode = 3A2A001E	
                    workState	= 3A28001E
                    workStreet = 3A29001E
                    workURL = 3A51001E
                    */
                    $fields[$row][$c] = $data[$c];
                    }
                }
            }
        fclose($handle);
 
        // Ok, now build the header row:
        $zimbraheader = array("birthday","company","department","dlist","email","email2","email3","fileAs","firstName",
        "fullName","homeCity","homeCountry","homeFax","homePhone","homePhone2","homePostalCode","homeState","homeStreet","homeURL","initials","jobTitle","lastName","middleName","mobilePhone",
        "nickname","notes","pager","type","workCity","workFax","workPhone","workPhone2","workPostalCode","workState","workStreet","workURL");
 
        for ($i=0;$i<sizeof($zimbraheader);$i++)
            {
            echo $zimbraheader[$i] . ",";
            }
 
        echo "\n";
 
        // Loop through all the input rows
        foreach ($fields as $key => $val)
            {
            $nabfields = $fieldmap;
            $nabvalues = $fields[$key];
 
            // Only import people objects, not company objects.
            if ($nabvalues[0] == "U")
                {
                echo get_value_by_mapi("birthday", "",$nabvalues,$nabfields) . ",";            
                echo get_value_by_mapi("company", "3A16001E",$nabvalues,$nabfields) . ",";                                                                 
                echo get_value_by_mapi("department", "3A18001E",$nabvalues,$nabfields) . ",";
                echo get_value_by_mapi("dlist", "",$nabvalues,$nabfields) . ",";
                echo get_value_by_mapi("email", "3003001E",$nabvalues,$nabfields) . ",";
                echo get_value_by_mapi("email2", "",$nabvalues,$nabfields) . ",";
                echo get_value_by_mapi("email3", "",$nabvalues,$nabfields) . ",";
                echo get_value_by_mapi("fileAs", "",$nabvalues,$nabfields) . ",";
                echo get_value_by_mapi("firstName", "3A06001E",$nabvalues,$nabfields) . ",";
                echo get_value_by_mapi("fullName", "3001001E",$nabvalues,$nabfields) . ",";
                echo get_value_by_mapi("homeCity", "3A59001E",$nabvalues,$nabfields) . ",";
                echo get_value_by_mapi("homeCountry", "3A5A001E",$nabvalues,$nabfields) . ",";
                echo get_value_by_mapi("homeFax", "",$nabvalues,$nabfields) . ",";
                echo get_value_by_mapi("homePhone", "3A09001E",$nabvalues,$nabfields) . ",";
                echo get_value_by_mapi("homePhone2", "",$nabvalues,$nabfields) . ",";
                echo get_value_by_mapi("homePostalCode", "3A5B001E",$nabvalues,$nabfields) . ",";
                echo get_value_by_mapi("homeState", "3A5C001E",$nabvalues,$nabfields) . ",";
                echo get_value_by_mapi("homeStreet", "3A5D001E",$nabvalues,$nabfields) . ",";
                echo get_value_by_mapi("homeURL", "3A50001E",$nabvalues,$nabfields) . ",";
                echo get_value_by_mapi("initials", "",$nabvalues,$nabfields) . ",";
                echo get_value_by_mapi("jobTitle", "3A17001E",$nabvalues,$nabfields) . ",";
                echo get_value_by_mapi("lastName", "3A11001E",$nabvalues,$nabfields) . ",";
                echo get_value_by_mapi("middleName", "3A44001E",$nabvalues,$nabfields) . ",";
                echo get_value_by_mapi("mobilePhone", "3A1C001E",$nabvalues,$nabfields) . ",";
                echo get_value_by_mapi("nickname", "",$nabvalues,$nabfields) . ",";
                echo get_value_by_mapi("notes", "3004001E",$nabvalues,$nabfields) . ",";
                echo get_value_by_mapi("pager", "3A21001E",$nabvalues,$nabfields) . ",";
                echo get_value_by_mapi("type", "",$nabvalues,$nabfields) . ",";
                echo get_value_by_mapi("workCity", "3A27001E",$nabvalues,$nabfields) . ",";
                echo get_value_by_mapi("workFax", "3A23001E",$nabvalues,$nabfields) . ",";
                echo get_value_by_mapi("workPhone", "3A08001E",$nabvalues,$nabfields) . ",";
                echo get_value_by_mapi("workPhone2", "",$nabvalues,$nabfields) . ",";
                echo get_value_by_mapi("workPostalCode", "3A2A001E",$nabvalues,$nabfields) . ",";
                echo get_value_by_mapi("workState", "3A28001E",$nabvalues,$nabfields) . ",";
                echo get_value_by_mapi("workStreet", "3A29001E",$nabvalues,$nabfields) . ",";
                echo get_value_by_mapi("workURL", "3A51001E",$nabvalues,$nabfields) . ",";
 
                echo "\n";
                }
            }
 
 
        }
    else
        {
        echo "Could not find input file: " . $inputfile . " \n";
        }
    }    
?>

Favorite EBay Searches January 19, 2009 No Comments

EBay is a place that lacks description. It is a staple of the Internet, and is often criticized for being a hodge-podge outlet that resembles a dollar store. However, there are treasures that can be found on EBay that extend well beyond what can be purchased at your local store. Now, I know you can probably find lots of crazy things for sale at specialized Internet stores.. But really, where else but EBay can you find 100′s of vendors selling them in an atmosphere of free market competition? Here are a few of my favorite EBay searches and some information about them.

Ancient Roman Stuff

Ancient Roman artifacts are plentiful on Ebay. The Roman empire lasted nearly 1200 years and encompassed, at its peak, nearly 2.3 million square miles of what is now the general area known as Europe. 88 million people called Rome their capital, at the peak. The peak of the Roman civilization lasted from 27 BC – AD 476, that’s a minimum 1,533 years ago. Museums are filled with the best of Roman stuff. So much so, that they rarely accept new artifacts, unless they are exceptional. What that means for you is that you can have an incredible piece of history for almost nothing. One of the common pieces of Roman history sold on EBay are ancient roman coins. These coins come in a variety of states and conditions. They are often sold “uncleaned”, which means that the people who found them (often with a metal detector in “hordes”), have not stripped the corrosion from their surface. However, you can be quite assured that they have kept any pieces that are gold or silver.

About a year ago, I got mildly curious about these coins and ordered a set of 20 or so un-cleaned Roman coins. After following the detailed directions for cleaning that came with the coins, I found about 1/4 of them cleaned reasonably well. Here is a detail of one of these coins. I have no idea who/from where the militant looking guy or lady is on the surface, but it was a lot of fun for about $10 bucks.

A Roman coin that I cleaned
unknown


Search ‘Ancient Roman’ on Ebay

Surgical Supplies

You simply cannot find some treasures at your local retail outlets, or anywhere else for that matter. This includes stuff like surgical sutures. You or someone you know has probably been in situation where you were far away from medical help and needed it quickly. Unfortunately, many medical establishments, especially those in the US figure that you are an absolute idiot and can’t tend to your own body or that of someone else, without a 10 year degree. With a little bit of You Tube training, you can easily suture an open wound and maybe save someone from disfigurement or death from loss of blood. The only problem is that you can’t buy the basic supplies needed to tie a suture, a med school 101 procedure. At least in the US, your local pharmacy doesn’t carry this stuff. But on EBay, it’s a click away.

suture

Search ‘suture’ on EBay

Well, that’s it for now. I’ll be sure to be back with more EBay treasures soon =)

iCAMView Pro November 27, 2008 No Comments

If you’ve ever searched on eBay or Google for IP-based camera security things, you’ve undoubtedly seen advertisements for these little plastic USB camera servers called the iCAMView. I finally got curious about these things and ordered one off eBay. I’ll admit, they’re really impressive. One of the downfalls of many IP-based cameras and camera servers is always the software. In general, you get what you pay for in this regard. However, the iCAMView device software is excellent.

The one thing that grabbed my attention about the iCAMView is that they recently released a new version of the device called the “iCAMView Pro”, which can use higher resolution USB cameras. Image resolution is one of my pet peeves and largely why I generally choose to stay away USB-based camera servers.

iCAMViewPro

http://icamview.com/

Once I started testing the device, a few things caught my eye. First, there is an option to automatically reboot the device at a specified interval. Most manufacturers of these things have some ridiculous idea that their devices will stay running indefinitely, without rebooting. I’ve tested many IP camera devices and they universally eventually lock up or start running slow. At least the iCAMView people were humble enough to add this option.

Secondly, you can plug a USB storage device into the thing for local storage. It even manages old files and deletes them when the storage hits a user-specified limit. This is another thing they added to the software to make the device a “set it and forget it” camera server.

Third, it’s fast! I don’t know how they manage to put a full web server packed with features such as motion detection in such as small package, but it’s amazing how responsive the web interface is, even when it’s actively capturing photographs.

Fourth, it supports many wireless USB dongles and even supports WEP encryption/keys.

Overall, the device is very impressive and well worth a look. It has user upgradable firmware and I’ll soon be testing the higher resolution USB cameras with it.