<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>EricDaugherty.com</title>
    <link>https://blog.ericdaugherty.com/</link>
    <description>Eric Daugherty&#39;s Blog</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Thu, 05 Mar 2026 03:13:01 +0000</lastBuildDate>
    
    <atom:link href="https://blog.ericdaugherty.com/index.xml" rel="self" type="application/rss+xml" />
    
    <item>
      <title>Site Redesign and Migration</title>
      <link>https://blog.ericdaugherty.com/posts/2026-03-05-site-redesign/</link>
      <pubDate>Thu, 05 Mar 2026 03:13:01 +0000</pubDate>
      <guid>https://blog.ericdaugherty.com/posts/2026-03-05-site-redesign/</guid>
      <description>&lt;p&gt;My old site design was getting a bit old and it was time for a cleanup. Now that AI is all the rage I used Claude Code to come up with a new site design and applied it.&lt;/p&gt;
&lt;p&gt;This is really 2 different sites&amp;hellip; &lt;a href=&#34;https://www.ericdaugherty.com&#34;&gt;www.ericdaugherty.com&lt;/a&gt; and &lt;a href=&#34;https://blog.ericdaugherty.com&#34;&gt;blog.ericdaugherty.com&lt;/a&gt;. www was hosted via GitHub Pages while blog was hosted using Google&amp;rsquo;s &lt;a href=&#34;https://blogger.com&#34;&gt;Blogger&lt;/a&gt;. I felt it was time to move to a simpler static blog setup, so I migrated that to Hugo, also hosted by GitHub Pages.&lt;/p&gt;
&lt;p&gt;For the www site, I just had to have Claude Code come up with a new design based on my very rough descriptions, and then I iterated on it with Claude until it looked acceptable.&lt;/p&gt;
&lt;p&gt;For the blog site, I needed to create a new repository, initialize a Hugo site, import all the blog posts, import the styling from the www site, and then throw them all together and fix any and all of the issues. When I say &amp;lsquo;I needed&amp;rsquo;, I mean &amp;lsquo;I prompted Claude to&amp;hellip;&amp;rsquo;.&lt;/p&gt;
&lt;p&gt;While this is hardly custom software development, my experience with the migration makes me more convinced that I will be using AI as my interface to all things technical. While it wasn&amp;rsquo;t perfect, especially with tweaking the visual layout, I accomplished the entire process in a few hours of prompting while multi-tasking.&lt;/p&gt;
&lt;p&gt;I realized the old site was still using FeedBurner, which has long been abandoned (although still functional), so the Subscribe link now points directly to the index.xml (Hugo&amp;rsquo;s RSS Feed) as does the Meta header. The old FeedBurner link has been updated to point to that as well.&lt;/p&gt;
&lt;p&gt;Let me know if you see any issues. I cleaned up some content and fixed some long-broken images but mostly it is just a fresh coat of paint and a new home.&lt;/p&gt;
</description>
    </item>
    <item>
      <title>Portable Power Pricing (or how I built something with AI)</title>
      <link>https://blog.ericdaugherty.com/2026/02/portable-power-pricing-or-how-i-built.html</link>
      <pubDate>Sat, 21 Feb 2026 23:47:00 +0000</pubDate>
      <guid>https://blog.ericdaugherty.com/2026/02/portable-power-pricing-or-how-i-built.html</guid>
      <description>&lt;p&gt;In my recent blog post&amp;nbsp;&lt;a href=&#34;https://blog.ericdaugherty.com/2025/12/surviving-great-psps-of-2025.html&#34;&gt;Surviving the Great PSPS of 2025&lt;/a&gt;, I outlined my interest in Portable Solar Generators, and I&#39;ve been looking for a side project to test out the latest AI capabilities. I found the answer in&amp;nbsp;&lt;a href=&#34;https://www.portablepowerpricing.com/&#34;&gt;PortablePowerPricing.com&lt;/a&gt;&lt;/p&gt;&lt;p&gt;I was recently looking to buy an additional battery for my&amp;nbsp;&lt;a href=&#34;https://us.ecoflow.com/products/delta-2-max-portable-power-station&#34;&gt;EcoFlow Delta 2 Max&lt;/a&gt;, but since all of these products are nearly always sold at a significant discount to their MSRP, I wasn&#39;t really sure how good of a deal it was. There are existing price trackers, but I realized there was another interesting aspect here. I wasn&#39;t just looking for the best price, but for the best price per Watt hour (Wh). I realized there was an opportunity to build a price comparison site specifically for this product category.&lt;/p&gt;&lt;p&gt;I primarily develop in VS Code using devcontainers, so that&#39;s what I did for this project. I ended up with 2 different projects, one to crawl the sites to get prices, and one for the public website. Each was a separate VS Code project (devcontainer) and git repo. I used Claude Code (Opus 4.5 and 4.6) via the VS Code plugin. I used the Claude Code Max (5x, $100/month) subscription, and only ran out of quota once during the project.&lt;/p&gt;&lt;p&gt;Throughout this project, I didn&#39;t write any code, or build any config files. I made some very minor edits by hand on occasion, but Claude Code wrote almost every line of every file in both projects.&lt;/p&gt;&lt;p&gt;I won&#39;t go through every step in the process, but I did want to highlight a few experiences and learnings...&lt;/p&gt;&lt;h2 style=&#34;text-align: left;&#34;&gt;Price Discovery&lt;/h2&gt;&lt;div&gt;Clearly a key aspect of this is capturing the prices for each product and documenting their changes over time. I started by asking Claude about potential tools for this, and it ended up recommending &lt;a href=&#34;https://github.com/clucraft/PriceGhost&#34;&gt;PriceGhost&lt;/a&gt;. This is a pretty new project that was also built almost entirely with AI. I liked the pitch that it could use AI to help figure out the right price instead of leveraging the &#39;old school&#39; approaches of parsing ever changing HTML pages.&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;div&gt;It worked fine for a little while, but as I added more sites and more pages I ran into issues. One was that this is a new project and the code isn&#39;t really battle tested. I had to fork the project and make some changes (I submitted a PR) to get it to work for some pages where the title was too long, but for others it just wouldn&#39;t work at all. I also found that the AI aspect of it wasn&#39;t really effective. I was using Ollama as my model, but it would not reliably find the right price on the page.&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;div&gt;I went back to Claude and discussed the challenges and we ended up settling on &lt;a href=&#34;https://github.com/dgtlmoon/changedetection.io&#34;&gt;ChangeDetection.io&lt;/a&gt;. This is a more established and traditional tool, but Claude told me that most (all?) of the sites I would be scraping used Shopify and we could often just pull the JSON file for each product and easily get the product without parsing HTML.&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;div&gt;But the real magic here was having Claude write a script that would automatically&amp;nbsp;setup the watches for me. It queried my production database, found the URLs of each product, used the ChangeDetection API to create a watch, and then updated the production database with the watch&#39;s ID so I could sync the prices.&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;div&gt;This is the kind of script that I could always write, but Claude Code did it in ~ 30 seconds and could make any changes needed to update, etc.&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;div&gt;But the real long term issue here is how difficult will it be to maintain the price scraping functionality? As a non-revenue generating side project, I will only have so much patience and it isn&#39;t quite so automated that I can just ignore it. That was the promise of&amp;nbsp;&lt;a href=&#34;https://github.com/clucraft/PriceGhost&#34;&gt;PriceGhost&lt;/a&gt;&amp;nbsp;but it isn&#39;t really there yet.&lt;/div&gt;&lt;div&gt;&lt;h2&gt;Product Discovery&lt;/h2&gt;&lt;/div&gt;&lt;div&gt;The most surprising discovery in this project was using Claude to build up the list of brands, products, and product relationships (which expansion batteries worked with which base stations). This is where the whole project would have fallen apart without AI. Realistically, I would not have spent the time to collect and format this information. It would have taken quite a few hours, and it is boring, mostly mindless work that is not justified for a side project.&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;div&gt;However, it was not perfect. I did discover a few minor issues it made, but it was able to self-correct them and do additional research instead of me having to go manually fix them.&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;div&gt;But this is where I really burned the tokens. Having Claude research websites and generate the product listings was very intense, and initially I would only add one brand per coding session. It often took &amp;gt;25% of my quota to research a single brand. In hindsight, I wonder if I should have used Sonnet for these tasks.&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;div&gt;The one time I did run out of tokens was when I had it research all of the brands and fix any mistakes it made, which it did do, but ran through the full quota.&amp;nbsp;&lt;/div&gt;&lt;h2 style=&#34;text-align: left;&#34;&gt;Coding&lt;/h2&gt;&lt;div&gt;Writing the code for the project was really the least surprising and interesting part. It is a pretty vanilla Next.JS app that uses Server Side Rendering (SSR) to query the data and build out the static content. I had no real issues getting it to build the pages I wanted, and each feature I added pretty much worked without issue.&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;div&gt;While I expected this to be the largest section of learnings and revelations, it was really just straight forward. I used Plan Mode to outline and iterate on each feature I wanted to add, and then once it was correct Claude Code just built it out without issue.&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;div&gt;I could have used projects like &lt;a href=&#34;https://github.com/github/spec-kit&#34;&gt;Spec Kit&lt;/a&gt;&amp;nbsp;to manage the requirements and features, and I&#39;d like to explore something like that for a future project, but for the scope of this project it was unnecessary.&lt;/div&gt;&lt;h2 style=&#34;text-align: left;&#34;&gt;Deployment&lt;/h2&gt;&lt;div&gt;As a side project, I didn&#39;t want to spend a lot of money hosting this, but I did want to put it out in the world, so I asked Claude about the best options for &#39;free&#39; plans and settled on &lt;a href=&#34;https://supabase.com/&#34;&gt;Supabase&lt;/a&gt;&amp;nbsp;and &lt;a href=&#34;https://vercel.com/&#34;&gt;Vercel&lt;/a&gt;. Claude walked me through the whole process of setting the accounts and deploying the projects. Going from running locally to fully deployed in the cloud took less than 30 minutes.&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;div&gt;I did pony up some cash for a real domain, in case it actually becomes something.&lt;/div&gt;&lt;h2 style=&#34;text-align: left;&#34;&gt;Conclusion&lt;/h2&gt;&lt;div&gt;While I embarked on this project to get more hands on experience using Claude Code to write custom software, the biggest surprise I found was how useful it was automating data collection and data entry tasks. Those are the kinds of things that cause a project like this to be abandoned because the &#39;fun part&#39; is over and there is just &#39;work&#39; left. Instead, the project remained interesting and enjoyable all the way through.&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;div&gt;&lt;div&gt;That said, I was still pretty impressed with its ability to write code. For green field side projects like this, I really don&#39;t see myself hand writing code anymore.&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;I also did discover another similar project called &lt;a href=&#34;https://whichwatts.com/&#34;&gt;WhichWatts&lt;/a&gt;, which seems like a more established and more feature complete version. It has also gone through the effort to commercialize it using Affiliate links. Although I do think my addition of comparing bundles (Base + additional battery combinations) makes it easier to compare target capacity by $/Wh.&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;div&gt;That does bring up an interesting final point. I think the cost to build a site like this is going to go down dramatically, but the effort to maintain and commercialize it will still exist. I expect we&#39;ll see a lot of cool software get built but abandoned because it will be too niche to justify the ongoing effort. That&#39;s always been true to a degree, I think it will just accelerate.&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;div&gt;Note: This post was written by a human, with editing feedback from Claude.&lt;/div&gt;
</description>
    </item>
    <item>
      <title>JVC Projector HDMI CEC Control</title>
      <link>https://blog.ericdaugherty.com/2026/01/jvc-projector-hdmi-cec-control.html</link>
      <pubDate>Sat, 03 Jan 2026 20:06:00 +0000</pubDate>
      <guid>https://blog.ericdaugherty.com/2026/01/jvc-projector-hdmi-cec-control.html</guid>
      <description>&lt;p&gt;I recently upgraded my projector to a 4k JVC model along with an upgraded AV Receiver (Denon). I previously used an old Harmony remote to control the whole system, but with the new components and the realization that I really only use the Apple TV as an input device, I wanted to simplify the remote setup.&lt;/p&gt;&lt;p&gt;The Apple TV and the Denon worked well together using HDMI CEC. This allows the Apple TV to turn the Denon AV Receiver on and off as needed, as well as to send volume up/down and mute signals over the HDMI cable.&lt;/p&gt;&lt;p&gt;However, the JVC Projectors do not support HDMI CEC, so I still needed the projector remote to turn it on and off. I could have re-programmed the Harmony remote, but they don&#39;t make them anymore, and the Apple TV remote is smaller and has all the functionality I need.&lt;/p&gt;&lt;p&gt;The JVC Projectors do support remote control over TCP/IP. I did some research and wrote a quick &lt;a href=&#34;https://github.com/ericdaugherty/jvcprojectorcontrol&#34; target=&#34;_blank&#34;&gt;Go library to control the JVC Projector remotely&lt;/a&gt;. There are good libraries in other languages and some mobile apps (see the README in that repo) but I didn&#39;t find any in Go.&lt;/p&gt;&lt;p&gt;The next step was to listen to the HDMI CEC bus to trigger the correct On/Off commands at the right time. That leads to the inspiration for this project. I came across &lt;a href=&#34;https://johnspecificproblems.net/posts/hdmi-cec/&#34;&gt;John Lian&#39;s post on HDMI-CEC&lt;/a&gt;. While he was addressing a different issue, it made me realize that I could build a &#39;bridge&#39; between the HDMI-CEC bus and the JVC Projector.&lt;/p&gt;&lt;p&gt;I used the broad approach John outlined by setting up a Raspberry Pi (Model 2 B+ in my case) and connecting it to my Denon AV Receiver. The &lt;i&gt;cec-client&lt;/i&gt; tool worked to see all the traffic on the bus, and it was fairly straightforward to identify the two different messages sent by the Apple TV I needed to listen to.&lt;/p&gt;&lt;p&gt;From there I wrote up a &lt;a href=&#34;https://github.com/ericdaugherty/jvcprojectorhdmicecbridge&#34; target=&#34;_blank&#34;&gt;simple go app&lt;/a&gt; that would run the&amp;nbsp;&lt;i&gt;cec-client&lt;/i&gt;&amp;nbsp;tool and parse its output. When it saw one of the two messages it used the Go library I wrote to turn the projector on or off.&lt;/p&gt;&lt;p&gt;I then set up the app to run as a service on the Raspberry Pi. So now to turn on (or off) the entire system I just need to turn the Apple TV on (or off).&lt;/p&gt;
