A Brief Introduction

I’m originally from the Chicago area but have called Seattle home since 2013. I work as a Software Engineer and in my free time, I enjoy cycling, running, and using photography as an excuse for exploring the PNW. I also poke around with web development, consulting for my dad’s business, DDM Garage Doors.

One of the authors of the Bible, Paul, writes, “If anyone speaks, let him speak as the oracles of God. If anyone ministers, let him do it as with the ability which God supplies, that in all things God may be glorified through Jesus Christ, to whom belong the glory and the dominion forever and ever” (1 Peter 4:11 NKJV). Though this web site is about me and who I am, my greatest desire is not that I look good but that it act as an instrument through whose music God uses me to proclaim Christ, His death and resurrection for the sins of the world, and the truths of His Word. My greatest desire is that my life would say with Paul, “For to me, to live is Christ, and to die is gain.” To learn more, I’d encourage you to read The Only thing in Life that Really Matters.

Please keep in mind that all content on my site reflects my own personal opinions, not those of my employer.

Below you’ll find my blog for posting updates in my life, changes to my web site, or just random thoughts going through my head. All posts since March 3, 2001 are available here. Enjoy!

Connecting the Concept2 Rower/PM5 to your Garmin Watch

About a year ago, I bought a Concept2 Rowing Machine but until recently have struggled to find the best way to connect it to my Garmin watch to record activities. It turns out the best way to connect your Garmin to the PM 5 is to use Concept2’s tools rather than Garmin’s.

I find this, by the way, incredibly disappointing; I feel like Garmin has really missed an opportunity to make this seamless!

To set this up:

  1. Create an Online Logbook at Concept2
  2. Connect your logbook with Garmin and/or Strava.
  3. Install the ErgData app on your phone
  4. Log in to your online logbook in the ErgData app.

Then, for each workout:

  1. Connect your heart rate monitor (either watch or chest strap) to the PM5.
  2. Connect the ErgData app to the PM5.
  3. Sync the workout from the ErgData app to your online logbook when you’re done!

Concept2 handles the rest and even uploads an “image” of the PM5 to accompany the workout on Strava! So cool! Bravo Concept2!

Photo

I initially tried connecting things by way of my Fenix 3 HR via the ErgIQ app:

PM5 ➡️ ErgIQ app (on watch) ➡️ Garmin Connect ➡️ Strava

Getting the watch / ErgIQ app connected to the PM5 is a little tricky, but when it works, it gives me rich set of data about the workout. This approach has two main problems:

  1. The ErgIQ app crashes at the end of every workout and sometimes during the workout. The resulting activity appears in Garmin Connect as a “Trail Run”; I can change it and see the ErgIQ metrics, but this is by no means a seamless experience.
  2. The ErgIQ app isn’t authorized to modify the “real” workout metrics, so collecting data is useless except for viewing it in the Connect app. I see two graphs of things like stroke rate. The “real” one looks particularly broken:


    This behavior is the same for heart rate as well! I’ve found wrist-based heart rate to be particularly inaccurate while rowing so I use the Garmin HRM-run monitor. Activities report the “real” HR using the wrist-based data and the other from ErgIQ (which has the HRM data from the PM5). This seems to happen whether I connect the HRM to both the watch and the PM5 or only the PM5.

    Then, when syncing to Strava, Garmin sends only the “real” data, so stroke rate, number of strokes, HR, calories, etc. all appear in Strava with incorrect values. One workout showed 34 calories burned over a distance of 0m, speed of 0s/500m, and an average HR of 69 bpm.

Another approach I tried was to use the built-in “Indoor Rowing” Fenix 3 app. This works well for getting accurate HR data in both Connect and Strava. The watch is able to figure out stroke rate reasonably well but I (obviously) miss out on the richer metrics around power, distance, etc.

The Concept2 integration solves all these problems. It’s “authorized” by Garmin, so Garmin reports a single set of metrics for the workout. Details about the intervals show up correctly in Connect. Strava’s view of the workout not only has the fancy image of the PM5 but reports all the data – distance, cadence, speed, heart rate, power – correctly.

My only complaint with the Concept2 integration is how it handles rest intervals. In both Strava and Connect, all the metrics (even heart rate) seem to drop off during rests. This is evident in both the tabular interval data visible in Garmin Connect as well as in the graphs in both Connect and Strava.

