Tuesday, June 12, 2018

An Ideal Soundtrack My Amtrak Adventure

Last week, as I was sitting in the Amtrak waiting area at Union Station, it occurred to me that I could try tuning in to the behind the scenes operations. I did a quick Google search for Amtrak radio frequencies and this page came up. I plugged my SDR dongle into my Linux laptop and kicked off this rtl_fm command:

  rtl_fm -f 160440000 -l 20| \
   tee ~/dl/chatter.raw |  \
   play -t raw -r 24k  -es -b 16 -c 1 -V1 -

Along with a burst of static, I could clearly hear radio traffic! I quickly ran through squelch values, and found that -l 200 kept the static at bay. Here's a sample of what I heard:

The relative calm of the waiting area contrasted nicely with the near continuous discussion over the radio.

It seems that whenever I get inspired to listen in on radio traffic, I spend nearly all my time trying to dial in the right frequency; usually giving up before I hear anything. But not this time, everything just worked!

Pulling away from the station, I found that the traffic ceased. As we approached other stations, I tried to tune in again. rtl_fm supports monitoring multiple frequencies at once, making for a command line radio scanner. Here's a command line I used to listen in on multiple Amtrak channels:

 rtl_fm -f 160.425M -f 160.650M -l 200 | tee ~/dl/chatter.raw | \
   play -t raw -r 24k  -es -b 16 -c 1 -V1 

And here's a command line that scans nearly all 100 of the known Amtrak channels:

rtl_fm -l 200 \
-f 159.81M -f 159.93M -f 160.05M -f 160.185M -f 160.2M -f 160.215M -f 160.23M \
-f 160.245M -f 160.26M -f 160.275M -f 160.29M -f 160.305M -f 160.32M -f 160.335M \
-f 160.35M -f 160.365M -f 160.38M -f 160.395M -f 160.41M -f 160.425M -f 160.44M \
-f 160.455M -f 160.47M -f 160.485M -f 160.5M -f 160.515M -f 160.53M -f 160.545M \
-f 160.56M -f 160.575M -f 160.59M -f 160.605M -f 160.62M -f 160.635M -f 160.65M \
-f 160.665M -f 160.68M -f 160.695M -f 160.71M -f 160.725M -f 160.74M -f 160.755M \
-f 160.77M -f 160.785M -f 160.8M -f 160.815M -f 160.83M -f 160.845M -f 160.86M \
-f 160.875M -f 160.89M -f 160.905M -f 160.92M -f 160.935M -f 160.95M -f 160.965M \
-f 160.98M -f 160.995M -f 161.01M -f 161.025M -f 161.04M -f 161.055M -f 161.07M \
-f 161.085M -f 161.1M -f 161.115M -f 161.13M -f 161.145M -f 161.16M -f 161.175M \
-f 161.19M -f 161.205M -f 161.22M -f 161.235M -f 161.25M -f 161.265M -f 161.28M \
-f 161.295M -f 161.31M -f 161.325M -f 161.34M -f 161.355M -f 161.37M -f 161.385M \
-f 161.4M -f 161.415M -f 161.43M -f 161.445M -f 161.46M -f 161.475M -f 161.49M \
-f 161.505M -f 161.52M -f 161.535M -f 161.55M -f 161.565M | tee ~/dl/chatter.raw | \
   play -t raw -r 24k  -es -b 16 -c 1 -V1 

Though, I have no idea how practical a list of frequencies this long is.

There's something empowering about listening to behind the scenes radio traffic. It's like glimpsing a secret world while your fellow waiting-room dwellers are none the wiser. I don't ride Amtrak frequently, but next time I do I'll arrive ready to tune in.

Monday, June 11, 2018

Frogs, Family and Fun - Visiting The National Arboretum

I've been to the National Arboretum in DC a number of times: alone with Shira, with my Mother-in-Law and Ron, with our running buddy and probably other times as well. It's always a good time. But yesterday's visit with our 7 and 10 year old cousins was definitely a unique and awesome experience. (Oh, and Ryan and Andrea, you guys were fun, too!)

I got a hands on education in the in the delicate arts of frog catching, caterpillar hunting, dragonfly spotting, tree climbing and honey-suckle gathering. For my part, I got to educate the boys about Milk Weed, and found a number of Monarch Butterflies enjoying their delicacy of choice. I felt like a kid who was getting in the best possible mischief.

I can see I still have a lot to learn about enjoying the outdoors, and I look forward to these young gurus teaching me their many skills!

Friday, June 08, 2018