</description>
    </item>
    <item>
      <title>Surviving the Great PSPS of 2025</title>
      <link>https://blog.ericdaugherty.com/2025/12/surviving-great-psps-of-2025.html</link>
      <pubDate>Sat, 20 Dec 2025 01:08:00 +0000</pubDate>
      <guid>https://blog.ericdaugherty.com/2025/12/surviving-great-psps-of-2025.html</guid>
      <description>&lt;p&gt;Colorado is having a dry winter, and over the past few days we&#39;ve had very high winds along the Front Range area, where I live. These conditions create a high risk for wildfires, as evidenced by the &lt;a href=&#34;https://en.wikipedia.org/wiki/Marshall_Fire&#34;&gt;Marshal Fire&lt;/a&gt; in 2021. Xcel instituted a Public Safety Power Shutoff (PSPS) event twice in 3 days to reduce this risk.&lt;/p&gt;&lt;p&gt;The first event started about 10a on Wednesday and lasted until Noon on Thursday (14 hours). The second event started at 6a on Friday and lasted into Saturday (over 24 hours).&amp;nbsp;&lt;/p&gt;&lt;p&gt;There are many ways to deal with an outage like this. Some of these include (from less to more advanced):&lt;/p&gt;&lt;p&gt;- Buy ice, candles. Low tech but it works&lt;/p&gt;&lt;p&gt;- Portable Batteries to power essentials&lt;/p&gt;&lt;p&gt;- Portable Generator to power essentials&lt;/p&gt;&lt;p&gt;- Whole Home Generator&lt;/p&gt;&lt;p&gt;- Bi-Directional EV&lt;/p&gt;&lt;p&gt;- Solar + Battery&lt;/p&gt;&lt;p&gt;While the ideal situation would be whole house solar with battery backup, for an uninterrupted supply of power, it is also an expensive solution that is only beginning to make economical sense if you can use it on a variable rate power plan to shift your daily usage. See &lt;a href=&#34;https://www.basepowercompany.com/&#34;&gt;Base Power&lt;/a&gt; in Texas.&lt;/p&gt;&lt;p&gt;A gas generator is a well tested and durable solution, either portable or a larger whole-house version. The downsides are that they require regular maintenance if not used regularly, they are noisy, and they burn gas.&lt;/p&gt;&lt;p&gt;While I do have (and love) my EV, it is not capable of bi-directional use. IE, I can&#39;t draw power from it other than to drive or through the cigarette adapter (generally limited to ~100W).&amp;nbsp;&lt;/p&gt;&lt;p&gt;What I do have is a portable battery and solar setup I primarily use to augment the onboard solar and batteries of my camper. But the setup also works well in this situation.&lt;/p&gt;&lt;p&gt;My Setup (Prices current of 12/2025, including sales):&lt;/p&gt;&lt;p&gt;- &lt;a href=&#34;https://us.ecoflow.com/products/delta-2-max-portable-power-station&#34;&gt;EcoFlow Delta 2 Max&lt;/a&gt; ($850)&lt;/p&gt;&lt;p&gt;- &lt;a href=&#34;https://us.ecoflow.com/products/400w-portable-solar-panel&#34;&gt;EcoFlow 400W Solar Array&lt;/a&gt; ($599)&lt;/p&gt;&lt;p&gt;- &lt;a href=&#34;https://www.renogy.com/products/400w-lightweight-portable-solar-suitcase&#34;&gt;Renology 400W Solar Array&lt;/a&gt;&amp;nbsp;($499)&lt;/p&gt;&lt;p&gt;- &lt;a href=&#34;https://us.ecoflow.com/products/800w-alternator-charger&#34;&gt;EcoFlow 800W Alternator Charger&lt;/a&gt; ($289)&lt;/p&gt;&lt;p&gt;The Delta 2 Max is a 2kWh portable battery with inverter. This means it can provide 2 kW of power for an hour (or 200 W for 10 hours). It can also charge from the solar panel arrays (up to 2) or the Alternator Charger (among other sources).&lt;/p&gt;&lt;p&gt;During the outage, I used extension cords to plug in our refrigerator, chest freezer, and networking gear (Internet, WIFI, etc), plus some phone/laptop chargers and lights.&amp;nbsp;&lt;/p&gt;&lt;p&gt;The networking components draw a constant ~100W. The Refrigerator and Freezer each draw about 100-200W while the compressor is running, and the rest are generally less than 100W.&amp;nbsp;&lt;/p&gt;&lt;p&gt;This means that the average draw was somewhere around 200-300W throughout the day.&amp;nbsp;&lt;/p&gt;&lt;p&gt;While the sun is out (which is pretty brief as we approach the winer solstice) I used the solar arrays to keep the battery charged. But since they days were short, and there was quite a bit of cloud cover, the 800W of solar panels averaged only a couple hundred watts, generally about break-even. It is important to remember that even in peak conditions, the panels generally don&#39;t deliver their advertised power. I think about 350W is the best I&#39;ve seen off either panel in the direct Colorado sunshine.&lt;/p&gt;&lt;p&gt;So for the rest of the day I utilized the 800W Alternator charger installed in our pickup truck (normally used to tow our camper). The Alternator Charger is an adapter that is hard wired to the battery terminals and outputs up to 800W to the EcoFlow battery. This means it is roughly capable of charging the battery (assuming no power draw) in about 2 1/2 hours.&amp;nbsp;&lt;/p&gt;&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;&lt;a href=&#34;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixbloz9v7RJcVSl2kAlKYibztRAPa_VTyMkrLbLAvvLgXrrS19ZikRByI5_icDp19iAmUrPxZ5NbAmbnP-hhqba-eK27nPYrdw7j_qrxSwMzET8cN7eLKqP4gDZPslIlskR_r3Vm5Uw78LlvwTRQ7_3IsGNIRDzq7kZXHv7PRXMjMEQJy_arXj_89wv8Q/s5712/IMG_2098.png&#34; imageanchor=&#34;1&#34; style=&#34;margin-left: 1em; margin-right: 1em;&#34;&gt;&lt;img border=&#34;0&#34; data-original-height=&#34;4284&#34; data-original-width=&#34;5712&#34; height=&#34;480&#34; src=&#34;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixbloz9v7RJcVSl2kAlKYibztRAPa_VTyMkrLbLAvvLgXrrS19ZikRByI5_icDp19iAmUrPxZ5NbAmbnP-hhqba-eK27nPYrdw7j_qrxSwMzET8cN7eLKqP4gDZPslIlskR_r3Vm5Uw78LlvwTRQ7_3IsGNIRDzq7kZXHv7PRXMjMEQJy_arXj_89wv8Q/w640-h480/IMG_2098.png&#34; width=&#34;640&#34; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;This setup kept all the essentials running through the outage. The battery did drain overnight, but for the few hours the power was out the refrigerator and freezer stayed cold enough.&lt;/p&gt;&lt;p&gt;I only had to run the truck to charge the battery in the evening after the sun set, and then again right before bed to tip it off for overnight.&lt;/p&gt;&lt;p&gt;Overall this was pretty successful. We didn&#39;t have the constant noise from a gas generator, although we did have the truck noise in the evenings. It kept the food cold and the family entertained during the outage. With the truck standing in as a gas (diesel) generator to top up the battery, this solution can provide power indefinitely.&lt;/p&gt;&lt;p&gt;Since the primary usage of this setup is for camping, it is a nice bonus as a home backup solution. From a cost perspective, it is pretty expensive compared to a portable gas generator. A &lt;a href=&#34;https://powerequipment.honda.com/generators/models/eu2200i&#34;&gt;high end portable Honda&lt;/a&gt; that can output much more power would cost about 1k (less than half of this solution), but it would have to be running the entire time you want power.&lt;/p&gt;&lt;p&gt;The whole house solutions are much nicer, but for the limited use cases (this is the first outage of this size I can remember in 15 years here) they are very expensive for the value.&lt;/p&gt;&lt;p&gt;For shorter outages, the EcoFlow Delta 2 Max by itself, or with a single set of panels does compare well to a gas generator for a silent, more environmentally friendly approach. But for longer outages like this one it requires the gas backup (in this case the Alternator Charger).&lt;/p&gt;&lt;p&gt;One limitation we did discover is that the battery backup for our internet provider is only about 12 hours, but luckily for us they added a gas generator during the second outage to keep everything running.&lt;/p&gt;
</description>
    </item>
    <item>
      <title>Go Web Server using Lambda Function URLs</title>
      <link>https://blog.ericdaugherty.com/2022/05/go-web-server-using-lambda-function-urls.html</link>
      <pubDate>Sat, 21 May 2022 18:45:00 +0000</pubDate>
      <guid>https://blog.ericdaugherty.com/2022/05/go-web-server-using-lambda-function-urls.html</guid>
      <description>&lt;p&gt;It has been possible to use Lambda Functions to host Go (Golang) Web Servers for years. I wrote about a &lt;a href=&#34;https://blog.ericdaugherty.com/2016/03/lamda-go-and-gotsport-via-api-gateway.html&#34;&gt;project doing this back in 2016&lt;/a&gt;. More recently, the &lt;a href=&#34;http://github.com/apex/gateway&#34;&gt;github.com/apex/gateway&lt;/a&gt; project provided a drop in replacement for net/http&amp;rsquo;s ListenAndServe function that supported &lt;a href=&#34;https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-rest-api.html&#34;&gt;AWS REST APIs&lt;/a&gt;, and later the &lt;a href=&#34;https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api.html&#34;&gt;AWS HTTP APIs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Amazon recently released the ability to access a &lt;a href=&#34;https://docs.aws.amazon.com/lambda/latest/dg/lambda-urls.html&#34;&gt;Lambda Function directly via URL&lt;/a&gt;, without needing to configure the REST APIs or HTTP APIs. This remove an extra layer of configuration, complexity, and potentially cost, for deployments.&lt;/p&gt;
&lt;p&gt;I created a fork of the &lt;a href=&#34;http://github.com/apex/gateway&#34;&gt;github.com/apex/gateway&lt;/a&gt; at &lt;a href=&#34;http://github.com/ericdaugherty/gateway&#34;&gt;github.com/ericdaugherty/gateway&lt;/a&gt; that supports this new approach. A trivial example of usage in the README is:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;package&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;log&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;net/http&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;os&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;github.com/ericdaugherty/gateway&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;http&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;HandleFunc&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;hello&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Fatal&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;gateway&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;ListenAndServe&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;:3000&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;hello&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;w&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;http&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;ResponseWriter&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;http&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Request&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// example retrieving values from the api gateway proxy request context.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;requestContext&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;ok&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;gateway&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;RequestContext&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Context&lt;/span&gt;())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; !&lt;span style=&#34;color:#a6e22e&#34;&gt;ok&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;requestContext&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Authorizer&lt;/span&gt;[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;sub&amp;#34;&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#a6e22e&#34;&gt;fmt&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Fprint&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;w&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Hello World from Go&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;userID&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;requestContext&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Authorizer&lt;/span&gt;[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;sub&amp;#34;&lt;/span&gt;].(&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;fmt&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Fprintf&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;w&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Hello %s from Go&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;userID&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is effective and addresses the issue, but can make local development a challenge. I often replace the main implementation above with:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// check and see if we are running within AWS.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;aws&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; len(&lt;span style=&#34;color:#a6e22e&#34;&gt;os&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Getenv&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;AWS_REGION&amp;#34;&lt;/span&gt;)) &amp;gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;http&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;HandleFunc&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;hello&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// run using apex gateway on Lambda, or just plain net/http locally&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;aws&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#a6e22e&#34;&gt;gateway&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;ListenAndServe&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;:8080&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	} &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#a6e22e&#34;&gt;http&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;ListenAndServe&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;:8080&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This uses the net/http implementation of ListenAndServe locally during development, and the gateway library when deployed via Lambda. No configuration is needed, as the AWS_REGION environment variable is &lt;a href=&#34;https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html&#34;&gt;automatically set by AWS when running as a Lambda Function&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;One limitation of the Lambda Function URLs is that you can only access the Lambda Function URL via the aws domain (ex: &lt;code&gt;https://&amp;lt;function id&amp;gt;.lambda-url.&amp;lt;aws region&amp;gt;.on.aws/&lt;/code&gt;). One easy solution to this is to use &lt;a href=&#34;https://aws.amazon.com/cloudfront/&#34;&gt;AWS CloudFront&lt;/a&gt;. For a simple use case, create a &lt;a href=&#34;https://aws.amazon.com/certificate-manager/&#34;&gt;SSL Certificate in AWS&lt;/a&gt; for your custom domain, and then create a CloudFront distribution, using the SSL Certificate and the AWS Function URL as the default origin. If you use this approach, you should also make sure to set the &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control&#34;&gt;HTTP Cache-Control Header&lt;/a&gt; on your cache-able responses to improve performance and reduce your Lambda invocations.&lt;/p&gt;
&lt;p&gt;You can use a more complicated CloudFront approach if your project uses a lot of static assets. The static assets can be deployed via S3 and the CloudFront distribution can pull from S3 or Lambda depending on the request path, but for smaller or low traffic deployment, everything can be served from the Lambda function.&lt;/p&gt;
&lt;p&gt;From here you can build your application using any traditional tooling that is supported by the Go http/net library.&lt;/p&gt;
</description>
    </item>
    <item>
      <title>Package Delivery Detector</title>
      <link>https://blog.ericdaugherty.com/2019/02/package-delivery-detector.html</link>
      <pubDate>Fri, 08 Feb 2019 16:37:00 +0000</pubDate>
      <guid>https://blog.ericdaugherty.com/2019/02/package-delivery-detector.html</guid>
      <description>&lt;p&gt;The following is an outline of how and why I built a tool to detect when packages are delivered to my front porch. If you have enough of the right infrastructure in place, this will serve as a guide to get it setup yourself. All the source code is available, so if your setup is different, you can take and modify what I&amp;rsquo;ve done to work for you.&lt;/p&gt;
