Wednesday, March 24, 2021

Art on the Run

One of my favorite pieces of art is Ripple by Tejo Remy and Rene Veenhuizen. You won't need to make an appointment at a gallery to see this piece as it's bolted to the outside of Arlington County's Water Treatment plant. You need only stroll along a delightful trail to take in its 800 feet of social commentary.

I enjoy Ripple for a number of reasons. It's creatively placed and has a well camouflaged meaning. I've come to appreciate that what looks like kids detritus is actually a novel statement on unexpected consequences, ripples if you will, of our behavior. And I get to ponder all this while jogging by.

Given my affinity for seemingly random acts of art, you can appreciate my joy of when I was running the Four Mile Run Wetlands Trail (not far from Ripple) and came across what seemed to be a painted stack of rocks. It wasn't immediately obvious what I was looking at: something discarded, a one off art project? As I ran further on, I saw another stack of painted rocks. Now I had to stop and take some pics. Check it out:

So what I did discover? It seems the Alexandria Commision on the Arts has sponsored three recent art installations. One of them is by Alexander Rudd. And on Alex's Instagram page are these pics:

Those are the sculptures I saw on the trail. In Alex's feed they were in the back of a moving truck.

As for more details, the article describing the art installations says only: "the Alexandria Commission on the Arts did not release information on the nature of those projects."

We got a good 'ol fashion public art puzzle to decode.

I don't think Alex is going to give us much in the way of hints. His 'gram bio reads:

Conceptual read vs arbitrary appearance + algorithmic disposability = objectivity confusion

Yeah, that clears everything up.

Still, challenge-excepted. I'll have to add Four Mile Run to my upcoming running routes to get some more viewing time in.

Tuesday, March 23, 2021

Caption Me: Divorced Birds Edition

I caught this scene on my run yesterday:

While I'm not sure of the caption yet, these pics do feel worthy of the r/DivorcedBirds treatment. And what is DivorcedBirds you ask? Reddit explains:

For pictures of fabulous fowl who look like serial monogamists. Please post pictures of birds who look like they are twice divorced (or more!) Pictures/Videos must be of actual birds (feathered fowl), not human women, not art or paintings or photoshopped. No dead birds.

In truth, that description doesn't do the sub-reddit justice. Divorced Birds is an exercise in (very) short story writing with the theme of, well, divorce. It captures so many emotions: anger, hope, envy, shame and more with just a pithy headline and follow-up comments. It ranges from unbelievably wholesome to downright wicked. Here's an example that's currently on the home page:

So, what's the deal with the above birds? Leave your divorce, or not, related captions as a comment.

Thursday, March 18, 2021

Who's reading what? Tracking User Interaction in WordPress's Arconix FAQ Plugin

I added a FAQ to my website and found myself with a new question: what FAQs were people actually reading? I'm using the WordPress Arconix FAQ plugin which let me easily author and display questions and answers, but doesn't appear to track usage.

My solution: push an event to Google Analytics every time a user expands a FAQ. Google Analytics does the heavy lifting, as it will store and report on event activity. All I have to do is to convince WordPress to send the event when the user clicks on a question. Here's how I did that.

Step 1: Add Event Tracking to my Theme

In my theme's functions.php I added the following code:

add_action('wp_enqueue_scripts', function() {
  wp_enqueue_script('theme-analytics',
                    get_template_directory_uri() . "/js/analytics.js",
                    ['jquery'],
                    md5_file(__DIR__ . "/js/analytics.js"));
});

This loads js/analytics.js when my theme loads, and forces the browser to pick up changes when I modify analytics.js. I've made analytics.js depend on jquery. This simplifies the code that will be found in analytics.js.

Step 2: Add The Event Tracking Code

Inside of js/analytics.js I added the following code:

jQuery(document).ready(function($) {
  var MEASUREMENT_ID = 'UA-XXXXXXXXXX-1';

  function sendEvent(action, category, label) {
    console.log("GA Event", action, category, label);
    
    gtag('event', action, {
      event_category: category,
      event_label: label,
      value: 1,
      send_to: MEASUREMENT_ID
    });  
  }

  $(document).on('click', '.arconix-faq-title', function() {
    var me = this;
    setTimeout(function() {
      var expanded = $(me).hasClass('faq-open');

      if(expanded) {
        sendEvent('show', 'faq', $(me).text());
      }
    }, 100);
  });

});

You'll want to be sure you update the value of MEASUREMENT_ID so that it contains your analytics property identifier.

I'm monitoring clicks on the CSS class .arconix-faq-title. I added a 100 millisecond delay because I'm not confident of the timing of things. I want to check if the FAQ is expanded, the time delay insures that when I do this check the UI code will have already run.

With these bits of code in place I can now check out FAQ activity in Google Analyics:

If I drill down into Event Category and set the Event Label as the secondary dimension I'm able to see the questions that get the most traction.

But Wait, There's More!

Now that I've gotten the hang of sending events, I couldn't stop at just tracking the FAQ page. I added this code to analytics.js to track Contact Form 7 submissions. In this case, I'm using the subject of the e-mail as the event label:

  $(document).on('wpcf7submit', function() {
    var subject = event.detail.formData.get('your-subject');

    sendEvent('submit', 'contact-us', subject);
  });

My company's home page includes a call to action button and a mini FAQ. I added events to track their interaction as well:

$(document).on('click', '.q-and-a-grid .entry', function() {
    var q = $(this).find('.question').text();
    sendEvent('click', 'home-faq', q);
  });


  $('a[href="#content-start"]').click(function() {
    sendEvent('click', 'home-button', 'learn-more');
    return true;
  });

You've Got To Have Goals

Along with viewing these events within Google Analytics, it's possible to set these up as goals. For example, here's the goal definition for my FAQ page:

One interesting use for goals is to power experiments within Google Optimize. For example, you could run A/B tests on different wording for a question, and have Google pick the one that's read most frequently.

Want a Hand?

Want help setting up custom even tracking for your website? I'll spare you reading the FAQ and tell you that I'm glad to help. Drop me an email via questions@ideas2executables.com or hit me up on my contact-us page.

Tuesday, March 16, 2021

On This Day, Blogger Edition

I recently built On This Day: Google Photos Edition. This tiny web app shows you photos you took on the current date going back 15 years. I built it originally for finding Zoom Backgrounds, but found it to be a suprising source of inspiration and entertainment.

