<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:base="en">
	<title>JohnJonesFour</title>
	<subtitle>I&#39;m a software engineer and innovator with a creative approach.</subtitle>
	<link href="https://johnjonesfour.com/feed/feed.xml" rel="self"/>
	<link href="https://johnjonesfour.com/"/>
	<updated>2026-03-07T00:00:00Z</updated>
	<id>https://johnjonesfour.com</id>
	<author>
		<name>John Jones</name>
		<email></email>
	</author>
	
	<entry>
		<title>Featured on Make: Building a Rocket Ship in the Basement</title>
		<link href="https://johnjonesfour.com/2026/03/06/rocket-featured-on-make/"/>
		<updated>2026-03-07T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2026/03/06/rocket-featured-on-make/</id>
		<content type="html">&lt;p&gt;My rocketship playhouse was featured on Make&#39;s Substack!&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>F-Stop Printing in the Darkroom — Full Workflow + Custom ΔStop 100 Timer</title>
		<link href="https://johnjonesfour.com/2026/02/13/delta-stop-timer-demo/"/>
		<updated>2026-02-14T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2026/02/13/delta-stop-timer-demo/</id>
		<content type="html">&lt;p&gt;In this video I walk through my full f-stop printing workflow—from the very first ½-stop test strip, to refining exposure in ⅛-stop steps, and finally shaping the image with dodging and burning in stops instead of seconds.&lt;/p&gt;
&lt;p&gt;I also demonstrate a project I’ve been building: a modern enlarger timer designed specifically for f-stop printing, created to make darkroom exposure more predictable, repeatable, and intuitive.&lt;/p&gt;
&lt;p&gt;Whether you’re new to the darkroom or returning to film after years away, this approach can make printing feel far more like craft than chance.&lt;/p&gt;
&lt;p&gt;In this video:&lt;/p&gt;
&lt;p&gt;What f-stop printing is and why it works&lt;/p&gt;
&lt;p&gt;Making a first test strip in ½-stop intervals&lt;/p&gt;
&lt;p&gt;Refining exposure in ⅛-stop steps&lt;/p&gt;
&lt;p&gt;Dodging and burning using stop-based adjustments&lt;/p&gt;
&lt;p&gt;How an f-stop enlarger timer simplifies the entire process&lt;/p&gt;
&lt;p&gt;If you’re interested in the timer project or want to learn more about f-stop printing, links and details are below.&lt;/p&gt;
&lt;p&gt;Thanks for watching—and happy printing.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>My Son Asked for a Rocket Ship… So I Built One</title>
		<link href="https://johnjonesfour.com/2026/01/18/rocketship-playhouse/"/>
		<updated>2026-01-19T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2026/01/18/rocketship-playhouse/</id>
		<content type="html">&lt;p&gt;What happens when your son watches Apollo 13 on repeat and asks for a rocket ship? You start sketching… then open Fusion… and suddenly you’re building a two-story rocket ship playhouse.&lt;/p&gt;
&lt;p&gt;This project was inspired by an old Make magazine video, but I leveled it up into a two-level structure with a hatch, windows, ladder, and a control panel inside. I focused more on structure, durability, and imagination than complicated mechanical gimmicks—and I think that paid off.&lt;/p&gt;
&lt;p&gt;The build combines:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A 2×3 framed structure with OSB flooring&lt;/li&gt;
&lt;li&gt;Laser-cut hatches and window openings&lt;/li&gt;
&lt;li&gt;3D-printed details like engine bells, window trim, and handles&lt;/li&gt;
&lt;li&gt;Fabric-lined interiors using moving blankets for a real spacecraft feel&lt;/li&gt;
&lt;li&gt;Vinyl graphics, flexible trim, and mock thrusters&lt;/li&gt;
&lt;li&gt;A custom control panel with switches, displays, sound effects, a bass shaker, and two interactive game modes for docking and landing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It’s big, it’s ambitious, it barely fits in my basement, and my very excited four-year-old supervised the entire build.&lt;/p&gt;
&lt;p&gt;There are a few things I’d love to improve in a future update—like glowing engines and reworked window trim—so let me know what you’d do differently or what upgrades you’d like to see next.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Pegboard Everywhere: Start to Finish Workbench Build</title>
		<link href="https://johnjonesfour.com/2025/11/15/pegboard-everywhere/"/>
		<updated>2025-11-16T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2025/11/15/pegboard-everywhere/</id>
		<content type="html">&lt;p&gt;I completely rebuilt the back wall of my workbench — and the result is a fully custom pegboard system packed with storage for electronics, tools, and all my photography + videography gear.&lt;/p&gt;
&lt;p&gt;In this video, I walk through the entire build on John Builds Things: from framing a massive 8&#39; x 2&#39; pegboard panel to routing the edges, adding LED lighting, designing 3D-printed organizers, and wiring everything up with ESPHome and Home Assistant.&lt;/p&gt;
&lt;p&gt;If you saw my last video, you may have noticed the new backdrop — today you get the full build!&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>DIY Toy Rocket Ship Panel (Apollo-Inspired) – My Son’s Favorite!</title>
		<link href="https://johnjonesfour.com/2025/09/10/toy-rocketship-panel/"/>
		<updated>2025-09-11T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2025/09/10/toy-rocketship-panel/</id>
		<content type="html">&lt;p&gt;Take imaginative play to the next level 🚀! In this project, I built my 4-year-old a homemade Apollo-inspired rocket control panel using laser-cut acrylic, UV printing, and an Arduino. Complete with flashing lights, toggle switches, and authentic Apollo sound effects, this prototype turned a simple play tent into a full space mission.&lt;/p&gt;
&lt;p&gt;Along the way, I learned a lot about laser cutting, UV printing, Arduino coding, and electronics assembly—and I share all the details in this video.&lt;/p&gt;
&lt;p&gt;Whether you’re into DIY builds, parenting hacks, Apollo-era nostalgia, or just love space history, this project has something for you. And stay tuned—this is just a prototype for a much bigger build coming soon!&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>I Built the Ultimate Darkroom Timer (and Finally Finished My Dream Film Space)</title>
		<link href="https://johnjonesfour.com/2025/07/10/i-built-the-ulimate-darkroom-timer/"/>
		<updated>2025-07-11T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2025/07/10/i-built-the-ulimate-darkroom-timer/</id>
		<content type="html">&lt;p&gt;Welcome to my film photography sanctuary — a custom-built darkroom with a DIY ESP32-powered development timer at its heart. In this video, I’ll give you a full tour of the space I spent years dreaming about and months building.&lt;/p&gt;
&lt;p&gt;You’ll learn how I designed my flush-mounted darkroom timer, built a custom epoxy-coated sink, and organized a fully functional 5x10 darkroom with ventilation, water, and light control — all tailored for black &amp;amp; white film and paper development.&lt;/p&gt;
&lt;p&gt;▶️ What you&#39;ll see:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;My DIY light table and Besseler enlarger setup&lt;/li&gt;
&lt;li&gt;A walkthrough of the ESP32-based development timer&lt;/li&gt;
&lt;li&gt;A web-controlled interface for film timing&lt;/li&gt;
&lt;li&gt;How I built and plumbed my own development sink&lt;/li&gt;
&lt;li&gt;Tips for efficient darkroom layout and storage&lt;/li&gt;
&lt;li&gt;STL file links for 3D printed parts (below)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Whether you’re a fellow film shooter, a DIY tech enthusiast, or just here for the satisfying build footage — you’re in the right place.&lt;/p&gt;
&lt;p&gt;🛠 All STL files &amp;amp; resources available in the description below.&lt;/p&gt;
&lt;p&gt;📸 Don’t forget to like, subscribe, and check out the rest of the John Builds Things channel for more film photography and DIY tech builds.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>I Turned My Basement Into a Home Theater | DIY Star Ceiling + Room Makeover</title>
		<link href="https://johnjonesfour.com/2025/06/14/i-turned-my-basement-into-a-home-theater%20copy/"/>
		<updated>2025-06-15T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2025/06/14/i-turned-my-basement-into-a-home-theater%20copy/</id>
		<content type="html">&lt;p&gt;Transforming a dusty spare room into a full-blown home movie theater? Challenge accepted.&lt;/p&gt;
&lt;p&gt;In this DIY home theater build, I take you behind the scenes of turning our unused basement room into a cozy, cinematic escape — complete with a custom starfield ceiling, hunter green walls, and a surround sound system (all using FREE equipment left by the previous owners!).&lt;/p&gt;
&lt;p&gt;Whether you&#39;re a home improvement junkie, a movie lover, or someone just curious about building your own theater setup on a budget, this project shows what’s possible with a bit of elbow grease and creativity.&lt;/p&gt;
&lt;p&gt;▶️ In this video you&#39;ll see:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The full room transformation from spare room to theater&lt;/li&gt;
&lt;li&gt;How I designed and built a DIY fiber optic star ceiling&lt;/li&gt;
&lt;li&gt;My painting and prep tips for getting that classic theater feel&lt;/li&gt;
&lt;li&gt;How I mounted posters and set up the surround sound system&lt;/li&gt;
&lt;li&gt;The gear I inherited for free (and how I made it all work!)&lt;/li&gt;
&lt;li&gt;Final walkthrough + cinematic tour of the completed room&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;🎥 Shot with time-lapse, voiceover, and lots of practical tips.&lt;/p&gt;
&lt;p&gt;This was a true passion project and I hope it inspires your own.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Building a CNC + LED Smart Clock</title>
		<link href="https://johnjonesfour.com/2025/05/19/3d-printed-cnc-machine-clock/"/>
		<updated>2025-05-20T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2025/05/19/3d-printed-cnc-machine-clock/</id>
		<content type="html">&lt;p&gt;My office has plenty of great toys, but it was missing something important: a stylish clock. Since I had just finished assembling a lightweight, 3D-printed CNC machine based on a Dremel, I started brainstorming ways to put it to use. That’s when I came across these circular programmable LEDs from Adafruit. The contrast between modern LED lighting and natural wood struck me as a perfect concept for a sleek, custom wall clock.
See how I built this clock today on John Builds Things.&lt;/p&gt;
&lt;p&gt;This CNC machine is simple and not particularly robust—the creator even acknowledges that in the project&#39;s description. It’s designed as an easy entry point for hobbyists looking to explore CNC milling.
My motivation for building it was straightforward: I had spare stepper motors and an ESP32 CNC driver board left over from another project. Since I already had some of the key components, I figured, why not print the rest and put it together? While printing all the parts took time, before long, I had a working CNC machine.
With the CNC machine assembled and my clock project in mind as its first real test, I started planning. Constraints can help guide a creative process, and in this case, I had two major ones:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;First, the millable area on this small CNC machine is really only a few inches across. That would limit me from designing anything that required large contiguous pieces of wood.&lt;/li&gt;
&lt;li&gt;Second, the LED rings from Adafruit were a fixed size. I found that the outer diameter of the largest LED ring was beyond the millable area of my CNC machine, and so that outer ring would have to be a separate part from the wooden face that sat inside.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With those constraints established, I decided I would mill the main wooden face with the smaller ring embedded in the face behind a 3D printed cover. This small ring would show the hours much like the hour hand on a clock. Then the larger ring would sit in it’s out 3D printed cover surrounding the wooden face. This large ring would show the minutes and seconds like the minute and sweep hands of a clock.&lt;/p&gt;
&lt;p&gt;I created the design in Fusion 360, intending to use its CAD/CAM tools to generate the G-code for the CNC machine. However, I found the software overwhelming—it&#39;s built for serious milling professionals.
Instead, I turned to Kiri:Moto, a web-based tool with a much more intuitive and streamlined interface, which made setting up the CNC job significantly easier.&lt;/p&gt;
&lt;p&gt;Since this was my first CNC project, I approached it carefully. There were many variables to consider when dialing in the right speeds and settings. Jumping straight into hardwood would have been a mistake—not to mention a safety hazard—so I started with plywood, testing different settings with partial models before committing to full-size cuts.&lt;/p&gt;
&lt;p&gt;Here are a few things I learned:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It’s loud. A high-speed Dremel creates a piercing whine that gets annoying fast.&lt;/li&gt;
&lt;li&gt;It requires slow cutting speeds. My initial settings generated so much smoke I thought it might ignite.&lt;/li&gt;
&lt;li&gt;It’s not a “set and forget” machine. Unlike 3D printing, I couldn’t walk away for long periods.&lt;/li&gt;
&lt;li&gt;I went through a LOT of test pieces. Trial and error was necessary to get it right.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;After many iterations, I finally landed on the right settings. My patience paid off—the final cut turned out well. I did have to do some manual finishing since the machine didn’t quite cut through all the wood fibers, but that’s expected from an entry-level setup.&lt;/p&gt;
&lt;p&gt;To ensure the backside cuts aligned with the front, I 3D-printed a positioning jig. This allowed me to center the milling bit accurately and align the CNC machine’s starting point with the G-code. The method worked perfectly, and the back cut matched up exactly with the front. After milling, I sanded the wood, drilled a hole for wiring using a drill press, and sealed everything with a few coats of Danish oil.&lt;/p&gt;
&lt;p&gt;The easiest part of this build was 3D-printing the LED covers. I love 3D printing—it’s much quieter than CNC milling!&lt;/p&gt;
&lt;p&gt;Let&#39;s go through this code. So in the setup function, we do some basic value intialization and deal with some compiler macros I wrote to toggle various test modes and log debug info to the default serial output. After that FastLED starts up to control the two GPIO pins controlling the LEDs. I actually could&#39;ve just used one GPIO pin and daisy chained the LED rings. Either would be good approaches I think. After that we setup a few classes I wrote: Face is a class that encasulates control of one or more clock hands and then we setup the NTP - network time protocol - controller with Wifi. I thought about using a spare GPS board I have to get the time data from space, but that board doesn&#39;t always get a great signal. NTP time of Wifi would be fine.&lt;/p&gt;
&lt;p&gt;In the loop statement, we force the NTP class to update it&#39;s internal time and then we get that back as a time struct. We use that time struct to inspect the current second, minute, and hour. With those values then we can know what percentage threw the current minute, hour, and day we are. That percentage determines which LED index to illuminate and which to turn off. Now we could just do a simple color for each hand of the clock but I wanted to do something a little cooler looking. Instead I asked ChatGPT for some help with a basic color algorithm that turns a Hue/Saturation/Lightness value into an RGB value. That way, I could take the percentage value and convert that to a Hue value, which is really just a percentage. This creates the effect of a gradually changing gradient as each hand progresses from 0 to 59 for seconds and minutes and 0 to 23 for hours.&lt;/p&gt;
&lt;p&gt;Here&#39;s a deeper look at Face.cpp which sets the hand positions on the LED rings. This class knows the previous positions of the hands and when it receives updated positions it first tries to clear the old position or restore the other hand&#39;s color value if the positions overlap. Then it sets the position and color from tne new values. This nice bit of encapsulation made it easy to debug.&lt;/p&gt;
&lt;p&gt;Turning to GradientColor, this is the class that does some color math thanks to ChatGPT. With the percentage input value, that allows us to calculate where on the Hue gradient we should be. Saturation and Lightness are fixed values that I set based on preference. Then the conversion algorithm takes that HSL and turns it into an RGB.&lt;/p&gt;
&lt;p&gt;Assembly was fairly easy, but the hardest part was soldering together the large ring. I had to carefully link the three traces for positive, data, and ground which were quite tiny. To reinforce the connection - which I felt was quite flimsy - I dabbed a bit of glue on each connection and let it set overnight.&lt;/p&gt;
&lt;p&gt;For the electronics, I originally prototyped the code using a full ESP32 board but later switched to an ESP32-C3, which is smaller and better suited for the compact design. The LED control is handled using the FastLED library, which makes setting RGB values for an LED array straightforward and highly customizable.
With the rings soldered and ready, I set the microcontroller into “test mode” so that it would only light the top – 0 position – led so that I could get alignment right. Making sure the top LED was exactly centered was critical to making this look great. Now with position set I could cover up the inner ring with it&#39;s plastic cover. I regret not milling out that channel just two or three millimeters deeper because when I pass my hand over the club I feel just a slight rise from the ring. It&#39;s not enough to notice from afar, but it&#39;s one of those things that I know and that just bugs me. I&#39;ll fix it next time I do this I guess. With the front assembled, I just had to insert the piece in the back for hanging on a hook and compact the wiring and microcontroller into the cavity in the back.&lt;/p&gt;
&lt;p&gt;And that’s it—the clock is finished! This project was a fantastic test for my CNC machine. Despite its limitations, it handled the task well, and I’m excited to push its capabilities further in future builds. Thanks for watching! If you enjoyed this project, don’t forget to like, subscribe, and hit the bell icon to stay updated on John Builds Things!&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Ultimate Ford Maverick Mod: A Flux Capacitor!</title>
		<link href="https://johnjonesfour.com/2024/12/30/ford-maverick-flux-capacitor/"/>
		<updated>2024-12-31T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2024/12/30/ford-maverick-flux-capacitor/</id>
		<content type="html">&lt;p&gt;If you’re going to build a Time Machine into a car (or truck), why not do it with some style? That was exactly the question I had as a Back to the Future fan  myself. This question even vexed my toddler. He’d ask me: Why can’t my truck go 88 mph in a residential area? Where is the Flux Capacitor? What is a Flux Capacitor? Why can&#39;t we climb up to the top of our town courthouse clock? These little guys ask the tough questions.&lt;/p&gt;
&lt;p&gt;Let’s answer those questions today on John Builds Things&lt;/p&gt;
&lt;p&gt;So why much love for Back to The Future? It was one of the first movies that really inspired me to pursue a career in tech. Doc Brown’s reassurance to Marty that “if you put your mind to it; you can accomplish anything” has always resonated with me and it serves as a constant reminder to persevere and work through challenges one problem at a time. I introduced my son to the movie mostly because the time travel scenes are just so cool, but in time he will hopefully be able to absorb the same lesson. Until then, we’ll both enjoy watching that Delorean accelerate up to 88 and disappear in a flash of 80s special effects brilliance.&lt;/p&gt;
&lt;p&gt;Given the affection for Back to the Future I’ve created in my house, it seemed only right to take on a project in honor of it. My 2022 Ford Maverick, a compact truck I’ve also fallen in love with, has a bit of a design quirk: on the dashboard there is a cubby with seemingly little utility. The maker community has had no shortage in ideas for things to fill it with: everything from the practical like a phone holster to the impractical but awesome like a chicken nugget and sauce caddy. These are great projects in themselves. But they won’t get you back to 1955 or to the um future of 2015 anytime soon. I decided that this little cubby is where I’d put a flux capacitor for both my and my son&#39;s amusement.&lt;/p&gt;
&lt;p&gt;The area is pretty small, measuring only 50 by 80 ish millimeters, but that seemed like just enough room for a simple model. I did find a project on Printables that was a sort of blank for creating models for that cubby, but upon close inspection the dimensions were a little off. My assumption is that this was made for a different model year where they had changed sizes slightly.&lt;/p&gt;
&lt;p&gt;I looked around on printing sites for a compact flux capacitor model, but nothing great came up. Having built a larger one a few years ago, I was not afraid of the challenge. The trick would be matching the cubby’s cavity closely.&lt;/p&gt;
&lt;p&gt;That was no worry; I just measured the space myself taking extra care. With the data in hand I modeled it in Fusion360 and printed it on my Ender 3 S1 Pro. The model is in a few pieces to accommodate the different colored parts on the front and to allow easy assembly of the front and back as separate components. The Fluxing effect is achieved just with a set of bright white LEDs powered by a simple script running on a Raspberry Pi Pico. I chose the Pico because honestly I had one laying around. This code is so simple that it will run on just about any microcontroller.&lt;/p&gt;
&lt;p&gt;Let&#39;s take a look at the code. After some simple initialization code in the setup section, the loop section isn&#39;t much more complicated. All the script really does is control three GPIO outputs to create the LED chasing effect. There are three basic modes: a normal mode with a static time delay for the chasing and a dynamic mode that gradually decreases the delay over a few seconds and third “time travel” mode blinks all the LEDs to simulate a time travel sequence. Activating that dynamic mode is done via a button wired to another GPIO pin in a pull up configuration. Again, this code is so simple it could run on just about any microcontroller that supports the Arduino framework. Of course it would be pretty simple to port this to another framework or language.&lt;/p&gt;
&lt;p&gt;The great thing about the Pi Pico in this project is that it comes without the header pins pre-soldered. That way, I did not need to fuss with wiring up a protoboard or manipulating the existing pins;&lt;/p&gt;
&lt;p&gt;I could just solder my jumpers right in. In this case I only need to solder the three leads for the chasing LEDs. (There are nine total LEDs but they are synchronized so you can tie three together to the same pin each) Those of course get wired to ground in addition to the “time travel mode” pushbutton that then also gets soldered to another GPIO pin. That’s all. Just four GPIO pins and ground/power. Oh the power is being supplied by a AA battery pack. We let the Pi Pico regulate the volts. AA batteries aren’t always ideal for microcontrollers because of their falloff as they drain, but this is a simple enough project that it doesn’t really matter. We’re not doing any serious computing.&lt;/p&gt;
&lt;p&gt;The toughest challenge with this project was the small size. Making it look like a convincing prop while fitting into a space of only a few square millimeters was a unique task. To glue up the various parts of the model I used a special type of glue that can be cured instantly using a special light.&lt;/p&gt;
&lt;p&gt;I just stumbled upon this one day at Home Depot but I absolutely love it. It’s obviously much more expensive than normal glue since the bottle literally has a built in light, so use it sparingly. But that light cures it so fast making small part, tedious glueups light this a breeze.&lt;/p&gt;
&lt;p&gt;Options for installing this project in my truck were a little limited. I was reticent to start drilling into the dash or using anything like glues to secure it, and so I went with a bit of a lazy option and just shoved everything into the cubby and pushed the face back until it fit with a little friction. It definitely wobbles a little when I turn it on or press the little red button, but not so much so that it shakes loose. This gives me the option to modify or remove it in the future without a lot of hassle. I’m in no hurry to get rid of this truck as I absolutely love it, but I want to have my options open.&lt;/p&gt;
&lt;p&gt;There are about a million build your own flux capacitor videos out there, and they all have their merits ranging from quick and easy to authentic film prop reproduction quality. However I’ve never seen one quite like this and so I hope you won’t mind my contribution to this vast pool. Overall this project was a lot of fun and I completed over the course of just two or three weekends, which was mostly eaten up by design and test fitting. Feel free to download the source to the project and the model files to modify and fit in your own dashboard. Just don’t drive too fast or you won’t know when you are.&lt;/p&gt;
&lt;p&gt;Thanks for watching. As always, please like this video and subscribe to John Builds Things.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>ESP32 Powered Darkroom F Stop Printing Timer</title>
		<link href="https://johnjonesfour.com/2024/09/23/esp32-darkroom-timer/"/>
		<updated>2024-09-24T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2024/09/23/esp32-darkroom-timer/</id>
		<content type="html">&lt;h2 id=&quot;abridged-transcript&quot; tabindex=&quot;-1&quot;&gt;Abridged Transcript: &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2024/09/23/esp32-darkroom-timer/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I absolutely love film photography. From the moment I was first given a hand-me-down Nikon SLR in the early 2000s until today, the process of loading a fresh role of film into a camera excites me. Much like most working professionals I spend much of my day staring at a computer screen, and so digital photography lost its luster to me long ago because it ended up just being another screen based activity.&lt;/p&gt;
&lt;p&gt;Instead, film photography is a fully screenless activity. Each frame I shoot has value to me and the work of exposing and developing a print with hands is far more fulfilling than fiddling with some photo adjustment controls in Lightroom. And yes while a big part of the film photography fad includes a healthy - or unhealthy - amount of gear acquisition syndrome, as a technophile I am a big supporter of awesome gear.&lt;/p&gt;
&lt;p&gt;Film photography is so special to me that I convinced my wife to allow me to expand a basement closet into a a proper darkroom. While I&#39;d really love a larger space, this five foot by ten foot room has everything I need for an ergonomic film development and printing workflow.&lt;/p&gt;
&lt;p&gt;It includes a sink that I custom made from wood and an immense amount of epoxy to make it water tight and a dry area that includes a large Beseler enlarger.&lt;/p&gt;
&lt;p&gt;It even has this cool switch panel to toggle timers, lights, and ventilation.&lt;/p&gt;
&lt;p&gt;Today&#39;s video on John Builds Things is about my latest project creating a custom print exposure timer for my darkroom.&lt;/p&gt;
&lt;p&gt;Throughout this video, you’ll seen me assembling the inner components of the timer. Basically, this entails an ESP32 microcontroller board mounted to a custom PCB.&lt;/p&gt;
&lt;p&gt;Despite my insistence that this is an &amp;quot;analog&amp;quot; hobby, I am not so puritanical about the process that I will not employ some digital tools to aid in producing better results. As such I decided my old analog timer was just too limiting, and I had been reading about a more advanced type of printing known as F Stop printing.&lt;/p&gt;
&lt;p&gt;This style of printing is slightly more complicated, and while there exist numerous timers on the new and used market that have this functionality they all are expensive. I&#39;ve seen new timers - really well made timers - costing upwards of $600 dollars online. With a little bit of coding and assembly, I realized I could make one that would likely total less than $100 in parts.&lt;/p&gt;
&lt;p&gt;And so after writing the software and testing on a breadboard, I designed a PCB and sent it away for fabrication. I then designed a case in Fusion 360 that would work nicely at my printing station in my darkroom, and I printed it on my Ender 3 S1 Pro.&lt;/p&gt;
&lt;p&gt;The PCB is mostly just a socket for the ESP32 dev board and screw terminals for the front panel. I use an MCP23017 I/O expander to add a few extra GPIO ports since this timer uses several. A regular old shift register would&#39;ve worked well here too, but I had a bunch of these ICs on hand so I went with that. As always I&#39;ve linked the GitHub repo with the firmware and CAD files for the PCB in the description.&lt;/p&gt;
&lt;p&gt;For the front control panel, I opted for a heavily dial-centric interface. There is a rotary encoder to select the base exposure time. Turning this changes the time in half second increments. And then there are three rotary switches. These select various test and final print option and give the panel a very tactile, analog feel. They are great for working with in the low light conditions of a darkroom.&lt;/p&gt;
&lt;p&gt;Additionally, to make working with the controls easy in the darkroom I used a special technique I saw in a hobby flight simulator channel where you print a few layers of the front panel in black with the label text knocked out and then at the right level swap the filament for white or clear to finish the back of the panel. This allows red LEDs from the PCB to shine through and backlight the display for safe visibility in the dark much like cockpit avionics.&lt;/p&gt;
&lt;p&gt;With assembly and testing complete I installed the timer in my darkroom. On my way too serious looking switch panel, I already had a 5 volt toggle to control my development timer which is also an ESP32 project. I knew the power supply was capable of delivering as many amps as I needed to run two controllers and associated displays and lights, so I opted to tie into that.&lt;/p&gt;
&lt;p&gt;This is the end of the road for my old timer and so that is going into storage. I also ran a cable to below my workstation to hook into the relay control for the enlarger so that it could stay out of sight. It just connects via a headphone jack&lt;/p&gt;
&lt;p&gt;Let&#39;s dive in on how the timer works. On the front panel, there are six controls:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A first toggle switch to flip between test strip mode and final print mode. More on that in a moment&lt;/li&gt;
&lt;li&gt;A second toggle focus switch that overrides the timer and manually enables the exposure light to allow cropping and focusing of the image before printing&lt;/li&gt;
&lt;li&gt;A rotary encoder to set a base time. This defaults to 15 seconds on start&lt;/li&gt;
&lt;li&gt;A rotary switch to select 1/4, 1/3, 1/2 or whole stops when testing&lt;/li&gt;
&lt;li&gt;A rotary switch to select which offset from the base time to expose the final print&lt;/li&gt;
&lt;li&gt;And that all important start button that kicks off the timer&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here&#39;s the typical process using this timer:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Set a base time using the rotary encoder&lt;/li&gt;
&lt;li&gt;Set a stop delta using the rotary switch&lt;/li&gt;
&lt;li&gt;Produce a test strip, repeating by varying the base time and stop delta as necessary&lt;/li&gt;
&lt;li&gt;Flip the timer from test mode into final print mode&lt;/li&gt;
&lt;li&gt;Select which stop offset was optimal from the test strip&lt;/li&gt;
&lt;li&gt;Produce the final print&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In normal test strip printing, I increment the exposure times by fixed intervals, for instance starting at 30 seconds and exposing more of the paper every five seconds. While this seems to make sense at first, linear exposure times do not result in linear changes on a print. Look at the exposure times on any camera, they increment geometrically, doubling every step. In photography that&#39;s referred to as a stop. Generating test strips using geometric increments mimics this approach and results in more useful test prints that get me to my desired final print time much faster.&lt;/p&gt;
&lt;p&gt;This workflow is far more efficient than with my old timer, I feel I&#39;m better able to identify and iterate on proper exposures for my prints. Generally I&#39;m pretty critical of my own work - always looking back on prints and finding little imperfections and flaws that I could&#39;ve spent more time on. That&#39;s the fun and challenge of film photography and photography in general: there&#39;s always another variation, another blend of techniques that can maybe yield a slightly better image. I&#39;m not certain if this timer will reduce that feeling in the future but I do know it helps me move through those variations just a little bit faster which hopefully helps in the future.&lt;/p&gt;
&lt;p&gt;Thanks for watching today. If you like this more photography oriented video and want to learn more about my process and some of the custom tools I&#39;ve built for myself, please let me know in the comments. I&#39;m happy to share more of the projects that I&#39;ve previously done and can spend more time overall showing how I&#39;ve constructed my darkroom. And as always, please like this video and subscribe to John Builds Things. Bye!&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Live Train Announcements with a Raspberry Pi</title>
		<link href="https://johnjonesfour.com/2024/04/11/live-train-announcement-raspberry-pi/"/>
		<updated>2024-04-12T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2024/04/11/live-train-announcement-raspberry-pi/</id>
		<content type="html">&lt;h2 id=&quot;abridged-transcript&quot; tabindex=&quot;-1&quot;&gt;Abridged Transcript: &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2024/04/11/live-train-announcement-raspberry-pi/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;From a pretty young age my son loved trains, and I’ll admit that I do too. In