High School Graduation - A Truly Sweet Simcha

A huge Mazel Tov to M on her high school graduation. What a treat it was to see her leading the Anthem and Hatikva for the festivities. And the fact that said festivities had one of the most amazing dinner/dessert spreads I've ever seen didn't hurt.

E started teaching me to how to play PUBG Mobile, though at this point I'm pretty much a lost cause. I can't seem to get past the fact that the character starts off this battle wearing nothing but boxer shorts. Don't get me wrong: programming from home should definitely be pants optional, but combat? Oh well, I'm sure there's more to the story.

What a fun time and I know M's going to crush it in college! We're so proud of her.

Wednesday, June 06, 2018

Meet Gesty, The Musical Micro:Bit

One of the quintessential projects for the micro:bit is to create a musical instrument of some sort. This usually involves implementing a crude version of a theremin, often by using a light sensor or potentiometer to control the sound. I've implemented my own take on this, using the Tinkertools Electro-Theremin tutorial as helpful inspiration.

Here's my code:

let debug     = true;
let nextNote  = input.runningTime();

if(debug) {
  OLED.init(64, 128);
}

basic.forever(() => {
  let now = input.runningTime();
  let roll = 90 + input.rotation(Rotation.Roll);
  let pitch = 90 + input.rotation(Rotation.Pitch)
  let right = roll / 36;
  let down = pitch / 36;
  let freq = 131 + (roll * 5);
  let rest = 20;
  let duration = pitch * 3;
  let fudge    = pins.analogReadPin(AnalogPin.P1);

  basic.clearScreen();
  led.plot(right, down);

  if(now > nextNote) {
    nextNote = input.runningTime() + duration + rest;
    
    music.playTone(freq + (input.buttonIsPressed(Button.A) ? fudge  : 0) , 
                   duration + (input.buttonIsPressed(Button.B) ? fudge  : 0));
  }
  
  if(debug) {
    OLED.showString(roll + "/" + freq + "::" + pitch + "/" + duration + "::" + fudge);
  }
});

(You can find the full source code here.)

I'm using the left-right roll of the micro:bit to determine the note frequency, and the forward-back pitch of the micro:bit to determine note duration. By tilting the micro:bit in various directions, you can generate different musical effects.

But wait, there's more!

I've implemented a simple UI using the 5x5 LED grid on the micro:bit. As you tilt micro:bit in the various directions, the relevant LED lights up showing how far to the left/right or forward/back you are. I also added the potentiometer that came with the Tinkercademy kit to turn the A and B buttons on the micro:bit into overrides. You can dial in a value on the potentiometer then press A to modify the current frequency or B to modify the current direction. Finally, I'm using the tiny screen that comes with the Tinkercademy kit for debugging output.

I was pleased at how accurate the data from input.rotation(...) is. And it's downright amazing how simple the Tinkercademy kit is to operate. I plugged in a few piece of hardware and was making noise in no time.

To prove this all works, check out the video below. Warning: I've got no idea how to get Gesty, the Musical Micro:Bit to, actually make pleasing music. So for now, it just makes mind numbing screeches. Enjoy!

Tuesday, June 05, 2018

It's All Connected - Adventures in Blockchain Implementation

Programming Praxis followed up its hashing implementation exercise with a challenge that uses this newly crafted code. The idea is to implement a blockchain. The only thing I know about crypto currencies like Bitcoin is that they're all about blockchains. I figured I'd tackle this exercise and then use my new found knowledge to quit my day job and become a crypto currency day trader.

After a few minutes with this exercise, I realized my plans of striking it digitally rich would have to wait. Implementing blockchains would be: (a) simple to do, and (b) not put me any closer to understanding Bitcoin.

A blockchain is a cleverly crafted list of items, where each item contains a hashed value that matches the previous item. If any of the items on the list are tampered with, the hashs won't match and the blockchain will be trivially noted as compromised.

As a contrived example, consider this list of readings from the Potomac River water Gauge:

