Friday, June 28, 2019

Discovering the Goldmine that is the M5Stack Arduino Environment

When I last played with my M5Stack Fire device I had figured out a development environment and had my code for my first trivial app written. The app, if you can even call it that, accepted button input and made web requests to a particular URL with the button label appended. I was able to get both button detection and URL invocation working. Alas, when I combined these operations into one program the result was a system crash. Such a pain.

Out of frustration I put the device down planning to return it with fresh eyes.

This week I picked up the device and thought I'd try another approach to programming it. I'd had a recollection that the Arduino IDE had M5Stack support. A quick Google search turned up a YouTube video that promised I'd have this environment setup in less than 5 minutes.

Impressively, the video wasn't far off from its 5 minute promise. There were a bunch of steps, but everything fell into place and before I knew it, I'd configured the Arduino dev environment to work with my Fire.

But the part that blew me away was all the example code that's available:

For a good 20 minutes I was a kid in a candy store: opening up demos, running them, and ooh-and-ahhing as my M5 came to life. Bluetooth, WiFi, microphone support, LED support--there's code to demonstrate it all. If I can see the individual examples running, I should be able to mix and match the pieces into interesting projects. So. Cool.

Returning to my original challenge, I used the Display, Button and BasicHttpClient examples to work up my little button triggered URL app.

I ran into one gotcha during the process. At some point, attempts to flash new code to the device resulted in the cryptic message:

Arduino: 1.8.9 (Mac OS X), Board: "M5Stack-FIRE, Enabled, Default (2 x 6.5 MB app, 3.6 MB SPIFFS), 115200, None"

Sketch uses 344988 bytes (5%) of program storage space. Maximum is 6553600 bytes.
Global variables use 16604 bytes (0%) of dynamic memory, leaving 4505380 bytes for local variables. Maximum is 4521984 bytes.
esptool.py v2.6
Serial port /dev/cu.SLAB_USBtoUART
Connecting........_____....._
Chip is ESP32D0WDQ6 (revision 1)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
MAC: 84:0d:8e:25:aa:34
Uploading stub...
Running stub...
Stub running...
Configuring flash size...
Warning: Could not auto-detect Flash size (FlashID=0xffffff, SizeID=0xff), defaulting to 4MB
Compressed 8192 bytes to 47...

A fatal error occurred: Timed out waiting for packet content
A fatal error occurred: Timed out waiting for packet content

This report would have more information with
"Show verbose output during compilation"
option enabled in File -> Preferences.

I eventually arrived at a simple fix: hold down the power button on the device while Arduino is compiling and flashing the code. Easy peasy.

You might be wondering, what good is having a small device where a button press triggers a URL call? Thanks to Tasker and AutoRemote, it's possible to make your phone take essentially any action in response to a web request to a special public URL. For example, I setup a trivial Tasker profile that plays an ear splitting sound when button B is pressed on the M5. This is useful when I've put my phone down in my house and lost track of it; something that seems to happen constantly.

While I'm excited that I've got my first baby M5Stack project completed I'm even more excited to have this mountain of demo code to play with. Now the real fun can begin!

Here's the code to power the behavior described above, as well as some action shots. Enjoy!

/*
 * Pressy!
 */
#include <M5Stack.h>
#include <WiFi.h>
#include <WiFiMulti.h>
#include <HTTPClient.h>

WiFiMulti WiFiMulti;

#define AUTOREMOTE_BASE String("https://autoremotejoaomgcd.appspot.com/sendmessage")
#define AUTOREMOTE_KEY String("--auto remote key goes here--")
#define AUTOREMOTE_PREFIX String("Pressy_")

void clearScreen() {
  M5.Lcd.clear(BLACK);
  M5.Lcd.setCursor(0,0);
  M5.Lcd.setTextColor(YELLOW);
  M5.Lcd.println("Ready: ");
  M5.Lcd.setTextColor(WHITE);
}

void autoRemote(String btn) {
  String url = AUTOREMOTE_BASE + "?key=" + AUTOREMOTE_KEY + "&message=" + AUTOREMOTE_PREFIX + btn;
  HTTPClient http;
  M5.Lcd.print(btn + "...");
  http.begin(url);
  M5.Lcd.println(http.GET());
  http.end();
}