our first house we lived within earshot of some train tracks, and so out of my
own curiosity about which trains were passing by and to make something
interesting for our precocious toddler, I turned an old Lionel train signal from
Ebay into something that announces arriving and departing trains with some model
railroading flair. It uses a Raspberry Pi Zero to query Amtrak’s live train
timetable and uses AWS Polly and some audio processing to make an authentic
sounding train announcement along with some GPIO magic to activate the signal.&lt;/p&gt;
&lt;h3 id=&quot;software&quot; tabindex=&quot;-1&quot;&gt;Software &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2024/04/11/live-train-announcement-raspberry-pi/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;So I built this gadget almost two years and I decided that it was time to share
it with you all. What you’re seeing here is a bit of a rebuild and refinement of
the original. Like all projects, I started with the software to get something
basic working There were three key things to think through for the software:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Where to get the arrival/departure data&lt;/li&gt;
&lt;li&gt;How to generate the announcement voice&lt;/li&gt;
&lt;li&gt;How to make the audio have an authentic railroading sound&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;getting-the-data&quot; tabindex=&quot;-1&quot;&gt;Getting the Data &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2024/04/11/live-train-announcement-raspberry-pi/&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Amtrak doesn’t expose an actual API, but after some digging I discovered many
people have reverse engineered their live train tracking dashboard to get that
data. Amtrak doesn’t expose an actual API, but after some digging I discovered
many people have reverse engineered their live train tracking dashboard to get
that data.&lt;/p&gt;
&lt;p&gt;I converted this all to Python and compared notes with some others who’ve
reverse engineered this service. In no time I was getting real JSON data output
that was in a fairly easy to understand layout. You can see from this pretty
printed output, the data contains an entry for each train and details which line
it’s running on and the scheduled and real arrival and departure times for each
station on the route.&lt;/p&gt;
&lt;h4 id=&quot;generating-voice&quot; tabindex=&quot;-1&quot;&gt;Generating Voice &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2024/04/11/live-train-announcement-raspberry-pi/&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;For those who aren’t familiar with the Raspberry Pi Zero, it’s the little
sibling of the massively popular Raspberry Pi single board computer. The Zero
offers the same architecture and I/O but in a much smaller form factor and
reduced processor and ram specs. Its small form factor and just enough computing
power to manage the JSON parsing and decryption work made it perfect for this
project. I knew I wanted to run this on something small like a Raspberry Pi Zero
and have a fairly high quality voice so generating it locally was probably out.
And so I turned to AWS Polly to generate the speech. My use of it would be well
below any serious billing threshold and it offered the quality and voice
controls I was looking for.&lt;/p&gt;
&lt;h4 id=&quot;mixing-down&quot; tabindex=&quot;-1&quot;&gt;Mixing Down &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2024/04/11/live-train-announcement-raspberry-pi/&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;I wanted to create an authentic sound mixing the announcer voice with some
atmosphere audio. SOX is a Swiss army knife for audio manipulation and luckily
there’s a Python library that bridges into it which had exactly the
functionality I needed. Using SOX’s manipulation and mixing tools, I could start
with a custom track I premixed containing a train signal bell and ambient crowd
noise simulating a busy train station and then add some echo to the synthesized
voice, prepend some blank audio to add a delay, and mix it. With a little bit of
tweaking to the levels, effects, and timing I had a winner.&lt;/p&gt;
&lt;h3 id=&quot;hardware&quot; tabindex=&quot;-1&quot;&gt;Hardware &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2024/04/11/live-train-announcement-raspberry-pi/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;With the software figured out it was time to build the hardware. I searched the
usual 3D printing sites for any model railroading meshes that I could use and
came up short, so I chose to buy an old Lionel train signal from Ebay. These
signals are designed to run on voltage a bit higher than I would have available
and so the lights and electromagnet to drop the gate would barely function. So I
removed the existing electronics. Then I replaced the lamps with red LEDs and
started designing a base for the signal in Fusion 360 that could also house a
servo motor to actuate the gate.&lt;/p&gt;
&lt;p&gt;With an enclosure I was satisfied with I updated my code to activate both the
audio effects and servo when a train was arriving or departing at my nearby
station. Finally it was time for assembly which was pretty straightforward. I
intentionally left the bottom of this exposed as it would just be sitting up on
a shelf where my family would rarely interact with it. From time to time we do
unplug it as the voice can get a little annoying, but every time it activates it
gives my todler – and me – a thrill. To amplify and output the audio, I used a
speaker bonnet made for the Pi Zero from Adafruit. These tend to go in and out
of production, so any audio hat will do. I wired the new LEDs into two GPIO pins
on the Pi Zero. And wired the servo to another. You can see when it rotates, it
pulls the signal down in place of the electromagnet originally in the signal&lt;/p&gt;
&lt;p&gt;If you’d like to build this yourself, I’ve posted the STL files to Printables
and you’ll just need to get an old Lionel train signal. Just about any model
should be fine. Feel free to make a PR against the repo if you make any cool
modifications. There’s also plenty of documentation on the software so that you
can replicate what I’ve done here.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Nighttime Time Lapse Photography with AllSky</title>
		<link href="https://johnjonesfour.com/2023/11/30/nighttime-time-lapse-photography-with-allsky/"/>
		<updated>2023-12-01T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2023/11/30/nighttime-time-lapse-photography-with-allsky/</id>
		<content type="html">&lt;p&gt;A few weeks ago, I came across an &lt;a href=&quot;https://spectrum.ieee.org/all-sky-camera&quot;&gt;intriguing article on IEEE Spectrum&lt;/a&gt; featuring a Raspberry Pi sky time-lapse project. The project utilized the standard &lt;a href=&quot;https://www.amazon.com/Raspberry-Pi-Camera-Sensitivity-Alternative/dp/B08LHJR3K4?crid=3E19FY02EBBZN&amp;amp;keywords=raspberry+pi+hq+camera&amp;amp;qid=1700336184&amp;amp;sprefix=raspberry+pi+hq+camera%2Caps%2C68&amp;amp;sr=8-3&amp;amp;ufe=app_do%3Aamzn1.fos.17d9e15d-4e43-4581-b373-0e5c1a776d5d&amp;amp;linkCode=ll1&amp;amp;tag=recommmendsby-20&amp;amp;linkId=e68282119d1937ce32a7e4f4de3952d5&amp;amp;language=en_US&amp;amp;ref_=as_li_ss_tl&quot;&gt;Raspberry Pi HQ&lt;/a&gt; camera with a wide-angle lens, running an open-source program called &lt;a href=&quot;https://github.com/thomasjacquin/allsky&quot;&gt;AllSky&lt;/a&gt; to control and export time-lapse videos. Being fascinated by the intersection of cameras and astronomy, I couldn&#39;t resist taking on this project.&lt;/p&gt;
&lt;p&gt;After researching the software and exploring my options, I discovered that AllSky supported various camera types. However, I leaned towards the Pi HQ camera due to its seamless integration with the Raspberry Pi and the abundance of compatible lenses. Despite the current scarcity of Raspberry Pis, I was fortunate to have a spare Pi 4 on hand. Note that AllSky can also run on a Pi 3B if that&#39;s what you can find. As for Raspberry Pi 5 support, I&#39;m unsure at this point. I promptly ordered the HQ camera and a lens to kickstart the project. Notably, I opted for a super-wide-angle lens, stopping short of a fisheye, as my desired mounting location would render a fisheye lens capturing mostly my house and surrounding trees. The flexibility to choose the right lens is a distinct advantage of the Pi HQ camera.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://johnjonesfour.com/images/allsky.jpg&quot; alt=&quot;allsky kit&quot;&gt;&lt;/p&gt;
&lt;p&gt;Before delving into fabrication, it&#39;s crucial to test the basic hardware and software setup to identify and address potential issues. During this phase, I encountered a challenge as Raspberry Pi updated its OS to Debian Bookworm, which AllSky did not support at that moment. Although I attempted to install the dev branch addressing this issue, it proved unstable. However, the active contributor community reassured me that solutions were in progress. I reverted to the older Pi OS and successfully followed the AllSky setup steps. During installation, it immediately recognized the Pi HQ camera so there were no configuration changes needed to get it working.&lt;/p&gt;
&lt;p&gt;Once AllSky was operational, I navigated the web interface to configure the settings. While the setup for nightly time-lapse videos is mostly turnkey, it&#39;s up to the operator to determine the destination. AllSky supports several upload protocols such as SFTP, FTP, and S3. Since I have a sizable Synology NAS on my local network, I created a dedicated account for AllSky with SFTP access to a new share. Configuring these parameters in AllSky was straightforward, although it required manual editing of a configuration file, a somewhat unconventional choice amidst a predominantly form-based interface. AllSky&#39;s &lt;a href=&quot;https://htmlpreview.github.io/?https://raw.githubusercontent.com/thomasjacquin/allsky/master/html/documentation/troubleshooting/uploads.html&quot;&gt;support offers debug/testing tips&lt;/a&gt; for ensuring correct upload settings.&lt;/p&gt;
&lt;p&gt;Again before continuing on with a build I wanted to make sure everything worked properly, so I let the camera run for a few days in my office. This allowed me to check the time lapse videos and make sure everything like focus was set properly.&lt;/p&gt;
&lt;p&gt;Simultaneously, I entered the housing design phase, inspired by the original PVC pipe housing that the creator of AllSky used. I, however, chose to design a model in Fusion 360 and 3D print it, deviating from the plywood chassis that he used. While there were existing AllSky housing projects &lt;a href=&quot;https://www.printables.com/search/all?q=AllSky&quot;&gt;on platforms like Printables and Thingiverse&lt;/a&gt;, none adopted the PVC pipe approach, which appealed to me.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://johnjonesfour.com/images/allskymodel.gif&quot; alt=&quot;model animation&quot;&gt;&lt;/p&gt;
&lt;p&gt;Creating the models was straightforward, thanks to detailed schematics from Raspberry Pi and widely available PVC pipe dimensions. However I did go through three draft designs while I figured out key decisions such as: Should I include the camera mount with the dome connector or part of the chassis? (I made it part of the chassis.) How should I secure the chassis to the pipe? (Friction fit) Should I include a fan? (I did, mainly to help move air across the dome to prevent fogging.)&lt;/p&gt;
&lt;p&gt;I 3D printed the internal chassis and dome adapter on my Ender S1 Pro and commenced the assembly. The compact chassis posed a challenge during assembly, but with some effort, I made it work. I also secured the security camera dome using Gorilla Glue and stainless steel M3 screws. It&#39;s already held up through one storm so far, so it seems like that was adequate.&lt;/p&gt;
&lt;p&gt;Adopting a feature from the IEEE Spectrum article, I incorporated a watertight bulkhead connector to ensure a secure connection from the power supply to the external unit. In past projects I would&#39;ve just soldered the connection from the cord directly in, but that always left some risk that an errant tug on the line would rip apart the electronics. With the bulkhead connector and the screws holding the bottom plate on, this is a nice tight connection that would be very hard to break.&lt;/p&gt;
&lt;p&gt;With assembly complete it was time for installation. I chose to install my camera on a structure I had previously built for a weather station. (Which I also need to do a feature on at some point.) This location provided a relatively unobstructed view of the sky and an easy access to get the power cord inside to a GFCI outlet. I can&#39;t say my wife was too pleased with more electronics being mounted on our garage but she did eventually admit that the videos it produces are amazing.&lt;/p&gt;
&lt;p&gt;Something to note here too is that I have a pretty robust home WiFi network and there&#39;s an access point just on the other side of a window from where I positioned the camera. Your miles may vary here and route I suggest taking if you don&#39;t think you can get a good WiFi signal in your installation location of choice is to purchase a Raspberry Pi Power over Ethernet hat and then run both the power and network connection to the Pi over that hard line.&lt;/p&gt;
&lt;p&gt;Because I had taken time to setup and test AllSky inside, the system pretty much worked perfectly once I powered it back on, producing stunning nighttime time lapse videos every day and uploaded them to my NAS for long term storage. Two settings I changed were the relative brightness and contrast fields ever so slightly. This was necessary because the camera catches the top of my house which has a light colored siding that reflects the yellow hue of the sodium vapor street lamps. It&#39;s enough to confuse the auto balance in the software and drown out the stars, and so some customization was needed to really bring out the starlight in the images.&lt;/p&gt;
&lt;p&gt;I also enabled daytime time lapse videos too which are just as stunning as their nighttime counterparts, particularly on a partially cloudy day where you can see those clouds whizzing by. My house is also in the landing approach zone for Dulles and a small private airport and so we see come cool shots of planes when they zoom by too.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://johnjonesfour.com/images/allskyt.gif&quot; alt=&quot;allsky timelapse&quot;&gt;&lt;/p&gt;
&lt;p&gt;Overall I&#39;m very pleased with this project. Each morning, I eagerly watch the latest time-lapse video while sipping my coffee, captivated by the hypnotic spectacle of stars spinning with the Earth&#39;s rotation.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Tracking Smoking Temperatures with Grillbot</title>
		<link href="https://johnjonesfour.com/2023/07/16/tracking-smoking-temperatures-with-grillbot/"/>
		<updated>2023-07-17T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2023/07/16/tracking-smoking-temperatures-with-grillbot/</id>
		<content type="html">&lt;p&gt;What’s the best way to overcomplicate grilling? Make a meat thermometer that logs temperatures to your computer. This helps me track and understand how different cuts and types of meat behave throughout the smoke. Smoking and grilling meat is one of my many hobbies and I&#39;m constantly searching for ways to improve. With my longer cooks like brisket, I want to be able to go back and review temperatures over the whole cook for different cuts. Building something like GrillBot allows me to capture that data in an easily digested format for later review.&lt;/p&gt;
