Tuesday, December 29, 2020

Making emacs happier on a Chromebook: An ssh and console friendly browse-url-browser-function

I recently learned of, and started playing with, emacs' oauth2 support. This library simplifies access to a slew of APIs and has single handedly added a number of mini-projects to my TODO list. I first experimented with oauth2 on my Mac. Evaluating this code opened up a browser window and started me down the OAuth permission process:

(defvar bs-blogger-token
  (oauth2-auth-and-store
   "https://accounts.google.com/o/oauth2/v2/auth"
   "https://oauth2.googleapis.com/token"
   "https://www.googleapis.com/auth/blogger"
   "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
   "YYYYYYYYYYYYYYYYYYYYYYYYYYYY")
  "The token to access blogger with.")

I copied the access code from my browser back into the emacs prompt. With the token defined, I was able to make authenticated API requests to Blogger. It was magic.

A few days later, eager to make improvements to my code, I again evaluated the above snippet. This time I was ssh'd into an AWS EC2 instance running a terminal version of emacs and was greeted with with the error: No usable browser found. Gah!

I searched Google and haphazardly poked around the browse-url library. It didn't take long before I learned that I could make the message go away by telling browse-url to use a native emacs web browser such as eww. For example:

(setq browse-url-browser-function 'eww-browse-url)
(browse-url "https://blogbyben.com")

While this is nifty, it's not very useful. When I invoke oauth2-auth-and-store the system attempts to open a convoluted URL that doesn't render right in eww. So while I fixed the "No usable browser found" error, I was no closer to using oauth2 on a console version of emacs.

What if I sidestepped the browser issue? What if I convinced browse-url to store the URL in a file or somewhere else out of the way. I could then grab the URL from the file, copy into my browser and be off and running. Turns out, this is surprisingly easy to do. And I don't need to store the URL in a file, I can store the URL in a temporary and easily accessible location: the kill ring. Here's the code to do this:

(defun kill-url-browse-url-function (url &rest ignore)
  (kill-new url)
  (message "Killed: %s" url))

(unless window-system
  (setq  browse-url-browser-function 'kill-url-browse-url-function))

On Windows or Mac, browse-url seamlessly opens URLs like it always has. In a console version of emacs, browse-url captures the URL in the kill ring where I can manually work with it.

In this particular case, I'm on a Chromebook using the Secure Shell App. I have the invaluable osc52.el library configured in my init like so:

(require 'osc52)
(osc52-set-cut-function)

With this code in place, emacs' kill ring is automtically sync'd with my Chromebook's copy and paste buffer. The result: evaluating browse-url places the URL not only in the emacs kill ring, but also in my Chromebook's paste buffer. This means that I can evaluate (oauth2-auth-and-store ...), Alt-Tab over to a browser window and hit Control-V to paste the URL that emacs tried to visit. In the case of oauth2 it started the authentication process as smoothly as if I had been on a windowing system.

Magic restored!

Wednesday, December 23, 2020

Using Tasker's Bluetooth Connection Event to Tackle Android Annoyances

The Bluetooth Connection Event, which was released as part of Tasker 5.8, helped me solve two Android annoyances. Here's the deal.

Smarter Keyboard Handling

For years, I've depended on the External Keyboard Helper (EKH) Pro App to optimize my Android handset for use with a hardware keyboard. I first discovered this app when I hooked my Galaxy S5 up to a Bluetooth keyboard and realized that with a bit of tweaking, I could turn my mobile phone into a Linux power-user's dream. Over the years, my phone and keyboard have changed, but the main pain point hasn't. I'd yet to find a seamless way of switching to and from EKH.

Fast forward to Tasker 5.8 and my Galaxy S10+; I've finally found an elegant solution for intelligently toggling between EKH and SwiftKey.

The solution starts by detecting the Bluetooth Connection event with the name of my keyboard, the iClever IC-BK06 Bluetooth Keyboard:

When a connection event occurs, the system determines if the connection was established or ended. Depending on this, the correct input method is set. Setting the Input Method is cumbersome because it requires AutoTools's Secure Settings. Once you've installed this plugin and run the relevant adb shell pm grant command, configuring it isn't hard. Browse to: Plugins > Auto Tools Secure Settings > Services > Input Method to enable a particular keyboard.

While the Profile and Task has a number of moving parts, it's been working perfectly. I can now flip open my iClever keyboard, wait a few moments and my device switches over to EKH and is ready to be used in hacker mode. I close up the keyboard, wait a few moments, and SwiftKey is re-enabled. It's beautiful!

Download this profile and its dependencies from Taskernet.

A Quieter Do-Not-Disturb Mode

Back in the day, one of the first Tasker profiles I wrote was to silence my phone when I flip it over. I use this Profile on a daily, or rather nightly, basis as I flip my phone every night before bed. Over the years, the strategy has changed for the best way to silence my phone. These days, when I flip my phone over I enable Do Not Disturb (DND) Mode. When I flip my phone right side up, I turn off DND Mode.

One of the features of DND Mode is the ability to define exceptions. For example, I opt to allow phone calls from starred contacts to break through, as well as alarms. The challenge I recently ran into was that I wanted to silence all Media during DND, unless I had my bluetooth headphones connected. In that case, I wanted DND mode to allow Media just like it allows Alarms.

Using the Bluetooth Connection Event, this turns out to be easy to do. I monitor Bluetooth activity for my headphones, the impressively named MPOW Thor. When 'Thor' connects or disconnects, I set the DND_EXCEPTIONS variable appropriately.

In the code run by the Flipped Profile, I no longer explicitly state the "Allowed Categories." Instead, I plug in the DND_EXCEPTIONS variable.

The result: when I flip over my phone the Do Not Disturb Mode will either allow or block Media based on whether my headphones last connected or disconnected from my device. Like the solution above, it may appear clunky, but it actually works quite reliably.

You can grab my 'Flipped' profile and its dependencies from Taskernet. Enjoy!

Thursday, December 17, 2020

Review: All Systems Red: The Murderbot Diaries

I randomly checked out Martha Well's All Systems Red: The Murderbot Diaries and was hooked after a few minutes of listening. From there, I devoured the book, squeezing in listening time whenever I had a free moment.

The book checked all my Sci-Fi boxes: likable characters, clever problem solving, a plot that moved and a future that felt both familiar and alien. I liked that it wasn't just the main character who had flashes of brilliance. The scientists, while out of their depth, step up to help fight for their survival. The book manages to explore lofty topics like what it means to be human and have free will, all while keeping a brisk pace and not losing focus on the the story.

There were other touches about the book that worked, too. It's short! Not exactly short-story-short, but at a little more than 3 hours it's a fraction of the length of most fiction I listen to. I also liked the implied diversity of the crew. From the names, I'm guessing that most of the characters are either of Indian or Asian descent. This, too, was a welcome variation from most of the fiction I read.

It's not unusual for me to finish a book in a series, truly love it, and then intentionally stop reading that series. Childish, yes, but it let's me savor what I just read. In the case of the Murderbot Diaries I'm too hooked: as soon as I finished book one, I reserved book two. This series is too well written to put down.

Tuesday, December 15, 2020

Screw It: The Search for the Perfect EDC Screwdriver Kit

A few weeks back we gave a friend's kid a birthday gift. We came prepared with the triple-A batteries it needed, but forgot a tool to remove the battery cover's security screw. Back in my day, battery covers just clicked into place; not so, today. I didn't panic. I grabbed my keychain multi-tool and went to work removing the screw. Alas, try as I may, I couldn't get it removed.

Naturally, I returned from that experience a chastened man. I spent the next few days feverishly searching Amazon for the perfect compact screwdriver kit. Here's where I landed.

An Itty Bitty Bit Driver

After much research, what I thought I wanted was a tiny bit driver. The idea being that you can carry one main tool and a bunch of 1/4" bits and thefore have the equivalent of a full screwdriver set. I picked up a keychain bit holder and a cheap L-shaped bit driver, mainly for the collection of bits.. After I made my purchase, I found this video from MeZillch warning that the bit driver I'd just bought wasn't going to work. He explained that the keychain's depth made it incompatible with compact bits.

And he was right:

Fortunately, I didn't give up. I grabbed a wooden barbeque skewer and a serrated kitchen knife and cut off a 1cm chunk of the skewer. I dropped that into the bit holder, followed by a bit, and found that the setup worked.

Inspired by the Versatool, I attached a carabiner to the keychain to let me apply extra torque to the driver. In basic tests, it works:

Keychain Screwdrivers

The other item I picked up was a set of keychain screwdrivers. One of them claimed to have a 1/4" hex cutout in handle. This was perfect! For common tasks the flathead and phillips screwdrivers would work, and for more esoteric needs I could use the hex cutout as a simple bit driver.

Like the keychain bit holder above, I was almost immediately disappointed by my purchase. The screwdrivers were fine, but the hex cutout was 6mm not 1/4" (or 6.3mm). My plan of using the cutout as a bit driver was a hard no. After fuming in frustration about the size mixup I realized that the hex wrench did have a plus. Each screwdriver's shank is 6mm hex. That means that you can feed one screwdriver into the handle of the other.

This is useful because it forms a T-grip and lets you apply hefty amounts of torque. This makes the screwdrivers vastly more functional. Another happy surprise: the flat head screwdriver does a terrific job of opening up boxes. It cuts through tape and cardboard with ease, and is less likely to take off my finger than a utility blade.

On the surface, the keychain screwdrivers aren't as versatile as the bit driver. But from a practical perspective, they're the winner. They are compact, robust, and the ability to make them into a T-shape makes them able to take on jobs big and small. The box cutting ability of the flat head screwdriver is another big win and makes this tool something I'm using multiple times a week. The bit setup has promise, but it's not especially compact and all the parts make the setup feel fiddly. For now, it sits in the kitchen utility drawer waiting to shine.

Friday, December 11, 2020

Review: The Barren Grounds: The Misewa Saga

I won't lie: I listened to The Barren Grounds: The Misewa Saga by David Robertson because of the cover art. I was totally getting a Guardians of the Galaxy vibe from it. Just a couple of minutes into the book I realized there may be some truth to the advice about not judging a book by its cover.

Instead of a fun space drama, I found myself in an intense preteen drama. Morgan, the main character, is a young indigenous girl who's been in foster care for years. When we join her, we find she's got a relatively new foster brother Eli, who's also indigenous, and a strained relationship with her white foster parents. From home to school and everywhere in between Morgan feels like she doesn't fit in.

The Barren Grounds follows the clever journey Morgan takes to meet this challenge. Kudos to the text for leaning into, rather than avoiding, touchy subjects like foster care, child separation and cross-cultural relationships. I'm not sure how kids will respond to Robertson's approach, but as an Uncle and Foster Dad I'm glad I read the book and will be able to recommend it to the children in my life. While entertaining, the book clearly offers an opportunity to approach and discuss complex and seemingly out-of-bounds issues.

I always get nervous when I see foster parents portrayed in books, TV and movies. Sometimes, they get it right (I'm looking at you, This is Us), more often not. Robertson, to his credit, has portrayed Morgan's foster parents in a fair light. Sure, they're trying too hard and don't always know the right thing to say (been there, done that). Frequently, they fall back on their training, which is good but clumsy. Most importantly, they treat Morgan and Eli with unconditional love. They know that a child lashing out and slamming doors is normal and all part of growing up. In that respect, they made me proud.

Why Morgan has been bouncing around the foster care system for years raises another set of important questions. The fact that she hasn't been placed in a permanent home may be convenient for the story line, but is troubling to read. The safety net designed to help children like Morgan has failed. Is this an accurate criticism or an exaggeration for the plot? I fear it's probably a healthy dollop of both.

Bottom line: The Barren Grounds was an intriguing read and a solid gateway to important topics that all kids should be exposed to.

Tuesday, December 01, 2020

Auto Packaging of Videos | ffmpeg To The Rescue

The challenge: find a way to programmatically add a title screen, watermark, and credit screen to a video. My first thought was to turn to ffmpeg; it's gotten me out of every other video processing jam before, surely it wouldn't fail me now.

My initial plan was to generate content using ImageMagick and then use ffmpeg to combine the various images and source video into a finished product. However, the more I learned about ffmpeg's filters the more I realized that I could do the content generation directly in ffmpeg.

To do this, I needed to grok two ffmpeg concepts. First, ffmpeg's complex_filter option allows chaining filters together to create interesting effects using a technique reminiscent of Unix pipes. One source of confusion: ffmpeg allows for writing the same expression in various ways, from verbose to cryptically terse. Consider these examples:

ffmpeg -i input -vf [in]yadif=0:0:0[middle];[middle]scale=iw/2:-1[out] output # 2 chains form, one filter per chain, chains linked by the [middle] pad
ffmpeg -i input -vf [in]yadif=0:0:0,scale=iw/2:-1[out] output                 # 1 chain form, with 2 filters in the chain, linking implied
ffmpeg -i input -vf yadif=0:0:0,scale=iw/2:-1  output                         # the input and output are implied without ambiguity

These all do the same thing, combining the yadif and scale filter. The first one explicitly names each stream ([in], [middle] and [out]) while the last example relies on implicit naming and behavior. This tersification extends to the filter definitions as well. These are equivalent expressions:

  scale=width=100:height=50
  scale=w=100:h=50
  scale=100:50

This ability to compose expressions with varying degrees of verbosity means that many one-liners I found on the web were initially hard to understand and even harder to imagine how they could be combined. Once I started thinking of filters and their arguments as chains of streams, and working with them using verbose notation, the problem was vastly simplified.

The other concept I needed to wrap my head around was that before I could filter a stream, I needed to have a corresponding input. In some cases, the input was obvious. For example, adding a watermark image and text to a video stream was relatively straightforward. One solution has the following shape:

  ffmpeg -i source.mp4 -i logo.png  \           [1] These are my inputs, 0 and 1 respectively
         -filter_complex "\
           [0:v][1:v]  overlay=.... [main]; \   [2] Overlay the first two streams, write the output to [main]
           [main] drawtext=... [main] \         [3] Add text to the main stream
         \" output.mp4

But what about adding a title screen before the main video? drawtext and overlay would let me add text and images to the stream, but what's the source of the stream in the first place? One solution: generate a stream using the oddly named virtual device lavfi. With this in mind, adding a title screen looks roughly like so:

  ffmpeg -i source.mp4 \        [1] Main video
         -f lavfi \
         -i "color=color=0xf2e4f2:\ [2] Generated input source
            duration=5:\                for our title screen
            size=1024x1024:\
            rate=30" \
         -filter_complex "\
          [0:]v ... [main]             [3] Do something with the source video and send it to [main]
          [1:v] drawtext=...[pre]\     [4] Draw on the virtual screen, send it to 'pre'
          [pre][main] concat [final]\  [5] Combined our [pre] and [main] streams for a final result
         \" output.mp4
        

With a solid'ish grasp of filters and lavfi generated streams, I was ready to tackle the original problem. I created a shell script for packaging video and the final result was looking acceptable. But then I ran into another issue: what's the best way to parameterize the script?

If I passed in all the arguments on the command line, about 40 in total, using the script would be a pain. If I stored all the options in a config file, then scripting the command would become tricky. With a bit of reflection, I realized there was a low effort, high value solution to this problem. At the top of the script, I define sane defaults for all the variables. I then process command line arguments like so:

config=$HOME/.pkgvid.last
cp /dev/null $config

while [ -n "$1" ] ; do
  arg=$1 ; shift
  if [ -f "$arg" ] ; then                [1]
    echo "# $arg" >> $config
    cat $arg >> $config
  fi
  
  name=$(echo $arg | cut -d= -f1)
  if [ "$name" != "$arg" ]; then         [2]
     echo $arg >> $config
  fi

  echo >> $config
done

. $config  [3]

For each command line argument, I first check if the argument is an existing file. If it is, I add the contents of that file to the end of ~/.pkgvid.last. I then check if the argument has the shape variable=value, if it does, then I add this expression to the end of ~/.pkgvid.last. Once I've processed all the command line arguments, magic happens at [3] by sourcing ~/.pkgvid.last. This reads the variable definitions that were defined in config files and on the command line, and has them override defaults.

This sounds confusing, but in practice, it's delightful to use. Consider blog.config which has been setup to override defaults for blog related videos:

main_video=$base_dir/water.short.mp4
main_caption_text="Ben Simon"

pre_title_text="A BlogByBen Video"
pre_bg_color=0x1d518a
pre_title_color=white
pre_image_w=250

main_caption_color=0x1d518a
main_image_w=75

post_title_text="blogbyben.com"
post_bg_color=$pre_bg_color
post_title_color=$pre_title_color

ffmpeg=/usr/local/bin/ffmpeg
logo=$base_dir/ben_logo.jpg

I can then override these settings by using command line options. For example:

 for part in $(seq 1 10); do
   pkg.sh blog.config pre_title_text="Blogging Secrets. Part $part of 10"
 done

This strategy lets me organize variables into a config file and judiciously overwrite them on the command line.

OK, that's more than enough theory. Let's see this in action. Below is a simple test video, followed by this same test video packaged up with the command: pkg.sh blog.config.

Below is the script that created this video. Hopefully this gives you a fresh appreciation for ffmpeg and an interesting example to play with.

#!/bin/sh

##
## This script is used for packing up a video
##


# Default Variables
base_dir=$(dirname $0)
font_dir=$base_dir/fonts
debug=off

ffmpeg=ffmpeg
ffprobe=ffprobe

logo=logo.png
output=output.mp4

main_video=input.mp4
main_image_x="main_w-overlay_w-20"
main_image_y="(main_h-overlay_h-40)"
main_image_w=500
main_image_h=-1
main_caption_text="Thanks for watching"
main_caption_x=20
main_caption_y="(h-text_h)-40"
main_caption_font_size=48
main_caption_font=Lato-Heavy.ttf
main_caption_color=0x222222
main_caption_start=0
main_caption_end=5

pre_duration=3
pre_image_w=500
pre_image_h=-1
pre_image_x="(main_w-overlay_w)/2"
pre_image_y="(main_h-overlay_h)*.80"
pre_bg_color=0x666666
pre_title_text="Title Text"
pre_title_font='Lato-Heavy.ttf'
pre_title_x="(w-text_w)/2"
pre_title_y="(h-text_h)/2"
pre_title_font_size=48
pre_title_color=white

post_duration=5
post_bg_color=0xE4E4E4
post_title_text="Thanks for watching"
post_title_font='Lato-Thin.ttf'
post_title_font_size=65
post_title_x="(w-text_w)/2"
post_title_y="(h-text_h)/2"
post_title_color=0x222222

config=$HOME/.pkgvid.last
cp /dev/null $config

while [ -n "$1" ] ; do
  arg=$1 ; shift
  if [ -f "$arg" ] ; then
    echo "# $arg" >> $config
    cat $arg >> $config
  fi
  
  name=$(echo $arg | cut -d= -f1)
  if [ "$name" != "$arg" ]; then
     echo $arg >> $config
  fi

  echo >> $config
done

. $config

main_video_width=$($ffprobe -show_streams $main_video 2> /dev/null |grep ^width= | cut -d = -f 2)
main_video_height=$($ffprobe -show_streams $main_video 2> /dev/null |grep ^height= | cut -d = -f 2)

if [ "$debug" = "on" ]  ; then
   ffmpeg="echo $ffmpeg"
fi

$ffmpeg -y  \
 -i $main_video \
  -i $logo  \
  -f lavfi -i color=color=$pre_bg_color:${main_video_width}x${main_video_height}:d=$pre_duration \
  -f lavfi -i color=color=$post_bg_color:${main_video_width}x${main_video_height}:d=$post_duration \
  -filter_complex "\
    [1:v] split [logo_a][logo_b] ; \
    [logo_a]scale=w=$main_image_w:h=$main_image_h[logo_a] ; \
    [logo_b]scale=w=$pre_image_w:h=$pre_image_h[logo_b] ; \
    [0:v][logo_a] overlay=${main_image_x}:${main_image_y} [main] ; \
    \
    [main]drawtext=fontfile=$font_dir/$main_caption_font: \
          text='$main_caption_text': \
          x=$main_caption_x: y=$main_caption_y: \
          fontsize=$main_caption_font_size: \
          fontcolor=$main_caption_color: \
          shadowcolor=black@.8: shadowx=4: shadowy=4: \
          enable='between(t,$main_caption_start,$main_caption_end)'[main]; \
    \
    [2:v]drawtext=fontfile=$font_dir/$pre_title_font: \
         text='$pre_title_text': \
         x='$pre_title_x': y='$pre_title_y': \
         fontsize=$pre_title_font_size: \
         fontcolor=$pre_title_color [pre]; \
    \
    [pre][logo_b] overlay=x='$pre_image_x':y='$pre_image_y' [pre] ; \
    \
    [3:v]drawtext=fontfile=$font_dir/$post_title_font: \
         text='$post_title_text':\
         x='$post_title_x': y='$post_title_y': \
        fontsize=$post_title_font_size: \
        fontcolor=$post_title_color [post]; \
   \
    [pre][main][post]concat=n=3 \
  " \
  -vsync 2 \
  $output

Monday, November 30, 2020

Mason Neck State Park - Enjoying Kid Friendly Hiking with Fun Kids

Last week we hiked Mason Neck State Park with friends. It had been years since we'd been to Mason Neck and I forgot what a little gem it is. We tackled the Wilson Spring and Bayview Trail, making for about 2 miles of hiking. This was the perfect distance for the kids. The trail winds through forest and swamps and there's no big ups to tire out little legs.

Shortly after the hike began we came across a geocache. The kids searched for, and Aurora quickly found, it. It was nice to start our hike with a win!

Midway thorugh our hike we stopped at the beach on the bay to properly explore the area. We marveled at the massive snail shells, and watched what appeared to be a duck-hunter doing his thing further down the beach. Shia even built a little sand castle!

I saw a number of hikers carrying big lenses, which is promising. My guess is that the glass was to be used for bird watching. We didn't see any interesting specimens this trip, but I'm sure they are there.

At about 40 minutes away from DC, Mason Neck is the perfect combo of backwoods adventure and family friendly convenience. I'm psyched to go back.

Monday, November 23, 2020

Countering News Bias - A Simple Solution

Getting your news from a single source is a great way to lock yourself into an echo chamber. Clearly, the smart news diet is the diverse news diet. And yet, there's no denying that I nearly always turn to CNN when I want to quickly check the news.

To try to kick this habit, I've added a new 'News' bookmark to my browser's toolbar. Rather than directing me to one site, it runs this code:

javascript:
(function(){
  U=[
  'https://cnn.com/',
  'https://msnbc.com/',
  'https://abcnews.go.com/',
  'https://foxnews.com/',
  'https://wsj.com/',
  'https://vox.com/',
  'https://arlnow.com/'
  ];
  window.location = U[Math.floor(Math.random()*U.length)]})()

This bookmarklet, as hopefully any first year CS student can tell you, creates an array of sites and picks one at random to visit. As long as I hit the News button on my toolbar, I won't be sucked into one news source.

I figured a random URL bookmarklet could come in handy in other contexts, so I created a tool for generating them. Check it out at http://code.benjisimon.com/bookmarklets/random-url.php. Here's an example of me creating a goof-off bookmarklet that sends me to a random site for a quick laugh:

Are my worries about news bias put to bed? Not by a long shot. But I feel like I'm at least headed in the right direction.

Check out the code behind the bookmarklet generator on github.

Thursday, November 19, 2020

Killer Artwork

Weeks ago, I was running at nearby Theodore Roosevelt Island and came across these cool abstract sketches:

And here's another example from our recent Blue Ridge Mountains adventure:

Is this ancient indigenous art? A coded message from aliens? Teens messing around? Nope, they're bug trails. Specifically, the side effect of beetle activity. The phenomenon even has an appropriate name: Beetle Galleries. While beautiful, they're art with consequences: the beetles can (often? always?) kill the tree.

That's some high stakes art right there.

Wednesday, November 18, 2020

Building a Garmin Watch Widget | Part 3: The UI

Once I had my Sun Compass Widget calculating the correct azimuth for the Sun, I was left with one more challenge: rendering this information graphically. My plan was to generate a simplified compass dial and then plot the current location of the Sun on it. The first hurdle was to realize I had to stop using XML based layouts and program the UI directly using the Dc object. The second challenge was learning how to convert polar to rectangular coordinates. With these obstacles surmounted, the UI came together with ease:

The above UI is drawn using a this strategy:

 for(degrees = 0; degrees < 365; degrees++) {
   var color = i % 90 == 0 ? "red" : "gray";
   drawDot(degrees, color);
 }
 var start = pol2rect(watchFaceWidth / 2, azimuth);
 var end   = pol2rect((watchFaceWidth / 2) - 5, azimuth);
 drawLine(start, end, "yellow");

In short: once you can think of a round watch dial as a polar coordinate space, working with it becomes a breeze.

Looking at the above UI, I realized that along with creating a compass I'd also started down the path of creating a solar clock. For example, when the Sun is due South, it's solar noon. To build on this idea, I added markings for sunrise and sunset.

In the above UI, the red dots represent cardinal directions, the yellow line represents the position of the sun and the green lines represent sunrise and sunset. You can tell this screenshot was taken about an hour after sunrise. You can also tell this was taken in the winter when the day is relatively short and the Sun will never be due East or West.

I built the above app for my watch, loaded it on my Vivoactive 4 and spent a week field testing it in the Blue Ridge Mountains.

Generally, the widget performed well. It loaded quickly and always displayed accurate information. The biggest shortcoming: the effort needed to derive my current direction. To figure this out, I had to either stop and rotate my body until the yellow mark lined up with the Sun, take off my watch and line the yellow mark up up with the Sun, or attempt to do this translation mentally. While in motion, none of those options were ideal. I wanted the watch to do the work, not me. So I added the following feature.

By default, the widget shows a North-is-up view:

Clicking the top watch button rotates through a series of variations. It shows the compass with the sun ahead, to the right, behind and to the left of me:

The idea is that I can re-orient the display to roughly match where the Sun is relative to me at that moment. For example if the Sun is roughly behind me, I can click through until the compass dial shows this, and then use the cardinal markings to learn my direction.  This view is sticky, so re-checking the Sun Compass a few minutes later doesn't require me clicking through the options again.

I've now got this version running on my watch and I psyched to field test it. Once I'm satisfied with the UI, I'll finish off this project by uploading it to the Garmin IQ Store so others can give the widget a try.

As always, check out the code for this project on github.

Monday, November 16, 2020

Blue Ridge Mountains Adventure - Day 4

I wanted to squeeze in one more hike before we called it a trip. In the name of efficiency, we opted to string together trails located within the Wintergreen Resort itself. We connected up the Old Appalachian Trail, Upper Shamokin Gorge Trail and Chestnut Springs Trail. Given the density of housing within the area and the range of clientele who visit the resort, I had my expectations for these trails set low. If we got out in the woods for another hour or so, that was going to be considered a win.

The Old Appalachian Trail started off delightful, and as expected, quite tame. However, things took a turn for the interesting when we started down the Upper Shamokin Gorge Trail. This was a fun trail with plenty of rock scrambling. I finished that trail quite impressed. The Chestnut Trail brought us back to reality as we made up way up from the gorge to civilization and our car.

We stopped at one of the overlooks for one more picture and I continued to be blown away by the scene in front of me. This place is truly gorgeous.

With our last trail logged, we officially called it a trip and made our way back to DC.

I couldn't have been happier with our choice of vacation spots. The weekday off-season rate we paid for our condo was incredibly low. And the range of hikes, from those just a few minute drive away, to an hour away, were all impressive. And I could get used to drinking hard cider every night.

With all that said, we can't really speak to how nice a resort Wintergreen is. We didn't engage with any of the amenities, activities or restaurants. Still, there's no arguing they've got an ideal location. I'd go back in a minute.