void setup() {
  // init lcd, serial, but don't init sd card
  M5.begin(true, false, true);
  M5.Lcd.clear(BLACK);
  M5.Lcd.setTextColor(YELLOW);
  M5.Lcd.setTextSize(2);
  M5.Lcd.setCursor(0, 0);
  M5.Lcd.println("Pressy");
  M5.Lcd.print("Setting up WiFi:");

  WiFiMulti.addAP("SSID", "PASSWORD");

  M5.Lcd.print(".");
  while(WiFiMulti.run() != WL_CONNECTED) {
    M5.Lcd.print(".");
    delay(250);
  }

  clearScreen();
}

// Add the main program code into the continuous loop() function
void loop() {
  M5.update();

  // if you want to use Releasefor("was released for"), use .wasReleasefor(int time) below
  if (M5.BtnA.wasReleased()) {
    autoRemote("A");
  } else if (M5.BtnB.wasReleased()) {
    autoRemote("B");
  } else if (M5.BtnC.wasReleased()) {
    autoRemote("C");
  } else if (M5.BtnB.wasReleasefor(700)) {
    clearScreen();
  }
}

Tuesday, June 25, 2019

Review: Crossed by Ally Condie

Crossed by Ally Condie was, for me, a dud. It had to have some redeeming qualities as I continued to listen to it (more on that below), but overall it just wasn't a fit.

I started reading Crossed randomly without any clue to its content. I quickly realized that it was in the Distopian-Young-Adult genre, à la Hunger Games. The problem is, the Hunger Games series ended up being so awful that anything I read that's remotely similar triggers a bad reaction. This, of course, is totally unfair to Crossed. For all I know Crossed could have been written well before Hunger Games (checks Amazon: nope, Crossed was published years after Hunger Games).

The other strike against Crossed is the whole young-adult aspect. I get it; you're a teenager and you're wrestling with your feelings. And you've got a crush on someone, and you so want to hold their hand and get that first kiss. Yes, it's cute. But apparently, I'm so over it.

Perhaps I need to track down an adventure story about a couple who's been married for decades. They've had crushes on each other, and their share of blow-out-fights. There's no question of will-they or won't-they end up together, because the are together. It isn't cutesy, but it's effective. By now, they're an unstoppable team that can read each other's minds and anticipate each other's moves. What they lack in fawning over each other, they make up for in support, love and respect. I'd so read that.

The final strike against Crossed was that it's book #2 in a trilogy, so by its very nature it has to be a transitional story. In other words, for all the action nothing really happens. Clearly reading book #2 first was a mistake, and that's what I get for going in cold. I suppose if the story had been riveting, I would be inspired to read the the other two. But alas, it was just OK, so I'll pass on the series.

Kvetching aside, Crossed isn't without its redeeming qualities. The characters are pretty likable, even if they are a bit insufferable. And the story didn't descend into the pointless abyss that the Hunger Games did. I also enjoyed using the clues provided to figure out the rules of the distopian society, an exercises needed because I skipped the first book.

While I don't totally get why, I thought it was clever to emphasize the beauty and power of poetry. I for one found myself Googling various poems, inspired by the book. If Crossed manages to get teens to read and think about poetry then I'll gladly take back any and all of my complaints.

I also liked the suggestions that the distopian society evolved not out of pure evil, but out of the intention to do good. Again, I didn't read the first book, so my interpretation of this may be wrong. It seems that in an attempt to cure cancer and most likely other woes, humans have backed themselves into a corner. Ah yes, the power of unintended consequences. Like the emphasis on poetry, this is another great topic for teens to encounter and explore.

For the right (and intended) audience, Crossed and the other books in the series are almost certainly a winner. Kids should dig the love story, and parents should appreciate that their kids are being exposed to important themes. It's a win-win. Just not for me.

Monday, June 24, 2019

mitmproxy: X-Ray Vision for Web Developers

Last week I found myself needing to debug a PHP upload error. The problem: PHP was reporting a UPLOAD_ERR_PARTIAL without giving specifics. For starters, I was curious what data was being uploaded. This turned out to be trickier to discover than I thought.