I suppose I can understand the problem for the non-HR metrics, but I find it especially surprising the HR drops off during this time.

All in all, I’m quite pleased with this integration. It saves me a whole lot of headaches in getting things set up for each workout, the data set is complete across both Strava and Garmin Connect, and I no longer need to take a photo of the PM5 and attach it to my workout.

If you’ve gotten this far and are wondering, “where’s the watch?”, you’re absolutely right! I rowed this afternoon without my watch, and got all these metrics. In this approach, the watch’s value is limited to providing HR data, which, again, I’ve found to be quite inaccurate. Would I buy my Fenix 3 HR again (or maybe the Fenix 5)? I’d consider it – but not for its usefulness as an HR monitor. The battery life is amazing, and it’s convenient to get notifications on my wrist.

Xenon bulb replacement on 2002 BMW 330ci (e46)

I noticed recently that one of the headlights on my 2002 BMW 330ci (e46) was dimmer than the other:

Remembering back to reading I’d done years ago, I initially was apprehensive about attempting a repair myself. The whole system is more complicated than a normal halogen setup, so I wasn’t even certain what the root cause was. Rather than repairing myself, I planned to have my mechanic take care of it for me. But, when I saw that the list price on GetBmwParts.com for the replacement bulbs was around $200 each, I feared I’d spend somewhere close to $500 for the repair, that is, if the bulbs were the issue. Absolutely absurd, especially considering replacing headlights on any other car has never cost me more than around $50 for the pair. In fact, on my Civic, the last time I replaced a bulb, it was $10.

So, I set out to figure out if I could troubleshoot the problem and fix it myself. One of the advantages of having a car that’s more than 15 years old is the abundance of content available online. I found a helpful thread on E46 Fanantics with troubleshooting instructions. The first step: swap the left and right bulbs to see if the problem followed.

But before I could do this, I had to figure out how to get at the bulb. I tried removing it without removing the whole headlight assembly but there wasn’t enough space to get the rear cap off the back of the bulb. So, I found a pair of videos on YouTube. First, for removing the headlight assembly:

And then for replacing the bulb:

When I removed the bulb from the side which was discolored / dimmer, I noticed a small crack and figured that the issue was, indeed, the bulb:

I swapped the driver’s side and passenger’s side bulbs to confirm, and the problem followed. Conclusion: the bulb needed to be replaced.

From my reading online (especially the E46 Fanantics post), I gathered that it’s ideal to replace the bulbs together to ensure consistent brightness – I guess the bulbs lose a little bit of intensity over time. So, I picked up a pair of new Philips D2S Xenon HID bulbs.

After installing them, everything looked great:

But then, as I was writing this post, I discovered that counterfeit bulbs are prevalent. I went to Philip’s web site to verify the authenticity of the bulbs. Although I had ordered both from Amazon, one of them registered as fake. But, I forgot on which side I placed each of the bulbs! Incredible! I tried to inspect the bulbs to discern for obvious signs of counterfeiting but nothing stood out. Reviewers on Amazon suggested that fake bulbs had caused them issues with the rest of the system, so I decided to return both and try again. Fortunately, Amazon’s return policy is extremely generous and I was able to return both bulbs. Replacements are on the way; pending authenticity verification, I’ll hopefully get these installed soon!

As a side note: A friend shared a helpful tip for re-installing the headlight assembly. There’s one screw that is particularly difficult to reach (the one Zach, from the video, uses a socket extension to reach). I don’t have a magnet in my socket set, so I was worried about dropping the screw into the ether. I used a small dab of liquid super glue on either side of the screw head, attached it to the socket, and let it dry. In my first couple attempts, one of the socket extensions released but left the socket attached. I removed the screw again and then wiggled it just enough so it could break free from the socket with a little force once it was screwed in. This did the trick, and I was able to reassemble without dropping the screw somewhere unreachable.

Road Bike Inner Tube Replacement

Last weekend while I was out enjoying Seattle’s beautiful weather, speeding down hills on my road bike, I had the inevitable misfortune of getting a flat tire. So, this week, for the first time in my life (at age 30!), I set out to learn how to change my bike tire.

There is certainly an abundance of tutorials for how to change your bike tire and inner tube, so I do not intend to reiterate existing material. Rather, I wanted to highlight some of the questions I had and share the answers I discovered in the hopes that, if you’re doing this for the first time, too, I may just save you a little bit of time.