(define water-guage-levels 
  '("USGS    2018-06-04 00:00    EST    70200    P    7.78    P"
    "USGS    2018-06-04 00:15    EST    70600    P    7.80    P"
    "USGS    2018-06-04 00:30    EST    70900    P    7.81    P"
    "USGS    2018-06-04 00:45    EST    71500    P    7.84    P"
    "USGS    2018-06-04 01:00    EST    72300    P    7.88    P"
    "USGS    2018-06-04 01:15    EST    72500    P    7.89    P"
    "USGS    2018-06-04 01:30    EST    73300    P    7.93    P"
    "USGS    2018-06-04 01:45    EST    74200    P    7.97    P"
    "USGS    2018-06-04 02:00    EST    75000    P    8.01    P"
    "USGS    2018-06-04 02:15    EST    76100    P    8.06    P"
    "USGS    2018-06-04 02:30    EST    76500    P    8.08    P"
    "USGS    2018-06-04 02:45    EST    77400    P    8.12    P"
    "USGS    2018-06-04 03:00    EST    79100    P    8.20    P"
    "USGS    2018-06-04 03:15    EST    79800    P    8.23    P"
    "USGS    2018-06-04 03:30    EST    81100    P    8.29    P"
    "USGS    2018-06-04 03:45    EST    82500    P    8.35    P"))

If I published this list, and someone came along with a similar yet tweaked list, who's would you believe?

Now suppose I publish this list as a blockchain:

> (define water-guage-levels-bc
  (fold (lambda (entry bc)
          (bc-adjoin bc entry))
        (bc-init)
        water-guage-levels))

> (bc-show water-guage-levels-bc)
(16 "USGS    2018-06-04 03:45    EST    82500    P    8.35    P" "8671b82a1a8d1c9ff668c11b133c4c67" "1b3df4a71fe0c8fbd3ed6ddf9f39193a")
(15 "USGS    2018-06-04 03:30    EST    81100    P    8.29    P" "a294e5e636810aa425c1c5e1f497893" "8671b82a1a8d1c9ff668c11b133c4c67")
(14 "USGS    2018-06-04 03:15    EST    79800    P    8.23    P" "fe60902aa25d4cbe978169a3bbc8cb6" "a294e5e636810aa425c1c5e1f497893")
(13 "USGS    2018-06-04 03:00    EST    79100    P    8.20    P" "c3fcd4c796b0a923aa857d1e9e80792" "fe60902aa25d4cbe978169a3bbc8cb6")
(12 "USGS    2018-06-04 02:45    EST    77400    P    8.12    P" "45423288514717ecc5829ad011e657dd" "c3fcd4c796b0a923aa857d1e9e80792")
(11 "USGS    2018-06-04 02:30    EST    76500    P    8.08    P" "c125bd9184f97dc149363a8b05766ec" "45423288514717ecc5829ad011e657dd")
(10 "USGS    2018-06-04 02:15    EST    76100    P    8.06    P" "105fb72524937269e0a75e2d3512ab49" "c125bd9184f97dc149363a8b05766ec")
(9 "USGS    2018-06-04 02:00    EST    75000    P    8.01    P" "52ab9918616a5742262e928cf4fdc4" "105fb72524937269e0a75e2d3512ab49")
(8 "USGS    2018-06-04 01:45    EST    74200    P    7.97    P" "b67c5dd2c33111f4fc48d7312a1812f" "52ab9918616a5742262e928cf4fdc4")
(7 "USGS    2018-06-04 01:30    EST    73300    P    7.93    P" "dbca959d4ec7d930d3537d4516df4198" "b67c5dd2c33111f4fc48d7312a1812f")
(6 "USGS    2018-06-04 01:15    EST    72500    P    7.89    P" "f03e16843cca2258a127d7f495ab2d8" "dbca959d4ec7d930d3537d4516df4198")
(5 "USGS    2018-06-04 01:00    EST    72300    P    7.88    P" "4263d01116c6c42d12c3b17197ffadd5" "f03e16843cca2258a127d7f495ab2d8")
(4 "USGS    2018-06-04 00:45    EST    71500    P    7.84    P" "629f057e62534baaa0b14e6f4fd8a" "4263d01116c6c42d12c3b17197ffadd5")
(3 "USGS    2018-06-04 00:30    EST    70900    P    7.81    P" "beaf7199fac22cfc8f565120133a342d" "629f057e62534baaa0b14e6f4fd8a")
(2 "USGS    2018-06-04 00:15    EST    70600    P    7.80    P" "dcba2a9715e2e5ce4e27a19e1c69f3d" "beaf7199fac22cfc8f565120133a342d")
(1 "USGS    2018-06-04 00:00    EST    70200    P    7.78    P" "7f4e55deaa11a9a796adedebbb50b0" "dcba2a9715e2e5ce4e27a19e1c69f3d")
(0 "Genesis Block" "0" "7f4e55deaa11a9a796adedebbb50b0")