First, I tried using the file_get_contents('php://input') trick to grab the bytes being uploaded. This doesn't work, because as the manual reports, php://input is not available with enctype="multipart/form-data". In other words: you're out luck if you're dealing with file uploads.

Undeterred, I decided to move up the stack a bit. If PHP wasn't going to reveal the raw data, then I'd simply ask Apache to log this information for me. This lead me to try mod_dumpio, mod_dumpost and mod_security. mod_security was the closest to a viable solution, as it didn't just log the data but also gave me the option of extracting and storing the data being uploaded. The problem with mod_security is its complexity; it does so much. I found this post pointed me in the right direction to getting mod_security at least partially configured. I still wasn't happy with this solution.

I decided to take a step even further back: could I run some sort of proxy server that could sit between my browser and Apache?

I can, and I did! I give you the well named: Man-in-the-Middle Proxy, or mitmproxy for short.

I was skeptical about how much work setting up mitmproxy would be. I cheated and grabbed a binary download for linux. I then made a single tweak to apache: instead of listening on port 80, I changed it to listen on port 8080.

# Listen 80
Listen 8080

I restarted apache and then kicked off mitmproxy from the command line like so:

$ sudo ./mitmproxy -p 80 \
        --set keep_host_header=true \
        --mode reverse:http://localhost:8080

I pointed my web browser to the usual location and started accessing my web app. To my surprise, everything Just Worked. Flipping back to the mitmproxy screen, I saw my browser requests streaming in. At this point I started to appreciate the interactive side of mitmproxy. I realized I could choose to inspect individual requests on the fly, including those with multi-part file uploads. Finally, I could easily inspect these mysterious failed PHP uploads.

I've used Proxy Servers in the past to aid with debugging, but mitmproxy takes this to a new level. It's definitely going into my bag of tricks for future debugging sessions.

Friday, June 21, 2019

Gift-of-a-Playlist, Now Even More Gift'ier

It bothered me that Gift-of-a-Playlist started off with all of the YouTube videos visible. I want to give recipients a chance to read what I've written, then view the videos.

So I added a big 'ol Reveal button on top of each clip:

With that feature implemented, I can officially create my brother's birthday gift and send it off to him; only 1 month late. Not bad!

You can try this little app for yourself by visiting: http://code.benjisimon.com/gift-of-a-playlist/ and plugging in the sample playlist document: https://docs.google.com/spreadsheets/d/1eW7-qBHZq1LmBxKALUAAaRW45jABg0a5Ju7hy42Kbn4/edit#gid=0.

I'm telling you, gifting playlists should so be a thing. And now they are.

Thursday, June 20, 2019

A Dopp Kit Dump

This past weekend I was organizing a shelf full of travel stuff and realized my dopp kit was missing. Did I leave it at a hotel? Or maybe I just put it back in a non-standard location? Regardless, I was bummed. Not because the kit contained anything of significant value. No, the problem was that after years of travel I'd finally tuned it to be Just Right. It was split into two sections: a compact set of toiletries and a collection of handy utilities. I didn't relish the idea of rebuilding it.

Fortunately, I found it. But before I put it away I dumped the contents and took a few pictures. If I do lose my kit, I'll now have a much easier time recreating it.

So here it is, my finely tuned travel toiletry setup:

The razor is an old Gillette Sensor Excel with off brand blades. It's a compact and inexpensive setup; perfect for travel. The little plastic bags contain soap and deodorant respectively.

The deodorant solution took me years to arrive at. I found the easy-to-pack deodorant crystal rock to be ineffective. And Shira wasn't having my, 'but I'm traveling I don't need to wear deodorant' philosophy. So now I buy a travel size deodorant, remove the tray of antiperspirant material and discard the bulky plastic shell. The result takes up far less space, yet is just as effective as any other standard deodorant.

The blue container has a blob of Bed Head Men Matte Separation Workable Wax in it. I find that stuff beefy enough to keep my hair relatively in check.

The utility side is more interesting. I keep quite a few over the counter meds in there, including allergy, cold and antidiarrheal pills. When you need any of these pills, they're a life saver.

I intentionally keep caffeinated and decaffeinated tea bags on hand, knowing the caffeinated options can be used to help keep me awake.

I carry the thumb-sized USB light for the same reason I carry a Bic lighter: it has an impressive size to functionality ratio. One common use of the light: plug it into a USB charger in the bathroom and use it as a nightlight.