&lt;p&gt;This solution is built using Go, AWS, Google Vision, and Docker.&lt;/p&gt;
&lt;h3&gt;
Motivation&lt;/h3&gt;
In November I attended Amazon&#39;s AWS re:Invent conference to catch up on the current state of the cloud. I came away from the conference inspired to leverage an area that I didn&#39;t have a ton of experience, Machine Learning. I attended several SageMaker sessions and came up with a project idea:
&lt;p&gt;A tool that would use Machine Learning to detect when a new package was delivered to my home.&lt;/p&gt;
&lt;p&gt;I already had a lot of key infrastructure in place, including a Ubiquity UniFi camera that was pointed at my front porch. I also had a Synology Diskstation with Docker that I used to run both my UniFi controller and UniFi NVR.&lt;/p&gt;
&lt;p&gt;This project took a long time. I kicked off work in early December and I just recently got to the point where I&amp;rsquo;m happy with the results.&lt;/p&gt;
&lt;h3&gt;
Image Capture&lt;/h3&gt;
The key to this project is to build a Machine Learning model that can determine if there is a package at my front door. To build this model, I needed a lot of input data, so I started with a small app that would take a snapshot from my camera every few minutes and save it.
&lt;p&gt;Luckily, UniFi cameras make it easy to grab a JPEG of the current frame. This is what my front door looks like right now:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhMv5UKVeEA3oLTMLwjDTG_W69LFVDhc3TfxSnZmo5hifQnLS7ftq9XbBI9Qw-509_u6HzzlScth-jgFShBlDPOBKRVV9tIbayDLCyvvm43CoI6_Wwy0rpB77IqCDT25OP0oH2Au_atbUM/s1600/snap1.jpeg&#34; imageanchor=&#34;1&#34;&gt;&lt;img border=&#34;0&#34; height=&#34;225&#34; src=&#34;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhMv5UKVeEA3oLTMLwjDTG_W69LFVDhc3TfxSnZmo5hifQnLS7ftq9XbBI9Qw-509_u6HzzlScth-jgFShBlDPOBKRVV9tIbayDLCyvvm43CoI6_Wwy0rpB77IqCDT25OP0oH2Au_atbUM/s400/snap1.jpeg&#34; width=&#34;400&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I was able to grab this image with a simple HTTP GET request to the camera&amp;rsquo;s IP. Example: http://192.168.1.5/snap.jpeg&lt;/p&gt;
&lt;p&gt;In order to enable this, I did have to turn on Anonymous Snapshots directly on the camera. Navigate to http://192.168.1.5/camera/config and click:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjo3Zlp3tkIR3zdrUXm4ULVIqRtB9YN5Mhc4ZXvKFT2qjRBr6SK2XL56DvozG62MdtD134XVQqGjGtzxWA8n1gUjjJHdt32P0YxWcfZRQxB_6czJzsZCWYjr6I_NEuCXaN2RBI-YJrfvIs/s1600/Screen+Shot+2019-02-07+at+11.11.05+AM.png&#34; imageanchor=&#34;1&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjo3Zlp3tkIR3zdrUXm4ULVIqRtB9YN5Mhc4ZXvKFT2qjRBr6SK2XL56DvozG62MdtD134XVQqGjGtzxWA8n1gUjjJHdt32P0YxWcfZRQxB_6czJzsZCWYjr6I_NEuCXaN2RBI-YJrfvIs/s400/Screen+Shot+2019-02-07+at+11.11.05+AM.png&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Since I would be running my image capture tool within my local network, I did not need to expose the camera to the Internet.&lt;/p&gt;
&lt;p&gt;While this camera placement works well for my every-day use, it contains a lot of extra data that isn&amp;rsquo;t relevant to detecting a package, so I needed to crop each image down to just the area where I thought packages might be placed. Here is the result of my crop:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7-LU4SP0PnthxE7S_D3q8lZWktPAOEo0LTUwLmRglBV1y0O0sb5fFY_q3B3h7GwRKsSRE4XkKVYwfpaRjDon8oRNqYfugo2xuvySkILa5TCo34fCZSMFiEMFBeIW1CekX7yfP3hIJ4TQ/s1600/2019-02-07T11%253A04%253A17-07%253A00.jpeg&#34; imageanchor=&#34;1&#34;&gt;&lt;img border=&#34;0&#34; height=&#34;301&#34; src=&#34;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7-LU4SP0PnthxE7S_D3q8lZWktPAOEo0LTUwLmRglBV1y0O0sb5fFY_q3B3h7GwRKsSRE4XkKVYwfpaRjDon8oRNqYfugo2xuvySkILa5TCo34fCZSMFiEMFBeIW1CekX7yfP3hIJ4TQ/s400/2019-02-07T11%253A04%253A17-07%253A00.jpeg&#34; width=&#34;400&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I now had everything in place to build out an app to start capturing images. I wrote the app in Go (golang) as it is perfect for this type of systems programming. The app simply polls the camera every 10 minutes, grabs the image, crops it (if desired), and stores it in an S3 bucket or on the local file system.&lt;/p&gt;
&lt;p&gt;The source code is available on &lt;a href=&#34;https://github.com/ericdaugherty/imagefetcher&#34;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I wrapped the app in a Docker container and deployed it on my Synology. The Docker image is available on &lt;a href=&#34;https://hub.docker.com/r/ericdaugherty/imagefetcher&#34;&gt;Docker Hub&lt;/a&gt;. Here is a sample Docker run command:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;docker run --restart always \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; -d \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; -e TZ=&amp;#39;America/Denver&amp;#39; \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; -v /volume1/imagefetcher:/img \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; --name imagefetcher \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; ericdaugherty/imagefetcher \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; -imageURL http://192.168.1.5/snap.jpeg \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; -dir /img \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; -sleepHour 22 \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; -wakeHour 7 \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; -rect 0,400,900,1080
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This creates and runs a new Docker container that captures the images, crops them, and stores them locally.&lt;/p&gt;
&lt;p&gt;Since packages are generally not delivered in the middle of the night, and the night images are much harder to see, I decided to only run the image fetcher (and eventually the package detector) from 7a to 10p.  So I added parameters to the ImageFetcher tool to stop capturing images overnight.&lt;/p&gt;
&lt;p&gt;The &lt;i&gt;-e TZ=&amp;lsquo;America/Denver&amp;rsquo;&lt;/i&gt; parameter sets the timezone for the Docker container. This is important so that the timestamps are correct and the logic to sleep and wake work correctly.&lt;/p&gt;
&lt;p&gt;The &lt;i&gt;-v /volume1/imagefetcher:/img&lt;/i&gt; parameter maps the directory on the Synology to /img in the container, and then later the &lt;i&gt;-dir /img&lt;/i&gt; specifies that the snapshots should be written to /img in the container, which will result in them being stored in /volume1/imagefetcher on the Synology.&lt;/p&gt;
&lt;p&gt;If you would prefer to store the images on S3, you can add these parameters:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    -e AWS_ACCESS_KEY_ID=&amp;#39;&amp;lt;Your Access Key ID&amp;#39; \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    -e AWS_SECRET_ACCESS_KEY=&amp;#39;&amp;lt;Your Access Key Secret&amp;gt;&amp;#39; \
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;to the docker run command and this parameter:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    -s3Bucket &amp;lt;bucket name&amp;gt; \
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;to the imagefetcher command. You can then drop the &lt;i&gt;-v /volume1/imagefetcher:/img&lt;/i&gt; and &lt;i&gt;-dir /img&lt;/i&gt;, or keep them both and store the images twice!&lt;/p&gt;
&lt;p&gt;Now you wait and capture data&amp;hellip; I started with a month&amp;rsquo;s worth of images before I trained the first model, but I continue to capture images and plan on training a new model with the larger set.&lt;/p&gt;
&lt;h3&gt;
Machine Learning Model&lt;/h3&gt;
&lt;div&gt;
You should now have a S3 bucket or directory full of JPEG images. Now comes the fun part, you need to manually sort the images into two different labels.&amp;nbsp; I chose the highly descriptive &#39;package&#39; and &#39;nopackage&#39; labels.&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
I use a MacBook, so I used finder to quickly preview the images and run through them until I saw a change of state. Then I moved all the images into either the &#39;package&#39; or &#39;nopackage&#39; directory. Repeat until you&#39;ve processed all of the images.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
This is pretty labor intensive, but it did go faster than I expected it.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
You should end up with two folders, named &#39;package&#39; and &#39;nopackage&#39;.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
I then spent quite a while trying to figure out how to train the model using SageMaker. I found this pretty frustrating as I&#39;m not really fluent in Python, and it turns out the Machine Learning space is pretty large and not super-obvious to pick up. Luckily, I came across a post from Jud Valeski where he was building a similar tool to &lt;a href=&#34;http://one.valeski.org/2018/12/machine-play.html&#34;&gt;determine when the wind blew the covers off his patio furniture&lt;/a&gt;. He used Google Vision for his solution, so I took a look.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
As it turns out, using Google Vision to build a simple model is drop dead simple. I signed up for Google Cloud account, created a project, and then created a new Google Vision model. To create the model, I simply had to zip up the 2 directories and upload it. In about 10 minutes, I had a functional model!&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
Google also provides you a HTTP Endpoint that you can use to evaluate images against your model. You simply post a JSON body including your image base64 encoded, and it gives you back what label matches, along with its confidence level.&lt;/div&gt;
&lt;h3&gt;
Package Detector&lt;/h3&gt;
With the trained model in place and a public endpoint I can hit, all that was left was to build the final tool.
&lt;p&gt;The final source code is available on &lt;a href=&#34;https://github.com/ericdaugherty/packagedetector&#34;&gt;GitHub&lt;/a&gt;. The Docker image is also available on &lt;a href=&#34;https://hub.docker.com/r/ericdaugherty/packagedetector&#34;&gt;Docker Hub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I lifted much of the logic from the imagedetector to grab and crop the JPEG image. I then wrote new code to base64 encode the image and upload it to Google Vision. Based on the response, if a package is detected, an email is sent out to notify me.&lt;/p&gt;
&lt;p&gt;The current version supports email as the notification tool.  I leveraged &lt;a href=&#34;https://aws.amazon.com/ses/&#34;&gt;Amazon&amp;rsquo;s Simple Email Service (SES)&lt;/a&gt;, but you can use any SMTP server you have appropriate access to.&lt;/p&gt;
&lt;p&gt;This tool supports two triggers. The simple approach is to specify a simple interval, ex: &lt;i&gt;-interval 5&lt;/i&gt; and it will check every 5 minutes. However, I realized that the Unifi NVR is already doing motion detection, so if I could trigger based on that, it would only evaluate when there was a reason to do so. I came across a cool project by mzak on the Unifi Community Forums. Here is the &lt;a href=&#34;https://github.com/mzac/unifi-video-mqtt&#34;&gt;GitHub Repo&lt;/a&gt;. Mzak realized that the NVR wrote a line to a log file (motion.log) every time motion was detected or ended. I leveraged his work to &lt;a href=&#34;https://github.com/ericdaugherty/unifi-nvr-motiondetection&#34;&gt;build a go library&lt;/a&gt; that would also monitor the log file. To use this, you must map the location of the motion.log file into the docker container. I do so with &lt;i&gt;-v /volume1/docker/unifi-video/logs:/nvr&lt;/i&gt; and then point the packagedetector at this location with the &lt;i&gt;-motionLog /nvr/motion.log&lt;/i&gt; parameter.&lt;/p&gt;
&lt;p&gt;You can run the docker using the following command:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo docker run \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    --restart always \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    -d \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    -e TZ=&amp;#39;America/Denver&amp;#39; \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    -v /volume1/packagedetector:/pd \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    -v /volume1/unifi-video/logs:/nvr \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    --name packagedetector \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ericdaugherty/packagedetector:latest \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    -imageURL http://192.168.1.5/snap.jpeg \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    -rect 0,400,900,1080 \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    -motionLog /nvr/motion.log \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    -cameraID AABBCCDD1122 \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    -gAuthJSON /pd/google-json-auth-file.json \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    -gVisionURL https://automl.googleapis.com/v1beta1/projects/... \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    -sleepHour 22 \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    -wakeHour 7 \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    -emailFrom test@example.com \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    -emailTo test@example.com \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    -emailServer email-smtp.us-east-1.amazonaws.com \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    -emailUser &amp;lt;amazon ses id&amp;gt; \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    -emailPass &amp;#34;&amp;lt;amazon ses password&amp;gt;&amp;#34; \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    -emailOnStart true
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I now receive an email every time a new package is delivered!&lt;/p&gt;
&lt;p&gt;Looking ahead, I&amp;rsquo;m interested in building in support for SMS or even push notifications, although I would also need to build an iOS app for that. I also plan on continuing to refine the model with additional images until I&amp;rsquo;m confident it will be correct nearly all the time.&lt;/p&gt;
</description>
    </item>
    <item>
      <title>Simple HipChat AddOn in Go</title>
      <link>https://blog.ericdaugherty.com/2017/03/simple-hipchat-addon-in-go.html</link>
      <pubDate>Fri, 17 Mar 2017 00:12:00 +0000</pubDate>
      <guid>https://blog.ericdaugherty.com/2017/03/simple-hipchat-addon-in-go.html</guid>
      <description>&lt;p&gt;We use HipChat at work to stay connected. It works well and has some fun plugins, including Karma. I&amp;rsquo;ll bet a few of you can guess what happens when you create a karma system in a company full of smart technical folks.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;THEY GAME THE SYSTEM.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;One of the more interesting hacks they discovered is to use the &amp;lsquo;find and replace&amp;rsquo; functionality built into HipChat to secretly give (or more likely take) karma to/from another person. The proper usage of the Find/Replace functionality is something like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;gt; I can typo good.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;gt; s/typo/type
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The first line would be a typo that you made, and the second line allows you to &amp;lsquo;fix&amp;rsquo; the typo. The key part here is that the second line modifies the first line and is not visible to any other users.&lt;/p&gt;
&lt;p&gt;Who could let this fun discovery go unused? Not our team! Our chats were quickly filled with Karma Ninjas giving and taking karma from behind a thin veil of secrecy.&lt;/p&gt;
&lt;p&gt;Luckily the &lt;a href=&#34;https://bitbucket.org/atlassianlabs/ac-koa-hipchat-karma&#34;&gt;Karma bot is open source&lt;/a&gt;. So I forked the source, made a fix and submitted a pull request right?&lt;/p&gt;
&lt;p&gt;&lt;b&gt;WRONG&lt;/b&gt;. Karma is written in JavaScript and writing JavaScript doesn&amp;rsquo;t make me happy. So I decided to create a Karma Ninja bot instead to &amp;lsquo;out&amp;rsquo; the folks attempting to be Karma Ninjas. Since I&amp;rsquo;ve been exploring Go on AWS Lambda recently, I figured this would be a great excuse to write more Go!&lt;/p&gt;
&lt;h3&gt;
Starting Points&lt;/h3&gt;
There are a few options out there already that really helped accelerate&amp;nbsp;the effort.
&lt;p&gt;First, the &lt;a href=&#34;https://github.com/eawsy&#34;&gt;eawsy&lt;/a&gt; folks created a great &lt;a href=&#34;https://github.com/eawsy/aws-lambda-go-shim&#34;&gt;shim&lt;/a&gt; for Lambda that allows you to deploy Go easily and it runs FAST. Their solution is much faster from a cold start than the other NodeJS shims out there.&lt;/p&gt;
&lt;p&gt;Second, Nicola Paolucci has a great post on the Atlassian Blog about &lt;a href=&#34;https://developer.atlassian.com/blog/2015/09/easy-hipchat-addons-in-golang/&#34;&gt;building a HipChat addon in Go&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Finally, there is a &lt;a href=&#34;https://github.com/davidrjonas/hipchat-addon&#34;&gt;HipChat Addon&lt;/a&gt; project by David Jonas on GitHub.&lt;/p&gt;
&lt;p&gt;The eawsy Lambda shim is being actively developed and the team was very responsive to a few of the issues/questions I had, including one that is &lt;a href=&#34;https://github.com/golang/go/issues/19529&#34;&gt;apparently a bug in Go 1.8&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The Atlassian blog post does a great job of walking through how to build an add-on. This was the template I ended up using for my addon. The only note I have here is that Nicola uses the roomId to store the authentication token, but that only works for single room installations. I used the OAuthId field instead which seems to work for both single room and global installations.&lt;/p&gt;
&lt;p&gt;David&amp;rsquo;s project did not compile out of the box because of changes in the JWT Token upstream project. I thought about trying to fix is, but ultimately I wanted to understand how it all worked so I wrote mine without the template. I may go back and try to fix that project as an alternate implementation.&lt;/p&gt;
&lt;h3&gt;
State&lt;/h3&gt;
When I was envisioning building the plug-in, I assume I could build it statelessly. I assumed you could simply register a webhook with a regex and a callback URL that would return a message. But the webhook does not appear to let you return a message, you need to make a separate call into the HipChat API to post a message. Because of this, you need to maintain some state between calls.
&lt;p&gt;&lt;i&gt;EDIT: My first guess was correct, you can return a response to the webhook call and post a message. I ended up over-complicating it but the point was to learn more about AWS so it was still time well spent. Here is info on how to &lt;a href=&#34;https://www.hipchat.com/docs/apiv2/webhooks&#34;&gt;pass a response to the webhook&lt;/a&gt;.&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;I explored using both DynamoDB and S3 to maintain the needed state, which is really just an OAuth Token. They both worked fine but ultimately I chose to use S3 as it was the simpler and cheaper option for this use case.&lt;/p&gt;
&lt;p&gt;Amazon does publish an &lt;a href=&#34;http://docs.aws.amazon.com/sdk-for-go/api&#34;&gt;AWS SDK in Go&lt;/a&gt; which made interacting with DynamoDB and S3 pretty easy. I do think the SDK is a bit &amp;rsquo;low level&amp;rsquo; and could probably be made a lot easier to use with some convenience functions, but I&amp;rsquo;m just happy they support Go so I shouldn&amp;rsquo;t complain.&lt;/p&gt;
&lt;p&gt;The eawsy team does the necessary work to make the Lambda environment available from Go, meaning that you can simply use the Go SDK without any explicit authentication credentials or setup. By default, you simply inherit the IAM Role of the Lambda function. In this case, I simply needed to add permission to access DynamoDB and Se.&lt;/p&gt;
&lt;h3&gt;
Code&lt;/h3&gt;
The code itself is pretty straightforward. Like most go projects, I&#39;m always impressed with how much you can get done in just a few lines of code across a handful of files. I published the project on GitHub:&amp;nbsp;&lt;a href=&#34;https://github.com/ericdaugherty/hipchat-karmacop&#34;&gt;ericdaugherty/hipchat-karmacop&lt;/a&gt;.
&lt;h3&gt;
AWS&lt;/h3&gt;
This code does require some AWS setup. You need to define a Lambda function and build and upload the project using the Makefile (see the README for instructions).
&lt;p&gt;You also need to setup an Amazon API Gateway entry to reference your AWS Lambda function. This will give you a public URL that your AddOn will reside at.&lt;/p&gt;
&lt;p&gt;An S3 bucket is required to store the OAuth token used to post responses back to the HipChat API.&lt;/p&gt;
&lt;p&gt;Finally, you need to add permission to read and write to S3 to the Lambda IAM Role configured for the Lambda Function.&lt;/p&gt;
&lt;h3&gt;
That&#39;s It&lt;/h3&gt;
&lt;div&gt;
This was a fun little project and shows a good use case for using Lambda to implement add-ons to cloud applications like HipChat.&lt;/div&gt;
</description>
    </item>
    <item>
      <title>How and Why I Became a &#39;Manager&#39;</title>
      <link>https://blog.ericdaugherty.com/2017/03/how-and-why-i-became-manager.html</link>
      <pubDate>Thu, 16 Mar 2017 03:05:00 +0000</pubDate>
      <guid>https://blog.ericdaugherty.com/2017/03/how-and-why-i-became-manager.html</guid>
      <description>&lt;p&gt;After my previous post about &lt;a href=&#34;http://blog.ericdaugherty.com/2017/03/technical-interviewing-and-hiring.html&#34;&gt;my approach to technical interviewing&lt;/a&gt;, I received some requests to write more about my career path. In this post, I&amp;rsquo;m will attempt to answer a question I get asked somewhat regularly: Why did I stop writing code and become a manager?&lt;/p&gt;