I've been publishing this blog for almost 16 years. I've often struggled with maintaining past posts. Services (I'm looking at you, Google Picasa!) and technologies (and you, Flash!) have come and gone over the years, leaving some posts pathetically broken.

If had a tool like On This Day: Pics, that let me see what blog posts I created on this date, I could review and edit these posts. If I made this a daily habit, I could slowly work my way through my 6,500+ posts without breaking a sweat.

I give you: On This Day: Posts edition, a quick web app that dumps out the posts you authored on this day.

I see that most of the posts from today are still looking good. The video I shot of Shira driving in Australia back in 2009 still works, and the pics from our first day traveling in Singapore are also there.

I did find, however, that write-only a tool I made to experiment with append only writing was broken. After a few minutes of JavaScript debugging, it was back to working.

There's still a need for tools to help maintain my blog. The labels, for example, are a mess and a bulk-label-fixing tool has been on my radar for years. But this day by day, spot-check-and-fix approach seems to hold real promise.

Sunday, March 14, 2021

You've got Questions. I've got Answers. Finally.

If you have an online business, it's a no-brainer to offer a FAQ section on your website. And yet, for years, my website went without this basic page. I'd always been too busy, or had some other execuse, to put it together.

But no more! I finally forced myself to focus on this, and a few hours later the Ideas2Executables FAQs page was born. I've never been so excited over a list of questions and answers before.

Can you think of any business or software development questions you'd like answered that didn't make the list? I'm a question-answering beast now!

Thursday, March 11, 2021

You Need This Information | What to do in a Power Line Emergency

If you're think I'm blogging about power line safety as part of my disaster planning kick, I applaud your attentiveness. But no, this post is all thanks to the goofy sub-Reddit /r/ThatLookedExpensive.

Here's the Reddit post that inspired this PSA. It features a dump truck that makes contact with power lines and disaster strikes from there. The comments on the post talk about what actions you can safely take if you find yourself in this situation. Ultimately, someone linked to this dramatization offered by Puget Sound Energy.

As I watched this video, I thought to myself: how do I not know this information? This feels like information that I should know.

So yeah, because you live and drive near power lines, you need to know this information too. Take 5 minutes and watch the video.

For a quick recap of what to do during a power line emergency, check out my First Aid Cheat Sheet.

Wednesday, March 10, 2021

On This Day, Google Photos Edition

A First World Problem

With so many meetings and events being held on Zoom I found I had a new first world problem: what photo should I select for my background?

After a bit of experimentation I arrived at a solution: I'd look at    photos that I taken on the current date going back a few years, and pick from one of those.

While I did this to help narrow the photos to pick from, it had surprising side effects. I found it instructive and inspirational to look back through the years and see where I was traveling and what projects I was working on. Picking a background became a useful little exercise in itself.

The Manual Solution

Convincing Google Photos to show photos taken on a particular day took a bit of experimentation. I found what works best is to search for date spelled out and quoted like so: "March 10, 2010". Yes, the quotes are necessary. Attempts use other formats didn't work reliably.

The Automated Solution

Naturally, I got tired of searching by hand. So I made an app for that. I give you: https://code.benjisimon.com/on-this-day/.

This little web app is slow and buggy. Most of the coding effort was spent getting OAuth2 authentication to work, and even then it's struggling with token-refreshing. I also spent more time than I'd like to admit puzzling out how to call the searchMediaItems method on the PhotosLibraryClient. Ultimately, I figured out how to get it to accept the specific date and result size I was targeting.

Still, as rough as the app is, it meets the need. I can now visit the on-this-day, and after a few moments, I see photos I took going back to 2010.

I'm now using this to set my desktop background on a daily basis. I'm telling you, this little walk down memory lane is surprisingly enlightening.

Check out the code for on-this-day over at github. If you're looking for OAuth and Google Photo PHP API SDK examples, this code may be instructive. Or maybe it's a cautionary tale. Either way, I hope you find the code and site useful.

Monday, March 08, 2021

Review: The Unthinkable: Who Survives When Disaster Strikes - and Why

I finished The Unthinkable: Who Survives When Disaster Strikes - and Why by Amanda Ripley and couldn't help but feel overwhelmed. She covers so many disasters from some many different perspectives, that by the end of the text I was maxxed out.

I'm sure that wasn't her intention, but the comprehensive nature of of the book struck me as a disadvantage. Ripley's a journalist, not guru, so her mission is to share what survivors and experts have to say about their experiences, not tout her own philosophy. This leads to advice that naturally contradict each other.

Consider this example: first, to reduce information overload focus on disasters that are likely to happen. Ignore the media hype. You're more likely to be injured in a car accident, then be involved in an active shooter incident, so focus on steps you can take to increase your safety on the road.

However, a second tenet of the book is that it's precisely the outlier events, the unthinkable ones if you will, that leave you open to catastrophic handling because they so unfamiliar. So it's not likely that you will end up in an active shooter incident, but if you do and you've done no preparation, chances are you'll react poorly.

In short, by the end of the text, I didn't anymore anecdotes or experts; I just wanted to be told what to do!

After some reflection, however, I've come to appreciate The Unthinkable's approach. Yes, it's goal of being comprehensive can be daunting, but it also elucidates a novel strategy. To me, it goes like this:

During an emergency, a person travels through three stages: denial, deliberation and the decisive moment. Ideally, you'd zip through denial, have quick and intelligent deliberations and execute the best actions available to you during the decisive moment. Through Ripley's research, we appreciate that there's no way to guarantee an optimial traverse through these stages. Even your everday temperment isn't a strong indicator of how you'll perform.

But, there is good news: through small changes, you can improve your performance in a disaster. Take the active shooter scenario above. If you complete Run / Hide / Fight Training, get into the habit of noticing exits when you walk into buildings, practice leaving through different routes at your work place and keep a trauma kit at your desk, you'll be in a far better position to survive an active shooter. You'll also be in a better position to deal with a fire, earthquake or coutless other disasters that call for evacuation and the possiblity of mass casualties.

Using this approach, I can imagine a simple disaster optimization algorithm: identify risks, look for simple yet effective ways to mitigate these risks and put these discoveries into practice. And repeat.

So yes, Unthinkable is daunting. But in this context it's also uplifting. You don't need to spend huge amounts of money or re-work your entire life to be disaster ready. What you do is need is imagination, creativity and perseverance. As disaster after disaster shows in Ripley's book, small changes can have a mighty impact.

Thursday, March 04, 2021

Yum! Greens n Teff, Vegan Ethiopian Goodness

We tried Greens N Teff last night for the first time, and it was an all around fantastic experience. GnT is a purely vegan Ethiopian place that opened down the street from us. Considering how much we like Ethiopian food and how rare fully vegetarian restaruants are in the area, this is a bit like winning the lottery.

On paper the place seemed great, but we tried to temper our expectations.

While on a walk in the warm'ish weather yesterday we placed an order and 25 minutes later dropped by to pick it up. GnT is tucked away in a tired looking part of the Pike. What we found when walked in delighted us: a spotless location with a warm couple who welcomed us and had our food ready. I asked about Minchet, a dish that was available to order but didn't have a description on the menu. Their response: here, try a sample. It was tasty, perhaps we'll get it next time.

Once home, we chowed down on our order. The Mushroom Tibs was probably the winner for the tastiest dish, though they were all solid.

There hasn't been a whole lot of great resterauant news for us during the pandemic. Heck, we lost our donut shop at the end of last year. But a vegan restaurant walking distance from our home? What more could we ask for?

Between GnT and Dama (also on Columbia Pike), we're more than covered for all our Ethiopian food needs.

Update: Apparently we aren't the only ones who are impressed with Greens N Teff. Arlnow reports that the community in general is a fan.

Tuesday, March 02, 2021

Using 1password To Seamlessly Manage Subversion Credentials

Subversion Authentication is Painful. Let's Fix That.

Subversion authentication is a pain. For years, I got by on plain text storage, but that's insecure and deprecated. On some platforms, like Mac OS, it's possible to use the system keychain to manage access. However, this becomes both a source of inconsistency and frustration as I work across multiple devices and operating systems.

What I wanted was to have subversion pull my credentials out of 1password. But how would I even begin to add this support to subversion?

Inspired by the 1password command line tool and this recipe for managing ssh keys in 1password, I found a way. This post is my attempt to document that process.

Assemble the Pieces

Convincing subversion to use 1password for authentication requires three properly configured components. If any of the three tools isn't setup right, the chain fails and authentication is a no go. Let's go through this step by step.

Tool 1: op

The op command line tool gives you shell script access to 1password. Make sure you can sign in and pull down the password for the relevant subversion accounts. Here's how that may look:

# The First Time
$ op signin allmysecrets.1password.com contact.ben.simon@gmail.com

# After the account is setup
$ eval $(op signin allmysecrets)

# Print the password for your subversion account.
$ op get item 'master svn account' | \
  jq -r '.details.fields[] | select(.designation=="password").value'

Tool 2: gpg-agent

gpg-agent is the glue that holds our solution together. Subversion, as we'll see in a moment, can be convinced to consult gpg-agent for credentials. Using gpg-agent's ability to preset a passphrase, it's possible to programmatically insert credentials into gpg-agent. To configure this, make sure your gpg-agent config file, ~/.gnupg/gpg-agent.conf, has the following settings:

allow-loopback-pinentry
allow-preset-passphrase
default-cache-ttl 34560000
max-cache-ttl 34560000

The allow-preset-passphrase setting is key. It's what allows gpg-agent to accept passphrases from an external source like 1password. The high values for default-cache-ttl and max-cache-ttl ensure that once I store credentials in gpg-agent they won't time out. This is personal preference, and if you wished, you could lower this value.

Having a properly configured gpg-agent gets you most of the way there. The last challenge to setting up gpg-agent is finding the location of the gpg-preset-passphrase command on your system. In every version of Linux I use, I find it's in a different location. For example, I'm composing this blog post on a Windows Subsystem for Linux Ubuntu instance, and gpg-preset-passphrase is found in /usr/lib/gnupg/.

With the config setup and gpg-preset-passphrase found, it's time to try this out.

# Confirming our test creds aren't there.
$ echo "GET_PASSPHRASE --no-ask --data passphrasetest1 a b c" | gpg-connect-agent
ERR 67108922 No data <GPG Agent>
  
# Store the credentials
$ /usr/lib/gnupg/gpg-preset-passphrase -c -P "ShhhItsASecret" passphrasetest1  

# Confirm they are stored
$ echo "GET_PASSPHRASE --no-ask --data passphrasetest1 a b c" | gpg-connect-agent
D ShhhItsASecret
OK

Tool 3: subversion

First off, your version of subversion needs to be built with gpg-agent support. Check this by running svn --version:

$ svn --version
svn, version 1.13.0 (r1867053)
   compiled Mar 24 2020, 12:33:36 on x86_64-pc-linux-gnu
...
The following authentication credential caches are available:

* Gnome Keyring
* GPG-Agent
* KWallet (KDE)

If GPG-Agent isn't listed under available authentication credential caches then you need to build or download a version of subversion that does have this support. It'll be worth it, I promise.

Next, update ~/.subversion/config so that it uses the gpg-agent authentication cache:

### Section for authentication and authorization customizations.
[auth]
### Set password stores used by Subversion. They should be
### delimited by spaces or commas. The order of values determines
...
# password-stores = gpg-agent,gnome-keyring,kwallet
### To disable all password stores, use an empty list:
password-store = gpg-agent

Next, perform an operation that requires subversion authentication, like say 'svn up.' If all goes well, svn should prompt you for your password by using your system's gpg-agent pin-entry program. You can cancel out of this.

Finally, determine a number of important details with how subversion is interacting with gpg-agent. Do this by looking in the subversion auth directory, ~/.subversion/auth/svn.simple/. You should see files named like so:

$ cd ~/.subversion/auth/svn.simple
$ ls -1
3f08d77847ea42f0b7b1ccd66fd14138
a7c22b9f8eabdc93df040f5954967d2b
7f0a2d6c2e00dc758c9385fe821f07cb

There should be one file for each subversion domain that you access. Peeking inside one of these files should show you something like the following:

$ cat 3f08d77847ea42f0b7b1ccd66fd14138
K 8
passtype
V 9
gpg-agent
K 15
svn:realmstring
V 39
<https://master.securerepo.com:443> SVN
K 8
username
V 3
dev
END

This config file tells subversion that whenever it wishes to access repositories hosted on master.securerepo.com, it should do so using the username 'dev' and consulting gpg-agent for the password.

Let's Do This

Before we tie all this together, let's look at how this supposed to work. Suppose you enter the command:

$ svn checkout https://master.securerepo.com/projects/borken/trunk

Subversion will look for the configuration file that correspond to the realm string <https://master.securerepo.com:443 SVN>. In our example above, it will find this in the file named 3f08d77847ea42f0b7b1ccd66fd14138. It will then use this filename to ask gpg-agent for the credentials matching the keygrip 3f08d77847ea42f0b7b1ccd66fd14138. Our goal, therefore, is to to preset our passphrase for this hex value using the password found in 1password.

Here's one solution to accomplish this:

# sign in to 1password
$ eval $(op signin allmysecrets)

# store the svn repo password in a shell variable
$ svn_password=$(op get item 'master svn account' | \
  jq -r '.details.fields[] | select(.designation=="password").value')

# store the password in gpg-agent with the correct hex key
$ /usr/lib/gnupg/gpg-preset-passphrase -c -P "$svn_password" 3f08d77847ea42f0b7b1ccd66fd14138

# And we're done! svn should find the credentials in gpg-agent and
# not bother asking us
$ svn checkout https://master.securerepo.com/projects/borken/trunk src
$ cd src
$ svn switch ^/branches/feature-x

I have a shell script that contains a mapping of svn domains to 1password uuid's. This let's me run a single command to authenticate  all svn domains in one go. Every time I run it I feel a bit of joy; my passwords are securely stored in 1password and they are seamlessly available to svn.

It was a long journey to get this all sorted, but it was so worth it!

Monday, March 01, 2021

Preparing for the Unthinkable, Part 2 - Trauma Kits

Let's Talk Trauma Kits

Shira and I recently attended Until Help Arrives (UHA) training, a County designed class to train bystanders to save lives during a mass casualty event. In the class, they highlighted a number of store-bought materials minimally trained individuals can use to save lives. I've compiled these items to form two different types of trauma kits.

The first kit is a full sized, deluxe version. It's bulky, but contains everything a person needs to execute the TECC Active Bystander Guidelines. It's intended to be staged near a potential catastrophe: say, in a desk drawer, behind a bimah, or in the trunk of a car.

The pocket version is more compact, but requires that you augment it with materials in the field. It's intended to go in your purse, or man bag. It gets deployed when a crisis happens at an unexpected location, like a concert or a movie theater.

Before I dive into the details of each kit, let's talk about a favorite topic: improvisation.

On Improvisation

Whenever the topic of trauma kits comes up, there's typically mention of two principles: first, that the items found in a trauma kit can be improvised from everyday materials. If you have a table cloth, fork and steak knife, then you can craft a tourniquet with relative ease. The second principle: for a minimally trained individual, purpose-built devices are going to be far more successful then their improvised cousins. So, yes you can make a tourniquet out of a table cloth, but in a high-stress situation where a loved one has minutes to live, you're going to be far more successful with a SAM XT tourniquet than the home made version.

I would  add to that discussion that there's broadly two flavors of improvisation: planned and unplanned. One common improvisation is to create a chest seal out of a Ziploc bag and tape. Consider Alice who happens to keep a Ziploc bag and tape in her purse. Both these items are nearly weightless, bulk-less and have countless uses. If an emergency happens, Alice can improvise a chest seal out of items she knows she has on her person. Bob, on the other hand, hasn't staged these items. If an emergency happens, he needs to first search out a baggie and tape before he can start improvising. The best option is for both Alice and Bob to have access to a stocked trauma kit which would contain a medical grade chest seal. If that's not possible, I'd argue that Alice is in a far better position than Bob to deliver care even though they are both improvising.

My point: if you have to fallback on improvisation, a little planning can go a long way.

The Full Size Kit

Primary Tourniquet: SAM XT - this is the UHA's recommended tourniquet and I see why. Its satisfying click when you tighten it in place helps assure you  that you're using it properly. In the situation above where a loved one is bleeding uncontrollably, this is the device you want on hand.

Secondary Tourniquet: SWAT-T Tourniquet. Not as easy to deploy as the SAM-XT, but still a reputable, field proven tourniquet. It has the advantage of being multi-purpose and working on limbs of any size, including children and pets.

Pressure Dressing: 6" Israeli Bandage. The Israeli Bandage is a legendary piece of battlefield kit. Instead of having to carefully dress a wound with a gauze pads and then wrap it with an Ace-wrap to keep it in place, the Israeli Bandage provides an all in one solution. For bleeding that doesn't call for a tourniquet the Israeli Bandage is the way to go.

Secondary Pressure Dressing: SWAT-T Tourniquet + Gauze.

Wound Packing Aid: Rolled Gauze. This may get swapped out with hemostatic gauze at some point, but for now, cheap, multi-purpose gauze is the winner.

Chest Seal: HyFin Vent Chest Seal. Bulky and relatively expensive these do one job, and do it well. If you ever need to do deploy a chest seal, you're going to be glad you didn't skimp.

Hypothermia/Shock Treatment: S.O.L. Heatsheet Emergency Blanket. In theory, a cheap (around $1.00) Mylar blanket will keep a victim warm while you take other actions to raise their body temperature. However, I like to splurge on the upgraded Heatsheet version because they are less likely to tear, are quieter to use and I've got a track record of using them successfully in the woods.

Other Tools: Medical Scissors and Nitrile Gloves. The scissors are helpful in exposing wounds. The gloves are standard PPE.

The Pocket Version

Primary Tourniquet: SWAT-T. Given the size restrictions of the pocket kit, the SWAT-T is the way to go.

Primary Pressure Bandage: SWAT-T + any fabric you have on hand. The fabric can be almost anything: a handkerchief, COVID mask, part of your clothing, etc.

Wound Packing Aid: Any fabric you have on hand. This may be on your person, a handkerchief, Buff, t-shirt, etc. Or, this may be in your environment: a sheet, towel, table cloth, curtains, etc.

Chest Seal: improvised from the bag that holds the kit, and the included tape.

Hypothermia/Shock Treatment: S.O.L. Heatsheet Emergency Blanket.

Other Tools: Leukotape and Nitrile Gloves. The Luekotape is a terrifically useful item to have and has far more uses than just creating a chest seal.

Pro Tip: All the items I purchased for the above kits were reimbursed when Shira submitted a Flex Spending Account (FSA) claim. She classified both styles of tourniquets, the chest seals and the the Israeli bandages under the section pertaining to bandages.  So if you find yourself with extra FSA bucks at the end of the year, it's a safe bet to put some of that money towards emergency medical supplies.

Thursday, February 25, 2021

Preparing for the Unthinkable, Part 1- Until Help Arrives Training

Last week Shira and I took Arlington County's Until Help Arrives (UHA) training. This is one of the County's strategies for preparing for mass casualty events, like shootings, terrorist attacks and other unthinkable events.

UHA focuses both on mindset and skills. The skills are designed to deal with a handful of medical conditions that meet two essential criteria: they're deadly and they can be easily treated. This all took place over Zoom, though I assume we'll follow-up with in-person training in the future.

I recommend the course, and think it compliments CPR and other first aid training well. It gives you specific skills you can use in a crisis without information overload.

If you're on the fence as to whether this training is for you, or you're not in Arlington County, I'd suggest watching the prerequisite video. This isn't a replacement for the class, but does provide a significant amount course material and is well done. I've embedded the video below.

Another resource to check out is the Tactical Emergency Casualty Care (TECC) Guidelines for Active Bystanders. UHA is based on these guidelines. You could assume, like I did, that a document with such a verbose name would be a dense and complex read. You'd be wrong. The TECC Active Bystander Guidelines is essentially a 3 page, bulleted list of what a non-trained individual can do to help in a mass casulty event. It's quite accessible.

Whenever I attend training like this, I try to take action afterwards to cement my learning. I'm currently listening to the book, Unthinkable: Who Survives When Disaster Strikes - and Why by Amanda Ripley that they recommend during the session. I've updated my First Aid Cheatsheet to include the TECC recommendations. Heck, I'm publishing this blog post. However, the main action I've undertaken is to organize a trauma kit. That's the subject of part 2 of this series; stay tuned.

Update: your wait over. Check out the trauma kit post.

Wednesday, February 24, 2021

Review: Rogue Protocol, Exit Strategy and Network Effect

I binge read (well, listened to) the three remaining books in Martha Well's Murderbot Diary series Rogue Protocol, Exit Strategy and Network Effect. I did so with the gusto normally reserved for binge watch a TV series I'm hooked on. While the books have similar plots, the characters and scenarios were different enough that I never tired of them. What a joy it is to find a series that you just can't put down.

I continue to be impressed by the diversity that the Wells brings to her characters, both human and non-human. At times, it's bots that show brilliance and empathy and at other times it's humans that do so.

I kept thinking back to the TV show The Good Doctor. Murderbot and the protagonist of the TV Show, Dr. Shaun Murphy, are brilliant problem solvers with super human gifts, yet they can be stumped by the simplest of human interactions. One feature of the TV show is that they manage to show Murphy in both these lights, casting a truly fascinating shadow on those around him. There may be times you may feel sympathy for Murphy and wish he could be 'normal', though just as often you feel sorry for his co-workers who are, alas, just 'normal.'

Wells brings this same dynamic to the Murderbot, and it adds real depth to the characters.

I noted in my review of Artificial Condition that Wells cleverly highlights ethical questions about AI and cloning. I say cleverly, because she manages to wrap these very thorny problems in a veneer of fun and adventure. I liked how she continued this practice in the three remaining books I read.

I don't recall which book it was, but at one point one of the humans explains to Murderbot that her ancestors, like him, arrived on their planet packed in the cargo hold. I found this exchange to be incredibly powerful, as it brought to mind images of slave ships and their implication.

Like all good series, I was both pleased and bummed to finally finish it. Though, I see from looking up the books on Amazon that there's a 6th book coming out. I can't wait!

Monday, February 22, 2021

A Tasker based OPM Status Monitor

The DC Area's 'Snowday For Adults' Indicator

The DC area just endured another bust of a winter storm. While we hoped and prepared for inches of snow, we got sleet instead.

One indicator for how extreme upcoming weather will be is the Federal Government's Office of Personnel Management's (OPM) status page, opm.gov/status. The directives on this page tell Federal employees when they need to report to the office. At a minimum, it impacts something like 280,000 local residents.

When the OPM status page announces that the government is closed, thousands of people stay home and adults get to bask in the joy of hearing there's a snow day. On a practical level, you can expect many local organizations will follow OPM's lead and be open or closed for the day.

During this last winter-storm event I learned that not only does OPM have a web page and app to check the current status, but they have an API endpoint as well. I wasn't quite sure what I could do with this API, but I couldn't resist doing something with it.

OPM Alerts in Tasker

One easy way to experiment with the API was to leverage Tasker. I used a now familiar pattern to do so.

  1. I used the HTTP Request action to invoke the API at https://www.opm.gov/json/operatingstatus.json.
  2. I used the JavaScriplet action to parse the JSON response from the API, storing the relevant information in local variables.
  3. I used standard Tasker actions to process this parsed data, including: checking to see if the status had changed since I last checked it, using the Say action to read the new status aloud, and using the Notify action to a trigger a notification.
  4. I created a profile that runs every two minutes throughout the day to invoke the task described above.

All of this came together surprisingly easily. My plan is to leave the profile that queries OPM off, and turn it on next time we have a storm on the horizon. I should then be notified if and when OPM decides to change their status.

You can grab the code for the OPM Monitor Profile and Task from TaskerNet.

But Does it work?

I should have to wait until the next big snow to know if the OPM Monitor above works. However, last Saturday morning at 12:07am, I was dramatically awoken by my phone making an announcement:

OPM Alert: STATUS: OPEN WITH MAXIMUM TELEWORK FLEXIBILITIES TO ALL CURRENT TELEWORK ELIGIBLE EMPLOYEES, PURSUANT TO DIRECTION FROM AGENCY HEADS

Apparently I had left the profile active, and the OPM site was resetting its status back to 'open.' So yeah, the profile works. Actually, it may work too well. I may need to adjust it so that between certain hours of the day it doesn't make a verbal announcement.

Thursday, February 18, 2021

Dialing Up The Romance on My iPad Love Note Delivery System

One could argue that my Tasker / iPad powered Love Note Delivery System lacked a certain romantic spark. But I've fixed that. Check out v2:

It looks awfully similar to version 1 with one important difference. In the bottom right hand corner there's a number (in the snapshot above, it reads 17). That's the magic, right there.

I updated the Tasker code so that before the love note is sent to the iPad it's recorded in a Google Sheet. This change means that notes aren't just displayed on the iPad, but recorded for posterity. If I keep my love note authoring up by the end of the year I should have hundreds of them. Perhaps I'll compile them into a book or poster to be delivered for next Valentine's Day.

Recording data in a Google Sheet from Tasker is a problem I recently tackled, so that part of the code was trivial. What was new territory was deriving the sequence number to show in the bottom right hand corner.

My first plan: query the Google Sheet using the GSheets API to determine how many data rows are in the current spreadsheet. Alas, I couldn't find an API endpoint that returned this information.

I could pull back all the love notes currently stored in the Google Sheet and count them, but that's inefficient and would get more so as I added notes.

Fortunately, there was an easy solution: after appending rows to a sheet the API returns back a JSON object reporting what changed. From this, I can determine which row was inserted into the spreadsheet, and from there I can determine the sequence number.

I updated my GSheets Append Row task to return the server's API response. I then parsed this response like so:

var r = JSON.parse(results);
var seq = r.updates.updatedRange.replace(/.*[A-Z]/, '') - 1;
var payload = JSON.stringify({
  message: message,
  sequence: seq
});

(Find the complete Tasker code here)

I've updated the web page the iPad uses to receive messages to parse the payload structure described above. In it, it now finds a sequence value which it can proudly display in the bottom right hand corner of the screen.

Now if you'll excuse me, I better get writing. These love notes aren't going to compose themselves!*


*Or could they? Note to self: investigate machine learning strategies to generate love notes.

Wednesday, February 17, 2021

Just A Bit Overengineered: A JavaScript Based, MQTT Powered, iPad Love Note System

The Inspiration: Itty Bitty Sidekick Screens

A few months ago, on a trip to to Best Buy I found myself impressed with their prices tags. Not their prices, but the tags themselves. Apparently, Best Buy replaced old school plastic tags with E-Ink displays. I left that store wondering if I could get my hands on my own little E-Ink display and use it to power a project.

Fast forward to this last weekend where I read about two more e-ink based projects: an Always On E-Ink Org Agenda and a Literary Clock Made form an E-reader. The second project is especially interesting because it describes repurposing an old Kindle rather than building an e-ink project from scratch.

I didn't have a Kindle lying around, but I did have an iPad that wasn't getting much use. It wasn't an e-ink display, but I wondered if it could stand in for one.

And then Valentines Day arrived and I realized what I could use a sidekick screen for: as a message board for leaving love notes to my beloved.

Did I need to turn an iPad into the equivalent of a digital Post-It note? Uh yes, yes I did.

Wait a Minute, Didn't I Just Do This project?

I just got done building PimgStack a project to recycle hardware by using my phone to deliver messages to a device that would display said messages on screen. Isn't this the same thing, but instead of using a Raspberry Pi I'm using an iPad, and instead of delivering images I'm delivering love notes? Yes and yes.

So if they're basically the same project, I found myself wondering if I could recycle any code to make my iPad project come together faster.

Powering an iPad Love Note Delivery System

What made PimgStack work was the message delivery mechanism, MQTT. This left me wondering how I could push MQTT messages to an iPad, and once they arrived, how I could react to them. I got an important clue when I setup the MQTT broker on Amazon's MQ service. Check this out:

Amazon's MQ service provides multiple connection points that talk different protocols. In my Raspberry Pi project I made use of MQTT. A quick Google Search revealed that 'wss' is the Secure Web Sockets protocol. I'd never used Web Sockets, but I figured they must be built into the latest version of Safari running on the iPad.

The Plan: Web Sockets talking to Amazon MQ

If I could write a web page that opened up a Web Socket to talk to Amazon MQ's WSS endpoint, then I'd be home free. It could listen on the socket for messages and when they arrived, render the message in HTML. The page would then repeat the process, waiting for another message to arrive.

I whipped up a quick web page, attempted to open up a WebSocket using the WSS URL and ... nothing. What I'd hoped would Just Work, didn't. As I Googled around, I started to get the impression that it wasn't possible to just connect up to a WSS URL from plain JavaScript.

Fortunately, I kept digging and eventually found Paho.MQTT, a library for bridging WebSockets and MQTT. I updated my simple HTML page to pull in the Paho.MQTT library from this CDN:

  <script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.min.js" type="text/javascript"></script>

I then plugged in the WSS host, port, username and password from the Amazon MQ connection area into this code:

var client = new Paho.MQTT.Client(Env.host, Env.port, Env.path, Env.clientId);

client.onMessageArrived = function(m) {
  console.log("Message arrived!", m);
};

client.connect({
  userName: Env.username,
  password: Env.password,
  useSSL: true,
  mqttVersion: 3,
  onFailure: function(e) {
    console.log("FAIL!");
  },
  onSuccess: function() {
    console.log("WHoo!");
    client.subscribe(Env.topic);
  }
});

After punching a hole in the security group to allow 61619, I found that the socket would successfully connect. I wrote a quick Tasker action using the same MQTT Client that I used in my last project, and sent off a message. And Bam! the message was received on the iPad. It was like magic. Damn Paho.MQTT is cool.

I updated the code above to render the message in HTML, picked a sexy font from Google Fonts and made sure to set the meta tag apple-mobile-web-app-capable. The result is a full screen message board which looks surprisingly polished. As a bonus, emoji seamlessly come through. How cool is that?

I whipped up a quick Tasker action that lets me push text to the iPad. I realized that because this is backed by Tasker, any automation I can imagine on my phone can now use the iPad as a display. Say I want to display my step count on the iPad or the last received text message or last incomning phone number. This would all be pretty easy to do now that I've got the iPad listening on an MQTT channel that Tasker can publish to.

Find the source code that powers the iPad component here. It really is amazing how much functionality you can get out of a little plain HTML web app.

Now if you'll excuse me, I need to go compose sweet nothings to my wife.

Friday, February 12, 2021

PimgStack Part 3 - A Tasker Based Phone UI

I have my stack based digital photo frame, PimgStack, nearly functional. The Raspberry Pi side of the equation is working well. I can send it image stack commands and have it react by showing the relevant photo on screen. All that was left to do was to create a simple Android app for sending images and stack commands to the Pi.

Tasker, Of Course

My go to strategy for building uber-lightweight mobile apps is to turn to Tasker. I tackled building this app in 3 steps.

Step 1. MQTT Communication

I created a Task that pushes messages to PimgStack by publishing an MQTT message. This may sound tricky, but was actually quite simple. There are a number of MQTT Tasker plugins already written, so I all had to do was pick one, fill in the message parameters and I was done.

I first built my Task around the MQTT Publish Plugin, but found that it wasn't reliable. It seemed to randomly fail to publish messages. I then switched to MQTT Client and now message sending is realiable.

Step 2. Core Tasks

I Implemented Tasks that correspond to each of the PimgStack operations: push, pop and clear. Pop and clear are trivial because they are static messages. Push is trickier, because it works in terms of a URL. But what if I have a local file and not a URL?

I solved this issue by falling back on another project of mine: 3shrink. I've setup 3shrink so that I can turn any file on my phone into a URL. You can see that I'm using this service to translate anything that doesn't look like a URL into one:

Step 3. Add Shortcuts

Finally, I needed a hassle free way to invoke the tasks above. For the Pop and Clear Tasks I used the Tasker Widget to add 1-click shortcuts to my home screen. They are now a press away from being executed. Push is trickier, as it requires an associated image or URL to an image to operate.

The heavy lifting to solve this problem is done by AutoShare. AutoShare connects a Task with the Android Share menu. From my photo gallery, for example, I can share a pic with the 'pimgstack:push' AutoShare command. When I do this, the file is delivered to the Push Task. This Task detects that it's working with a file, converts it to a URL via 3shrink and then delivers the URL to PimgStack via an MQTT message.

And It Works!

It all works! There are definitely improvements to be made. The process of delivering PimgStack commands takes a few seconds, not the instant behavior I'd hoped for. But the system is stable, I've put an unused Raspberry Pi and Monitor to use, and I can beam images from my phone to a screen. I'm calling this a win!

Tuesday, February 09, 2021

PimgStack Part 2 (again) - The Joy of MQTT

My attempt at using AWS SQS for pushing messages to my Raspberry Pi based digital photo frame (aka PimgStack) was a bust. I needed a lighter-weight solution for pushing messages to the Pi. MQTT provides message passing services and does so while being featherweight, so I decided to give it a try.

The Proof of Concept

First, I headed to an AWS EC2 Linux server and installed and started mosquitto:

# Server IP: 192.168.1.213
$ sudo yum install mosquitto
...
$ mosquitto -v
1612876887: mosquitto version 1.6.10 starting
1612876887: Using default config.
1612876887: Opening ipv4 listen socket on port 1883.
1612876887: Opening ipv6 listen socket on port 1883.
1612876888: New connection from ... on port 1883.
1612876888: New client connected from ... as mosqsub|2145-pimgstack1 (p2, c1, k60).
...

I also had to update the server's security group to allow all traffic for port 1883. (This was a temporary measure, of course. Once my proof-of-concept was done, I turned this off.)

I then connected up to my Raspberry Pi, installed mosquitto-clients and kicked off the mosquitto_sub command.

$  sudo apt install mosquitto-clients
...
$ mosquitto_sub -h 192.168.1.213 -t foo/bar -C 1

-h connects to the server above. -t says to listen on topic foo/bar and -C says to listen for a single message and quit.

Running the above command just hung there. That was perfect.

Finally, I connected to a third Linux box, installed mosquitto there, and invoked mosquito_pub like so:

$ sudo yum install mosquitto
...
$ mosquitto_pub -h 192.168.1.213 -t foo/bar -m "Hello World"

To my shock and amazement, "Hello World" was printed on the Pi's screen and the command exited.

I had just demonstrated the pieces of the puzzle needed to power PimgStack.

The Real Deal

Now that I had a way to receive messages from the cloud, I needed to implement code to handle those messages. That was the easy part. You can see the entire source code for the PimgStack Raspberry Pi client here, however the interesting part is as follows:

while true; do
  message=$(mos_sub pimgstack/1)
  case $message in
    push:*)
      do_push $message
      ;;

    pop)
      do_pop
      ;;

    clear)
      do_clear
      ;;
    *)
      do_error $message
      ;;
  esac
  img_display