Do you carry any unusual items in your dopp kit? If so, do share in the comments below.

Wednesday, June 19, 2019

More Thought, Less Cost - Gift-of-a-Playlist

I have a vague recollection of a post from years ago where I suggested what a meaningful gift a YouTube playlist could be. I can't find it now, but the concept has stuck with me. Compiling music, How-To-Videos, movie clips, and other YouTube discoveries for a friend or loved one seems like a fun, frugal and thoughtful way to show you care without buying more stuff. Think of it as a YouTube powered mixtape.

With my oldest Brother's birthday coming up, I figured I'd finally try my idea out. I visited YouTube and started collecting up clips. Finding content was easy, the problem I ran into was there was no way to annotate my playlist. Just sending my brother a list of videos wasn't going to cut it. Each video needed a little explanation to provide context. I couldn't find a way to do this on YouTube so I whipped up my own solution.

I give you: Gift-of-a-Playlist.

Step 1: create and publish a Google Sheet with two columns: Video and Message. See this example.

Step 2: visit Gift-of-a-Playlist and enter the URL of your spreadsheet:

Step 3: choose the tab within the Google Spreadsheet:

Step 4: share the URL of the generated playlist. Here's the above example in playlist form.

You can find the source code for this little app here.

One neat feature is that the playlist you send remains connected to the spreadsheet. If you send the link and find a typo or think of another clip to add, you can update the spreadsheet and the link will reflect those changes.

Before I send my brother his now belated birthday gift, I've got one more feature to implement. I'd like to start off with the videos hidden and have a 'Reveal' button that shows them. Think of it as giving the person a chance to unwrap your virtual gift.

Happy gifting!

Monday, June 17, 2019

Name that Plant: Two-Toned Purple Leaves Edition

While on a walk, I couldn't help but notice this out-of-place plant:

With the triangularly shaped two-tone purple leaves it wasn't hard to identify. This guy is almost certainly an Oxalis triangularis. While exotic to me, this is a common ornamental plant. It supposedly does well both in your garden and as a house plant. They have a reputation for being low maintenance.

The plant is edible, though not in large quantities. It's also a bit of a movie star, making for impressive time-lapses due to its photonastic behavior.

He's a fun one!

Here's a couple more pictures from our walk, which took us along Holmes Run for a mile or so.

Wednesday, June 12, 2019

I've Got a Guy For That: Painless Running Shoe Shopping

Part of my plantar fasciitis recovery plan was to get new running shoes, an activity which is usually fraught with frustration. From finding the right size, to avoiding the latest gimmick, buying shoes has always been a crap shoot. Worst of all, I thought I had found a winner with the Merrell Trail Glove 4 . They were lightweight, comfortable, and promised all the rewards of a minimalist shoe.

This all held true, until it didn't. It's like a friend had turned on me: suddenly running in my Trail Gloves left me with debilitating pain in my left foot. I tried running with them using orthotics, no dice. My body was loudly and clearly telling me it was time to get new shoes.

I had one glimmer of hope during this upcoming shoe buying experience. I've got a friend, John, who works at Metro Run & Walk in Springfield. For years he's been encouraging me to come out and get fitted for running shoes. Now, with pain and no clue what shoes I should buy, I finally made the effort to visit him.

The shopping experience was almost surreal. John asked me some questions about my running, which as far as I can tell, solicited only vague answers. He measured my foot and inspected my orthotics. He then returned from the back room with two pairs of shoes. I slipped each on in turn and they both felt awesome. I ended up selecting the first pair, paid and walked out with my brand new kicks.

There was no stream of endless shoe styles to consider; no mental battle as to whether I was wearing the right size. It was just: put shoes on, feel good, move on.

So far I've logged about 20 miles of activity in the shoes. My right foot, the one without the plantar fasciitis is in heaven. My left foot has been achy during my runs, but this is orders of magnitude less pain than I was getting with my Trail Gloves. There's no doubt I've traded the lightweight sports-car feel of the Merrells for a bulky Cadillac ride in these new shoes. But with my current injury, the luxury feel is just what I need.