I referred to the following two tutorials while changing my inner tube:

  1. Replacing bike tire and tube at Instructables.com
  2. Replacing bicycle tire at WikiHow.com

What tube should I buy?

The following factors are important in choosing a tube:

  1. Size (more on that later)
  2. Valve type – presta vs. schrader. I assume if you own a bike pump, you’ve already figured out the difference. Schrader is the type on my car, Presta was engineered to facilitate pumping tires to higher pressures and is the one I have on my bike.
  3. Valve length – based on the depth of your wheel. Unless you’ve got crazy deep rims, I expect 42mm is sufficient.

Tube Size

Bike Tire

The size of the tube you need depends on the size of your tire. You can find the tire size listed somewhere on the sidewall, just as you would on your car. As is depicted above, my tires indicate 700x25c, so I found a tube that fits tires size 20-25. According to customer input on Amazon, it’s preferable to buy a tube whose range is smaller rather than larger (i.e. 20-25 would be preferable to 25-32).

Of course, as with anything I buy from Amazon, I take a look at the customer reviews to make a final decision between competing options. There wasn’t an obvious choice, so I just ended up purchasing the Continental 42mm Presta Valve Tube, size 700 x 20-25.

Do I need to replace the tire too?

The general guidance as to whether your tire must be replaced seems to be based on the wear on your current tire. If you can see the threads in the tire, or if there are a lot of nicks in the rubber, it seems it would be time to change it. I decided not to change my tire; I had only about 600-700 miles on it, and it didn’t seem to be worn significantly, nor was there much damage to it. After removing it, I inspected it from the inside and concluded that I made the right decision. Once I need new tires, I intend to take a closer look at the Continental GatorSkin DuraSkin Tire, which appears to last a long time and protect the tube quite well.

Removing the tire

I found removing the tire to be the most difficult part of the job. One of the tutorials suggests using a screwdriver; I prefer to avoid using tools that have the opportunity to inflict permanent damage. (Same goes for removing those panels in the car interior). Plus, I already had two types of bike tire levers on hand.

The method I used was the two lever approach. I inserted the first lever and used its “hook” to attach it to one of the spokes and hold it in place.

The "other end" of the lever has a hook you can use to attach to one of your spokes.

I tried using another of these same levers, but I either had too much difficulty getting it underneath the tire, or else I could not slide it along the rim. I found the lever on the Topeak Alien II to have a slightly slimmer edge that was more effective at prying the tire from the rim.

Compare the two levers; the second is the Topeak.

Bike Tire Lever

Topeak Alien II Bike Tire Lever

Best wishes on your repair, and happy riding!

git push

I’ve spent some time recently messing around with (learning) git. I needed a better solution for tracking changes to the work I do for DDM Garage Doors, Inc., and git seems to be the leading choice these days.

As part of this exercise, I also moved my C++ Examples from high school over to Github. I’m not convinced there’s much value in keeping this code up online, other than nostalgia. In addition to the rather rudimentary nature of most of the assignments, I saw several optimizations I could make to the code and realized that most of my interview questions at Microsoft and Google required more complex solutions. In any case, as the first code I ever wrote in a strongly typed OO language, it holds a special place in my heart.

PayPal Fraud and Poor Customer Service

After several months of inactivity on PayPal, I logged in to my account today to confirm that my address was correct. I noticed that a second address had been added to my profile, one that has not been used in any payment I’ve transacted.

So, I called PayPal support and chose the “fraud” option. The first lady I spoke with was able to identify when the address was added, and that it was added as a gift address, but told me not to worry about it … that I must have added it when making a payment. I explained to her that it was not related to either of my *two* PayPal transactions (the most recent of which was in January). I eventually was able to speak with someone with further insight into my account, who explained that someone had gained access to my account, added the address, tried to make a payment, and they had detected it as fraud. I was advised to change my password and security questions.

Contrast this with the fraud against my Discover Card this past spring. My CC was first denied, they locked down my whole account, and I had to get a new one. The card was denied before Discover had a chance to reach out to me, but I did receive a delayed phone call AND email notification that I needed to contact them about my account, after I had already called in to address the issue.