done

The above sets up an infinite loop to receive messages. The shell wrapper function mos_sub is invoked to receive messages. When a message arrives, it is trivially parsed. The stack operations 'push', 'pop' and 'clear' are defined, everything else is ignored.

Once the stack is modified, img_display is called and the top most image on the stack is shown. If the stack is empty, a placeholder image is shown.

At this point, I had a very clunky, but functional digital photo frame. The commands below push two images, displaying them each, then pops the top one off, showing the first image that was pushed.

$ mosquitto_pub  -h 192.168.1.213 -t pimgstack/1 -m push:https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjPoJ3YPU-CU74S9vR_dS0Qjon4BY8QB0aRgjToWVGEg6d3BM9DjugGBktFAODEGK19cAEtKVpfLs8SkIg7Phln7p8Romo2BPLJGMbjvzafBv21TLMljqsBxkGLATuioU-ZCipM/s4032/20200808_200622.jpg
$ mosquitto_pub  -h 192.168.1.213 -t pimgstack/1 -m push:https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgGAEMuswEi-6PRl80WPhyphenhyphenJ_QP7oAxxabWO-4qWTbY9lez-0sd5JfUtDHvzmKLghuaPu8vrTLFXY-c4ZJ5J2X3GflMPAUAqSiwX6yNoUg1SfbuAPT1kVtGgtMDIgPoae6rpZBCk/s4032/20200809_093032.jpg
$ mosquitto_pub  -h 192.168.1.213 -t pimgstack/1 -m pop