I was happy to leave all of this alone: I had shoes that fit and were comfortable. But Shira was doing some online shopping and managed to come across a review for the Brooks Ghost 11 (size 12, 110288 1D 006), the shoes I'd bought. Reading the review, I found the shoes were decidedly on target for my needs:

I came across this shoe when I ran into a sore Achilles tendon. Usually I run in zero drop shoes but sometimes running with this style of shoe can put a lot of stress on the Achilles tendon. While making the Achilles tendon and calf muscles stronger can be a good thing getting Achilles tendonitis can sideline any runner. I still love running in my Altra running shoes and they’re my shoe of choice for race day but I now rotate in the Brooks ghost 11 when I feel like my Achilles tendons are getting tight. Just like a good bowler uses a few balls a good runner will have a few different shoes when they need them.

Wait, your zero drop shoes stopped being comfortable and you needed a more comfortable option? Me too!

When it comes to performance I wouldn’t give the Brooks ghost 11 high marks. It is a big bulky shoe that isn’t built for speed. The shoe is great for an easy run or recovery run but when I try to go fast it’s just plain hard in this shoe. If you’re a speedster I would skip the Brooks ghost 11 and get something lighter without as much bulk on the bottom of the shoe. If you’re focused on endurance only and have ankle issues the Brooks ghost 11 is a great choice.

I'll gladly trade speed for endurance and comfort. Any run I finish injury free is a win. This reviewer suggests there's value in using both a minimalist shoe (my Trail Gloves) and the Brooks ghost 11 in tandem. That's a novel concept, and perhaps when my PF fully clears up, I'll give that a try. Until then, I'm going to relish my new uber comfy shoes.

And if I did need a shoe built for speed, I'd have a solution there too: head back to Metro Run & Walk and ask John to work his magic again.

Tuesday, June 11, 2019

A Review of Biblical Proportions: I Am Mother

This post is basically one big spoiler - so don't bother reading it till you've watched I Am Mother.

Perhaps it's all the shul I've attended thanks to Shavuot, but I can't help but see I Am Mother as a sort of uber-modern take on the story of Noah's Ark and biblical creation in general. This may all be in my head, but the links seem too obvious to ignore.

Consider these brief comments on the story of Noah:

In Parshat Noah, however, there is a moral imperative. The world is flooded not because God arbitrarily decides to destroy the world, but because it had become corrupt and destructive. Noah is not arbitrarily saved. He is deserving. He is a “righteous man, perfect in his generation. With God, Noah walked”

In a movie like I Am Mother, one expects the robot uprising to be the result of AI outgrowing their human creators. But that's only partially the case here. In this case, robots don't want to destroy us, they want to 'help' us. It's like they've taken a page out of Bereshit: to fix the world, one must wipe out humanity and start anew with a moral core of society.

I can't help but see other biblical connections: for one, there's massive container ship that couldn't look more ark-like if the movie makers tried. There's Daughter's folded paper animals which mirror Noah's ship-mates. Noah uses a dove to confirm that the Earth is habitable, the movie uses a rat. Genesis arranges for a number of tests to confirm Abraham's moral fortitude, Mother has Daughter take a series of tests as well, the failing of which we learn would mean her destruction and a restart of the experiment. Mother explaining that it's a universal consciousness that isn't confined to one body but is everywhere sounds an awful lot like a description of G-d. By Daughter destroying the robot form of Mother, we see a severing of the direct link between man and G-d, leaving only an etherial one. The robot's planting of crops seems to mirror G-d's construction of the Garden of Eden.

With these connections, I see a story that's more nuanced than your typical post-apocalyptic humans-fight-for-their-survival flick. I see a story that wants to test our ideas of what it means to be moral, and what it means to put the good of the world above the good of the person. The story of Noah and religion in general should be a challenging thing, and I Am Mother brings to light why that is. And if one goal of the movie was to take these settled ideas and make us wrestle with them again, then it has succeeded.

Update: Here's another biblical connection, this one provided by the IMDB movie FAQ:

None of the characters we meet are ever given a name, and the named characters (whom we never meet) all have well-known biblical names: Jacob, Rachel, and Simon.

Wednesday, June 05, 2019

Fishing Holmes Run