&lt;h3&gt;
How did I choose Software?&lt;/h3&gt;
Before we can dive into why I became a manager, we need to explore a bit more about who I was. Growing up, I loved Legos. I would spent hours building custom lego creations. As I got older, my family got a Commodore 64 and I spent hours playing cartridge games, watching Zaxxon fail to load from cassette tape, and typing in programs printed in computer magazines.
&lt;p&gt;While I was in High School, I ran a dial-up BBS on a computer made from parts cobbled together from old computers and occasional purchases from Computer Shopper. I took all three classes in our computer lab at the High School: Typing, BASIC Programming, and Computer Drafting. Typing was the most important of those three. When I exhausted every class at the High School, I took a Turbo Pascal class at the local community college. If I wasn&amp;rsquo;t already convinced (I was), after that I knew I wanted to write software for a living.&lt;/p&gt;
&lt;p&gt;I went to the University of Illinois to study Computer Science. Studying Computer Science at UIUC was the fulfillment of a dream for me, but I quickly made a small change.  During my Freshman year I switched to Computer Engineering. This was precipitated by my experience with the Introduction to Algorithms class. It involved way too much math and theoretical thinking. I just wanted to learn was how computers worked. The Computer Engineering program offered a lot more low-level classes and less theoretical programming classes. My favorite class was our microprocessor design, where we started with basic logic gates and built up to a pipelined processor, including writing a program and running it on the processor. My second favorite class was x86 Assembly programming. I was happy with my choice to switch to Computer Engineering, and it put me in the position I dreamed of as a kid, I got to get paid to write software.&lt;/p&gt;
&lt;h3&gt;
Did I like Software?&lt;/h3&gt;
After graduating I started working for a consulting company. They did a great job of training new college graduates with a well-defined self-directed training course in C++. That was, practically speaking, the first and last time I got paid to write C++. The world was quickly transitioning to the web and Java. I was among the first members of our staff to learn Java for new projects, and being a primary Microsoft shop, a little J++ as well.
&lt;p&gt;It was amazing. I was learning something new every day, and getting to build applications that real people were using every day. It was also when I first learned to dislike process and management.&lt;/p&gt;
&lt;p&gt;This is probably best exemplified by a quick story. On the first project I had an opportunity to lead, I was asked to document the object model. According to our process, this involved opening up Microsoft Word, and defining each class and public method, including method signatures and JavaDoc formatted documentation in Microsoft Word. I thought it was a complete waste of time, I was frustrated that management didn&amp;rsquo;t understand software development, and I just wanted all the &amp;lsquo;useless&amp;rsquo; people to get out of the way and let me build software.&lt;/p&gt;
&lt;p&gt;I had another experience that furthered my distain for anyone other than developers. We were implementing Enterprise Application Integration (EAI) software written by Active Software for one of our clients. This involved moving data between several different enterprise systems. The pattern we used was to define a single message format for each data type that would contain all the data that could be consumed by every end systems. Active Software called this a &amp;lsquo;Canonical Message&amp;rsquo;. However, apparently the client didn&amp;rsquo;t understand what the world &amp;lsquo;canonical&amp;rsquo; meant, and I spent the better part of an afternoon and the entire evening in a conference room with the entire project team attempting to come up with a new way to describe a canonical message without using the word canonical. It drove me CRAZY. I wanted everyone to get out of my way and let me go build something.&lt;/p&gt;
&lt;p&gt;I swore I would never do anything but write code.&lt;/p&gt;
&lt;h3&gt;
What Changed?&lt;/h3&gt;
&lt;div&gt;
Fast forward a few years, and I had built up a solid skill-set, primarily in Enterprise Java but also a solid understanding in C#.Net. I learned how to build websites in Oracle Application Server 1.0, proprietary frameworks, Enterprise Java, Spring, and other variations.&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
I loved learning new frameworks. Each one brought solutions to problems I had struggled with, and as I learned, a new set of problems.&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
And ultimately, this was the primary factor in my evolution. After a while, a new framework brought a quick review and shrug instead of excitement and a deep examination.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
My focus over this period was building enterprise applications running inside large companies. These are important systems and &#39;fun&#39; to work on, but at the end of the day are largely reading data from a database, displaying it in HTML, capturing some changes, and writing data back to the database. It got old, especially when I became somewhat disillusioned with the evolution of the frameworks.&lt;/div&gt;
&lt;div&gt;
&lt;p&gt;During this time, I also got to spend a lot more time working directly with clients. While this is often a frustrating experience, it allowed me to learn about a lot of different companies, industries, and business models. I came to respect the challenges that they each faced and the solutions that they had developed.&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;
These factors caused me to pick my head up and look around at the world I lived in, and wonder what else I could be doing.&lt;/div&gt;
&lt;h3&gt;
What Did I Know?&lt;/h3&gt;
&lt;div&gt;
After spending close to a decade working primarily for consulting companies, I had broad but shallow knowledge of a lot of industries. I had worked for Fast Food, Manufacturing, Insurance, HR, Logistics, Banking, Financial Exchanges, and Retailers. I knew a little about a lot, but I didn&#39;t have deep knowledge of any. I realized that what I really understood was the consulting industry.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
This worked out well since I currently worked for a consulting company. I looked around I started seeing other problems I could solve. Luckily, I was part of a fast-growing company with the trust of the ownership.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
It started small. We had a small project and needed a part time PM. I stepped as the technical architect, but I also worked with the client to make sure we were delivering what they needed, and keeping them up to date on the project status and the challenges.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
I also learned how the owner interviewed and I started helping out as the company was growing quickly and he was doing more interviews than he could handle alone.
&lt;p&gt;I built trust and over time I took on more responsibility.&lt;/div&gt;&lt;/p&gt;
&lt;h3&gt;
How Did I Learn?&lt;/h3&gt;
&lt;div&gt;
I often get asked how I learned how to manage a company. The answer is pretty simple: just like I learned how to code. I read a lot and learned through trial-and-error. I also had great mentors.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
This is an example conversation I often had with the owner of one of the companies:&lt;/div&gt;
&lt;div&gt;
Me: We should be doing X.&lt;/div&gt;
&lt;div&gt;
Him: Why?&lt;/div&gt;
&lt;div&gt;
Me: Because &amp;lt;a lot of reasons I thought made sense&amp;gt;.&lt;/div&gt;
&lt;div&gt;
Him: No.&lt;/div&gt;
&lt;div&gt;
Me: Why?&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
Him: Because &amp;lt;a lot of reasons I had not thought about&amp;gt;.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
I would take what he said and reformulate my pitch based on this new information. It would often take several iterations before I either got a yes or I became convinced my idea wouldn&#39;t work for our company.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
The great thing about the owner was that nearly every yes was followed by &#34;go do this, I&#39;m holding you accountable&#34;. This was a great way to grow my responsibility and take ownership of my ideas and see them through.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
This conversation happened a lot, and over time I really understood what he cared about, and what his overall philosophy was. I agreed with much of his philosophy, but not all of it. We had many spirited discussions over the years, and I came away with a well developed philosophy of my own.
&lt;p&gt;During this time, I expanded my reading habits beyond technology. I started reading business books, articles and anything and everything that taught me about how businesses worked. I learned about public companies by reading their 10K filings. I took a deeper look at our clients and tried to learn why they made decisions that I had previously thought were&amp;hellip; strange. &lt;/div&gt;&lt;/p&gt;
&lt;h3&gt;
And That Was That?&lt;/h3&gt;
&lt;div&gt;
My transition to &#39;manager&#39; wasn&#39;t over. My family decided to relocate to Colorado. Starting over in a new city with almost no professional network, I chose to go back to senior technical positions where I was actively writing code. I still have a passion for building software and, luckily enough, skills that were still valuable. But I eventually grew restless and started looking for a way to leverage my broader skills.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
Then a very important event in my story happened: I took the family to Disney World. No, I didn&#39;t have an epiphany on my tenth ride on &#34;It&#39;s A Small World&#34;, we only rode it 3 times. Instead, I met the CEO of Xcellent Creations on the flight home and found an opportunity to leverage the expertise I&#39;d developed over the years to help grow a small mobile shop into an industry leader that we eventually sold to WPP.&lt;/div&gt;
&lt;p&gt;Ultimately, I enjoy solving problems and building things. Whether those are Legos, software, or organizations, it doesn&amp;rsquo;t really matter. What matters is that it that the problems are challenging and that I&amp;rsquo;m working with great people.&lt;/p&gt;
&lt;p&gt;My hope is that the organization that we&amp;rsquo;ve built today does for our employees what my past companies did for me. I want to help our team expand their skills, take on new challenges, grow as individuals, and view the world differently. And along the way, ship great apps.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Technical Interviewing and Hiring</title>
      <link>https://blog.ericdaugherty.com/2017/03/technical-interviewing-and-hiring.html</link>
      <pubDate>Sun, 05 Mar 2017 02:35:00 +0000</pubDate>
      <guid>https://blog.ericdaugherty.com/2017/03/technical-interviewing-and-hiring.html</guid>
      <description>&lt;p&gt;There has been some discussion in the technical community recently about the use of algorithms and coding tests during the interview process.  Here is a sample:&lt;/p&gt;