The Raspberry Pi side of the photo frame is now finished. Up next, I need to implement sending MQTT messages from my Android device. And then I'll be ready to push and pop images from my phone with ease.

Thursday, February 04, 2021

Staying Close To Home: Using AutoHotKey to Jump Between Screens

Like most programmers, I've got two monitors and a dislike for taking my fingers off the home-row. On my Mac, I was inspired to write a Keyboard Maestro script to hot-key jump between my screens.

On Windows, I got by Alt-Tab'ing. I've got a new Windows box, and in the spirit of experimentation, I wondered if I could write a similar screen-jumping script for Windows.

The obvious choice was to build this in AutoHotKey. A quick Google Search turned up a similar request. A bit of MsgBox experimentation revealed that MouseGetPos returns positive x values for 'Monitor 1' and negative for 'Monitor 2'. SysGet can be used to determine the bounds of a monitor and MouseMove can be used to, well, move the mouse.

Once I had this figured out, I was able to build a version of a ScreenJump that hopped the mouse between the monitors. Version 1 plopped the mouse pointer in the center of the screen:

#SingleInstance,Force
CoordMode,Mouse,Screen

;; Windows+J does the jump
#j::
MouseGetPos,X,Y
If(X > 0) {
  SysGet, M, Monitor, 2
  Zone := -1
} else {
  SysGet, M, Monitor,1
  Zone := 1
}
CenterX := ((MRight - MLeft) / 2) * Zone
CenterY := (MBottom - MTop) / 2
MouseMove CenterX, CenterY, 0
return