PayPal, you failed on four counts:

  1. You didn’t notify me that there was fraud against my account, and that I should change my password to avoid it in the future. Instead, I had to notice the issue two months later, and call you.
  2. You let the fraudulent address remain in my account profile.
  3. You blocked the email notification I should have normally received indicating that an address was added to my account.
  4. Your customer service rep … the one I reached when I said I was calling about fraud … told me not to worry about it.

Uploading .htaccess with FileZilla and Zero-Byte Files

I’m running FileZilla 3.6.0.2 at the moment, and just ran into an issue uploading an htaccess file. Every time I uploaded it, it would appear to transfer successfully, but then the server would report a zero-byte file. I tried renaming the file, no luck. Finally, I got the file to transfer by switching my connection mode to Active instead of Passive. Go figure.

FileZilla FTP Settings Dialog

Looks like there’s a FileZilla update. I’ll install that in the hopes of avoiding the issue in the future.

Edit >> Same issue with 3.7.3. Oh well. Active it is. I’d switch to SFTP; Fluid Hosting sent us a note awhile back stating that SFTP was now enabled but every time I’ve tried it, I’ve gotten a connection failure.

Working around Litespeed’s mod_rewrite intermittent 404 Issue

I use mod_rewrite all over the place. Who doesn’t these days? IIS 7 even has a URL rewriting module that will convert your mod_rewrite rules. But I digress.

I noticed a few months back that I’d get an occasional 404 error for a page that I know exists and is handled by a mod_rewrite rule. Within a few seconds, the access logs reported a URL matching the same rewrite rule with a 200 OK response. If I hit the URL myself after receiving notification of the failure, it would work. I concluded there’s some sort of bug in Litespeed and escalated to the Fluid Hosting support folks. They suggested that they are “pretty sure that Litespeed developers are aware of the issue” and that it is a known issue because a Google Search for “htaccess rewrite problem litespeed” yields lots of results. Not so convinced myself; most of those results are people confused about how to configure mod_rewrite. In any case, my problem is still not resolved, and I continue to receive emails when these 404s occur. Probably a few each day. Not only would I like to reduce some of the noise, but I also want to avoid losing business if someone happens across a 404 error. So, this evening, I set out to create a workaround.

The hypothesis I’m testing with this solution is that the 404 error resolves itself automatically within about a second. The gist of the solution is to add a custom 404 error handler that spits out some Javascript that retries the request and, if it receives a 200 response, replaces the body of the page with that content. After a threshold is reached, it will stop trying and display an error message.

I initially tried implementing this retry on the server side to keep the client unaware of what was going on. Although I had a way around it, the risk of infinite recursion (and consuming all the server’s threads) was greater than I wanted to accept. (I.e. 404 error handler calls the target page, which calls my 404 error handler again. Certainly adding a header or query param to the request could help avert this.)

So … I’d share the entire code for this solution but it’s embedded in all the rest of my error handling mess that I’d rather keep to myself for now. I dream of polishing off every little pet project I’ve got, but in reality, I can’t see it happening anytime soon. So, I’ll give you an outline of what I did and hopefully you can adapt the solution to work for your needs. I did this with PHP but these concepts should transfer to any language.

Configure a Handler for 404 Errors

This is as simple as adding the following line to your .htaccess file. If you don’t know what htaccess is, this solution isn’t for you … because you couldn’t have really gotten in this mess without it … unless WordPress or some other tool configured the problematic mod_rewrite for you.
ErrorDocument 404 /404.php

Determine your retry criteria

I don’t want to retry every request. I decided to restrict retries to GET requests matching a certain set of URL patterns that I know are handled by mod_rewrite rules.

function ShouldRetryRequest()
{
	$retryPatterns = array(
		"/blog\/.*/",
		"/errorSimulator/",
	);

	// Only retry GET requests. Is REQUEST_METHOD ever not set?
	if (!isset($_SERVER['REQUEST_METHOD']) || $_SERVER['REQUEST_METHOD'] != "GET") {
		return false;
	}

	// Only retry if it matches our pattern
	foreach ($retryPatterns as $pattern) {
		if (preg_match( $pattern, $_SERVER['REQUEST_URI'] )) {
			return true;
		}
	}		

	return false;
}

Return 200 OK from your error handler