It's the same data, but now hashes link each item in the chain. If an attacker attempted to publish a hacked version (say they change 7.97 to 7.92), then the hashes would no longer match and the blockchain would be outed as an obvious fraud. Another useful property of blockchains is that I can continue to append new readings without weakening the integrity of previous entries.

As to how this powers Bitcoin, well your guess is as good as mine. Implementing a crypto-currency will have to be another exercise for another day. In the mean time, check out the source code of my solution and enjoy this video that details What Bitcoin is and How It Works.

Monday, June 04, 2018

Dabbling in Computational Magic: Pearson Hashing

Hashing is one of the closest things we have to magic in the computer world. Imagine I told you that I could turn any piece of text, from a single letter to the entire Bible, into a unique 32 character string. Impossible! you'd say: one can generate an infinite number of strings, yet 32 characters is a fixed space. And yet, this is exactly what hashing algorithms like MD5 deliver.* In short, magic.

(Not sure why this is exciting? Consider how much value we get from finger prints, which behave much like hashes. They're not perfect, but they do a great job of uniquely identifing and verifying a person. The same can be said about hashes and data.)

Programming Praxis has an excellent exercise on the topic: Pearson Hashing which is a fun way to understand how hashing works.

The paper that describes Pearson Hashing is only 4 pages long and quite readable. I highly recommend printing it out and spending some quality time with it.

Pearson Hashing isn't quite as magical as MD5: its promise isn't a unique number for any piece of text. Instead, it maps arbitrary text to a number between 0 and 255 in an essentially random fashion. It does have some useful properties: text that's similar produces completely different Person Hashing values and text that uses the same characters but in a different order also produce different values. You can imagine using Pearson Hashing as a rough check to make sure text hasn't been tampered with or other sanity checks.

The implementation of Pearson Hashing is almost trivial:

(define (pearson-hash text)
    (let loop ((hash 0)
               (chars (map char->integer (string->list text))))
      (cond ((null? chars) hash)
            (else
             (loop (vector-ref (*T*) (bitwise-xor hash (car chars)))
                   (cdr chars))))))

I say almost trivial, because the algorithm calls for the construction of a randomly permuted table of values. I found that generating this random table took far more time than the implementation of the hash algorithm. You can find my full implementation here.

To make the exercise more interesting, I branched out a bit and implemented a few variations of pearson-hashing. These include:

  • simple-hash: described in the Pearson Hashing paper as an algorithm that uses an even simpler approach to hashing.
  • pearson-hash-mod: rather than use bitwise-xor to derive the next hash value, this approach uses mod 256.
  • lr-pearson-hash: the Pearson Hashing paper describes an approach to generating larger range hash values. That is, a wider range than just 0 - 255.

I grabbed a list of the top 10,000 English words identified by Google and used it to compare each of the hashing functions above.

My comparison is weak, but still somewhat informative.

As promised, the Pearson Hashing variations always give a different hash on similar text, even if an anagram is used (the simple-hash fails this test). The pair of numbers after the hashing tests represent the max and min bucket size from hashing the 10,000 words from Google. If hashing the 10,000 words was perfectly distributed, then each of the 256 hash values would get 39 or so words associated with them. You can see that all the hashing algorithms were in that neighborhood, with Pearson Hashing being slightly better than the simple-hash approach.

My test also shows that if I use the Pearson Hashing algorithm and generate 40 bit values (that is, I run pearson-hash 5 times for each string) then each of the 10,000 words get a unique hash value. MD5 works in terms of 128 bit values, which I could approximate by running the running pearson-hashing algorithm 16 times. I've got no idea how MD5 works, but I imagine generating 128 bit Pearson Hash values would start to get me into the neighborhood of the magic hashing I initially described.

*Note: MD5 isn't as secure as the I'm making it out to be. There are known issues. There are stronger hashes currently available. I expect those will be obsolete one day, too. MD5 is good enough for our purposes.

Friday, June 01, 2018

They're Kosher and Travel Friendly - But How Do They Taste?

This summer we're traveling internationally with our niece and nephew who keep stricter Kosher than Shira and I do. To add to the complexity, my nephew is diabetic. The optimist in me thinks we'll have no problem making do with local foods. For part of the trip, we'll be near cities with Kosher Restaurants, which mean the supermarkets in these areas should have hekshered products. In my mind's eye, we'll stock up with food at these locations and will have no problems when we're off the beaten path.

But I've traveled enough to know that things rarely go as planned. As a bit of insurance, we plan to bring alone some key staples: bread, tuna packets, peanut butter, etc. While planning this out, I decided to do a bit of research about Kosher shelf stable prepackaged meals. Think airplane food meets MRE's. I found a few interesting leads on the web, but the cost and nutritional profile of the food (not to mention past experience with Airplane food) lead me to more or less drop the idea.

Then, while we were shopping for Passover at Shalom Kosher, I stumbled on an isle full of refrigeration-free boxed meals. There were a number of different brands, and within those brands, quite a few meal choices. I called Shira over to take a look. The thought of having a zero-effort meal, ready to go for the kids was nice. And as she patiently looked through each food option, she noticed a wide nutritional range. Sure, some where packed with fat and sugar, but others seemed downright healthy. Shira carefully selected a few diabetic-friendly options and we added them to our Passover purchases.

We're still a couple months off from our trip, but Shira reminded me that if we want to depend on these meals we better actually try them. So earlier this week, that's what we did. We busted out a 'Stuffed Chicken' and a 'Vegetarian Pepper Steak' and heated them up. Below, you'll find my review of these culinary wonders. We have a few more meal options to try, however, I'm fairly optimistic that we'll bringing along a few of these meals as a backup. The elegant way they solve a potentially tricky situation makes up for the cost of the meals and the pain of schlepping them (or perhaps mailing them there ahead of time?).

Stay tuned, as I try more of the meals, I'll add them below.

Meal Mart: Stuff Chicken with Rice & Mushrooms

My first thought was that this meal was high in fat and calories, but at 25g and 460 calories, if it really is what you eat for dinner it's probably reasonable. And at 7 net grams of carbs, it passes Shira's can I feed this to a Kosher Diabetic test. And check it out: 48g of protein--nice, right?

You can heat the meal up in the microwave or by dropping it into hot water.

As for the taste: it's pretty good. It's not going to win any awards, but I imagine a kid that eats chicken would eat this. As the number of carbs on the box suggest, there isn't a whole lot of Rice & Mushrooms in this dish. Again, if you have a kid who likes chicken, but is not into rice or mushrooms, this dish would probably work.

Would I travel with it? Yes.

La Briute: Vegetarian Pepper Steak

This meal appears to be a nutritional wonder: 280 calories, a measly 1.5g of fat, 13 net grams of carbs and a whopping 50g of protein. It also comes with a self heating mechanism. We followed the provided instructions, and sure enough, the food was piping hot. I realize this is standard protocol for military MREs, but having never seen a self heating meal at work, I've got to say I'm impressed.

So the meal looks great on paper and heats itself up. Where's the catch? You know exactly where the catch is: taste. I peeled back the film on the top of the meal and was greeted by an unusual odor. It didn't help that the food looked like one congealed block. I mixed up the contents a bit and took a bite. The best I can say is that it was edible. The vegetarian pepper steak didn't really taste bad, but it also didn't exactly taste like food. Shira couldn't even stomach a bite. I ended up eating the meal, but I wouldn't want to depend on a kid being so indulgent.

Would I travel with it? No. If the meal has the nutritional profile you're after, go for it. But there other La Briute options that are self heating and hopefully taste better.

Wednesday, May 30, 2018

Taming the Chaos - A Custom Bag Organizer Solution

I've got a day trip coming up that has the unusual distinction of requiring me to bring a laptop (usually I get by with my folding Bluetooth keyboard and cell phone). This means that I'll face a common bag conundrum: bring one bag, mainly my briefcase, and have all the items from my man-bag haphazardly tossed in. Or bring two bags, my briefcase and man-bag, and worry about keeping track of two bags.

With my burgeoning sewing skills, it occurred to me that I may be able to add structure to my briefcase by creating an insert. This would give me the organization I'm after, and allow me to keep track of a single bag. Because the insert I'm sewing would be customized to the shape of the bag, it would fit my gear efficiently. (Yes, it occurred me that I was effectively re-creating a purse organizer. And no, I'm not bothered by this at all.)

I busted out my briefcase, took some measurements and sewed together a large nylon pocket:

I typically carry four categories of gear: everyday (hand sanitizer, snacks, etc.), first-aid (pills and various forms of tape), electronics (keyboard, power bank, etc.) and hiking (Bic lighter, space blanket, etc.). My plan was to section off the large pocket into 4 areas. However, it just wasn't wide enough for this, so I had to settle on sewing it into thirds.

To my surprise and delight, the gear all fit. And what's more, when packed, the insert fit neatly into the bag:

As I finished sewing the initial pouch it hit me that I should have made the bag 1" larger. And I bet with some effort, I could find a way to make a 4 pouch system.

But still, I think the result will be quite functional and I'm psyched to try it out on my next trip.

Assuming this approach works, I plan to create a version that's customized to fit in the large day-pack I use for traveling. You better believe I'm going to measure that one with more care.

One inch I'm telling you...I was off by one friggin inch. Classic.

Tuesday, May 29, 2018

Adventures in Aerial Therapy - Sandy Spring Adventure Park

Given my fear of heights, the last place you'd expect to find me is over at the Sandy Spring Adventure Park. But it was this fear, and my hope to tame at least a small part of it, that was our motivation for hitting the park. Sandy Spring consists of 13 'trails' that take you to various heights in the trees. A trail is made up of zip-lines and obstacles, all connected by a series of platforms. Basically it's a playground suspended up to 65 feet in the air. Through an ingenious system, you clip into a safety cable at the start of the trail and you are guaranteed to remain attached until you step safely on the ground.

We picked Sandy Spring because it promised so many skill options. I figured I could master the course designed for 5 year olds (yes, 5 year olds!) and then move on from there. We brought along Fearless M and Dauntless P, who I knew would gladly embrace the more challenging courses. There would be something for everyone.

The safety briefing and test obstacles went by quickly and before I knew it, I was standing with Shira on the start platform. From this platform, all 13 trails begin, some with impossible looking ladder climbs, others with fairly tame looking bridges. I felt surprisingly calm, a fact I owe to the promise that I'd be able to tackle a Purple level course. You know, the one designed for 5 year olds.

There are two purple courses, and one of the staff members suggested we go on Firefly: it had no other guests on it at the moment. So we walked over to the Firefly start point and I was hit with my first test. The first obstacle in Firefly was a zip-line. Whoa. Shira clipped in, got settled and zipped across to the following platform. And just like that, it was my turn. I tried desperately not to think. Sure, I could have spent time psyching myself up (you're totally safe! kids are doing this! you literately can't get hurt!), but that would have given the voice in the back of my head time to chime in with reasons why I shouldn't do this. So as mindlessly as possible I clipped in and pushed off from the start platform.

Whooooo! I was flying!

As I zipped across the tree tops (at 15 feet in the air, mind you), the harness hugged me securely and I felt surprisingly comfortable. I was expecting to be hit by a wave of fear and panic; it never happened. I simply zipped and it was simply fun. I have to say, it was awesome.

Feeling good, Shira and I then turned our attention to the next obstacle: a sort of wobbly bridge thing. I had no problem getting across it. And so it went: Shira would knock off an obstacle, promise me that it wasn't too hard and then it would be my turn. Our big finish was another zip-line to the ground. I finished the course and felt like I'd accomplished something huge.

After checking in on P and M, and of course finding them in the tree tops and quite happy, we made our way back to the start platform. We completed the second purple course without issue and then made our way to a Yellow course. While Yellow is the next notch up in terms of difficulty, it's still considered easy and it's still at 15 feet.

The first couple of obstacles in Yellow were no problem. But by the third obstacle, I found that I was truly working to stay upright. While I expected the zip-lines to be the tricky part, they turned out to be easy. The various bridge like structures, even on Yellow, were no joke. Given the safety harness system, you're never falling more than a few feet. But still, I was truly humbled by how much effort I needed to put into the Yellow course.

After our 3rd course it was time to call it a day. We pulled the kids off of the Black Diamond course, which if given enough time, they'd almost certainly have completed (as well as done the next level). They were truly fearless and exactly the type of person this park was designed for.

Overall, our experience at Sandy Spring was great. The staff was warm and helpful, and the course options were as plentiful as promised. Two notes of caution, however.

First, if you buy tickets on the website it talks about buying gloves. What we didn't see on the site, however, was that this is an optional purchase. If you don't buy gloves, then there are free ones to rent (unlike say Skyzone, where you have to buy the special socks). We had purchased the gloves, but asked for a refund in the store. They were glad to give us one.

Second, be aware that even the easiest course is far from non-trivial. Some kids are fearless; while others are far less so. Convincing a kid to take that first leap into the unknown may be asking too much. This may be an adventure best tackled when kids are older. If you do find yourself at the park with a kid who's not ready to make the leap, then perhaps a few more times on the safety-practice-course would give them the confidence to try this.

I do wish the course was a bit more affordable, especially for someone like myself who's not really taking full advantage of their offerings. But from poking around the park's website I see that they are considered "the largest aerial climbing course in the US." This is a top tier location, so it makes sense that they would charge top dollar.