After a few days, I refined the above function to capture the mouse coordinate and then jump to the other screen. I use these captured coordinates as the destination to jump back to, rather than always jumping to screen-center. Additionally, I added logic to give focus to the window the mouse lands on.

This function, triggered by Windows-j has become so embedded in muscle memory I can't help but wonder how I lived without.  Such is the joy of keyboard shortcuts.

#SingleInstance,Force
CoordMode,Mouse,Screen

ScreenJump() {
  MouseGetPos,X,Y
  global LastM1X, LastM1Y, LastM2X, LastM2Y

  ;; X > 0 is one screen, X < 0 is another screen
  ;; Before we jump away from the screen, capture our
  ;; 'last' coordinates so we can return there.
  If(X > 0) {
    LastM1X := X
    LastM1Y := Y
    SysGet, M, Monitor, 2
    Zone := -1
    LastX := LastM2X
    LastY := LastM2Y
  } else {
    LastM2X := X
    LastM2Y := Y
    SysGet, M, Monitor,1
    Zone := 1
    LastX := LastM1X
    LastY := LastM1Y
  }

  ;; Do we know our last position?
  ;; Great, jump there. If not, go to the center of the window.
  if(LastX != "") {
    CenterX := LastX
    CenterY := LastY
  } else {
    CenterX := ((MRight - MLeft) / 2) * Zone
    CenterY := (MBottom - MTop) / 2
  }

  ;; Give Focus to the window the Mouse is hovering over.
  MouseMove CenterX, CenterY
  MouseGetPos,,,GuideUnderCursor
  WinGetTitle, Title, ahk_id %GuideUnderCursor%
  WinActivate, %Title%
  return
}