When you’ve decided you want to retry the request, you’ll want to be sure you return 200 OK from your script. Many modern browsers, when they see a 404 response, will return a “friendly” error message that tries to give the user an idea of what happened but probably only confuses him further. If you’re not retrying this particular request, go ahead and return 404. Returning 200 OK ensures your content will be displayed and executed. The other advantage to returning 200 OK is that if the search engine is the “user” who happens to hit this scenario, it won’t remove the page from its index … then again, it will have totally different content associated with that page, unless it evaluates and execute the Javascript I provide a little further on, so maybe we don’t really gain anything there.

You’ll notice that my Javascript retry code expects an error condition in order to retry; once it sees a 200 OK, it will simply display that content. If that content again is our error page with the retry script, you’ll get stuck in an infinite loop. So, be sure to continue to return a 404 for requests coming from the retry script. I’m simply returning 200 OK only when the attempt query param, added by the retry script, is not present. The web server, because it has reached my script through ErrorDocument, takes care of returning the 404 when the following condition does not match.

$retry = ShouldRetryRequest();

// Original GET/POST params are not passed to the error document, so can't use $_REQUEST
if ($retry && strpos($_SERVER['REQUEST_URI'], "attempt=") === false) {
	header('HTTP/1.0 200 OK');
}

Hide your <body> at page load

I’d recommend putting display:none on your <body> tag. The code below will either replace the content if the 404 issue resolves itself, or remove the display:none to show the error text if the maximum number of retry attempts is reached.

Return the script that will do the retry

Your error document needs to return the code that will do the retry. This is the bit of code I whipped up this evening. Whereas I typically prefer to put my scripts toward the end of the document, I wanted to get the first retry fired off as quickly as possible, so I put this near the top of my <head> tag.

I decided to use jQuery to take advantage of its friendly AJAX APIs, so you’ll want to be sure to include the jQuery script ahead of this one.

var retryCount = 0;
var maxRetries = 5;
// How long to wait between retries, in milliseconds
var retryFrequencyMS = 200;

var theUrl = window.location.href;
var params = { 
	attempt: retryCount, 
	log404: false // This tells my error handler not to do what it would normally do with this particular request
};


function retry() {
	retryCount++;
	$.ajax({
	  url: theUrl,
	  data: params,
	  success: function(html) {
		var newDoc = document.open("text/html", "replace");
		newDoc.write(html);
		newDoc.close();
	  },
	  error: function(jqXHR, status) {
		if (retryCount == maxRetries) {
			params['log404'] = true;
		}
		if (retryCount <= maxRetries) {
			params['attempt'] = retryCount;
			setTimeout(retry, retryFrequencyMS);
		}
		else {
			// Ensure body has loaded
			$(document).ready(function() {
				$(document.body).show();
			});
		}
	  },
	  dataType: 'html',
	  cache:false
	});
}
retry();

Testing the retry logic

I put together a second simple PHP file that simulates the intermittent 404 error. The first time it is hit, it returns our error handling code. Subsequent requests, it will respond with a 404 header, until a threshold is reached.

Success!

Finally, hit the simulated 404 script from your browser. Change the attempt threshold from 2 to a value above your retry count to simulate the behavior in which the server persists in returning 404s for all retry attempts.

http://localhost/errorSimulator.php

So … there it is. I’ve still got to put this code into production; it will be interesting to see if the volume of 404 error reports from this issue go down. I’m also interested in how long it takes for Litespeed to recover from this issue. I’ll provide updates if I find better values for the retry interval and/or maximum number of attempts.

Let me know how this works for you, if I can answer any questions, or if you spot any errors.

An Update…

Saturday, 2:20 pm PST – So, an hour or two after implementing the solution I described above, I got a report about one of these 404 errors. The script retried the page, but the server persisted in ignoring the mod_rewrite rule. Certainly I could introduce a greater delay between retries. But then it struck me … since this issue is occurring really with rules targeting a single script, why not use my error handler to forward requests to the script internally, instead of with a HTTP request. The script exists on the file system, so fake the PATH_INFO server variable, I include the script, and I’m in business.

Will continue to keep an eye on this and see how it works out.

Mt. Rainier at Midnight

Ellen recently suggested I take a picture of Mt. Rainier from our rooftop every day. (Or, on a cloudy day, where Mt. Rainier would normally be visible.) A few nights ago, I was going through the first months’ worth of photos and noticed that I can spot a faint shadow of Mt. Rainier in this shot I took around midnight on July 23rd.

Mt. Rainier is visible even at night ... if you look carefully