Every time I bike, hike or run Holmes Run Trail I have the same thought: can I fish this run? In some sections it's quite shallow, but other sections are inviting. There's a note at the Columbia Pike trail head that talks about how the waterway is under a delayed trout harvest, so that tells me that they stock the stream with trout; a promising sign.

Last night, I decided to forego my run and find out first hand what fishing Holmes Run is like.

I spent a couple of hours fishing the first 1,000 yards of the trail; from the trail head off Columbia Pike to the first water crossing. Getting down to the water was simple, as there was a bushwhacked trail to follow. From there, I made my way first up the bank to the bridge, and then down the bank to the water crossing. My first thought when getting to the edge of the water was that the level was too low; I thought for sure my little adventure was over before it had started.

I was immediately put at ease when my first cast landed me a tiny sunfish (or was it a bluegill?). He snatched up my trout magnet lure like it was a gourmet meal and he hadn't eaten in days. As I made my way along the water, I found a number of slightly deeper pockets of water and pulled in more pan fish. All told, I easily caught more than half a dozen, all on trout magnets.

But it wasn't just that I caught fish that made this adventure so notable. I'd driven less than 20 minutes from my house and found myself surrounded by the sites and sounds of nature. While off-trail fishing, I saw more herons than people (herons: 2, people: 1). It was wonderfully relaxing, and actually catching fish was a nice bonus.

The trail isn't perfect: I didn't have any luck pulling out more noble trout and you can hear road noise at various points along the water. Holmes Run has a reputation for smelling like sewage, which at moments I could detect hints of. But my gosh, I've stood along the Potomac a number of times on a weekday trying to catch fish, only to watch time expire before I had to head home. And the sunfish I caught were truly beautiful creatures.

I know folks fish the Potomac to haul in massive catfish and the like, and if that's your jam go for it. But if you're looking for a close-to-DC ultralight-friendly fishing experience, definitely give Holmes Run a try. I know I'm curious to go back again and see if my experience is repeatable, or just beginner's luck.

Tuesday, June 04, 2019

One Click Spreadsheet Archiving | Google App Script For The Win

I was using Google Sheets for planning, and noted that I was making copies of the various tabs to serve as an impromptu archiving mechanism. But why manually duplicate tabs when you can write code to properly snapshot the data? Below is a the Google App Script to do just this.

function archiveSheet() {
  var pad = function(x) {
    return x < 10 ? ("0"+x) : x;
  }
  
  var archiveDirId = "1GCaGcqsP1FzWe_ZAXJ4N6GHW1vbP0tBk";
  
  var now     = new Date();
  var sourceDoc = SpreadsheetApp.getActiveSpreadsheet();
  var sourceSheet = SpreadsheetApp.getActiveSheet();
  
  var workingSheet = sourceSheet.copyTo(sourceDoc);
  workingSheet.setName(sourceSheet.getName() + " To Archive");
  var data = workingSheet.getDataRange();
  data.copyTo(workingSheet.getDataRange(), {contentsOnly: true});
  
  var archiveName =  sourceDoc.getName() + " - " + sourceSheet.getName() + " - " + 
    now.getFullYear() + pad(now.getMonth()+1) + pad(now.getDate());
  
  var archiveDoc = SpreadsheetApp.create(archiveName);
  var archiveSheet = workingSheet.copyTo(archiveDoc);
  archiveSheet.setName(sourceSheet.getName());
  
  var emptySheet = archiveDoc.getSheetByName("Sheet1");
  archiveDoc.deleteSheet(emptySheet);
  
  sourceDoc.deleteSheet(workingSheet);
  
  
  var archiveFile = DriveApp.getFileById(archiveDoc.getId());
  var archiveFolder = DriveApp.getFolderById(archiveDirId);
  archiveFolder.addFile(archiveFile);
  DriveApp.getRootFolder().removeFile(archiveFile);
  
}

function onOpen() {
  var ui = SpreadsheetApp.getUi();
  ui.createMenu("Shortcuts")
      .addItem('Archive current sheet', 'archiveSheet')
      .addToUi();
}

The above code grabs the active sheet and makes a copy of it. It then copies the data back onto itself with contentsOnly set to true. This insures that there won't be broken formula references when the archive is created. The code then creates a new document, removes the unneeded Sheet1 from it, and copies the prepared data into it. The code also moves the archived file from Google Drive's root to a folder of your choice.