&lt;h2 id=&quot;chassis&quot; tabindex=&quot;-1&quot;&gt;Chassis &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2023/07/16/tracking-smoking-temperatures-with-grillbot/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;First I started by modeling a case in Fusion 360, and then I printed on my aging Makerbot Replicator 2. (One of these days I&#39;ll get myself a respectable printer.)&lt;/p&gt;
&lt;p&gt;Starting the assembly I add brass threaded inserts to the chassis that holds the PCB and the case two secure the chassis.&lt;/p&gt;
&lt;h2 id=&quot;electronics&quot; tabindex=&quot;-1&quot;&gt;Electronics &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2023/07/16/tracking-smoking-temperatures-with-grillbot/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The core of this is an ESP32. A simpler microcontroller would absolutely suffice but it’s what I had laying around. All that’s needed is something with at least two analog to digital pins. The thermometers themselves are just thermistors and so I use a voltage divider circuit so that the ADC pins can determine the temperature based on the voltage they register.&lt;/p&gt;
&lt;p&gt;The software is built using PlatformIO and designed to work on an ESP32 platform such as &lt;code&gt;esp32doit-devkit-v1&lt;/code&gt; but any decent microcontroller platform that supports the Arduino toolchain, has two ADC pins, and serial communication should suffice.&lt;/p&gt;
&lt;h2 id=&quot;software&quot; tabindex=&quot;-1&quot;&gt;Software &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2023/07/16/tracking-smoking-temperatures-with-grillbot/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In the main application loop, I take a reading for both thermometers and add the values to a special union structure that easily allows me to convert the double values to a byte array. Then I convert the byte array to a base 64 string and print the value to serial for reading by the host computer.&lt;/p&gt;
&lt;p&gt;To read the temperature there are two key functions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The first called &lt;code&gt;takeReading&lt;/code&gt; accepts the PIN number and then reads and averages a few ping readings.&lt;/li&gt;
&lt;li&gt;The second called &lt;code&gt;calculateTemp&lt;/code&gt; converts the pin reading value into a fareneheight temperature.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;logger&quot; tabindex=&quot;-1&quot;&gt;Logger &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2023/07/16/tracking-smoking-temperatures-with-grillbot/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://johnjonesfour.com/images/grillbotdashboard.png&quot; alt=&quot;Web Dashboard&quot;&gt;&lt;/p&gt;
&lt;p&gt;The logger reads the serial port connected to the bot and displays and logs the temperatue value coming from the bot. All data is stored using Sqlite. There are a few different configuations that can be passed in via command line flags. It is a Go project and also has a minimal web interface written in vanilla HTML/CSS/JS. The web UI exists to monitor temperature and control some basic settings.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>I Built a Bluetooth Low Energy, ESP32-Based Darkroom and Film Development Timer</title>
		<link href="https://johnjonesfour.com/2022/02/05/darkroom-timer/"/>
		<updated>2022-02-06T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2022/02/05/darkroom-timer/</id>
		<content type="html">&lt;p&gt;Developing is just subjecting exposed film or paper to a series of chemical
reactions. It involves &amp;quot;developing&amp;quot; which brings out the exposed image,
&amp;quot;stopping&amp;quot; which halts the chemical reaction of developing to freeze the image,
and &amp;quot;fixing&amp;quot; which removes light sensitivity from the medium so it can be viewed
in normal light. All chemical reactions are sensitive to quantity, temperature,
and time, and different films and developers have different developing time
requirements. Develop for too long or too short a time and it changes the
quality of the image.&lt;/p&gt;
&lt;p&gt;There&#39;s a niche category of darkroom timer apps, but I had two key issues
with them. First, some offer a &amp;quot;darkroom&amp;quot; mode that turns the interface all
red, but that doesn&#39;t stop notifications or accidental swipes from interrupting
and causing exposure mistakes. And second, darkrooms are wet areas because of
the chemicals. That doesn&#39;t play nicely with phones.&lt;/p&gt;
&lt;p&gt;So, I decided to build my own solution!&lt;/p&gt;
&lt;h2 id=&quot;design&quot; tabindex=&quot;-1&quot;&gt;Design &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2022/02/05/darkroom-timer/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://johnjonesfour.com/images/darkroom-timer/prototype.jpg&quot; alt=&quot;Timer prototype&quot;&gt;&lt;/p&gt;
&lt;p&gt;The darkroom timer apps have one key advantage: develop time selection. Because
of all the combinations of film, developer, temperature, etc, there are a
multitude of options to sort through when developing. I therefore chose to not
design any sort of interface for the timer itself - rather I chose to
implement the interface as an app and transmit the time selection to the timer
using Bluetooth Low Energy.&lt;/p&gt;
&lt;p&gt;The timer itself would just need a button to start the timer. After each timer
completes, it would advance to the next timer (given that developing is a
three-step process, a different timer is needed for each step).&lt;/p&gt;
&lt;h2 id=&quot;electronics&quot; tabindex=&quot;-1&quot;&gt;Electronics &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2022/02/05/darkroom-timer/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://johnjonesfour.com/images/darkroom-timer/breadboard.png&quot; alt=&quot;Timer breadboard&quot;&gt;&lt;/p&gt;
&lt;p&gt;For the timer itself, not having an interface meant it could be very simple:
it just needed a four-digit seven-segment display, a button to start/skip the
current timer, and a way to adjust the brightness. For the display, Adafruit
had a perfect &lt;a href=&quot;https://www.adafruit.com/product/1270&quot;&gt;red 1.2inch display&lt;/a&gt; that
interfaces via I2C. To control the display and receive the times I chose the
ESP32-based &lt;a href=&quot;https://www.adafruit.com/product/4335&quot;&gt;TinyPico&lt;/a&gt; because of its
built in BLE module, I2C pins, and analog pins. A momentary pushbutton starts
the timer, and a potentiometer connected to an analog pin sets the display&#39;s
brightness. A buzzer also sounds when each timer completes.&lt;/p&gt;
&lt;h2 id=&quot;software&quot; tabindex=&quot;-1&quot;&gt;Software &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2022/02/05/darkroom-timer/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://johnjonesfour.com/images/darkroom-timer/xcode.png&quot; alt=&quot;Xcode&quot;&gt;&lt;/p&gt;
&lt;p&gt;The data from the app comes from a film community website that has an extensive
database of films and developers. This was my first project using the new(ish)
SwiftUI interface design feature in iOS development, and it made wiring up the
data models to a series of drilldown tables an absolute breeze. It doesn&#39;t seem
to have all the customization options of the long used &lt;code&gt;UITableView&lt;/code&gt; but for
simple needs it is far better. In the app, I also added the ability to define a
custom sequence of timers as well.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://johnjonesfour.com/images/darkroom-timer/message.png&quot; alt=&quot;Data message&quot;&gt;&lt;/p&gt;
&lt;p&gt;To send the selected set of times to the timer in a BLE message, I designed a
custom data format that encodes an array of integers into a byte array. The
first byte of the message contains the &amp;quot;start character,&amp;quot; ASCII code &lt;code&gt;0x02&lt;/code&gt;,
the second byte is  an unsigned integer that&#39;s the number of timers in the
message, and then the rest of the message are an amount of two byte pairs
specified in that length bit. Each pair is an unsigned 16 bit integer of
seconds. The last part of the message is the ASCII code &lt;code&gt;0x03&lt;/code&gt; to signal the
end of the message.&lt;/p&gt;
&lt;h3 id=&quot;send&quot; tabindex=&quot;-1&quot;&gt;Send: &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2022/02/05/darkroom-timer/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;pre class=&quot;language-swift&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;times&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;UInt16&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; message &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    message&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MessageStart&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    
    message&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;UInt8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;times&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;count&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    times&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;forEach &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; time &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; parts &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;UInt8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;UInt8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;time &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0x00FF&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;UInt8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;time &lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
        parts&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;forEach &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; part &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt;
            message&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;part&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    message&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MessageEnd&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; clockDevice &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clockDevice&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; clockCharacteristic &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clockCharacteristic &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        clockDevice&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;message&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; clockCharacteristic&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; type&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CBCharacteristicWriteType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;withResponse&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;receive&quot; tabindex=&quot;-1&quot;&gt;Receive: &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2022/02/05/darkroom-timer/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;pre class=&quot;language-c++&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-c++&quot;&gt;bool readBLE() {
  std::string value = pCharacteristic-&gt;getValue();
  if (value.compare(lastBLEValue) == 0) {
    return false;
  }
  lastBLEValue = value;
  const char* bytes = value.c_str();
  int l = value.length();
  if (bytes[0] != MESSAGE_START) {
    return false;
  }
  int length = uint8_t(bytes[1]);
  if (length &gt;= MAX_LENGTH || length == 0 || bytes[2 + (length * 2)] != MESSAGE_END) {
    return false;
  }
  for (int i = 0; i &lt; length; i++) {
     timerSet[i] = uint16_t(bytes[(i * 2) + 2]) | (uint16_t(bytes[(i * 2) + 3]) &lt;&lt; 8);
     Serial.printf(&quot;Seconds: %d&#92;n&quot;, timerSet[i]);
  }
  currentTimersN = length;
  return true;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/johnjones4/darkroom-timer-II&quot;&gt;View the full code on GitHub&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;enclosure&quot; tabindex=&quot;-1&quot;&gt;Enclosure &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2022/02/05/darkroom-timer/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://johnjonesfour.com/images/darkroom-timer/cad.png&quot; alt=&quot;Enclosure CAD&quot;&gt;&lt;/p&gt;
&lt;p&gt;I modeled the enclosure in Fusion360. The parts are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The enclosure&lt;/li&gt;
&lt;li&gt;A mount for the perfboard&lt;/li&gt;
&lt;li&gt;Spacers to flush the display against the front of the case&lt;/li&gt;
&lt;li&gt;A knob for the potentiometer&lt;/li&gt;
&lt;li&gt;A dome-shaped cover for the button&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;a href=&quot;https://www.thingiverse.com/thing:5235876&quot;&gt;Download the STLs on Thingiverse&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://johnjonesfour.com/images/darkroom-timer/print.gif&quot; alt=&quot;Enclosure print&quot;&gt;&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>How To Write a Go/Lambda Based Twitter Meme Bot</title>
		<link href="https://johnjonesfour.com/2021/10/31/go-serverless-twitter-meme-bot/"/>
		<updated>2021-11-01T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2021/10/31/go-serverless-twitter-meme-bot/</id>
		<content type="html">&lt;p&gt;It&#39;s been a while since I wrote a fun bot, so on Sunday evening I cracked my knuckles, set up a new repo and wrote a bot I&#39;ve been thinking about for a little while: a &amp;quot;pep talk&amp;quot; generator themed after Apple&#39;s hit show &lt;em&gt;Ted Lasso&lt;/em&gt; and a cool &lt;a href=&quot;https://www.reddit.com/r/coolguides/comments/qacund/handy_pep_talk_guide/&quot;&gt;&lt;em&gt;pep talk generator&lt;/em&gt; graphic posted on Reddit&lt;/a&gt;. You can see the bot in-action on Twitter: &lt;a href=&quot;https://twitter.com/LassoPepTalkBot&quot;&gt;@LassoPepTalkBot&lt;/a&gt;. Here&#39;s how my bot works:&lt;/p&gt;
&lt;h2 id=&quot;generate-the-pep-talk&quot; tabindex=&quot;-1&quot;&gt;Generate the Pep Talk &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2021/10/31/go-serverless-twitter-meme-bot/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;First, let&#39;s generate the talk&#39;s text. To do that, I encoded the chart&#39;s text in a 2D array (truncated here), and then wrote a function that goes through the top level array, randomly chooses one element from each array, and adds that to a string.&lt;/p&gt;
&lt;pre class=&quot;language-go&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; parts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token string&quot;&gt;&quot;Champ&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token string&quot;&gt;&quot;Fact:&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token string&quot;&gt;&quot;Everybody says&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token string&quot;&gt;&quot;the mere idea of you&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token string&quot;&gt;&quot;your soul&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token string&quot;&gt;&quot;your hair today&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token string&quot;&gt;&quot;has serious game&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token string&quot;&gt;&quot;rains magic&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token string&quot;&gt;&quot;deserves the Nobel Prize&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token string&quot;&gt;&quot;24/7.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token string&quot;&gt;&quot;can I get an amen?&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token string&quot;&gt;&quot;and that&#39;s a fact.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;generateRandomSentence&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	str &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; strings&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Builder&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; i&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; strs &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;range&lt;/span&gt; parts &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		index &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; rand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Intn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;strs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		str&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;WriteString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;strs&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;index&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			str&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;WriteString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;,&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;parts&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			str&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;WriteString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot; &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; str&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;get-an-image&quot; tabindex=&quot;-1&quot;&gt;Get An Image &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2021/10/31/go-serverless-twitter-meme-bot/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I sourced 17 images of Ted Lasso from the Google Image Search and put them in an S3 bucket. This function picks a random one.&lt;/p&gt;
&lt;pre class=&quot;language-go&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
	nImages &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;17&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getRandomImage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;client &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;s3&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Client&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Image&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	imageFileName &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; fmt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Sprintf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;%d.jpg&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; rand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Int31n&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;nImages&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	input &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;s3&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;GetObjectInput&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		Bucket&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; aws&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Getenv&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;SOURCE_IMAGES_BUCKET&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		Key&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;    aws&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;imageFileName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	resp&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; client&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;GetObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Background&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; input&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; err &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; jpeg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Decode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;resp&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Body&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;build-the-meme&quot; tabindex=&quot;-1&quot;&gt;Build the Meme &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2021/10/31/go-serverless-twitter-meme-bot/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To assemble the actual meme, I used the library &lt;a href=&quot;https://github.com/fogleman/gg&quot;&gt;fogleman/gg&lt;/a&gt;, which has some helpful graphics functions on top of Go&#39;s standard library. This function takes the pep talk sentence and the chosen image, renders the text with a nice little drop shadow on the image, and returns the new image.&lt;/p&gt;
&lt;pre class=&quot;language-go&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  shadowOffset &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;renderSentence&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sentence &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; image image&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Image&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Image&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	ctx &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; gg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;NewContextForImage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	fontSize &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;float64&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Bounds&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Max&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Y&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;21.0&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; err &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;LoadFontFace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Getenv&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;FONT_FACE_PATH&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; fontSize&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; err &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	x &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;float64&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Bounds&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Max&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;X&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2.0&lt;/span&gt;
	y &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;float64&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Bounds&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Max&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Y&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fontSize &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2.0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;SetRGB&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;DrawStringAnchored&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sentence&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; x&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;shadowOffset&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; y&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;shadowOffset&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;SetRGB&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;DrawStringAnchored&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sentence&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; y&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;nil&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;transmit-to-twitter&quot; tabindex=&quot;-1&quot;&gt;Transmit to Twitter &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2021/10/31/go-serverless-twitter-meme-bot/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;With an assembled image, the bot uploads it to Twitter in &lt;code&gt;uploadImage&lt;/code&gt; and then tweets it in &lt;code&gt;tweetMeme&lt;/code&gt;. This uses a generic OAuth1 library combined with standard HTTP requests rather an any sort of Twitter client.&lt;/p&gt;
&lt;pre class=&quot;language-go&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
	twitterConfig &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; oauth1&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;NewConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Getenv&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;TWITTER_CONSUMER_KEY&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Getenv&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;TWITTER_CONSUMER_SECRET&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	twitterToken  &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; oauth1&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;NewToken&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Getenv&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;TWITTER_ACCESS_TOKEN&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Getenv&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;TWITTER_ACCESS_TOKEN_SECRET&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	twitterClient &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; twitterConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Client&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;oauth1&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;NoContext&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; twitterToken&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; tweetMediaResponse &lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	MediaIdString &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;`json:&quot;media_id_string&quot;`&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;uploadImage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;img image&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Image&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; jsonBytes bytes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Buffer
	&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; opts jpeg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Options
	opts&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Quality &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;50&lt;/span&gt;
	err &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; jpeg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Encode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;jsonBytes&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; img&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;opts&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; err &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

	imgB64 &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; base64&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;StdEncoding&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;EncodeToString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;jsonBytes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Bytes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

	&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; v url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Values &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;make&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	v&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;media_category&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;tweet_image&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	v&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;media_data&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; imgB64&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

	req&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; http&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;NewRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;POST&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://upload.twitter.com/1.1/media/upload.json&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; bytes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;NewReader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;v&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Encode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; err &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

	req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Header&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Content-type&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;application/x-www-form-urlencoded&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

	resp&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; twitterClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Do&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;req&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; err &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

	respBody&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; io&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ReadAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;resp&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Body&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; err &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; resp&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;StatusCode &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; fmt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Errorf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;error response: %s&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;respBody&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; respStruct tweetMediaResponse
	err &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; json&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Unmarshal&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;respBody&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;respStruct&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; err &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; respStruct&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;MediaIdString&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;nil&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;tweetMeme&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;text &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; mediaId &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; v url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Values &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;make&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	modifier &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; quoteModifiers&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;rand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Intn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;quoteModifiers&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
	v&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;status&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; fmt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Sprintf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&#92;&quot;%s&#92;&quot;&#92;n - Coach Lasso (%s)&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; text&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; modifier&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	v&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;media_ids&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; mediaId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	req&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; http&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;NewRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;POST&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://api.twitter.com/1.1/statuses/update.json?&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;v&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Encode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; err &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; err
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

	req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Header&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Authorization&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; fmt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Sprintf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Bearer: %s&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Getenv&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;TWITTER_TOKEN&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Header&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Content-type&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;application/json&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

	resp&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; twitterClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Do&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;req&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; err &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; err
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

	respBody&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; io&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ReadAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;resp&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Body&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; err &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; err
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; resp&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;StatusCode &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; fmt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Errorf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;bad response: %s&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;respBody&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

	log&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;respBody&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;nil&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;the-lambda-handler&quot; tabindex=&quot;-1&quot;&gt;The Lambda Handler &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2021/10/31/go-serverless-twitter-meme-bot/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Bringing it altogether, a Lambda handler calls all these functions:&lt;/p&gt;
&lt;pre class=&quot;language-go&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ctx context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Context&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event events&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CloudWatchEvent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	cfg&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;LoadDefaultConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Background&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; err &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token function&quot;&gt;panic&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

	client &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; s3&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;NewFromConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cfg&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

	rand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Seed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;time&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;UnixNano&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	sentence &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;generateRandomSentence&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	image&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getRandomImage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;client&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; err &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token function&quot;&gt;panic&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	meme&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;renderSentence&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sentence&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; image&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; err &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token function&quot;&gt;panic&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	mediaId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;uploadImage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;meme&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; err &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token function&quot;&gt;panic&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	err &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;tweetMeme&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sentence&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; mediaId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; err &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token function&quot;&gt;panic&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	lambda&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;handler&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;deploying&quot; tabindex=&quot;-1&quot;&gt;Deploying &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2021/10/31/go-serverless-twitter-meme-bot/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I deploy this app using the Serverless framework, which is a breathtakingly simple way to deploy to Lambda. The &lt;code&gt;serverless.yml&lt;/code&gt; file for this is straightforward; it provisions a bucket, permissions, and one Lambda that runs every hour.&lt;/p&gt;
&lt;pre class=&quot;language-yml&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-yml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; peptalkbot

&lt;span class=&quot;token key atrule&quot;&gt;provider&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; aws
  &lt;span class=&quot;token key atrule&quot;&gt;runtime&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; go1.x

  &lt;span class=&quot;token key atrule&quot;&gt;iamRoleStatements&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;Effect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Allow&quot;&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;Action&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
       &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;s3:GetObject&quot;&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;Resource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;Fn::Join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;arn:aws:s3:::&quot;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;custom.sourceImagesBucketName&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/*&quot;&lt;/span&gt;

&lt;span class=&quot;token key atrule&quot;&gt;custom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;sourceImagesBucketName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; peptalkbotsourceimages

&lt;span class=&quot;token key atrule&quot;&gt;package&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;individually&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;exclude&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; ./&lt;span class=&quot;token important&quot;&gt;**/*.go&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; ./go.*
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; ./&lt;span class=&quot;token important&quot;&gt;**/*.jpg&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; ./Makefile
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; ./&lt;span class=&quot;token important&quot;&gt;**/*.yml&lt;/span&gt;

&lt;span class=&quot;token key atrule&quot;&gt;functions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;generate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; peptalk
    &lt;span class=&quot;token key atrule&quot;&gt;package&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;include&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; ./peptalk
    &lt;span class=&quot;token key atrule&quot;&gt;events&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;schedule&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; rate(1 hour)
    &lt;span class=&quot;token key atrule&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;SOURCE_IMAGES_BUCKET&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;custom.sourceImagesBucketName&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;TWITTER_ACCESS_TOKEN&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;file(./priv.yml)&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;TWITTER_ACCESS_TOKEN&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;TWITTER_ACCESS_TOKEN_SECRET&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;file(./priv.yml)&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;TWITTER_ACCESS_TOKEN_SECRET&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;TWITTER_CONSUMER_SECRET&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;file(./priv.yml)&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;TWITTER_CONSUMER_SECRET&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;TWITTER_CONSUMER_KEY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;file(./priv.yml)&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;TWITTER_CONSUMER_KEY&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token key atrule&quot;&gt;resources&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;Resources&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;SourceBucket&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; AWS&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;S3&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;Bucket
      &lt;span class=&quot;token key atrule&quot;&gt;Properties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;BucketName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;custom.sourceImagesBucketName&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To learn more about this, check out the repo: &lt;a href=&quot;https://github.com/johnjones4/pep-talk-generator&quot;&gt;johnjones4/pep-talk-generator&lt;/a&gt;&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Raspberry Pi Model Rocket Telemetry Flight 3: 1200 Feet! (With Project Updates)</title>
		<link href="https://johnjonesfour.com/2021/10/11/raspberry-pi-rocket-telemetry-flight-success/"/>
		<updated>2021-10-12T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2021/10/11/raspberry-pi-rocket-telemetry-flight-success/</id>
		<content type="html">&lt;p&gt;I recently joined &lt;a href=&quot;https://novaar.org/drupal7/&quot;&gt;NOVAAR, Northern Virginia Association of Rocketry&lt;/a&gt; so that I could have consistent access to a launch site and so I could learn from the club&#39;s members to grow my own skill. (And potentially pursue high-powered certification.) At the club&#39;s launch on October 9 on the Great Meadow in the Plains, VA, I was able to fly my custom telemetry rocket with much success. I detail that below along with information about what I&#39;ve updated with the &lt;a href=&quot;https://github.com/johnjones4/white-vest&quot;&gt;telemetry project I call &lt;em&gt;White Vest&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This is the third time I&#39;ve taken my custom-designed, Raspberry Pi-based model rocket telemetry system out and it&#39;s the rocket&#39;s sixth flight overall. After mixed, but encouraging results from the first two sessions and a fair amount of refactoring and design refinement, this flight was a huge success. Flying on an Aerotech F44-8W motor, the rocket had an apogee of just over 1200 feet which is a new record for it! At this point, I feel the design of the entire setup is just about as optimal as it can be, save for the occasional tweaks that will cross my mind.&lt;/p&gt;
&lt;h2 id=&quot;flight-info&quot; tabindex=&quot;-1&quot;&gt;Flight Info &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2021/10/11/raspberry-pi-rocket-telemetry-flight-success/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;timeline&quot; tabindex=&quot;-1&quot;&gt;Timeline &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2021/10/11/raspberry-pi-rocket-telemetry-flight-success/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://johnjonesfour.com/images/rocket/oct921_altitude.png&quot; alt=&quot;Altitude plot from the flight&quot;&gt;&lt;/p&gt;
&lt;p&gt;As can be seen in the chart above, the rocket accelerated &lt;em&gt;extremely&lt;/em&gt; quickly and reached it&#39;s apogee after about 7 seconds. To understand more about the flight, I updated my software to monitor the flight&#39;s velocity and acceleration and determine what &amp;quot;mode&amp;quot; the flight is in according to the following rules:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Velocity hits 5 m/s → Launch, Powered Ascent&lt;/li&gt;
&lt;li&gt;Velocity positive but Acceleration drops below 0 → Unpowered Ascent&lt;/li&gt;
&lt;li&gt;Velocity goes negative → Free fall&lt;/li&gt;
&lt;li&gt;Acceleration drops to ~0 → Parachute deployed&lt;/li&gt;
&lt;li&gt;Acceleration and Velocity drop to near zero → Recovery&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Based on these rules, here is the data for this flight:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Phase&lt;/th&gt;
&lt;th&gt;Length (seconds)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Powered Ascent&lt;/td&gt;
&lt;td&gt;1.439633s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unpowered Ascent&lt;/td&gt;
&lt;td&gt;6.747054s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Free fall Descent&lt;/td&gt;
&lt;td&gt;1.723669s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Controlled Descent&lt;/td&gt;
&lt;td&gt;122.346487s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;I have confidence in these numbers because the powered ascent phase roughly matches the motor manufacturer&#39;s data for thrust duration and the sum of the unpowered ascent and free fall descent phases matches the delay time according to the motor manufacturer&#39;s data.&lt;/p&gt;
&lt;h3 id=&quot;travel&quot; tabindex=&quot;-1&quot;&gt;Travel &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2021/10/11/raspberry-pi-rocket-telemetry-flight-success/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://johnjonesfour.com/images/rocket/oct921_map.jpg&quot; alt=&quot;Map from the flight&quot;&gt;&lt;/p&gt;
&lt;p&gt;Note that the descent phase of the flight lasted ... a long time. I used too large of a chute, but I did so because I was concerned about the descent phase overall with the more powerful motor. For the future I&#39;ll use a smaller chute because the rocket ended up landing 410 meters from the launch location. (I know this exactly because the GPS chip actually worked this time.)&lt;/p&gt;
&lt;h2 id=&quot;telemetry-tracking-updates&quot; tabindex=&quot;-1&quot;&gt;Telemetry Tracking Updates &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2021/10/11/raspberry-pi-rocket-telemetry-flight-success/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;inboard-module&quot; tabindex=&quot;-1&quot;&gt;Inboard Module &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2021/10/11/raspberry-pi-rocket-telemetry-flight-success/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I made several updates to the inboard module for this flight based on its performance on the first few flights. Overall, those improvements were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Replaced the old GPS module, which rarely found a signal with &lt;a href=&quot;https://www.adafruit.com/product/746&quot;&gt;one from Adafruit&lt;/a&gt;. It uses the same serial interface, so the software didn&#39;t need to change.&lt;/li&gt;
&lt;li&gt;Switched to I2C for the altimeter, which yielded a faster sampling rate.&lt;/li&gt;
&lt;li&gt;Added a momentary switch to the module. When pressed, it gracefully stops the software and restarts it. This allows one to &amp;quot;end&amp;quot; the capture session safely instead of just pulling the battery.&lt;/li&gt;
&lt;li&gt;A new chassis, shown below.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://johnjonesfour.com/images/rocket/assembled_oct9.jpg&quot; alt=&quot;Assembled telemetry chassis&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://johnjonesfour.com/images/rocket/telemetrymodule_oct9.png&quot; alt=&quot;Fusion360 render of the telemetry chassis&quot;&gt;&lt;/p&gt;
&lt;p&gt;This new chassis better braces the sensors in place, is stronger for hard landings, and it accomodates the new GPS chip and reset button.&lt;/p&gt;
&lt;h3 id=&quot;software&quot; tabindex=&quot;-1&quot;&gt;Software &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2021/10/11/raspberry-pi-rocket-telemetry-flight-success/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In addition to the physical changes, I refactored the software somewhat to speed up the sampling rate after some further experimentation which different threading patterns. Overall I think I&#39;ve optimized it as much as possible despite only getting to about 35 readings per second. (With the camera disabled, it can be much faster.)&lt;/p&gt;
&lt;p&gt;For the ground software, the visualization platform is now entirely written in Go. Users can opt for a web dashboard or a terminal-based one. While the web version looks cooler, I find the terminal-based mode to be easier to read outside and faster for verifying if the tool is capturing. Again, this is only a backup if the inboard module fails to save data or gets destroyed.&lt;/p&gt;
&lt;p&gt;I&#39;ve also written some new tooling to make it easier to ingest the captured data, derive some summary stats from it, and generate charts. (The altitude chart and summaries in this post came from this tool.) To read more about that, check out the &lt;a href=&quot;https://github.com/johnjones4/white-vest&quot;&gt;GitHub project&lt;/a&gt;.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>How I Backup (2021 Edition)</title>
		<link href="https://johnjonesfour.com/2021/07/07/how-i-backup-2021/"/>
		<updated>2021-07-08T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2021/07/07/how-i-backup-2021/</id>
		<content type="html">&lt;p&gt;Experiencing any sort of data loss makes you paranoid about backing up. Between hard drives dying, &lt;code&gt;rm -rf&lt;/code&gt;&#39;ing directories, and closing accounts without exporting, I&#39;ve had my share of data loss events. Because of that, I am always examining my file backup solution and attempting to be as comprehensive and redundant as possible. (Within reason!) In the past I&#39;ve leaned more on homegrown solutions for backing up my cloud accounts in general, but this year I&#39;ve been attempting to use more existing tools along with trying to move most of my mass file storage out of the cloud as a primary access point. Here&#39;s a look at that layout.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://johnjonesfour.com/images/backup.png&quot; alt=&quot;Logical diagram&quot;&gt;&lt;/p&gt;
&lt;p&gt;At my house, I run a small fleet of Raspberry Pi&#39;s (mostly 4&#39;s) to run a number of services. First, I have one Pi attached to a RAID1 array setup to act as a local file server. Devices on my home network (managed by OpenWRT) can access the file server over SMB, AFP, and NFS. That file server holds about 1TB of data and gets backed up every 24 hours to AWS Glacier. I store most project and personal files on this file server rather than any one of my local machines.&lt;/p&gt;
&lt;p&gt;In addition, I have a Raspberry Pi running a docker-compose stack containing a few different applications. It hosts some side-project work, and it backs up data for those services to the file server. (Which then also get backed up to AWS Glacier.) More specific to this topic, this docker-compose stack runs a set of containers that regularly download files from various cloud services and backs up the data to the file server:&lt;/p&gt;
&lt;h2 id=&quot;rclone&quot; tabindex=&quot;-1&quot;&gt;&lt;a href=&quot;https://rclone.org&quot;&gt;Rclone&lt;/a&gt;: &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2021/07/07/how-i-backup-2021/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Think of rclone as rsync for the cloud! I have one container running with rsync installed that syncs-down files from my Google Drive and Dropbox accounts every 24 hours. A cool feature of rclone is that for Google Drive it automatically converts Google formats into their counterpart file formats. (ie Google Docs -&amp;gt; Word, Google Scheets &amp;gt; Excel, etc)&lt;/p&gt;
&lt;h2 id=&quot;github&quot; tabindex=&quot;-1&quot;&gt;&lt;a href=&quot;https://cli.github.com&quot;&gt;GitHub&lt;/a&gt;: &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2021/07/07/how-i-backup-2021/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This is still more of a homegrown solution, but it works. For this service I have a container use the GitHub CLI to grab a list of all repos I own, and then a BASH script iterates through that list and syncs the Git repo. This also runs every 24 hours.&lt;/p&gt;
&lt;h2 id=&quot;offlineimap-proton-bridge&quot; tabindex=&quot;-1&quot;&gt;&lt;a href=&quot;http://www.offlineimap.org&quot;&gt;OfflineIMAP&lt;/a&gt; + &lt;a href=&quot;https://github.com/ProtonMail/proton-bridge&quot;&gt;Proton Bridge&lt;/a&gt; &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2021/07/07/how-i-backup-2021/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Often overlooked, but incredibly important, I backup emails too. For that I use a tool called OfflineIMAP that syncs down any IMAP mailbox to a local Maildir backup. (You can actually use a tool called Mutt to use this local Maildir like an email client.) In addition, I use Proton Bridge running privately in this container to tunnel a connection to ProtonMail&#39;s servers. This also runs every 24 hours.&lt;/p&gt;
&lt;h2 id=&quot;lastpass&quot; tabindex=&quot;-1&quot;&gt;&lt;a href=&quot;https://github.com/lastpass/lastpass-cli&quot;&gt;Lastpass&lt;/a&gt; &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2021/07/07/how-i-backup-2021/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Lastly I back up my Lastpass account to a CSV using their CLI every 24 hours.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;A cool thing to note with this setup is the amount of open source tools it uses by these various cloud providers. While I need to use a third-party solution (Rclone) for Dropbox and Google Drive, all of the other services utilize open source tools maintained by those services, which has made it easy to set all of this up on an ARM-based system like Raspberry Pi.&lt;/p&gt;
&lt;p&gt;Finally, you can reference my docker-compose stack I use (with private info scrubbed) to set up your own backup solution: &lt;a href=&quot;https://github.com/johnjones4/apps.local.johnjonesfour.com&quot;&gt;https://github.com/johnjones4/apps.local.johnjonesfour.com&lt;/a&gt;.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Another Update On My Raspberry Pi Powered Model Rocket Telemetry System</title>
		<link href="https://johnjonesfour.com/2021/06/14/rocket-update/"/>
		<updated>2021-06-15T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2021/06/14/rocket-update/</id>
		<content type="html">&lt;p&gt;&lt;img src=&quot;https://johnjonesfour.com/images/rocket/toughbook.jpeg&quot; alt=&quot;Toughbook&quot;&gt;&lt;/p&gt;
&lt;p&gt;Since &lt;a href=&quot;https://johnjonesfour.com/2020/10/04/model-rocket-telemetry-part-2/&quot;&gt;first blogging about this project last year&lt;/a&gt;, I&#39;ve gone through several
iterative improvements of the platform, and so I&#39;d like to share the latest and
greatest on this project. This is one of those &amp;quot;never really done&amp;quot; side projects
right now, and so long as I have time to devote to model rocketry and this work,
I will continue to make improvements where I see fit, offer help where/when I
can, and accept code and design contributions to the project. (I will post most updates &lt;a href=&quot;https://github.com/johnjones4/white-vest&quot;&gt;here&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;Recall that in April, I &lt;a href=&quot;https://johnjonesfour.com/2021/04/05/raspberry-pi-model-rocket-update/&quot;&gt;published a post&lt;/a&gt; with info from a recent flight. Overall
those flights were spectacular performances of the rocket itself, but the
software came up short. In retrospect I over engineered the improvements to the
platform and lost focus on simplicity, which is not wise when working in a space
so fraught with potential for disaster. From that launch I took away some key
lessons and somewhat went back to basics. Here&#39;s what I did:&lt;/p&gt;
&lt;h2 id=&quot;inboard-software&quot; tabindex=&quot;-1&quot;&gt;Inboard Software: &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2021/06/14/rocket-update/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For that flight, I had refactored the software to devote a thread to monitoring
each sensor. (Pressure/temp, acceleration/magnetic, and GPS) Another thread
would continually loop and atomically access the latest data from the other
threads to write to a log. Yet another thread would also do the same check but
focus just on transmitting the reading over the LoRa transceiver. Finally one
more thread would operate the video capture. This really didn&#39;t work.&lt;/p&gt;
&lt;p&gt;The core problem was that the sensors report data out at vastly different
speeds. The pressure sensor is slow, giving back data just a few times a second.
The accelerometer/magnetometer is relatively fast and can give back readings
nearly hundreds of times per second. Therefore in the log file it looks as if
the pressure never changes while the attitude of the rocket seems to be all over
the place. Analyzing that data is not fun nor does the added resolution of many
accelerometer readings really improve anything. Also the GPS receiver never
captured a signal.&lt;/p&gt;
&lt;p&gt;Another big problem I did not fix from the first flight was the disk write lag.
A few videos and some sensor data continued to &amp;quot;get lost&amp;quot; because I&#39;d hard
reboot the Pi before the disk had time to complete its writes. Hard reboots are
also not terribly healthy for Raspberry Pis either.&lt;/p&gt;
&lt;h2 id=&quot;ground-tracking&quot; tabindex=&quot;-1&quot;&gt;Ground Tracking: &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2021/06/14/rocket-update/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The ground station did not do better. In fact it didn&#39;t work. I had rigged up
the LoRa receiver to another Raspberry Pi Zero. The software on there was to do
a simple job of reading from the LoRa and then pushing that data back out via a
websocket. I had configured the Pi to treat the USB port as an ethernet
link-local connection so that when I connected it to a laptop, it would assign
the laptop an IP making two way communication between the devices simple. I
should&#39;ve anticipated this, but the Debian-powered Toughbook I picked up for
this work only connected to the device 50% of the time - mainly just because
Linux is finicky with these kinds of networking hacks. The day of the launch I
wasn&#39;t lucky and it just couldn&#39;t connect.&lt;/p&gt;
&lt;p&gt;The more complicated the plumbing ...&lt;/p&gt;
&lt;h2 id=&quot;updates&quot; tabindex=&quot;-1&quot;&gt;Updates: &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2021/06/14/rocket-update/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;With a new focus on simplicity and resiliency, I made some core updates to the
project to account for these naive design decisions on my part. While I do not
know when I&#39;ll be able to run a full field test of these changes, I think they
do a great bit in terms of making the project more stable. Here&#39;s the summary:&lt;/p&gt;
&lt;h3 id=&quot;inboard-software-1&quot; tabindex=&quot;-1&quot;&gt;Inboard Software &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2021/06/14/rocket-update/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;First off, I went back to a limited thread system. There&#39;s now one thread that
reads the pressure/temperature sensor and the accelerometer/magnetometer and
puts that data into a queue. Another thread reads the GPS signal. (This is a
really slow sensor so it stays in its own lane.) There are still dedicated
sensors for log writing, telemetry transmission, and video capture.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/johnjones4/white-vest/master/doc/wiring.png&quot; alt=&quot;Circuit diagram&quot;&gt;&lt;/p&gt;
&lt;p&gt;However, the big change here is solving for the disk write/reboot problem. I&#39;ve
added to the design a simple pushbutton that adds minimal weight but now gives
me the ability to &#39;stop&#39; the current session and start a new one. All of the
threads in the system loop so long as a shared flag is set to true. Once the
software detects the button push, it sets that flag to false and all threads
wrap up their work on their own time and come to a close. Additionally, the
video capture and log writing threads will naturally close after 10 minutes to
prevent the disk from getting too full. This solution now allows safe stopping
of the current session and clean writes to the disk so long as power is not
interrupted in flight.&lt;/p&gt;
&lt;h3 id=&quot;inboard-hardware-chassis&quot; tabindex=&quot;-1&quot;&gt;Inboard Hardware Chassis: &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2021/06/14/rocket-update/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://johnjonesfour.com/images/rocket/chassis.png&quot; alt=&quot;Rocket chassis&quot;&gt;&lt;/p&gt;
&lt;p&gt;When I first started this project, I had little experience using CAD/modeling
tools to design parts for 3D printing. Largely out of ignorance I started with
FreeCAD, which is an OK-at-best tool. Throughout the span of COVID, I&#39;ve had
more time to educate myself with modeling and I&#39;ve since leveled-up to using
Autodesk&#39;s Fusion360 - a far more powerful (and stable!) tool, despite it having
some limits imposed by Autodesk to limit commercial use that actually get in the
way of hobbyist use.&lt;/p&gt;
&lt;p&gt;With these improved CAD skills, I&#39;ve designed a better chassis for the hardware
that improves on the following areas:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Stronger coupler section&lt;/li&gt;
&lt;li&gt;Better connection between coupler and chassis&lt;/li&gt;
&lt;li&gt;Stronger frame above/below Raspberry Pi&lt;/li&gt;
&lt;li&gt;Better centering inside rocket
fuselage&lt;/li&gt;
&lt;li&gt;More stable attachment of sensors, particularly the GPS antenna&lt;/li&gt;
&lt;li&gt;Mount for the restart button&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;ground-tracking-1&quot; tabindex=&quot;-1&quot;&gt;Ground Tracking &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2021/06/14/rocket-update/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/johnjones4/white-vest/master/doc/telemetry.gif&quot; alt=&quot;New dashboard&quot;&gt;&lt;/p&gt;
&lt;p&gt;I also went way simpler here. Before I&#39;d rely on a network level
communication protocol between the receiver hardware and the laptop, and the
dashboard software was - while beautiful - a pretty complex React/Electron
application. Instead, I&#39;ve now opted to use an Arduino Uno connected to the LoRa
as the ground controller, and it sends signal data to the laptop using
tried-and-true serial.&lt;/p&gt;
&lt;p&gt;The ground station visualization software is also much simpler. Instead of the
full blow React/Electron solution, I opted to write a quick tracking monitor in
Go (which I&#39;ve also been forcing myself to use more as part of a professional
development goal) and render a terminal-based dashboard. Before doing this, I
also spent a little time actually evaluating &lt;a href=&quot;https://nasa.github.io/openmct/&quot;&gt;NASA&#39;s open source telemetry visualization software&lt;/a&gt;, but I felt that went against my newly-found principle
that simplicity is paramount.&lt;/p&gt;
&lt;p&gt;While a terminal-based dashboard offers less data resolution than a
browser-based one and less general opportunities for visualization mediums, I
feel this is the right choice because the key concern I have at these launches
is: &#39;are we capturing good data?&#39; A terminal-based dashboard can answer that
question for me quickly without having to worry about the stability of the
link-level connection, or if the the application is hogging too much battery in
the field. It just works, and I can do the more fun visualizations later.&lt;/p&gt;
&lt;h2 id=&quot;next-steps&quot; tabindex=&quot;-1&quot;&gt;Next Steps &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2021/06/14/rocket-update/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As I said, I&#39;m not sure when I&#39;ll get out to test this any time soon, but for
now with my ground testing, this seems to be working very well. The only thing
that doesn&#39;t seem to be working consistently is the GPS capture. The &lt;a href=&quot;https://www.amazon.com/gp/product/B084MK8BS2/ref=ppx_yo_dt_b_asin_title_o00_s00?ie=UTF8&amp;amp;psc=1&quot;&gt;receiver is a passive antenna&lt;/a&gt;, which I understand is slower, but sometimes I still get no
data at all. I&#39;ve read a bit that the type of receiver I&#39;m using doesn&#39;t perform
well on battery, which is an obvious requirement here. Therefore I&#39;m going to
spend some time looking for a drop-in replacement that might work better on a
battery.&lt;/p&gt;
&lt;p&gt;If you have any tips on my GPS module search, or if you&#39;d like help implementing
this project for yourself, please get in touch using my &lt;a href=&quot;https://johnjonesfour.com/contact&quot;&gt;contact form&lt;/a&gt; or via the
&lt;a href=&quot;https://github.com/johnjones4/white-vest&quot;&gt;GitHub project&lt;/a&gt;!&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>An Update On My Raspberry Pi Powered Model Rocket</title>
		<link href="https://johnjonesfour.com/2021/04/04/raspberry-pi-model-rocket-update/"/>
		<updated>2021-04-05T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2021/04/04/raspberry-pi-model-rocket-update/</id>
		<content type="html">&lt;p&gt;When I asked the woman behind the counter in the hobby shop for a
pack of Estes E12-6 motors, she responded with:&lt;/p&gt;
&lt;p&gt;&amp;quot;Oh you must be one of those serious model rocket guys?!&amp;quot;&lt;/p&gt;
&lt;p&gt;Yeah, lady. I am serious.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://johnjonesfour.com/images/apollo13.gif#small&quot; alt=&quot;Ed Harris in Apollo 13&quot;&gt;&lt;/p&gt;
&lt;p&gt;This past weekend, on a long-overdue trip home to PA to visit my family,
we planned a short excursion to some wide-open space we have free access
to for an afternoon of rocketry. &lt;a href=&quot;https://johnjonesfour.com/2020/10/04/model-rocket-telemetry-part-2/&quot;&gt;Back in September, she had her maiden flight&lt;/a&gt;
with one successful launch, and one not-so-successful launch when the
parachute failed, and now it was time for more.&lt;/p&gt;
&lt;p&gt;With the past few months of COVID-19 essentially locking me at home,
making some updates to this project was one of the many, many, many
projects I took on. Here&#39;s a summary of what&#39;s new:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Refactored air software that separates each sensor reading out into
its own thread. This allows for a slightly better sample rate than
my single-threaded approach.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added a GPS module for air and ground that reports readings over
serial&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ground no longer logs data to the Raspberry Pi, it just buffers
readings in-memory and emits them via a websocket.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Refactored dashboard software to a React-powered Electron app.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Refactored altitude calculation to use the first few readings of the
pressure sensor as the &amp;quot;floor.&amp;quot;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enabled Ethernet link-local support in the ground Raspberry Pi so
just connecting it to a host machine over USB powers it and provides
a network interface for communication.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It was a cold but sunny day in PA when we launched. Spring hadn&#39;t quite
reached the Appalachian Mountains, but it was good enough for flight. So
we set up the base station and prepped the rocket for launch. OpenRocket
estimated we&#39;d hit at least 500 feet with these E12-6 motors, and
luckily in this area there&#39;s far less concern around amateur flight than
in DC where I live.&lt;/p&gt;
&lt;p&gt;If you listen carefully to the audio in the beginning of the clip, you
can hear my young nieces shrieking with excitement as they watch the
rocket ascend into the sky. Despite having three great flights topping
600 feet, the data capture was not great. On the first flight, the new
software logged more data than last time, but there were a few key
issues that I will need to work on for future launches:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;My Toughbook was unable to connect to the ground Raspberry Pi
despite what I thought was extensive testing. I could&#39;ve spent more
time debugging before the flight, but it was cold and my family was
getting impatient. Therefore I relied on chance that I was getting
good data.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I was not getting good data. The first flight was successful with
proper data getting logged inboard, but on subsequent flights, it
appears the data may have been truncated or corrupted. The logs from
those launches show &amp;quot;flat&amp;quot; data that doesn&#39;t change, indicating it
never captured the &amp;quot;launch event.&amp;quot;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;On the first flight, the GPS module never acquired a signal.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The GPS antenna broke on the impact on the first flight because the
nose it the ground too hard, thus rendering that component useless.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The first two videos turned out excellent, but the third video was
totally black. It seems the Raspberry Pi did not have time to save
the video capture data.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The coupler section between the two rocket sections is still getting
shoved up into the payload section when it hits the ground too hard.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;However, the good news is that the rocket continues to be stable with
higher-powered motors. (The first flight used D&#39;s rather than E&#39;s here.)
My plan is to make some additional improvements to the software and
structure while still chasing down more powerful motors such as the
AeroTech F44W. The improvements will include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Use simpler hardware for the ground station that can just emit data
over a USB-serial connection. (ie. Arduino) This will require
updating the dashboard software to collect data over serial rather
than websockets.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Redesign the payload chassis to be even stronger without adding too
much weight.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add a small pushbutton to the air hardware that forces the software
to stop recording, perform any file saving cleanup, and reboot the
software. Right now I just pull the battery cable after flights and
I think that is causing the file corruption.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Potentially find a GPS module that locks onto a signal faster.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content>
	</entry>
	
	<entry>
		<title>Capturing Model Rocket Telemetry Using a Raspberry Pi Zero (Part 2)</title>
		<link href="https://johnjonesfour.com/2020/10/03/model-rocket-telemetry-part-2/"/>
		<updated>2020-10-04T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2020/10/03/model-rocket-telemetry-part-2/</id>
		<content type="html">&lt;p&gt;&lt;a href=&quot;https://news.ycombinator.com/item?id=24679435&quot;&gt;Q&amp;amp;A on Hacker News about this project&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;As we&#39;re driving out to our launch site in Leesburg, VA, my wife turns to me and asks, &amp;quot;did you intentionally wear a yellow and blue striped rugby shirt to match your yellow and blue striped rocket?&amp;quot; I look down, and realize that, yes I had indeed accidentally dressed exactly like my rocket. I wonder if that ever happens to Elon?&lt;/p&gt;
&lt;h2 id=&quot;launches&quot; tabindex=&quot;-1&quot;&gt;Launches &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2020/10/03/model-rocket-telemetry-part-2/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://johnjonesfour.com/images/rocket/launch.jpg&quot; alt=&quot;Launch images&quot;&gt;&lt;/p&gt;
&lt;p&gt;Very special thank you to my exceptionally patient wife who tagged along, captured these great pictures, and ended up actually having a lot of fun with her very nerdy husband.&lt;/p&gt;
&lt;p&gt;Ida Lee Park in Leesburg, VA allows rocket launches up to 400 feet with a permit, so I applied earlier in September for an October 3rd launch and received fast approval. With the day upon us, my wife and I made the 40 minute drive to Leesburg (apparently in uniform?), set up in our designated part of the park, and got to work.&lt;/p&gt;
&lt;h3 id=&quot;launch-1&quot; tabindex=&quot;-1&quot;&gt;Launch 1 &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2020/10/03/model-rocket-telemetry-part-2/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://johnjonesfour.com/images/rocket/telemetry.gif&quot; alt=&quot;Telemetry&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://johnjonesfour.com/images/rocket/launch1.png&quot; alt=&quot;Launch 1&quot;&gt;&lt;/p&gt;
&lt;p&gt;The first launch was a huge success. The rocket reached 110.3 meters (~362 feet), which beat my simulation&#39;s prediction of 90 meters, and it reached a peak velocity of 41.69 meters per second (~93 mph) at 1.44 seconds into the flight. We can see the rocket started to descend very quickly after apogee, and the chute deployed successfully 7.5 seconds into the flight. From there, the chute allowed for a linear rate of descent to a gentle field landing.&lt;/p&gt;
&lt;h3 id=&quot;launch-2&quot; tabindex=&quot;-1&quot;&gt;Launch 2 &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2020/10/03/model-rocket-telemetry-part-2/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://johnjonesfour.com/images/rocket/launch2.png&quot; alt=&quot;Launch 2&quot;&gt;&lt;/p&gt;
&lt;p&gt;Launch 2 was less of a success. While the ascent went smoothly, telemetry was lost 4.57 seconds into flight and the ejection charge ripped the shock cord causing the two rocket components to separate with no parachute to slow their descent. This incident also knocked out the battery cable causing inboard video and data capture to also fail. Unfortunately no video data could be recovered.&lt;/p&gt;
&lt;h2 id=&quot;next-steps&quot; tabindex=&quot;-1&quot;&gt;Next Steps &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2020/10/03/model-rocket-telemetry-part-2/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://johnjonesfour.com/images/rocket/recovery.jpg&quot; alt=&quot;Rocket after launch&quot;&gt;&lt;/p&gt;
&lt;p&gt;The second launch revealed three major issues that need to be resolved:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The shock cord is not strong enough to absorb the force caused by separation, likely because of how heavy the payload is. In a future upgrade I will need to double-up the cords OR use two parachutes for each component to let them descend separately.&lt;/li&gt;
&lt;li&gt;When the payload section hit the ground, it drove the coupler way into the body tube. Upon impact, the battery bracket absorbed the force and shattered. However all of the electronics survived. Part of the coupler also broke off when I attempted to pull the it out of the payload section, so this will all need to be redesigned to better absorb hard landings.&lt;/li&gt;
&lt;li&gt;There must be a buffer that holds a certain amount of camera footage before saving it to disk. Because the battery disconnected in-flight, no flight video saved. I&#39;d like to figure out how to write video data more frequently in case the battery issue happens again.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In addition to those major issues, I&#39;d also like to make the following improvements:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The software performed well, but I&#39;d still like to increase the data capture rate. There are Adafruit libraries for the components I&#39;m using in C++, so I&#39;m considering rewriting &lt;code&gt;air.py&lt;/code&gt; in C++, but I&#39;d really love it if someone talked me out of that.&lt;/li&gt;
&lt;li&gt;Between launches I manually rebooted the ground computer so that it would clear the visualizations and start logging to a new file. To make this easier, I plan to add the option to the dashboard to start new &amp;quot;capture sessions&amp;quot; without needing to restart the system.&lt;/li&gt;
&lt;/ol&gt;
</content>
	</entry>
	
	<entry>
		<title>Capturing Model Rocket Telemetry Using a Raspberry Pi Zero (Part 1)</title>
		<link href="https://johnjonesfour.com/2020/10/02/model-rocket-telemetry-part-1/"/>
		<updated>2020-10-03T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2020/10/02/model-rocket-telemetry-part-1/</id>
		<content type="html">&lt;p&gt;&lt;a href=&quot;https://news.ycombinator.com/item?id=24679435&quot;&gt;Q&amp;amp;A on Hacker News about this project&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://johnjonesfour.com/images/rocket/rocket_header.jpg&quot; alt=&quot;The Rocket!&quot;&gt;&lt;/p&gt;
&lt;p&gt;It seems like everyone&#39;s getting back into old hobbies in the time of COVID-19. For me, that&#39;s come as model rocketry, something I spent quite a bit of time doing growing up in the Appalachian Mountains of Pennsylvania. On a whim, I bought a model rocket a few weeks back, built it, and launched it while visiting home in PA. Armed with excitement from this rediscovered hobby – along with the admiration of two young nieces who were sure the rocket was going to the moon – I decided it was time to kick things up a notch and custom build a rocket and telemetry module.&lt;/p&gt;
&lt;h2 id=&quot;hardware-selection&quot; tabindex=&quot;-1&quot;&gt;Hardware Selection &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2020/10/02/model-rocket-telemetry-part-1/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Basing this entire project around a &lt;a href=&quot;https://www.adafruit.com/product/3400&quot;&gt;Raspberry Pi Zero&lt;/a&gt; was an obvious choice to me because of its light weight and excellent ecosystem of easily-programmed sensors available. From there, it was a matter of selecting the types of sensors and other hardware I&#39;d need to build into this project. I came up with the following:&lt;/p&gt;
&lt;h3 id=&quot;power&quot; tabindex=&quot;-1&quot;&gt;Power &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2020/10/02/model-rocket-telemetry-part-1/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.adafruit.com/product/1781&quot;&gt;Lithium Ion Cylindrical Battery - 3.7v 2200mAh&lt;/a&gt; - A fairly light battery that outputs plenty of power. I also chose it because, being cylindrical, it seemed easy to secure it centered in the rocket body.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.adafruit.com/product/1903&quot;&gt;PowerBoost 500 Basic - 5V USB Boost @ 500mA from 1.8V+&lt;/a&gt; - This boosts the output of the battery to the 5V needed by the Pi.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;sensors&quot; tabindex=&quot;-1&quot;&gt;Sensors &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2020/10/02/model-rocket-telemetry-part-1/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.adafruit.com/product/3966&quot;&gt;Adafruit BMP388 - Precision Barometric Pressure and Altimeter&lt;/a&gt; - The purpose of this, primarily, is to capture altitude, but it also records temperature and pressure. (Altitude is derived from pressure, in this case.)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.adafruit.com/product/1120&quot;&gt;Triple-axis Accelerometer+Magnetometer (Compass) Board - LSM303&lt;/a&gt; - This determines the attitude of the rocket.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.adafruit.com/product/3508&quot;&gt;Zero Spy Camera for Raspberry Pi Zero&lt;/a&gt; - Pointed out the side of the rocket, this can grab some awesome images.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;telemetry-transmission-reception&quot; tabindex=&quot;-1&quot;&gt;Telemetry Transmission/Reception &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2020/10/02/model-rocket-telemetry-part-1/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.adafruit.com/product/3072&quot;&gt;Adafruit RFM95W LoRa Radio Transceiver Breakout - 868 or 915 MHz - RadioFruit&lt;/a&gt; - This is a very cool module that transmits byte array packets at 868 or 915 MHz. (At first, I tried a cheaper 433 MHz module from another vendor and it did not work &lt;em&gt;at all&lt;/em&gt;.) I bought two of these so I could use one to transmit and one to receive.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With all of these components chosen, I wired them up to a Raspberry Pi Zero. (First via breadboard for testing then soldered for final assembly.) For more on the wiring, see the &lt;a href=&quot;https://johnjonesfour.com/2020/10/02/model-rocket-telemetry-part-1/&quot;&gt;appendix&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;wiring&quot; tabindex=&quot;-1&quot;&gt;Wiring &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2020/10/02/model-rocket-telemetry-part-1/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The air hardware is wired to the following Raspberry Pi pins:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PowerBoost 500 Basic:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;5v -&amp;gt; Pi 5V&lt;/li&gt;
&lt;li&gt;Ground -&amp;gt; Pi Ground&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;BMP388 (SPI 0)&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;VIN -&amp;gt; Pi 3V&lt;/li&gt;
&lt;li&gt;GND -&amp;gt; Pi GND&lt;/li&gt;
&lt;li&gt;SCK -&amp;gt; Pi SCLK&lt;/li&gt;
&lt;li&gt;SDI -&amp;gt; Pi MOSI&lt;/li&gt;
&lt;li&gt;SDO -&amp;gt; Pi MISO&lt;/li&gt;
&lt;li&gt;CS -&amp;gt; D5&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LSM303 (I2C)&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;VIN -&amp;gt; Pi 3V&lt;/li&gt;
&lt;li&gt;GND -&amp;gt; Pi GND&lt;/li&gt;
&lt;li&gt;SCL -&amp;gt; Pi SCL&lt;/li&gt;
&lt;li&gt;SDA -&amp;gt; Pi SDA&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Camera&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Pi Camera ribbon connection&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RFM95W (SPI 1)&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;VIN -&amp;gt; Pi 3V&lt;/li&gt;
&lt;li&gt;GND -&amp;gt; Pi GND&lt;/li&gt;
&lt;li&gt;SCK -&amp;gt; Pi SPI1 SCLK&lt;/li&gt;
&lt;li&gt;SDI -&amp;gt; Pi SPI1 MOSI&lt;/li&gt;
&lt;li&gt;SDO -&amp;gt; Pi SPI1 MISO&lt;/li&gt;
&lt;li&gt;CS -&amp;gt; Pi D24&lt;/li&gt;
&lt;li&gt;RST -&amp;gt; CE0&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The ground hardware, much more simply, is wired to the following Raspberry Pi pins:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;RFM95W (SPI 1)&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;VIN -&amp;gt; Pi 3V&lt;/li&gt;
&lt;li&gt;GND -&amp;gt; Pi GND&lt;/li&gt;
&lt;li&gt;SCK -&amp;gt; Pi SCLK&lt;/li&gt;
&lt;li&gt;SDI -&amp;gt; Pi MOSI&lt;/li&gt;
&lt;li&gt;SDO -&amp;gt; Pi MISO&lt;/li&gt;
&lt;li&gt;CS -&amp;gt; Pi CE1&lt;/li&gt;
&lt;li&gt;RST -&amp;gt; Pi D25&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;software-development&quot; tabindex=&quot;-1&quot;&gt;Software Development &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2020/10/02/model-rocket-telemetry-part-1/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Before building the rocket and housings for the hardware, I needed to test the electronics and architect a software solution that would optimize for a high data sample rate. With that goal in mind, I devised the following:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://johnjonesfour.com/images/rocket/software.png&quot; alt=&quot;Software Design Graphic&quot;&gt;&lt;/p&gt;
&lt;p&gt;To see the source, check out the &lt;a href=&quot;https://github.com/johnjones4/white-vest&quot;&gt;GitHub Project&lt;/a&gt;. I&#39;ve named the software &lt;em&gt;White Vest&lt;/em&gt; after the iconic &lt;a href=&quot;https://airandspace.si.edu/stories/editorial/gene-kranz%E2%80%99s-apollo-13-vest&quot;&gt;white vest that flight director Gene Kranz wore during Apollo 13&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;air&quot; tabindex=&quot;-1&quot;&gt;Air &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2020/10/02/model-rocket-telemetry-part-1/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In the sensor reading thread, I access two devices, the BMP388 and LSM303. Those values for pressure, temperature, acceleration, and magnetic orientation get added to a thread-safe queue and a thread-safe single value holder. This thread is able to capture about 4 sets of sensor readings per second. (Unfortunately the BMP388 reads out data quite slowly.)&lt;/p&gt;
&lt;p&gt;A separate thread reads the thread-safe queue and logs that data to a CSV file on the device. Further, an additional thread reads that thread-safe single value and transmits that data via the RFM95W as floats packed into a byte array. (I only transmit the most-recently-read data rather than everything from the queue because I want the ground receiver to have near-realtime data.)&lt;/p&gt;
&lt;p&gt;Finally a third thread records video from the camera. All data recording terminates 30 minutes after program start, but it will continue to transmit telemetry.&lt;/p&gt;
&lt;h3 id=&quot;ground&quot; tabindex=&quot;-1&quot;&gt;Ground &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2020/10/02/model-rocket-telemetry-part-1/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;On the ground, the RFM95W receives those transmitted byte arrays,unpacks them to floats, and puts the data into a thread-safe queue. Another thread reads from that thread-safe queue, logs that data to a CSV file, and appends the data to a thread-safe data buffer.&lt;/p&gt;
&lt;p&gt;A third thread acts as a webserver to serve the dashboard web app, and a final thread listens for websocket connections to stream the contents of the data buffer as data becomes available.&lt;/p&gt;
&lt;h2 id=&quot;hardware-assembly&quot; tabindex=&quot;-1&quot;&gt;Hardware Assembly &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2020/10/02/model-rocket-telemetry-part-1/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;With the electronics tested and operational, I designed a set of 3D printable components to mount the telemetry module in a rocket. Based on the &lt;a href=&quot;https://estesrockets.com/product/001980-designers-special/&quot;&gt;Estes builders&#39; kit&lt;/a&gt; I was using, I knew 4cm across was the widest body I could support. Therefore I designed a mounting system to support a tube of that width.&lt;/p&gt;
&lt;p&gt;After a few iterations, I ultimately designed the following parts for printing:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://johnjonesfour.com/images/rocket/parts.jpg&quot; alt=&quot;Designed Hardware&quot;&gt;&lt;/p&gt;
&lt;p&gt;Those pieces, when assembled with the electronics, look like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://johnjonesfour.com/images/rocket/telemetery_assembled.jpg&quot; alt=&quot;Assembled Hardware&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;rocket-design&quot; tabindex=&quot;-1&quot;&gt;Rocket Design &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2020/10/02/model-rocket-telemetry-part-1/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The rocket has two main components: &lt;strong&gt;The booster section&lt;/strong&gt;, which is the bottom half of the rocket, and contains the motor, fins, and recovery parachute. That section is joined via a coupler (one of the 3D-printed parts) to the upper-half of the rocket, which is the &lt;strong&gt;payload section&lt;/strong&gt; containing the telemetry module.&lt;/p&gt;
&lt;p&gt;After the main launch motor burns out, the engine fires an ejection charge up into the rocket. That force pushes the parachute up and blows out the coupler, forcing the two sections to separate and the parachute to deploy. (Both sections are attached to the parachute via shock cord.)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://johnjonesfour.com/images/rocket/rocket.png&quot; alt=&quot;Rocket Design Graphic&quot;&gt;&lt;/p&gt;
&lt;p&gt;The graphic above is from my design in &lt;a href=&quot;https://github.com/openrocket/openrocket&quot;&gt;OpenRocket&lt;/a&gt;, which is an open source tool for designing and testing model rockets. To learn more about the principles of model rocket design, I suggest the classic book, &lt;a href=&quot;https://www.goodreads.com/book/show/2052922.Handbook_of_Model_Rocketry&quot;&gt;&lt;em&gt;Handbook of Model Rocketry&lt;/em&gt;&lt;/a&gt;, which is considered a go-to resource for the hobby.&lt;/p&gt;
&lt;p&gt;Based on this design, using an &lt;a href=&quot;https://estesrockets.com/product/001567-d12-5-engines/&quot;&gt;Estes D12-5&lt;/a&gt; motor, I could expect this rocket to top out at around 90 meters, or about 295 feet. There are more powerful motors available, but those require more sophisticated launch controllers and this was just first test.&lt;/p&gt;
&lt;p&gt;Once assembled, the rocket looked like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://johnjonesfour.com/images/rocket/rocket.jpg&quot; alt=&quot;Assembled Rocket Graphic&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;To see the results of launching this, check out &lt;a href=&quot;https://johnjonesfour.com/2020/10/04/model-rocket-telemetry-part-2/&quot;&gt;Part 2&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>AWS Honey Code is Visual Basic for a New Generation, and that&#39;s a Good Thing</title>
		<link href="https://johnjonesfour.com/2020/07/25/aws-honey-code-is-visual-basic/"/>
		<updated>2020-07-26T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2020/07/25/aws-honey-code-is-visual-basic/</id>
		<content type="html">&lt;p&gt;&lt;img src=&quot;https://johnjonesfour.com/images/vb.jpg&quot; alt=&quot;Visual Basic 4&quot;&gt;&lt;/p&gt;
&lt;p&gt;My first job as a software developer, as it is for many, was as an
intern. It was a B2B data transformation software shop that still
supported old IBM mainframes and also offered a newer Java Enterprise
Edition version of the platform with software teams supporting both. As
an intern, I reported the &amp;quot;Director of Special Projects,&amp;quot; a guy who did
exactly what that sounds like: whatever needed to be done that didn&#39;t
fall precisely into any other departments&#39; purviews. He was a good boss
and always found interesting projects for me to tackle, but, more
importantly for this story, he was a Visual Basic wizard. Sales needed a
custom reporting system? VB can do that. HR needed an internal tool to
track and categorize contractor hours? VB can do that. Professional
Services needed a new cost estimator? Yep. VB. While this attracted
endless, but good-natured ribbing from the engineering team, his little
VB projects filled in the gaps quickly and did a just-good-enough job to
keep the company&#39;s gears moving at little cost. As Amazon rolls out its
new Honey Code platform, I see a lot of hand wringing from the developer
community, but we should welcome this tool. Just as VB did, it empowers
enterprise users to build quick solutions to very specific problems and
keeps engineering teams focused on shipping products.&lt;/p&gt;
&lt;p&gt;There&#39;s a touch of elitism that crops up when new tools like Honey Code
come along, and I&#39;ve absolutely launched criticisms at such platforms in
the past. We often see new tools like this targeted at non-technical
founders as a tool for getting their MVP out quickly and without that
burdensome expense of engineering talent. They fail to mention that all
that work will be lost when you eventually have to hire some engineers
to code up v2. These tools rarely properly offer customization and
code-addons that allow for extensibility pass what the core service can
provide. (And who&#39;d handle building that extension when there&#39;re no
engineers!?) These tools are just too limited and ill suited to be
seriously considerable as a tool to build a startup&#39;s MVP.&lt;/p&gt;
&lt;p&gt;But Amazon has taken a different approach with Honey Code. They&#39;re not
pitching snake oil to founders here; that&#39;s something Amazon needs to
concern itself with. Instead Amazon is positioning it as a key tool for
mission critical processes that would otherwise fall back on unreliable
spreadsheets. That&#39;s what my old Director of Special Projects showed me
with his VB prowess. These aren&#39;t products shipping out to customers;
their &amp;quot;good enough&amp;quot; interfaces get the job done.&lt;/p&gt;
&lt;p&gt;Why not just have a dev team tackle these problems? Wouldn&#39;t that make
the resulting application more manageable and scalable? Maybe. Some
enterprise problems are extremely critical in the moment but don&#39;t exist
long enough to rationalize a dev team taking the time to produce a
platform. That&#39;s why the solution is often just a simple spreadsheet; it
gets the job done quickly. Dedicating valuable and scarce developer time
to something just doesn&#39;t make sense, but what does make sense is
training up a few tech-savvy team members on Honey Code. It solves
problems. It saves time.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Using Webpack to Build Browser Extensions</title>
		<link href="https://johnjonesfour.com/2020/05/12/building-browser-extensions-with-webpack/"/>
		<updated>2020-05-13T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2020/05/12/building-browser-extensions-with-webpack/</id>
		<content type="html">&lt;p&gt;The broader Javascript packaging ecosystem has evolved greatly in the
past several years. Tools like Grunt, Gulp, and others have fallen by
the wayside in favor of newer, more robust tools like Webpack and Babel.
Because of that, there exist many a &lt;em&gt;create-X&lt;/em&gt; toolchains like
&lt;a href=&quot;https://reactjs.org/docs/create-a-new-react-app.html&quot;&gt;create-react-app&lt;/a&gt;
and &lt;a href=&quot;https://cli.vuejs.org/&quot;&gt;Vue CLI&lt;/a&gt; that deliver to user/developers
easy starting points for using these packaging tools. Yet, there does
not yet seem to be good tooling, or even good examples out there for
how to use Webpack and Babel to build a browser extension. Because of
that, I&#39;ve compiled a short tutorial outlining how I structure my
browser extension,
&lt;a href=&quot;https://github.com/johnjones4/Standard-Notes-Clipper&quot;&gt;Standard Notes Web Clipper&lt;/a&gt;.
This tutorial covers the basic file structure of the project, the Webpack
configuration, and how to build the project.&lt;/p&gt;
&lt;p&gt;Note that to follow this tutorial, you should have a cursory
understanding of
&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Build_a_cross_browser_extension&quot;&gt;how browser extensions work&lt;/a&gt;
and Webpack. Also, to see all of this in a working example, check out
the link to my extension.&lt;/p&gt;
&lt;h2 id=&quot;project-structure&quot; tabindex=&quot;-1&quot;&gt;Project Structure: &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2020/05/12/building-browser-extensions-with-webpack/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When building browser extensions, there are three sets of files that
control the various parts of the extension. Those are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Background Scripts:&lt;/strong&gt; One or more JS files that run in the
background.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Content Scripts:&lt;/strong&gt; One or more JS files that execute in the
context of a browser tab and can access the DOM.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Configuration:&lt;/strong&gt; Usually an HTML file plus one or more JS files to
present a user configuration interface.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://johnjonesfour.com/images/code.png&quot; alt=&quot;Preview of my code&quot;&gt;&lt;/p&gt;
&lt;p&gt;Therefore, we structure our project so that each of these three areas of
concerns have their own directory. In addition, we have a &lt;code&gt;manifest.json&lt;/code&gt;
file that browsers read to configure the extension, and there&#39;s also a
&lt;code&gt;static&lt;/code&gt; folder for static assets such as images and html files.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;* src
  * background
    * lib (All imports for background.js)
    * background.js
  * content
    * lib (All imports for content.js)
    * content.css
    * content.js
  * settings
    * lib (All imports for settings.js)
    * settings.css
    * settings.js
  * manifest.json
* static
  * icon16.png
  * icon48.png
  * icon128.png
  * settings
    * index.html
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Our &lt;code&gt;manifest.json&lt;/code&gt; files looks something like this:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;manifest_version&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;permissions&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;activeTab&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;storage&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;contextMenus&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;icons&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;16&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;icon16.png&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;48&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;icon48.png&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;128&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;icon128.png&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;background&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;scripts&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&quot;background/background.js&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;persistent&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;content_security_policy&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;script-src &#39;self&#39;; object-src &#39;self&#39;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;content_scripts&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;matches&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;http://*/*&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://*/*&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;js&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;content/content.js&quot;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;browser_action&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;default_title&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;web_accessible_resources&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;content/content.css&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;options_ui&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;page&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;settings/index.html&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;open_in_tab&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;webpack-configuration&quot; tabindex=&quot;-1&quot;&gt;Webpack Configuration: &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2020/05/12/building-browser-extensions-with-webpack/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To use this configuration, we need to install the following Node
modules. Note that I&#39;ve also included a JSX transformer here because my extension&#39;s settings use JSX.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;babel-eslint&lt;/code&gt;
&lt;code&gt;babel-loader&lt;/code&gt;
&lt;code&gt;clean-webpack-plugin&lt;/code&gt;
&lt;code&gt;copy-webpack-plugin&lt;/code&gt;
&lt;code&gt;css-loader&lt;/code&gt;
&lt;code&gt;eslint&lt;/code&gt;
&lt;code&gt;eslint-config-standard&lt;/code&gt;
&lt;code&gt;eslint-config-standard-preact&lt;/code&gt;
&lt;code&gt;eslint-plugin-import&lt;/code&gt;
&lt;code&gt;eslint-plugin-node&lt;/code&gt;
&lt;code&gt;eslint-plugin-promise&lt;/code&gt;
&lt;code&gt;eslint-plugin-react&lt;/code&gt;
&lt;code&gt;eslint-plugin-standard&lt;/code&gt;
&lt;code&gt;extract-text-webpack-plugin&lt;/code&gt;
&lt;code&gt;html-webpack-plugin&lt;/code&gt;
&lt;code&gt;node-sass&lt;/code&gt;
&lt;code&gt;postcss-loader&lt;/code&gt;
&lt;code&gt;source-map-loader&lt;/code&gt;
&lt;code&gt;style-loader&lt;/code&gt;
&lt;code&gt;transform-json-webpack-plugin&lt;/code&gt;
&lt;code&gt;web-ext&lt;/code&gt;
&lt;code&gt;webpack&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;In the case of a general Webpack build, there&#39;s usually one starting
source file like &lt;code&gt;index.js&lt;/code&gt; that includes necessary image, CSS, and JS
files, and Webpack outputs a condensed set of files that cover
everything included. However in our case, we need to output three
different classes of files for background, content, and configuration
and all of those classes of files have different source files because
they handle different parts of the extension. All of this is done in a
file titled &lt;code&gt;webpack.config.js&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To do this, first we need to set up &lt;code&gt;webpack.config.js&lt;/code&gt;. (See the final &lt;a href=&quot;https://github.com/johnjones4/Standard-Notes-Clipper/blob/master/webpack.config.js&quot;&gt;webpack.config.js file&lt;/a&gt;.) Now, let&#39;s set up some standard resolvers and modules we
can reuse for all the configurations we&#39;ll make&lt;/p&gt;
&lt;pre class=&quot;language-js&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; path &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;path&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; CopyWebpackPlugin &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;copy-webpack-plugin&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; TransformJson &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;transform-json-webpack-plugin&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./package.json&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; _resolve &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;extensions&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;.jsx&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;.js&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;modules&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;__dirname&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;node_modules&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&#39;node_modules&#39;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; _module &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;rules&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;&#92;.jsx?$&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;exclude&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;__dirname&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;src&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;enforce&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;pre&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;source-map-loader&#39;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;&#92;.jsx?$&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;exclude&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;node_modules&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;babel-loader&#39;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;&#92;.css$&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;loader&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;style-loader&#39;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;loader&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;css-loader&#39;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now that we have that we can start defining all the Webpack
configurations we&#39;ll need. First will be the background scripts. Note
that this also includes plugins to copy anything inside a &lt;code&gt;static&lt;/code&gt;
directory to the final build and to output a &lt;code&gt;manifest.json&lt;/code&gt; file to
the final build. We do some special processing to &lt;code&gt;manifest.json&lt;/code&gt; to
copy some info from our &lt;code&gt;package.json&lt;/code&gt; before outputting. Other than that,
this file processes our &lt;code&gt;background.js&lt;/code&gt; file and outputs everything to
&lt;code&gt;build/background&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;devtool&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;source-map&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;__dirname&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;src&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;background&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;background.js&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;__dirname&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;build&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;background&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;background.js&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;plugins&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CopyWebpackPlugin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token literal-property property&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;__dirname&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;static&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;**&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;*&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token literal-property property&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;./&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token literal-property property&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;static/&#39;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TransformJson&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;__dirname&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;src&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;manifest.json&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;manifest.json&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token literal-property property&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;package&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;description&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token literal-property property&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;package&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;version
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; _resolve&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; _module
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next we include a rule to process &lt;code&gt;settings.js&lt;/code&gt; and output everything
to &lt;code&gt;build/settings&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;  &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;devtool&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;source-map&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;__dirname&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;src&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;settings&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;settings.js&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;__dirname&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;build&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;settings&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;settings.js&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; _resolve&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; _module
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, we have two rules for content scripts. The do this
because we have to lazy-load our CSS when we render our in-tab UI using the
&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM&quot;&gt;shadow DOM&lt;/a&gt;. Because of that, our &lt;code&gt;settings.js&lt;/code&gt; file does not import the CSS and therefore Webpack doesn’t see it by default.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;  &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;devtool&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;source-map&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;__dirname&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;src&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;content&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;content.js&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;__dirname&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;build&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;content&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;content.js&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; _resolve&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; _module
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;devtool&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;source-map&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;__dirname&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;src&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;content&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;content.css&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;__dirname&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;build&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;content&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;content.css&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; _module
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Also note we have a &lt;code&gt;.babelrc&lt;/code&gt; with the following contents:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;sourceMaps&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;presets&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;@babel/preset-env&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;plugins&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;@babel/plugin-transform-react-jsx&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;&quot;pragma&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;h&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;building&quot; tabindex=&quot;-1&quot;&gt;Building &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2020/05/12/building-browser-extensions-with-webpack/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To build this project, use the standard Webpack commands. Usually, these
are included in the &lt;code&gt;scripts&lt;/code&gt; portion of a &lt;code&gt;package.json&lt;/code&gt; file.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Build:&lt;/strong&gt; &lt;code&gt;webpack --config webpack.config.js&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Watch:&lt;/strong&gt; &lt;code&gt;webpack --watch --progress --config webpack.config.js&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;next-steps&quot; tabindex=&quot;-1&quot;&gt;Next Steps &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2020/05/12/building-browser-extensions-with-webpack/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;With the project now compiling properly, we can start coding the browser extension in a much more organized fashion now that we can separate out our source files using imports and be able to easily include third-party modules.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Lean Into Your Side Projects</title>
		<link href="https://johnjonesfour.com/2020/02/26/lean-into-your-side-projects/"/>
		<updated>2020-02-27T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2020/02/26/lean-into-your-side-projects/</id>
		<content type="html">&lt;p&gt;There&#39;s a common refrain in
software among engineers when they see a product that interests them: &amp;quot;I could build that.&amp;quot; The phrase captures
a lot about what&#39;s going on in that their heads the instant they utter
it. At first, it seems to be a statement of boasting – that they
feel they are just as good (or better) than whoever made the original
product. Thinking deeper, it&#39;s a personal challenge they submit to
themselves as a test of their meddle as creators to see how they compare
against another. Ultimately, it&#39;s a sign of someone who is always
thinking creatively about the why and the how of the way things work,
and it&#39;s a mindset to be encouraged. However, rarely in the course of daily work do developers have the opportunity to freely explore their own &amp;quot;I could build that&amp;quot; notions.
This is why we should lean into our side projects.&lt;/p&gt;
&lt;p&gt;&amp;quot;Side project&amp;quot; can have a few interpretations, and I do not mean for
this to be confused with a term like &amp;quot;side hustle&amp;quot; which implies a
stronger business effort. While they can overlap, what I&#39;m referring to
here is a project that is wholly owned by its creator. It&#39;s not
something that is not meant to be a contribution to a broader community
and it therefore is something that can exist mostly independent of
external controls. The inspiration for a side project can come from
anywhere. Maybe it&#39;s born out of an &amp;quot;I could build that&amp;quot; reaction
whereby a developer simply tries to see if they can make a slight
improvement, or maybe it&#39;s a more original, albeit esoteric, solution to
a personal inconvenience. Either way, it&#39;s important that the creator of
a side project feel a deep sense of ownership of the project&#39;s
direction.&lt;/p&gt;
&lt;p&gt;I&#39;m not the first to take a stance on side projects. Proponents have told me they
help developers build valuable skills and they&#39;re a way to take part in
the vast open source ecosystem. Critics have said to me that developers need to take
a break and enjoy living outside of their machines for a while, and that side projects are simply a waste of energy if they benefit only their creators. I agree that there must be moderation
between screen time and &amp;quot;everything else.&amp;quot; &lt;a href=&quot;https://johnjonesfour.com/2020/01/24/go-analog/&quot;&gt;I&#39;ve advocated for
that&lt;/a&gt;. However, I think side projects offer more benefit than detriment when done for the right reasons.&lt;/p&gt;
&lt;p&gt;What I&#39;m talking about with side projects is the creative
outlet they offer. As engineers, we&#39;re constantly tasked with solving
complex problems within an organizational framework. In this environment,
we must build things according to what the team designs, which often is
exciting but is also sometimes constraining. With the diverse experience and
views of a team, there is potential to build great products, but
that also takes away a freedom to implement zany ideas and open
exploration.&lt;/p&gt;
&lt;p&gt;A side project offers that freedom. As something wholly owned by its
creator and designed almost exclusively to solve its creator&#39;s needs,
it&#39;s something that isn&#39;t meant to provide the scalable value of a
regular software product, but it does provide immense value to its
creator in the solution it provides and the freedom it grants. The
utility others may find in it as a product or open source project is
extraneous, and sometimes publicizing a side project is detrimental to
the direct value it provides to its creator because those outside
influences ultimately can take away that sense of ownership. That is
something to decide on a case-by-case basis.&lt;/p&gt;
&lt;p&gt;I have some side projects that I&#39;ve turned into &amp;quot;mini&amp;quot; products because
I wanted them to provide value to others while some other projects I&#39;ve
kept as purely personal endeavors. To illustrate some examples, I have a
&lt;a href=&quot;https://github.com/johnjones4/Standard-Notes-Clipper&quot;&gt;small open source project that&#39;s a &amp;quot;web clipping&amp;quot;
extension&lt;/a&gt; for the
open source app, &lt;a href=&quot;https://standardnotes.org/&quot;&gt;Standard Notes&lt;/a&gt;. Initially,
I simply created it because that was a feature I needed for Standard
Notes, and luckily that project has a highly enthusiastic and welcoming
creator and community who&#39;ve helped me expand the project. On the other
hand, I cobbled together a Raspberry-Pi &lt;a href=&quot;https://www.hackster.io/johnjones4/dresser-top-dashboard-913ed3&quot;&gt;personal dashboard
project&lt;/a&gt;,
which I shared as an open source project, but ultimately did nothing
strongly promote its use among others. The reason is that it&#39;s an
ultra-specific solution that I regularly tweak for no one but myself
because no one but myself needs this exact thing. Again, side projects
are to fulfill their creators&#39; needs and curiosities first.&lt;/p&gt;
&lt;p&gt;Like many personal endeavors, there&#39;re few &amp;quot;wrong ways&amp;quot; to do a side
project. Side projects should scratch an itch, and the minute they stop
doing that, it&#39;s ok to abandon them. Move on to the next thing that
excites you, empowers you, and makes you excited to create.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Go Analog</title>
		<link href="https://johnjonesfour.com/2020/01/23/go-analog/"/>
		<updated>2020-01-24T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2020/01/23/go-analog/</id>
		<content type="html">&lt;p&gt;&lt;img src=&quot;https://johnjonesfour.com/images/scotland.jpg&quot; alt=&quot;OS 9 Desktop&quot;&gt;&lt;em&gt;Me in the highlands of Scotland doing some landscape photography.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Photography is my passion. The art of composing a picture, shooting manually to understand how to get the right ratio of aperture to shutter speed, the cropping and editing process, and yes even the pursuit of the new and interesting gear are all elements of this hobby that I enjoy. Amazingly I’m using the same hand-me-down 35mm film Nikon N6006 today that my parents gave to me in 2000. The reason for this is that I embrace the medium of analog photography because it isn’t dependent on a computer and it’s an activity that gives me something real at the end of the process.&lt;/p&gt;
&lt;p&gt;Yes, I have a digital SLR. A Nikon D200 was my trusted companion through college that I used working as a part-time photographer for the school’s communications office. I brought it with me when I moved to DC and spent many weekends on photography adventures with friends, but when it came time to edit what I had captured, I’d feel a certain exasperation. I’m a software engineer by trade, and so much of my professional life is spent staring at the sharp blue light of a MacBook Pro’s display. Technology is then another passion of mine, and I use computers just as easily as a limb. However, every time I’d load my shots onto my laptop for editing, I’d quickly lose interest and move onto something else. Picking the right photos out of the hundreds I’d shoot and working towards a final edited master just lost appeal for me, and I realized it was because of the inherently ephemeral nature of digital photography left me with no real artifact at the end of the creative process. Sure, I could print and frame the final edited photos, but that was not a part of the process I enjoyed. This is a hobby, after all.&lt;/p&gt;
&lt;p&gt;So, when we moved into a townhouse in Alexandria, VA in late 2017, I carved out a little corner to become a darkroom. After cleaning up my old film Nikon, purchasing some used darkroom equipment on eBay, and mixing the development chemicals, I began my re-entry into the world of analog photography. With my first two rolls of 36 exposures, I spent hours in the darkroom under the warm red glow of the safety light crafting a few treasured pictures. In the 2+ years I’ve spent back in the world of film, I’ve shot hundreds of rolls of film and printed many more pictures. Rarely, a film camera doesn’t accompany me on a trip somewhere or even a walk to get coffee on the weekend.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://johnjonesfour.com/images/darkroom.jpg&quot; alt=&quot;Me in my darkroom&quot;&gt;&lt;em&gt;This is my small but functional darkroom/workbench. Here I&#39;m developing rolls of film inside a development tank. The bottle is light-proof so I can pour in and pour out the development chemicals without exposing the film to outside light.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;With a digital camera’s capacity to store practically unlimited quantities of images, I’d take hundreds of photos and like none of what I captured. With film, I have only 36 exposures and each one represents a fraction of what I spent to buy that roll of film. Each time I take a picture, it costs something to me, and so that encourages me to think about the value of what I’m shooting. I carefully consider what I’m doing each time my finger falls on the shutter button – what this picture means, how I can develop it, and what I can do with the final print. The development process is the same way. From a roll of film, I have to make careful choices about which few shots I think are the best to make into final prints. When taking pictures with my digital camera, I rarely thought about the value of what I was shooting, and I can scarcely recall anything about my day around the time I took a digital photo.&lt;/p&gt;
&lt;p&gt;While I’m developing, that’s the sole thing I concentrate on because there’re no distractions from notifications on my phone or computer. (It’s hard to mindlessly scroll through Instagram when every light source is a risk to ruin your film!) By the end of the process, I wind up with a handful of physical pictures that mean something to me. They document an idyllic winter morning in DC, or a wonderful trip abroad with my wife, or perhaps just something beautiful I saw while exploring. When looking at a finished picture, I can often remember what was happening when it – how I was feeling, what the weather was, who I saw that day, etc. The process captures whole memories that run deeper than what the negatives show.&lt;/p&gt;
&lt;p&gt;This process fulfills me and gives me a creative outlet. When I return to that blue light of my laptop, I feel refreshed and I find my ability to enjoy my work there increases too. By growing my fulfillment outside of the digital world, I’ve increased my overall satisfaction with my digital life. What is your version of “going analog?” If you live and work in the world of technology like me, you often find your favorite activities associated with a device – whether it be a laptop, phone, etc. Ask yourself if that’s working for you? Perhaps there’s a way you need to ditch devices for a while to take part in something that provides more fulfilling value to you. Analog photography might just be that way.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Accessing Dropbox From A Vintage Macintosh</title>
		<link href="https://johnjonesfour.com/2020/01/06/dropbox-and-netatalk-for-vintage-macs/"/>
		<updated>2020-01-07T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2020/01/06/dropbox-and-netatalk-for-vintage-macs/</id>
		<content type="html">&lt;p&gt;There&#39;s a small, but growing, niché of nerds out there who enjoy an activity aptly-named &lt;em&gt;vintage computing&lt;/em&gt; or sometimes &lt;em&gt;retrocomputing&lt;/em&gt;. Think of it along the lines of buying and fixing up classic cars. There&#39;s a love the design and an astonishment for how primitive yet still amazing this old technology is. The design of the first Apple II motherboard makes one think about all the decisions Wozniak had to make every step of the way to get it just right. The translucent plastic casing of the original iMac marks the bold and industry redefining step Jonny Ive took in the late 90s. Today, we expect our electronics to have the detail and polish of fine art, but it was in these days that the industry pioneers set these standards. Because of that, using - or reusing - this hardware and software is an enjoyable pastime for the sentimental design-loving technophile.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://johnjonesfour.com/images/OS9.png&quot; alt=&quot;OS 9 Desktop&quot;&gt;&lt;/p&gt;
&lt;p&gt;I consider myself one of these vintage computing enthusiasts - particularly with Mac hardware and the &amp;quot;classic&amp;quot; Mac OSes, which ended with OS 9 before the release of the completely rebuilt OS X. While OS X lives on to be the more stable, versatile, and scalable OS that the classic Mac OS architecture could not be, there&#39;s an appreciable element of design and utilitarianism with these relics. They didn&#39;t do multitasking well – forcing you to do one task at a time. The interface was quirky, but if you took the time to learn it, then you could get around much faster than you can with modern interfaces. But above all else, sharing a similar sentiment with those who fix up old muscle cars, it&#39;s just plain fun to use old machines and software.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://johnjonesfour.com/images/ibook.jpg&quot; alt=&quot;iBook&quot;&gt;&lt;/p&gt;
&lt;p&gt;Writing is an activity I enjoy with vintage hardware. It is a task where the lack of good multitasking is a bonus as focus stays directed, and one doesn&#39;t need a machine with the latest processor and gigabytes of RAM to do it well. &lt;a href=&quot;https://www.youtube.com/watch?v=D7wmMZmMinM&quot;&gt;Keyboards also aren&#39;t what they used to be.&lt;/a&gt; I wrote much of the first draft of this post on a clamshell iBook that came out in 2000. Remember those? With translucent color plastic trim and a clean white keyboard, the iBook and iMac were integral parts of Jonny Ive&#39;s redefinition of what a computer looked like. There&#39;re not many modern computing tasks these machines can take on today, but they can run an old copy of Microsoft Word just fine.&lt;/p&gt;
&lt;p&gt;One challenge I faced with my embrace of vintage computing was file management. I frankly don&#39;t trust the old hard drive in my iBook, and I while I looked up steps for updating the old drive with a new SSD, that seemed like an incomplete solution as I&#39;d still have to shuttle files back and forth via a USB drive. However, these machines were some of the first on the laptop market to feature integrated Wi-Fi, which Apple branded as &amp;quot;Airport&amp;quot; back then. What if I could set up something that allowed me to wirelessly access my Dropbox account from my iBook? One option I explored was simply sharing my MacBook Pro&#39;s home directory so that the iBook could reach it, but I love Dropbox&#39;s new slimmer on-demand file access feature which wouldn&#39;t be compatible with shared volumes. With the easy route ruled out, I decided to build a solution. Here&#39;s how:&lt;/p&gt;
&lt;h2 id=&quot;wi-fi&quot; tabindex=&quot;-1&quot;&gt;Wi-Fi &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2020/01/06/dropbox-and-netatalk-for-vintage-macs/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The first challenge was Wi-Fi. Modern wireless access point security, &lt;em&gt;WPA&lt;/em&gt;, did not exist when OS 9 came out, and so accessing my existing home network was out. What was available for security in 2000 was &lt;em&gt;WEP&lt;/em&gt;. While insecure now, I decided to accept the risk of setting up a hidden wireless network with WEP security using a spare router. Also, I set the router to operate in &amp;quot;bridge mode&amp;quot; so that, upon connecting to it, my iBook would be on the same subnet as the rest of my home network and receive an IP address from my home network&#39;s existing DHCP server. This got my iBook on the Internet.&lt;/p&gt;
&lt;h2 id=&quot;server-hardware&quot; tabindex=&quot;-1&quot;&gt;Server Hardware &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2020/01/06/dropbox-and-netatalk-for-vintage-macs/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://johnjonesfour.com/images/asus.jpg&quot; alt=&quot;iBook&quot;&gt;&lt;/p&gt;
&lt;p&gt;To get my iBook accessing Dropbox, I had to set up an intermediary device that would act like a Network Attached Storage (NAS) device that also synced to Dropbox. I discovered that Dropbox only supports Intel architecture for their Linux client, and I wanted to make sure I was using the official Dropbox client for this as I&#39;ve had shaky experiences with third-party solutions. This restriction therefore limited me from using something simple like a Raspberry Pi, and instead I browsed &lt;a href=&quot;https://www.pcliquidations.com/&quot;&gt;PC Liquidations&lt;/a&gt; for slim/nettop PCs that would be low power consumers. In that search, I found this &lt;a href=&quot;https://www.pcliquidations.com/p25745-asus-eb1033-atom-d2550&quot;&gt;Asus EB1033 Atom&lt;/a&gt;. I ordered it along with an external hard drive enclosure. (I had a spare 2 TB drive laying around, but you could also customize the order on PC Liquidations to just upgrade the built in drive.) I loaded the machine up with &lt;a href=&quot;https://ubuntu.com/download/server&quot;&gt;Ubuntu Server 18.04.3&lt;/a&gt; running off of the external drive so that I could keep my Dropbox folder in the home directory.&lt;/p&gt;
&lt;h2 id=&quot;file-access&quot; tabindex=&quot;-1&quot;&gt;File Access &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2020/01/06/dropbox-and-netatalk-for-vintage-macs/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In the days of OS 9, the preferred way for accessing network files was using Apple&#39;s proprietary Apple Filing Protocol (AFP) - which is still in use today on the Mac OS and as an open source project called Netatalk. It&#39;s easy and fast on OS 9 so I wanted to stick with it, and Netatalk is great project which I&#39;ve used many times over the years. The project wiki contains a detailed step-by-step tutorial for installing it on Ubuntu &lt;a href=&quot;http://netatalk.sourceforge.net/wiki/index.php/Install_Netatalk_3.1.12_on_Ubuntu_18.04_Bionic&quot;&gt;here&lt;/a&gt;. When I got to the step configuring &lt;code&gt;/usr/local/etc/afp.conf&lt;/code&gt;, I just shared home directories because that is where the Dropbox folder lives. (Note that you&#39;ll need some light Linux experience to do this.) At this point, I could access the new server from my iBook (Using OS 9&#39;s Network Browser located under the Apple menu) and manipulate files in the home directory.&lt;/p&gt;
&lt;h2 id=&quot;dropbox-access&quot; tabindex=&quot;-1&quot;&gt;Dropbox Access &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2020/01/06/dropbox-and-netatalk-for-vintage-macs/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The last step of this process was to get Dropbox syncing on the server. To do that, I followed another detailed step-by-step tutorial on Tech Republic &lt;a href=&quot;https://www.techrepublic.com/article/how-to-install-and-run-dropbox-from-a-headless-linux-server/&quot;&gt;here&lt;/a&gt;. Once I had Dropbox up and running as a background process on the server, I left it overnight so it could sync down my entire account, which is about 700 GB. With this complete, the server now will constantly keep an updated version of my Dropbox account and any local changes made will sync up.&lt;/p&gt;
&lt;h2 id=&quot;outcome&quot; tabindex=&quot;-1&quot;&gt;Outcome &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2020/01/06/dropbox-and-netatalk-for-vintage-macs/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;With Dropbox syncing to my new slim server and it shared over AFP, I can now access any Dropbox file from my iBook, edit those files on my iBook, and watch them automatically sync to Dropbox. It allows me to rest assured my work on the iBook is safe, and I can easily move from machine to machine to work in a very modern why while using vintage hardware.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>My 2018 Open Source Contributions</title>
		<link href="https://johnjonesfour.com/2018/06/11/my-2018-open-source-contributions/"/>
		<updated>2018-06-12T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2018/06/11/my-2018-open-source-contributions/</id>
		<content type="html">&lt;p&gt;I love the open source community not just for it&#39;s ability to unite developers and other technologists around common interests and problems but also because it is a showcase of what everyone - from beginners up to the masters - are exploring. In the interest of that, I want to share a few projects I&#39;ve built this year to serve some of my own needs. The three projects I present here are the result of specific needs in my life around optimizing information that is important to me and managing my professional contacts. They are specific tools for my specific challenges, but, as open source projects, perhaps they may be useful to you as well.&lt;/p&gt;
&lt;h2 id=&quot;inbrief&quot; tabindex=&quot;-1&quot;&gt;InBrief &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2018/06/11/my-2018-open-source-contributions/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/johnjones4/InBrief/master/screenshot.png&quot; alt=&quot;InBrief screenshot&quot;&gt;{: .img-responsive}&lt;/p&gt;
&lt;div class=&quot;github-widget&quot; data-repo=&quot;johnjones4/InBrief&quot;&gt;&lt;/div&gt;
&lt;p&gt;The second screen in my office is alive with headlines, tweets, my calendar, my todos, and the weather thanks to InBrief. It&#39;s a personal dashboard app I built using Electron. All of it is fully customizable making it one of the few open source projects I&#39;ve built that practically resembles a fully-realized product. Users can download a compiled binary from GitHub to try out the app on Mac and Linux. (Windows could potentially be supported, but I&#39;d love some help getting that setup!)&lt;/p&gt;
&lt;p&gt;Here are a few of its features in-depth:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;RSS&lt;/strong&gt; - While I long ago &lt;a href=&quot;https://web.archive.org/web/20130719031013/http://www.adfero.com:80/really-simple-syndication-d-2013/&quot;&gt;declared RSS dead&lt;/a&gt; when Google decommissioned Google Reader, I now see how wrong I was given the amount of readers on the market and the number of sites still actively supporting RSS. InBrief has a widget that scrapes a set of categorized RSS feeds, merges the stories by category, and presents a set of consolidated headlines.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Twitter&lt;/strong&gt; - Twitter still reigns as my most-checked social media platform primarily because the number of techies that I follow. InBrief has a widget that displays tweets from various lists in my account. (I break out people I follow by individuals, companies, humor, and open-source/industry general.)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Email and Tasks&lt;/strong&gt; - Keeping a pulse on what&#39;s on my plate, InBrief reads out my unread and flagged emails from Exchange and Gmail as well as todos due today and this week from Todoist and Asana.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Calendar&lt;/strong&gt; - InBrief provides an overview of my agenda for the day based on my Exchange calendar.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Weather&lt;/strong&gt; - Of course a must-have for any dashboard, I think this is actually a useful place for a short weather forecast.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;what-inspired-inbrief&quot; tabindex=&quot;-1&quot;&gt;What Inspired InBrief &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2018/06/11/my-2018-open-source-contributions/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I was using a few different tools simultaneously to do what InBrief now does for me. Those being:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;TweetDeck&lt;/strong&gt; - I have been a longtime TweetDeck user and have always loved it, but I needed something that could share screen real estate.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Feedly&lt;/strong&gt; - It has tons of great features, but it was slow and I didn&#39;t feel I was getting value out of the premium plan.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Email/Calendar/Todos&lt;/strong&gt; - These have not been supplanted by InBrief, but I desired something that could consolidate the status from these tools in one place.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;the-stack-of-inbrief&quot; tabindex=&quot;-1&quot;&gt;The Stack of InBrief &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2018/06/11/my-2018-open-source-contributions/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Say what you will about Electron&#39;s bulk, it allows for some extremely rapid cross-platform application development. Within the Electron environment, InBrief runs on React using from-scratch CSS framwwork and uses a simple JSON datastore to save settings.&lt;/p&gt;
&lt;h3 id=&quot;what-s-next-for-inbrief&quot; tabindex=&quot;-1&quot;&gt;What&#39;s Next for InBrief &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2018/06/11/my-2018-open-source-contributions/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I&#39;ve been collaborating with someone on improving the UI and UX of this project, and so I plan to continue doing so throughout the rest of the year and as I have time.&lt;/p&gt;
&lt;p&gt;In addition, I&#39;d like to explore:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;OPML&lt;/strong&gt; - Support pointing to an OPML file for importing categories and feeds.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Themes&lt;/strong&gt; - Build out more themes and styles for the tool to add additional variety.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If anyone would like to see other services and widgets supported, please file a feature request on GitHub!&lt;/p&gt;
&lt;h2 id=&quot;rolodex&quot; tabindex=&quot;-1&quot;&gt;Rolodex &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2018/06/11/my-2018-open-source-contributions/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/johnjones4/Rolodex/master/screenshot.png&quot; alt=&quot;InBrief screenshot&quot;&gt;{: .img-responsive}&lt;/p&gt;
&lt;div class=&quot;github-widget&quot; data-repo=&quot;johnjones4/Rolodex&quot;&gt;&lt;/div&gt;
&lt;p&gt;Seeking to better manage my professional relationships, I built Rolodex, a web app that syncs-down my Exchange, Google, and LinkedIn contacts into one master list. In this app, I can star contacts for which I have a special relationship, see how often I am in touch with my network, set reminders for how often I&#39;d like to be communication with specific individuals, and log notes after interactions so that I can remember everything. Networking is not my first-best talent, but Rolodex has given me a structure in which I can do a better job of managing relationships and building new ones.&lt;/p&gt;
&lt;h3 id=&quot;what-inspired-rolodex&quot; tabindex=&quot;-1&quot;&gt;What Inspired Rolodex &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2018/06/11/my-2018-open-source-contributions/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Late last year, the Wall Street Journal published a look at &lt;a href=&quot;https://www.wsj.com/articles/david-rockefellers-famous-rolodex-is-astonishing-heres-a-first-peek-1512494592&quot;&gt;the system used by David Rockefeller&lt;/a&gt; to track all of his associations. Given the period in which he built this system, it was all done on notecards in a custom-built Rolodex system. Inspired by his system, I decided to take a more active approach to tracking my professional relationships. I built my own system because while there were many interesting personal-CRM apps on the market, they were all transaction oriented. For my needs, I wanted something that was more about managing a wide net of contacts but not necessarily targeting any sort of goal.&lt;/p&gt;
&lt;h3 id=&quot;the-stack-of-rolodex&quot; tabindex=&quot;-1&quot;&gt;The Stack of Rolodex &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2018/06/11/my-2018-open-source-contributions/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Rolodex is a web app backed by Node.JS storing everything in SQLite3 and uses React+Bootstrap4 on the frontend. The entire thing is fully Dockerized making for easy deployment and reuse.&lt;/p&gt;
&lt;h3 id=&quot;what-s-next-for-rolodex&quot; tabindex=&quot;-1&quot;&gt;What&#39;s Next for Rolodex &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2018/06/11/my-2018-open-source-contributions/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I use this web app as part of my everyday routine, and so I&#39;m constantly finding small bugs and ways I want to tweak the platform. Right now, there&#39;s a bug in the sync-scheduler and I&#39;m considering switching to Postgres for the datastore. This is a project I will continually add features to as long as its a part of my workflow.&lt;/p&gt;
&lt;p&gt;Features I&#39;m considering right now are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Report Export&lt;/strong&gt; - Grab a CSV of notes for certain contacts&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sync-Up&lt;/strong&gt; - After pulling and merging contacts from various sources, push that merged data back up to one or all services&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Speed Improvements&lt;/strong&gt; - Syncing down the data is too slow, and so I&#39;d like to find ways to optimize this.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;feedpage&quot; tabindex=&quot;-1&quot;&gt;FeedPage &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2018/06/11/my-2018-open-source-contributions/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/johnjones4/FeedPage/master/screenshot.png&quot; alt=&quot;InBrief screenshot&quot;&gt;{: .img-responsive}&lt;/p&gt;
&lt;div class=&quot;github-widget&quot; data-repo=&quot;johnjones4/FeedPage&quot;&gt;&lt;/div&gt;
&lt;p&gt;Lastly, in keeping with my information addiction, I wanted a bare-bones RSS feed to set as my homepage. After building InBrief, I ditched Feedly and felt it was too slow to switch back to. Therefore, I cobbled together FeedPage in an afternoon to serve this need.&lt;/p&gt;
&lt;p&gt;Every few minutes, FeedPage pulls down an OPML file from my Gists (OPML is a standard XML format for storing a categorized list of RSS feeds), pulls the latest copies of each feed, and organizes the feed items into categorized lists based on the OPML file&#39;s categories. The page displaying the feed is just plain HTML and pulls Bootstrap from a CDN so it loads extremely quickly. Anytime I want to manage my feed, I just edit the OPML file on GitHub.&lt;/p&gt;
&lt;h3 id=&quot;the-stack-of-feedpage&quot; tabindex=&quot;-1&quot;&gt;The Stack of FeedPage &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2018/06/11/my-2018-open-source-contributions/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Feedpage is Node.JS web app that uses a simple EJS template to render the feed. The entire &lt;code&gt;index.js&lt;/code&gt; file – the only actual JS code in the project – clocks in at a short 141 lines.&lt;/p&gt;
&lt;h3 id=&quot;what-s-next-for-feedpage&quot; tabindex=&quot;-1&quot;&gt;What&#39;s Next for FeedPage &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2018/06/11/my-2018-open-source-contributions/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Not much. This is a simple tool doing a simple job. If anyone sees a way to speed up the OPML parsing or feed scraping, please shout!&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>SSIR: Open Source Software Is Philanthropy</title>
		<link href="https://johnjonesfour.com/2017/10/29/open-source-software-is-philanthropy/"/>
		<updated>2017-10-30T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2017/10/29/open-source-software-is-philanthropy/</id>
		<content type="html">&lt;p&gt;From Stanford Social Innovation Review:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It is increasingly important that any serious enterprise—whether startup, media brand, government agency, foundation, or nonprofit organization—have access to cutting edge, reliable, and useful digital tools that extend their reach and accelerate their success. While commercial software continues to play a role for many organizations, more and more institutions are tapping into the less constrained and often less expensive world of open source software for solutions. Open source is vital for the digital community, providing an accessible, predominantly free forum for building everything from websites to analytic data platforms. Open source also spurs creativity and resource sharing among groups that otherwise would rarely connect. It’s a form of collaboration that is becoming mission-critical for many nonprofits that lack the budgets to build robust digital products on their own. When properly supported, open source software can spark innovation, accelerate social good, and ultimately help change the world.&lt;/p&gt;
&lt;/blockquote&gt;
</content>
	</entry>
	
	<entry>
		<title>Case Foundation Blog: The Technology Powering the Impact Investing Network Map</title>
		<link href="https://johnjonesfour.com/2017/08/07/the-technology-powering-the-impact-investing-network-map/"/>
		<updated>2017-08-08T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2017/08/07/the-technology-powering-the-impact-investing-network-map/</id>
		<content type="html">&lt;p&gt;From the Case Foundation blog:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The Case Foundation has worked over the last two years with data partners ImpactSpace and Crunchbase to develop the Impact Investing Network Map, an interactive tool showcasing the publicly available transactions between companies and investors within Impact Investing. We believe that by bringing the connections between actors to life—looking specifically at the investments that connect them—we can foster a better understanding of the size, breadth, depth, and, importantly, the enormous potential of this field.&lt;/p&gt;
&lt;/blockquote&gt;
</content>
	</entry>
	
	<entry>
		<title>Doomsday Machine Followup: Docker Project</title>
		<link href="https://johnjonesfour.com/2017/07/16/doomsday-machine-followup-docker-project/"/>
		<updated>2017-07-17T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2017/07/16/doomsday-machine-followup-docker-project/</id>
		<content type="html">&lt;p&gt;Doomsday Machine is a tool for backing up cloud services to a local machine. This project is a followup to a Node.js project I started in April. While that project worked well, I wasn&#39;t happy with the code&#39;s performance and lack of support, being that the project was just my code. Instead, I&#39;ve constructed this project which is really just an amalgamation of others&#39; great projects.&lt;/p&gt;
&lt;p&gt;With a rising threat of ransomware and other data-crippling threats, backups are indispensable. While cloud services, by their nature, are extremely stable and backed-up often, there is still the threat that a malicious entity could compromise or corrupt those services.&lt;/p&gt;
&lt;p&gt;To help curb that threat, I’ve created Doomsday Machine, a tool that backs up a user’s cloud service data to a local machine in a generally accessible format. It supports cloud file services such as Dropbox and Google Drive, IMAP Email, Google Contacts, LastPass, Evernote, GitHub, and Todoist. I recommend this backup be run on a separate machine that is dedicated to the backup task so that should the primary machine be compromised, the backups are safe elsewhere.&lt;/p&gt;
&lt;p&gt;For my purposes, I am using an old Dell PowerEdge 1950 that was sitting unused. I picked up two 1TB 2.5 inch SATA drives with drive caddies, configured them to run in RAID 0, and installed CentOS 7. An actual server like I’ve used here is not required, but it was available for me at the time of writing.&lt;/p&gt;
&lt;p&gt;To learn how to setup Doomsday Machine to backup your services, check out the project&#39;s &lt;a href=&quot;https://github.com/johnjones4/Doomsday-Machine-2&quot;&gt;page on GitHub&lt;/a&gt;.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Why AR Will Supersede VR</title>
		<link href="https://johnjonesfour.com/2017/06/21/why-ar-will-supersede-vr/"/>
		<updated>2017-06-22T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2017/06/21/why-ar-will-supersede-vr/</id>
		<content type="html">&lt;p&gt;Apple’s unveiling of their augmented reality development tool, AR Kit, at the World Wide Developer Conference is an indicator that they see the future of augmented reality being much stronger than the future of virtual reality. Augmented reality, or AR, is a user interface paradigm whereby a mobile device overlays information such as 3D graphics on live video captured by the device and positions those graphics based on the device’s GPS coordinates, direction, and orientation. This, in effect, joins the real world with the digital world as viewed by the phone and allows apps to augment what the user sees in reality. (See what I did there?) Whereas virtual reality, or VR, is an user interface paradigm whereby the user places a specialized pair of goggles on his or her head that projects a 3D image specifically for the user and that adjusts based on the user’s direction and orientation. VR experiences can include both 360° video recorded by a specialized camera that captures from multiple angles and conventional 3D graphics. While both AR and VR offer mobile-oriented, 3D experiences, I agree with Apple’s decision to pursue AR in a stronger way because it produces an experience that many can share simultaneously, it actively integrates both a new user interface paradigm and traditional tap-based gestures, and it requires no additional hardware other than a phone unlike VR which requires a specialized headset therefore making it a far more ubiquitous medium.
Samsung’s Gear VR headset.&lt;/p&gt;
&lt;p&gt;In the realm of virtual reality, when a user places a VR set on his or her head, VR transports him or her to a different, isolated world. It is the ultimate immersive experience because the user is totally separated from normal reality. This creates an enthralling experience well suited to the games and riveting documentaries so often associated with VR. However, the experience is an isolated one. Shared VR experiences are only now beginning to take shape, and there is work to be done before they are practical beyond novelty. VR, for the time being, is primarily a mono-directional interface whereby the user can only consume content rather than consume and create it. Albeit, the user in VR does have a level of control over how he or she consumes the content by moving about within the virtualized space, it is still just generally consumption of information rather than a true exchange with another party.&lt;/p&gt;
&lt;p&gt;Instead, AR offers a shared experience so that multiple users with independent devices or just one shared device may all see, hear, and interact with the content and then make choices to manipulate or change the content using the controlling device’s other inputs such as voice or touch. Pictured at the beginning of this post, I visited National Geographic’s space at SXSW 2017 and tool part in their AR exhibit. The use of AR in this instance connected me to the content of the experience — the augmentation of a series of wildlife photos — but also connected me with my colleagues viewing the content with me and with the party that was happening all around me. This was neither a distracting nor isolating experience: AR was simply one ingredient in the recipe of this event.
IKEA’s catalog app allows users to see the furniture store’s products in users’ homes using AR.&lt;/p&gt;
&lt;p&gt;While some have called Apple’s unveiling of AR Kit a move that is too late and that AR on the iOS platform has already taken off, I disagree that this is a too-far-gone decision on Apple’s part. While the feasibility of AR as a successful medium is proven thanks to such innovative games as Pokemon Go, Apple sees the value in offering a tool to developers that decreases the barrier to entry for AR and that will eventually then increase the saturation of AR in the iOS ecosystem.&lt;/p&gt;
&lt;p&gt;A constant issue seen with VR is the difficulty in getting the right hardware in the hands of consumers. Early adopters and tinkerers have either sprung for expensive headsets like Oculus or have gone the low-budget route of Google Cardboard, but the overall market penetration remains low with a projected 2.5M unit sales of VR devices for 2017 whereas as Apple alone ships more iPhones than that in one quarter. Meanwhile, AR requires no additional hardware outside of a single mobile device. By virtue of that simplicity, the ratio of the number of users who can be considered AR Kit-ready vs the number of users who can be considered VR-ready is almost exactly the ratio of the number of active iOS devices vs the number of active VR hardware and VR-enabling devices such as Google Cardboard. Meaning: the market for AR far exceeds the market for VR.&lt;/p&gt;
&lt;p&gt;This is by no means a death sentence for VR. It is still a valuable tool and one that will remain relevant, but it will never be as widespread as some hope. VR and AR are still different mediums attacking the similar problem of creating enhanced digital experiences, and therefore VR will continue to thrive where total immersion benefits the user. However, history shows us that users tend to avoid total immersion and prefer interactivity that compliments or integrates-with their environment. For evidence of that, look at the downfall of 3D TV or the long-forgotten Nintendo VirtualBoy. Instead, we see AR used in applications that are a part of a larger interaction ecosystem: event management apps give users heads-up-displays of what is going on around then, entertainment apps place users’ favorite characters in the real world, retail apps finally make relevant connections between the brick and mortar and e-commerce.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Backing Up Cloud Services With Doomsday Machine</title>
		<link href="https://johnjonesfour.com/2017/05/13/backing-up-cloud-services-with-doomsday-machine/"/>
		<updated>2017-05-14T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2017/05/13/backing-up-cloud-services-with-doomsday-machine/</id>
		<content type="html">&lt;p&gt;&lt;em&gt;I&#39;ve constructed a new version of Doomsday Machine based on other backup tools and coordinated all in a Docker image. &lt;a href=&quot;https://johnjonesfour.com/2017/07/17/doomsday-machine-followup-docker-project/&quot;&gt;Read More&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;With a rising threat of ransomware and other data-crippling threats, backups are indispensable. While cloud services, by their nature, are extremely stable and backed-up often, there is still the threat that a malicious entity could compromise or corrupt those services.&lt;/p&gt;
&lt;p&gt;To help curb that threat, I&#39;ve created &lt;a href=&quot;https://github.com/johnjones4/Doomsday-Machine&quot;&gt;&lt;em&gt;Doomsday Machine&lt;/em&gt;&lt;/a&gt;, a tool that backs up a user&#39;s cloud service data to a local machine in a generally accessible format. It supports Dropbox, Google Drive, IMAP Email, Google Contacts, LastPass, Evernote, GitHub, and Todoist. I recommend this backup be run on a separate machine that is dedicated to the backup task so that should the primary machine be compromised, the backups are safe elsewhere.&lt;/p&gt;
&lt;p&gt;Below is how I&#39;ve setup &lt;em&gt;Doomsday Machine&lt;/em&gt; for my own purposes:&lt;/p&gt;
&lt;h3 id=&quot;the-computer&quot; tabindex=&quot;-1&quot;&gt;The Computer &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2017/05/13/backing-up-cloud-services-with-doomsday-machine/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://johnjonesfour.com/img/doomsdaymachine_server.jpg&quot; alt=&quot;Backup server&quot;&gt;{: .img-responsive}&lt;/p&gt;
&lt;p&gt;For my purposes, I am using an old Dell PowerEdge 1950 that was sitting unused. I picked up two 1TB 2.5 inch SATA drives with drive caddies, configured them to run in RAID 0, and installed CentOS 7. An actual server like I&#39;ve used here is not required, but it was available for me at the time of writing.&lt;/p&gt;
&lt;h3 id=&quot;docker&quot; tabindex=&quot;-1&quot;&gt;Docker &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2017/05/13/backing-up-cloud-services-with-doomsday-machine/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To run the service on the machine, I set it up to run containerized using Docker Compose. For those unfamiliar with containerization, check out &lt;a href=&quot;https://www.docker.com/what-docker&quot;&gt;Docker&#39;s great guide&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;docker-compose.yml&lt;/code&gt; file I use is below with a few notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The Git project for &lt;em&gt;Doomsday Machine&lt;/em&gt; is in a directory next to this file named doomsdaymachine.&lt;/li&gt;
&lt;li&gt;The file mysql.env contains the variables for MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE, and MYSQL_ROOT_PASSWORD.&lt;/li&gt;
&lt;li&gt;The file vars.env contains all other variables list on the &lt;a href=&quot;https://github.com/johnjones4/Doomsday-Machine/blob/master/Readme.md&quot;&gt;readme&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The directory /var/backup on the container is mapped to /var/backup on the host machine. Make sure to create the directory on the host machine.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;language-yaml&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;2&#39;&lt;/span&gt;

&lt;span class=&quot;token key atrule&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;mysql&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; mysql
    &lt;span class=&quot;token key atrule&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; mysql&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;/var/lib/mysql/
    &lt;span class=&quot;token key atrule&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;3306:3306&quot;&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;env_file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; ./mysql.env
  &lt;span class=&quot;token key atrule&quot;&gt;doomsdaymachine&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ./doomsdaymachine
    &lt;span class=&quot;token key atrule&quot;&gt;links&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;mysql:mysql&quot;&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;8000:8000&quot;&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;./doomsdaymachine:/src/app&quot;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/var/backup:/var/backup&quot;&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;env_file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; ./mysql.env
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; ./vars.env
    &lt;span class=&quot;token key atrule&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;app/start.sh&quot;&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;restart&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; always

&lt;span class=&quot;token key atrule&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;mysql&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Also, the Dockerfile inside &lt;code&gt;./doomsdaymachine&lt;/code&gt; is:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FROM node:6.9.4

RUN apt-get update &amp;amp;&amp;amp; apt-get install -y netcat
RUN mkdir /src
WORKDIR /src
ADD package.json package.json
RUN npm install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, there is an additional file in &lt;code&gt;./doomsdaymachine&lt;/code&gt; that is the entry executable for the container named &lt;code&gt;start.sh&lt;/code&gt;. It waits for the MySQL server to become available before starting note. Make sure to make the file executable by running &lt;code&gt;chmod +x ./doomsdaymachine/start.sh&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash
echo &amp;quot;&amp;gt;&amp;gt; Waiting for MySQL to start&amp;quot;
WAIT=0
while ! netcat -z mysql 3306; do
  sleep 1
  WAIT=$(($WAIT + 1))
  if [ &amp;quot;$WAIT&amp;quot; -gt 600 ]; then
    echo &amp;quot;Error: Timeout waiting for MySQL to start&amp;quot;
    exit 1
  fi
done
node /src/app/index.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To start it all up, simply run &lt;code&gt;docker-compose build&lt;/code&gt; and &lt;code&gt;docker-compose up -d&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;configuration&quot; tabindex=&quot;-1&quot;&gt;Configuration &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2017/05/13/backing-up-cloud-services-with-doomsday-machine/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/johnjones4/Doomsday-Machine/master/screenshot.png&quot; alt=&quot;Doomsday Machine screenshot&quot;&gt;{: .img-responsive}&lt;/p&gt;
&lt;p&gt;With the system up and running, it&#39;s time to configure each service. Depending upon server configurations, the URL for the service will be different, but if running it on a local machine at the default port, the URL will be http://localhost:8000.&lt;/p&gt;
&lt;p&gt;To create new backup service, click the &lt;em&gt;+ Add Service&lt;/em&gt; button in the upper right-hand corner of the screen and choose a service to add. Each service&#39;s setup page contains brief instructions on how to configure it. Most services rely on some sort of OAuth credentials or token that vary by provider. After the configuration of a new service, it&#39;s backup will begin during the system&#39;s next backup sweep.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>An Opportunity For Responsible Innovation With Amazon’s Alexa</title>
		<link href="https://johnjonesfour.com/2017/03/29/an-opportunity-for-responsible-innovation-with-amazons-alexa/"/>
		<updated>2017-03-30T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2017/03/29/an-opportunity-for-responsible-innovation-with-amazons-alexa/</id>
		<content type="html">&lt;p&gt;There has been a need in the ever-expanding universe of Internet of Things (IoT) devices for some unifying factor. I’ve written about this before, calling for an open standard to make IoT devices interoperable. This is important because without a common standard or other unifying factor, these devices, designed to facilitate ease in our lives, only create frustration out of poor interoperability and inconsistent user experiences, and what we are beginning to see – thanks to the success of their Echo product line and engagement with the developer community – is Amazon’s Alexa becoming that unifying nucleus of the IoT in a way that could last over the long term. With that, there is an opportunity to launch new enterprises, using Alexa as a springboard, oriented towards socially conscious, responsible action because of how integrated the IoT will be in our lives.&lt;/p&gt;
&lt;p&gt;Amazon’s line of Echo products, while on the market for about two years, have become increasingly popular in the past six months thanks to the release of the smaller, cheaper Echo Dot product. For only $50, consumers can lay the foundation for their own smart home of IoT devices using the Alexa-equipped Echo Dot. From there, Alexa can accept installation of new “skills” in the same way one adds apps to an iPhone using a simple interface. Users can then begin engaging with those skills via Alexa’s voice-controlled interface. These skills are often offshoots of the same apps consumers are already using such as music services like Spotify, news outlets like CNN, and productivity tools like Todoist. Because of that, Alexa serves as an elegant voice-powered extension to existing personal ecosystems of apps. You don’t even need to use Siri on your phone now that Alexa runs on the Amazon app.&lt;/p&gt;
&lt;p&gt;The design of the Echo line and the Alexa system as products facilitate easy adoption by consumers, but what will drive this success in a much broader and outwardly-facing way is how Amazon is providing new, fertile ground for cultivation by entrepreneurs. Alexa’s “Skills Store” is an open system whereby virtually anyone with a product idea can introduce new potential and abilities for Alexa. Unlike Apple’s Siri which offers only limited extensibility, Amazon designed Alexa with a low barrier to entry for developers — stating it takes only an hour to cobble together your first custom Alexa skill. It’s a system that encourages, rather than hinders, experimentation as evidenced by Amazon’s choice to allow Alexa to run on devices beyond what they manufacture such as this developer kit released last year.&lt;/p&gt;
&lt;p&gt;Amazon is even offering incentives to developers in the form of free resources on their cloud computing service, Amazon Web Services (AWS), to use for the backends of new skills. (All Alexa skills need to have some sort of server to process requests for Alexa, and while hosting costs are declining year over year, this is still a significant expense for upstart teams looking to craft the next killer app.) Amazon’s courting of the developer community in this way causes there to be little reason for all new web and mobile apps to not begin offering an Alexa skill as a way to offer an easy interface for their service.&lt;/p&gt;
&lt;p&gt;Because of this success and continued outreach by Amazon, we are about to see a land rush of services that make novel use of Alexa’s voice-based interface in the same way we saw the outpouring of enthusiasm for Apple’s App Store almost a decade ago. The product success is already there to provide the user base, and Amazon’s engagement with the community appears to be working with a growth from 1,000 skills in June 2016 to 10,000 skills in February 2017. This growth does bring its own form of troubles. Where Apple was noted for its strict quality review process in the early days of the App Store, Amazon will perhaps need to begin considering some sort of quality assurance process for the skills store. But despite the quality control challenges ahead for Amazon, this explosion in new skills is indicative of a rising tide for Alexa.&lt;/p&gt;
&lt;p&gt;What makes this opportunity unique for entrepreneurs is that the IoT is so much more integrated into our lives as consumers. When we begin to see Alexa having a more embedded presence in our kitchen appliances, smart mirrors, TVs, cars, and more, an opportunity emerges for services that help us act out more socially conscious and responsible behavior. A kitchen powered by Alexa could give consumers tips on what products in their fridges and pantries are raised responsibly or sourced fairly. A skill built around an incentive or gamification model could reward users for making more efficient use of resources their homes such as power and water. Wherever there is a nexus of consumer action and a smart device, there can be an opportunity for good in this “skills”-driven revolution.&lt;/p&gt;
&lt;p&gt;While these ideas have existed since the concept of IoT gained traction, there has been little motivation to make these services work. However, Amazon seems to be taking the same strategic route Microsoft took with Windows: get as many devices running it and as many developers building for it as possible, and it will be impossible for consumers to ignore. Like Windows, it could then cement itself for decades. Building on that, startup hoping to make this vision of services encouraging responsible behavior can realize a rapid path to a wide user base and establish a strong, early foothold in Alexa’s vast IoT landscape.&lt;/p&gt;
&lt;p&gt;The path for Alexa to succeed is not without barriers. In addition to the potential issue of managing quality with the sudden growth in new skills, there are also external issues Amazon must negotiate. While Siri was a brief fascination when Apple released it, the assistant never quite lived up the hype — representing a setback for all voice-powered digital assistants. This is somewhat a reflection on Apple’s recent shortcomings but also a reflection on the still burgeoning field of “voice” as a user interface medium. Despite it being a part of science fiction for decades, we have yet to write the playbook on the core tenants of a good voice interface. That combined with some unique security concerns introduced by an audio-based interface make for interesting adoption hurdles that Amazon will need to help consumers overcome.&lt;/p&gt;
&lt;p&gt;If Amazon can negotiate these challenges and continue to foster an environment of experimentation and innovation for the developer community, Alexa stands to be the next major platform for innovation and the nucleus of the Internet of Things for the foreseeable future. The right teams just need to capitalize on that.&lt;/p&gt;
&lt;p&gt;And what better way to embrace this trend than to take part in it? I’ve been speaking with some friends in the development community about the skills they are building as side projects. Some have a social good component to them and others are just proofs-of-concept to understand how the Alexa developer kit works. It’s time for me to also join in and create my own Alexa skill. What do you think the next great skill for Alexa should be? Tweet the idea to me @johnjones4!&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Featured On IOT Weekly</title>
		<link href="https://johnjonesfour.com/2017/02/07/featured-on-iot-weekly/"/>
		<updated>2017-02-08T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2017/02/07/featured-on-iot-weekly/</id>
		<content type="html">&lt;blockquote&gt;
&lt;p&gt;Open standards and a common communication protocol is needed if we expect devices to truly bring value in the world of the Internet of Things in the future. This article focuses on this need, specifically when it comes to devices that live in the ecosystems being built by Amazon, Samsung, Google and Apple.&lt;/p&gt;
&lt;/blockquote&gt;
</content>
	</entry>
	
	<entry>
		<title>BASIC Inspired A Generation Of Technologists. What Will Inspire The Next?</title>
		<link href="https://johnjonesfour.com/2017/02/06/basic-inspired-a-generation-of-technologists-what-will-inspire-the-next/"/>
		<updated>2017-02-07T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2017/02/06/basic-inspired-a-generation-of-technologists-what-will-inspire-the-next/</id>
		<content type="html">&lt;p&gt;I received a Christmas gift in fourth grade that profoundly impacted my career path and thus the rest of my life. That gift was a VTech PowerPad Plus “pre-computer.” While just a toy, the PowerPad line of products from the late 1980s and early 1990s were functioning computers that featured, among a handful of educational games, a functioning BASIC interpreter. For the uninitiated, BASIC is an computer programming language designed to be simple, versatile, and for learning. Exploring BASIC on that spartan machine ignited a passion for technology and programming in me that burns to this day.&lt;/p&gt;
&lt;p&gt;Invented at Dartmouth College in the 1960s, BASIC is an acronym for Beginner’s All-purpose Symbolic Instruction Code, and the idea behind it was to create an accessible language that college students could use to write their own mainframe programs without having to learn more complex languages such as FORTRAN and ALGOL. It existed in relative obscurity as a research project, as many early computing innovations did, until the late 1970s when it exploded, and we have none other than Microsoft to thank for that explosion.&lt;/p&gt;
&lt;p&gt;Prior to dominating the operating system and office suite markets, Microsoft was a vendor of programming language interpreters and compilers. One of their first commercial successes was a BASIC interpreter for early popular systems like the Apple II, Commodore 64, Atari, and other personal computers based on the affordable MOS 6502 CPU. Because Bill Gates was able to design his BASIC to work on so many machines (and yes, Bill Gates did much of the programming in those early days) it fast became a popular tool on which early developers cut their teeth.&lt;/p&gt;
&lt;p&gt;From there, early software publishers produced code that, with a little modification, could transfer between those nascent computer systems of the 1980s. Because modems and disks were expensive, users received most code in the form of books published with pages and pages of computer code that had to be copied, by hand, from page to screen. Simple games such as Pong or Solitaire were hundreds or thousands of lines long containing monotonous repetitions of PRINTs, INPUTs, and LETs that BASIC employed to represent instructions. But the cost in time-spent entering those lines of code was made up in educational value. As users tediously poured through those pages, and even without formal training, they could begin to subtly change the code, inject their own ideas, and in time, write their own programs.&lt;/p&gt;
&lt;p&gt;That was the same motivation behind putting a BASIC interpreter into the VTech line of PowerPads. Packaged with an introductory BASIC manual covering the construction of simple input and output routines, it was enough for a 10-year-old to begin a journey into the world of software development. In the next year or so I’d advance to a copy of QBASIC on an old MS-DOS PC in middle school, then to PHP and Javascript in high school, and finally to a wider universe of languages in college studying computer science.&lt;/p&gt;
&lt;p&gt;But what made BASIC wonderful and the key to my early inspiration were a few simple points:&lt;/p&gt;
&lt;p&gt;First, it was ubiquitous. Microsoft’s BASIC interpreter ran on every popular machine, making it a quasi-standard for the day. Later, other software vendors would produce similar variants of BASIC not too dissimilar from Microsoft’s version. Many manufactures, such as Apple, also simply hard-wired BASIC into their machines so that they could run it even without a disk drive.&lt;/p&gt;
&lt;p&gt;Second, it was accessible. BASIC’s syntax is self explanatory and therefore comfortable for even the most novice user. PRINT sends text to the screen, INPUT reads from the keyboard, RECT draws a rectangle, LINE draws a line, etc. And beyond this catalog of simple, literally-named commands, many versions of BASIC included more advanced concepts such as arrays and program flow control structures from big-brother languages so that as users’ skills and understanding grew, BASIC could grow with them and provide more robust options.&lt;/p&gt;
&lt;p&gt;Third, it was free. A copy of BASIC came with almost every new computer in the 1980s and early 1990s with almost no restrictions on use, and the distributions often came with several sample programs preloaded such as Microsoft’s version of Snake, called Nibbles. Just like the books of code I mentioned, this showed what was possible with such a simple toolset and sparked creativity and open experimentation.&lt;/p&gt;
&lt;p&gt;Those elements that led to BASIC’s success in the late 1970s, 1980s, and early 1990s are also what led it to inspire me. As just a kid, it was easy to explore such a simple language that came as a built-in feature on a toy computer. I can distinctly recall writing my first program on that toy laptop: a simple exercise instructing the computer to print out the sentence, “Hello, world!” Suddenly, computers and all of the activities like playing games, word processing, and exploring the early Web were less of a mystery to me. It made sense how a computer did all of that. Another person wrote instructions, just like I did, that made the computer do what he or she wanted. Now the world of technology was only limited by my skill and imagination, and I set out on a path to build both.&lt;/p&gt;
&lt;p&gt;That outcome was the same for so many because BASIC turned the computer from a one-way information source to a truly interactive tool, a digital canvas for the early computer learner. My wife, a first grade teacher, often reminds me that the job of an educator is to not simply deliver information and skills to students. Rather, it is to find and cultivate what inspires students to be lifelong learners. For so many in generation X and Y, it was BASIC that inspired them to become software developers, engineers, scientists, and more. The question then becomes, what is the BASIC of the next generation?&lt;/p&gt;
&lt;p&gt;The value of teaching coding is not lost on us today. From sites like Codecademy to Obama leading the Day of Code, we intrinsically understand how important technical skills are in our society. But those platforms and initiatives are merely skill delivery platforms that feel too close to serious, vocation-oriented education focused on preparing users for the workforce. They do not inspire. They presuppose that the user is strongly motivated, and they teach modern, proper languages so that the outcome is a serious set of skills. They run in contradiction to what made BASIC such a catalyst of inspiration.&lt;/p&gt;
&lt;p&gt;Instead, we need something that fulfills the key things that made BASIC work: a tool that is ubiquitous, accessible, and free.&lt;/p&gt;
&lt;p&gt;To be ubiquitous in this era is a much simpler challenge using the web. What once had to be built directly into a product can ship, like all modern software, on an iterative basis over rapid and wide-reaching delivery channels. Building it on the web is the first step to seeding a broad community of users, but the work only starts there. A true community of users and evangelists is what truly makes something like this ubiquitous.&lt;/p&gt;
&lt;p&gt;To be accessible, it must be something that is not an attempt at job training. Online code learning tools serve an older audience who have the maturity to work through a handful of modern computing concepts before being able to produce something tangible. Programming in Javascript or Ruby is far too complicated for a beginner, and the plethora of content online about modern languages would simply overwhelm someone searching for help. BASIC worked because an elementary school student could begin using it in five minutes but gradually enhance their skills as they became more confident. No online learning platform does this.&lt;/p&gt;
&lt;p&gt;And to be free, it is easier than ever today thanks to the open source movement. As an open source project, it could attract numerous contributors of features and, more importantly, sample code for others to play with. Whatever this tool is, it must be managed in the open — perhaps by a federation of companies and organizations dedicated to inspiring the next generation of coders.&lt;/p&gt;
&lt;p&gt;The big question that remains is what exactly is this tool? Is it a new iteration of BASIC? Taking the core tenets of BASIC as a programming language with a simple, versatile syntax and wrapping it in a modern user experience could make it just as accessible as it once was, but regardless of the literal platform, it will be the community that makes it a success. The ubiquity of BASIC — its presence on so many machines and the vast catalog of software and instructional tools — is what positioned it to inspire so many and make it a success.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>New Project: GrammarLint</title>
		<link href="https://johnjonesfour.com/2017/01/01/new-project-grammarlint/"/>
		<updated>2017-01-02T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2017/01/01/new-project-grammarlint/</id>
		<content type="html">&lt;p&gt;With having _theInitialCommit rolling and making a personal goal to write more, I was unhappy with the grammar checking tools available in the open source community. There&#39;re some great online offerings such as Grammarly and Hemmingway. However there is nothing that works from the command line or one that can run in a build tool such as Grunt.&lt;/p&gt;
&lt;p&gt;While this wasn&#39;t a problem foremost on my mind, I came across a post by &lt;a href=&quot;http://matt.might.net/articles/shell-scripts-for-passive-voice-weasel-words-duplicates/&quot;&gt;Matt Might&lt;/a&gt; that detailed how he built some simple proofing tools as Bash scripts. As an attempt to iterate on his work and build in some things that I wanted to see, I created GrammarLint. To learn more about it, check it out on &lt;a href=&quot;https://github.com/johnjones4/grammarlint&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Where The Internet of Things Runs Aground</title>
		<link href="https://johnjonesfour.com/2016/12/30/where-the-internet-of-things-runs-aground/"/>
		<updated>2016-12-31T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2016/12/30/where-the-internet-of-things-runs-aground/</id>
		<content type="html">&lt;p&gt;The big holiday gift of the season seems to have been the Amazon Echo Dot. The small voice-controlled gadget was the perfect combination of low-cost and high-tech for gadget lovers and techies everywhere. Amazon&#39;s line of &amp;quot;Echo&amp;quot; products represents the first big hit seller in voice-controlled digital assistants following on the mediocre performance and reviews of such similar offerings by Apple first with Siri and then Microsoft and Google with their own voice-controlled assistants integrated into their core suite of products. With the market for digital assistants now established and Amazon solidified as a front runner, I turn my attention to the ecosystem of peripheral Internet of Things (IoT) enabled devices around it.&lt;/p&gt;
&lt;p&gt;The lights in my home are IoT enabled and controlled by both an app on my phone provided by the lights&#39; manufacturer and through my two Echo devices. It&#39;s great; I can tune the lights using my voice to just what I need, set them on timers, and set an &amp;quot;away mode&amp;quot; that overrides the settings if I am out of the apartment for an extended period. But that, save for a few other devices such as the Nest, is really about the best Alexa, Amazon&#39;s name for the voice assistant powering Echo, can do with smart home technology. There are hundreds of other electronic devices in my apartment - many that are or will eventually be IoT ready - but there&#39;s no way for Alexa to talk to them. My TV, stereo system, kitchen appliances, etc are all, while possibly independently sophisticated, islands of connectivity. I cannot ask Alexa to preheat my oven, change the channel on my Xfinity box, or put the tea kettle on because those manufacturers have not expressly built in support for Alexa.&lt;/p&gt;
&lt;p&gt;What is needed is an open standard that manufacturers can begin building into their product lines. This standard would describe the communications interface for IoT devices that all digital assistants such as Siri or Alexa can talk to. That interface would include definitions for basic &amp;quot;intents,&amp;quot; actions to perform as indicated by a user&#39;s language, and some framework around device discoverability and management. While simple in description, this standard would be a rich catalog of every type of device and action in the IoT landscape plus some extra genericized intents for future expansion.&lt;/p&gt;
&lt;p&gt;In essence, I am asking for a common language that all digital assistants can use to communicate with IoT devices. Without it, all of these Internet ready devices are their own little walled gardens within each manufacturer&#39;s ecosystem. Such that GE appliances may only work with Google, Samsung TVs might only work with Samsung phones, etc. Let&#39;s not even begin to consider how alienated Apple&#39;s HomeKit seems to be becoming as a failed extension of the omnipresent Apple ecosystem.&lt;/p&gt;
&lt;p&gt;An open standard helps consumers because it removes the proprietary interface that individuals must use to work with IoT devices. For instance, my smart light bulbs wouldn&#39;t necessarily need to be controlled by just the app from the manufacturer and any devices they choose to make compatible with them. Instead,  any digital assistant could control a device as long as they both adhere to the standard. For instance, I could ask Alexa to turn the lights off, but if I forget to do that after I leave the house, I could also ask Siri on my phone. If I buy a new IoT enabled kitchen appliance, I just need to know it&#39;s compatible with the standard and not if it works with Alexa or not. It would be the same as the WiFi icon that started appearing on computers several years ago to show they adhered to that standard.&lt;/p&gt;
&lt;p&gt;This helps device manufacturers of both the IoT devices and the controlling digital assistants. For makers of digital assistants, this makes their device compatible with virtually any other IoT device. They can then focus their product development efforts on making their AI and skill suite as rich and smart as possible - which would be independent of the standard and therefore an area where a &amp;quot;secret sauce&amp;quot; could win them the industry. IoT enabled device manufactures will see an upside in the same way all manufactures have realized the benefits of embracing open source. A shared code base that is stable, useful, and - most importantly in the realm of IoT - secured by independent verification will solve most of manufacturers&#39; R&amp;amp;D efforts.&lt;/p&gt;
&lt;p&gt;This will work in the same way other open standards have. The web itself is a success story of an open standard embraced by the market. In the early 90s there were countless online services offering proprietary Internet experiences, but the openness of the web and the flourishing of content there drove those siloed products to extinction. What will flourish in an open IoT ecosystem are secure, compatible devices that effortlessly communicate with each other.&lt;/p&gt;
&lt;p&gt;And what organization is to lead this open standard? Given Mark Zuckerberg&#39;s recent exploration into his own digital assistant, Jarvis, I think Facebook stands a good chance at leading the work into a standard. They have the power to bring manufactures along with them the same way Steve Jobs was able to do so with the iTunes Music Store over a decade ago. They certainly have a vested interested in acquiring the data that comes out of the interaction. And they also have a proven record of establishing and fostering robust open communities with their work releasing libraries such as React and authoring the Open Graph standard.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>_theInitialCommit Featured on TechNative</title>
		<link href="https://johnjonesfour.com/2016/12/12/the-initial-commit-featured-on-technative/"/>
		<updated>2016-12-13T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2016/12/12/the-initial-commit-featured-on-technative/</id>
		<content type="html">&lt;p&gt;_theInitialCommit was featured on the site &lt;em&gt;TechNative&lt;/em&gt; this week. It&#39;s been an exciting time for that project as the interest in it seems to grow week over week. I&#39;m also excited to share that Maddison Long, also featured in the article, will be joining the project to help recruit new interview candidates and further promote it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;But who are these people dedicating so much of their time to collaborative projects? In September 2016, John Jones launched the blog, _TheInitialCommit, with the goal of it being the medium for which we can discover the personal stories of open source developers. An interview is published every Tuesday, and touches upon everything from his or her backgrounds, to why he or she decided to become a contributor to their respective open source communities.&lt;/p&gt;
&lt;/blockquote&gt;
</content>
	</entry>
	
	<entry>
		<title>Algorithmic Journalism</title>
		<link href="https://johnjonesfour.com/2016/11/11/algorithmic-journalism/"/>
		<updated>2016-11-12T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2016/11/11/algorithmic-journalism/</id>
		<content type="html">&lt;p&gt;In hindsight of this election, there have been countless articles on how social media, so much more than ever before, affected the outcome of the race. And while the deeper causes of this election have much more to do with the evolution of this country over the past several decades, there is room for a conversation over how the ever growing study of artificial intelligence (AI) in computer science now has enormous affect on how individuals receive news and information online.&lt;/p&gt;
&lt;p&gt;A few months ago, Facebook admitted it had been editorially managing its trending content list rather than allowing an algorithm to decide as many suspected. With validated outrage among those who felt there was a bias in Facebook&#39;s trends, the tech company introduced a new, purely AI-backed trending recommendations system and within days it was &lt;a href=&quot;https://www.washingtonpost.com/news/the-intersect/wp/2016/08/29/a-fake-headline-about-megyn-kelly-was-trending-on-facebook/&quot;&gt;promoting fake news&lt;/a&gt;. Despite promises by Facebook and other social platforms to build better monitoring tools for junk content, the fake news continued to the point that &lt;a href=&quot;https://www.buzzfeed.com/craigsilverman/how-macedonia-became-a-global-hub-for-pro-trump-misinfo?utm_term=.mxGDVWA5V#.pqjR8BQD8&quot;&gt;a few teens in the Balkans collected a tidy sum in ad revenue&lt;/a&gt; from both lifted and fictitious pro-Trump/anti-Clinton content with the bulk of their traffic coming from social media.&lt;/p&gt;
&lt;p&gt;We see now how any content on social media - fake or real - and the artificial intelligence backed algorithms the platforms employ to decide what content users see has contributed to not only the outcome of the election but also the outpouring of surprise and rebuke at the results. The &lt;em&gt;Wall Street Journal&lt;/em&gt;&#39;s May posting of &lt;em&gt;&lt;a href=&quot;http://graphics.wsj.com/blue-feed-red-feed/&quot;&gt;Blue Feed, Red Feed&lt;/a&gt;&lt;/em&gt; again shows us what an echo chamber these feeds have become whereby users constantly see only what the platforms think they want to see. (If it&#39;s human nature to want your opinions validated, then that&#39;s something these platforms have been unequivocally successful at delivering.)&lt;/p&gt;
&lt;p&gt;With successes in AI such as Google now being able to &lt;a href=&quot;https://www.wired.com/2016/09/googles-clever-plan-stop-aspiring-isis-recruits/&quot;&gt;redirect behavior shown to be at risk for ISIS recruitment&lt;/a&gt; and failures such as Microsoft&#39;s Twitter bot &lt;a href=&quot;http://www.nytimes.com/2016/03/25/technology/microsoft-created-a-twitter-bot-to-learn-from-users-it-quickly-became-a-racist-jerk.html&quot;&gt;breaking down into a racist tirade&lt;/a&gt;, 2016 has been quite the year for artificial intelligence and it will only continue to grow in how it impacts or our lives. So much so that outgoing President Obama is &lt;a href=&quot;https://www.wired.com/2016/10/president-obama-mit-joi-ito-interview/&quot;&gt;commenting on the issue&lt;/a&gt; that we must be careful how we design these systems and apply them across society. For it is not his fear of a Terminitor-esc robot uprising, but a fear that an isolated handful of brilliants minds are making decisions that have vast societal and economic impact without the input of others.&lt;/p&gt;
&lt;p&gt;Newspaper publisher William Randolph Hearst&#39;s famous quip, &amp;quot;You furnish the pictures and I&#39;ll furnish the war,&amp;quot; is applicable once again in a new way. This time, it is the technologists who wield the power to start the war and virtually everyone else who provides the pictures. We&#39;ve allowed the algorithms that Noam Chomsky &lt;a href=&quot;https://www.technologyreview.com/s/423917/unthinking-machines/&quot;&gt;pointed out&lt;/a&gt; five years ago have only rudimentary understanding of semantics but no understanding of cultural value decide what we read and where we place our focus. A key part of Trump&#39;s rise and even others who mocked or profited from his rise was those individuals&#39; ability to use strong rhetoric and game these platforms to raise their messages and content. And our own behavior seems to mimic these algorithms as we obsess more over polls than we do positions.&lt;/p&gt;
&lt;p&gt;In a way, this is not new. The rise of cable news years ago and those networks&#39; eventual political polarization sparked a similar debate whereby individuals would choose the network that most aligned with their preexisting beliefs and simply consume whatever content was delivered. But the difference now is that what was once a human job, editorially selecting the stories and information deemed &amp;quot;fit to print,&amp;quot; is now largely the job of computers that have an understanding of words but not of decency. Because of that we have media platforms that only mimic the dividedness we see in our lives where we hear &amp;quot;I don&#39;t know a single Trump voter&amp;quot; or &amp;quot;I don&#39;t know a single Clinton voter.&amp;quot;&lt;/p&gt;
&lt;p&gt;If we are as divided as this election seems to indicate, then must endeavor to tell the real, human stories of the divided and use technology to connect rather than to isolate. We must tell the story of the small towns across the country that feel left behind by progress and the economy. We must tell the story of the immigrant children who come to their teacher crying for fear of deportation. And we must find a way for each side to hear and understand each other. Those are the stories we must build algorithms to understand.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>New Project: _theInitialCommit</title>
		<link href="https://johnjonesfour.com/2016/09/30/the-initial-commit/"/>
		<updated>2016-10-01T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2016/09/30/the-initial-commit/</id>
		<content type="html">&lt;p&gt;Virtually all developers rely on open source software as part of their daily lives. It powers everything from development environments to servers and everything in between. Even non-developers, while rarely realizing it, use open software because it powers much of the web and app ecosystems.&lt;/p&gt;
&lt;p&gt;With the challenges both developers of the most prominent projects have in maintaining their projects and developers of new projects have in reaching a critical mass of users, I wanted to create a site that showcases some of the most notable members of the open source community as well as the community’s unsung heroes – of which there are many.&lt;/p&gt;
&lt;p&gt;This blog is meant to be a medium for those stories. Each published interview will feature multiple aspects of the individual: everything from his or her backgrounds, to why he or she got into development, to why he or she decided to become a contributor to the open source community.&lt;/p&gt;
&lt;p&gt;The objective of this series is to show the pride and commitment these individuals have in the work they do, and that their work is meant for the good of the community.&lt;/p&gt;
&lt;p&gt;If you are an open source contributor and would like to be interviewed, let me know using the contact information below, or reach out or sign up if you are interested in just getting involved!&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>New Node.js Package: Mongoosey</title>
		<link href="https://johnjonesfour.com/2016/05/16/new-nodejs-package-monoosey/"/>
		<updated>2016-05-17T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2016/05/16/new-nodejs-package-monoosey/</id>
		<content type="html">&lt;p&gt;This is a simple Mongoose frontend that can be built into a pre-existing Express application.&lt;/p&gt;
&lt;h2 id=&quot;installation-and-usage&quot; tabindex=&quot;-1&quot;&gt;Installation and Usage &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2016/05/16/new-nodejs-package-monoosey/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To add Mongoosey to a project, first install the npm package:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# npm install --save mongoosey
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, pass your express app and monogoose instance to Mongoosey:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var mongoosey = require(&#39;mongoosey&#39;);
mongoosey.set(&#39;app&#39;,app);
mongoosey.set(&#39;monogoose&#39;,monogoose);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For a full example, take a look at &lt;a href=&quot;https://github.com/johnjones4/Mongoosey/blob/master/demo.js&quot;&gt;demo.js&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;After that, Mongoosey will setup routes under &lt;code&gt;/db&lt;/code&gt; to manage all defined Mongoose models. For instance, if there is a model named &lt;em&gt;User&lt;/em&gt;, Mongoosey will setup the following pages to manage it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;/db/User&lt;/strong&gt; - Lists all MongoDB documents in the collection associated with that model&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;/db/User/new&lt;/strong&gt; - Creates a new instance of that model for editing&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;/db/User/&lt;em&gt;ObjectId&lt;/em&gt;&lt;/strong&gt; - Displays the object with that ObjectId for editing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To add a layer of security, simply add a route handler to Mongoosey similar to a Connect app:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mongoosey.use(function(req,res,next) {
  // Authorization logic via HTTP authentication or session validation ...
});
&lt;/code&gt;&lt;/pre&gt;
</content>
	</entry>
	
	<entry>
		<title>Presidential Candidates Tweets N-Grams (Twitter API + D3.js)</title>
		<link href="https://johnjonesfour.com/2016/04/30/presidential-candidates-tweets-ngrams/"/>
		<updated>2016-05-01T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2016/04/30/presidential-candidates-tweets-ngrams/</id>
		<content type="html">&lt;p&gt;This is a small data collector and visualization that scans the Twitter API for tweets from presidential candidates, analyzes the N-grams of the tweets, and renders the most popular n-grams based on frequency. The N-gram algorithm is based off of the one found on &lt;a href=&quot;http://www.text-analytics101.com/2014/11/what-are-n-grams.html&quot;&gt;Text Mining, Analytics &amp;amp; More&lt;/a&gt; and the visualization is built with &lt;a href=&quot;https://d3js.org/&quot;&gt;D3.js&lt;/a&gt;.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Contributing to Backup Tool</title>
		<link href="https://johnjonesfour.com/2016/01/09/backup-tool-contributions/"/>
		<updated>2016-01-10T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2016/01/09/backup-tool-contributions/</id>
		<content type="html">&lt;p&gt;My latest project, BackupTool, has really taken off. I&#39;ve added a new section to the project outlining how contributors can help out with the project. This is my first open source project to get this sort of traction, so it is quite exciting.&lt;/p&gt;
&lt;p&gt;Having these sort of guidelines is absolutely crucial for an open-source project. Behind the veil that discussion boards provide to contributors, it is easy to forget the normal courtesies we grant each other when dealing face-to-face. Open source software is a great thing because it not only provides others with valuable tools, but it also gives less-experienced developers a place to grow their talents. With BackupTool, I&#39;d like to foster the sort of environment that welcomes aid and encourages collaboration amongst software developers of all skill levels.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Sweaters &amp; Sustenance</title>
		<link href="https://johnjonesfour.com/2016/01/05/sweaters-and-sustenance-video/"/>
		<updated>2016-01-06T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2016/01/05/sweaters-and-sustenance-video/</id>
		<content type="html">&lt;p&gt;My best friend, John Allen, worked with a videographer to produce this amazing documentary about his non-profit, Sweaters &amp;amp; Sustenance. Getting to volunteer with it this holiday season was such a joy, and it&#39;s great to see John&#39;s work recognized in a such a fine way through this video.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>New Project: Backup Tool</title>
		<link href="https://johnjonesfour.com/2015/12/29/new-project-backup-tool/"/>
		<updated>2015-12-30T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2015/12/29/new-project-backup-tool/</id>
		<content type="html">&lt;p&gt;&lt;strong&gt;Update (1/3/15):&lt;/strong&gt; I&#39;ve also built a Mac status bar app that displays the backup progress of the tool. It is not a separate implementation of BackupTool. Rather, it is a tool to monitor the status of BackupTool&#39;s progress in the Mac OSX status bar.&lt;/p&gt;
&lt;p&gt;I wasn&#39;t impressed with the open-source Glacier backup tools out there, so I&#39;m implementing my own in Node.js. There are more than a few open-source backup tool options out there, but I just wasn&#39;t happy with them. I wanted something that worked with Amazon Glacier and worked on a variety of platforms with or without a GUI.&lt;/p&gt;
&lt;p&gt;Nothing out there seemed to fit those requirements, so I&#39;m creating one called Backup Tool. The tool uses a JSON file to store configuration options for directories to backup, destination information, and AWS authentication information. The core backup functionality is running stably on my Mac, and next I&#39;ll be rolling it out to another Fedora machine I have. After that, I plan to go back and write an interactive installer that builds the configuration file and sets up scheduling. (That is all manual right now.) Possibly after that I will look at some GUI options.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Content is Changing: Is Your Team Ready?</title>
		<link href="https://johnjonesfour.com/2015/11/24/content-is-changing-is-your-team-ready/"/>
		<updated>2015-11-25T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2015/11/24/content-is-changing-is-your-team-ready/</id>
		<content type="html">&lt;p&gt;&lt;em&gt;Content Science Review&lt;/em&gt; recently featured an article co-authored by my colleague Jessica Mancari and myself. In it, we discuss a new approach to content and digital production that we&#39;ve been expirmenting with in our teams at Adfero.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When we produced our first multimedia campaign together, we quickly realized that our old way of building content would no longer fly. Trying to fit yesterday’s process into today’s sophisticated content model is like trying to fit a round peg in a square hole. Content is increasingly multidimensional – built around videos, interactive data visualizations, parallax features, first person interviews, and a journalistic approach to storytelling. More complex content unfolds in more complex ways.&lt;/p&gt;
&lt;/blockquote&gt;
</content>
	</entry>
	
	<entry>
		<title>Chef&#39;s Hat Featured on LifeHacker</title>
		<link href="https://johnjonesfour.com/2015/11/08/chefs-hat-featured-on-lifehacker/"/>
		<updated>2015-11-09T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2015/11/08/chefs-hat-featured-on-lifehacker/</id>
		<content type="html">&lt;p&gt;Chef&#39;s Hat was featured on the lifestyle blog &lt;em&gt;Lifehacker&lt;/em&gt; last weekend. This is a very exciting moment for the app as that post has brought it much attention and validation to the effort. I&#39;m excited for the project to begin catering to a wider audience thanks to the positive review from Alan Henry at Lifehacker.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;... it’s one of the fastest, and it’s nice and simple—if all you need is a tool to snip and save those recipes in a beautiful, visual format, or a reminder to go back and actually make those pretty dishes you stumble on, this is a tool worth looking at. Plus, it’s completely free.&lt;/p&gt;
&lt;/blockquote&gt;
</content>
	</entry>
	
	<entry>
		<title>TruckToMe Now On React Native</title>
		<link href="https://johnjonesfour.com/2015/10/10/trucktome-now-on-react-native/"/>
		<updated>2015-10-11T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2015/10/10/trucktome-now-on-react-native/</id>
		<content type="html">&lt;p&gt;All mobile developers, entrenched in either the Android or iOS camps, have always been skeptical of new cross-platform development tools that offer performance and quality &lt;em&gt;just like&lt;/em&gt; natively-developed code. It has been the Holy Grail of the mobile development world. That’s why when word of React Native started popping up in the usual dev news circles, I didn’t pay it much attention. It was just another attempt at the impossible.&lt;/p&gt;
&lt;p&gt;However, when I saw Marc Shilling’s post &lt;a href=&quot;https://medium.com/ios-os-x-development/an-ios-developer-on-react-native-1f24786c29f0&quot;&gt;An iOS Developer on React Native&lt;/a&gt; start to attract a lot of attention, I performed a more serious exploration into React Native’s documentation. What I found based on Marc’s great review - that I will not try to replicate, so please read it - and React Native’s documentation was that this was a toolkit with serious potential. While other cross-platform dev tools like Appcelerator Titanium try to construct awkward abstractions around iOS’s and Android’s similar-but-different UIs, React Native brings its own set of tooling and interface paradigms. It means you have to learn an entirely different style of mobile development, but it works.&lt;/p&gt;
&lt;p&gt;With some new, albeit entry-level, knowledge of React Native, I set out to test my new skills. An old app of mine, &lt;a href=&quot;http://trucktome.com/&quot;&gt;TruckToMe&lt;/a&gt;, has been sitting on the shelf for quite some time and yet still receiving a decent amount of daily downloads. Being such a simple app, it seemed a perfect project for these new skills. A few weeknights of work later, I’m submitting TruckToMe version 4.0 - now powered by React Native - to the iTunes App Store. Next I&#39;ll be getting the app working on Android. I&#39;d always planned to release an Android version of TruckToMe when I first built it, however other projects came along that prevented me from continuing that.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Chef&#39;s Hat Featured on ArlNow</title>
		<link href="https://johnjonesfour.com/2015/08/09/chefs-hat-featured-on-arlnow/"/>
		<updated>2015-08-10T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2015/08/09/chefs-hat-featured-on-arlnow/</id>
		<content type="html">&lt;p&gt;Chef&#39;s Hat was featured on the local blog &lt;em&gt;ArlNow&lt;/em&gt; as part of the site&#39;s regular feature on local startups:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A Courthouse resident has created an app to help people save recipes they find online. John Jones developed Chef’s Hat, a mobile and web app that works like an online recipe box. Users sign in through Facebook or Twitter and then start electronically clipping recipes.&lt;/p&gt;
&lt;/blockquote&gt;
</content>
	</entry>
	
	<entry>
		<title>New Drupal Modules: Mobile Preview and Social Media Links</title>
		<link href="https://johnjonesfour.com/2015/07/31/new-drupal-modules/"/>
		<updated>2015-08-01T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2015/07/31/new-drupal-modules/</id>
		<content type="html">&lt;p&gt;I&#39;ve been a Drupal site developer for a number of years, and I&#39;ve always enjoyed the quality and robustness of the Drupal contributed module community. For those of you not familiar with this, Drupal as an open source platform relies on the contributions of third-party modules, called contrib modules, to bring it the agility and feature-richness it is know for. Given the experiences I&#39;ve acquired on projects such as &lt;a href=&quot;https://mycareeratva.va.gov/&quot;&gt;MyCareer@VA&lt;/a&gt; and a personal commitment to contribute more open source software, I decided to release two modules I created to the Drupal community.&lt;/p&gt;
&lt;h2 id=&quot;mobile-preview&quot; tabindex=&quot;-1&quot;&gt;Mobile Preview &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2015/07/31/new-drupal-modules/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Mobile Preview gives content editors an easy way to preview their work on a mobile device while publishing content. The preview area is sized to match the pixel dimensions of an iPhone 6.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.drupal.org/project/mobile_preview&quot;&gt;View on Drupal.org&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;social-media-links&quot; tabindex=&quot;-1&quot;&gt;Social Media Links &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2015/07/31/new-drupal-modules/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This module adds a formatter for Link fields that sets a class in the link based on what social media site is in the link. (i.e. a link to a Facebook page will receive the class &amp;quot;facebook&amp;quot;.) It can be used in theming when you need to replace a set of social media links with their respective icons.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.drupal.org/project/sml&quot;&gt;View on Drupal.org&lt;/a&gt;&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>New Project: Chef&#39;s Hat</title>
		<link href="https://johnjonesfour.com/2015/06/17/new-project-chefs-hat/"/>
		<updated>2015-06-18T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2015/06/17/new-project-chefs-hat/</id>
		<content type="html">&lt;p&gt;I love to cook, and I love to find new recipes online. However, I thought there was not an easy way to save my found recipes in a way that made them easy to use again later. Sure, most big food websites have a login feature that let&#39;s you bookmark recipes on their site, but keeping recipes siloed within each website is not ideal. Social media sites like Pinterest let you save recipes, but those tools are not tailored to the needs of cooks: easy search of your saved recipes and a readable, useful interface that is optimized for viewing while cooking. All of that is offered by my tool, Chef&#39;s Hat&lt;/p&gt;
&lt;p&gt;Chef&#39;s Hat is a webapp - and soon to include iPhone app - that is there to always remember your recipes for you. Save recipes you find on the web to your Chef&#39;s Hat account; you can then later search through your library of saved recipes and pull up the one you&#39;re looking for.&lt;/p&gt;
&lt;p&gt;Think of Chef&#39;s Hat as a bookmarking tool for your recipes. When you&#39;re visiting one of your favorite recipe sites, you can click the Chef&#39;s Hat button in your browser, and it will then save that recipe to your Chef&#39;s Hat account. Your Chef&#39;s Hat account keeps track of those saved recipes for you to search and explore later.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>New CocoaPod: EmailFeedback</title>
		<link href="https://johnjonesfour.com/2015/05/19/new-cocoapod-emailfeedback/"/>
		<updated>2015-05-20T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2015/05/19/new-cocoapod-emailfeedback/</id>
		<content type="html">&lt;p&gt;This is yet another feedback prompt. This one directs users to submit email feedback or write a review. The app will ask users of the after a set period of time (30 days by default) to review the app. If the user declines, the user is then asked if he or she prefers to just send feedback directly. Selecting this option opens an email composer view with some information about the app and device preloaded. All of the strings and time intervals used in this Pod are customizable.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>JohnJonesFour Now Using HTTPS</title>
		<link href="https://johnjonesfour.com/2015/04/20/now-using-https/"/>
		<updated>2015-04-21T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2015/04/20/now-using-https/</id>
		<content type="html">&lt;p&gt;Using a free Class 1 SSL certificate from &lt;a href=&quot;https://www.startssl.com/&quot;&gt;StartSSL&lt;/a&gt; and a great tutorial provided by Bryce Fisher-Fleig called &lt;a href=&quot;https://bryce.fisher-fleig.org/blog/setting-up-ssl-on-aws-cloudfront-and-s3/&quot;&gt;Setting Up SSL on AWS CloudFront and S3&lt;/a&gt;, I was able to update JohnJonesFour to support HTTPS as the default protocol. While my site does not directly collect or share any private data, I wanted give both StartSSL and CloudFront&#39;s SSL support a try.&lt;/p&gt;
&lt;p&gt;From StartSSL&#39;s site, here&#39;re the details on what a free Class 1 SSL certificate get&#39;s you:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The StartSSL™ Free (Class 1) certificates are domain or email validated and mostly referred to as the free certificates. Because the checks are performed mostly by electronic means, they require only minimal human intervention from our side. The validations are here to make sure, that the subscriber is the owner of the domain name, resp. email account. You may find additional information on this subject in our CA policy.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;The StartSSL™ Free certificates are intended for web sites which require protection of privacy and prevent eavesdropping. However information presented within these certificates, except the domain name and email address, are not verified. Should you need higher validated certification, please check out our StartSSL™ Verified (Class 2) certificates.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;100% Free The StartCom Certification Authority, provides the StartSSL™ Free certificates instantly, without limitations and free of charge under the condition, that the subscriber provides his/her complete, correct personal details and accepts the Subscriber Obligations of the StartCom CA Policy. Secure your web server and mail traffic now by using the Certificate Control Panel.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://www.startssl.com/?app=1&quot;&gt;Source: https://www.startssl.com/?app=1&lt;/a&gt;&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Is Your Website Ready for Google’s Latest Algorithm Change?</title>
		<link href="https://johnjonesfour.com/2015/04/20/is-your-website-ready-for-googles-latest-algorithm-change/"/>
		<updated>2015-04-21T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2015/04/20/is-your-website-ready-for-googles-latest-algorithm-change/</id>
		<content type="html">&lt;p&gt;My latest post on insights.adfero.com:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Beginning April 21, 2015, Google announced that it is updating its search algorithm to start using mobile-friendliness as part of sites’ search result rankings. This should not come as a surprise to webmasters who’ve witnessed Google’s growing emphasis on mobile compatibility in places such as Google PageSpeed Insights and Google Webmaster Tools, not to mention the overall industry trend towards mobile optimization.&lt;/p&gt;
&lt;/blockquote&gt;
</content>
	</entry>
	
	<entry>
		<title>New Plugin: jScrollability</title>
		<link href="https://johnjonesfour.com/2014/12/06/jscrollability/"/>
		<updated>2014-12-07T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2014/12/06/jscrollability/</id>
		<content type="html">&lt;p&gt;This jQuery plugin helps you build single-scroll pages with complex scroll-based animations a la the NYTimes Snowfall feature. As the user scrolls, this plugin will animate the position of items based on the scroll position. To use it, you select various page elements, set the boundary points for their behavior, and then define a functor to compute the behavior.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Adfero&#39;s Recent Open Source Contributions</title>
		<link href="https://johnjonesfour.com/2014/11/24/adferos-recent-open-source-contributions/"/>
		<updated>2014-11-25T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2014/11/24/adferos-recent-open-source-contributions/</id>
		<content type="html">&lt;p&gt;My latest post on insights.adfero.com:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Our interactive team relies greatly on the open source community when building sites and apps. This past quarter, while we’ve been busy building some great sites for our clients, we’ve had the opportunity to give back to the open source community by posting some code fixes and new modules. It’s important that companies who depend on open source software for their daily work contribute back to the community, and it’s something we encourage our development team to do whenever possible.&lt;/p&gt;
&lt;/blockquote&gt;
</content>
	</entry>
	
	<entry>
		<title>Jumpstarting Your Web Analytics Playbook</title>
		<link href="https://johnjonesfour.com/2014/11/08/general-assembly/"/>
		<updated>2014-11-09T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2014/11/08/general-assembly/</id>
		<content type="html">&lt;p&gt;Starting in December, I&#39;ll be hosting a series of workshops at the Washington DC branch of General Assembly. The first one will be a short class on getting into web analytics; it&#39;s absolutley worth checking out.&lt;/p&gt;
&lt;h3 id=&quot;about-this-class&quot; tabindex=&quot;-1&quot;&gt;About This Class &lt;a class=&quot;header-anchor&quot; href=&quot;https://johnjonesfour.com/2014/11/08/general-assembly/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;It’s never too late to learn and apply web analytics to your digital program. Whether you’re launching an integrated marketing campaign or simply want to understand audience behavior on your website, it is nearly impossible to measure and communicate success without web analytics. Don’t make the mistake of analyzing metrics at the end of a project; setting specific goals and benchmarks on the front end will help you stay on track (and course correct, if necessary).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;The good news? You don’t have to be a webmaster to integrate analytics into your digital communications project! During this session, we’ll introduce you to concepts behind a sophisticated measurement plan that will help you achieve your intended results. You’ll walk away with a deeper understanding of analytics and a game plan for your next big project.&lt;/p&gt;
&lt;/blockquote&gt;
</content>
	</entry>
	
	<entry>
		<title>Posted to GitHub: Oh My Tabs</title>
		<link href="https://johnjonesfour.com/2014/10/26/oh-my-tabs/"/>
		<updated>2014-10-27T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2014/10/26/oh-my-tabs/</id>
		<content type="html">&lt;p&gt;This is my first attempt at a Chrome extension. It loads the latest links from Today in Tabs and displays them in a popup menu. The primary reason that I wrote this was because I had never written a Chrome extension before, but I also wrote this so that I could more quickly get to the links found in &lt;a href=&quot;http://tinyletter.com/todayintabs&quot;&gt;Today in Tabs&lt;/a&gt;.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Relaunching My Site On Jekyll</title>
		<link href="https://johnjonesfour.com/2014/10/13/new-site/"/>
		<updated>2014-10-14T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2014/10/13/new-site/</id>
		<content type="html">&lt;p&gt;It&#39;s been a while since I&#39;ve posted anything, so I thought it might be time to correct that. Here&#39;s my new site now powered by Jekyll and running on Amazon S3. The blog content is slimmed-down a little, but I&#39;ve retooled the projects page a bit to me more organized. Not to mention, I&#39;ve updated the projects page with a few open source items I&#39;m working on.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Moving My Private Projects to GitLab</title>
		<link href="https://johnjonesfour.com/2014/01/15/moving-my-private-projects-gitlab/"/>
		<updated>2014-01-16T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2014/01/15/moving-my-private-projects-gitlab/</id>
		<content type="html">&lt;p&gt;Git is such a fantastic tool for developers, and it has saved me on numerous occasions in personal and professional projects. However, the one aspect of Git that I was never fond of was the cost of private hosted Git repository services. Services like GitHub, Springloops, and Beanstalk all charge for such a limited amount of space and such a low cap on the number of private projects that developers always seemed to be getting the shaft.&lt;/p&gt;
&lt;p&gt;Despite that, I caved last year and upgraded my GitHub account to a paid plan so that I could start pushing my private code to the cloud for safe keeping. For the past twelve months, I&#39;ve seen the monthly notification in my inbox that GitHub had charged their rent to my credit card, and every month I&#39;ve felt like a sucker. Not when Rdio collected payment would I get upset. Not when Netflix took their scoop would I get upset. But GitHub, &amp;quot;aw man&amp;quot;, I&#39;d think, they are really winning here. They are charging me money for what was such a simple thing; just a few megabytes of disk space amongst petabytes.&lt;/p&gt;
&lt;p&gt;But there were little alternatives. Sure, it isn&#39;t that hard to install Git on a server, and it&#39;s not that much harder to allow other hosts to push to it remotely. But that takes time, and there&#39;s no way in that scenario to automate the creation and management of the individual repos. It just wasn&#39;t the same as the nice web interfaces offered by the big boys. Again, I was stuck. That was until, I recently saw talk of &lt;a href=&quot;http://gitlab.org/&quot;&gt;GitLab&lt;/a&gt; gaining traction. What was this? An open-source Git web interface? It seemed too good to be true.&lt;/p&gt;
&lt;p&gt;Touring the features on the site, I saw that GitLab&#39;s feature set was a near-clone of GitHub&#39;s popular features. You can have public or private repositories, work alone or with collaborators, group repos into organizational units, and view all styles of useful data on repos such as popular committers and branch displays. GitLab seemed to be the answer to all of my hosted Git anxieties.&lt;/p&gt;
&lt;p&gt;With a healthy bit of skepticism in hand, I logged into my Amazon EC2 server tonight and began the rather lengthy &lt;a href=&quot;https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md&quot;&gt;install process&lt;/a&gt; for GitLab. Install processes that long always make me nervous. Seeing all of those make commands and strict version requirements told me that I&#39;d surely be bailing out by step three. To my joy and surprise, the install procedure was perfect. End-to-end the install guide accounted for detailed variations a user&#39;s system might have and offered suitable workarounds to make the software functional. Within 40 minutes I was up and running with my own private Git repository service.&lt;/p&gt;
&lt;p&gt;GitLab&#39;s interface was a breeze to use, making the transfer of my private projects such as &lt;a href=&quot;http://trucktome.com/&quot;&gt;TruckToMe&lt;/a&gt; and &lt;a href=&quot;http://wearwhatapp.com/&quot;&gt;WearWhat&lt;/a&gt; simple and fast. GitLab is an amazing open source project that is ready for the big game in professional development teams. For those teams , GitLab frees them from the bounds of limited Git services that prevented them from better collaborating and better managing their code. For me, GitLab just takes away some anxiety.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>What We Learned from Healthcare.gov</title>
		<link href="https://johnjonesfour.com/2013/10/31/what-we-learned-healthcaregov/"/>
		<updated>2013-11-01T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2013/10/31/what-we-learned-healthcaregov/</id>
		<content type="html">&lt;p&gt;My latest post on insights.adfero.com:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Anyone who works in an industry even remotely tech-related in Washington DC has repeatedly fielded the recent question, “what do you think of Healthcare.gov?” The news, and its DC-typical host of rumors, consumed this town and this country for a time. The failure of the site to function properly led to decreased public confidence in the new healthcare system established by the Affordable Healthcare Act, and it led to a very humiliating experience for the Obama Administration, the Department of Health and Human Services and the contractors involved, including CGI Federal. However, another tragedy in this family of tragedies is that the Healthcare.gov failure represents a missed chance for the government to set a higher bar for web-based government services.&lt;/p&gt;
&lt;/blockquote&gt;
</content>
	</entry>
	
	<entry>
		<title>3 Things in the Digital World to Watch</title>
		<link href="https://johnjonesfour.com/2013/10/22/3-things-digital-world-watch/"/>
		<updated>2013-10-23T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2013/10/22/3-things-digital-world-watch/</id>
		<content type="html">&lt;p&gt;My latest post on insights.adfero.com:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The buzz in Drupal groups is about the upcoming release of Drupal 8. With this major release, the maintainers of the prolific content management system (CMS) all but rebuilt Drupal from the database to the front end. The change comes as the result of an increased focus on creating code that is portable between Drupal and the larger web development community. The upside is that building a sophisticated Drupal site could become easier as Drupal sites will be able to more easily borrow code from existing open source projects. The downside is that current Drupal developers need to relearn nearly the entire platform.&lt;/p&gt;
&lt;/blockquote&gt;
</content>
	</entry>
	
	<entry>
		<title>Latest App: WearWhat</title>
		<link href="https://johnjonesfour.com/2013/08/31/latest-app-wearwhat/"/>
		<updated>2013-09-01T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2013/08/31/latest-app-wearwhat/</id>
		<content type="html">&lt;p&gt;I&#39;m excited to announce the launch of my latest app, WearWhat. The app is a different kind of weather app that gives you crucial information about weather when you start your day. The app is $0.99, and available in the iTunes US store. Learn more from this &lt;a href=&quot;https://johnjonesfour.com/download/WearWhatPressRelease.pdf&quot;&gt;press release&lt;/a&gt;.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Upcoming App Launch: WearWhat?</title>
		<link href="https://johnjonesfour.com/2013/07/31/upcoming-app-launch-wearwhat/"/>
		<updated>2013-08-01T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2013/07/31/upcoming-app-launch-wearwhat/</id>
		<content type="html">&lt;p&gt;Next month, I&#39;m releasing a new app titled WearWhat?. The app delivers quick local weather forecast information to users on the go. It&#39;s hard getting dressed in the morning without knowing the weather. You don&#39;t have time to wait for the weather report on the morning news, and you sure can&#39;t run outside unclothed to see if it&#39;s raining. (Trust us, that was a bad idea.) Instead, you can use WearWhat? to get a notification with the weather report at the time you choose.&lt;/p&gt;
&lt;p&gt;For more information, &lt;a href=&quot;http://wearwhatapp.com/&quot;&gt;sign up&lt;/a&gt; for our mailing list to get instantly notified when the app becomes available.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Mobile Strategy Doesn’t End with Your Website</title>
		<link href="https://johnjonesfour.com/2013/04/28/mobile-strategy-doesn%E2%80%99t-end-your-website/"/>
		<updated>2013-04-29T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2013/04/28/mobile-strategy-doesn%E2%80%99t-end-your-website/</id>
		<content type="html">&lt;p&gt;My latest post on insights.adfero.com:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Now that Facebook has unveiled redesigned mobile pages featuring stronger action-oriented controls, it’s time examine how we implement mobile strategy outside of websites. Audiences absorb a brand through a variety of channels, and assuming that all mobile roads lead to your responsive website is too narrow of a focus.&lt;/p&gt;
&lt;/blockquote&gt;
</content>
	</entry>
	
	<entry>
		<title>Really Simple Syndication (d. 2013)</title>
		<link href="https://johnjonesfour.com/2013/03/13/really-simple-syndication-d-2013/"/>
		<updated>2013-03-14T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2013/03/13/really-simple-syndication-d-2013/</id>
		<content type="html">&lt;p&gt;My latest post on insights.adfero.com:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Google declared Really Simple Syndication (a.k.a. RSS) dead yesterday when it announced the discontinuation of their Reader app. RSS found itself struggling for mainstream relevance over the past few years as few outside of news junkies and technophiles understood the value of the obscure format. As a result of the discontinuation, Google Reader and RSS have been tossed into Google’s proverbial dustbin of abandoned technologies with the likes of Google Wave and Orkut.&lt;/p&gt;
&lt;/blockquote&gt;
</content>
	</entry>
	
	<entry>
		<title>TruckToMe Featured on CBS</title>
		<link href="https://johnjonesfour.com/2013/01/07/trucktome-featured-on-cbs/"/>
		<updated>2013-01-08T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2013/01/07/trucktome-featured-on-cbs/</id>
		<content type="html">&lt;p&gt;TruckToMe was featured on Washington&#39;s CBS affiliate:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Whether you’re in Rosslyn craving Korean or near Union Station wishing you had some midday meatballs, a new app can tell you where to find the District’s dozens of food trucks at any given time.&lt;/p&gt;
&lt;/blockquote&gt;
</content>
	</entry>
	
	<entry>
		<title>TruckToMe Featured on DCist</title>
		<link href="https://johnjonesfour.com/2013/01/06/trucktome-featured-on-dcist/"/>
		<updated>2013-01-07T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2013/01/06/trucktome-featured-on-dcist/</id>
		<content type="html">&lt;p&gt;TruckToMe was featured on the local blog &lt;em&gt;DCist&lt;/em&gt; by editor Ben Freed:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Washington&#39;s office workers can&#39;t get enough food trucks, and now there&#39;s another tool by which people looking for a mobile lunch can find their spot. John Jones, a smartphone app developer, launched today a new iOS program called TruckToMe.&lt;/p&gt;
&lt;/blockquote&gt;
</content>
	</entry>
	
	<entry>
		<title>TruckToMe Featured on CellarBlog</title>
		<link href="https://johnjonesfour.com/2013/01/06/trucktome-featured-on-cellarblog/"/>
		<updated>2013-01-07T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2013/01/06/trucktome-featured-on-cellarblog/</id>
		<content type="html">&lt;p&gt;TruckToMe was featured on the local blog &lt;em&gt;CellarBlog&lt;/em&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you live in the DC area, and need more than wine to survive, the TruckToMe app is a must have. Available for the iPhone (iOS 6.1 required), this app is beautiful in its simplicity.&lt;/p&gt;
&lt;/blockquote&gt;
</content>
	</entry>
	
	<entry>
		<title>TruckToMe, My Latest App</title>
		<link href="https://johnjonesfour.com/2012/12/31/trucktome-my-latest-app/"/>
		<updated>2013-01-01T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2012/12/31/trucktome-my-latest-app/</id>
		<content type="html">&lt;p&gt;I&#39;ve just released my latest app, &lt;a href=&quot;https://itunes.apple.com/us/app/trucktome/id588690792?ls=1&amp;amp;mt=8&quot;&gt;TruckToMe&lt;/a&gt;. It&#39;s a food truck locator that is based on the Twitter handles of over 100 DC area food trucks. Here&#39;s an excerpt from the &lt;a href=&quot;http://trucktome.com/2013/01/04/released/&quot;&gt;press release&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Now available on the iTunes App Store, TruckToMe is the best app for finding your favorite food trucks in the Washington DC region. TruckToMe works by watching the Twitter handles of over 100 DC area food trucks and tracks when they check in to new locations and when they mention DC area food truck hot spots such as Union Station and Farragut Square. Unlike other similar apps, TruckToMe provides an easy interface and faster speed that makes finding the best food trucks easy.&lt;/p&gt;
&lt;/blockquote&gt;
</content>
	</entry>
	
	<entry>
		<title>Washington Post Unveils API</title>
		<link href="https://johnjonesfour.com/2012/09/05/washington-post-unveils-api/"/>
		<updated>2012-09-06T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2012/09/05/washington-post-unveils-api/</id>
		<content type="html">&lt;p&gt;My latest post on insights.adfero.com:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This past week, the Washington Post unveiled an API, or Application Programming Interface, for accessing Washington-centric information such speech transcripts, issue information and campaign finance data. An API is a toolkit that allows any developer to leverage data produced by the Washington Post in his or her own program, such as an app or website. This API, once for internal use only at the newspaper, powers tools such as the Campaign Finance Explorer and the White House Visitors Log.&lt;/p&gt;
&lt;/blockquote&gt;
</content>
	</entry>
	
	<entry>
		<title>The New Frontier of Mobile Apps with Mitt Romney’s VP Announcement</title>
		<link href="https://johnjonesfour.com/2012/08/12/new-frontier-mobile-apps-mitt-romneys-vp-announcement/"/>
		<updated>2012-08-13T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2012/08/12/new-frontier-mobile-apps-mitt-romneys-vp-announcement/</id>
		<content type="html">&lt;p&gt;My latest post on insights.adfero.com:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Watching the coverage of Mitt Romney’s just-announced running-mate, Paul Ryan, Saturday morning, I could not help but get excited every time newscasters mentioned “confirmed via the Mitt Romney VP app.” For the first time, an app has been used as an organization’s primary method of pushing news. Not a website, not a Facebook page, but an app was used to break the Apple-like secrecy of Romney’s VP nominee. But did the Romney campaign choose an app just to look more “hip” than the opposition or was there a genuine, strategic purpose for this choice?&lt;/p&gt;
&lt;/blockquote&gt;
</content>
	</entry>
	
	<entry>
		<title>Usability Testing Is Like Proofreading (With Two Tools To Help You Get Started)</title>
		<link href="https://johnjonesfour.com/2012/06/20/usability-testing-is-like-proofreading-with-two-tools-to-help-you-get-started/"/>
		<updated>2012-06-21T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2012/06/20/usability-testing-is-like-proofreading-with-two-tools-to-help-you-get-started/</id>
		<content type="html">&lt;p&gt;My latest post on insights.adfero.com:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Proofreading your own writing invariably leads to missing massive mistakes hidden in plain sight. How many times have you sent that email or filed that story in haste only to later go back and see breathtaking gaffes in grammar and spelling that make you question your very ability to comprehend the English language? This same principle applies to a user experience that has slowly evolved from a straightforward concept in the mockup to a complex, over-revised mess in the final product: potentially large usability problems hidden in plain sight because of project fatigue.&lt;/p&gt;
&lt;/blockquote&gt;
</content>
	</entry>
	
	<entry>
		<title>3 Alternatives to Google Maps</title>
		<link href="https://johnjonesfour.com/2012/06/10/3-alternatives-google-maps/"/>
		<updated>2012-06-11T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2012/06/10/3-alternatives-google-maps/</id>
		<content type="html">&lt;p&gt;My latest post on insights.adfero.com:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;With Apple’s announcement of new mapping software for its iOS line of software today, we are abruptly reminded that, like word processing and file sharing, Google did not invent interactive maps. There is a world of alternative platforms to Google Maps that offer advanced customization and other features Google doesn’t offer in their API. Below is a list of my favorite platforms that can be used to enhance the user experience in any website or mobile app ...&lt;/p&gt;
&lt;/blockquote&gt;
</content>
	</entry>
	
	<entry>
		<title>Content Delivery Networks Demystified</title>
		<link href="https://johnjonesfour.com/2012/03/26/content-delivery-networks-demystified/"/>
		<updated>2012-03-27T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2012/03/26/content-delivery-networks-demystified/</id>
		<content type="html">&lt;p&gt;My latest post on insights.adfero.com:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The worst thing to wake up to on a Saturday morning is a notification that your organization’s web server is down. When that happens, no one can see your website. You’re “off the air,” so to speak. It may seem odd, but your entire website relies on one single point of failure: the server. Whether it is because of a bad hosting vendor, a cyberattack or an unexpected spike in traffic, when that server goes down, public confidence in your organization and brand go down with it.&lt;/p&gt;
&lt;/blockquote&gt;
</content>
	</entry>
	
	<entry>
		<title>Please Stop Overusing QR Codes</title>
		<link href="https://johnjonesfour.com/2011/11/21/please-stop-overusing-qr-codes/"/>
		<updated>2011-11-22T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2011/11/21/please-stop-overusing-qr-codes/</id>
		<content type="html">&lt;p&gt;My latest post on insights.adfero.com:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It’s alright to admit that you’re annoyed by the amount of QR codes used in advertising; we’re all tired of seeing them. Those two dimensional barcodes, or quick response codes (QR Codes), were originally invented to help Toyota track auto parts, but with the advent of high-resolution mobile cameras and the intent of providing consumers with an easy way of accessing information on the go, QR codes are placed on everything from bank ads to cigarette boxes. But, like any fad, they are blindingly too common and implemented too improperly to add any sort of uniqueness to a digital strategy.&lt;/p&gt;
&lt;/blockquote&gt;
</content>
	</entry>
	
	<entry>
		<title>PaperBoy Featured on PennLive</title>
		<link href="https://johnjonesfour.com/2011/03/27/paperboy-featured-on-pennlive/"/>
		<updated>2011-03-28T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2011/03/27/paperboy-featured-on-pennlive/</id>
		<content type="html">&lt;p&gt;PaperBoy was featured on the Harrisburg Patriot&#39;s website, PennLive:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Take Dickinson College senior John Jones. Frustrated over the lack of an app to view the school’s students newspaper, the Dickinsonian (he’s editor in chief), the 21-year-old Schuylkill County native spent winter break programming an app that allows college papers to upload their stories so students can view them in a newspaper-like format.&lt;/p&gt;
&lt;/blockquote&gt;
</content>
	</entry>
	
	<entry>
		<title>PaperBoy Featured on CumberLink</title>
		<link href="https://johnjonesfour.com/2011/03/05/paperboy-featured-on-cumberlink/"/>
		<updated>2011-03-06T00:00:00Z</updated>
		<id>https://johnjonesfour.com/2011/03/05/paperboy-featured-on-cumberlink/</id>
		<content type="html">&lt;p&gt;PaperBoy was featured on the Carlisle Sentinel&#39;s website, CumberLink:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;But thanks to a 21st-century upgrade of the title and role of the paperboy, courtesy of a computer science major at Dickinson College, those problems may be yesterday&#39;s news.&lt;/p&gt;
&lt;/blockquote&gt;
</content>
	</entry>
</feed>