#j:: ScreenJump()

Wednesday, February 03, 2021

PimgStack Part 2 - An AWS Dead End

With my Raspberry Pi set up I was ready to move on to the next step of my stack based digital photo-frame project, aka, PimgStack. I wanted to tackle the question of how the Pi would pick up messages from my phone, or really any source, telling it which stack operation to perform next. One strategy I'd used in the past was to depend on AWS's Simple Queue Service (SQS). My phone, and other entities, interested in manipulating the PimgStack would drop commands into an SQS queue, and the Raspberry Pi would perform a long-poll to pick up these directives.

The AWS command line tool appeared to support pulling items from an SQS queue. So the plan was simple: install the AWS command line tool on the Pi and write a wrapper script to pull commands from a queue. Easy peasy.

AWS CLI - Strike 1

I downloaded AWS CLI version 2 from Amazon, unpacked it and ran the 'aws' command. No dice, the binary wouldn't run. Apparently, the Pi architecture wasn't compatible with the binaries Amazon provides. Undeterred, I moved on to plan B: I'd install version 1 of the AWS command. That version is Python based, which should work on a Pi.

AWS CLI - Strike 2

I ran sudo pip install aws and patiently waited for the command to finish. Instead of being successful, I got an error. The interesting part was:

    arm-linux-gnueabihf-gcc -pthread -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fno-strict-aliasing -Wdate-time -D_FORTIFY_SOURCE=2 -g -fdebug-prefix-map=/build/python2.7-InigCj/python2.7-2.7.16=. -fstack-protector-strong -Wformat -Werror=format-security -fPIC -DUSE__THREAD -DHAVE_SYNC_SYNCHRONIZE -I/usr/include/ffi -I/usr/include/libffi -I/usr/include/python2.7 -c c/_cffi_backend.c -o build/temp.linux-armv7l-2.7/c/_cffi_backend.o
      c/_cffi_backend.c:15:10: fatal error: ffi.h: No such file or directory
       #include <ffi.h>
                ^~~~~~~
      compilation terminated.
      error: command 'arm-linux-gnueabihf-gcc' failed with exit status 1

