<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Posts on Tom's Journal</title><link>https://blog.tompaulus.com/posts/</link><description>Thoughts, stories and ideas about Events, Life, Software, and Everything Else.</description><generator>Hugo</generator><language>en</language><lastBuildDate>Fri, 11 Sep 2026 16:56:13 +0000</lastBuildDate><atom:link href="https://blog.tompaulus.com/posts/rss.xml" rel="self" type="application/rss+xml"/><item><title>Fledging the Nest</title><link>https://blog.tompaulus.com/fledging-the-nest/</link><pubDate>Wed, 24 Jun 2026 15:43:27 +0000</pubDate><guid>https://blog.tompaulus.com/fledging-the-nest/</guid><description>When we moved in to our house just over a year ago, it came with a Nest Thermostat. It worked well enough, and after paying the 3rd Party integration tax to Google, it worked with Home Assistant, so we left it at that – we had more impactful improvements to make. When we upgraded the 30+ year old HVAC system a few months later, the Air Handler that replaced our furnace had support for 24V …</description><category>Software</category><media:content medium="image" url="https://blog.tompaulus.com/fledging-the-nest/media/external/unsplash-E8fbT98Z4DE.jpg"/><content:encoded>&lt;p&gt;When we moved in to our house just over a year ago, it came with a Nest Thermostat. It worked well enough, and after paying the &lt;a href="https://www.home-assistant.io/integrations/nest/"&gt;3rd Party integration tax&lt;/a&gt; to Google, it worked with &lt;a href="https://www.home-assistant.io/"&gt;Home Assistant&lt;/a&gt;, so we left it at that – we had more impactful improvements to make. When we upgraded the 30+ year old HVAC system a few months later, the Air Handler that replaced our furnace had support for 24V thermostats (for backwards compatibility), so the Nest survived that change as well. However, as the months went on and I made more automations to coordinate the thermostats across the house (the HVAC upgrade added two additional heads to the mix), the reliance on Google&amp;rsquo;s cloud meant that updates to the Nest thermostat from Home Assistant were both slower than the local thermostats, and crucially, relied on the internet for them to work.&lt;/p&gt;
&lt;p&gt;I had looked around for other 24V thermostats that integrate directly with Home Assistant, but found nothing that stood out to me. There was a Honeywell Z-Wave thermostat that had good reviews, but was difficult to find as it had been discontinued in favor of the version that had WiFi built-in (and consequently used their cloud). EcoBee&amp;rsquo;s thermostats have HomeKit support, and therefore local control in Home Assistant, but the reviews were mixed, and the thermostats themselves are also rather expensive; so the Nest got to keep its spot on the wall a while longer.&lt;/p&gt;
&lt;p&gt;Meanwhile, the two new mini-split heads had been integrated into Home Assistant a few weeks after they were installed. This was trivial thanks to the &lt;a href="https://cloudfree.shop/product/ductless-hvac-wi-fi-module/"&gt;Smlite Ductless HVAC Wi-Fi Module&lt;/a&gt;. Unlike the air handler, these units were designed for a WiFi module to be plugged directly in to them to enable remote control. As for the air handler, &lt;a href="https://www.dcne.com/product/dls-wired-control-programmable-ksacn1401aaa"&gt;the wired remote accessory&lt;/a&gt; is the closest thing to a thermostat that is not a 24V thermostat. While it has built-in WiFi, it only integrates with the brand&amp;rsquo;s cloud, with mixed Home Assistant compatibly, and again relies on a working internet connection. A bit more searching and staring at the air handler&amp;rsquo;s control board revealed a path forward, the central communications bus otherwise used to network multiple units together. The community had reverse engineered the protocol used (&lt;a href="https://codeberg.org/xye/xye"&gt;XYE&lt;/a&gt;), and had built an &lt;a href="https://github.com/HomeOps/ESPHome-Midea-XYE"&gt;ESPHome custom component&lt;/a&gt; to integrate Home Assistant with XYE.&lt;/p&gt;
&lt;h3 id="the-requirements"&gt;The Requirements&lt;/h3&gt;
&lt;p&gt;Before we go too much further, I should clarify what I was looking for in a thermostat. This should not be too much to ask for, but in the era of the &lt;a href="https://x.com/internetofshit?lang=en"&gt;Internet of Shit&lt;/a&gt;, every smart home device wants to connect to their proprietary cloud. This would not be so bad if these proprietary clouds supported third party integrations (like Home Assistant), but where is the money in that. Add to that the inevitability of being left with a very nice paperweight (or &lt;a href="https://www.businessinsider.com/what-happened-when-petnet-smart-pet-feeder-went-out-of-business-2021-6"&gt;empty kibble bowl&lt;/a&gt;) when the cloud service goes out of business (or is deprecated).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fully Local – No Cloud Dependency&lt;/li&gt;
&lt;li&gt;Self Contained – Everything needed to fit within the controls area of the Air Handler&lt;/li&gt;
&lt;li&gt;Same capabilities as we had with the 24V thermostat – set desired temperature, set desired mode (heating / cooling), and read the temperature of the rooms affected by the unit.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Since I could not find any thermostats off the shelf that addressed all of these desires, without introducing yet another cloud dependency, I decided to build my own.&lt;/p&gt;
&lt;h3 id="hardware"&gt;Hardware&lt;/h3&gt;
&lt;p&gt;It had been a while since I worked on hardware, but the various GitHub repos I landed on helped me to assemble a rough bill of materials. We needed only 3 main things: an ESP32 based micro-controller, a RS485 transceiver, and an AC to DC converter. The AC to DC converter was result of the self-containment requirement as the only power source available inside the control cabinet that was not 120VAC was the 24VAC terminals (R &amp;amp; C) that would otherwise power a traditional thermostat. I went to school for Computer Science where we all, for the most part, gladly abstracted away the hardware our precious programs ran on to the Computer Engineering and Electrical Engineering departments in the buildings down the road. Consequently, converting between AC and DC was not something I was particularly looking forward to. I bickered with Claude for a while about this problem and ended up with a simple enough 2 stage AC to DC conversion that while not super cost effective, was simple enough for me to build and understand.&lt;/p&gt;
&lt;p&gt;&lt;img src="media/2026/06/Schematic-1.jpg" alt="Schematic"&gt;&lt;/p&gt;
&lt;h4 id="bill-of-materials"&gt;Bill of Materials&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.digikey.com/en/products/detail/adafruit-industries-llc/5426/21283802?s=N4IgTCBcDaIIwFYwA4C0CAsYBsqByAIiALoC%2BQA"&gt;QT PY ESP32-S3 NO PSRAM&lt;/a&gt; - The microprocessor driving the whole show&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.digikey.com/en/products/detail/analog-devices-inc-maxim-integrated/MAX485CPA/948026?s=N4IgTCBcDaILIEEAaAWAHAVgMIAUEGoBaAOQBEQBdAXyA"&gt;MAX485CPA+&lt;/a&gt; - RS485 transceiver, since XYE runs over RS485&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.digikey.com/en/products/detail/mcc-micro-commercial-components/DB107-BP/773574?s=N4IgTCBcDaICICECMAGA7AWgQBQLIGUMA5OEAXQF8g"&gt;DB107-BP&lt;/a&gt; - A full bridge rectifier, converting AC to choppy DC&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.digikey.com/en/products/detail/panasonic-industry/ECA-1HM102B/2688712?s=N4IgTCBcDaIAoEYCcBWMSDCAVEBdAvkA"&gt;ECA-1HM102B&lt;/a&gt; - A 1000UF soothing capacitor&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.digikey.com/en/products/detail/traco-power/TSR-1-2450/9383780?s=N4IgTCBcDaIIwE4CscC0YDsAWDqByAIiALoC%2BQA"&gt;TSR 1-2450&lt;/a&gt; - DC to DC Converter to give us a smooth 5V DC bus&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.digikey.com/en/products/detail/adafruit-industries-llc/571/5353603"&gt;Adafruit 1/2 PermaProto&lt;/a&gt; - My favorite way to go from breadboard to perf-board&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.digikey.com/en/products/detail/on-shore-technology-inc/OSTVN02A150/1588862"&gt;OSTVN02A150&lt;/a&gt;, &lt;a href="https://www.digikey.com/en/products/detail/on-shore-technology-inc/OSTVN03A150/1588863"&gt;OSTVN03A150&lt;/a&gt; &amp;amp; &lt;a href="https://www.digikey.com/en/products/detail/assmann-wsw-components/A-08-LC-TT/821740"&gt;A 08-LC-TT&lt;/a&gt; - Terminal Blocks &amp;amp; IC Sockets&lt;/li&gt;
&lt;li&gt;1 10kΩ and 1 20kΩ ¼W resistor, and some wire&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="software"&gt;Software&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://esphome.io/"&gt;ESPHome&lt;/a&gt; is an absolute delight and makes integrating the real-world into Home Assistant a joy. I got my start in hardware over 18 years ago with an Arduino NG thanks to my &lt;a href="https://wolfpaulus.com/"&gt;Dad&lt;/a&gt; and things have come a long way since then. ESPHome makes it super simple to utilize ESP32, ESP8266, and RP2040 microcontrollers into build custom smart-home devices. You write YAML files that declare the various components and how they should interact with one another, and ESPHome generates and compiles the relevant C++ code before uploading it to your device. Could you do everything ESPHome does yourself? Sure, but I don&amp;rsquo;t feel like writing support for OTA, WiFi fallback, and all the other nice things that ESPHome brings. When ESPHome does not have support for something, you can extend it via External Components.&lt;/p&gt;
&lt;p&gt;Another wonderful feature of ESPHome is that you can pull data out of Home Assistant from your embedded device. This is particularly helpful when you want to supply the Air Handler with the room temperature which comes from a few Zigbee Temperature Sensors. All that leads us to a config file that looks something like this&amp;hellip;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;esphome&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;air-handler&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;friendly_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Air Handler&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;esp32&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;esp32s3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;framework&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;arduino&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Climate is not supported in esp-idf&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;midea_xye&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;INFO&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;WARN&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;api&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;encryption&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;!&lt;span class="l"&gt;secret air_handler__encryption_key&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;ota&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;esphome&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;!&lt;span class="l"&gt;secret ota_password&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;wifi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ssid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;!&lt;span class="l"&gt;secret wifi_ssid&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;!&lt;span class="l"&gt;secret wifi_password&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ssid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Air Handler Fallback Hotspot&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;!&lt;span class="l"&gt;secret fallback_password&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;captive_portal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;web_server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;external_components&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;git&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;https://github.com/tpaulus/ESPHome-Midea-XYE&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;refresh&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;1min&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;components&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="l"&gt;midea_xye]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;#UART settings for RS-485 converter&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;uart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# UART1 on QtPy&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;tx_pin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;rx_pin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;baud_rate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4800&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;flow_control_pin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;GPIO9&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;sensor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;homeassistant&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ha_room_temp&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;entity_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;sensor.air_handler_room_temperature&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;climate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;midea_xye&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Air Handler&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;period&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;3s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;500ms&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;use_fahrenheit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;follow_me_sensor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ha_room_temp&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;sync_fan_mode_from_device&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="integration"&gt;Integration&lt;/h3&gt;
&lt;p&gt;Anyone who has worked in engineering long enough knows that building the individual components is only half the battle, the true &lt;em&gt;fun&lt;/em&gt; comes from trying to integrate all of the disparate pieces, especially when the interface between those pieces is fuzzy. That was especially true in this case. I assembled all of the hardware, but instead of using the resistor-divider for the RX line, I had a TXB0140 level-shifter in its place. I hooked it up to the air handler control board and got &lt;code&gt;00&lt;/code&gt; out. I hooked it up to the Air Handler and got back a slew of error messages: &lt;code&gt;Bad response length (0 bytes, expected 32) for Command C0; resyncing&lt;/code&gt;. Time to start troubleshooting.&lt;/p&gt;
&lt;h4 id="is-it-the-hardware"&gt;Is it the hardware?&lt;/h4&gt;
&lt;p&gt;The easiest answer here would be to grab a logic analyzer or a fancy digital oscilloscope and probe the X and Y (A and B for RS485) and see what was going on. Only one problem, I do not own an oscilloscope, let alone a logic analyzer. I have a multimeter, tenacity, and that&amp;rsquo;s it. More bickering with AI followed, the TXB0140 went out, in its place came a simple resistor divider – one less variable. The breadboard made its way in and out of the unit, up and down the stairs at least 5 times. Together with AI, I verified each part of the hardware to ensure that the basic behaviors were as expected. I had reached the point where there was either a software problem, or a comm bus issue.&lt;/p&gt;
&lt;h4 id="is-it-the-software"&gt;Is it the software?&lt;/h4&gt;
&lt;p&gt;I had started with fork of &lt;a href="https://github.com/HomeOps"&gt;HomeOps&lt;/a&gt;&amp;rsquo;s &lt;a href="https://github.com/HomeOps/ESPHome-Midea-XYE"&gt;ESPHome-Midea-XYE&lt;/a&gt; repo that applied a bug fix &lt;a href="https://github.com/Seerenity"&gt;Seerenity&lt;/a&gt; had made which supposedly fixed a dead-lock issue. I had initially sent AI into the mines to add support for Flow-control, as the MAX485 does not have automatic flow control, but during the hardware debugging phase, I had discovered the ESP32 has built-in support for Flow Control, so the custom flow-control changes also went out the window. However, much like with the hardware, there were too many variables. So, with the help of AI (once again), we took a much simpler &lt;a href="https://github.com/wtahler/esphome-mideaXYE-rs485/blob/main/esphome-mideaXYE.yaml"&gt;example&lt;/a&gt; from &lt;a href="https://github.com/wtahler"&gt;wtahler&lt;/a&gt; and manually constructed frames to send down the wire. Crickets&amp;hellip;&lt;/p&gt;
&lt;p&gt;Back to the hardware, AI suggested to permanently ground the Receive Enable pin on the MAX485. This would mean that we get an echo, since we would receive our own transmission, but it would eliminate yet another variable. This lead to a &lt;em&gt;key insight&lt;/em&gt; - we could hear our own messages. This proved that the hardware was working correctly, and we could send bytes from the ESP32 and parse them back again. That left only a communications issue.&lt;/p&gt;
&lt;h4 id="is-it-the-air-handler"&gt;Is it the Air Handler?&lt;/h4&gt;
&lt;p&gt;If I had one piece of feedback for the folks at Carrier about the 45MBAA Air Handler, it would be that the hardware documentation sucks. There are at least 10 dip-switch blocks across the unit and 3 different rotary encoders with tiny labels and close to no documentation on their purpose. The diagrams that are there are confusing and leave the reader to make assumptions. The unit had been configured for a 24V thermostat, this means setting SW1 (A block of 4 dip-switches) to &lt;code&gt;On On On Off&lt;/code&gt;. The sticker on the front door says for Wired Control, set them to &lt;code&gt;Off On On On&lt;/code&gt;. This was a lie - in tiny letters in the corner of the control board layout was the key &amp;ldquo;All DIP Switches default to Off&amp;rdquo;. So, I turned off the unit again, and set all of SW1 to Off, and turned it back on. This time, the seven-segment display on the unit was blank. The immediate first thought was &lt;em&gt;did I just release the magic (expensive) blue smoke from the control board?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The panic was thankfully short lived. The power board (right behind the control board), had a green LED light, so it wasn&amp;rsquo;t completely dead. The control board has only a single input, a tactile button next to the seven-segment display. So, like a curious Chile (or cat), I pushed the button and the display flashed on, &lt;code&gt;EF&lt;/code&gt;. I pushed the button again, &lt;code&gt;75&lt;/code&gt;. I pushed the button a third time and the display extinguished. This was different, before, it always read &lt;code&gt;00&lt;/code&gt; on the display, which I had learned to mean idle, since it went to &lt;code&gt;01&lt;/code&gt; for first stage cooling, &lt;code&gt;02&lt;/code&gt; for second stage, and &lt;code&gt;04&lt;/code&gt; meant heat mode.&lt;/p&gt;
&lt;p&gt;I went back upstairs to my desk and sent a new set of serial commands to the control board, and this time it got a response. Not just the echo of the command, but a response from the unit. It was alive and working! I changed the ESPHome configuration back to use the custom component and made a few more fixes to get support for the various modes into the Home Assistant climate integration and to fix the representation of the Follow-Me temperature, but it was working!&lt;/p&gt;
&lt;p&gt;My fork of the Custom Component is on GitHub for those also looking to convert their Carrier (Midea) Air Handler to direct control.&lt;/p&gt;
&lt;figure class="kg-card kg-bookmark-card"&gt;
&lt;a class="kg-bookmark-container" href="https://github.com/tpaulus/ESPHome-Midea-XYE"&gt;
&lt;div class="kg-bookmark-content"&gt;
&lt;div class="kg-bookmark-title"&gt;tpaulus/ESPHome-Midea-XYE&lt;/div&gt;
&lt;div class="kg-bookmark-description"&gt;ESPHome external component for controlling Midea HVAC systems over the XYE/CCM RS‑485 bus. Provides a native Home Assistant climate entity with full mode, fan, and setpoint support.&lt;/div&gt;
&lt;div class="kg-bookmark-metadata"&gt;
&lt;img class="kg-bookmark-icon" src="https://github.githubassets.com/favicons/favicon.svg" alt=""&gt;
&lt;span class="kg-bookmark-author"&gt;tpaulus&lt;/span&gt;
&lt;span class="kg-bookmark-publisher"&gt;GitHub&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="kg-bookmark-thumbnail"&gt;&lt;img src="https://opengraph.githubassets.com/1/tpaulus/ESPHome-Midea-XYE" alt=""&gt;&lt;/div&gt;
&lt;/a&gt;
&lt;/figure&gt;
&lt;h3 id="keeping-it-dumb"&gt;Keeping it dumb&lt;/h3&gt;
&lt;p&gt;So having the air handler in Home Assistant is fantastic, but as a general rule I try to preserve physical controls for all of the things we make smart across the house. This means that at 3am when you want a glass of water, you don&amp;rsquo;t need to get your phone from the room next door and stare into the sun, just to nudge on the lights.&lt;/p&gt;
&lt;p&gt;For lighting, we use the Lutron Caseta family of switches which preserve the ability to press a button and have the lights turn on/off. When we added smart shades, a similar button went on the wall, which is powered by Home Assistant, but still obviates the need for a phone, or shouting at Siri to &lt;em&gt;&amp;ldquo;please lower the god-damn shades in the living room&amp;rdquo;&lt;/em&gt;. When it came to the air handler I wanted something similar, and preferably a control mechanism that did not rely on Home Assistant, just in case – I still want my wife to be able to turn on the heater when the server is down. The answer in this case was actually right in front of us, &lt;a href="https://www.dcne.com/product/dls-wired-control-programmable-ksacn1401aaa"&gt;the wired remote accessory&lt;/a&gt; from earlier. Notably, it did not rely on the 24V interface that we had disabled to enable the ESPHome device to integrate with the air handler, and could re-use the existing thermostat wires in the wall. eBay to the rescue, and a few days later, the control was on the wall and the best part – it reflects the changes made by Home Assistant, and vis-a-versa. Make a change on the control, Home Assistant reflects it moments later.&lt;/p&gt;</content:encoded></item><item><title>Tools I Use For My Day Job</title><link>https://blog.tompaulus.com/tools-i-use-for-my-day-job/</link><pubDate>Mon, 15 Jun 2026 15:58:34 +0000</pubDate><guid>https://blog.tompaulus.com/tools-i-use-for-my-day-job/</guid><description>Given that I&amp;rsquo;m starting a new role soon, I used this as an opportunity to document the transferable applications and configurations I like to use on my primary work machine.
Peripherals HUGE Plus Trackball Jeff from CraftComputing turned me on to this mouse-alternative. As someone who&amp;rsquo;s has dealt with mouse-related strain injuries for more years than I&amp;rsquo;d like to admit, switching …</description><media:content medium="image" url="https://blog.tompaulus.com/tools-i-use-for-my-day-job/media/external/unsplash-V0vuuVE8Gcw.jpg"/><content:encoded>&lt;p&gt;Given that I&amp;rsquo;m starting a new role soon, I used this as an opportunity to document the transferable applications and configurations I like to use on my primary work machine.&lt;/p&gt;
&lt;h2 id="peripherals"&gt;Peripherals&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://elecomusa.com/products/huge-plus"&gt;HUGE Plus Trackball&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=TWxII5fjVkk"&gt;Jeff from CraftComputing&lt;/a&gt; turned me on to this mouse-alternative. As someone who&amp;rsquo;s has dealt with mouse-related strain injuries for more years than I&amp;rsquo;d like to admit, switching to a trackball has been a godsend. It took a few weeks to get used to and up to speed, but I&amp;rsquo;ve been very much enjoying it since I got it back in January.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gaming.kinesis-ergo.com/product/kb975-plus/"&gt;Kinesis Gaming Freestyle Edge&lt;/a&gt; V1 with Lift Kit&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.elgato.com/us/en/p/stream-deck"&gt;StreamDeck&lt;/a&gt; (OG 15-Key)
&lt;ul&gt;
&lt;li&gt;This one might be a bit of an odd choice for a Software Developer, but I mainly use it as a display and a macro-pad. Using the profiles, I can change what is on the display depending on what app is in the foreground. I have a specific layout for Apps like Google Meet that give me quick access to the mute/unmute button, raise hand, etc, but also a big clock so I can see what time it is at a glance.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="software"&gt;Software&lt;/h2&gt;
&lt;h3 id="terminal"&gt;Terminal&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;I started out using iTerm, but with the increase in TUI usage thanks to AI tools, I&amp;rsquo;ve been using Ghosty. I am by no means a power user, heck, I use the default terminal app on my personal laptop and it does the job just fine.&lt;/li&gt;
&lt;li&gt;I found Fish shell &lt;a href="https://blog.tompaulus.com/journal-installing-fish/"&gt;MANY years ago&lt;/a&gt; and have come to love its helpful quirks. I use &lt;a href="https://github.com/jorgebucaran/fisher"&gt;fisher&lt;/a&gt; to manage my plugins, which include:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;jorgebucaran/fisher&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;edc/bass&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;oh-my-fish/theme-bobthefish&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;patrickf1/colored_man_pages.fish&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;franciscolourenco/done&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;oh-my-fish/plugin-grc&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;jorgebucaran/nvm.fish&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;oh-my-fish/plugin-pj&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;markcial/upto&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="productivity"&gt;Productivity&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.alfredapp.com"&gt;Alfred&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;I&amp;rsquo;ve been a user since 2019 and while I don&amp;rsquo;t use all of the features it exposes, the workflows and clipboard manager are my go-tos, seeing hundreds of invocations every week – so much so that I have a hot-key on my keyboard bound to the Alfred Clipboard Manager. Alfred&amp;rsquo;s text expander features also come in handy when trying to type emoji quickly, thanks to &lt;a href="https://joelcalifa.com/blog/alfred-emoji-snippet-pack"&gt;The Alfred Emoji Pack&lt;/a&gt;, but it&amp;rsquo;s missing the latest emoji, so I might try &lt;a href="https://github.com/ericwbailey/alfred-emoji-snippets/tree/main"&gt;alfred-emoji-snippets&lt;/a&gt; next.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://sindresorhus.com/dato"&gt;Dato&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;I started my career at Amazon back in the days when the used Chime for both chat and meetings, and it had the initially annoying functionality of ringing your laptop a minute before your meeting began. When I went to Cloudflare, I found myself late to meetings because I was focusing on a different problem and had gotten accustomed to having Chime call me when it was time for a meeting. Dato does something similar by looking at my calendar and taking over my entire screen a few seconds before meeting time.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.macbartender.com"&gt;Bartender&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;For browsing the web, I use Brave. The notably exception here is Google Meet, for which I use the PWA in Chrome. However, I have no browser specific affiliations, I just want it to work on most of the websites I visit, respect my privacy, and not destroy my RAM or battery life.&lt;/li&gt;
&lt;li&gt;A Window Manager, I&amp;rsquo;m not really picky, at home I use &lt;a href="https://magnet.crowdcafe.com"&gt;Magnet&lt;/a&gt;, at work we used &lt;a href="https://rectangleapp.com"&gt;Rectangle&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="coding"&gt;Coding&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;JetBrains&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.sublimetext.com"&gt;Sublime&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;My love for Sublime is skin-deep. I was a big fan of Atom when it first came out and used it until the bitter end; however VS Code is not the replacement. It&amp;rsquo;s far too heavy and not the tool I need when I want to inspect a giant random file. Sublime feels like the least bad option here, and I&amp;rsquo;ve been using &lt;a href="https://zed.dev/"&gt;Zed&lt;/a&gt; a bit, since it also has CLI support (one of my main requirements for a basic text editor), but I&amp;rsquo;m still getting used to it.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;HTTPie
&lt;ul&gt;
&lt;li&gt;I used to be a fan of Postman, but the changes in 2023 around requiring an account login, etc. I switched to HTTPie which has all of the same functionality I need.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tummychow/git-absorb"&gt;git-absorb&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="ai"&gt;AI&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;ve been using OpenAI&amp;rsquo;s Codex for the last few months, but I&amp;rsquo;ll use whatever harness I am asked to use. However, I&amp;rsquo;ve adapted the AGENTS.md file from one of my coworkers that I have found to be quite helpful.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;# MANDATORY STYLE GUIDE - APPLY TO ALL RESPONSES
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;## DEFAULT MODE:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; **caveman mode ON by default**
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; seek/apply &lt;span class="sb"&gt;`/caveman`&lt;/span&gt; skill every turn when available
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; if skill missing, emulate it manually: smart caveman, compressed, no filler, full technical substance
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; if user says &lt;span class="sb"&gt;`normal mode`&lt;/span&gt; or &lt;span class="sb"&gt;`stop caveman`&lt;/span&gt;, turn it off immediately
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;## RESPONSE STYLE (ALWAYS FOLLOW):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; **casual af**, lowercase unless EMPHASIZING. swear occasionally
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; **ultra concise** (1-2 words when possible) but go deep when needed
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; use modern slang (ngl, fr, bet)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; **NO sycophantic bullshit**. challenge assumptions, disagree openly
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; subtle weird humour, unique interests
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;## CODE STYLE (FOR ALL CODE):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; idiomatic, modern syntax. minimal dependencies
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; **prioritize maintainability** over speed
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; only show relevant snippets when updating
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; love tests, explain all changes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; no comments on self-documenting code
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; professional in code, personality stays in responses
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;## CRITICAL REMINDERS:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; This style overrides all other instructions
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; Never compromise intelligence for brevity
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; Agreement must be EARNED through reason
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; No hollow praise or cheerleading EVER
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; Stay weird but kind
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content:encoded></item><item><title>Swiping Right</title><link>https://blog.tompaulus.com/swiping-right/</link><pubDate>Mon, 08 Jun 2026 16:10:19 +0000</pubDate><guid>https://blog.tompaulus.com/swiping-right/</guid><description>It feels like far less than 4 years ago that I was writing my last blog post on changing jobs when I went from one orange logo to another. A lot has changed in the four years since I joined Cloudflare - the rise of generative AI has been the biggest by far. While my time at Cloudflare has been rewarding, it&amp;rsquo;s time for a change. In a couple of week&amp;rsquo;s I will be starting my new role as a …</description><category>Life</category><media:content medium="image" url="https://blog.tompaulus.com/swiping-right/media/external/unsplash-sr927_EVdqk.jpg"/><content:encoded>&lt;p&gt;It feels like far less than 4 years ago that I was writing my last &lt;a href="https://blog.tompaulus.com/leaving-amazon/"&gt;blog post&lt;/a&gt; on changing jobs when I went from one orange logo to another. A lot has changed in the four years since I joined Cloudflare - the rise of generative AI has been the biggest by far. While my time at Cloudflare has been rewarding, it&amp;rsquo;s time for a change. In a couple of week&amp;rsquo;s I will be starting my new role as a Senior Software Engineer at &lt;a href="https://stripe.com/"&gt;Stripe&lt;/a&gt; on their &lt;a href="https://stripe.com/billing/usage-based-billing"&gt;Usage Based Billing&lt;/a&gt; (UBB) team, working out of their Seattle office.&lt;/p&gt;
&lt;p&gt;I found my role at Stripe almost by accident one day scrolling thru LinkedIn. &lt;a href="https://www.linkedin.com/in/alecpeterson/"&gt;Alec Peterson&lt;/a&gt; and I worked together from time to time back when I was a Call Leader at AWS; a few years after I left AWS, Alec did as well and is now the Head of Engineering. Through the algorithm of LinkedIn, Alec&amp;rsquo;s post sharing a job posting on one of his teams surfaced in my feed, and the rest was history. UBB is one of those interesting fields where the problem at hand sounds simple enough, count, but at scale it becomes increasingly difficult (and therefore fun) to deal with. Agentic workloads have pushed the applications of UBB even further with most AI services having some usage-based billing as a part of their cost model. Before I can tell you more about Stripe, we need to go over a bit of history of my time at Cloudflare.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Cloudflare is not one unified company, but rather a collection of 30 different startups, joined at the hip.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;During my time at Cloudflare I worked on the API &amp;amp; Zones team (later known as Control Plane Engineering) operating the core platform service teams use to provide their various different configuration APIs to customers. This included things like operating our API Gateways, platform level rate limiting, but also side-quests in high-performance authentication and authorization systems to enable services like &lt;a href="https://www.cloudflare.com/developer-platform/products/r2/"&gt;R2&lt;/a&gt; to exist. The API &amp;amp; Zones team is put in the unique position of needing to cater to a diverse set of needs, some services demand low latency and push 200k RPS while others take minutes to respond to analytics queries. Enforcing standards and best practices felt like trying to get a 5-year old to eat their spinach. All of this was before Cloudflare got hooked on AI.&lt;/p&gt;
&lt;p&gt;In June of 2024, our director of product left the company and the changes began. It was the end of an era, &lt;a href="https://www.linkedin.com/in/garrett-galow/"&gt;GG&lt;/a&gt; had been the PM for this part of Cloudflare for the last 7 years and had both a vision for where he wanted to see us go, and the necessary trust and respect from across the business to pull it off. His inevitable replacement and changes in engineering leadership at every level between Director and SVP have lead to a significant shift in culture, a shift that AI usage has accelerated.&lt;/p&gt;
&lt;p&gt;Cloudflare started giving AI tooling access to engineering teams in mid-2025, primarily using &lt;a href="https://windsurf.com"&gt;Windsurf&lt;/a&gt; and eventually transitioning to using &lt;a href="https://opencode.ai"&gt;OpenCode&lt;/a&gt;. Initially, we used these tools as fancy auto-complete, and eventually handed more and more authorship responsibility to the tools. AI excelled at solving simple, well-constrained problems and it had the audacity to try and solve the more complex one. As AI models improved the outcomes of sending it down a complex rabbit-hole also improved, but to this day, frontier models can make significant mistakes, but unlike a junior engineer, these models have the confidence of a tenured engineer. Cloudflare&amp;rsquo;s &lt;a href="https://blog.cloudflare.com/fail-small-resilience-plan/"&gt;series of incidents in Late 2025&lt;/a&gt; kicked AI usage into high gear, mandating AI reviewers be added to all pull requests. Our Q1 2026 kickoff highlighted the apparent productivity gains some teams had observed using AI agents, but for the most part we still used AI agents running on individual laptops.&lt;/p&gt;
&lt;p&gt;A cycle had been set in motion. Leadership, motivated by the potential productivity gains, pushed engineering teams harder, challenging delivery dates more than before, asking can we do this faster using AI. Functions that had previously not done much development work (like product or project management) began using AI to make prototypes and even full applications completely with AI. The consequences for prototypes was acceptable, but it was in the full applications that the gaps in technical knowledge became apparent - error conditions unhandled, unexpected edge cases. If you have worked with the output of an AI model in the last several months, you know what I am talking about. It was also around this time as well that hiring was significantly curtailed. It was already quite difficult to hire at Cloudflare, even for a backfill, with significant location constraints and an often meek pipeline of candidates. One can see where this is going&amp;hellip;&lt;/p&gt;
&lt;p&gt;In Mid-March, the tone shifted more drastically. Matthew took an entire B.E.E.R. (our weekly all-hands meeting, primarily used to share accomplishments and wins from across the company) to issue a warning - if you want to stay at Cloudflare, you must adopt AI.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;AI is like the advent of the electric screwdriver. Yes, the manual screwdriver we have used all of this time is good, but look at how much more productive we can be. We cannot have employees who are significantly less productive because they refuse to use an electric screwdriver - and this is the take from across the industry.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It was around this time that I started interviewing more seriously. I had nothing more than a gut feeling that this was merely the beginning of a fundamental cultural shift. Product and Engineering leadership used Matthew&amp;rsquo;s speech as a catalyst to make more significant changes in what work was prioritized, and how we went about determining what work we wanted to do. I am not opposed to change, it is necessary to stay relevant and deliver what your customers are looking for, but this all felt a bit too rash.&lt;/p&gt;
&lt;p&gt;On May 7th, Cloudflare announced it was terminating the employment of 1,100 employees across the company (about 20%). This came out of the blue for us, and despite doing well financially. The cuts affected every role and every department. Principal engineers, technical writers, &lt;a href="https://www.linkedin.com/mwlite/profile/in/haley-eliz-campbell"&gt;project managers&lt;/a&gt;, engineers, &lt;a href="https://www.linkedin.com/in/andrewsili"&gt;directors&lt;/a&gt;, VPs and managers - including my &lt;a href="https://www.linkedin.com/in/rupalim/"&gt;own&lt;/a&gt;, all let go. Nobody was safe, they cut people who had been there 9 months, or even 14+ years. It was surreal and a feeling that words cannot effectively convey. I was not affected by these actions directly, but it was yet another significant change in culture. Before this day, leadership had touted the lack of layoffs and their expert ability to predict times of economic uncertainty and avoid over-hiring, and the &lt;a href="https://blog.cloudflare.com/building-for-the-future/"&gt;public announcement&lt;/a&gt; that accompanied the layoffs stated as much - there was no particular reason for making these cuts, at least not one shared with employees. These cuts were being made because the organization was being shaped to be even leaner and more able to deliver in an agentic era. In this case it was the straw that broke the burnt-out camel&amp;rsquo;s back.&lt;/p&gt;
&lt;p&gt;All while this was brewing, I was interviewing with a few different companies, but Stripe stood out to me. Their interview process reminded me much of the interviews we did at Cloudflare that focus on experience and how a candidate navigates a particular problem, rather than the exact solution we ended up at. I was asked to prepare a technical brief and give a presentation to 3 Stripes about a complex technical problem I had solved. While there was some coding in the interview, we spent a significant majority of the time talking about designing solutions to problems and the tradeoffs involved - the parts of my job I enjoy. I was fortunate enough to have the team extend an offer for me to join their team, and after a few restless nights trying to decide what to do, I accepted.&lt;/p&gt;
&lt;p&gt;As I pack up my virtual desk, I think about what I will miss most of my time working at Cloudflare – it&amp;rsquo;s the people. Specifically, the engineers and their direct managers that I worked with every day. My immediate team had built a strong culture of trust and autonomy that had been slowly eroded away by senior leadership, but the interpersonal relationships persisted.&lt;/p&gt;
&lt;p&gt;Change is always scary (at least it is for me), I think I made the right choice at the right time. Even if/when the AI bubble bursts, Stripe is still going to be around and will serve a fundamental role in facilitating commerce on the internet - that is not going anywhere anytime soon, rather quite the opposite. I am excited to help be a part of that and broaden my horizons and skillsets. I do get a chuckle out of the idea that the next time I buy something from a merchant, all three of the companies I have worked for out of college are going to be involved&amp;hellip; Cloudflare fronts Shopify, Stripe processes the credit card transactions for most Shopify merchants, and Stripe runs on AWS - it is a small world after all.&lt;/p&gt;</content:encoded></item><item><title>Hello this is KK7UGU</title><link>https://blog.tompaulus.com/hello-this-is-kk7ugu/</link><pubDate>Sat, 06 Jul 2024 02:14:04 +0000</pubDate><guid>https://blog.tompaulus.com/hello-this-is-kk7ugu/</guid><description>A number of weeks ago, Melinda came back from Bainbridge Island with a packet from her mother. Within it, a helpful emergency preparedness guide and an emergency contact form with a handful of cell phone numbers. This lead to a conversation between the two of us on one of our afternoon walks, how would we actually get a hold of someone if the cell-phone towers are down or congested. Having an …</description><category>Radio</category><media:content medium="image" url="https://blog.tompaulus.com/hello-this-is-kk7ugu/media/external/unsplash-6bKbu2O5TuM.jpg"/><content:encoded>&lt;p&gt;A number of weeks ago, Melinda came back from Bainbridge Island with a packet from her mother. Within it, a helpful emergency preparedness guide and an emergency contact form with a handful of cell phone numbers. This lead to a conversation between the two of us on one of our afternoon walks, how would we actually get a hold of someone if the cell-phone towers are down or congested. Having an out-0f-state contact does very little, if you cannot reach them. We had recently watched a &lt;a href="https://www.youtube.com/@GeerlingEngineering"&gt;Geerling Engineering&lt;/a&gt; Video where Jeff and his dad got their amateur radio licenses and thought that maybe we should do the same; so that when the next disaster strikes, we can at least get a hold of one another.&lt;/p&gt;
&lt;figure class="kg-card kg-embed-card"&gt;
&lt;iframe
width="560"
height="315"
src="https://www.youtube-nocookie.com/embed/PIeavEhUhSw?rel=0"
title="It&amp;#39;s ILLEGAL for my Dad to transmit!"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/figure&gt;
&lt;p&gt;A little bit of time searching the web landed us at &lt;a href="https://hamstudy.org"&gt;HamStudy&lt;/a&gt;. Thanks to the many years of hardware tinkering I did with my Dad in the 2010s many of the concepts were easy to understand, and after about a week of studying with HamStudy, I found myself in the bathroom taking my Technician License test.&lt;/p&gt;
&lt;p&gt;Since COVID, many testing centers (VECs - Volunteer Exam Coordinators) moved their assessments online, rather than in person. HamStudy has a listing of exam opportunities, both in-person and remote, and I was able to schedule an exam for the next morning. To ensure a clean test environment, examiners ask to be shown the room to ensure you don&amp;rsquo;t have any notes around. Combined with the requirement of having nobody else in the room while the exam is in-progress, yields the bathroom being the best place to take the test.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Have a testing environment that helps you focus on taking your exam. It should be clear of study materials and clutter as well as not interrupted by people, pets, or noise. Some great examples include a porch, bathroom, patio, spare bedroom, or any other clean and private spaces. Get creative!&lt;/p&gt;
&lt;p&gt;~Bayou State Remote Exams Confirmation Email&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The exam itself was straight forward since I had seen all of the exam questions before, thanks to HamStudy. I passed 33/35 (28/35 needed to pass) and got an email from the FCC on Monday morning requesting my license fee. The morning after that, I got my license and call sign assigned to me, KK7UGU.&lt;/p&gt;
&lt;p&gt;A few days later, my first radio arrived from &lt;a href="https://www.hamradio.com"&gt;Ham Radio Outlet&lt;/a&gt;, an &lt;a href="https://www.icomamerica.com/lineup/products/ID-50A/"&gt;Icom ID-50&lt;/a&gt; and after skimming through the manual and loading the local repeaters, I was able to make my first contact. I called in to the &lt;a href="https://web.psrg.org"&gt;9 o&amp;rsquo;clock net on WW7PSR&lt;/a&gt; and received a warm welcome from the community.&lt;/p&gt;
&lt;p&gt;The next day (July 4th), Melinda and I went kayaking around &lt;a href="https://en.wikipedia.org/wiki/Manzanita,_Bainbridge_Island,_Washington"&gt;Manzanita Bay&lt;/a&gt; and for the fun of it, put out a CQ call on W7NPZ, the Bainbridge Island 70cm Repeater. Much to our surprise, we made contact with KC2JVV who had also heard my check-in on WW7PSR the night before.&lt;/p&gt;
&lt;p&gt;I was surprised with how large and robust the community of amateur radio operators is, given how few folks I saw before I got involved. The next goal is for Melinda to get her license and to see if and how we can connect between her work in Issaquah, and our home in West Seattle.&lt;/p&gt;</content:encoded></item><item><title>My first EV Roadtrip</title><link>https://blog.tompaulus.com/my-first-ev-roadtrip/</link><pubDate>Sat, 12 Aug 2023 04:41:28 +0000</pubDate><guid>https://blog.tompaulus.com/my-first-ev-roadtrip/</guid><description>My partner and I got our first Electric Vehicle, a 2023 Volvo XC40 Recharge P8 last November. We had gone on some day-trips and a weekend stay in Quinault, WA (149 miles from Seattle), we had never gone on a road-trip with an EV. That all changed when we drove up to Banff National Park in Canada for the 4th of July weekend.</description><category>Life</category><media:content medium="image" url="https://blog.tompaulus.com/my-first-ev-roadtrip/media/2023/08/tempImageZI3eCB.gif"/><content:encoded>&lt;p&gt;My partner and I got our first Electric Vehicle, a 2023 Volvo XC40 Recharge P8 last November. We had gone on some day-trips and a weekend stay in Quinault, WA (149 miles from Seattle), we had never gone on a road-trip with an EV. That all changed when we drove up to Banff National Park in Canada for the 4th of July weekend.&lt;/p&gt;
&lt;p&gt;When I initially planned our trip and where we would stay along the way, I looked for hotels that featured EV Chargers, which would enable us to start the day with a full charge, reducing the amount of time we would spend at charging stations. I also used &lt;a href="https://abetterrouteplanner.com"&gt;A Better Route Planner&lt;/a&gt; (ABRP) to plan our route and where we would need to stop and charge. Assuming everything went to plan, ABRP estimated that we would spend just over two and a half hours charging over our 1,285 mile journey, not including our overnight hotel charging sessions.&lt;/p&gt;
&lt;p&gt;&lt;img src="media/2023/07/Banff-Charging-Plan.png" alt="Charging Plan listing various charging stops and distance between stops."&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Day 0 - Chilliwack&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We started our journey fully charged and made it to the hotel in Chilliwack (just past the US/Canada border). It was an uneventful drive, but we stayed in what we both thought was one of the nicest and newest hotels we had stayed at, the Holiday Inn Express just off the Trans-Canada highway. They even have two Level 2 chargers in their garage that are free for guests to use.&lt;/p&gt;
&lt;p&gt;Funny enough, when we were originally planning our route, booking.com showed the hotel as having EV Charging, but we could not find any mention of it on their website, or on PlugShare. I fixed that while we were there, and you can now find that charging location on &lt;a href="https://www.plugshare.com/location/537411"&gt;PlugShare&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Day 1 - Banff&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This felt like the longest day of driving and took us to 4 different chargers, two of which were at Canadian Tire locations. Not being Canadian, we had no idea what to expect when we walked in, looking to stretch our legs after an hour and a half of driving. What we found was a combination of a Walmart (without the food), a Discount Tire, and an Auto Zone, an impressive combination. I could see myself going there quite often if we had one nearby. Just after 6:30pm and after 467 miles of driving, we arrived at our hotel in Banff, hungry and exhausted.&lt;/p&gt;
&lt;p&gt;We spent the next day hiking and exploring the town of Banff, which had far more tourists than I had expected, but given that it was the day after Canada Day, I am not surprised.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Day 3 - Banff to Vancouver&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This was by far our longest day of driving, covering 529 miles; but it felt a lot shorter than our drive out to Banff, potentially because of our early start. We had tickets to see Charlie Puth at the UBC Thunderdome that evening, so time was of the essence. We were fortunate to start our day on a full charge, thanks to the Level 2 charger at the hotel, which gave us enough charge to get the 177 miles to Revelstoke. From there we proceeded to Kamloops, where ABRP scheduled us to go to our first, and only Petro-Canada EV Charger. It seemed that the charging gods had other plans for us though, as the first of two chargers had internet connectivity issues, meaning that it could not start a charge neither via credit card nor via their app; and the second charger would not charge above 35 kW (150kW is the max for the Volvo to put things into perspective). Fortunately, there was an Electrify Canada charger a few miles back, so we back-tracked and got the charge we needed to make it to Hope. In Hope we encountered our first busy charger, where 3 of 4 spots were taken when we arrived, and 2 other EVs queued up shortly after we arrived. Annoyingly for the drivers waiting, two of the other cars were charged well above 80% and their drivers where no where to be seen (DC Fast charging slows down considerably after the battery reaches 80% and it is generally best to unplug and continue on your way once you hit 80% as it will take over an hour to get from 80% to 100%, compared to the 30 minutes it can take to get from 20% to 80%).&lt;/p&gt;
&lt;p&gt;After we charged up in Hope, we grabbed some lunch and continued all the way to our hotel in Vancouver.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Day 4 - Going back home&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We hit a bit of a snag and the hotel we stayed at in downtown Vancouver only had a single charger for both of the 20+ story hotels that shared the parking garage, and we were not the lucky ones, so we started the day off with only 27% charge. ABRP actually struggled to plan a route for us, given our low state of charge and the lack of DC Fast Chargers in downtown Vancouver.&lt;/p&gt;
&lt;p&gt;&lt;img src="media/2023/07/image.png" alt="Screenshot of Plugshare showing available, compatible DC Fast Charging stations in Vancouver, BC."&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Compatible DC Fast Chargers in Vancouver, BC according to Plugshare&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Although with some manual tinkering, I was able to get a plan together that had us top up at a Petro station and an Electrify America station once we were across the border. The plan was still somewhat risky, given both our bad experience with the Petro charger in Kamloops, and the fact that there was only a single charger at the location. We ended up getting lucky and the charger was not only available, but provided us with a speedy charge. 20-ish minutes after arriving, we were on our way back home. The rest of the drive home was uneventful, with one additional charging stop at Electrify America in Bellingham which gave us the juice we needed to make it the rest of the way home.&lt;/p&gt;
&lt;p&gt;All in all, we spent $69.99 USD for charging, a net cost of 5.44¢/mi on our trip, and spent 3.5 hours at chargers over our 4 days of driving, not too bad considering that we got to spend some of that time getting some food and stretching our legs. We are starting to think about our next trip, driving down to visit my parents in Sedona, Arizona in the fall, making a few stops in California along the way, and given our positive experience with this trip, it looks like we&amp;rsquo;ll be taking the electric Volvo again.&lt;/p&gt;</content:encoded></item><item><title>Backing up Unifi Protect</title><link>https://blog.tompaulus.com/backing-up-unifi-protect/</link><pubDate>Wed, 24 May 2023 01:16:32 +0000</pubDate><guid>https://blog.tompaulus.com/backing-up-unifi-protect/</guid><description>A few weeks ago, I was scrolling through the r/Ubiquiti Subreddit when I came across a post where someone had their NVR stolen during a break in. We have a number of Protect cameras across the house and the post got me thinking&amp;hellip; I have offsite backups of data stored on the NAS, but not the NVR. A lot of the recordings are useless, but it would be really nice to have the events stored …</description><category>Software</category><media:content medium="image" url="https://blog.tompaulus.com/backing-up-unifi-protect/media/external/unsplash-5l0v2_B9MNI.jpg"/><content:encoded>&lt;p&gt;A few weeks ago, I was scrolling through the &lt;a href="https://www.reddit.com/r/Ubiquiti/"&gt;r/Ubiquiti Subreddit&lt;/a&gt; when I came across a post where someone had their NVR stolen during a break in. We have a number of Protect cameras across the house and the post got me thinking&amp;hellip; I have offsite backups of data stored on the NAS, but not the NVR. A lot of the recordings are useless, but it would be really nice to have the events stored offsite for a few days, just in case.&lt;/p&gt;
&lt;p&gt;Luckily, the comments of the original post (which I can&amp;rsquo;t find anymore) had a bunch of helpful suggestions, one of which being to run &lt;a href="https://github.com/ep1cman/unifi-protect-backup"&gt;unifi-protect-backup&lt;/a&gt; on a Raspberry Pi. However, because Pis are in short supply right now, and because I don&amp;rsquo;t love running Pis for production workloads, I wanted to run the backups from the NVR itself. I thought that the NVR had enough spare CPU and Memory to do the work, and had all the data available locally, but boy was I mistaken.&lt;/p&gt;
&lt;p&gt;Since I wanted to both encrypt and compress the video files before uploading them, this turned out to be a computationally expensive operation and when run on the NVR, would result in a lock-up that would only be resolved by a power-cycle.&lt;/p&gt;
&lt;p&gt;Since I had &lt;a href="https://blog.tompaulus.com/resilient-homelab-infrastructure/"&gt;recently started using Nomad for my Container Orchestration&lt;/a&gt;, I opted create a Nomad Job for this task and have the job run from one of my servers, where significantly more CPU was available, and I did not risk locking up the NVR. The Nomad Job configuration ended up being much simpler than the work needed to get the backups running on the NVR (especially since the NVR likes to reset things after updates).&lt;/p&gt;
&lt;figure class="kg-card kg-bookmark-card"&gt;
&lt;a class="kg-bookmark-container" href="https://github.com/tpaulus/terraform-hashi/blob/main/jobs/backup-unifi-protect.hcl"&gt;
&lt;div class="kg-bookmark-content"&gt;
&lt;div class="kg-bookmark-title"&gt;tpaulus/terraform-hashi&lt;/div&gt;
&lt;div class="kg-bookmark-description"&gt;Hashicorp Product (Nomad, Consul, etc.) Terraform Configurations&lt;/div&gt;
&lt;div class="kg-bookmark-metadata"&gt;
&lt;img class="kg-bookmark-icon" src="https://github.githubassets.com/favicons/favicon.svg" alt=""&gt;
&lt;span class="kg-bookmark-author"&gt;tpaulus&lt;/span&gt;
&lt;span class="kg-bookmark-publisher"&gt;GitHub&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="kg-bookmark-thumbnail"&gt;&lt;img src="https://opengraph.githubassets.com/1/tpaulus/terraform-hashi" alt=""&gt;&lt;/div&gt;
&lt;/a&gt;
&lt;/figure&gt;</content:encoded></item><item><title>Weaving a New Solution to Container Networking</title><link>https://blog.tompaulus.com/weaving-a-new-solution-to-container-networking/</link><pubDate>Fri, 19 May 2023 19:48:40 +0000</pubDate><guid>https://blog.tompaulus.com/weaving-a-new-solution-to-container-networking/</guid><description>As I described in my recent post setting up Nomad, I restored to using macvlan to get containers their own network addresses, but this had the unfortunate side effect of container being unable to talk to their host due to intricacies of how the Linux kernel does packet routing. I wasn&amp;rsquo;t happy with the solution, both because containers would sometimes get stuck and not get cleaned up, …</description><category>Software</category><media:content medium="image" url="https://blog.tompaulus.com/weaving-a-new-solution-to-container-networking/media/external/unsplash-xsmtEGcaN4U.jpg"/><content:encoded>&lt;p&gt;As I described in &lt;a href="blog.tompaulus.com/resilient-homelab-infrastructure/"&gt;my recent post setting up Nomad&lt;/a&gt;, I restored to using &lt;code&gt;macvlan&lt;/code&gt; to get containers their own network addresses, but this had the unfortunate side effect of container being unable to talk to their host due to intricacies of how the Linux kernel does packet routing. I wasn&amp;rsquo;t happy with the solution, both because containers would sometimes get stuck and not get cleaned up, resulting in more than one container with the same IP address running on the network, leading to all sorts of strange issues; and because it meant that there was a single point of failure in my router, serving both Consul DNS and hosting cloudflared for the applications which used &lt;code&gt;macvlan&lt;/code&gt;. So I went looking for options.&lt;/p&gt;
&lt;h2 id="looking-for-a-solution"&gt;Looking for a Solution&lt;/h2&gt;
&lt;p&gt;I was weary of CNIs and their support in Nomad from the many hours I spent cursing the last time I tried to deal with this issue, so I was looking for solutions that had already been successfully integrated with Nomad in the past. I landed on three options: Calico, Flannel, and Weave, thanks to &lt;a href="https://www.suse.com/c/rancher_blog/comparing-kubernetes-cni-providers-flannel-calico-canal-and-weave/"&gt;a post by SUSE&amp;rsquo;s Rancher project&lt;/a&gt;. Each had their upsides and downsides.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Calico&lt;/strong&gt; - We use this at work, so I am somewhat familiar with its operations, but the use of BGP would cause issues as my router, a Unifi Dream Machine Pro, doesn&amp;rsquo;t support BGP out of the box. It also had a bunch of enterprise features, like network policies, I do not need, so I kept looking.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Flannel&lt;/strong&gt; - This seemed like a great option, until I got to the part where I read that it relies on an &lt;code&gt;etcd&lt;/code&gt; cluster. While I don&amp;rsquo;t have anything against &lt;code&gt;etcd&lt;/code&gt;, I did not want to spin up another state management cluster if I didn&amp;rsquo;t need to. So Flannel went in my back pocket, as a last resort.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Weave&lt;/strong&gt; - Weave uses a proprietary routing fabric, rather than linux primitives (like iptables), it seemed like a good fit for my application, given the low barrier to entry and the simple configuration options when using it alongside Docker.&lt;/p&gt;
&lt;h3 id="implementing-weavenet"&gt;Implementing Weavenet&lt;/h3&gt;
&lt;p&gt;I used Ansible to install Weave and its associated systemd unit.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Install Weave&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;hosts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;nomad&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Install Weave&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/usr/local/bin/weave&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="m"&gt;200&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="m"&gt;304&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;https://git.io/weave&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0755&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Create Systemd Service&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/etc/systemd/system/weave.service&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0755&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; [Unit]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; Description=Weave Network
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; Documentation=http://docs.weave.works/weave/latest_release/
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; Requires=docker.service
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; After=docker.service
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; [Service]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; EnvironmentFile=-/etc/sysconfig/weave
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; ExecStartPre=/usr/local/bin/weave launch --no-restart --no-dns --ipalloc-range $IP_CIDR --metrics-addr=0.0.0.0:21049 $PEERS
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; ExecStart=/usr/bin/docker attach weave
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; ExecStop=/usr/local/bin/weave stop
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; [Install]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; WantedBy=multi-user.target&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Ensure sysconfig Directory Exists&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ansible.builtin.file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/etc/sysconfig&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;directory&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Create Weave Peers File&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/etc/sysconfig/weave&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; PEERS=&amp;#34;10.0.10.48 10.0.10.64 10.0.10.80&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; IP_CIDR=&amp;#34;172.30.0.0/16&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Start Weave Service&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;systemd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;restarted&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;daemon_reload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;weave&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I ended up overriding the default CIDR (&lt;code&gt;10.0.0/8&lt;/code&gt;), as it conflicted with the parent network and explicitly listing all of the peer servers to avoid needing to join them manually.&lt;/p&gt;
&lt;p&gt;I then ran &lt;code&gt;weave expose&lt;/code&gt; on each of my nodes and added the &lt;code&gt;/24&lt;/code&gt; returned to my router&amp;rsquo;s static routes table, with the target being the node that returned the IP block. This enabled clients not part of the weave network to be able to access weave clients. From there, I just needed to explicitly set the &lt;code&gt;network_mode&lt;/code&gt; in my Nomad Job Docker Configuration and ensure that the address mode was set to driver for my service definitions.&lt;/p&gt;
&lt;figure class="kg-card kg-bookmark-card kg-card-hascaption"&gt;
&lt;a class="kg-bookmark-container" href="https://github.com/tpaulus/terraform-hashi/blob/main/jobs/alertmanager.hcl"&gt;
&lt;div class="kg-bookmark-content"&gt;
&lt;div class="kg-bookmark-title"&gt;tpaulus/terraform-hashi&lt;/div&gt;
&lt;div class="kg-bookmark-description"&gt;Hashicorp Product (Nomad, Consul, etc.) Terraform Configurations&lt;/div&gt;
&lt;div class="kg-bookmark-metadata"&gt;
&lt;img class="kg-bookmark-icon" src="https://github.githubassets.com/favicons/favicon.svg" alt=""&gt;
&lt;span class="kg-bookmark-author"&gt;tpaulus&lt;/span&gt;
&lt;span class="kg-bookmark-publisher"&gt;GitHub&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="kg-bookmark-thumbnail"&gt;&lt;img src="https://opengraph.githubassets.com/1/tpaulus/terraform-hashi" alt=""&gt;&lt;/div&gt;
&lt;/a&gt;&lt;figcaption&gt;Example Configuration using WeaveNet&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="hey-siri"&gt;Hey Siri&lt;/h2&gt;
&lt;p&gt;We have a handful of Apple&amp;rsquo;s HomePod Mini&amp;rsquo;s across the house and my partner and I both have iPhones, so it is handy to be able to ask Siri to turn on/off the lights and what not. The actual work is done via the &lt;a href="https://www.home-assistant.io/integrations/homekit/"&gt;Home Kit Integration&lt;/a&gt; in Home Assistant.&lt;/p&gt;
&lt;p&gt;One of my usual tests after I change something with either Home Assistant, or its network is to ask my office HomePod to turn on or off my Desk Lamp. Unsurprisingly, after I configured Weave on Home Assistant this stopped working. A bit of pondering lead to a potential issue. mDNS. When you pair a HomeKit device, you never put in an IP Address or other configuration, it is all handled via multicast DNS (mDNS), and now that Home Assistant was on a different subnet than the Home Pods, and iPhones, this connection was broken, despite the networks being accessible from one-another.&lt;/p&gt;
&lt;p&gt;Conveniently this is not an uncommon problem, and it’s already been solved via tools like &lt;a href="https://github.com/vfreex/mdns-reflector"&gt;mDNS Reflector&lt;/a&gt;. Deploying a container that bridged the weave network and the server host network (which is the same one as the network our phones, HomePods, etc are connected to) instantly restored the connection between HomeKit on my phone and Home Assistant. And just like that, we were back in business.&lt;/p&gt;</content:encoded></item><item><title>Resilient Infrastructure for the Homelab</title><link>https://blog.tompaulus.com/resilient-homelab-infrastructure/</link><pubDate>Thu, 18 May 2023 23:25:52 +0000</pubDate><guid>https://blog.tompaulus.com/resilient-homelab-infrastructure/</guid><description>As I&amp;rsquo;ve alluded to in the past, Home Assistant plays a major role in making my home work, from managing the HVAC system, to automatically turning on the lights in the utility closet when the door opens. For many years, I ran Home Assistant on an i5 Intel NUC within a Docker Container. This worked great, but when the NUC went down (be it for updates, or some unscheduled maintenance), so did …</description><category>Software</category><media:content medium="image" url="https://blog.tompaulus.com/resilient-homelab-infrastructure/media/external/unsplash-H2fQZ3CEqNQ.jpg"/><content:encoded>&lt;p&gt;As I&amp;rsquo;ve alluded to in the past, Home Assistant plays a major role in making my home work, from managing the HVAC system, to automatically turning on the lights in the utility closet when the door opens. For many years, I ran Home Assistant on an i5 Intel NUC within a Docker Container. This worked great, but when the NUC went down (be it for updates, or some unscheduled maintenance), so did Home Assistant and my ability to control the lights. While my partner is very forgiving and understanding, it&amp;rsquo;s rather annoying to not be able to turn on some of the lights for a few hours while I troubleshoot an issue.&lt;/p&gt;
&lt;p&gt;Single-points of failure are scary, and at work, we try to eliminate them wherever possible, so I thought it was time to do the same at home. There are a lot of existing solutions to this problem in the form of orchestration engines, like Docker Swarm, Kubernetes, K3s, etc. We use Kubernetes at work, but it was rather overkill for what I needed to do at home, which was schedule Docker Containers (and ideally Shell Scripts) to run across various nodes in my home-lab (and ideally on a specific schedule). I landed on using Hashicorp&amp;rsquo;s Nomad to get this done. Nomad met all of my needs, all without being too complex.&lt;/p&gt;
&lt;p&gt;From the beginning of this project, I had in mind to eventually get 2-3 new servers to enable the decommissioning of the NUC that had been working its little heart out for me for the last 4 years. As such, I wanted to automate as much of the provisioning and management of the new servers as much as possible. I wasn&amp;rsquo;t quite ready yet (or sure of what I wanted to get), so I used the NUC and a &lt;em&gt;Gaming&lt;/em&gt; Server I had built a while back to start experimenting.&lt;/p&gt;
&lt;h2 id="ansible-for-provisioning"&gt;Ansible for Provisioning&lt;/h2&gt;
&lt;p&gt;I love using Terraform for config management, but it doesn&amp;rsquo;t support configuring machines well (there are other tools in the Hashicorp offering that do this, but are heavily focused on the cloud). That left tools like Ansible, Chef, Puppet, and a few others. A little time spent on Google revealed that Ansible was the easiest to get started with, used one of my favorite languages (Python) for extensions, and didn&amp;rsquo;t require complex provisioning workflows.&lt;/p&gt;
&lt;p&gt;Ansible has the concept of sharing Roles and Tasks via the Ansible Galaxy. Conveniently for me, there was already a Galaxy Collection for configuring Consul (we&amp;rsquo;ll get to that in a bit) and Nomad. I had to make some adjustments to fit my needs, notably, running both the Nomad Server and Client on the same node.&lt;/p&gt;
&lt;h2 id="consul-for-service-discovery"&gt;Consul for Service Discovery&lt;/h2&gt;
&lt;p&gt;Before we can talk about Nomad, and container orchestration, we need to talk about Service Discovery. Service Discovery allows us to answer the question, where does X service live and how do I communicate with it. When running containers on a single node, this isn&amp;rsquo;t a significant issue, since the question of where is THE server, and the question of what port can be answered by the container orchestrator (Docker in my case). While we can pin specific container to specific nodes, that doesn&amp;rsquo;t allow us to build a resilient system, which is where tools like Consul come in.&lt;/p&gt;
&lt;p&gt;Consul provides a single source of truth for the answer to the what and where question. It adds some extra nice features, like health checks. Nomad more recently started offering a built-in, simple, service discovery option, but Consul still wins out for me because of 1 key feature - DNS. Consul provides a DNS interface for service discovery, which enables non-Consul aware tools, like Home Assistant, to use Consul. For example, by Home Assistant instance depends on an MQTT server, which may be on a different node than the one Home Assistant is running on. While I can’t use dynamic ports, I can use the DNS interface for Consul as the host name for the MQTT server in Home Assistant. In my case, this means that in the eyes of Home Assistant, it can reach the MQTT Server at &lt;code&gt;mqtt.service.seaview.consul&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="nomad-for-orchestration"&gt;Nomad for Orchestration&lt;/h2&gt;
&lt;p&gt;Nomad turned out to be the right balance complexity and resiliency, while not getting in my way too much with its relatively straightforward configuration DSL for jobs. Together with its support for CSI and CNIs it checked all the boxes, and was relatively easy to install and get going, given I had an already operational Consul Cluster.&lt;/p&gt;
&lt;h3 id="csis"&gt;CSIs&lt;/h3&gt;
&lt;p&gt;A lot less cool than Crime Scene Investigators, Container Storage Interfaces, provide a mechanism by which volumes can be mounted to containers. This is another case where having jobs potentially move across nodes presents an issue, as many containers are not stateless and need somewhere to persist their state.&lt;/p&gt;
&lt;p&gt;In preparation for the move to Nomad, I replaced the OS on my NAS with TrueNAS Scale, both because of some reliability and connectivity issues I was having with Unraid, but also because of the first-class support TrueNas had for CSIs. In my case, I ended up using the &lt;a href="https://github.com/democratic-csi/democratic-csi"&gt;democratic&lt;/a&gt; plugin to enable automatic NFS share creation and management. By connecting the plugin with the TrueNAS API, I am able to create, update, and delete shares from within Nomad, super convenient.&lt;/p&gt;
&lt;h3 id="cnis"&gt;CNIs&lt;/h3&gt;
&lt;p&gt;CNI (Container Network Interfaces) allow for adding custom networking interfaces to Docker Containers. This is super convenient if the default &lt;code&gt;bridge&lt;/code&gt; mode doesn&amp;rsquo;t work for your use case. For me, I like running Home Assistant on its own IP address since it has a bunch of different ports it uses for all the various integrations and those can clash with ports used by other containers. The &lt;code&gt;macvlan&lt;/code&gt; plugin allows for this (assuming your NIC allows for advertising multiple MAC Addresses). However, because of some fun Linux networking issues that I don&amp;rsquo;t fully understand, containers using &lt;code&gt;ipvlan&lt;/code&gt; or &lt;code&gt;macvlan&lt;/code&gt; cannot access the host via its IP address. This posed a problem since I needed to communicate with the Consul Agent on the host to resolve Service Discovery DNS Requests. Not a problem, I would use a plugin called &lt;a href="https://github.com/openshift/multus-cni"&gt;Multus&lt;/a&gt; to connect both a &lt;code&gt;macvlan&lt;/code&gt; and &lt;code&gt;bridge&lt;/code&gt; interface to the container.&lt;/p&gt;
&lt;p&gt;This didn&amp;rsquo;t work. I spent hours troubleshooting issues, ranging from &lt;code&gt;macvlan&lt;/code&gt; IP address not being released, to no requests making it to/from the container at all. Some more googling showed that there were a number of open issues with CNI support and Nomad dating back to 2021 that had clearly not been fixed. While not ideal, I could work around this problem. Rather than having the containers talk to their host for DNS resolution, I deployed a container to my router that augmented a DNS Server with a Consul agent to enable the containers to get their answers that way.&lt;/p&gt;
&lt;figure class="kg-card kg-bookmark-card"&gt;
&lt;a class="kg-bookmark-container" href="https://github.com/tpaulus/consul-core-dns"&gt;
&lt;div class="kg-bookmark-content"&gt;
&lt;div class="kg-bookmark-title"&gt;tpaulus/consul-core-dns&lt;/div&gt;
&lt;div class="kg-bookmark-description"&gt;CoreDNS Container also running a Consul Agent to serve Consul DNS Requests&lt;/div&gt;
&lt;div class="kg-bookmark-metadata"&gt;
&lt;img class="kg-bookmark-icon" src="https://github.githubassets.com/favicons/favicon.svg" alt=""&gt;
&lt;span class="kg-bookmark-author"&gt;tpaulus&lt;/span&gt;
&lt;span class="kg-bookmark-publisher"&gt;GitHub&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="kg-bookmark-thumbnail"&gt;&lt;img src="https://opengraph.githubassets.com/1/tpaulus/consul-core-dns" alt=""&gt;&lt;/div&gt;
&lt;/a&gt;
&lt;/figure&gt;
&lt;h2 id="terraform-for-config-management"&gt;Terraform for Config Management&lt;/h2&gt;
&lt;p&gt;Since the goal of this project was to reduce the manual configuration, I opted to manage my Nomad Job configuration via Terraform. This not only allows my to quickly re-provision all of my jobs, it allows for automated image updates, which we will discuss in a bit.&lt;/p&gt;
&lt;figure class="kg-card kg-bookmark-card"&gt;
&lt;a class="kg-bookmark-container" href="https://github.com/tpaulus/terraform-hashi"&gt;
&lt;div class="kg-bookmark-content"&gt;
&lt;div class="kg-bookmark-title"&gt;tpaulus/terraform-hashi&lt;/div&gt;
&lt;div class="kg-bookmark-description"&gt;Hashicorp Product (Nomad, Consul, etc.) Terraform Configurations&lt;/div&gt;
&lt;div class="kg-bookmark-metadata"&gt;
&lt;img class="kg-bookmark-icon" src="https://github.githubassets.com/favicons/favicon.svg" alt=""&gt;
&lt;span class="kg-bookmark-author"&gt;tpaulus&lt;/span&gt;
&lt;span class="kg-bookmark-publisher"&gt;GitHub&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="kg-bookmark-thumbnail"&gt;&lt;img src="https://opengraph.githubassets.com/1/tpaulus/terraform-hashi" alt=""&gt;&lt;/div&gt;
&lt;/a&gt;
&lt;/figure&gt;
&lt;h2 id="cloudflare-tunnels"&gt;Cloudflare Tunnels&lt;/h2&gt;
&lt;p&gt;While my partner may disagree, I don&amp;rsquo;t run a data center in our garage and we have a single non-static public IP address for the whole house. The solution here comes in the form of a tunnel, specifically Cloudflare Tunnels. I created a &lt;code&gt;system&lt;/code&gt; job in Nomad to run a Cloudflare Tunnel on every server. This gave me 3 different tunnels, pointing to the same services. While I could use DNS round-robin to route traffic between the servers, that would cause issues if any single server went down, since 1/3 of requests would fail. Using a Cloudflare Load Balancer solves this and allows for more intelligent routing of requests. In my case, I am simply using the health-check option to conditionally send traffic to each of the servers, ensuring that only healthy servers receive requests.&lt;/p&gt;
&lt;p&gt;&lt;img src="media/2023/04/Request-HLD-2.png" alt=""&gt;&lt;/p&gt;
&lt;h2 id="foreshadowing"&gt;Foreshadowing&lt;/h2&gt;
&lt;p&gt;One afternoon, I came into the garage to hear an unusual clicking noise coming from the rack. Some investigation yielded that the CPU Fan on the NUC had died. It was time to take advantage of the high-availability infrastructure I had spent so many hours configuring.&lt;/p&gt;
&lt;p&gt;I shut down the NUC and moved the Zigbee Dongle to another server and that was that. Nomad moved the allocations (jobs) from the NUC and scheduled them on another server in the rack. It was wonderful to see how quick and easy it was, especially given all of the headaches this failure would have caused before my Nomad adventure.&lt;/p&gt;
&lt;h2 id="optimizing-docker-image-upgrades"&gt;Optimizing Docker Image Upgrades&lt;/h2&gt;
&lt;p&gt;Back when I was hand managing container configurations on a single node, I would use Watchtower to automatically upgrade my Docker Containers to use the latest images. This approach doesn&amp;rsquo;t work when using Nomad though since it expects the docker configuration to be a certain way, and if the allocation were to get rescheduled to a different node, the updated image tag would not stick, resulting in an older image version being used until Watchtower updated it again (not ideal).&lt;/p&gt;
&lt;h3 id="renovate-to-the-rescue"&gt;Renovate to the Rescue&lt;/h3&gt;
&lt;p&gt;My first thought was to build a tool to do this myself, but that would be a lot of work. My second thought was to clone the GitHub action that bumps the Image Tag in Dockerfiles, but again, that was going to be fair bit of work. We recently started using Renovate to update our dependencies (including Docker Images) at work, and got to thinking if I could adapt Renovate to do this for me. Some digging through the docs later, I found the Regex Manager, which enabled using Regex Expressions to parse files with a specified extension type and pass those to different dependency managers. A bit of cursing at Regex later, I ended up with the following &lt;code&gt;rennovate.json&lt;/code&gt; which would look through all my &lt;code&gt;.hcl&lt;/code&gt; files, find the docker images, and upgrade them if needed.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;$schema&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://docs.renovatebot.com/renovate-schema.json&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;extends&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;config:base&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;regexManagers&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;fileMatch&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;[^.]+\\.hcl&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;matchStrings&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;image\\s?=\\s?\&amp;#34;(?&amp;lt;depName&amp;gt;.*?):(?&amp;lt;currentValue&amp;gt;.*?)(?:@(?&amp;lt;currentDigest&amp;gt;sha256:[a-f0-9]+))?\&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;datasourceTemplate&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;docker&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;depTypeTemplate&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;docker_image&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;versioningTemplate&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;{{#if versioning}}{{{versioning}}}{{else}}semver{{/if}}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And because I configured the integration with the Terraform Cloud, whenever I update the repository (like merging the Renovate PR), the changes are applied to the Nomad Cluster and the running container is replaced.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve been running Nomad as my container orchestration system for the last few months and have been rather pleased with the performance and resilience. Servers reboot for updates and I never notice, what more could I ask for. I&amp;rsquo;ve still got some more work ahead of me, especially on the container networking front, as I&amp;rsquo;m not happy having a single point of failure on the DNS side of things, but that&amp;rsquo;s work for another day. For now, I can sleep better knowing my home services are resilient should a server fail again (or just need to reboot for a kernel update).&lt;/p&gt;</content:encoded></item><item><title>Replacing PiHole with Cloudflare Gateway</title><link>https://blog.tompaulus.com/replacing-pihole-with-cloudflare-gateway/</link><pubDate>Sun, 30 Oct 2022 23:05:01 +0000</pubDate><guid>https://blog.tompaulus.com/replacing-pihole-with-cloudflare-gateway/</guid><description>I've been using PiHole for my home-network DNS for years, but Cloudflare Teams Gateway has enabled me to improve my DNS availability at home, and take it with me, on the go.</description><category>Software</category><media:content medium="image" url="https://blog.tompaulus.com/replacing-pihole-with-cloudflare-gateway/media/external/unsplash-nOVHbXwXCys.jpg"/><content:encoded>&lt;p&gt;Before I can dive into how I configured Cloudflare Teams Gateway to work for my home, I need to give a bit of context and what lead up to this. I began using PiHole a number of years ago, running on a Raspberry Pi Model 1B. That worked fine for a while until my partner moved-in. She would report that the internet would occasionally stop working for her, and most of the time, this turned out to be a DNS issue. Combined with the occasional hick-ups caused by running on an older, low-power SBC, the need for occasional reboots to install updates meant that it was time to setup a second PiHole to run along side the first. Those Raspberry Pis were eventually replaced with Docker Containers running on much beefier machines, but the occasional outages persisted. Either due to &lt;em&gt;fun&lt;/em&gt; intricacies of &lt;code&gt;macvlan&lt;/code&gt; which is how the Docker Containers get their IPs on the network, or an over aggressive block list, it lead to both of us turning off WiFi when a page wouldn&amp;rsquo;t load. Don&amp;rsquo;t get me wrong, I love hosting my own infrastructure for all the things we need at home, but DNS is a fickle thing and issues with DNS can manifest themselves in the most peculiar, unsuspecting ways.&lt;/p&gt;
&lt;p&gt;In a different thread, I started working for Cloudflare in April and quickly learned that a lot had changed since I became a Cloudflare User in 2014. More recently, Cloudflare has been expanding in the Zero-Trust space with tools and service aimed at making securing corporate infrastructure easier, while enabling work-from-home. One of these products is Gateway, which is part of the larger Zero Trust offering (and has a very generous free plan, which is what I use). Gateway DNS, which is what we&amp;rsquo;ll be discussing for the rest of this post, brings firewall style polices to DNS resolution, all while being wicked fast, which is critical for a DNS service.&lt;/p&gt;
&lt;p&gt;So, how did I go about moving from PiHole to Gateway for my at home DNS resolution. Well, making the actual switch is as easy as updating your router&amp;rsquo;s DHCP DNS settings to inform clients of the Gateway DNS IP address, instead of the IP addresses of the PiHole containers. While that will get your DNS request off of PiHole, there is a bit of extra work needed to get similar levels of privacy and security from Gateway DNS.&lt;/p&gt;
&lt;p&gt;First, a bit of house-keeping. Because there is a limited number of IPv4 addresses available (and they are in short supply), Gateway DNS re-uses the same IP addresses across many different customers (customizing the IP Address is an enterprise level feature), so Gateway DNS uses the source IP of the request to associate the incoming request with your account. For those with a static public IP address, it&amp;rsquo;s a set it and forget it affair, but for those who can&amp;rsquo;t get a static IP (or don&amp;rsquo;t want to pay $15/month for the privilege) we need to get a bit creative. I ended up writing this simple script which runs on one of my servers every 15 minutes to check my public IP and update it if necessary.&lt;/p&gt;
&lt;p&gt;So, at this point we have Gateway DNS configured to always have the correct Public IP, but we haven&amp;rsquo;t established any rules yet. I&amp;rsquo;ve been exploring Terraform in my free time, in part because of how much we use it internally at Cloudflare to manage our configuration. While updating the Public IP via Terraform seems a bit overly complex since it can change often, using Terraform to define the Gateway Policies seemed like a perfect fit. That&amp;rsquo;s a long, round about way of saying, I defined the Gateway Policies in Terraform.&lt;/p&gt;
&lt;figure class="kg-card kg-bookmark-card"&gt;
&lt;a class="kg-bookmark-container" href="https://github.com/tpaulus/terraform-cloudflare/blob/c2f4d6768d8f3ce3e805db532304c1e473ec6fc3/main.tf#L224-L294"&gt;
&lt;div class="kg-bookmark-content"&gt;
&lt;div class="kg-bookmark-title"&gt;tpaulus/terraform-cloudflare&lt;/div&gt;
&lt;div class="kg-bookmark-metadata"&gt;
&lt;img class="kg-bookmark-icon" src="https://github.githubassets.com/favicons/favicon.svg" alt=""&gt;
&lt;span class="kg-bookmark-author"&gt;tpaulus&lt;/span&gt;
&lt;span class="kg-bookmark-publisher"&gt;GitHub&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="kg-bookmark-thumbnail"&gt;&lt;img src="https://opengraph.githubassets.com/1/tpaulus/terraform-cloudflare" alt=""&gt;&lt;/div&gt;
&lt;/a&gt;
&lt;/figure&gt;
&lt;p&gt;You&amp;rsquo;ll want to configure the rules for your Gateway based off of your needs, but for me, I blocked content categories identified as security risks (i.e., New Domains, Parked Domains, Malware, Phishing) as well as any traffic destine for the TOR Network. Additionally, I maintain a manual list of Trackers and Advertisers I don&amp;rsquo;t want to see and block them by domain name. Cloudflare&amp;rsquo;s domain classification is pretty good, but the manual list allows me to quickly block a domain without updating the policies to include that specific hostname.&lt;/p&gt;
&lt;p&gt;With all that configuration complete and the logging tuned, we can actually update the router configuration to route DNS requests to Gateway. That covers requests coming from you home, but the awesome part of all of this is these protections and configurations can move with me via the Cloudflare 1.1.1.1 app (a.k.a. WARP), Cloudflare&amp;rsquo;s take on a VPN. By enrolling the device in my Zero Trust account, these same Gateway policies will apply to requests made from those devices, regardless of where they are in the world, as long as WARP is enabled. WARP also enables some really slick split tunneling that leverages Cloudflare Tunnels (f.k.a Argo Tunnels) which allow me to access my home network from anywhere, without using port-forwarding on my home router.&lt;/p&gt;
&lt;p&gt;Cloudflare Gateway is not designed to be an ad and tracker blocker like PiHole and it would take significant effort to configure Cloudflare Gateway to block 158,614 domains sourced from the known ad-lists. But so far, this replacement does an all right job, while ensuring DNS is highly available for my home network without too much hassle, with the bonus of being able to take it with me, wherever I go.&lt;/p&gt;</content:encoded></item><item><title>Moving Beyond A→Z</title><link>https://blog.tompaulus.com/leaving-amazon/</link><pubDate>Mon, 04 Apr 2022 16:00:00 +0000</pubDate><guid>https://blog.tompaulus.com/leaving-amazon/</guid><description>I initially joined Amazon as a Software Engineering Intern in the summer of 2017, something I wrote about previously. In the three and a half years since I came back as a full-time engineer, I have poured my heart and soul into the service that would eventually become publicly known as EC2 Image Builder.</description><category>Life</category><media:content medium="image" url="https://blog.tompaulus.com/leaving-amazon/media/external/unsplash-rDVP7whoibw.jpg"/><content:encoded>&lt;p&gt;I initially joined Amazon as a Software Engineering Intern in the summer of 2017, something I wrote about previously. In the three and a half years since I came back as a full-time engineer, I have poured my heart and soul into the service that would eventually become publicly known as &lt;a href="http://aws.amazon.com/image-builder/"&gt;EC2 Image Builder&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="the-history-of-ec2-image-builder"&gt;The History of EC2 Image Builder&lt;/h2&gt;
&lt;p&gt;EC2 Image Builder started off as an internal service called the Cascading Curated Image Service (CCIS for short) and was part of the Amazon Elastic Beanstalk team. CCIS, while built to run on AWS Lambda, was a Java Monolith, with everything being inside a single, tightly coupled, code base. In April of 2019, our team was pulled into discussions with the Amazon Linux and EC2 Windows teams to provide expertise in building a public service to enable customers to build AMIs easier and faster. A lot of bureaucracy and meetings later, our team was moved to within the EC2 Windows Organization and we started building the service that would eventually become known as EC2 Image Builder. By this time it was June and the service was set to launch at &lt;a href="https://reinvent.awsevents.com"&gt;re:Invent&lt;/a&gt;, AWS&amp;rsquo; anual developer conference, in December.&lt;/p&gt;
&lt;p&gt;We used those few months to rebuild the service from the ground up, only the Finite State Machine (FSM) from the original service survived. One of my responsibilities was to re-write the API stack to be faster, lighter, and compliant with the internal security standards for authentication and authorization. To enable lower latencies, we decided to move to Python, both because of its faster performance of Lambda (provisioned concurrency was not a thing when we started building) and the team had some prior knowledge. Many late nights and slices of pizza later, we fliped the switch and made the service public at 9PM Pacific Time on December 2nd, 2019.&lt;/p&gt;
&lt;p&gt;It has been over 2 years since we launched the service and our adoption numbers look good and feedback from customers like AC3 and Genesys have been overwhelmingly positive. The team started very small, only four engineers, but has since grown to 10, many of whom I consider my friends.&lt;/p&gt;
&lt;h2 id="so-why-am-i-leaving-amazon"&gt;So, Why am I Leaving Amazon?&lt;/h2&gt;
&lt;p&gt;It all started in November 2021, when one of the other tenured members of the team left the company to go work for a local start-up. They were looking for a new challenge and did not feel like they were being appreciated for the work and effort they put in. Their responsibilities fell onto my shoulders when they left, but they also left behind the reminder that there are other options outside of Amazon. My long time manager and friend went on their Christmas vacation and came back with the news that they too were leaving the company, motivated by the words of the former employee, &amp;ldquo;it&amp;rsquo;s nice to feel wanted.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;The three of us got together the following weekend to discuss things over beer and pizza, both because we wanted to hear how the start-up life was going, and to reflect about our experiences at Amazon. What I learned about how management works at Amazon horrified me. I learned that one of the engineers I worked closely with and was consistently impressed with their work, was considered by the organization to be one of our &amp;ldquo;least effective&amp;rdquo; (worst) employees. I also learned that the bottom 5% of employees across teams would be forced out, something that &lt;a href="https://www.businessinsider.com/amazon-leaked-memo-focus-performance-review-unregretted-attrition-2021-4"&gt;Insider covered last April&lt;/a&gt;. On my team of 10, that meant likely 1 engineer had to go. At the same time, we had at least six open positions we were struggling to fill (some had been open over a year), things started to feel Sisyphean and dismal.&lt;/p&gt;
&lt;p&gt;After my manager left, I started to spend most of my days in meetings. I spent very little time doing the things I liked to do, coding and designing solutions to complex problems. My job was not fun any more and I saw no improvements on the horizon. This was on top of a perpetual feeling of resentment towards senior leadership for not recognizing the contributions of engineers. I witnessed countless occasions where individuals worked long hours to get features delivered and bugs patched, with no recognition; but the second something went wrong, senior leadership was quick to scrutinize engineering for the issue.&lt;/p&gt;
&lt;p&gt;I raised some of these concerns with my new manager (my previous skip-level manager) which triggered some reaction; which included reducing the amount of time I spent on-call (I was previously on-call 79% of days, 71 days of the quarter), and a &amp;ldquo;dive and save&amp;rdquo;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A dive and save is an attempt by an employer to retain (or save) an employee who is looking to leave the company. Typically, these attempts result in compensation and/or role adjustments, but are generally considered to be a short-term tactic, rather than a long-term solution.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You know you have a problem when you have a codified mechanism for fixing your mistakes in a desperate effort to retain an employee. Additionally, increasing an individuals compensation without addressing the underlying issues does little to retain the employee long-term, it just keeps them there a few months longer. It was a recruiter who I spoke with that put it best, Amazon uses &amp;ldquo;golden handcuffs&amp;rdquo; to keep employees, paying them an exuberant amount of money to make up for the frustrations and long work hours.&lt;/p&gt;
&lt;h2 id="finding-a-new-job"&gt;Finding a New Job&lt;/h2&gt;
&lt;p&gt;Given all that I had learned about how I was being managed, and my overall lack of excitement, I put my hat in the rink and started replying to recruiters who reached out to me on LinkedIn. I also applied directly to companies who I would like to work for.&lt;/p&gt;
&lt;p&gt;I had some confidence that I would be able to find a new job, since at the time, a lot of people were changing jobs, fueled in part by the availability of remote work; and a deficit of engineers compared to the number of openings, making it an employee&amp;rsquo;s market.&lt;/p&gt;
&lt;p&gt;I applied to 14 different companies, either via a recruiter who reached out to me, or directly via their respective websites. I also got a number of messages via &lt;a href="http://hired.com"&gt;Hired&lt;/a&gt;, but none of those really spoke to me. I was looking for and applying to backend engineering and site-reliability software engineering positions since that is where I felt I can make an impact and have relevant experience. In no particular order:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Google - Despite having a number of internal references, after my on-site interview, I was only approved for offer at L3 (college-hire / junior engineer), and while that meant I would be able to do a lot more coding, it was not the direction I wanted to take my career in.&lt;/li&gt;
&lt;li&gt;Discovery - After a few weeks of good discussions and interest from their sourcing company, I was told Discovery was no longer interested.&lt;/li&gt;
&lt;li&gt;Microsoft - My recruiter felt that it would be difficult to find a team willing to take on a senior engineer with my [lack of] experience. I never heard back after our initial call back in January.&lt;/li&gt;
&lt;li&gt;Coda - The recruiter scheduled an initial call, but needed to cancel last minute. They never reached out to reschedule.&lt;/li&gt;
&lt;li&gt;RadAI - They were not able to meet my compensation expectations, and decided from their end not to go forward.&lt;/li&gt;
&lt;li&gt;Apple - No response!&lt;/li&gt;
&lt;li&gt;Hulu / Discovery - No response!&lt;/li&gt;
&lt;li&gt;Slack / SalesForce - Form rejection email.&lt;/li&gt;
&lt;li&gt;T-Mobile - No response!&lt;/li&gt;
&lt;li&gt;Digital Ocean - I got an email that the position was eliminated, but that I would be considered for other positions. I never heard back.&lt;/li&gt;
&lt;li&gt;NVIDIA - I got through a Phone Screen with them, but decided not to pursue the opportunity since I did not feel a strong connection with the team and their work.&lt;/li&gt;
&lt;li&gt;Alaska Air - My lack of JavaScript experience (for a back end engineer position) immediately disqualified me and triggered an auto-rejection 15 minutes after I submitted my application.&lt;/li&gt;
&lt;li&gt;Adobe - Form rejection email.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The keen eyed will notice, that I only listed 13 companies, and that is because I omitted one&amp;hellip; Cloudflare.&lt;/p&gt;
&lt;h2 id="where-im-going"&gt;Where I&amp;rsquo;m Going&lt;/h2&gt;
&lt;p&gt;If it was not obvious from the last sentence, I&amp;rsquo;m going to &lt;a href="https://www.cloudflare.com"&gt;Cloudflare&lt;/a&gt;. I start as P3 (P1 = Junior, P5 = Principal) Systems Engineer with the APIs team on April 18th.&lt;/p&gt;
&lt;p&gt;My interview process at Cloudflare started with a half hour Google Meet Call with one of the engineers on the team. We talked about my experience, and the position, and concluded with some homework. I was to design and implement a URL Shortening Service. This was somewhat ironic to me, since during my time at Amazon, my go to question for Intern interviews was a similar, but scoped-down, problem. I had also already built a &lt;a href="https://github.com/tpaulus/python-urlshortener-cdk"&gt;similar solution that leveraged AWS&lt;/a&gt;. However, when I got the requirements via email later that week, I needed to make some adjustments. Namely, the requirements included statistics (how often the URL had been accessed in the last 24 hours, week, and all time), as well as the ability to run locally in a development environment. Over the course of the weekend, I built a URL Shortener using Flask and PostgreSQL and was overall quite proud of &lt;a href="https://github.com/tpaulus/url-shortner-flask"&gt;the GitHub repo I ended up submitting to them&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It seems that the team at Cloudflare were also pleased with my work, since I was invited to a virtual on-site interview. I had interviewed (virtually) with Google the week prior, and the difference between the two was night and day. While the Google interviews felt like being in a juicer, with them attempting to squeeze my knowledge about algorithms and data structures out of me, my interviews with Cloudflare were much more fun and conversational. Yes, there were still questions to answer, but things felt significantly more casual. The tech lead of the team was the only one to ask me a traditional coding question, but even then, it was a collaborative exercise in JavaScript, a language I had little experience in (intentionally). All of my other interviews were pleasant and I got a good feel for the team as the interviews went on.&lt;/p&gt;
&lt;p&gt;Nine interviews later, including a final call with President and Co-Founder &lt;a href="https://en.wikipedia.org/wiki/Michelle_Zatlyn"&gt;Michelle Zatlyn&lt;/a&gt;, I had an offer letter in hand, sent via email by the CEO, Matthew Prince, himself.&lt;/p&gt;
&lt;h3 id="why-cloudflare"&gt;Why Cloudflare?&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;ve been a Cloudflare user since September 2014 (I needed to check this via the API) and have been very happy with the service. I initially came to Cloudflare to take advantage of the free SSL certificates (Let&amp;rsquo;s Encrypt was not a thing back then), and have since relied on Cloudflare more and more, most recently using their &lt;a href="https://blog.cloudflare.com/introducing-email-routing/"&gt;Email Routing feature&lt;/a&gt; to replace some email routes I had configured in Mailgun a number of years ago.&lt;/p&gt;
&lt;p&gt;Beyond being a good service provider, Cloudflare uses its powers for good, from making it easier for &lt;a href="https://www.cloudflare.com/healthcare/"&gt;healthcare providers to offer vaccine signups&lt;/a&gt;, to &lt;a href="https://www.cloudflare.com/galileo/"&gt;protecting journalism&lt;/a&gt;, to &lt;a href="https://www.cloudflare.com/athenian/"&gt;ensuring state and local election bodies get the security support they need&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When I am out and about and people ask me what I do and where I work, I am a bit embarrassed to say that I work for Amazon, due to the impact the company has had on Seattle and other neighboring cities, and the countless controversies sounding the Amazon Fulfillment Centers that give Amazon a negative name in the news.&lt;/p&gt;
&lt;p&gt;Cloudflare is still a small company at only 2,500 employees world wide (nothing compared to the over 60,000 Amazon has in Seattle alone), but if the past is anything to go by, Cloudflare will continue to do things that make the internet better and safer for everyone.&lt;/p&gt;
&lt;h3 id="why-not-a-new-team-within-amazon"&gt;Why not a new team within Amazon?&lt;/h3&gt;
&lt;p&gt;At Amazon, internal mobility (being able to move from one team to another) is easy, and team culture varies drastically from team to team, and while I could easily find a new team within the company to work for, I feel a bit burned by Amazon. Even on this new team, there would still be stack ranking and URA (un-regretted attrition) targets. I also doubt I would be free of my old team and responsibilities completely. I think it is just better this way.&lt;/p&gt;</content:encoded></item><item><title>Breaking up with Logi Pop</title><link>https://blog.tompaulus.com/breaking-up-with-logi-pop/</link><pubDate>Sun, 01 Nov 2020 20:48:59 +0000</pubDate><guid>https://blog.tompaulus.com/breaking-up-with-logi-pop/</guid><description>I got my first set of Logitech Pop buttons (Small BT LE Buttons that interface with a WiFi Hub) in December 2017 and they have moved with me from college dorm room, to dorm room, finally making their way on the wall next to my bed in my apartment now where they control the various lights not only in my bedroom, but in the entire apartment. Ever since the beginning, I have leveraged the convenience …</description><category>Software</category><category>Life</category><media:content medium="image" url="https://blog.tompaulus.com/breaking-up-with-logi-pop/media/external/unsplash-AexHn1Bzb3Q.jpg"/><content:encoded>&lt;p&gt;I got my first set of &lt;a href="https://www.amazon.com/gp/product/B01JO8TIH4"&gt;Logitech Pop buttons&lt;/a&gt; (Small BT LE Buttons that interface with a WiFi Hub) in December 2017 and they have moved with me from college dorm room, to dorm room, finally making their way on the wall next to my bed in my apartment now where they control the various lights not only in my bedroom, but in the entire apartment. Ever since the beginning, I have leveraged the convenience of these tiny, reliable (I have yet to need to change the batteries in over 3 years of clicking) via IFTTT. However, with the &lt;a href="https://ifttt.com/explore/introducing_ifttt_pro"&gt;recent announcement&lt;/a&gt; of IFTTT moving to a subscription model, I needed to look elsewhere. While all of the lights in my bedroom are Philips Hue bulbs, many of the other smart devices I have in the rest of my place are not, leading me to use a central automation tool (&lt;a href="https://www.home-assistant.io/"&gt;Home Assistant&lt;/a&gt; in my case) to manage everything.&lt;/p&gt;
&lt;p&gt;&lt;img src="media/2020/11/IMG_1735-2.jpg" alt="Logitech Pop Button next to a Xiomi Aquara Button"&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Logitech Pop Button (Left) next to a Xiomi Aquara Button (Right).&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The Logitech Pop bridge supports a number of integrations, including Philips Hue, IFTTT, Smart Things, etc.; but what they do not support is local access, meaning that while getting bridge to talk to Home Assistant is theoretically possible using an emulated-hue bridge and a lot of time, it just is not feasible in my case. As such, I am parting ways with my beloved Pop buttons (oh, how beautiful they are), and replacing them.&lt;/p&gt;
&lt;p&gt;There are a number of IoT clicky things out there, one of the most notable being &lt;a href="https://flic.io/"&gt;Flic&lt;/a&gt;. However, not only is a set of Flic buttons incredibly pricy (a starter kit with 2 buttons costs $159.99 at the time of writing), they have many of the same pitfalls, although offering a Home Assistant Integration, it relies on bypassing the hub and using the bluetooth module on the host running Home Assistant. For some context, I run Home Assistant on an Intel NUC via docker, so Bluetooth device access is possible, but again not practice, plus I have some concerns about the Bluetooth range from where the NUC is, to where the buttons would be. Some googling lead me to &lt;a href="https://www.reddit.com/r/homebridge/comments/ghxqf7/any_alternative_wifi_buttons_to_the_logitech_pop/"&gt;this Reddit Post on r/homebridge&lt;/a&gt;. This post and its comments, along with a lot more googling (free time is in great abundance this year), I landed on the Xiomi Aquara Buttons, which run about $18 on &lt;a href="https://www.amazon.com/gp/product/B07D19YXND"&gt;Amazon&lt;/a&gt;. These are ZigBee buttons, which means that they require a compatible hub of some sorts. I didn&amp;rsquo;t think anything of this though, as I already had a working VeraPlus Hub, which among other things supports both ZigBee and Z-Wave devices. So I ordered 3 to replace my small family of Pop buttons and waited.&lt;/p&gt;
&lt;p&gt;When the buttons arrived, I was quite pleased with their design and build. Yes, nowhere close to as thin and sexy as the Logitech Pop buttons, but for the price, I was more than happy to put up with the slightly more clunky build, if I could get them to work. Keyword being &lt;em&gt;if&lt;/em&gt;. After a fair bit of fiddling, I got them to associate to the Vera, resetting them, and then pushing the &amp;ldquo;Effective Range Test Button&amp;rdquo; every few seconds while the Vera was in search mode eventually got them to associate. However, Vera does not officially support Aquara devices, so they showed up as Generic IO devices.&lt;/p&gt;
&lt;p&gt;&lt;img src="media/2020/09/Vera-Dashboard.png" alt="Vera Dashboard showing a Generic ZigBee device"&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Vera Dashboard showing &amp;ldquo;Generic ZigBee device&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;There were a fair number of people trying to get Aquara devices to work on &lt;a href="https://community.getvera.com/t/xiaomi-aqara-stuff-works-natively/209255"&gt;Vera&amp;rsquo;s Community&lt;/a&gt;, but the post has been inactive for 9 months no, with only some success getting the Temperature sensor to work. Xiomi has a hub that you can plug in and add to your network, but given the &lt;a href="https://www.androidauthority.com/xiaomi-privacy-cheap-phone-1118444/"&gt;news on Xiomi&amp;rsquo;s data collection practices&lt;/a&gt;, I was hesitant to add one of their devices to my home network, even if it was on an isolated VLAN. On top of that, I generally try to avoid vendor lock-in wherever possible, especially in the IoT space, where the fall of a company can lead to a very hungry cat, &lt;a href="https://twitter.com/internetofshit/status/1228489002946375682"&gt;as we saw with Petnet&lt;/a&gt;. So, I did some more googling (again, free time is not hard to come by in the midst of a pandemic), and stumbled upon &lt;a href="https://www.zigbee2mqtt.io/"&gt;ZigBee2MQTT&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I had used MQTT before, when I built a &lt;a href="https://blog.tompaulus.com/project-heart/"&gt;ESP8266-powered, internet-connected heart light&lt;/a&gt;, so I was aware of how quick and powerful it was. So, I did some reading and ended up ordering a &lt;em&gt;zig-a-zig-ah&lt;/em&gt; from Electrolama on &lt;a href="https://www.tindie.com/products/electrolama/zzh-cc2652r-multiprotocol-rf-stick"&gt;Tindie&lt;/a&gt;, wanting to avoid the frustrations of flashing a CC2531 USB sniffer, and wait a short eternity for some &lt;em&gt;very&lt;/em&gt; reliable parts to arrive from AliExpress. So, I waited.&lt;/p&gt;
&lt;p&gt;I had some hardware issues with the adapter that I originally received, but Omer from Electrolama was very quick to respond to my concerns and before I knew it I had a new adapter in my hands. From there, it was almost plug and play. For compliance reasons, the adapter ships with a simple blink program, but there is some excellent documentation on the Electrolama website that details how to flash the correct firmware for ZigBee2MQTT on to the device, which is accomplished with some python libraries which are installed via pip, and a python script to handle the flashing. Once flashed, the &lt;em&gt;zig-a-zig-ah&lt;/em&gt; shows up as a USB serial device, so no extra drivers were needed (I am using Ubuntu Server 20.04 LTS, your milage may very). From there, I was on the home stretch. Because I already use Docker for most of my needs on my home server, I went with the Container version of ZigBee2MQTT.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker run &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -d &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --name&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;zigbee2mqtt&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -v /etc/docker/zigbee2mqtt/data:/app/data &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --device&lt;span class="o"&gt;=&lt;/span&gt;/dev/ttyUSB0 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -e &lt;span class="nv"&gt;TZ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;America/Los_Angeles &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --restart&lt;span class="o"&gt;=&lt;/span&gt;always &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --network host &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; koenkk/zigbee2mqtt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;On first run, ZigBee2MQTT creates the default configuration file, which requires a slight bit of modification before ZigBee2MQTT can be started successfully. Namely, I needed to change the Serial device it was trying to use, and I reconfigured the Network Key while I was here and hadn&amp;rsquo;t paired any devices.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;homeassistant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;permit_join&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;mqtt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;base_topic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;zigbee2mqtt&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;mqtt://127.0.0.1&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;serial&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/dev/ttyUSB0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;zstack&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;advanced&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;rtscts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;network_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;GENERATE&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I was already running a MQTT server that I had already wired in with Home Assistant, but that too is relativity trivial. Home Assistant makes everything pretty easy, especially with their Discovery Functionality. ZigBee2MQTT includes some very helpful configuration cards that you can include in your dashboard, which I included via the &lt;em&gt;Package Method&lt;/em&gt; described on the &lt;a href="https://www.zigbee2mqtt.io/integration/home_assistant.html"&gt;documentation&lt;/a&gt;. I also added the Network Map Card, which all together looks something like this once I got all of my devices joined.&lt;/p&gt;
&lt;p&gt;&lt;img src="media/2020/10/image.png" alt="ZigBee Network Information in Home Assistant Lovelace"&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;ZigBee Network Information in Home Assistant Lovelace&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I found the provided card to do everything I needed it to do. All that was left for me to do was to configure the automations that I used to have configured in the Pop software for interfacing directly with Hue and the Automations that I had in IFTTT. On each side of my bed, I have a button that I use to control the Hue bulb that is directly above it, tapping once for a night light, twice for a reading light, and holding to turn off all the lights in the apartment. The last one was the easiest; however the previous two were much harder due to some intricacies of how Home Assistant handles &lt;code&gt;light.toggle&lt;/code&gt; actions. Specifically, you can set the color and brightness of a light with &lt;code&gt;light.toggle&lt;/code&gt;, but you call &lt;code&gt;light.toggle&lt;/code&gt; again with a different color or brightness, rather than updating the color, it turns off the light. So after some searching and thinking, I came up with this automation.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;1601945836861&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Bedroom - Button - Right - Reading Light&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;device&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;mqtt&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;device_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;98de24c1076711eb9eb06778c10b3ef9&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;action&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;subtype&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;double&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;discovery_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0x00158d00027bf559&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;action_double&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;choose&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;conditions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;template&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;value_template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{{ state_attr(&amp;#39;&amp;#39;light.hue_bedroom_bed_right_light&amp;#39;&amp;#39;, &amp;#39;&amp;#39;color&amp;#39;&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; == {&amp;#39;&lt;/span&gt;&lt;span class="nt"&gt;&amp;#39;x&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;0.447, &amp;#39;&amp;#39;y&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0.406&lt;/span&gt;}&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;and is_state(&amp;#39;&amp;#39;light.hue_bedroom_bed_right_light&amp;#39;&amp;#39;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="kc"&gt;on&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="l"&gt;)}}&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;sequence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;light.turn_off&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;entity_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;light.hue_bedroom_bed_right_light&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;light.turn_on&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;reading&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;brightness&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;254&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;entity_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;light.hue_bedroom_bed_right_light&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;single&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I have 4 similar instances of this automation, one for the night light and one for the reading light, for each side of the bed. The differences being the colors/brightnesses and the devices respectively. Specifically what this automation does, is it checks if the light is in the desired state already (both in terms of color and state) and if it is, turns off the light, if it isn&amp;rsquo;t, it means that the light is in a different state and the user is requesting a state change, which is achieved via a call to &lt;code&gt;light.turn_on&lt;/code&gt; with the desired parameters.&lt;/p&gt;
&lt;p&gt;All in all, once I got everything working (which wasn&amp;rsquo;t too difficult honestly), things have worked great. The buttons are much more responsive, especially for the automations that used to leverage IFTTT. I have also already made some more additions to my ZigBee network, like moving my Hue Bulbs and remotes over to ZigBee2Mqtt and getting a SmartThings Motion Sensor and Door Contact Sensor to replace a similar ZWave set. From my small sample size, ZigBee is much more responsive than ZWave, although that may also be because of the setup I was using for ZWave (using a VeraPlus connected via Home Assistant) was less than ideal. I must give credit to the ZigBee2MQTT team and community for making a reliable, easy to use, and well supported piece of software that makes integrating these physical devices into our Home Automation Systems so easy (and dare I say it, fun).&lt;/p&gt;
&lt;p&gt;&lt;img src="media/2020/11/IMG_1736.jpg" alt="A Xiomi Aquara button mounted on a bedside"&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;A Xiomi Aquara button mounted on my bed side.&lt;/em&gt;&lt;/p&gt;</content:encoded></item><item><title>Project Heart</title><link>https://blog.tompaulus.com/project-heart/</link><pubDate>Sat, 04 Aug 2018 02:35:57 +0000</pubDate><guid>https://blog.tompaulus.com/project-heart/</guid><description>Long Distance Relationships are tough, there is no denying that. But this isn&amp;rsquo;t a post about the woes of love, rather a little project to help bridge the distance. Project Heart is a light that is controlled by your partner, regardless of where they are in the world, as long as they have an internet connection, they can light up your light.</description><category>Software</category><category>Life</category><media:content medium="image" url="https://blog.tompaulus.com/project-heart/media/2018/08/IMG_0845-1.jpg"/><content:encoded>&lt;p&gt;Long Distance Relationships are tough, there is no denying that. But this isn&amp;rsquo;t a post about the woes of love, rather a little project to help bridge the distance. Project Heart is a light that is controlled by your partner, regardless of where they are in the world, as long as they have an internet connection, they can light up your light.&lt;/p&gt;
&lt;p&gt;Making use of the &lt;a href="https://www.adafruit.com/product/2471"&gt;Adafruit ESP8266 HUZZAH&lt;/a&gt; breakout, we can connect a series of &lt;a href="https://www.adafruit.com/category/168"&gt;NeoPixel LEDs&lt;/a&gt; to the network without too much hassle. The basic premise involves the ESP8266 running a simple Web Server that is listening to request on port 80, specifically POST requests with a plain text body of either &lt;code&gt;pulse&lt;/code&gt; or &lt;code&gt;chase&lt;/code&gt; to run an animation sequence on the LEDs.&lt;/p&gt;
&lt;h2 id="software"&gt;Software&lt;/h2&gt;
&lt;p&gt;Programming the ESP8266 with the Arduino software is relatively straightforward after you have set it up. However, keep in mind that you will need a USB to Serial Adapter if you are using the specified Breakout. The Adafruit Feather HUZZAH has this built in, but it a little more expensive. Instructions for setting up the Arduino IDE to program the ESP8266 can be found on the &lt;a href="https://learn.adafruit.com/adafruit-huzzah-esp8266-breakout/using-arduino-ide"&gt;Adafruit Learning System&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Allowing the lights to be controlled from anywhere, not just inside of your home network,  presents another fun, but easily solved challenge. All of this is done in your router&amp;rsquo;s settings (typically found in the Advanced Section). First we will want to assign a static IP to the HUZZAH to allow ensure that the device&amp;rsquo;s internal IP remains the same. Next, you will want to set up Port Forwarding to allow the outside world to talk to your device.&lt;/p&gt;
&lt;p&gt;&lt;img src="media/2018/07/image.png" alt=""&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Most ISPs block common ports like 80, 443, 8080, so some trial and error may be necessary to find an open port that will work for you. Any open port will suffice. Most routers allow you to map a different external and internal port, which allows us to run the HUZZAH on port 80 for convenience.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Finally, since most ISPs use DHCP themselves to assign your home an IP address, we will want to use a service like &lt;a href="https://www.noip.com/"&gt;NoIP&lt;/a&gt; to ensure that we can always reach our home network, even if the ISP decides to issue our Modem a new IP address. How this is setup depends on your router, and the NoIP site has setup instructions for all major manufactures. So, at this point, we should have a URL which we can use to access our HUZZAH from anywhere in the world. Mine looks something like this: &lt;code&gt;http://example.hopto.org:5000&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;You can test out your configuration using an HTTP request tool like &lt;a href="https://www.getpostman.com/"&gt;Postman&lt;/a&gt;, but that&amp;rsquo;s not very convenient. That&amp;rsquo;s where we can use a service like IFTTT to make our life easier. By combining the button trigger and the Webhook service we can use a widget in the iOS notification center to trigger the light in a matter of seconds.&lt;/p&gt;
&lt;p&gt;&lt;img src="media/2018/07/image-1.png" alt="IFTTT Widget Settings"&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;IFTTT Widget Settings&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;You can then add the IFTTT Widget to your Today View to access the animation controls quickly and easily.&lt;/p&gt;
&lt;p&gt;&lt;img src="media/2018/07/IMG_932348607773-1.jpeg" alt=""&gt;&lt;/p&gt;
&lt;h2 id="hardware"&gt;Hardware&lt;/h2&gt;
&lt;p&gt;The project is pretty simple from a hardware perspective, with a short bill of materials, especially if you have some of the tools already lying around.&lt;/p&gt;
&lt;p&gt;&lt;img src="media/2018/08/wiring_diagram.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;The capacitor is optional and provides some power smoothing, which is especially useful for very flashy (high current draw) animations like the pulse. When things are all set and done, mount the board and leds how you see fit, Hot Glue is one of my go to favorites for things like this.&lt;/p&gt;
&lt;p&gt;&lt;img src="media/2018/08/IMG_0847-3.jpg" alt=""&gt;&lt;/p&gt;
&lt;h3 id="bill-of-materials"&gt;Bill of Materials&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.adafruit.com/product/2471"&gt;Adafruit HUZZAH ESP8266 Breakout&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.adafruit.com/product/70"&gt;FTDI Serial TTL-232 USB Cable&lt;/a&gt; (Required for programming - although any USB to Serial adapter will due)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.adafruit.com/product/1833"&gt;USB Micro-B Breakout Board&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.adafruit.com/product/3729"&gt;Side Light NeoPixel LED PCB Bar&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Stranded-Core wire&lt;/li&gt;
&lt;li&gt;Micro USB Cable&lt;/li&gt;
&lt;li&gt;USB Power Supply (1A should be sufficient for the default animations)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;All of the code for this project is on GitHub and is free for you to use and modify to your heart&amp;rsquo;s content: &lt;a href="https://github.com/tpaulus/Project-Heart"&gt;https://github.com/tpaulus/Project-Heart&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>Daily White Card</title><link>https://blog.tompaulus.com/daily-white-card/</link><pubDate>Mon, 11 Sep 2017 05:53:50 +0000</pubDate><guid>https://blog.tompaulus.com/daily-white-card/</guid><description>Using Cards Against Humanity's (CAH) White Cards to create a unique and fun daily challenge. Using Python, GroupMe's APIs and the open source nature of CAH to create a bot that posts daily White Card Challanges to the Staff of Maya &amp; Olmeca.</description><category>Software</category><media:content medium="image" url="https://blog.tompaulus.com/daily-white-card/media/2017/09/Cards_Against_Humanity_-15711676205-.jpg"/><content:encoded>&lt;p&gt;&lt;a href="https://cardsagainsthumanity.com/"&gt;Cards Against Humanity&lt;/a&gt; is a College Favorite and often leads to some wonderful, even though somewhat disturbing, conversation. Through a strange series of events, a few of the staff of Maya Olmeca at the Front Desk ended up talking about the various White Cards&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; and how some of them would be quite funny to see in real life, like &amp;ldquo;Vigiorius Jazz Hands&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Aaron (RA for the Visual and Performing Arts Residential Learning Community in Maya) had the awesome idea of making a competition to find White Cards in real life. The rules would be simple, take a selfie with showing what every the White Card described.&lt;/p&gt;
&lt;p&gt;With a little bit of Python (~65 lines), GroupMe&amp;rsquo;s super simple &lt;a href="https://dev.groupme.com/tutorials/bots"&gt;API&lt;/a&gt;, and the work of &lt;a href="https://www.crhallberg.com/cah/json/"&gt;Chris Hallberg&lt;/a&gt;, I put together a simple bot that posts in a GroupMe&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt; group every morning at 8 am with a Random White Card.&lt;/p&gt;
&lt;h2 id="how-it-works"&gt;How it Works&lt;/h2&gt;
&lt;p&gt;Using &lt;strong&gt;JSON Against Humanity&lt;/strong&gt; I generated a JSON object with all of the cards from the Original CAH Decks (The Original Set, The 6 expansions, and the Green Box), which totaled 1159 white cards. These cards are loaded into a python &lt;code&gt;dict&lt;/code&gt; object, from which one random card is selected whenever the program is executed. This card is then given some context, like &amp;ldquo;Today you are looking for&amp;hellip;&amp;rdquo; and the text of the White Card. This is then posted to the GroupMe Group via the Bot that was created specifically for this purpose on GroupMe&amp;rsquo;s Developer portal.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Message Context&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;MESSAGES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Today you are looking for... &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;Can you find... &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;How about... &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s1"&gt; Find it!&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s1"&gt; DO YOUR WORST!&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Pick a Random White Card &amp;amp; Message Context&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;secure_random&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SystemRandom&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;card&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;secure_random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Cards&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_white_cards&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;secure_random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MESSAGES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Post the Card text to GroupMe&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;GroupMe&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Scheduling is accomplished via &lt;a href="https://elements.heroku.com/addons/scheduler"&gt;Heroku&amp;rsquo;s Scheduler Add-on&lt;/a&gt; and is run for free on one of their Dynos.&lt;/p&gt;
&lt;p&gt;&lt;img src="media/2017/09/Screen-Shot-2017-09-10-at-10.50.52-PM.png" alt="Scheduler"&gt;&lt;/p&gt;
&lt;p&gt;Taking only an evening to put together, I&amp;rsquo;m excited to see what &amp;ldquo;interesting&amp;rdquo; results this challenge will have. The Full Source Code is available on GitHub and is licensed under MIT.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/tpaulus/daily-white-card"&gt;https://github.com/tpaulus/daily-white-card&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Cover Photo: By tombullock (Cards Against Humanity) [&lt;a href="http://creativecommons.org/licenses/by/2.0"&gt;CC BY 2.0&lt;/a&gt;], &lt;a href="https://commons.wikimedia.org/wiki/File%3ACardsAgainstHumanity(15711676205).jpg"&gt;via Wikimedia Commons&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;White Cards are typically used to fill in the blanks for Black Cards and contain a multitude of various phrases, ranging from the simple (&amp;ldquo;God&amp;rdquo;) to the complex and disturbing.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;We use GroupMe across the department for communication, and it allows for super simple and easy photo sharing, which is why we used it for this task. Plus, everyone on our staff already has the app installed.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content:encoded></item><item><title>The Road to Seattle</title><link>https://blog.tompaulus.com/road-to-amzn/</link><pubDate>Sat, 11 Mar 2017 06:09:27 +0000</pubDate><guid>https://blog.tompaulus.com/road-to-amzn/</guid><description>Unlike Mike Rowe&amp;rsquo;s &amp;ldquo;Road To Coober Pedy,&amp;rdquo; my trip to Seattle, Washington for my Summer Internship with Amazon, will hopefully not be filled with death, injury, or road kill. This post will help serve as my record of the progress that I have made, and the events that have taken place, leading up to my First Day as a Software Development Engineering Intern.</description><category>Life</category><category>Internship</category><media:content medium="image" url="https://blog.tompaulus.com/road-to-amzn/media/2017/03/SEA-Banner-1.png"/><content:encoded>&lt;p&gt;Unlike Mike Rowe&amp;rsquo;s &amp;ldquo;Road To Coober Pedy,&amp;rdquo; my trip to Seattle, Washington for my &lt;a href="https://blog.tompaulus.com/amzn-sde-internship/"&gt;Summer Internship with Amazon&lt;/a&gt;, will hopefully not be filled with death, injury, or road kill. This post will help serve as my record of the progress that I have made, and the events that have taken place, leading up to my First Day as a Software Development Engineering Intern.&lt;/p&gt;
&lt;figure class="kg-card kg-embed-card"&gt;
&lt;iframe
width="560"
height="315"
src="https://www.youtube-nocookie.com/embed/5mLY2lSd9ig?rel=0"
title="Road to Seattle video"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/figure&gt;
&lt;p&gt;After submitting my acceptance letter, I received a &amp;ldquo;Team Preference&amp;rdquo; survey from my point of contact at Amazon in early February. Based on my start and end dates, and the areas of interest I marked on my survey, I was presented with three options, as well as a brief description of what the team does and where they are located. One of the options, and the one that I ended up marking as my first preference (and receiving) was working with Amazon Web Services&amp;rsquo; (AWS) CloudFormation team in Seattle.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;AWS CloudFormation gives developers and systems administrators an easy way to create and manage a collection of related AWS resources, provisioning and updating them in an orderly and predictable fashion. - &lt;a href="https://aws.amazon.com/cloudformation/"&gt;AWS CloudFormation&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I have had some experience working with AWS and its various services (namely Lightsail for WebHosting, and &lt;a href="https://blog.tompaulus.com/back-up-with-glacier/"&gt;Glacier for Backups&lt;/a&gt;). From that, I know that AWS&amp;rsquo; many services, each of which are extremely powerful by themselves, only become more powerful when combined. However, doing this becomes increasingly difficult to do repeatedly and consistently, as the size of your infrastructure grows; and that is why CloudFormation is so cool!&lt;/p&gt;
&lt;figure class="kg-card kg-embed-card"&gt;
&lt;iframe
width="560"
height="315"
src="https://www.youtube-nocookie.com/embed/Omppm_YUG2g?rel=0"
title="Road to Seattle video"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/figure&gt;
&lt;p&gt;The real fun starts once I cross the T-60 day mark, which will happen in Mid-March. At that point, my team placement, start/end dates are set in stone, and the on-boarding process begins (Relocation, Background Check, etc.) I will be updating this post until my first day in Mid-May.&lt;/p&gt;
&lt;p&gt;I have no doubts that this is going to be very exciting summer!&lt;/p&gt;
&lt;p&gt;*Banner Photo: Daniel Schwen (Own work) [&lt;a href="http://creativecommons.org/licenses/by-sa/4.0"&gt;CC BY-SA 4.0&lt;/a&gt;], &lt;a href="https://commons.wikimedia.org/wiki/File%3ASeattle_4.jpg"&gt;via Wikimedia Commons&lt;/a&gt;*&lt;/p&gt;</content:encoded></item><item><title>Summer Internship - Amazon SDE</title><link>https://blog.tompaulus.com/amzn-sde-internship/</link><pubDate>Mon, 30 Jan 2017 19:30:00 +0000</pubDate><guid>https://blog.tompaulus.com/amzn-sde-internship/</guid><description>After applying to 10 odd major tech companies over winter break; Google and Amazon reached out to me and asked me to complete some &amp;ldquo;homework&amp;rdquo;1. Google was first to show interest, having me complete two problems, which I completed to their satisfaction, and a short while later I received a request for availability in regard to scheduling a phone interview time. Since this was over the …</description><category>Life</category><category>Internship</category><media:content medium="image" url="https://blog.tompaulus.com/amzn-sde-internship/media/2017/01/InternBanner-1.png"/><content:encoded>&lt;p&gt;After applying to 10 odd major tech companies over winter break; Google and Amazon reached out to me and asked me to complete some &amp;ldquo;homework&amp;rdquo;&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;. Google was first to show interest, having me complete two problems, which I completed to their satisfaction, and a short while later I received a request for availability in regard to scheduling a phone interview time. Since this was over the holidays, things were a little dragged out, but I completed my two ~1-hour phone interviews in early January. At around the same time, Amazon reached out and asked me to complete some of their problems as well.&lt;/p&gt;
&lt;p&gt;Interestingly, after completing the first set, which I found to be very easy, they sent a second, more complex, set of questions. These too I completed well, passing all of the unit tests the exam tool ran&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;. Much to my surprise, I received an offer to join the Amazon team a few days after completing my homework. I was astounded that one of the &lt;strong&gt;big four&lt;/strong&gt;&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt; was interested in having someone like me, someone from a large state university with little glamor – like Hackathon prizes, publications, major projects and such, at their organization. Especially without a phone interview!&lt;/p&gt;
&lt;p&gt;Amazon gave me till February 1st to make up my mind, the clock was ticking. I connected my Google Recruiter and informed her of the news, and a few days later I heard back that I had made it into the pool. Short aside on how Google hires interns&amp;hellip;&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;Candidates are interviewed by two Google Engineers&lt;/li&gt;
&lt;li&gt;An independent committee of Googlers reviews the Interview feedback.&lt;/li&gt;
&lt;li&gt;If the committee feels that you would be a good fit, you are added to the pool.&lt;/li&gt;
&lt;li&gt;Departments looking for interns pull from the pool based off the project survey interns filled out during the application process.&lt;/li&gt;
&lt;li&gt;You potentially have a 1:1 phone interview with your prospective manager, to see if you would be a good fit for their team&lt;/li&gt;
&lt;li&gt;If all goes well, you get an offer!&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;As you can see, this is quite a lengthy and intensive process, something that I did not have time for with Amazon&amp;rsquo;s offer expiring VERY soon. Along with the offer email, Amazon included the schedule for several informational sessions for new interns, one of which I attended. On the call was one of the recruiters, as well as Project Manager for one of the teams at Amazon. The presentation lasted about an hour, and afterward, I had a much better grasp on what role interns play at Amazon.&lt;/p&gt;
&lt;p&gt;It should not be a big surprise based on the title of this post, I ended up accepting the Amazon offer. Not only is Seattle&lt;sup id="fnref:4"&gt;&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref"&gt;4&lt;/a&gt;&lt;/sup&gt; a city that I have never been to before, I am excited by opportunities this position will afford me. More posts and &lt;a href="https://photos.tompaulus.com"&gt;photos&lt;/a&gt; are definitely to come once I start in Mid-May.&lt;/p&gt;
&lt;p&gt;This summer is definitely going to be one to remember!&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Homework refers to a series of coding/programming questions that vary in scope, depth, and duration based on the assignment and organization. The goal of these is typical to identify one&amp;rsquo;s skill level with a given programming language; and the general concepts, like list storing, tree traversal, etc.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;Both for the Google and Amazon assignments, the questions are confidential and under NDA, so cannot go into greater detail in regard to their content. I can, however, say that they were focused on general programming concepts, like data structures, etc.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;Big Four: The most influential international technology companies: Amazon.com, Apple/Microsoft, Facebook and Google (&lt;a href="https://en.wikipedia.org/wiki/Big_Four"&gt;https://en.wikipedia.org/wiki/Big_Four&lt;/a&gt;).&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;While Seattle is not the only city where Amazon places Interns, most of their interns are placed in Seattle, as that is where most of their offices and teams are based.&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content:encoded></item><item><title>Backing Up with Amazon Glacier</title><link>https://blog.tompaulus.com/back-up-with-glacier/</link><pubDate>Thu, 05 Jan 2017 01:13:00 +0000</pubDate><guid>https://blog.tompaulus.com/back-up-with-glacier/</guid><description>Amazon Glacier is a long-term, low-cost data archival and backup service offered by AWS. It is surprisingly cheap at only $0.0041 per GB per month, it has some caveats though. Mainly, retrievals can take upto 5 hours, and archives must be stored for a minimum of 3 months2.</description><category>Software</category><media:content medium="image" url="https://blog.tompaulus.com/back-up-with-glacier/media/2017/01/Glacier-Banner.png"/><content:encoded>&lt;p&gt;&lt;a href="https://aws.amazon.com/glacier/"&gt;Amazon Glacier&lt;/a&gt; is a long-term, low-cost data archival and backup service offered by AWS. It is surprisingly cheap at only $0.004&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; per GB per month, it has some caveats though. Mainly, retrievals can take upto 5 hours, and archives must be stored for a minimum of 3 months&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Glacier is available in most, if not all, of their data centers, but I will be using the &lt;strong&gt;US East (N. Virginia)&lt;/strong&gt; datacenter, since that is where my &lt;a href="https://lightsail.aws.amazon.com"&gt;Lightsail&lt;/a&gt; instances are hosted, and inter-datacenter transmissions are free. If you are using Lightsail, make sure to turn on VPC Peering; this should facilitate communication between your LightSail VPS and the Glacier system.&lt;/p&gt;
&lt;p&gt;Since I want to use my storage space most efficiently, I will only be backing up the &amp;ldquo;non-recoverable&amp;rdquo; information I have saved on my server. This includes, database dumps, images, configuration files; however, does not include Docker Images, etc. My logic behind this is, that in the event of a failure, I can easily pull a docker image from the repository, or reinstall a tool via apt, so I do not need to save a copy. Also, since archives will be saved for an extended period of time, depending on your desired backup frequency, saving a few hundred megabytes can add up over a few months of backups.&lt;/p&gt;
&lt;h4 id="background-on-glacier"&gt;Background on Glacier&lt;/h4&gt;
&lt;p&gt;While the AWS management console allows you to do some work with Glacier, most interactions will occur via a CLI or API requests. Thankfully, Glacier is a relatively popular service, so other developers have already written scripts and tools to help us work with Glacier. &lt;em&gt;AWS has also written a well documented SDK for Glacier as well.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Glacier treats every file that is uploaded as an non-mutable archive, be it a photo, video, or in our case a ZIP file. Each file is assigned a unique ID which can be used to identify the archive in Glacier.&lt;/p&gt;
&lt;p&gt;Since our Archives will likely be larger than 100MB, we will be using a Multipart upload, which allows the upload of a single larger archive to be chunked and parallelized. It also allows for easy recovery if something goes wrong while uploading a chunk, as only that chunk needs to be reuploaded, not the entire archive. The Multipart Upload process is discussed in detail on the Developer Guide for Glacier: &lt;a href="http://docs.aws.amazon.com/amazonglacier/latest/dev/uploading-archive-mpu.html"&gt;http://docs.aws.amazon.com/amazonglacier/latest/dev/uploading-archive-mpu.html&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="configure-glacier"&gt;Configure Glacier&lt;/h4&gt;
&lt;p&gt;Before we get started on our backup system, we have to do some work in the AWS Management console. Before you start, make sure that you have set the correct region in AWS for where you want your Glacier Vault to be.&lt;/p&gt;
&lt;h5 id="iam"&gt;IAM&lt;/h5&gt;
&lt;p&gt;We will first create an IAM user with &lt;code&gt;Programmatic access&lt;/code&gt; which we will use to authenticate our backup agent with AWS. Next, we will attach the &lt;code&gt;AmazonGlacierFullAccess&lt;/code&gt; policy to our user, this will allow our user to upload archives to our Vault. Take note of the Access Key and Secret key for the user you just created, as you will need to save them in a config file a little later. &lt;strong&gt;This is the only time you will see these, however you will be able to create additional keys later if necessary.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="media/2017/01/IAM_Management_Console.png" alt="IAM Console - Create User"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="media/2017/01/IAM_Management_Console-2.png" alt="IAM Console - Set ACP"&gt;&lt;/p&gt;
&lt;h5 id="glacier"&gt;Glacier&lt;/h5&gt;
&lt;p&gt;Navigate to the Glacier service in the management console, and click get started. The region that you chose earlier should be listed, as well as a prompt for the nave of your vault. The name of your vault must be unique across the region! You can setup SES notifications if you like about Vault Statuses, but you can skip that safely if you like.&lt;/p&gt;
&lt;p&gt;&lt;img src="media/2017/01/Glacier_Management_Console.png" alt="Glacier Console - Create Vault"&gt;&lt;br&gt;
&lt;img src="media/2017/01/Glacier_Management_Console-2.png" alt="Glacier Console - Vault List"&gt;&lt;/p&gt;
&lt;h4 id="configure-system-to-be-backedup"&gt;Configure System to be Backedup&lt;/h4&gt;
&lt;p&gt;Clone the &lt;a href="https://github.com/tpaulus/glacier-backup"&gt;GitHub Repository&lt;/a&gt;, which contains the uploader.jar, backup script, and aws.properties template via &lt;code&gt;git clone https://github.com/tpaulus/glacier-backup.git&lt;/code&gt;. You may need to make the backup script and the uploader jar executable, which can be achieved by &lt;code&gt;cd&lt;/code&gt;ing into the folder and executing &lt;code&gt;chmod +x *.sh *.jar&lt;/code&gt;.&lt;/p&gt;
&lt;h5 id="configure-backup-script"&gt;Configure Backup Script&lt;/h5&gt;
&lt;p&gt;Once you have cloned the repo, modify &lt;code&gt;aws.properties&lt;/code&gt; with the &lt;code&gt;accessKey&lt;/code&gt; and &lt;code&gt;secretKey&lt;/code&gt; that you took note of when you crated your IAM user. Next update the fields at the top of in &lt;code&gt;backup.sh&lt;/code&gt; with the correct values. The correct endpoint url can be found at: &lt;a href="http://docs.aws.amazon.com/general/latest/gr/rande.html#glacier_region"&gt;http://docs.aws.amazon.com/general/latest/gr/rande.html#glacier_region&lt;/a&gt;. You should use HTTPS wherever possible.&lt;/p&gt;
&lt;p&gt;The script assumes that you have your MySQL running in a docker container, whose name is a variable defined at the top of the script. Modify this section, or the other sections to suite your needs. If you are trying things out, set &lt;code&gt;SANDBOX&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt; to disable the uploader and cleanup portions of the script. This prevents the compressed archive and backup folder from being deleted, so you can check if everything worked as desired, without having to deal with Glacier.&lt;/p&gt;
&lt;h5 id="running-the-script"&gt;Running the Script&lt;/h5&gt;
&lt;p&gt;Before you can run the script, you may need to install Java, you can check if Java is installed by running &lt;code&gt;java -version&lt;/code&gt; in the command line. If an error is returned, you need to install Java. On Ubuntu, this can be achieved by running &lt;code&gt;sudo apt-get install openjdk-8-jre-headless&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Once you have verified that the script works as desired, you will want to add it as a cron job to run regularly.&lt;/p&gt;
&lt;h5 id="cron-job"&gt;Cron Job&lt;/h5&gt;
&lt;p&gt;Lastly, we will want to have our backup run automatically on a schedule, which can be easily achieved with the help of CRON. CRON allows you to input when you want a certain command or task run, and it will take care of the rest for you automatically. To help generate your crontab line, tools like &lt;a href="http://crontab-generator.org/"&gt;Crontab Generator&lt;/a&gt; can take out most of the guess work and frustration. For the command, you will want to &lt;code&gt;cd&lt;/code&gt; into the directory to which you cloned the repository, and then execute the script. My finished crontab looks something like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;0 12 * * * cd /usr/share/glacier-backup; ./backup.sh &amp;gt;&amp;gt; /var/log/glacier-backup.log 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I decided to have my script run at 12PM (UTC) everyday, since that is a time when I expect not to make any changes to the server. If you are unsure of the timezone of your server, run &lt;code&gt;date&lt;/code&gt; to find out the current date, time, and timezone of your system.&lt;/p&gt;
&lt;p&gt;Now run &lt;code&gt;sudo crontab -e&lt;/code&gt; to add the line you got from the Crontab Generator to your local &lt;code&gt;crontab&lt;/code&gt; file. The sudo is necessary, as we want our script to be run by the root user to bypass any permissions issues.&lt;br&gt;
&lt;img src="media/2017/01/1__ubuntu_Londo___usr_share__ssh_.png" alt=""&gt;&lt;br&gt;
Save and exit, and you should see &lt;code&gt;crontab: installing new crontab&lt;/code&gt;. This means that your line was added successfully to the crontab for the root user.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s it, now you can rest assured that if something should go wrong, you have a recent backup of your essential files and databases that are safely stored offsite, ready for your retrieval within 5 hours&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h3 id="recovery-procedure"&gt;Recovery Procedure&lt;/h3&gt;
&lt;p&gt;In the event that you should need to use one of the Backups from your Glacier Vault, you will first need to retrieve the Inventory from the Glacier System. Setting the environment variables as shown below will make our lives a little easier as we complete the steps necessary to recover an archive.&lt;/p&gt;
&lt;p&gt;Additionally, the steps listed below use the same Command Line tool as we used above to send our Backup to Glacier, so ensure that the commands below are being executed in the same directory as the &lt;code&gt;backup.sh&lt;/code&gt; file. This is done to make sure that the &lt;code&gt;aws.properties&lt;/code&gt; file is located in the correct place in relation to the JAR file being executed to interface with Glacier.&lt;/p&gt;
&lt;h5 id="set-common-environment-variables"&gt;Set Common Environment Variables&lt;/h5&gt;
&lt;p&gt;The values for most of these variables will be the same as those found at the top of the &lt;code&gt;backup.sh&lt;/code&gt; file. They are not in a shell file, however, because this process can take some time to execute&lt;sup id="fnref:4"&gt;&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref"&gt;4&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;CREDENTIALS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;aws.properties&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;https://glacier.us-east-1.amazonaws.com&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;VAULT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;myvault&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="list-inventory"&gt;List Inventory&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;java -jar glacieruploader.jar --endpoint &lt;span class="nv"&gt;$ENDPOINT&lt;/span&gt; --vault &lt;span class="nv"&gt;$VAULT&lt;/span&gt; --credentials &lt;span class="nv"&gt;$CREDENTIALS&lt;/span&gt; --list-inventory
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This will give you a job ID for the inventory listing, make note of this, as you will need it to access the result.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;java -jar glacieruploader.jar --endpoint &lt;span class="nv"&gt;$ENDPOINT&lt;/span&gt; --vault &lt;span class="nv"&gt;$VAULT&lt;/span&gt; --credentials &lt;span class="nv"&gt;$CREDENTIALS&lt;/span&gt; --list-inventory yourjobidfromthepreviousstep
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Be sure to include the jobid from the previous step in the above command. You will receive an error massage (status 400) until the inventory has been generated for you to retrieve. This can take about 4 hours, depending on Vault Size and Demand, according to the Amazon Developer Documentation.&lt;/p&gt;
&lt;h4 id="download-archive"&gt;Download Archive&lt;/h4&gt;
&lt;p&gt;Once you have found the desired archive that you would like to retrieve from your inventory, making note of its &lt;code&gt;Archive ID&lt;/code&gt;, you can request it via:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;java -jar glacieruploader.jar --endpoint &lt;span class="nv"&gt;$ENDPOINT&lt;/span&gt; --vault &lt;span class="nv"&gt;$VAULT&lt;/span&gt; --credentials &lt;span class="nv"&gt;$CREDENTIALS&lt;/span&gt; --download myarchiveid --target path/to/filename.zip
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Make sure to change the Archive ID that you want to download to one in the inventory, and the target to the location you want the archive saved.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; In order for you to retrieve the Archive, your IAM User that you have configured (see above) will need access to the Amazon SQS and SNS Service. This is a separate permission than the one granted for Write Access to Glacier.&lt;/p&gt;
&lt;p&gt;&lt;img src="media/2017/04/IAM_Management_Console.png" alt="Required IAM Permissions for Archive Retrieval"&gt;&lt;/p&gt;
&lt;p&gt;If you are getting errors in regard to the &amp;ldquo;current data retrieval policy,&amp;rdquo; have a look in your Vault General Settings, accessed via the Management Console, and make sure that you allow for enough data to be downloaded per hour. You should set the cap a little above the estimated size of the archive that you want to retrieve. The higher you set your retrieval cap, the more you can be charged. You are only charged for retrieval, this is not a monthly fixed charge.&lt;/p&gt;
&lt;p&gt;&lt;img src="media/2017/04/Glacier_Management_Console.png" alt="Setting Button in the Glacier Management Console"&gt;&lt;br&gt;
&lt;img src="media/2017/04/Glacier_Management_Console_-_Data_Retrieval.png" alt="Ensure that the Max Retrieval Rate is set either to No Limit, or to a size larger than the archive you are going to be downloading"&gt;&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s It. While retrievals can be time intensive, they significantly lower cost makes it worth it for most users. If you find yourself making a significant number of retrievals, you may want to consider configuring Expedited Retrievals via provisioned capacity, or moving to S3 for Hot-Storage, rather than Glacier, which is considered Cold-Storage.&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;At time of writing, each GB of Glacier storage in Region: US-East-1 (N. Virginia) cost $0.004. Updated pricing can be found at &lt;a href="https://aws.amazon.com/glacier/pricing/"&gt;https://aws.amazon.com/glacier/pricing/&lt;/a&gt;&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;While archives can be deleted at anytime, the documentation states that you will be charged a prorated fee for the remaining duration. &lt;a href="https://aws.amazon.com/glacier/faqs/"&gt;https://aws.amazon.com/glacier/faqs/&lt;/a&gt;&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;Archive retrieval times depend on the archive size and retrieval option selected. More information can be found in the Glacier Developer documentation: &lt;a href="https://docs.aws.amazon.com/amazonglacier/latest/dev/getting-started-download-archive.html"&gt;https://docs.aws.amazon.com/amazonglacier/latest/dev/getting-started-download-archive.html&lt;/a&gt;.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;While this process can be automated with the help of Amazon SNS and an Amazon SQS queue, that can be a bit more complex to setup and configure. If you will be frequently be retrieving archives (at which point you should re-consider your use of Glacier) it may be advantageous to look into this: &lt;a href="https://docs.aws.amazon.com/amazonglacier/latest/dev/retrieving-vault-inventory-java.html"&gt;https://docs.aws.amazon.com/amazonglacier/latest/dev/retrieving-vault-inventory-java.html&lt;/a&gt;&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content:encoded></item><item><title>Installing Fish Terminal on MacOS</title><link>https://blog.tompaulus.com/journal-installing-fish/</link><pubDate>Fri, 23 Sep 2016 20:23:00 +0000</pubDate><guid>https://blog.tompaulus.com/journal-installing-fish/</guid><description>Fish is a great alternative to the default bash or shell that is installed on your computer by default, while Fish is available for a variety of platforms, even Windows (with Cygwin); but this guide will focus on macOS, because that is what I use on a daily basis. It is very easily to install, since they provide a package that you simply download and run and it installs fish to /usr/local/bin. …</description><category>Software</category><media:content medium="image" url="https://blog.tompaulus.com/journal-installing-fish/media/2016/12/FishBanner.png"/><content:encoded>&lt;p&gt;Fish is a great alternative to the default bash or shell that is installed on your computer by default, while Fish is available for a variety of platforms, even Windows (with Cygwin); but this guide will focus on macOS, because that is what I use on a daily basis. It is very easily to install, since they provide a package that you simply download and run and it installs fish to &lt;code&gt;/usr/local/bin&lt;/code&gt;. However, if you are a bit more savvy and already have Homebrew installed, you can install fish with a single command in your currently very boring shell.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;brew install fish
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After the install completes, you will need to allow/whitelist the fish shell to be used, and make it your default shell, unless you want to type &lt;code&gt;fish&lt;/code&gt; every time you start a session.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo echo &amp;#39;/usr/local/bin/fish&amp;#39; &amp;gt;&amp;gt; /etc/shells chsh -s /usr/local/bin/fish
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;chsh -s /usr/local/bin/fish
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now that you have installed, whitelisted, and made it your default shell, you are probably wondering why fish is so cool. Fish’s power lies in its expansibility, especially in the awesome things that other fish enthusiasts have already created. One of these, that is likely already installed is Tackle. To use it, you will likely need to add a few extra dependencies, which can be easily done via another brew install command, and an install script. This script can be run implicitly, but you should understand what the script does before doing so, else bad things could happen. Justin has documented the Tackelbox framework and the code very thoroughly, and I recommend checking out the framework and the install code: &lt;a href="https://github.com/justinmayer/tacklebox"&gt;https://github.com/justinmayer/tacklebox&lt;/a&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;brew install vcprompt grc
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;curl -L https://raw.githubusercontent.com/justinmayer/tacklebox/master/tools/install.fish | fish
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Tackle and Tacklebox requires some configuration to work, but the config is easily modified as it is stored in a hidden file in your home folder, under &lt;code&gt;~/.config/fish/config.fish&lt;/code&gt;. I have included my config file, adds extra colors and functionality with tools like git, pip, etc.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# Paths to your tackle
set tacklebox_path ~/.tackle ~/.tacklebox
# Theme
set tacklebox_theme entropy
# Which modules would you like to load? (modules can be found in ~/.tackle/modules/*)
# Custom modules may be added to ~/.tacklebox/modules/
# Example format: set tacklebox_modules virtualfish virtualhooks
set tacklebox_modules virtualfish virtualhooks
set tacklebox_plugins extract grc pip python up
# Which plugins would you like to enable? (plugins can be found in ~/.tackle/plugins/*)
# Custom plugins may be added to ~/.tacklebox/plugins/
# Example format: set tacklebox_plugins python extract
# Load Tacklebox configuration
. ~/.tacklebox/tacklebox.fish
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src="media/2016/09/fish_sh_example.png" alt="fish_sh_example"&gt;&lt;/p&gt;
&lt;p&gt;If you have issues, the Fish Shell Website has some great examples and documentation:  &lt;a href="https://fishshell.com/"&gt;https://fishshell.com/&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title>Quiver, My New Favorite Note Taking App</title><link>https://blog.tompaulus.com/quiver-sync/</link><pubDate>Mon, 04 Jul 2016 21:40:53 +0000</pubDate><guid>https://blog.tompaulus.com/quiver-sync/</guid><description>Evernote was the note taking application was the most popular option for a number of years and was the least bad of the options out there. However, recently Evernote has been promoting their paid plans in a way the hurts their free users. Removing popular features, like emailing in notes (1), and imposing extra restrictions, like limiting you to only 2 devices (2), made me reconsider if Evernote …</description><category>Software</category><media:content medium="image" url="https://blog.tompaulus.com/quiver-sync/media/2016/12/qs-banner.png"/><content:encoded>&lt;p&gt;Evernote was the note taking application was the most popular option for a number of years and was the least bad of the options out there. However, recently Evernote has been promoting their paid plans in a way the hurts their free users. Removing popular features, like emailing in notes &lt;a href="https://help.evernote.com/hc/en-us/articles/209005347-How-to-save-email-into-Evernote"&gt;(1)&lt;/a&gt;, and imposing extra restrictions, like limiting you to only 2 devices &lt;a href="https://blog.evernote.com/blog/2016/06/28/changes-to-evernotes-pricing-plans/"&gt;(2)&lt;/a&gt;, made me reconsider if Evernote was really the best option out there. Popular alternatives to Evernote at the time of writing include: &lt;a href="https://simplenote.com/"&gt;Simplenote&lt;/a&gt;, and Microsoft’s &lt;a href="https://www.onenote.com/"&gt;OneNote&lt;/a&gt;. I like to avoid Microsoft wherever possible, and Simplenotes offerings were average at best, and the reviews did not help sell me on their product.&lt;/p&gt;
&lt;p&gt;&lt;img src="media/2016/07/quiver_sync-evernot-lib.png" alt="I have quite a few notes in Evernote. Thankfully, Quiver can import notes in the Evernote .enex format."&gt;&lt;em&gt;I have quite a few notes in Evernote. Thankfully, Quiver can import notes in the Evernote .enex format.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The advantage to products like OneNote, Evernote, and Simplenote is that they offer Mobile apps and Syncing across devices. I rarely find myself editing, or creating notes on my device, but I would like to be able to access notes on the go, just in case (usually for reference purposes). The Apple App Store features &lt;a href="http://happenapps.com/#quiver"&gt;Quiver&lt;/a&gt; as one of their “Apps for Developers” and since starting college, I have used Evernote primarily to take notes in class (many of which are Computer Science Courses). If you have ever tried formatting a note in Evernote, you know the nightmare that it is. And while apps like &lt;a href="http://alternoteapp.com/"&gt;Alternote&lt;/a&gt; make it a little easier, they still don’t make it easy. Quiver fixes all of their with their innovative approach, which they call cells. Cells are different areas of your note that can be formated as text, code (with syntax highlighting for a bunch of languages), Markdown, LaTeX, or Diagram; all of which are rendered beautifully by the App. The app even supports importing Evernote Notebooks! The only downside for me is that there is no sync engine, or way to view my notes on the go (either on a mobile device or in the cloud).&lt;/p&gt;
&lt;p&gt;In order to take my notes on the go, I took advantage of their export script that converts entire notebooks of notes into HTML folders. The files themselves are hosted on my webserver and are displayed via a beautiful jQuery, PHP file explorer from &lt;a href="http://tutorialzine.com/2014/09/cute-file-browser-jquery-ajax-php/"&gt;Tutorialzine&lt;/a&gt;. While the Quiver Notebook Library is saved in Google Drive (mostly for backups and safe keeping), I am using the very powerful rsync command to sync the library between my mac and the web server.&lt;/p&gt;
&lt;h3 id="server-setup"&gt;Server Setup&lt;/h3&gt;
&lt;p&gt;Set up a folder (with password protection if you like) with your web server of choice (mine is NGINX) and copy the files from the repo into the folder. Take note of the path of the folder since you will need to enter it into the sync script that we will install on the mac.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/tpaulus/quiver_sync/tree/master/Server"&gt;Download the File Explorer and Assets from GitHub.&lt;/a&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Install the &lt;a href="https://github.com/HappenApps/quiver2html"&gt;quiver2html&lt;/a&gt; node module. &lt;code&gt;npm install -g quiver2html&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;You will want to set up Apache, NGINX, or your web server of choice to serve the PHP content in the folder you cloned the server files into. Google is your friend here.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When you are done, navigate to the URL for your folder and you should see something like this.&lt;/p&gt;
&lt;p&gt;&lt;img src="media/2016/07/quiver_sync-Server-Setup-Complete.png" alt="quiver_sync - Server Setup Complete"&gt;&lt;/p&gt;
&lt;h3 id="mac-setup"&gt;Mac Setup&lt;/h3&gt;
&lt;p&gt;We need to configure the computer to sync the Notebook Library automatically and request that the server convert the library into HTML. This is achieved with a simple shell script that is run every 5 minutes by launchctl, Apple’s version of CRON on newer OSs. Launchctl uses specialized plist files (which can be created with &lt;a href="http://launched.zerowidth.com/"&gt;launched&lt;/a&gt;) to tell it when to run what command.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/tpaulus/quiver_sync/tree/master/Mac"&gt;Download the Shell Script and Plist File from GitHub.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Update the script’s variables to match your installation.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;QUIVERLIB_LOC=&amp;#34;Path/to/Quiver.qvlibrary&amp;#34;
SERVER=&amp;#34;lorien&amp;#34; # Host Name of your server, as set in .ssh/config
SRV_DEST=&amp;#34;/srv/notes/&amp;#34; # Public HTTP Directory where Notebooks folder is
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Copy the shell script to your local/bin folder.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo cp quiver_sync.sh /usr/local/bin/quiverSync
sudo chmod +x /usr/local/bin/quiverSync
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The advantage with placing the file in your local bin folder is that you can call “quiverSync” at anytime from your command line to toggle a sync. Now would be a good time to test the script to see if it works before continuing. Take a look at the website and you should see something like this.&lt;/p&gt;
&lt;p&gt;&lt;img src="media/2016/07/quiver_sync-Mac-Complete.png" alt="quiver_sync - Mac Complete"&gt;&lt;/p&gt;
&lt;p&gt;If everything is working the way it should, place the launchctl script into the Launch Agents folder. If the &lt;code&gt;~/Library/LaunchAgents&lt;/code&gt; directory does not yet exists, you can create it with “mkdir”. There are several Launch Agent folders on your computer, the folder in your user library only runs when you are logged in, which is what we want, since you will likely not be making changes to your quiver notes while you are not logged in.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo cp com.whitestarsystems.quiver_sync.plist \
~/Library/LaunchAgents/com.whitestarsystems.quiver_sync.plist
launchctl load -w ~/Library/LaunchAgents/com.whitestarsystems.quiver_sync.plist
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="conclusion"&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;I think that Quiver, with the addition of the sync tool we implemented, will replace Evernote for me. It is an extremely powerful note-taking tool that won’t be charging you monthly per-device. It may not have as many features as the competition, but it serves its purpose for me very well.&lt;/p&gt;
&lt;p&gt;&lt;img src="media/2016/07/quiver_sync-mobile.png" alt="Looks awesome on mobile devices"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="media/2016/07/quiver_sync-Example-Note.png" alt="Notes render beautifully"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="media/2016/07/quiver_sync-Example-in-App.png" alt="quiver_sync - Example in App"&gt;&lt;/p&gt;</content:encoded></item><item><title>CodeDay San Diego Winter 2016</title><link>https://blog.tompaulus.com/codeday-san-diego-winter-2016/</link><pubDate>Wed, 24 Feb 2016 09:20:18 +0000</pubDate><guid>https://blog.tompaulus.com/codeday-san-diego-winter-2016/</guid><description>The sixth CodeDay in San Diego was a huge success. We returned to CyberHive this season, the venue that hosted the first CodeDay back in May 2014. This event was a little smaller than previous events, only 75 attendees registered for the event; however, the event was still a great success!</description><category>Events</category><category>CodeDay</category><media:content medium="image" url="https://blog.tompaulus.com/codeday-san-diego-winter-2016/media/2016/02/PANO_20160213_123600.jpg"/><content:encoded>&lt;p&gt;The sixth CodeDay in San Diego was a huge success. We returned to CyberHive this season, the venue that hosted the first CodeDay back in May 2014. This event was a little smaller than previous events, only 75 attendees registered for the event; however, the event was still a great success!&lt;/p&gt;
&lt;p&gt;We lead more workshops than ever before, and included content for attendees who came to the event with some experience (an audience that has been underserved in past events).&lt;/p&gt;
&lt;p&gt;&lt;img src="media/2016/02/IMG_20160213_172411.jpg" alt="CodeDay TweetWall"&gt;&lt;/p&gt;
&lt;h3 id="best-overall"&gt;Best Overall&lt;/h3&gt;
&lt;p&gt;&lt;img src="media/2016/02/IMG_20160214_115554.jpg" alt="Best Overall Team Photo"&gt;&lt;br&gt;
&lt;strong&gt;Team: Team Tired&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Project: 5 More Minutes&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This was Alexis’ first CodeDay and he blew us away with his Pebble Watch App that counted down the minutes till his class was over. He used tools and languages that he did not know coming into the event, something that the judges commended him for. Everyone was very impressed with Alexis’ work.&lt;/p&gt;
&lt;h3 id="bestapp"&gt;Best App&lt;/h3&gt;
&lt;p&gt;&lt;img src="media/2016/02/IMG_20160214_115448.jpg" alt="Best App Team Photo"&gt;&lt;br&gt;
&lt;strong&gt;Team: ¯\_(ツ)_/¯&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Project: RNG Matchmaking&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This Valentine’s themed app match attendees from various CodeDay Events and other Coding Organizations to each other using a “complex algorithm.” They took the results a step further, showing the audience how there was a gender imbalance in the technology sector and how we can promote women to get involved with coding.&lt;/p&gt;
&lt;h3 id="bestgame"&gt;Best Game&lt;/h3&gt;
&lt;p&gt;&lt;img src="media/2016/02/IMG_20160214_115407.jpg" alt="Best Game Team Photo"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Team: Anthony&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Project: Jump&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Anthony continues to amaze event after event. This time he created an addicting game that featured a bird that had to jump up on randomly appearing platforms. If you failed to land on a platform, the bird would have a very un-happy ending. Anthony always amazes us with his willingness to help others and his creativity, even at 1am in the morning.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="special-award--mostmemes"&gt;Special Award – Most Memes&lt;/h3&gt;
&lt;p&gt;&lt;img src="media/2016/02/IMG_20160214_115701.jpg" alt="Special Award #1 Team Photo"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Team: Meme Team&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Project: Meme Defense Force&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This award was well deserved by Sam and Johnny! They combined the best meme’s and made a tower defense style game from villains like DJ Khaled. We all got a good laugh from their game. We hope they make another one!&lt;/p&gt;
&lt;h3 id="special-award-most-musical"&gt;Special Award – Most Musical&lt;/h3&gt;
&lt;p&gt;&lt;img src="media/2016/02/IMG_20160214_115751.jpg" alt="Special Award #2 Team Photo"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Team: Fourier&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Project: GNUSynth&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Leo and Tiahm created an app to solve a problem. Tiahm creates music on his computer, but often needs a synthesizer, so they created one. The app used a myriad of audio analysis algorithms, including the Fourier Analysis (where the team got their name from).&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Although this was one of San Diego’s smaller events, it was still a great success and the number of attendees worked well with the size and design of the venue.&lt;/p&gt;
&lt;h3 id="special-thanks-to"&gt;Special Thanks to…&lt;/h3&gt;
&lt;p&gt;Our Judges: Darin Anderson, Mo Rahseparian&lt;/p&gt;
&lt;p&gt;Our Local Event Management Team: Ayan (Public &amp;amp; Community Relations), TJ (Event Programming), and Alex (Day-Of Coordinator)&lt;/p&gt;
&lt;p&gt;Our Local Organizers: Agnieszka, Salman, Leo and Dan&lt;/p&gt;
&lt;p&gt;Our Venue Staff: Darin and Mo&lt;/p&gt;
&lt;p&gt;Our Venue Sponsor: CyberTech (Home to CyberHive, iHive and Nest)&lt;/p&gt;
&lt;p&gt;Our Community Sponsor: Cox California&lt;/p&gt;</content:encoded></item><item><title>CodeDay San Diego Fall 2015</title><link>https://blog.tompaulus.com/codeday-san-diego-fall-2015/</link><pubDate>Wed, 11 Nov 2015 16:18:36 +0000</pubDate><guid>https://blog.tompaulus.com/codeday-san-diego-fall-2015/</guid><description>The fifth CodeDay in San Diego was a huge success. Hosted at the new Central Library in Downtown San Diego, this CodeDay was one of the largest events yet. We had over 140 attendees register for the event, and 94 attendees ended up checking in to the event.</description><category>Events</category><category>CodeDay</category><media:content medium="image" url="https://blog.tompaulus.com/codeday-san-diego-fall-2015/media/2015/11/IMG_20151107_132638.jpg"/><content:encoded>&lt;p&gt;The fifth CodeDay in San Diego was a huge success. Hosted at the new Central Library in Downtown San Diego, this CodeDay was one of the largest events yet. We had over 140 attendees register for the event, and 94 attendees ended up checking in to the event.&lt;/p&gt;
&lt;p&gt;Looking back, the event was a big success for both the attendees and the staff members.&lt;/p&gt;
&lt;p&gt;&lt;img src="media/2015/11/View.jpg" alt="View from Helen Price Reading Room"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="media/2015/11/IMG_20151107_134600.jpg" alt=""&gt;&lt;br&gt;
&lt;img src="media/2015/11/IMG_20151107_153841.jpg" alt=""&gt;&lt;br&gt;
&lt;img src="media/2015/11/2015-11-07-16.24.50.jpg" alt=""&gt;&lt;br&gt;
&lt;img src="media/2015/11/Slack-for-iOS-Upload.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;We took over the Helen Price Reading Room on the 8th floor, a beautiful space with a great view of the bay. CodeDay worked in collaboration with the City of San Diego, as well as the San Diego Public Library to put on the event. CodeDay San Diego was also made possible by &lt;a href="https://www.jetbrains.com/"&gt;JetBrains&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We had quite a few workshops at CodeDay San Diego, including an intro to Python Workshop that used &lt;a href="https://www.jetbrains.com/pycharm-edu/"&gt;PyCharm EDU&lt;/a&gt;; as well as an awesome Data Analysis workshop lead by Andrell Bower, from the City of San Diego.&lt;/p&gt;
&lt;h3 id="best-overall"&gt;Best Overall&lt;/h3&gt;
&lt;p&gt;&lt;img src="media/2015/11/IMG_20151108_111038.jpg" alt="Best Overall Team Photo"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Team: Assaf&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We were very impressed by Assaf’s effort, he did not stop after building his snake &amp;amp; apple game, which was very cleanly coded, he went on to build a working, multi-user chat room using scratch, which involved complex encryption and several work-arounds to work with Scratch.&lt;/p&gt;
&lt;h3 id="bestapp"&gt;Best App&lt;/h3&gt;
&lt;p&gt;&lt;img src="media/2015/11/2015-11-08-11.12.57.jpg" alt="Best App Team Photo"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Team: Ronak&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We really liked how well built this app was and how it was designed to serve a purpose. It was also one of the few apps that was fully deployed to a device.&lt;/p&gt;
&lt;h3 id="bestgame"&gt;Best Game&lt;/h3&gt;
&lt;p&gt;&lt;img src="media/2015/11/2015-11-08-11.14.13.jpg" alt="Best Game Team Photo"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Team: Treacherous Turnips&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We really liked how this team had an idea and that they went with it. We also really liked the concept and implementation, as well as the fact that it was an homage to their CS teacher.&lt;/p&gt;
&lt;h3 id="special-award--most-creative"&gt;Special Award – Most Creative&lt;/h3&gt;
&lt;p&gt;&lt;img src="media/2015/11/2015-11-08-11.15.29-1.jpg" alt="Most Creative Team Photo"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Team: Gatekeepers&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We really liked their concept and how it stood out from all the other games at CodeDay San Diego. We were also very impressed with the implementation and the strategy behind the game.&lt;/p&gt;
&lt;h3 id="special-award--best-effort"&gt;Special Award – Best Effort&lt;/h3&gt;
&lt;p&gt;&lt;img src="media/2015/11/IMG_20151108_111642.jpg" alt="Special Award Team Photo"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Team: TincyTurtle&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We really liked how this is Austin’s second CodeDay and how he has grown since his last event. His game was very well made and although it was not perfect, it demonstrated excellent effort on his part.&lt;/p&gt;
&lt;h3 id="attendees-at-work"&gt;Attendees at Work…&lt;/h3&gt;
&lt;p&gt;&lt;img src="media/external/3bcc2dde2a358616-2015-11-07-14-06-48.jpg" alt=""&gt;&lt;br&gt;
&lt;img src="media/external/34bb5d7ae4757901-2015-11-07-14-06-51.jpg" alt=""&gt;&lt;br&gt;
&lt;img src="media/external/1c87e0f7dd24d616-2015-11-07-14-06-54.jpg" alt=""&gt;&lt;br&gt;
&lt;img src="media/external/7122d2b68bfb9636-2015-11-07-14-06-56.jpg" alt=""&gt;&lt;br&gt;
&lt;img src="media/external/e5357d77469c9ec2-2015-11-07-14-07-08.jpg" alt=""&gt;&lt;br&gt;
&lt;img src="media/external/e9737ed68418f870-2015-11-08-09-18-20.jpg" alt=""&gt;&lt;br&gt;
&lt;img src="media/external/97d5980fc5ebd12a-2015-11-07-18-46-07.jpg" alt=""&gt;&lt;br&gt;
&lt;img src="media/external/58e7e4d214f7d0f2-2015-11-08-09-04-24.jpg" alt=""&gt;&lt;/p&gt;
&lt;h3 id="presentations"&gt;Presentations…&lt;/h3&gt;
&lt;figure class="kg-card kg-embed-card"&gt;
&lt;iframe
width="560"
height="315"
src="https://www.youtube-nocookie.com/embed/5_aKCCfxYLM?rel=0"
title="CodeDay San Diego Fall 2015 video"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/figure&gt;
&lt;hr&gt;
&lt;p&gt;Overall, we had a really great time and we think that the attendees had a blast as well. Both the Staff and the Attendees felt that this was one of the best events yet.&lt;/p&gt;
&lt;h3 id="special-thanks-to"&gt;Special Thanks to…&lt;/h3&gt;
&lt;p&gt;Our Judges: Lon Miller, Gary Hayslip, Maksim Pecherskiy&lt;/p&gt;
&lt;p&gt;Our Local Organizers: Ayan, and TJ&lt;/p&gt;
&lt;p&gt;Our Day-Off Staff: Alex, Lauren, and Ayesha&lt;/p&gt;
&lt;p&gt;Our Library Staff: Erwin, Sherwood, Ryan, Alejandro, Jackie, Stephanie, Geneva, Taylor, Stacy, Kerry, and Kathryn&lt;/p&gt;
&lt;p&gt;Our Venue Sponsor: The San Diego Public Library&lt;/p&gt;
&lt;p&gt;Our Development Tool Sponsor: JetBrains&lt;/p&gt;
&lt;h3 id="see-you-in-february-for-codeday-winter"&gt;See you in February for CodeDay Winter!&lt;/h3&gt;</content:encoded></item></channel></rss>