&lt;blockquote class=&#34;twitter-tweet&#34; data-lang=&#34;en&#34;&gt;
&lt;div dir=&#34;ltr&#34; lang=&#34;en&#34;&gt;
Hello, my name is David. I would fail to write bubble sort on a whiteboard. I look code up on the internet all the time. I don&#39;t do riddles.&lt;/div&gt;
— DHH (@dhh) &lt;a href=&#34;https://twitter.com/dhh/status/834146806594433025&#34;&gt;February 21, 2017&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&#34;&#34; charset=&#34;utf-8&#34; src=&#34;//platform.twitter.com/widgets.js&#34;&gt;&lt;/script&gt;
&lt;blockquote class=&#34;twitter-tweet&#34; data-lang=&#34;en&#34;&gt;
&lt;div dir=&#34;ltr&#34; lang=&#34;en&#34;&gt;
Hi, I&#39;m Py. Used to think I didn&#39;t need to learn CS. I was lazy. I practiced for interviews, and it made me a better Software Engineer. &lt;a href=&#34;https://t.co/yD5NzcmTwF&#34;&gt;https://t.co/yD5NzcmTwF&lt;/a&gt;&lt;/div&gt;
— Py ⚔ (@Piwai) &lt;a href=&#34;https://twitter.com/Piwai/status/834817808940756992&#34;&gt;February 23, 2017&lt;/a&gt;&lt;/blockquote&gt;
I have my own thoughts I would like to share, but this isn&#39;t really a topic for 140 characters.
&lt;h3&gt;
Why should you care what I think?&lt;/h3&gt;
I&#39;ve spent a lot of time interviewing and hiring developers across several organizations. For a while I was doing interviews every day. In fact, I remember telling my recruiter that he couldn&#39;t schedule more than 2 interviews in a day because by the third interview I found I was no longer effective. I estimate that I&#39;ve interviewed over 300 candidates, and been the primary decision maker on over half of those. &amp;nbsp;I have been a key member of the interviewing and/or hiring team in two custom software services companies that have grown from 30 or fewer people to over 115 during my tenure. &amp;nbsp;I&#39;ve done this a lot, and I was there to see my successes and my failures.
&lt;p&gt;I spent over a decade primarily as a software developer. I&amp;rsquo;ve written production code in LabView, C++, J++, Java, VB, Perl, C#/.Net, JavaScript, Flex, Scala, Objective-C, and probably more. I&amp;rsquo;ve probably written more Java than the rest combined, but I was always learning something new and during the time that I was doing these interviews, I knew what I was talking about. I would like to think I still very technically competent, although many of my current employees enjoy proving me wrong regularly.&lt;/p&gt;
&lt;p&gt;I leaned this approach from a great mentor early on, and adopted and extended it over the years. We all stand on the shoulders of giants.&lt;/p&gt;
&lt;h3&gt;
Is this the one true way?&lt;/h3&gt;
Before I describe my approach, I want to focus on a key point lost among the 140 character conversations. &amp;nbsp;Your interview process should identify candidates that will work in YOUR organization. Your company probably isn&#39;t just like David Heinemeier Hansson&#39;s, or anyone else you read on twitter. &amp;nbsp;It probably isn&#39;t just like the companies I&#39;ve worked for either, so if you blindly copy what was successful for me, you will probably still fail.
&lt;p&gt;I&amp;rsquo;ve spent most of my career working for services companies building great software, from enterprise back-end systems in Java and .Net to amazing mobile applications in Objective-C, Swift, Java, and Kotlin. Because these companies are services companies, I expect my employees to be able to interact directly with out clients. The people I hire must be capable of more than just writing great code, they must solve our clients problems, by understanding what they really need, communicating effectively with technical and non-technical people alike, and delivering a great technical solutions.&lt;/p&gt;
&lt;p&gt;I also expect my employees to be able to learn a new technology quickly. The landscape is always changing, and when you are building software for other companies, you do not have the luxury to define what technologies are and are not acceptable.&lt;/p&gt;
&lt;p&gt;Therefore, I focus on hiring great problem solvers. If you are not a good problem solver, you will not work on my teams, no matter how amazing you are at language/framework/platform XYZ.&lt;/p&gt;
&lt;h3&gt;
So what is the interview like?&lt;/h3&gt;
&lt;div&gt;
Most of these interviews last about an hour. The four sections are...&lt;/div&gt;
&lt;h4&gt;
Cool Stuff - ~15 Minutes&lt;/h4&gt;
&lt;div&gt;
I start all the interviews by asking the candidate what projects they think are cool or projects they are proud of. These can be work projects, open-source projects, school projects, or just tinkering around and learning projects. This engages the candidate and allows them to talk about topics they are comfortable with.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
This tells me several things. First, it tells me whether they communicate effectively. I actually prefer when they have a project in an area that I am not knowledgeable. Selfishly, it lets me learn about something new, but the primary reason is that it show me whether they can explain a technical concept to me that they understand well. If it is in an area that I know well, it lets me probe to see how well they really understand the topic.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
It also helps me level-set where the candidate is. Ignoring their resume, the projects and accomplishments they are proud of tell a very clear story about their skill level and world view.&lt;/div&gt;
&lt;h4&gt;
Coding Problems - ~20 Minutes&lt;/h4&gt;
&lt;div&gt;
What!?! Yes, I have two basic coding/problem solving problems that I make each candidate work through, on paper, in front of me.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;b&gt;Problem 1:&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;
This is essentially a recursive coding problem. You can solve it in any OO programing language with a basic knowledge of the core syntax. It does not require any API/Framework specific knowledge. The right answer is about 4 lines of code.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
I have given this problem to everyone from college students looking for their first job to senior architects. They should all be able to solve it. I do adjust my expectations based on the level indicated by their resume and &#39;Cool Stuff&#39; answers.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
Achieving a Passing mark on this doesn&#39;t necessarily require a perfect answer right away. I&#39;m happy to ask questions, and give small hints to the candidates if they get stuck. This is actually one of the most valuable parts of the process because it allows me to see how well they listen, and how they think through a problem. But if you can&#39;t write (simple) code down on paper with a reasonable level of accuracy, and you can&#39;t write a basic recursive method with a few hints, you are probably not going to cut it.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;b&gt;Problem 2:&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;
This is where I date myself somewhat. &amp;nbsp;Problem #2 is ideally writing a SQL statement, but is really a boolean logic problem. It does not require anything other than very basic SQL syntax. &amp;nbsp;No Joins, or anything even slightly fancy. The right answer would fit in a single tweet.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
Again, I have given this problem to everyone from college students looking for their first job to senior architects.&amp;nbsp;They should all be able to solve it. I have run into college students that didn&#39;t know SQL, and in those cases I was able to adapt it to be a boolean logic problem that they could solve in general set notation.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
Again, success here is showing me how you think through and solve problems. You don&#39;t need to write the correct answer down flawlessly the first time, but you have to show me that you can think through a problem, listen to feedback/hints if necessary and incorporate them into your thinking.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;b&gt;Alternate Problem:&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;
Depending on time and level, another question I&#39;ve asked A LOT, mostly to Java and .Net candidates, is how garbage collection works in the JVM (or on .Net).&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
Occasionally I&#39;ll get a candidate that actually knows the answer, and while impressive, this is actually somewhat disappointing. &amp;nbsp;The point of this question is to have them show me their problem solving skills applied to a real technology that they depend on every day, but probably don&#39;t think about.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
This questions shows me how well a candidate can listen, how well do they think on their feet, and how well do they actually understand computer science concepts.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
Again, the &#39;right answer&#39; isn&#39;t necessarily the point. Having a great conversation with me where in the end they really grasp the concept and feel like they are a better developer makes both me and the candidate feel good about the interview.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;b&gt;Side Note:&lt;/b&gt; I was working with a startup (not my full time job) and helping them make their first full time developer hire. I used these problems and rejected a candidate. The CTO, who had not actively developed in several years, challenged me about the questions, so I gave him the interview. He passed both in less than 10 minutes, and realized that he didn&#39;t want anyone on his team that couldn&#39;t solve these problems. The next person we interviewed passed, was hired, and did a great job for them for several years.&lt;/div&gt;
&lt;h4&gt;
Resume Due Diligence ~ 15 Minutes&lt;/h4&gt;
&lt;div&gt;
Once the problems are solved, I will use the next section to tackle any areas of interest that I think are appropriate. I will often read through the resume and ask questions about specific projects/accomplishments to determine how accurate the descriptions are and what role the candidate actually had on those projects.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
If they profess deep knowledge in a certain language/framework/platform, I may go deep in this area to see how much of an expert they really are. I love learning something new from&amp;nbsp;candidates, and they usually enjoy teaching the interviewer something new as well!&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
A big take-away from this section is: how honest are they about their resume? Were they exaggerating, or underselling themselves?&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
I can also use this time to follow up on areas the candidate showed a particular interest in to see how deep their knowledge is.&lt;/div&gt;
&lt;h4&gt;
Candidate Questions ~ 10 Minutes&lt;/h4&gt;
&lt;div&gt;
The final section is an opportunity for the candidate to ask me questions, about the role, culture, expectations, etc. While this is a key part in making sure the candidate is sold on working for me, their questions are also a window into their thought process and outlook, and can be informative in making a hire/no-hire decision.&lt;/div&gt;
&lt;div&gt;
&lt;p&gt;Ultimately thought, this section is about closing the candidate. Regardless of whether you will hire the candidate, you want them to be sold on your company, and you want them to feel good about the experience. Even if you pass on them, you want them to leave the interview with a positive opinion of you and your company. &lt;b&gt;It is a small world.&lt;/b&gt;&lt;/div&gt;&lt;/p&gt;
&lt;h3&gt;
Why do I do is this way?&lt;/h3&gt;
&lt;div&gt;
I&#39;ve covered a lot of the why along the way, but it important to reiterate and expand on my motivations and goals.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
First, I want to reiterate that I target hiring great technical consultants. We build software solutions for other companies, and the skills we look for reflect that. This is probably not a great approach for other types of companies.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
I firmly believe that in order to be a great software consultant, you have to be a great problem solver. To me, this includes listening and understanding the problem (solving the right problem), deep enough technical expertise to identify a good solution (there is rarely one RIGHT solution), the ability to communicate what the solution is, what the trade-offs are (there are always trade-offs) and how it addresses the actual problem, and the ability to deliver the solution.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
In the companies that I worked for, we certainly didn&#39;t expect an Associate Developer to interact directly with the client and execute all of these steps, but this is still the ultimate process and everyone should be able to participate at the level of their experience.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
I also don&#39;t believe in team interviewing for smaller services companies. A consultant working for one of these companies will need to be successful across different clients and different teams. I believe in the accountability of a single decision maker, and if the process is consistent, then the team members know the people getting hired went through the same process they did. Ultimately, if the team trusts the hiring manager, they will trust the candidate that they hire. However, this doesn&#39;t scale and as a company grows, the process needs to evolve.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
Again, this probably doesn&#39;t work for other types of companies, and team interviews make sense in a lot of cases.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
Finally, I do it this way because it works. Yes, I made a few bad hires over the years. In each case, I went back to look at the interview notes to see what I missed and how I could do better next time. In some cases I was being too optimistic because I was desperate to fill a need. In some cases the candidate simply snowed me. In some cases, there is an issue that I can&#39;t reasonably expect to uncover in an interview.&lt;/div&gt;
&lt;h4&gt;
Final Notes&lt;/h4&gt;
&lt;div&gt;
The lifeblood of the interview/hiring process is the great work that your recruiters do. I firmly believe in having in house recruiters and I&#39;ve been blessed to work with some great ones. You know who you are, thank you.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
I have no idea how many false negatives I&#39;ve had. I would argue it is unknowable. But the growth of both companies would suggest that I didn&#39;t turn away too many qualified candidates.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
These opinions are my own, and do not reflect those of my employer. I am no longer in a role where I make any hiring decisions on technical candidates, so don&#39;t expect that reading this post will give you an inside edge. ;)&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
This post is too long. Unfortunately, I&#39;m not a good enough writer to write a shorter post. &amp;nbsp;I&#39;m sorry.&lt;/div&gt;
&lt;script async=&#34;&#34; charset=&#34;utf-8&#34; src=&#34;//platform.twitter.com/widgets.js&#34;&gt;&lt;/script&gt;</description>
    </item>
    <item>
      <title>GoLang Alexa SDK</title>
      <link>https://blog.ericdaugherty.com/2017/03/golang-alexa-sdk.html</link>
      <pubDate>Sat, 04 Mar 2017 03:28:00 +0000</pubDate>
      <guid>https://blog.ericdaugherty.com/2017/03/golang-alexa-sdk.html</guid>
      <description>&lt;p&gt;I continue to be interested in Alexa, Amazon AWS Lambda, and Go (golang), and I&amp;rsquo;ve found a new way to deploy Alexa apps in Go on Lambda.&lt;/p&gt;
&lt;p&gt;While Go is still not an officially supported language on Amazon Lambda, there are several ways to make it work:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/jasonmoo/lambda_proc&#34;&gt;Lambda_proc&lt;/a&gt; - Node.js wrapper for your go binary&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/gopherjs/gopherjs&#34;&gt;GopherJS&lt;/a&gt; - Cross-Compile your Go code to NodeJS&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/eawsy/aws-lambda-go-shim&#34;&gt;eawsy Lamba Go Shim&lt;/a&gt; - Python shim for your go binary&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
In my previous project where I built a &lt;a href=&#34;http://blog.ericdaugherty.com/2016/03/lamda-go-and-gotsport-via-api-gateway.html&#34;&gt;screen scraper in Go and deployed it to Lambda&lt;/a&gt;, I utilized Lambda_proc. &amp;nbsp;This does work pretty well an is a solid solution.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
I found GopherJS to be an interesting project, but a pretty indirect way to get Go onto Lambda. &amp;nbsp;Lambda can run Go Linux binaries, so converting your Go to JavaScript seems like the wrong approach.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
The eawsy team released their new tool earlier this year and it seems to be the cleanest approach. &amp;nbsp;The overhead between the Python shim and your Go code is clean and fast. &amp;nbsp;They utilize a Docker container to build the necessary bridges from Python to Go, resulting in very fast execution. &amp;nbsp;You can write log messages to the Lambda console using log.Printf. And it is FAST. The HelloWorld skill runs in sub-millisecond times!&lt;/div&gt;
&lt;div&gt;
To build Alexa skills in Go on Lambda, we still need an SDK. &amp;nbsp;Amazon publishes SDKs for &lt;a href=&#34;https://github.com/amzn/alexa-skills-kit-java&#34;&gt;Java&lt;/a&gt;&amp;nbsp;and &lt;a href=&#34;https://github.com/amzn/alexa-skills-kit-js&#34;&gt;JavaScript&lt;/a&gt;&amp;nbsp;but not for Go. &amp;nbsp;So I ported the Java SDK into Go as &lt;a href=&#34;https://github.com/ericdaugherty/alexa-skills-kit-golang&#34;&gt;ericdaugherty/alexa-skills-kit-golang&lt;/a&gt;&amp;nbsp; While this does not (yet) have the full depth of the Amazon SDK, it does enable you to build simple Alexa skills using Go and deploy them on Lambda.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
Take a look at the&amp;nbsp;&lt;a href=&#34;https://github.com/ericdaugherty/alexa-skills-kit-golang&#34;&gt;alexa-skills-kit-golang&lt;/a&gt;&amp;nbsp;project for usage and samples, and give the&amp;nbsp;&lt;a href=&#34;https://github.com/eawsy/aws-lambda-go-shim&#34;&gt;eawsy Lamba Go Shim&lt;/a&gt;&amp;nbsp;a try for your next Go project on Amazon AWS Lambda.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
&amp;nbsp;- Updated 3/4/2017 - updated run time based on newer stable eawsy shim speeds.&lt;/div&gt;</description>
    </item>
    <item>
      <title>Lamda, Go, and GotSport via API Gateway</title>
      <link>https://blog.ericdaugherty.com/2016/03/lamda-go-and-gotsport-via-api-gateway.html</link>
      <pubDate>Sat, 12 Mar 2016 00:10:00 +0000</pubDate>
      <guid>https://blog.ericdaugherty.com/2016/03/lamda-go-and-gotsport-via-api-gateway.html</guid>
      <description>&lt;p&gt;While I&amp;rsquo;ve been primarily living in the mobile world recently, I&amp;rsquo;ve been intrigued by a the developments in a few other areas of technology.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://golang.org/&#34;&gt;Go&lt;/a&gt;, or golang, has been increasingly becoming my preferred language for side projects.  I really like the concise nature of the language, the ability to deploy on multiple platforms, and the short tool chain.  It &amp;lsquo;just works&amp;rsquo;, and is fun to program in.&lt;/p&gt;