There were a number of recommendations for fixing this on web. Ultimately, I got around this error by insalling libffi-dev.

  sudo apt install -y libffi-dev

When aws was finally installed, I ran it and got this error:

$ aws ec2 help
Traceback (most recent call last):
  File "/usr/local/bin/aws", line 6, in <module>
    from aws.main import main
  File "/usr/local/lib/python2.7/dist-packages/aws/main.py", line 7, in <module>
    from fabric import api as fab
ImportError: cannot import name api

Turns out, I installed the wrong command altogether. 'aws' refers to a now defunct library. What I wanted to install was 'awscli':

$ sudo pip uninstall aws ; sudo pip install awscli

AWS CLI - Strike 3

I finally had the 'aws' command installed, but then ran into a new problem. Kicking off the 'aws' command took forever. And by forever, I mean over 4 seconds:

$ time aws
Note: AWS CLI version 2, the latest major version of the AWS CLI, is now stable and recommended for general use. For more information, see the AWS CLI version 2 installation instructions at: https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html

usage: aws [options] <command> <subcommand> [<subcommand> ...] [parameters]
To see help text, you can run:

  aws help
  aws <command> help
  aws <command> <subcommand> help
aws: error: too few arguments

real    0m4.224s
user    0m3.611s
sys     0m0.612s

That's over 4 seconds to do nothing more than print out an error message.

This tells me that while my Raspberry Pi can technically run the aws command line utility, it's really not the right tool for the job. I needed to stop thinking like a programmer working on a general purpose system and start thinking like a programmer working in a resource-limited embedded environment.

So if AWS SQS wasn't the right tool, what was? I had a vague notation that MQTT might help. All I knew about MQTT was that it was used to pass messages to Internet-of-Things devices, may of which have far fewer resources than the Pi. Time to get schooled in MQTT.