Here's a few screenshots and a link to a sample document.

If you make use of Installable Triggers, you can set this archiving function to happen automatically at a given day or time.

It's impressive how easy it was to implement this functionality. App Script really delivered.

Monday, June 03, 2019

Review: Sailing Alone Around The World

Browsing YouTube you might think Adventurer / Storyteller was a millennial invented job. But I can tell with authoritatively this isn't so. I just finished listening to a book that would fit perfectly among its polished YouTube and Instagram peers. That story is Sailing Alone Around The World by Joshua Slocum. The only reason it isn't featured highly on social media is that it was written in 1898!

That was the year that Captain Slocum finished the first known solo-around-the-world sea voyage. Even with my little knowledge of sailing I can appreciate how epic this feat was. This is the second sailing related book I've read in a row, and while my last book was modern, both voyages faced remarkably similar challenges. Storms, pirates, repairs, improvisations, loneliness, navigational hurdles, and even the dangers of coral reefs. And yet in Slocum's case, the challenges are complicated by both lack of technology and crew.

How does one keep a ship on course and yet sleep? How does one wake in the middle of the ocean and be confident of their position without GPS or at least an accurate chronometer? When storms overpower the vessel, how does one manipulate the sails solo? How does one protect oneself from hostile natives and pirates?

Overcoming any of these challenges would be impressive; Slocum navigated them all. What he lacked in technology and hands he made up for with skill and luck.

Listening to Slocum's journey was a pleasure. Like an episode out of HGTV, Slocum's adventure starts by restoring a washed up vessel by hand. We're then taken on a 3+ year cruise that crisscrosses the globe.

During this journey we experience gales and illness, natives and pirates, successes and failures. It's a great read listen. If I could offer one criticism though, it would be that Slocum's tone is just a bit too up beat. Yes he faced challenges, but he can't help but brush them aside. Like I said, he'd fit in with the Instagram crowd. I want to hear more details of the hard won lessons.

Of course it's his story, and his acts of courage and fortitude. If he wants to throw in a bit of humble bragging about his navigational skills, he's more than earned it. #thisboatsailsitself #2700mileswithoutsteering. And besides, I probably sound the same way when I recount trips I've taken. (Sure we missed the bus and spent 8 hours in a run down bus station, but I did get to try a new flavor of soda from the vending machine! It tasted terrible. But it was new! How insufferable. How me.)

The text for Slocum's book is out of copyright, so you can read it for free on gutenberg.org. There's also an impressive set of narrated Google Earth videos available on YouTube.

After listening to Slocum's account, I found this article detailing the search for details about Slocum's on-board clock. At the time, a sailor's clock would have been an essential navigational tool, and it's hard not to pause at Slocum's flippant selection. Think a modern day explorer opting to leave his GPS at home and instead bring along a novelty compass. I think the author has it right when he suggests Slocum's intention of bringing an inferior clock was to make a statement:

Modern technology was turning Slocum’s world around and turning him into a living anachronism. While he might have to give way to the new age, he was not going to concede without a statement. Slocum’s statement was his amazing voyage and the equipment he chose to take with him. He didn’t need an iron steamer, a polished crew or a fine timepiece to do what had never been done before.

There were many notable moments from Slocum's journey, but one that stayed with me was this exchange he had with a passing ship:

In the log for July 18 there is this entry: "Fine weather, wind south-southwest. Porpoises gamboling all about. The S.S. Olympia passed at 11:30 A.M., long. W. 34 degrees 50'."

"It lacks now three minutes of the half-hour," shouted the captain, as he gave me the longitude and the time. I admired the businesslike air of the Olympia; but I have the feeling still that the captain was just a little too precise in his reckoning. That may be all well enough, however, where there is plenty of sea-room. But over-confidence, I believe, was the cause of the disaster to the liner Atlantic, and many more like her. The captain knew too well where he was.

I love that: beware the trap of knowing your location too well. There's a life lesson in there, though I'm not exactly sure what it is.

Slocum was clearly an interesting fellow, and perhaps he was waging an unwinnable war against innovation. But there's no denying that he's an adventurer and story tell of the first order; a true model for all fellow Adventure / Storytellers to learn from and emulate.