&lt;p&gt;AWS continues to add interesting services and features.  I recently &lt;a href=&#34;http://blog.ericdaugherty.com/2015/08/site-migration.html&#34;&gt;moved my website from GoDaddy to host on S3&lt;/a&gt;.  This really started my thinking about living in a &amp;lsquo;serverless world&amp;rsquo;.  While practically speaking hosting on S3 isn&amp;rsquo;t really all that different than virtual hosting at GoDaddy, it is really just scratching the surface.  You can now build pretty interesting applications without running a server (virtual or otherwise).&lt;/p&gt;
&lt;p&gt;I begin to think about how you could build a full application using the AWS stack without an EC2 instance.  Of course, a ton of thought has already been put into this.  The &lt;a href=&#34;https://github.com/serverless/serverless-starter&#34;&gt;Serverless&lt;/a&gt; tool allows you to easily configure Amazon to use the API Gateway with Lambda to deploy functional APIs.  There is also a website dedicated to &lt;a href=&#34;https://serverlesscode.com/&#34;&gt;living serverless&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve also been working on a few Amazon Alexa applications for the Echo, which also uses Lambda as the preferred deployment program.&lt;/p&gt;
&lt;p&gt;So I thought it was time to build something that actually works.&lt;/p&gt;
&lt;p&gt;My son plays soccer and the Colorado Soccer Association uses the event.gotsport.com website to post schedules and results.  So I built a screen scraper in Go to parse his schedule into JSON so I could use it in my Amazon Alexa application.  (The scraper is here: &lt;a href=&#34;https://github.com/ericdaugherty/gotsport-scraper&#34;&gt;&lt;a href=&#34;https://github.com/ericdaugherty/gotsport-scraper&#34;&gt;https://github.com/ericdaugherty/gotsport-scraper&lt;/a&gt;&lt;/a&gt;)  But I hard-coded the resulting JSON into that application.&lt;/p&gt;
&lt;p&gt;I figured I could build a general API that could be used by anyone to convert the schedules into JSON.  And I could easily deploy it on Lambda.&lt;/p&gt;
&lt;p&gt;In order to get Go running on Lambda, you need to &amp;lsquo;work around&amp;rsquo; the fact that it isn&amp;rsquo;t officially supported.  The lambda_proc library (&lt;a href=&#34;https://github.com/jasonmoo/lambda_proc&#34;&gt;&lt;a href=&#34;https://github.com/jasonmoo/lambda_proc&#34;&gt;https://github.com/jasonmoo/lambda_proc&lt;/a&gt;&lt;/a&gt;) does just that.  It uses a node.js wrapper that invokes your Go application within the Lambda runtime.  The repository has a good example that should you how to write and deploy a go app on Lambda.&lt;/p&gt;
&lt;p&gt;From there, I just needed a simple Go app to take the input JSON, run the gotsport-scraper I wrote, and return the resulting JSON.&lt;/p&gt;
&lt;p&gt;The final step was exposing the Lambda function as an HTTP API.  This is where the API Gateway comes in.  It allows you to specify an endpoint and method to trigger the Lambda function.  The basics are pretty straightforward.  You define a resource (/gotsport) and a method (GET), and map it to your Lambda function.  However, the tricky part is the mapping of the HTTP Request to the Lambda function, and the result of the Lambda function to the HTTP Response.&lt;/p&gt;
&lt;p&gt;Here is the full lifecycle:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi5MpIMtsnyRL0arVtmU-2Cow1tvWBSqn_dg4zbEoCH7tsjluewJWYuv3ifAHOz7IsBbrCoO_UwiqWs63pFCVrwoefYt2WaFZ-valBdUy9PA107kvu8LiydlSSFJjQKl-mXrayqRflTdOc/s1600/Screen+Shot+2016-03-11+at+4.15.37+PM.png&#34; imageanchor=&#34;1&#34;&gt;&lt;img border=&#34;0&#34; height=&#34;219&#34; src=&#34;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi5MpIMtsnyRL0arVtmU-2Cow1tvWBSqn_dg4zbEoCH7tsjluewJWYuv3ifAHOz7IsBbrCoO_UwiqWs63pFCVrwoefYt2WaFZ-valBdUy9PA107kvu8LiydlSSFJjQKl-mXrayqRflTdOc/s640/Screen+Shot+2016-03-11+at+4.15.37+PM.png&#34; width=&#34;640&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I decided to use Query String parameters to pass data to the function, so you could just cut/paste from the URL you wanted converted.  You can define the query strings in the Method Request section, but this just seems to allow you to use them when testing and isn&amp;rsquo;t required.  I did have to map the query parameters into the Lambda, so I created an application/json mapping (since the Lambda function consumes JSON).  The mapping function is:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;#34;eventId&amp;#34; : &amp;#34;$input.params(&amp;#39;EventID&amp;#39;)&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;#34;groupId&amp;#34; : &amp;#34;$input.params(&amp;#39;GroupID&amp;#39;)&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;#34;gender&amp;#34;  : &amp;#34;$input.params(&amp;#39;Gender&amp;#39;)&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;#34;age&amp;#34;     : &amp;#34;$input.params(&amp;#39;Age&amp;#39;)&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This maps the Query String Parameters using their names (again, as used on gotsport.com so you can cut and paste) into JSON values that match those used by me gotsport-scraper tool.  This is then passed to the Lambda function.&lt;/p&gt;
&lt;p&gt;The Lambda function runs, fetching the requested URL, scraping it, and returning a JSON value.  However, the lambda_proc function returns both an error value and a data value containing the results of the Lambda function.  I wanted to map the output to just contain the JSON representing the schedule.  So in the Integration Response step in the lifecycle, I used the application/json mapping function:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;#set($inputRoot = $input.path(&amp;#39;$&amp;#39;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$input.json(&amp;#39;$.data&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This just extracts the data element from the JSON returned from the Lambda function and passes it back as the HTTP Response Body.&lt;/p&gt;
&lt;p&gt;The proper approach in the Integration Response step is to use a RegEx to determine if an error occurred or not, and return the proper HTTP response code and appropriate body.  For now I&amp;rsquo;m assuming a 200 response with valid data.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s it!  I now have an HTML-&amp;gt;JSON screen scraper for the GotSport website deployed as an API.&lt;/p&gt;
&lt;p&gt;Want to see how this all works.  Here are the resources:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/jasonmoo/lambda_proc&#34;&gt;lambda_proc&lt;/a&gt; - Run GoLang apps on Lambda&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/ericdaugherty/gotsport-scraper&#34;&gt;gotsport-scraper&lt;/a&gt; - Screen Scrape the event.gotsport.com site&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/ericdaugherty/gotsport-lambda&#34;&gt;gotsport-lambda&lt;/a&gt; - Lamba function to execute gotsport-scraper&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://docs.aws.amazon.com/apigateway/latest/developerguide/welcome.html&#34;&gt;Amazon API Gateway&lt;/a&gt; - General API Gateway documentation&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html&#34;&gt;Amazon API Gateway Mapping&lt;/a&gt; - Documentation about writing mapping files.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
Want to test it out? &amp;nbsp;Grab a specific schedule from the &lt;a href=&#34;http://events.gotsport.com/events/Default.aspx?EventID=46461&#34;&gt;CSA Youth Advanced League 2016&lt;/a&gt; site. &amp;nbsp;Then cut/paste the query string parameters onto my API URL:&amp;nbsp;https://j4p9lh1dlb.execute-api.us-east-1.amazonaws.com/prod/gotsport &amp;nbsp;For example, the U-11 Boys Super League would be:&amp;nbsp;&lt;a href=&#34;https://j4p9lh1dlb.execute-api.us-east-1.amazonaws.com/prod/gotsport?EventID=46461&amp;amp;GroupID=511424&amp;amp;Gender=Boys&amp;amp;Age=11&#34;&gt;https://j4p9lh1dlb.execute-api.us-east-1.amazonaws.com/prod/gotsport?EventID=46461&amp;amp;GroupID=511424&amp;amp;Gender=Boys&amp;amp;Age=11&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;</description>
    </item>
    <item>
      <title>Unit Testing with throws in Swift 2.0</title>
      <link>https://blog.ericdaugherty.com/2015/11/unit-testing-with-throws-in-swift-20.html</link>
      <pubDate>Mon, 23 Nov 2015 00:26:00 +0000</pubDate>
      <guid>https://blog.ericdaugherty.com/2015/11/unit-testing-with-throws-in-swift-20.html</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve been working very slowly on a Swift version of Playlist Export, and today I finally got around to updating the project to Swift 2.0.  Luckily I wrote test cases for much of the logic to manage the export process, so I had some level of confidence that I would know if I broke anything.&lt;/p&gt;
&lt;p&gt;However, after I got the project to compile, only one of the test cases ran.  No errors or other indication why all the other cases were ignored.&lt;/p&gt;
&lt;p&gt;I did finally figure out that if you your test case has a throws clause, it will be IGNORED.&lt;/p&gt;
&lt;p&gt;So:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;testPlaylistNameExtension() &lt;span style=&#34;color:#66d9ef&#34;&gt;throws&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;p1&#34;&gt;
&lt;/div&gt;
&lt;div class=&#34;p1&#34;&gt;
will silently be ignored, which for a test case is a pretty bad scenario. &amp;nbsp;But if you handle the error in the function and change it to:&lt;/div&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;testPlaylistNameExtension() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;p1&#34;&gt;
&lt;/div&gt;
&lt;div class=&#34;p1&#34;&gt;
then everything is just fine. &amp;nbsp;So if you have test in Swift 2.0 that are ignored or not executed, check to see if you have a throws in the function declaration.&lt;/div&gt;
&lt;div class=&#34;p1&#34;&gt;
&lt;/div&gt;
&lt;div class=&#34;p1&#34;&gt;
&lt;/div&gt;
</description>
    </item>
    <item>
      <title>Home Theater</title>
      <link>https://blog.ericdaugherty.com/2015/10/home-theater.html</link>
      <pubDate>Sat, 17 Oct 2015 20:18:00 +0000</pubDate>
      <guid>https://blog.ericdaugherty.com/2015/10/home-theater.html</guid>
      <description>&lt;p&gt;I recently got to fulfill a longtime dream of building a &amp;lsquo;home theater&amp;rsquo;.  Luckily, the basement was already finished with a room that was large enough to house a home theater, so as home theater projects go, it was pretty straight forward.  This is a write up of the project, which I completed in May of this year, so I&amp;rsquo;ve had almost 6 months with it completed.&lt;/p&gt;
&lt;p&gt;The room was previously setup as a TV room, with a 60&amp;quot; Rear Projection DLP TV (Mitsubishi WD-60738), a very large entertainment center I purchased for a much larger room in a previous house, and a sectional couch.&lt;/p&gt;
&lt;p&gt;The 60&amp;quot; TV and too-large entertainment center:&lt;/p&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;
&lt;a href=&#34;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWwFnaxWlFkHuRFrO9KtC2fjSkHus2judZGCtfEIOiY7_RObmjlEkF3K7fanUmdhxkqROiSboJgTN7SefmdpsWxcC0pSo39fa_hJHRXMh2VtRRy-rZ3soPI_Pa4COnuue_iFCSKs28r78/s1600/IMG_1474.jpg&#34; imageanchor=&#34;1&#34; style=&#34;margin-left: 1em; margin-right: 1em;&#34;&gt;&lt;img border=&#34;0&#34; height=&#34;300&#34; src=&#34;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWwFnaxWlFkHuRFrO9KtC2fjSkHus2judZGCtfEIOiY7_RObmjlEkF3K7fanUmdhxkqROiSboJgTN7SefmdpsWxcC0pSo39fa_hJHRXMh2VtRRy-rZ3soPI_Pa4COnuue_iFCSKs28r78/s400/IMG_1474.jpg&#34; width=&#34;400&#34; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
This setup also consisted of:
&lt;p&gt;Onkyo TX-SR605&lt;/p&gt;
&lt;p&gt;3 Phase Tech PC-3 Speakers (Fronts)&lt;/p&gt;
&lt;p&gt;2 Phase Tech PC-60 Speakers (Rears)&lt;/p&gt;
&lt;p&gt;Phase Tech TODO Subwoofer&lt;/p&gt;
&lt;p&gt;Sony Blu-Ray Player&lt;/p&gt;
&lt;p&gt;DirecTV HR20-700 DVR&lt;/p&gt;
&lt;p&gt;I also had an IR repeater setup, with most of the components already located in the utility room next door.  The system was based on the &lt;a href=&#34;http://www.nilesaudio.com/product.php?prodID=MSU140&amp;amp;recordID=Main%20System%20Units&amp;amp;categoryID=Ingenious%20IR&amp;amp;prdcdID=FG01002&#34;&gt;Niles IR Repeater&lt;/a&gt; with the receiver and emitters cobbled together over many years.&lt;/p&gt;
&lt;p&gt;The room has three walls, with the back open to a larger room, and no windows on any of the three walls.  The room was 11.5&amp;rsquo; wide x 17&amp;rsquo; deep, which wasn&amp;rsquo;t quite as wide as I would have liked, but gave me plenty of length, especially since I could spill over into the larger room behind it.&lt;/p&gt;
&lt;p&gt;The existing room and equipment provided a solid foundation for the new home theater.  The Onkyo receiver and HR-20 were both used in the new setup.  I struggled about whether to keep the Phase Tech speakers or replace them, but ended up replacing them.  They had been &amp;lsquo;well used&amp;rsquo; over a ~15 year lifespan, and the speaker cones were in need of repair, some of the dome tweeters were smashed, and there was some loose material in the speakers that you could hear when you lifted them.  The rears were also pretty large to be wall mounted, which made it awkward to walk past.&lt;/p&gt;
&lt;p&gt;While researching the room, I leaned heavily on two resources:&lt;/p&gt;
&lt;p&gt;1. &lt;a href=&#34;http://thewirecutter.com/&#34;&gt;The Wirecutter&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;2. &lt;a href=&#34;http://www.avsforum.com/&#34;&gt;AVS Forum&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I really appreciated the Wirecutter&amp;rsquo;s approach of picking the best item in a given category.  It really simplifies the decision making process, but also does give you a few options in addition to their pick so you can really understand the trade-offs that exist.  This site is how I picked the screen, projector, and heavily influenced the selection of the speakers.&lt;/p&gt;
&lt;p&gt;For the projector, I pretty much took the Wirecutter&amp;rsquo;s &lt;a href=&#34;http://thewirecutter.com/reviews/an-awesome-projector/&#34;&gt;recommendation&lt;/a&gt;, and verified the selection by reading reviews on &lt;a href=&#34;http://www.avsforum.com/&#34;&gt;AVS Forum&lt;/a&gt;.  I selected the Sony VPL-HW40ES. Overall, I have been very happy with the projector.  The picture quality is great, it is relatively quiet, and has worked flawlessly so far.  I did briefly consider a 4k projector, as the HW40ES is only 1080p, but my logic was that the cost difference for early stage 4k projectors wasn&amp;rsquo;t worth it, and by the time 4k content becomes readily available there will be better projectors out for much less money.&lt;/p&gt;
&lt;p&gt;I mounted the projector to the ceiling with a set of Chief components, the CMA-101, CMS-003, and RPA-020.  This is very solid and pretty easy to get setup and aligned.&lt;/p&gt;
&lt;p&gt;There are a lot of options for screens, but since I was in a basement and had full control over the light, I didn&amp;rsquo;t need a high gain screen.  I could also use a fixed screen since it would be permanently mounted to the wall.  Again, I want with the Wirecutter&amp;rsquo;s &lt;a href=&#34;http://thewirecutter.com/reviews/best-projector-screen/&#34;&gt;recommendation&lt;/a&gt; of the &lt;a href=&#34;http://www.amazon.com/gp/product/B00HZRM9K0&#34;&gt;Silver Ticket 100&amp;quot;&lt;/a&gt; (diagonal) screen.  It takes a bit of effort to put it together, but it was straightforward and I was very pleased with the finished product.&lt;/p&gt;
&lt;p&gt;Once I decided to replace the speakers, I really struggled with how to select the best options.  Many quality speakers are now sold directly to consumers, allowing you to skip the AV Store listening rooms.  I spent a lot of time listening to speakers 15 years ago when I purchased the Phase Tech speakers originally, and frankly I didn&amp;rsquo;t want to go through that again.  I turned to the Wirecutter and AVS Forum again for advice.  In the end, I started with the Wirecutter &lt;a href=&#34;http://thewirecutter.com/reviews/best-surround-sound-system/&#34;&gt;recommendation&lt;/a&gt; for surround systems, although I found the runner up at the time, the NHT Absolute 5.1 system more interesting.  However, I had an existing sub that was more than adequate for the room, so I ended up with the &lt;a href=&#34;http://www.nhthifi.com/&#34;&gt;NHT&lt;/a&gt; Classic Three bookshelf speakers as the fronts, the Classic ThreeC as the center, and their thin Absolute Wall Speakers as rears.  I&amp;rsquo;ve been very happy with them so far as well.&lt;/p&gt;
&lt;p&gt;While I had an existing Sony Blu-Ray player, I decided to upgrade it because I wanted to move it to another room to replace an existing DVD player.  For this, I ended up going with a popular selection from the &lt;a href=&#34;http://www.avsforum.com/&#34;&gt;AVS Forum&lt;/a&gt;, the &lt;a href=&#34;https://www.oppodigital.com/blu-ray-bdp-103/blu-ray-BDP-103D-Overview.aspx&#34;&gt;OPPO BPD 103D&lt;/a&gt;.  I really like this player, but I&amp;rsquo;m not totally convinced on the Darby processing.  I have the Darby signal processing applied to both the  OPPO sourced video as well as running the DirecTV video through it.  I can certainly see some differences in the side-by-side option it has, especially when you crank the processing up high.  I have mine set at 35 I believe, and mostly take it on faith that it is better.  That said, as the owner of a phenomenal early DVD player, the DVP-7000, I&amp;rsquo;ve been mostly disappointed in every DVD or Blu-Ray player I&amp;rsquo;ve purchases since then.  The OPPO is the closest thing to the DVP-7000 that I&amp;rsquo;ve owned since, and that his high praise.&lt;/p&gt;
&lt;p&gt;To round out the electronics portion of the upgrade, I bought a Harmony 650 remote.  I&amp;rsquo;ve used Harmony remotes for as long as I can remember and I&amp;rsquo;ve been happy with all of them.  I was somewhat leery of moving from a Harmony One to the 650 based on some feedback I had read, but I was very please with the 650 and it does everything I need.&lt;/p&gt;
&lt;p&gt;For the furniture, the room wasn&amp;rsquo;t quite wide enough to put 4 seats and leave enough room for an aisle, so I ended up with two rows of 3 HT Design Devonshire chairs.  Since I could only fit 6 chairs, I added a bar behind the chairs with 4 stools, increasing our capacity to 10 people and providing a space to eat and socialize when we had people over to watch games, etc.  I&amp;rsquo;m generally happy with the chairs.  They are very comfortable and feel well-built.  I think the back-lit buttons are a bit too bright, and could generally do without the lights on the chairs, as guests have a tendency to turn them on without realizing it.  I need to just disconnect them, but have not gotten that far.&lt;/p&gt;
&lt;p&gt;The riser is a bit time-consuming to build, and the instructions are horrible.  There were some YouTube videos, but they seemed to use slightly different hardware and were of minimal use.  We did re-carpet the basement towards the end of the project, so we were able to get the riser carpeted to match the rest of the room easily.  The riser is really just tall enough, although anything taller and you&amp;rsquo;d be banging your head on the ceiling when you stood up, so it works well.&lt;/p&gt;
&lt;p&gt;I purchased all this from the following sources, and had good experiences with all:&lt;/p&gt;
&lt;p&gt;Craig at &lt;a href=&#34;http://avscience.com/&#34;&gt;AV Science, Inc.&lt;/a&gt; set me up with the projector and the Chief mounts.&lt;/p&gt;
&lt;p&gt;Alan at &lt;a href=&#34;http://www.htmarket.com/&#34;&gt;HT Market&lt;/a&gt; set me up with the chairs, riser, and bar.&lt;/p&gt;
&lt;p&gt;I bought the speakers directly from NHT.&lt;/p&gt;
&lt;p&gt;The rest was purchased on Amazon or elsewhere.&lt;/p&gt;
&lt;p&gt;Here is the finished product:&lt;/p&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;
&lt;a href=&#34;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjwPE8Df3b0eIU5t29FvTi1785oQdSY-UdcC6BqTlJRaOMUiWHYGfPkQzf_rodVQ08Db-_bXAMp0X0EC9eMPSeC125nANmpb0iWm7-MbVta3WanCVm9dMW4gP_SfccfQncF960nESVcfPM/s1600/DSC_9809.jpg&#34; imageanchor=&#34;1&#34; style=&#34;margin-left: 1em; margin-right: 1em;&#34;&gt;&lt;img border=&#34;0&#34; height=&#34;266&#34; src=&#34;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjwPE8Df3b0eIU5t29FvTi1785oQdSY-UdcC6BqTlJRaOMUiWHYGfPkQzf_rodVQ08Db-_bXAMp0X0EC9eMPSeC125nANmpb0iWm7-MbVta3WanCVm9dMW4gP_SfccfQncF960nESVcfPM/s400/DSC_9809.jpg&#34; width=&#34;400&#34; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;
&lt;a href=&#34;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWtFauc20upnrVeM1BT6C2sy4W0Q3Xzcop2FtqHatXx8-DYdH9JixGnpWFZhQCGYW9oWnJWWWGNuAiBbk_ozA1SpA_2ysDzr2SRRZQdOuSEdHFCpNvJec2VW4W4b8BHhnf-x-Y-lDwRIU/s1600/DSC_9810.jpg&#34; imageanchor=&#34;1&#34; style=&#34;margin-left: 1em; margin-right: 1em;&#34;&gt;&lt;img border=&#34;0&#34; height=&#34;266&#34; src=&#34;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWtFauc20upnrVeM1BT6C2sy4W0Q3Xzcop2FtqHatXx8-DYdH9JixGnpWFZhQCGYW9oWnJWWWGNuAiBbk_ozA1SpA_2ysDzr2SRRZQdOuSEdHFCpNvJec2VW4W4b8BHhnf-x-Y-lDwRIU/s400/DSC_9810.jpg&#34; width=&#34;400&#34; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;
&lt;a href=&#34;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEheE2g0ytNgxmuS8Y5PXCl880wcTfHDbAiPZJklfw3TJuK3yUqQZ05bAeYBgK86F-En0X5NQfS9PQQUJrXymydWIItmtN2KKT9ieQxlZKEkUvNwkXDB76MqGwyNezmA0qIF9IBA1vjCH44/s1600/DSC_9814.jpg&#34; imageanchor=&#34;1&#34; style=&#34;margin-left: 1em; margin-right: 1em;&#34;&gt;&lt;img border=&#34;0&#34; height=&#34;266&#34; src=&#34;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEheE2g0ytNgxmuS8Y5PXCl880wcTfHDbAiPZJklfw3TJuK3yUqQZ05bAeYBgK86F-En0X5NQfS9PQQUJrXymydWIItmtN2KKT9ieQxlZKEkUvNwkXDB76MqGwyNezmA0qIF9IBA1vjCH44/s400/DSC_9814.jpg&#34; width=&#34;400&#34; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;
&lt;a href=&#34;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPnJDuV45N1R6s5UloCuxRwm5e-1og1Bp0XcaDiqUfCIL8elgFr9X_BH3p36oiscsnOpq89sPU9n0M414-abBI2xTwZZmDk4PBZafUfWisX1d8mVlze0JDIcZ1cN5Ai4EbF7g0qDS6rwg/s1600/DSC_9815.jpg&#34; imageanchor=&#34;1&#34; style=&#34;margin-left: 1em; margin-right: 1em;&#34;&gt;&lt;img border=&#34;0&#34; height=&#34;266&#34; src=&#34;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPnJDuV45N1R6s5UloCuxRwm5e-1og1Bp0XcaDiqUfCIL8elgFr9X_BH3p36oiscsnOpq89sPU9n0M414-abBI2xTwZZmDk4PBZafUfWisX1d8mVlze0JDIcZ1cN5Ai4EbF7g0qDS6rwg/s400/DSC_9815.jpg&#34; width=&#34;400&#34; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;
&lt;a href=&#34;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGYxxs0-sBo2B29sIFxKADEwWTTJNRmsHVImiiwSPwRYo2mcsCDhDrSaJEtp71BTYkEoTh7k-SlemNLGxaRF1bf-0WCyJFTCyw_eZZTxKkOM-KJ7L7tcQdDQGFYYUD21QgjN_ZqVwlc_k/s1600/DSC_9817.jpg&#34; imageanchor=&#34;1&#34; style=&#34;margin-left: 1em; margin-right: 1em;&#34;&gt;&lt;img border=&#34;0&#34; height=&#34;266&#34; src=&#34;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGYxxs0-sBo2B29sIFxKADEwWTTJNRmsHVImiiwSPwRYo2mcsCDhDrSaJEtp71BTYkEoTh7k-SlemNLGxaRF1bf-0WCyJFTCyw_eZZTxKkOM-KJ7L7tcQdDQGFYYUD21QgjN_ZqVwlc_k/s400/DSC_9817.jpg&#34; width=&#34;400&#34; /&gt;&lt;/a&gt;&lt;/div&gt;
</description>
    </item>
    <item>
      <title>Site Migration</title>
      <link>https://blog.ericdaugherty.com/2015/08/site-migration.html</link>
      <pubDate>Wed, 05 Aug 2015 23:34:00 +0000</pubDate>
      <guid>https://blog.ericdaugherty.com/2015/08/site-migration.html</guid>
      <description>&lt;p&gt;After hosting this site on GoDaddy for many years, I&amp;rsquo;ve decided to migrate it to Amazon via S3.  Amazon has some great features for hosting static websites, although I&amp;rsquo;ve gone &amp;lsquo;bare bones&amp;rsquo; to start.  I&amp;rsquo;m just hosting using an S3 bucket.  I&amp;rsquo;m using the &lt;a href=&#34;http://wwwizer.com/naked-domain-redirect&#34;&gt;wwwizer Naked Redirect service&lt;/a&gt; to redirect ericdaugherty.com to &lt;a href=&#34;https://www.ericdaugherty.com&#34;&gt;www.ericdaugherty.com&lt;/a&gt;, and then hosting the site out of a S3 bucket.&lt;/p&gt;
&lt;div&gt;
Amazon has a DNS server (Route 53) and CDN (CloudFront) that are easy and inexpensive to use, but I don&#39;t think I need them yet. &amp;nbsp;For now, I&#39;m still using GoDaddy&#39;s DNS Server and the wwwizer &#39;hack&#39; instead of Route 53.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
The previous site utilized somewhat of a &#39;poor man&#39;s&amp;nbsp;template engine&#39;. &amp;nbsp;I had a html file for each page on the site, but had Apache evaluate them as PHP files and I used PHP Includes to build up the page using common components.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
Moving to a fully static website meant I needed a real template engine. &amp;nbsp;I selected &lt;a href=&#34;http://jekyllrb.com/&#34;&gt;Jekyll&lt;/a&gt;&amp;nbsp;and migrated the site over. &amp;nbsp;It was a pretty straight forward migration and ended up reducing the size of each file as I could use a true template instead of just having common components.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
I then use the AWS console tool to upload the generated website files to S3 for an easy deployment, also allowing me to finally retire FileZilla from my tool chain.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
Amazon has some pretty good &lt;a href=&#34;http://docs.aws.amazon.com/AmazonS3/latest/dev/website-hosting-custom-domain-walkthrough.html&#34;&gt;guides&lt;/a&gt; to doing this, but I also used two good blog posts:&amp;nbsp;&lt;a href=&#34;http://nicluo.com/amazon-s3-on-domain-root-without-route-53/&#34;&gt;Amazon S3 on Domain Root, without Route 53&lt;/a&gt;&amp;nbsp;and&amp;nbsp;&lt;a href=&#34;http://www.michaelgallego.fr/blog/2013/08/27/static-website-on-s3-cloudfront-and-route-53-the-right-way/&#34;&gt;Static website on S3, CloudFront and Route 53, the right way!&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
The blog portion of the site is still hosted at Blogger, which has and continues to work well. &amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
This also forced me to make a few updates to the site, fixing some broken links and removing some no-longer-relevant sections.&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;
&lt;div&gt;
Plus it gave me an excuse to finally post on the blog.&lt;/div&gt;
</description>
    </item>
    <item>
      <title>USA Pro Cycling Challenge 2014</title>
      <link>https://blog.ericdaugherty.com/2014/08/usa-pro-cycling-challenge-2014.html</link>
      <pubDate>Tue, 26 Aug 2014 03:12:00 +0000</pubDate>
      <guid>https://blog.ericdaugherty.com/2014/08/usa-pro-cycling-challenge-2014.html</guid>
      <description>&lt;p&gt;The USA Pro Challenge rolled through Golden this weekend, and I headed to town to try to get some shots.&lt;/p&gt;
&lt;p&gt;See all the &lt;a href=&#34;http://www.smugmug.com/gallery/n-NqKSX/&#34;&gt;USA Pro Challenge 2014 pictures&lt;/a&gt; on Smugmug.&lt;/p&gt;
&lt;p&gt;The race brought them through Golden and then up Lookout Mountain.  They managed this climb in less than half the time of my best effort.  They came back and did a loop through downtown Golden before heading down to Denver for the finish.&lt;/p&gt;
&lt;p&gt;I positioned myself at the corner of Washington and 10th to catch them coming through downtown Golden, with the &amp;lsquo;Welcome to Golden&amp;rsquo; sign in the background.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://www.smugmug.com/gallery/n-NqKSX/i-rts9HH5/A&#34; title=&#34;Photo &amp;amp; Video Sharing by SmugMug&#34;&gt;&lt;img alt=&#34;Photo &amp;amp; Video Sharing by SmugMug&#34; src=&#34;http://www.smugmug.com/photos/i-rts9HH5/0/S/i-rts9HH5-S.jpg&#34; title=&#34;Photo &amp;amp; Video Sharing by SmugMug&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Here is the overall winner rolling through Golden:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://www.smugmug.com/gallery/n-NqKSX/i-XZ223rh/A&#34; title=&#34;&#34;&gt;&lt;img alt=&#34;&#34; src=&#34;http://www.smugmug.com/photos/i-XZ223rh/0/S/i-XZ223rh-S.jpg&#34; title=&#34;&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This lone rider nearly got clipped by one of the support cars&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://www.smugmug.com/gallery/n-NqKSX/i-zw6RPZP/A&#34; title=&#34;&#34;&gt;&lt;img alt=&#34;&#34; src=&#34;http://www.smugmug.com/photos/i-zw6RPZP/0/S/i-zw6RPZP-S.jpg&#34; title=&#34;&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://www.smugmug.com/gallery/n-NqKSX/i-TKbm7Vc/A&#34; title=&#34;&#34;&gt;&lt;img alt=&#34;&#34; src=&#34;http://www.smugmug.com/photos/i-TKbm7Vc/0/S/i-TKbm7Vc-S.jpg&#34; title=&#34;&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://www.smugmug.com/gallery/n-NqKSX/i-wTvBgPG/A&#34; title=&#34;Photo &amp;amp; Video Sharing by SmugMug&#34;&gt;&lt;img alt=&#34;Photo &amp;amp; Video Sharing by SmugMug&#34; src=&#34;http://www.smugmug.com/photos/i-wTvBgPG/0/S/i-wTvBgPG-S.jpg&#34; title=&#34;Photo &amp;amp; Video Sharing by SmugMug&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    <item>
      <title>Fireworks in Estes Park</title>
      <link>https://blog.ericdaugherty.com/2014/07/fireworks-in-estes-park.html</link>
      <pubDate>Sat, 05 Jul 2014 16:44:00 +0000</pubDate>
      <guid>https://blog.ericdaugherty.com/2014/07/fireworks-in-estes-park.html</guid>
      <description>&lt;p&gt;I spent the 4th of July in Estes Park this year, and enjoyed a great fireworks show in front of the Rocky Mountains.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://www.smugmug.com/gallery/n-GWSwC/i-VhvfkWv/A&#34; title=&#34;&#34;&gt;&lt;img alt=&#34;&#34; src=&#34;http://www.smugmug.com/photos/i-VhvfkWv/0/S/i-VhvfkWv-S.jpg&#34; title=&#34;&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://www.smugmug.com/gallery/n-GWSwC/i-Tv38F3v/A&#34; title=&#34;&#34;&gt;&lt;img alt=&#34;&#34; src=&#34;http://www.smugmug.com/photos/i-Tv38F3v/0/S/i-Tv38F3v-S.jpg&#34; title=&#34;&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://www.smugmug.com/gallery/n-GWSwC/i-mK7jZ79/A&#34; title=&#34;&#34;&gt;&lt;img alt=&#34;&#34; src=&#34;http://www.smugmug.com/photos/i-mK7jZ79/0/S/i-mK7jZ79-S.jpg&#34; title=&#34;&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://www.smugmug.com/gallery/n-GWSwC/i-mM95tS7/A&#34; title=&#34;&#34;&gt;&lt;img alt=&#34;&#34; src=&#34;http://www.smugmug.com/photos/i-mM95tS7/0/S/i-mM95tS7-S.jpg&#34; title=&#34;&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;See &lt;a href=&#34;http://www.smugmug.com/gallery/n-GWSwC/&#34;&gt;the rest of the pictures here&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    <item>
      <title>Alluvial Fan in Rocky Mountain National Park</title>
      <link>https://blog.ericdaugherty.com/2013/11/alluvial-fan-in-rocky-mountain-national.html</link>
      <pubDate>Sun, 01 Dec 2013 00:24:00 +0000</pubDate>
      <guid>https://blog.ericdaugherty.com/2013/11/alluvial-fan-in-rocky-mountain-national.html</guid>
      <description>&lt;p&gt;I got to visit Estes Park and Rocky Mountain National Park this weekend, which gave me a chance to see some of the flood damage from this fall.  Nothing was quite as impressive as the amount of material deposited at the base of the Alluvial Fan in RMNP.  The river used to run down the mountain and under the bridge.  The bridge survived, but as you can see, it no longer crosses the river.  The river used to flow about 15 feet under this bridge.  Now there is rock and sand above the level of the bridge&amp;hellip;&lt;/p&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;
&lt;a href=&#34;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5YEb5d_1XGY_weKhF9yWjp1rgU58yanTXWB4F4KmyLi0sasL6nWR1P3NzaFgkhpfs3HcZkNtSfP9AdfN07b4HOH3INDdRncp3XWYHKusM2jI7A1o_ckit2t3WStHKynuCh037xheSq7A/s1600/DSC_7638.jpg&#34; imageanchor=&#34;1&#34; style=&#34;margin-left: 1em; margin-right: 1em;&#34;&gt;&lt;img border=&#34;0&#34; height=&#34;267&#34; src=&#34;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5YEb5d_1XGY_weKhF9yWjp1rgU58yanTXWB4F4KmyLi0sasL6nWR1P3NzaFgkhpfs3HcZkNtSfP9AdfN07b4HOH3INDdRncp3XWYHKusM2jI7A1o_ckit2t3WStHKynuCh037xheSq7A/s400/DSC_7638.jpg&#34; width=&#34;400&#34; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Instead the river has redirected a bit farther west and took out the road.  The amount of material that was brought down was impressive.  These signs both used to be normal height&amp;hellip;&lt;/p&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;
&lt;a href=&#34;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh1WsdZALb_7T3VUE1PV8jOPtou0zokfL-kfmVJ5ztPGg13KD3O6lnKeiov_IFeS27znuHajQZmR4y7ln9sAHvgLUp7K96EwRL9XyLLDiwVSJejvPnoMfE2x54MTKau4i4WjeG5rN4z4Dc/s1600/DSC_7703.jpg&#34; imageanchor=&#34;1&#34; style=&#34;margin-left: 1em; margin-right: 1em;&#34;&gt;&lt;img border=&#34;0&#34; height=&#34;266&#34; src=&#34;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh1WsdZALb_7T3VUE1PV8jOPtou0zokfL-kfmVJ5ztPGg13KD3O6lnKeiov_IFeS27znuHajQZmR4y7ln9sAHvgLUp7K96EwRL9XyLLDiwVSJejvPnoMfE2x54MTKau4i4WjeG5rN4z4Dc/s400/DSC_7703.jpg&#34; width=&#34;400&#34; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;
&lt;a href=&#34;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgija6Y7X1a3zGN_4vUd5HJsOHiaLvoxK_Ox4rWmzEYWDB_8RKeiPK1yekrrvyM_8AQomzoPOhD1iLHptjhfShi6sf_56JAZc1t1Pmz96A4qR2vND2HGJ-N-b8GvyU2Qbg2UwVYZWk8pfg/s1600/DSC_7717.jpg&#34; imageanchor=&#34;1&#34; style=&#34;margin-left: 1em; margin-right: 1em;&#34;&gt;&lt;img border=&#34;0&#34; height=&#34;266&#34; src=&#34;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgija6Y7X1a3zGN_4vUd5HJsOHiaLvoxK_Ox4rWmzEYWDB_8RKeiPK1yekrrvyM_8AQomzoPOhD1iLHptjhfShi6sf_56JAZc1t1Pmz96A4qR2vND2HGJ-N-b8GvyU2Qbg2UwVYZWk8pfg/s400/DSC_7717.jpg&#34; width=&#34;400&#34; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;And this sign was taken out by the flow of water, sand, and rocks.&lt;/p&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;
&lt;a href=&#34;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjQrZTaCuREGf0uELbuDhUSzpQ0AqphXcaM6jZC_edT_qgjis_gbRKIVra3LpSIlNh1uj9KGSN9UWimSEH4I6csng_23fvg2T7uDSJ5HJXwp-nTcl7By6zkUnBJNkTT_LvhT1-MgPYMjr0/s1600/DSC_7731.jpg&#34; imageanchor=&#34;1&#34; style=&#34;margin-left: 1em; margin-right: 1em;&#34;&gt;&lt;img border=&#34;0&#34; height=&#34;266&#34; src=&#34;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjQrZTaCuREGf0uELbuDhUSzpQ0AqphXcaM6jZC_edT_qgjis_gbRKIVra3LpSIlNh1uj9KGSN9UWimSEH4I6csng_23fvg2T7uDSJ5HJXwp-nTcl7By6zkUnBJNkTT_LvhT1-MgPYMjr0/s400/DSC_7731.jpg&#34; width=&#34;400&#34; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Just about everything in this picture was not here previously.  All these rocks and sand was brought down during the flood.&lt;/p&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;
&lt;a href=&#34;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh1JwbSoH9CDnVcALOyib_pPVLniWwLF0UUP7cS6RXWFWM7xs_SHWl_-gqUrLf22L-yVuS0q0AUs9MAyjgt0I4MMnMSs1VVrzxZG6DU3NY-uM7iSJ6iKrYgqSmpEaMpM26JK8UpDhxdhh0/s1600/DSC_7745.jpg&#34; imageanchor=&#34;1&#34; style=&#34;margin-left: 1em; margin-right: 1em;&#34;&gt;&lt;img border=&#34;0&#34; height=&#34;266&#34; src=&#34;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh1JwbSoH9CDnVcALOyib_pPVLniWwLF0UUP7cS6RXWFWM7xs_SHWl_-gqUrLf22L-yVuS0q0AUs9MAyjgt0I4MMnMSs1VVrzxZG6DU3NY-uM7iSJ6iKrYgqSmpEaMpM26JK8UpDhxdhh0/s400/DSC_7745.jpg&#34; width=&#34;400&#34; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;
&lt;/div&gt;
</description>
    </item>
    <item>
      <title>Hiking with a DSLR - Part 3... and other accessories</title>
      <link>https://blog.ericdaugherty.com/2013/10/hiking-with-dslr-part-3-and-other.html</link>
      <pubDate>Wed, 30 Oct 2013 22:28:00 +0000</pubDate>
      <guid>https://blog.ericdaugherty.com/2013/10/hiking-with-dslr-part-3-and-other.html</guid>
      <description>&lt;p&gt;Every year or so I post about my camera setup, and the gear I use to carry it while hiking, etc.  Here is this year&amp;rsquo;s update&amp;hellip;&lt;/p&gt;
&lt;p&gt;You can go back and read &lt;a href=&#34;http://blog.ericdaugherty.com/2012/10/hiking-with-dslr-part-2.html&#34;&gt;last year&amp;rsquo;s post&lt;/a&gt;, or my &lt;a href=&#34;http://blog.ericdaugherty.com/2010/07/hiking-with-dslr-camera.html&#34;&gt;2010 post&lt;/a&gt; to see how I&amp;rsquo;ve changed over time.&lt;/p&gt;
&lt;p&gt;Since last year, I&amp;rsquo;ve upgraded from the Nikon D300 to the D600.  Since it is now a full frame, I also switched my primary lens to the D600 24-85mm f/3.5-4.5 kit lens.  Both the D600 body and the lens are smaller than D300/17-55 f2.8 I carried before, although I did give up some speed on my walk around lens.&lt;/p&gt;
&lt;h3&gt;
Camera Mount&lt;/h3&gt;
The &lt;a href=&#34;http://buy.cottoncarrier.com/Cotton-Carrier-UNIVERSAL-ADAPTER-PLATE-p/766nhp.htm&#34;&gt;Cotton Universal Adapter&lt;/a&gt; is constantly attached to my camera. &amp;nbsp;With its&amp;nbsp;Arca-Swiss compatibility, it can easily be mounted on my Tri-Pod and Mono-Pod, as well as carried using the Cotton system. &amp;nbsp;I also have the &lt;a href=&#34;http://buy.cottoncarrier.com/cotton-carrier-hand-strap-p/801chs.htm&#34;&gt;Cotton Hand Strap&lt;/a&gt; attached as my primary strap. &amp;nbsp;I don&#39;t like or use a neck strap.
&lt;h3&gt;
Hiking/Walking&lt;/h3&gt;
I continue to find the &lt;a href=&#34;http://buy.cottoncarrier.com/cotton-carrier-strapshot-p/211cps.htm&#34;&gt;Cotton Strap Shot&lt;/a&gt; to be the ideal carrier solution while hiking. &amp;nbsp;Mine never leaves my REI daypack, which I carry on just about every day hike. &amp;nbsp;It holds the camera securely and out of the way while hiking, but I can still have it up and ready to shoot in seconds. &amp;nbsp;The tether is a nice bit of extra security as I don&#39;t keep a neck strap on my camera.
&lt;p&gt;While I own the &lt;a href=&#34;http://buy.cottoncarrier.com/cotton-carrier-camera-vest-p/635rtl-s.htm&#34;&gt;Cotton Carrier Vest&lt;/a&gt; and &lt;a href=&#34;http://buy.cottoncarrier.com/cotton-carrier-side-holster-p/501hsl.htm&#34;&gt;Side Holster&lt;/a&gt;, they don&amp;rsquo;t see a ton of activity.  The vest is great if you want a really stable mount for your DSLR, but I find that for most activities where it would make sense (biking, skiing), I carry my GoPro instead.  I do occasionally use the side holster, and it is a good companion to a waist pack, where you have a heavier belt meant to support weight of it.  I used it once this year on a shorter hike where I just wore a waist pack and it worked well.&lt;/p&gt;
&lt;p&gt;For more casual walks, I use my &lt;a href=&#34;http://www.blackrapid.com/products/classic&#34;&gt;BlackRapid strap&lt;/a&gt;.  It screws right into the Cotton Universal Adapter so it is quick to throw on the camera and works great for casual walks where you are taking a lot of pictures.  It isn&amp;rsquo;t stable enough for more intense walks or hiking, but if you are walking around town it is a great option.  I&amp;rsquo;ve had my RS-1 since 2008, and it continues to perform well.  Well worth the $44 dollars I spent, although it looks to be a bit more expensive now.&lt;/p&gt;
&lt;h3&gt;
Tri/Mono Pods&lt;/h3&gt;
I have a Gitzo Basalt Tripod (GT2932) that continues to work very well, and a&amp;nbsp;Sirui P-326 Monopod that I like as well. &amp;nbsp;The Sirui&#39;s padding is starting to split a little after only 6 months, but it is fine otherwise. &amp;nbsp;Both with Arca-Swiss compatible mounts.
&lt;h3&gt;
GoPro&lt;/h3&gt;
In addition to the DSLR, I added a GoPro Hero3 to the mix this year as well. &amp;nbsp;I primarily use it while skiing, although I have used it while swimming and snorkeling in the ocean this year as well. &amp;nbsp;For carrying the GoPro, I use three main options:
&lt;p&gt;The &lt;a href=&#34;http://gopro.com/camera-mounts/chest-mount-harness&#34;&gt;GoPro Chesty&lt;/a&gt; works great if you are primarily shooting video of yourself skiing.  It provides a very stable platform (your torso), and makes the camera accessible to you, allowing you to control it directly instead of using a WiFi control (iPhone or Remote).&lt;/p&gt;
&lt;p&gt;The &lt;a href=&#34;http://gopro.com/camera-mounts/helmet-front-mount&#34;&gt;Helmet Mount&lt;/a&gt; works well if you are taking video of someone else skiing.  It allows you to steadily track them while you move back and forth across the mountain.  It is more difficult to control, and pairs well with a WiFi control, although this really drains the battery, especially in cold conditions.&lt;/p&gt;
&lt;p&gt;I like both of the above and end up switching back and forth during the season.  Either is a solid option.&lt;/p&gt;
&lt;p&gt;This summer I picked up the &lt;a href=&#34;http://gopro.com/camera-mounts/hero3-wrist-housing&#34;&gt;Wrist Housing&lt;/a&gt;, primarily for my trip to Hawaii for use while swimming/snorkeling.  I found it worked well.  It was not overly obtrusive while swimming, and easy to control, even underwater.  I also used it on a kayak trip, and again, it didn&amp;rsquo;t bother me while paddling and kept the GoPro available for nearly every opportunity.&lt;/p&gt;
&lt;h3&gt;
Bags&lt;/h3&gt;
My main camera bag is the Lowepro SlingShot 200. &amp;nbsp;I&#39;ve used this bag for about 4 years now, and it is my primary carry bag. &amp;nbsp;It fits the D600 with the 17-55 mounted, as well as my 80-200 and a couple prime lenses. &amp;nbsp;This is really my main carry solution, as it works well to carry the DSLR when you want to have your other lenses available as well. &amp;nbsp;You can quickly rotate the back from your back to your chest and pull out the camera. &amp;nbsp;It also provides a steady platform for changing lenses on the move. &amp;nbsp;It looks like the &lt;a href=&#34;http://store.lowepro.com/sling-bags/slingshot-202-aw&#34;&gt;Slingshot 202&lt;/a&gt; is the current iteration of this bag.
&lt;p&gt;I also added a travel bag, the Fastpack 350.  This allows me to carry my camera, all the lenses, as well as my laptop and assorted cables.  This is my carry on bag when flying.  Although I often throw my SlingShot in my checked luggage if I&amp;rsquo;m traveling for more than a weekend, so I can use it once I&amp;rsquo;ve arrived.&lt;/p&gt;
&lt;h3&gt;
Summary&lt;/h3&gt;
Overall, I&#39;m very pleased with the solutions I have in place. &amp;nbsp;I&#39;m not looking to replace any of the solutions I have now, although I&#39;m sure as new products come out I will be tempted.
&lt;p&gt;Note: I purchased almost everything myself, including the Cotton Carrier Vest, SideHolster and various adapters, but Cotton did provide me with the StrapShot and Universal Adapter plate for free.&lt;/p&gt;
</description>
    </item>
    <item>
      <title>Kauai Hawaii</title>
      <link>https://blog.ericdaugherty.com/2013/10/kauai-hawaii.html</link>
      <pubDate>Tue, 15 Oct 2013 01:23:00 +0000</pubDate>
      <guid>https://blog.ericdaugherty.com/2013/10/kauai-hawaii.html</guid>
      <description>&lt;p&gt;I spent last week relaxing on beautiful Kauai Hawaii.  Here are some of the &lt;a href=&#34;http://www.smugmug.com/gallery/n-RqSx9&#34;&gt;pictures I took during the visit&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://www.smugmug.com/gallery/n-RqSx9/i-ngnCbCj/A&#34; title=&#34;&#34;&gt;&lt;img alt=&#34;&#34; src=&#34;http://www.smugmug.com/photos/i-ngnCbCj/0/S/i-ngnCbCj-S.jpg&#34; title=&#34;&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://www.smugmug.com/gallery/n-RqSx9/i-HVLNV2r/A&#34; title=&#34;&#34;&gt;&lt;img alt=&#34;&#34; src=&#34;http://www.smugmug.com/photos/i-HVLNV2r/0/S/i-HVLNV2r-S.jpg&#34; title=&#34;&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://www.smugmug.com/gallery/n-RqSx9/i-XndKrSX/A&#34; title=&#34;&#34;&gt;&lt;img alt=&#34;&#34; src=&#34;http://www.smugmug.com/photos/i-XndKrSX/0/S/i-XndKrSX-S.jpg&#34; title=&#34;&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://www.smugmug.com/gallery/n-RqSx9/i-6BkcKBX/A&#34; title=&#34;&#34;&gt;&lt;img alt=&#34;&#34; src=&#34;http://www.smugmug.com/photos/i-6BkcKBX/0/S/i-6BkcKBX-S.jpg&#34; title=&#34;&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://www.smugmug.com/gallery/n-RqSx9/i-d4fgg6z/A&#34; title=&#34;&#34;&gt;&lt;img alt=&#34;&#34; src=&#34;http://www.smugmug.com/photos/i-d4fgg6z/0/S/i-d4fgg6z-S.jpg&#34; title=&#34;&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://www.smugmug.com/gallery/n-RqSx9/i-d5wXGXb/A&#34; title=&#34;&#34;&gt;&lt;img alt=&#34;&#34; src=&#34;http://www.smugmug.com/photos/i-d5wXGXb/0/M/i-d5wXGXb-M.jpg&#34; title=&#34;&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://www.smugmug.com/gallery/n-RqSx9/i-qdZ9Dsn/A&#34; title=&#34;&#34;&gt;&lt;img alt=&#34;&#34; src=&#34;http://www.smugmug.com/photos/i-qdZ9Dsn/0/S/i-qdZ9Dsn-S.jpg&#34; title=&#34;&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    <item>
      <title>Coyote Pups</title>
      <link>https://blog.ericdaugherty.com/2013/06/coyote-pups.html</link>
      <pubDate>Mon, 10 Jun 2013 23:08:00 +0000</pubDate>
      <guid>https://blog.ericdaugherty.com/2013/06/coyote-pups.html</guid>
      <description>&lt;p&gt;There are a new litter of Coyote pups in Rocky Mountain National Park (RMNP) this year near the Beaver Meadows visitor center.  I was able to capture a few good shots of the pups playing around near their den.&lt;/p&gt;
&lt;p&gt;See &lt;a href=&#34;http://www.smugmug.com/gallery/29917563_g369Vn&#34;&gt;all the pictures here&lt;/a&gt;, a few selected shots below&amp;hellip;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://www.smugmug.com/gallery/29917563_g369Vn#!i=2567380572&amp;amp;k=LRN5NdF&amp;amp;lb=1&amp;amp;s=A&#34; title=&#34;Photo &amp;amp; Video Sharing by SmugMug&#34;&gt;&lt;img alt=&#34;Photo &amp;amp; Video Sharing by SmugMug&#34; src=&#34;http://www.smugmug.com/photos/i-LRN5NdF/0/M/i-LRN5NdF-M.jpg&#34; title=&#34;Photo &amp;amp; Video Sharing by SmugMug&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://www.smugmug.com/gallery/29917563_g369Vn#!i=2567381312&amp;amp;k=LqH68Pp&amp;amp;lb=1&amp;amp;s=A&#34; title=&#34;Photo &amp;amp; Video Sharing by SmugMug&#34;&gt;&lt;img alt=&#34;Photo &amp;amp; Video Sharing by SmugMug&#34; src=&#34;http://www.smugmug.com/photos/i-LqH68Pp/0/M/i-LqH68Pp-M.jpg&#34; title=&#34;Photo &amp;amp; Video Sharing by SmugMug&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://www.smugmug.com/gallery/29917563_g369Vn#!i=2567381606&amp;amp;k=CjMB9HZ&amp;amp;lb=1&amp;amp;s=A&#34; title=&#34;Photo &amp;amp; Video Sharing by SmugMug&#34;&gt;&lt;img alt=&#34;Photo &amp;amp; Video Sharing by SmugMug&#34; src=&#34;http://www.smugmug.com/photos/i-CjMB9HZ/0/M/i-CjMB9HZ-M.jpg&#34; title=&#34;Photo &amp;amp; Video Sharing by SmugMug&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://www.smugmug.com/gallery/29917563_g369Vn#!i=2567382717&amp;amp;k=qbvzgpZ&amp;amp;lb=1&amp;amp;s=A&#34; title=&#34;Photo &amp;amp; Video Sharing by SmugMug&#34;&gt;&lt;img alt=&#34;Photo &amp;amp; Video Sharing by SmugMug&#34; src=&#34;http://www.smugmug.com/photos/i-qbvzgpZ/0/M/i-qbvzgpZ-M.jpg&#34; title=&#34;Photo &amp;amp; Video Sharing by SmugMug&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
  </channel>
</rss>
