<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Jake Walker</title><description>Coder, maker, and builder of things</description><link>https://jakew.me/</link><item><title>Revamping a 2DS XL</title><link>https://jakew.me/revamping-a-2ds-xl/</link><guid isPermaLink="true">https://jakew.me/revamping-a-2ds-xl/</guid><description>Modding a New 2DS XL with a bigger battery, USB C port and a sort of failed shell replacement.</description><pubDate>Mon, 16 Mar 2026 18:13:23 GMT</pubDate><content:encoded>&lt;p&gt;Recently, I&apos;ve found myself watching quite a few YouTube videos about various game console repairs and modding. One that stood out to me as a really awesome project was &lt;a href=&quot;https://www.youtube.com/watch?v=rePOAoxBdRQ&amp;#x26;ref=jakew.me&quot; rel=&quot;noreferrer&quot;&gt;Zac Builds&apos; video on building a &quot;New New Nintendo 3DS&quot;&lt;/a&gt;. It&apos;s a good watch if you have time, and goes over a few mods like installing a bigger battery, Bluetooth audio and so on.&lt;/p&gt;&lt;p&gt;That along with other videos, spurred me on to buy a second hand New 2DS XL with the sole purpose of modding it. I thought it would make a great compact travel game console. I have a Switch 2 and Steam Deck which are both great, but they can both take up a little too much bag space sometimes when traveling.&lt;/p&gt;&lt;p&gt;Having had a DS Lite when I was younger, and an original 3DS in my late teen years, the DS family has quite a bit of nostalgia attached for me, and there&apos;s such a big library of games to explore. The nostalgia factor alone was a pretty good reason for me deciding to jump into this mini-project.&lt;/p&gt;&lt;p&gt;From doing a bit of research, I decided I&apos;d install a new battery, swap the shell and install a USB C port.&lt;/p&gt;&lt;h2 id=&quot;choosing-a-model&quot;&gt;Choosing a Model&lt;/h2&gt;&lt;p&gt;Nintendo released quite a few 2DS &amp;#x26; 3DS models over the console&apos;s lifetime, so there&apos;s quite a few to decide between. I was interested in some light emulation, and better performance, so I set my sights on either a New 2DS or New 3DS. The &quot;new&quot; versions both have slightly better CPUs than the original versions, and I also wanted an XL model for the larger screen.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Had I thought about this a couple of months earlier, I could have picked up a Japan &quot;LL&quot; model on my travels which would have been really cool. Although, from what I&apos;ve read the system language can&apos;t be changed from Japanese, even with custom firmware, however games should still work in English.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;In the end I went for a New 2DS XL. I was close to a New 3DS XL, but it was an extra £100 or so, and I wasn&apos;t that interested in the 3D features.&lt;/p&gt;&lt;h2 id=&quot;custom-firmware&quot;&gt;Custom Firmware&lt;/h2&gt;&lt;p&gt;As soon as I got the 2DS, I software modded it with custom firmware. There&apos;s a fantastic guide already out there on &lt;a href=&quot;https://3ds.hacks.guide/?ref=jakew.me&quot; rel=&quot;noreferrer&quot;&gt;3ds.hacks.guide&lt;/a&gt;, so I won&apos;t repeat anything here - plus the instructions can differ depending on your OS version and console model. With Nintendo shutting down their 3DS servers a while back, soft modding is a no-brainer - it brings un-official online multiplayer servers, gives the ability to manage game saves, bypass region-locked games and most importantly homebrew for using emulators and things.&lt;/p&gt;&lt;p&gt;It only took about an hour or so to complete the process and the instructions are super clear and easy to follow.&lt;/p&gt;&lt;p&gt;A few notable things I&apos;ve installed in the homebrew department are:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/jtothebell/fake-08?ref=jakew.me&quot; rel=&quot;noreferrer&quot;&gt;&lt;strong&gt;Fake-08 Emulator&lt;/strong&gt;&lt;/a&gt; - This is an emulator for Pico-8, a &quot;fantasy console&quot; which never existed. It has a retro feel and &quot;retro limitations&quot; like a 128x128 display with 16 colours and a maximum game size of 32k, but it was made in 2015. There&apos;s some amazing free games for this and it has a thriving and creative community which I love. One of my favourite games on there is &lt;a href=&quot;https://www.lexaloffle.com/bbs/?pid=11722&amp;#x26;ref=jakew.me&quot; rel=&quot;noreferrer&quot;&gt;Celeste&lt;/a&gt; (yes, &lt;a href=&quot;https://www.celestegame.com/?ref=jakew.me&quot; rel=&quot;noreferrer&quot;&gt;that Celeste&lt;/a&gt; if you&apos;ve heard it before, but it was the prototype version, made before the full Steam/Switch version), I&apos;m hoping to play the &lt;a href=&quot;https://github.com/ExOK/Celeste2?ref=jakew.me&quot; rel=&quot;noreferrer&quot;&gt;second version&lt;/a&gt; soon.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ctgp-7.github.io/?ref=jakew.me&quot; rel=&quot;noreferrer&quot;&gt;CTGP-7&lt;/a&gt; - a modpack for Mario Kart 7 which adds tons of new tracks, characters and more.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/DS-Homebrew/TWiLightMenu?ref=jakew.me&quot; rel=&quot;noreferrer&quot;&gt;TWiLight Menu++&lt;/a&gt; - a launcher with support for original DS games and a bunch of emulators.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://pretendo.network/?ref=jakew.me&quot; rel=&quot;noreferrer&quot;&gt;Pretendo Network&lt;/a&gt; - a project aiming to replace the now shut-down Nintendo online network, enabling online play for supported games.&lt;/li&gt;&lt;/ul&gt;&lt;figure class=&quot;kg-card kg-image-card kg-card-hascaption&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2026/03/IMG_1256.jpeg&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;2667&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2026/03/IMG_1256.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2026/03/IMG_1256.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2026/03/IMG_1256.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2026/03/IMG_1256.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;figcaption&gt;&lt;span style=&quot;white-space: pre-wrap;&quot;&gt;2DS XL running Celeste Classic on Pico-8 Emulator&lt;/span&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;h2 id=&quot;shell-swap&quot;&gt;Shell Swap&lt;/h2&gt;&lt;p&gt;The 2DS I picked up was definitely well-loved and came with a bit of a grimy shell and buttons, even after giving it a thorough deep clean, the case is a bit scratched up and there&apos;s just generally quite a bit of wear all over the shell and buttons. The console I picked up had a black &amp;#x26; blue shell. In the photos below you can see a few scratches on the exterior of the shell, especially on the bottom.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-gallery-card kg-width-wide&quot;&gt;&lt;div class=&quot;kg-gallery-container&quot;&gt;&lt;div class=&quot;kg-gallery-row&quot;&gt;&lt;div class=&quot;kg-gallery-image&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2026/03/IMG_1218.jpeg&quot; width=&quot;2000&quot; height=&quot;2667&quot; loading=&quot;lazy&quot; alt=&quot;&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2026/03/IMG_1218.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2026/03/IMG_1218.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2026/03/IMG_1218.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2026/03/IMG_1218.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/div&gt;&lt;div class=&quot;kg-gallery-image&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2026/03/IMG_1219.jpeg&quot; width=&quot;2000&quot; height=&quot;2667&quot; loading=&quot;lazy&quot; alt=&quot;&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2026/03/IMG_1219.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2026/03/IMG_1219.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2026/03/IMG_1219.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2026/03/IMG_1219.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;p&gt;I was able to find a new shell on AliExpress for £15 and in theory, it&apos;s a super easy way to give the console a new look and feel. I&apos;d have loved to get a clear or atomic purple one, but unfortunately there aren&apos;t any mass produced ones in colours other than the original.&lt;/p&gt;&lt;p&gt;Once the new shell arrived, I got to work moving all the internals across. After doing the full swap which took multiple hours, I found out right at the end that the case didn&apos;t snap together properly and meant the display couldn&apos;t open fully. The bottom part of the shell is comprised of two parts - the top which houses the buttons and touch screen, and the bottom part which the top snaps into. On the third-party shell, this didn&apos;t quite meet up properly. After doing some further looking into this I found many people on Reddit mentioning that third-party shells are usually very hit-or-miss.&lt;/p&gt;&lt;p&gt;In hindsight, I would have never attempted the shell swap - it was a huge pain and I did nearly break both screens trying to remove them. It&apos;s really not worth it even with a really beaten up original shell - it&apos;s much better to stick with the original and perhaps apply some vinyl skins or something. The build quality of third-party replacements, at least for the 2DS XL, really isn&apos;t there like you&apos;d find for Switch shells or other consoles.&lt;/p&gt;&lt;p&gt;On the bright side, after disassembling and reassembling the console two or three times, I&apos;m really familiar with how everything fits together, and it would be no trouble to do repairs in the future.&lt;/p&gt;&lt;p&gt;Regardless, I&apos;ll briefly walk through how I did the shell swap as I think it&apos;s pretty cool to see the internals. This is not designed to be a guide though, and I&apos;d recommend making use of the iFixit guides and YouTube videos as your primary reference if you&apos;re doing this yourself!&lt;/p&gt;&lt;p&gt;iFixit has some great guides, as usual, on the teardown process and common repairs which I roughly followed for disassembling the console. I believe they are made by the community, so there&apos;s a few details missing which I had to figure out for myself.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-bookmark-card&quot;&gt;&lt;a class=&quot;kg-bookmark-container&quot; href=&quot;https://www.ifixit.com/Device/New_Nintendo_2DS_XL?ref=jakew.me&quot;&gt;&lt;div class=&quot;kg-bookmark-content&quot;&gt;&lt;div class=&quot;kg-bookmark-title&quot;&gt;New Nintendo 2DS XL Repair Help: Learn How to Fix It Yourself.&lt;/div&gt;&lt;div class=&quot;kg-bookmark-description&quot;&gt;Released on July 28th, 2017, The New Nintendo 2DS XL is a technologically-improved remodel of the 2DS handheld. It is the ninth and last redesign of the Nintendo DS line, The console is much larger than the original 2DS model and adds all the features of the updated ‘New 3DS’ line of systems, minus the ability to play games in 3D on the top screen.&lt;/div&gt;&lt;div class=&quot;kg-bookmark-metadata&quot;&gt;&lt;img class=&quot;kg-bookmark-icon&quot; src=&quot;https://ghost.jakew.me/content/images/icon/apple-touch-icon-180x180.png&quot; alt=&quot;&quot;&gt;&lt;span class=&quot;kg-bookmark-author&quot;&gt;iFixit&lt;/span&gt;&lt;span class=&quot;kg-bookmark-publisher&quot;&gt;Reid Fleishman&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;kg-bookmark-thumbnail&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/thumbnail/hFpV4LDtSxvHrnwJ.medium&quot; alt=&quot;&quot; onerror=&quot;this.style.display = &amp;#x27;none&amp;#x27;&quot;&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p&gt;To start off, I followed &lt;a href=&quot;https://www.ifixit.com/Guide/New+Nintendo+2DS+XL+Front+Buttons+Replacement/98311?ref=jakew.me&quot; rel=&quot;noreferrer&quot;&gt;this guide for front button replacements&lt;/a&gt; which gets us to the point the motherboard is out of the console. The first step of this was gently prying around the gaps in the bottom shell and hinge to release the bottom of the shell. &lt;em&gt;For context, the gap shown in the hinge here is the part of the replacement shell which didn&apos;t snap together properly.&lt;/em&gt;&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2026/03/IMG_1220.jpeg&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;2667&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2026/03/IMG_1220.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2026/03/IMG_1220.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2026/03/IMG_1220.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2026/03/IMG_1220.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;With the back of the shell removed, I gently lifted the case and disconnected the ribbon connector for the back cameras and connectors for both speakers.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2026/03/IMG_1221.jpeg&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;1165&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2026/03/IMG_1221.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2026/03/IMG_1221.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2026/03/IMG_1221.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2026/03/IMG_1221.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;With the back part removed, we&apos;re down to the motherboard now. I removed the battery which was well stuck down and disconnected all of the ribbon cables. &lt;em&gt;Important note: two of the connectors are friction-fit and trying to remove the locking tab like you would normally can damage them! There&apos;s more info on this in the iFixit guide.&lt;/em&gt;&lt;/p&gt;&lt;figure class=&quot;kg-card kg-gallery-card kg-width-wide&quot;&gt;&lt;div class=&quot;kg-gallery-container&quot;&gt;&lt;div class=&quot;kg-gallery-row&quot;&gt;&lt;div class=&quot;kg-gallery-image&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2026/03/IMG_1222-2.jpeg&quot; width=&quot;2000&quot; height=&quot;2667&quot; loading=&quot;lazy&quot; alt=&quot;&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2026/03/IMG_1222-2.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2026/03/IMG_1222-2.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2026/03/IMG_1222-2.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2026/03/IMG_1222-2.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/div&gt;&lt;div class=&quot;kg-gallery-image&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2026/03/IMG_1223.jpeg&quot; width=&quot;2000&quot; height=&quot;1500&quot; loading=&quot;lazy&quot; alt=&quot;&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2026/03/IMG_1223.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2026/03/IMG_1223.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2026/03/IMG_1223.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2026/03/IMG_1223.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/div&gt;&lt;div class=&quot;kg-gallery-image&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2026/03/IMG_1224.jpeg&quot; width=&quot;2000&quot; height=&quot;2667&quot; loading=&quot;lazy&quot; alt=&quot;&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2026/03/IMG_1224.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2026/03/IMG_1224.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2026/03/IMG_1224.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2026/03/IMG_1224.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;p&gt;With the motherboard removed, I can take off the plastic piece on the back of the bottom touchscreen. Following the &lt;a href=&quot;https://www.ifixit.com/Guide/New+Nintendo+2DS+XL+Touchscreen+Replacement/98314?ref=jakew.me&quot; rel=&quot;noreferrer&quot;&gt;iFixit guide for touchscreen replacement&lt;/a&gt;, I gently pried on the screen to remove it from the bottom case as it&apos;s stuck down with adhesive. However, in the process I managed to separate the digitizer from the screen. I later found out you can gently push the screen from the bottom to remove it from the shell which would have kept the digitizer and screen as one, so I&apos;d recommend doing it that way. I reused the adhesive for the screen in the new shell, but it would have been sensible to replace it with some thin double-sided tape as the adhesive I removed got super tangled once I pulled it off the old shell. I honestly got a bit freaked out when I separated the digitizer from the screen and thought I might have broken it - but luckily didn&apos;t!&lt;/p&gt;&lt;p&gt;Below you can see the process of me first installing the digitizer, then the screen into the new shell.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-gallery-card kg-width-wide&quot;&gt;&lt;div class=&quot;kg-gallery-container&quot;&gt;&lt;div class=&quot;kg-gallery-row&quot;&gt;&lt;div class=&quot;kg-gallery-image&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2026/03/IMG_1225.jpeg&quot; width=&quot;2000&quot; height=&quot;2667&quot; loading=&quot;lazy&quot; alt=&quot;&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2026/03/IMG_1225.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2026/03/IMG_1225.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2026/03/IMG_1225.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2026/03/IMG_1225.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/div&gt;&lt;div class=&quot;kg-gallery-image&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2026/03/IMG_1226.jpeg&quot; width=&quot;2000&quot; height=&quot;2667&quot; loading=&quot;lazy&quot; alt=&quot;&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2026/03/IMG_1226.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2026/03/IMG_1226.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2026/03/IMG_1226.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2026/03/IMG_1226.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/div&gt;&lt;div class=&quot;kg-gallery-image&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2026/03/IMG_1227.jpeg&quot; width=&quot;2000&quot; height=&quot;2667&quot; loading=&quot;lazy&quot; alt=&quot;&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2026/03/IMG_1227.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2026/03/IMG_1227.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2026/03/IMG_1227.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2026/03/IMG_1227.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;p&gt;The buttons were pretty easy to swap over, and I just used the new purple front buttons that came with the new shell.&lt;/p&gt;&lt;p&gt;Unfortunately from here it gets even harder! With the touch screen removed from the old shell, I had to separate the top and bottom by removing the small hinge mechanism. The tiny hinge mechanism is just under the red antenna wire you can see here. It&apos;s installed super tight and honestly took an unreasonable amount of force to get it out. The best technique I found to work in the end was from &lt;a href=&quot;https://youtu.be/QqJsyRShcjM?si=-gSl52p0pLiJEdll&amp;#x26;t=478&amp;#x26;ref=jakew.me&quot; rel=&quot;noreferrer&quot;&gt;this YouTube video&lt;/a&gt; where you use a flat head screwdriver to lever it out.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2026/03/IMG_1228.jpeg&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;1500&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2026/03/IMG_1228.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2026/03/IMG_1228.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2026/03/IMG_1228.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2026/03/IMG_1228.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;With the top screen removed, I proceeded to take off the plastic piece from the back of the screen to reveal screws - taking these out and prying around the edge of the screen gets us to the point we have the back of the screen exposed. At this point I incorrectly pried around the edge of the screen and started peeling up the backlight. Again, not sure how I didn&apos;t end up breaking this screen! The screen is actually stuck to the front plastic &quot;lens&quot; piece, so we just need to take off this lens, keeping the screen attached. Below you can see the screen installed in the housing, and after just the screen on the lens.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-gallery-card kg-width-wide&quot;&gt;&lt;div class=&quot;kg-gallery-container&quot;&gt;&lt;div class=&quot;kg-gallery-row&quot;&gt;&lt;div class=&quot;kg-gallery-image&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2026/03/IMG_1229-1.jpeg&quot; width=&quot;2000&quot; height=&quot;2667&quot; loading=&quot;lazy&quot; alt=&quot;&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2026/03/IMG_1229-1.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2026/03/IMG_1229-1.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2026/03/IMG_1229-1.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2026/03/IMG_1229-1.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/div&gt;&lt;div class=&quot;kg-gallery-image&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2026/03/IMG_1230.jpeg&quot; width=&quot;2000&quot; height=&quot;2667&quot; loading=&quot;lazy&quot; alt=&quot;&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2026/03/IMG_1230.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2026/03/IMG_1230.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2026/03/IMG_1230.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2026/03/IMG_1230.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;p&gt;With the top screen removed, it&apos;s just a case of doing the same steps in reverse to install it into the new shell. Then I could attach the two halves back together, and re-install the motherboard. I put the buttons from the new shell in here, and also swapped out the top of the joystick for a replacement as it was pretty worn. On the bottom piece of the shell, I installed the new trigger buttons and moved over the springs, retaining plastic pieces and cameras.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-gallery-card kg-width-wide&quot;&gt;&lt;div class=&quot;kg-gallery-container&quot;&gt;&lt;div class=&quot;kg-gallery-row&quot;&gt;&lt;div class=&quot;kg-gallery-image&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2026/03/IMG_1231.jpeg&quot; width=&quot;2000&quot; height=&quot;2667&quot; loading=&quot;lazy&quot; alt=&quot;&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2026/03/IMG_1231.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2026/03/IMG_1231.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2026/03/IMG_1231.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2026/03/IMG_1231.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/div&gt;&lt;div class=&quot;kg-gallery-image&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2026/03/IMG_1232.jpeg&quot; width=&quot;2000&quot; height=&quot;2667&quot; loading=&quot;lazy&quot; alt=&quot;&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2026/03/IMG_1232.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2026/03/IMG_1232.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2026/03/IMG_1232.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2026/03/IMG_1232.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/div&gt;&lt;div class=&quot;kg-gallery-image&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2026/03/IMG_1233.jpeg&quot; width=&quot;2000&quot; height=&quot;2667&quot; loading=&quot;lazy&quot; alt=&quot;&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2026/03/IMG_1233.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2026/03/IMG_1233.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2026/03/IMG_1233.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2026/03/IMG_1233.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;p&gt;And with that, I reinstalled the battery, ribbon connectors and so on and I was ready to put the console back together.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2026/03/IMG_1234.jpeg&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;2667&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2026/03/IMG_1234.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2026/03/IMG_1234.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2026/03/IMG_1234.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2026/03/IMG_1234.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;Once I had it all back together, as mentioned earlier, I found it didn&apos;t snap together properly and was really gutted! To salvage the project, I swapped the bottom part of the shell back to the original as this was where the problem was. The top screen I left in the new shell as I was a bit traumatised by nearly breaking it and had slightly broken the original shell. I completely disassembled the bottom part again, and rebuilt it in the old shell, but keeping the new purple front buttons and triggers. In the end I have quite a cool looking black and purple console. I&apos;m not a huge fan of the silver on the top screen, but with the original being blue, I felt the silver matched the aesthetic better.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-gallery-card kg-width-wide&quot;&gt;&lt;div class=&quot;kg-gallery-container&quot;&gt;&lt;div class=&quot;kg-gallery-row&quot;&gt;&lt;div class=&quot;kg-gallery-image&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2026/03/IMG_1257.jpeg&quot; width=&quot;2000&quot; height=&quot;2667&quot; loading=&quot;lazy&quot; alt=&quot;&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2026/03/IMG_1257.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2026/03/IMG_1257.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2026/03/IMG_1257.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2026/03/IMG_1257.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/div&gt;&lt;div class=&quot;kg-gallery-image&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2026/03/IMG_1258.jpeg&quot; width=&quot;2000&quot; height=&quot;1500&quot; loading=&quot;lazy&quot; alt=&quot;&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2026/03/IMG_1258.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2026/03/IMG_1258.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2026/03/IMG_1258.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2026/03/IMG_1258.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;h2 id=&quot;battery-swap&quot;&gt;Battery Swap&lt;/h2&gt;&lt;p&gt;The new battery hadn&apos;t arrived in time for when I started on the shell swap, so I stuck with the old battery and installed the new one when it arrived, luckily not much disassembly is required for the install. It&apos;s the same size so fits in the same battery slot, but increases the capacity from 1,300 mAh to 2,000 mAh. That&apos;s ~1.5x larger so it should help a lot with battery life, plus with the console being at least a few years old, the old battery has probably lost some capacity from when it was new.&lt;/p&gt;&lt;h2 id=&quot;usb-c-mod&quot;&gt;USB C Mod&lt;/h2&gt;&lt;p&gt;My final mod for now, is swapping the charging port to a USB C connector. This is probably one of the best quality-of-life improvements as it means when travelling there&apos;s not an extra power adapter to carry round, and means you can charge the console from a power bank.&lt;/p&gt;&lt;p&gt;To begin, I removed the motherboard from the 2DS and applied some Kapton tape to the components around the connector, just to protect them from solder and heat. I used a bit of a cereal box as a soldering mat just to catch any solder bits and flux.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2026/03/IMG_1235.jpeg&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;2667&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2026/03/IMG_1235.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2026/03/IMG_1235.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2026/03/IMG_1235.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2026/03/IMG_1235.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;With the port taped off, I used a mixture of solder wick and a solder sucker to take off the old port. A hot air gun would have been a massive help here, but unfortunately I don&apos;t have one. I cleaned up the excess solder with more solder wick and cleaned off the area with isopropyl alcohol to remove the black bit shown below.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2026/03/IMG_1236.jpeg&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;2667&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2026/03/IMG_1236.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2026/03/IMG_1236.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2026/03/IMG_1236.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2026/03/IMG_1236.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;At this point, I double checked the connections to sanity check the part I bought and that I was putting it on the right way. I used a multimeter with one side on the game card port shield, and the other on each pad of the connector to see which pads were ground. I found the small arrow on the port points to the pin which carries voltage.&lt;/p&gt;&lt;p&gt;Next I applied some flux to the pads to help with the soldering and installed the port. I did some checking with the multimeter again to check for shorts, and I did find that the power and ground pins were shorted, although couldn&apos;t see just by looking, so I reflowed the joints which fixed the problem.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-gallery-card kg-width-wide&quot;&gt;&lt;div class=&quot;kg-gallery-container&quot;&gt;&lt;div class=&quot;kg-gallery-row&quot;&gt;&lt;div class=&quot;kg-gallery-image&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2026/03/IMG_1238-1.jpeg&quot; width=&quot;2000&quot; height=&quot;1500&quot; loading=&quot;lazy&quot; alt=&quot;&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2026/03/IMG_1238-1.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2026/03/IMG_1238-1.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2026/03/IMG_1238-1.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2026/03/IMG_1238-1.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/div&gt;&lt;div class=&quot;kg-gallery-image&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2026/03/IMG_1239.jpeg&quot; width=&quot;2000&quot; height=&quot;1500&quot; loading=&quot;lazy&quot; alt=&quot;&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2026/03/IMG_1239.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2026/03/IMG_1239.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2026/03/IMG_1239.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2026/03/IMG_1239.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;p&gt;With the port installed, I had to do some modification to the battery bracket as the USB C port is wider than the original charging port. I shaved off the part of the bracket that sits around the charging port. You can see where I did this below with the original left bracket and new bracket on the right. I also had to gently sand down the port on the bottom shell just so the new port would fit properly.&lt;/p&gt;&lt;p&gt;With the new port being a lot thinner than the original, there&apos;s a bunch of gap round it vertically which is a bit of a shame. I&apos;ve seen people install the port elsewhere on the board, like next to the SD card slot, or in place of the IR LED, but I thought I&apos;d just go for the full swap.&lt;/p&gt;&lt;p&gt;I was able to design a small 3D printed cover for the port just to clean it up a little.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-gallery-card kg-width-wide&quot;&gt;&lt;div class=&quot;kg-gallery-container&quot;&gt;&lt;div class=&quot;kg-gallery-row&quot;&gt;&lt;div class=&quot;kg-gallery-image&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2026/03/IMG_1240.jpeg&quot; width=&quot;2000&quot; height=&quot;1450&quot; loading=&quot;lazy&quot; alt=&quot;&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2026/03/IMG_1240.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2026/03/IMG_1240.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2026/03/IMG_1240.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2026/03/IMG_1240.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/div&gt;&lt;div class=&quot;kg-gallery-image&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2026/03/IMG_1241.jpeg&quot; width=&quot;2000&quot; height=&quot;1563&quot; loading=&quot;lazy&quot; alt=&quot;&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2026/03/IMG_1241.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2026/03/IMG_1241.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2026/03/IMG_1241.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2026/03/IMG_1241.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;kg-gallery-row&quot;&gt;&lt;div class=&quot;kg-gallery-image&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2026/03/IMG_1255.jpeg&quot; width=&quot;2000&quot; height=&quot;1294&quot; loading=&quot;lazy&quot; alt=&quot;&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2026/03/IMG_1255.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2026/03/IMG_1255.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2026/03/IMG_1255.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2026/03/IMG_1255.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/div&gt;&lt;div class=&quot;kg-gallery-image&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2026/03/IMG_1260.jpeg&quot; width=&quot;2000&quot; height=&quot;1410&quot; loading=&quot;lazy&quot; alt=&quot;&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2026/03/IMG_1260.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2026/03/IMG_1260.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2026/03/IMG_1260.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2026/03/IMG_1260.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;p&gt;Here&apos;s a video of me plugging in the console for the first time with the USB C port. As with all builds, it&apos;s bad luck to put it back together before testing, hence the bottom shell being off.&lt;/p&gt;&lt;video src=&quot;https://ghost.jakew.me/content/media/2026/03/Untitled.mp4&quot; controls&gt;&lt;/video&gt;&lt;h2 id=&quot;fin&quot;&gt;Fin&lt;/h2&gt;&lt;p&gt;With that, my mini-project on modding a 2DS XL is all wrapped up. &lt;/p&gt;&lt;p&gt;Having done a shell swap on my original Switch Joycons in the past, it makes for quite a fun project, but in this case it was a bit traumatic with the not so good quality replacement shell, and the difficulty of removing the screens and hinge. Supposedly the 2DS XL is one of the most difficult DS models to disassemble so I can&apos;t recommend doing a shell swap at all.&lt;/p&gt;&lt;p&gt;However, the USB C port is a pretty straightforward mod if you&apos;re comfortable with soldering.&lt;/p&gt;&lt;p&gt;I did also buy a Bluetooth audio module which I may install later on so that I can connect Bluetooth headphones for playing on the go. But for now, I&apos;ve got a unique looking 2DS XL now which will make the perfect travel console for trips.&lt;/p&gt;</content:encoded><author>Jake Walker</author></item><item><title>Automatic Post Notifications with n8n</title><link>https://jakew.me/automatic-post-notifications-with-n8n/</link><guid isPermaLink="true">https://jakew.me/automatic-post-notifications-with-n8n/</guid><description>I&apos;m an avid self-hoster and have had n8n setup for a while but not used it very much. That is until now...

This is a very quick post about how the automated notifications work on my blog with n8n. Probably quite niche, but hopefully useful for some!

I really like static sites, they&apos;re super fast and customisable while giving you the flexibility of using React or other frontend components without any JavaScript getting shipped to the user (unless you want it of course).

However, having had a s</description><pubDate>Sun, 31 Aug 2025 14:30:57 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;m an avid self-hoster and have had &lt;a href=&quot;https://n8n.io/?ref=jakew.me&quot; rel=&quot;noreferrer&quot;&gt;n8n&lt;/a&gt; setup for a while but not used it &lt;em&gt;very&lt;/em&gt; much. That is until now...&lt;/p&gt;&lt;p&gt;This is a very quick post about how the automated notifications work on my blog with n8n. Probably quite niche, but hopefully useful for some!&lt;/p&gt;&lt;p&gt;I really like static sites, they&apos;re super fast and customisable while giving you the flexibility of using React or other frontend components without any JavaScript getting shipped to the user (unless you want it of course). &lt;/p&gt;&lt;p&gt;However, having had a static site in the past, the main way of authoring is with markdown files, which is fine but I never found a decent editor that I enjoy writing in. So I use Ghost for this website&apos;s backend, it&apos;s got a fantasic editing experience. I recently switched my website to use a static site &lt;a href=&quot;https://astro.build/?ref=jakew.me&quot; rel=&quot;noreferrer&quot;&gt;Astro&lt;/a&gt; frontend which pulls posts from Ghost rather than using Ghost directly. I&apos;m hoping the best of both worlds.&lt;/p&gt;&lt;p&gt;&lt;em&gt;You can check out my site&apos;s source code &lt;/em&gt;&lt;a href=&quot;https://github.com/jake-walker/jakew.me?ref=jakew.me&quot; rel=&quot;noreferrer&quot;&gt;&lt;em&gt;here&lt;/em&gt;&lt;/a&gt;&lt;em&gt; by the way.&lt;/em&gt;&lt;/p&gt;&lt;h2 id=&quot;ghost-webhook&quot;&gt;Ghost Webhook&lt;/h2&gt;&lt;p&gt;Ghost allow you to create a webhook (&lt;a href=&quot;https://docs.ghost.org/webhooks?ref=jakew.me&quot; rel=&quot;noreferrer&quot;&gt;docs here&lt;/a&gt;) which can get triggered when something happens, in my case I&apos;m only interested in when a post is published.&lt;/p&gt;&lt;p&gt;In n8n, I have a webhook receiver which is linked up to Ghost, so it triggers when a post is published. The payload contains some info about the post, such as the title and URL. Great!&lt;/p&gt;&lt;p&gt;Before I had Astro, it was easy enough to just use this trigger to create a Bluesky post.&lt;/p&gt;&lt;h2 id=&quot;auto-building&quot;&gt;Auto building&lt;/h2&gt;&lt;p&gt;For my site, I host it with &lt;a href=&quot;https://workers.cloudflare.com/?ref=jakew.me&quot; rel=&quot;noreferrer&quot;&gt;Cloudflare Workers&lt;/a&gt; which is like Cloudflare&apos;s serverless functions offering. I have a mostly a static site, however the contact form, for example, uses some server-side code. Cloudflare recommend using Workers over Pages (which is just static site hosting) now, even for completely static sites.&lt;/p&gt;&lt;p&gt;Cloudflare automatically build my site whenever I commit to the repo, but no commits are made when I publish, so the site doesn&apos;t update when I publish. Sadly Cloudflare doesn&apos;t give you a webhook like they do for Pages for triggering builds manually.&lt;/p&gt;&lt;p&gt;To resolve this, I setup a GitHub Actions workflow in my website repo (&lt;a href=&quot;https://github.com/jake-walker/jakew.me/blob/c0a5a0c4c4870971a5bf9e78dbd3b84ce01222b5/.github/workflows/deploy.yml?ref=jakew.me&quot; rel=&quot;noreferrer&quot;&gt;here&lt;/a&gt;) which is triggered with a &lt;code&gt;workflow_dispatch&lt;/code&gt; event - essentially a manual trigger. Cloudflare build for me normally, so I don&apos;t want to trigger this build for every push &lt;em&gt;although I&apos;ll probably combine them in the end for simplicity but that&apos;s a job for another day.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;So once my n8n workflow receives the new blog post, it will now kick off the GitHub workflow:&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/08/Screenshot-2025-08-23-at-11.56.08-pm.png&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;1108&quot; height=&quot;568&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/08/Screenshot-2025-08-23-at-11.56.08-pm.png 600w, https://ghost.jakew.me/content/images/size/w1000/2025/08/Screenshot-2025-08-23-at-11.56.08-pm.png 1000w, https://ghost.jakew.me/content/images/2025/08/Screenshot-2025-08-23-at-11.56.08-pm.png 1108w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;h2 id=&quot;waiting-for-the-build&quot;&gt;Waiting for the build&lt;/h2&gt;&lt;p&gt;The issue now is that the build can take a few minutes and I want to wait for the site to be live (or at least &lt;em&gt;nearly&lt;/em&gt; live) before sending the notification. Luckily n8n has a cool &quot;wait&quot; node which can pause execution until a webhook has been received. &lt;em&gt;This wait webhook is separate to the initial webhook and is unique for the execution.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;When the workflow is dispatched, the wait webhook is passed in as an input to the workflow.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/08/Screenshot-2025-08-23-at-11.58.55-pm.png&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;1372&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/08/Screenshot-2025-08-23-at-11.58.55-pm.png 600w, https://ghost.jakew.me/content/images/2025/08/Screenshot-2025-08-23-at-11.58.55-pm.png 786w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;I have the wait node configured to only wait for up to an hour, which should be plenty of time, just in case the workflow fails and never calls back.&lt;/p&gt;&lt;h2 id=&quot;sending-the-notification&quot;&gt;Sending the notification&lt;/h2&gt;&lt;p&gt;Once the build completes, the Bluesky notification part is run. The &quot;format post&quot; node runs some JavaScript to create a JSON object to send to Bluesky as there&apos;s a bit of processing required to get it into the right format. This node can reach back to grab context from the initial webhook for all the post information.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/08/Screenshot-2025-08-24-at-12.01.38-am.png&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;411&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/08/Screenshot-2025-08-24-at-12.01.38-am.png 600w, https://ghost.jakew.me/content/images/size/w1000/2025/08/Screenshot-2025-08-24-at-12.01.38-am.png 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/08/Screenshot-2025-08-24-at-12.01.38-am.png 1600w, https://ghost.jakew.me/content/images/2025/08/Screenshot-2025-08-24-at-12.01.38-am.png 2024w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;The &quot;create session&quot; node makes a call to the Bluesky API to create a session using my auth token, then the final node creates a new post using the formatted post and the newly created session token.&lt;/p&gt;&lt;p&gt;There&apos;s some &lt;a href=&quot;https://docs.bsky.app/blog/create-post?ref=jakew.me&quot; rel=&quot;noreferrer&quot;&gt;great docs from Bluesky&lt;/a&gt; on how to create posts programatically which I based my n8n stuff off. Here&apos;s the script that builds up the Bluesky post object:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function parseUrls(text) {
  const spans = [];
  // partial/naive URL regex based on: https://stackoverflow.com/a/3809435
  // tweaked to disallow some training punctuation
  const urlRegex = /[$|\W](https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&amp;#x26;//=]*[-a-zA-Z0-9@%_\+~#//=])?)/g;

  let match;
  while ((match = urlRegex.exec(text)) !== null) {
    spans.push({
      start: match.index + match[0].indexOf(match[1]), // Adjust start based on full match
      end: match.index + match[0].indexOf(match[1]) + match[1].length, // Adjust end
      url: match[1],
    });
  }
  return spans;
}

function truncateToTwoSentences(text) {
  if (!text) {
    return &quot;&quot;; // Handle null or undefined input
  }

  const sentences = text.split(/[.?!]/).filter(s =&gt; s.trim() !== &quot;&quot;); // Split into sentences

  if (sentences.length &amp;#x3C;= 2) {
    return text.trim(); // Return original if fewer than 2 sentences, but trim whitespace
  }

  return sentences.slice(0, 2).join(&quot;. &quot;) + &quot;.&quot;; // Join the first two with &quot;. &quot; and add a final &quot;.&quot;
}

// loop over all the webhook inputs (probably just one)
return $(&apos;Webhook&apos;).all().map((item) =&gt; {
  console.log(item);

  // replace the url
  const newUrl = item.json.body.post.current.url.replace(&quot;ghost.jakew.me&quot;, &quot;jakew.me&quot;);
  
  const content = `I&apos;ve just published a new blog post \&quot;${item.json.body.post.current.title}\&quot; - ${newUrl}`;

  // create the actual post object
  return {
    &quot;json&quot;: {
      &quot;post&quot;: {
        &quot;$type&quot;: &quot;app.bsky.feed.post&quot;,
        &quot;text&quot;: content,
        &quot;createdAt&quot;: new Date().toISOString(),
        &quot;embed&quot;: {
          &quot;$type&quot;: &quot;app.bsky.embed.external&quot;,
          &quot;external&quot;: {
            &quot;uri&quot;: newUrl,
            &quot;title&quot;: item.json.body.post.current.title,
            &quot;description&quot;: truncateToTwoSentences(item.json.body.post.current.excerpt)
          }
        },
        &quot;facets&quot;: parseUrls(content).map((u) =&gt; ({
          &quot;index&quot;: {
            &quot;byteStart&quot;: u[&quot;start&quot;],
            &quot;byteEnd&quot;: u[&quot;end&quot;],
          },
          &quot;features&quot;: [
            {
              &quot;$type&quot;: &quot;app.bsky.richtext.facet#link&quot;,
              &quot;uri&quot;: u[&quot;url&quot;],
            }
          ],
        }))
      }
    }
  }
});&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;wrap-up&quot;&gt;Wrap Up&lt;/h2&gt;&lt;p&gt;The final result is a post like this:&lt;/p&gt;
&lt;!--kg-card-begin: html--&gt;
&lt;blockquote class=&quot;bluesky-embed&quot; data-bluesky-uri=&quot;at://did:plc:ywwkf35adpspwj5un2qofcnf/app.bsky.feed.post/3lx42kksamt2v&quot; data-bluesky-cid=&quot;bafyreiaqets6j7lduziu6bpnmucf7xepvj7quesapqdyj5yva32alpb2k4&quot; data-bluesky-embed-color-mode=&quot;system&quot;&gt;&lt;p lang=&quot;&quot;&gt;I&apos;ve just published a new blog post &quot;Light Up Festival Bucket Hat&quot; - https://jakew.me/light-up-festival-bucket-hat/&lt;br&gt;&lt;br&gt;&lt;a href=&quot;https://bsky.app/profile/did:plc:ywwkf35adpspwj5un2qofcnf/post/3lx42kksamt2v?ref_src=embed&amp;#x26;ref=jakew.me&quot;&gt;[image or embed]&lt;/a&gt;&lt;/p&gt;— Jake Walker (&lt;a href=&quot;https://bsky.app/profile/did:plc:ywwkf35adpspwj5un2qofcnf?ref_src=embed&amp;#x26;ref=jakew.me&quot;&gt;@jakew.me&lt;/a&gt;) &lt;a href=&quot;https://bsky.app/profile/did:plc:ywwkf35adpspwj5un2qofcnf/post/3lx42kksamt2v?ref_src=embed&amp;#x26;ref=jakew.me&quot;&gt;23 August 2025 at 23:49&lt;/a&gt;&lt;/blockquote&gt;&lt;script async src=&quot;https://embed.bsky.app/static/embed.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;!--kg-card-end: html--&gt;
&lt;p&gt;Yes, this is a little overkill, but it turned out to be quite a cool little project to create with n8n! Maybe in the future I&apos;ll add Mastodon and other platforms.&lt;/p&gt;&lt;p&gt;I&apos;d highly recommend having a play around with n8n, you can do some crazy complex workflows - I&apos;ve seen one online where people have made fully automated AI slop posting machines.&lt;/p&gt;</content:encoded><author>Jake Walker</author></item><item><title>Light Up Festival Bucket Hat</title><link>https://jakew.me/light-up-festival-bucket-hat/</link><guid isPermaLink="true">https://jakew.me/light-up-festival-bucket-hat/</guid><description>I create a very jank light up bucket hat with individually controllable LEDs for a festival.</description><pubDate>Sat, 23 Aug 2025 21:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;m going to a festival next weekend and one project I always think about doing when I come back from one is a light up bucket hat. The idea is pretty simple, grab a bucket hat, attach an LED strip somehow and write some simple code to display different patterns.&lt;/p&gt;&lt;p&gt;I have thought about this project a little last minute, so I only have a day or so over the weekend to get it complete.&lt;/p&gt;&lt;h2 id=&quot;gathering-the-parts&quot;&gt;Gathering the parts&lt;/h2&gt;&lt;p&gt;Luckily, I have a lot of the parts on hand, which is great for the short deadline I set myself.&lt;/p&gt;&lt;p&gt;For the lights, I&apos;m going to use a strip of WS2812b LEDs which are individually addressable. You can also get a flexible matrix, or fairy light versions and they&apos;re pretty common these days on online maker shops or eBay. For the microcontroller, I have a really small Raspberry Pi Pico on hand which is nice and compact. The LED strip doesn&apos;t require any extra electronics so it can be directly wired to the microcontroller.&lt;/p&gt;&lt;p&gt;Ideally the LED strip would have some extra protection against things like drinks being thrown in the air, or just the potential bumps and stuff that might happen. The best option for this is probably a flexible rubber channel. I found a couple online, but they didn&apos;t have amazing reviews and I didn&apos;t have enough time to order it. It also looks as though it would double as a bit of a diffusion layer too. &lt;em&gt;I won&apos;t link one here, as I don&apos;t want to potentially recommend something without trying it.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;As for powering the project, to make it look really slick looking I&apos;d put a rechargeable battery in the hat, however I&apos;m not sure I trust a battery that I&apos;ve modified right next to my head. So instead I&apos;m going to have a cable going from the hat into a battery bank in my pocket.&lt;/p&gt;&lt;p&gt;With the right microcontroller, you could also make a cool web interface, or have it Bluetooth controlled, or even something more crazy like &lt;a href=&quot;https://meshtastic.org/?ref=jakew.me&quot; rel=&quot;noreferrer&quot;&gt;Meshtastic&lt;/a&gt; or crowd controlled. I stuck with just a button to control it so that I could use my tiny microcontroller on it&apos;s own.&lt;/p&gt;&lt;h2 id=&quot;code&quot;&gt;Code&lt;/h2&gt;&lt;p&gt;I decided to start with the code and basic electronics, just to make sure everything works before I assemble it.&lt;/p&gt;&lt;p&gt;I used a larger Raspberry Pi Pico board that I use a lot for testing things out. I also decided to add in a resistor to the data line of the LEDs just for extra safety - it helps protect the microcontroller from excessive current draw and can prevent reflections of the data signal.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/08/IMG_4615.jpeg&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;1647&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/08/IMG_4615.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2025/08/IMG_4615.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/08/IMG_4615.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2025/08/IMG_4615.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;I got a basic Arduino project setup in Visual Studio Code with &lt;a href=&quot;https://platformio.org/?ref=jakew.me&quot; rel=&quot;noreferrer&quot;&gt;PlatformIO&lt;/a&gt; - I prefer it so much over using the Arduino IDE these days! I put together a simple program with the &lt;a href=&quot;https://fastled.io/?ref=jakew.me&quot; rel=&quot;noreferrer&quot;&gt;FastLED&lt;/a&gt; library.&lt;/p&gt;&lt;p&gt;With this project running of battery power - hopefully for as long as possible - I was trying to put a bit of thought into my code power-efficient. There are simpler (and more power-efficient) ways of driving the LEDs, but FastLED does a lot of heavy lifting when it comes to making good looking lighting effects.&lt;/p&gt;&lt;p&gt;I started off with &lt;a href=&quot;https://fastled.io/docs/df/df7/_blink_8ino-example.html?ref=jakew.me&quot; rel=&quot;noreferrer&quot;&gt;FastLEDs simple blink sketch&lt;/a&gt;, then edited it slightly to use all the LEDs available on my strip.&lt;/p&gt;&lt;p&gt;FastLED also have a bunch of other great sketches which have examples of lighting effects, and I also found this great blog post with a bunch of effects too which I used for inspiration:&lt;/p&gt;&lt;figure class=&quot;kg-card kg-bookmark-card&quot;&gt;&lt;a class=&quot;kg-bookmark-container&quot; href=&quot;https://www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/?ref=jakew.me&quot;&gt;&lt;div class=&quot;kg-bookmark-content&quot;&gt;&lt;div class=&quot;kg-bookmark-title&quot;&gt;Tweaking4All.com - Arduino - LEDStrip effects for NeoPixel and FastLED&lt;/div&gt;&lt;div class=&quot;kg-bookmark-description&quot;&gt;In this article I’ll show you a several LED strip effects for NeoPixel and FastLED. The code works for both libraries and any strip they support (theoretically).&lt;/div&gt;&lt;div class=&quot;kg-bookmark-metadata&quot;&gt;&lt;img class=&quot;kg-bookmark-icon&quot; src=&quot;https://ghost.jakew.me/content/images/icon/apple-touch-icon.png&quot; alt=&quot;&quot;&gt;&lt;span class=&quot;kg-bookmark-author&quot;&gt;Tweaking4All.com&lt;/span&gt;&lt;span class=&quot;kg-bookmark-publisher&quot;&gt;Hans&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;kg-bookmark-thumbnail&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/thumbnail/featured_image_arduino_ws2812_leds.png&quot; alt=&quot;&quot; onerror=&quot;this.style.display = &amp;#x27;none&amp;#x27;&quot;&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p&gt;I also did a little bit of vibe coding to get a bunch more effects built in too.&lt;/p&gt;&lt;p&gt;Here&apos;s the final code I had with 8 different lighting effects, plus an &quot;auto-cycle&quot; mode which loops through all the effects. With just a single button, I had some code which detected long vs. short presses of the button. For certain effects, like the solid colour, it would change the colour when long pressed, and for other effects it would adjust the brightness. Short pressing the button would cycle to the next effect.&lt;/p&gt;
&lt;!--kg-card-begin: html--&gt;
&lt;div class=&quot;not-prose&quot;&gt;&lt;script src=&quot;https://gist.github.com/jake-walker/03f4f63ad9d297d752321dc1afda23d1.js&quot;&gt;&lt;/script&gt;&lt;/div&gt;
&lt;!--kg-card-end: html--&gt;
&lt;h2 id=&quot;assembling&quot;&gt;Assembling&lt;/h2&gt;&lt;p&gt;Once I was happy with the code, I put the hat together.&lt;/p&gt;&lt;p&gt;I wanted to buy a cheap hat which was either black or white, but I wasn&apos;t able to find one locally which I was quite surprised at! My backup plan was a free branded bucket hat which I don&apos;t wear very often.&lt;/p&gt;&lt;p&gt;I started off by unpicking some of the stitches on one of the seams so I could poke the LED strip wires through to the inside.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-gallery-card kg-width-wide&quot;&gt;&lt;div class=&quot;kg-gallery-container&quot;&gt;&lt;div class=&quot;kg-gallery-row&quot;&gt;&lt;div class=&quot;kg-gallery-image&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/08/6312DA0D-E493-42F4-BAF1-FC7C66EC793C_1_105_c.jpeg&quot; width=&quot;768&quot; height=&quot;1024&quot; loading=&quot;lazy&quot; alt=&quot;&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/08/6312DA0D-E493-42F4-BAF1-FC7C66EC793C_1_105_c.jpeg 600w, https://ghost.jakew.me/content/images/2025/08/6312DA0D-E493-42F4-BAF1-FC7C66EC793C_1_105_c.jpeg 768w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/div&gt;&lt;div class=&quot;kg-gallery-image&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/08/FC2ABBE5-DCBB-4ACA-BE89-059E741BBCCB_1_105_c-1.jpeg&quot; width=&quot;768&quot; height=&quot;1024&quot; loading=&quot;lazy&quot; alt=&quot;&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/08/FC2ABBE5-DCBB-4ACA-BE89-059E741BBCCB_1_105_c-1.jpeg 600w, https://ghost.jakew.me/content/images/2025/08/FC2ABBE5-DCBB-4ACA-BE89-059E741BBCCB_1_105_c-1.jpeg 768w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;p&gt;With the wires poked through the hat, I soldered on my protective resistor to the green data line, then put some heat shrink tubing around it to stop it from shorting against the microcontroller. The power line is soldered to the 5v pad and ground to ground. The other end of the resistor can go to any pin.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Side note - this microcontroller - the Tiny 2040 is in fact very small, only about the size of a 50p coin or less! And has also has built-in Neopixel too&lt;/em&gt;&lt;/p&gt;&lt;figure class=&quot;kg-card kg-gallery-card kg-width-wide&quot;&gt;&lt;div class=&quot;kg-gallery-container&quot;&gt;&lt;div class=&quot;kg-gallery-row&quot;&gt;&lt;div class=&quot;kg-gallery-image&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/08/IMG_4616.jpeg&quot; width=&quot;2000&quot; height=&quot;2089&quot; loading=&quot;lazy&quot; alt=&quot;&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/08/IMG_4616.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2025/08/IMG_4616.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/08/IMG_4616.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2025/08/IMG_4616.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/div&gt;&lt;div class=&quot;kg-gallery-image&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/08/IMG_4617.jpeg&quot; width=&quot;2000&quot; height=&quot;1841&quot; loading=&quot;lazy&quot; alt=&quot;&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/08/IMG_4617.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2025/08/IMG_4617.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/08/IMG_4617.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2025/08/IMG_4617.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;p&gt;Finally I coated the microcontroller and it&apos;s wires in a healthy amount of hot glue to keep everything in place and protect against tugs on the wires.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/08/IMG_4618.jpeg&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;1476&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/08/IMG_4618.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2025/08/IMG_4618.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/08/IMG_4618.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2025/08/IMG_4618.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;Next up was sewing the hat. Luckily my mum (hi mum!) taught me to sew when I was younger, but it&apos;s been a couple of years since I have done so. I found some very cheap needles and transparent thread locally. The first parts weren&apos;t very neat, but I got into the swing of it pretty quickly.&lt;/p&gt;&lt;p&gt;First, I reinforced the hole I had made earlier to stop it from getting any bigger, then sewed the LED strip around the outside of the hat.&lt;/p&gt;&lt;p&gt;I should have probably chosen better needles, as I broke 3 of them just sewing this hat, but luckily there were plenty more. The transparent thread made it super difficult to see what I was doing, and I even considered swapping to black or white, but I powered through in the end.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-gallery-card kg-width-wide&quot;&gt;&lt;div class=&quot;kg-gallery-container&quot;&gt;&lt;div class=&quot;kg-gallery-row&quot;&gt;&lt;div class=&quot;kg-gallery-image&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/08/IMG_4619.jpeg&quot; width=&quot;2000&quot; height=&quot;2037&quot; loading=&quot;lazy&quot; alt=&quot;&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/08/IMG_4619.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2025/08/IMG_4619.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/08/IMG_4619.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2025/08/IMG_4619.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/div&gt;&lt;div class=&quot;kg-gallery-image&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/08/IMG_4620.jpeg&quot; width=&quot;2000&quot; height=&quot;2298&quot; loading=&quot;lazy&quot; alt=&quot;&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/08/IMG_4620.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2025/08/IMG_4620.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/08/IMG_4620.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2025/08/IMG_4620.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;p&gt;For the finishing touches, I hot glued the cable very lightly at the hole to keep it in place, and sewed the microcontroller to the underside of the rim using the unused pads.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/08/IMG_4719.jpeg&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;2667&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/08/IMG_4719.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2025/08/IMG_4719.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/08/IMG_4719.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2025/08/IMG_4719.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;I wanted to do this project in such a way where I could reuse the parts for other projects in the future, and ideally get the hat back to normal too - so I tried to be neat and non-destructive with everything. Time will tell, but I think it should be easy enough to unpick the thread keeping the LED strip and microcontroller in place, and pick off the hot glue used everywhere.&lt;/p&gt;&lt;h2 id=&quot;fin&quot;&gt;Fin&lt;/h2&gt;&lt;p&gt;Here&apos;s the finished product! The LEDs get very bright, so I did have to turn down the brightness when I was using it. I got a little bit of use from it, but I think it&apos;ll fit right into &lt;a href=&quot;https://www.emfcamp.org/?ref=jakew.me&quot; rel=&quot;noreferrer&quot;&gt;EMF Camp&lt;/a&gt; when I &lt;em&gt;hopefully &lt;/em&gt;go next year.&lt;/p&gt;&lt;video src=&quot;https://ghost.jakew.me/content/media/2025/08/hat.mp4&quot; controls&gt;&lt;/video&gt;</content:encoded><author>Jake Walker</author></item><item><title>Setting Up Lots of Raspberry Pis</title><link>https://jakew.me/setting-up-lots-of-raspberry-pis/</link><guid isPermaLink="true">https://jakew.me/setting-up-lots-of-raspberry-pis/</guid><description>How to set up a bunch of Raspberry Pis quickly and easily using sdm.</description><pubDate>Wed, 02 Apr 2025 17:00:20 GMT</pubDate><content:encoded>&lt;p&gt;I help out with a &lt;a href=&quot;https://www.stem.org.uk/?ref=jakew.me&quot; rel=&quot;noreferrer&quot;&gt;STEM&lt;/a&gt; club aiming to teach Sixth Form students skills in Cyber Security and other up and coming tech that doesn&apos;t normally get taught in school. I actually used to be in the same club when I was younger so it&apos;s nice to go full circle and help out there!&lt;/p&gt;&lt;p&gt;As part of the club, we need some computers for the students to work on. We managed to find around ten Raspberry Pi 400 computers which we are going to trail for a few sessions. Unfortunately, they are underpowered when compared to a modern laptop but hopefully they&apos;ll work well enough for some simple web browsing, Linux command line and other stuff. In cases we want more power, we might look to use cloud alongside these Raspberry Pis.&lt;/p&gt;&lt;h2 id=&quot;initial-tests&quot;&gt;Initial Tests&lt;/h2&gt;&lt;p&gt;To start, I initially booted up one Pi 400 with it&apos;s existing SD card to get a feel for how web browsing would perform, as well as some other simple tasks. I&apos;ve personally not used the Raspberry Pi OS Desktop for ages, and tend to just go for the Lite versions with no GUI. Unfortunately, web browsing was pretty sluggish with frequent freezes and slow load times.&lt;/p&gt;&lt;p&gt;To speed things up, I tried a 64-bit image which supposedly improves speed (especially for systems with &gt;4GB RAM). Maybe the original image was 32-bit, or a fresh install did the job, but thankfully this improved web browsing to the point it would work for our purposes. I also experimented with a very small overclock to squeeze a little more speed out, going from 1.8 GHz to 2.0 GHz. After that, it now performs fine.&lt;/p&gt;&lt;h2 id=&quot;setting-them-up&quot;&gt;Setting them up&lt;/h2&gt;&lt;p&gt;The default Raspberry Pi OS comes with an initial setup wizard, which is great if you&apos;re setting up 1, but means that I need to go through this wizard 10 times on each of the Pis. Doable, but I&apos;d rather automate it a little more.&lt;/p&gt;&lt;p&gt;My next thought was to write a Rubber Ducky script for my Flipper Zero to automate going through the setup wizard and any other steps we want (e.g. making a non-sudo student account, installing additional software, etc...). It would be a little error prone as it&apos;s not able to get feedback from the OS, but it would have been a good solution.&lt;/p&gt;&lt;p&gt;Eventually, I decided to try and make my own image that we could use in the future for any stuff. I found a couple of tools that did this from &lt;a href=&quot;https://www.packer.io/?ref=jakew.me&quot; rel=&quot;noreferrer&quot;&gt;HashiCorp Packer&lt;/a&gt;, &lt;a href=&quot;https://github.com/mkaczanowski/packer-builder-arm?ref=jakew.me&quot; rel=&quot;noreferrer&quot;&gt;ARM builder&lt;/a&gt;, to a purpose built tool called &lt;a href=&quot;https://github.com/gitbls/sdm?ref=jakew.me&quot;&gt;sdm&lt;/a&gt;. I tried the Packer builder but the image it made didn&apos;t end up booting. I&apos;m sure it&apos;s something I had missed but I didn&apos;t have the time to troubleshoot, so I tried sdm instead.&lt;/p&gt;&lt;p&gt;sdm works really well and each option is configured using a command line argument. For example, creating a user just needs the argument &lt;code&gt;--plugin user:&quot;adduser=student|password=student|nosudo&quot;&lt;/code&gt;. You can also do some fancy things like copying files locally (e.g. config files) to specific places in the image.&lt;/p&gt;&lt;p&gt;To save what I had done, I made a very simple script to generate the image:&lt;/p&gt;&lt;figure class=&quot;kg-card kg-code-card&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash
if [[ $EUID -ne 0 ]]; then
        echo &quot;This script must be run as root&quot; 1&gt;&amp;#x26;2
        exit 1
fi

ADMIN_PASSWORD=&quot;password&quot;

sdm --customize $1 \
        --plugin user:&quot;deluser=pi&quot; \
        --plugin user:&quot;adduser=student|password=student|nosudo&quot; \
        --plugin user:&quot;adduser=admin|password=${ADMIN_PASSWORD}&quot; \
        --plugin raspiconfig:&quot;boot_behaviour=B4&quot; \
        --plugin disables:piwiz \
        --plugin bootconfig:&quot;arm_freq=2000|over_voltage=6&quot; \
        --plugin wificonfig:&quot;country=GB&quot; \
        --plugin L10n:&quot;keymap=gb|locale=en_GB.UTF-8|timezone=Europe/London&quot; \
        --plugin network:&quot;ssh=service&quot; \
        --plugin copyfile:&quot;from=authorized_keys|to=/home/admin/.ssh/|chown=admin:admin|chmod=600|runphase=postinstall|mkdirif&quot; \
        --plugin copyfile:&quot;from=lightdm.conf|to=/etc/lightdm/|chown=root:root|chmod=644|runphase=postinstall&quot; \
        --plugin copyfile:&quot;from=05-setup.sh|to=/etc/sdm/0piboot/|chown=root:root|chmod=744|runphase=postinstall&quot; \
        --restart --bootscripts&lt;/code&gt;&lt;/pre&gt;&lt;figcaption&gt;&lt;p&gt;&lt;code spellcheck=&quot;false&quot; style=&quot;white-space: pre-wrap;&quot;&gt;&lt;span&gt;generate.sh&lt;/span&gt;&lt;/code&gt;&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;The above command does the following (in order):&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Delete&apos;s the default &lt;code&gt;pi&lt;/code&gt; user (although I don&apos;t think it actually exists anymore, so this may be redundant).&lt;/li&gt;&lt;li&gt;Adds an &lt;code&gt;admin&lt;/code&gt; and non-sudo &lt;code&gt;student&lt;/code&gt; users.&lt;/li&gt;&lt;li&gt;Sets the boot behaviour to auto-login to the desktop (as opposed to going to the console, or just going to the desktop).&lt;/li&gt;&lt;li&gt;Disables the setup wizard (&lt;code&gt;piwiz&lt;/code&gt;).&lt;/li&gt;&lt;li&gt;Applies the overclock to &lt;code&gt;/boot/config.txt&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;Sets the Wi-Fi region, keyboard, locale and timezone to the UK.&lt;/li&gt;&lt;li&gt;Enable SSH - for any bulk setting up later on (e.g. with Ansible).&lt;/li&gt;&lt;li&gt;Copy my SSH key to the relevant location so I can login over SSH without a password.&lt;/li&gt;&lt;li&gt;Copy the &lt;code&gt;lightdm.conf&lt;/code&gt; file which is setup to automatically login the &lt;code&gt;student&lt;/code&gt; user.&lt;/li&gt;&lt;li&gt;A custom script to set the hostname to a random value.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;This image worked like a charm, helping save me from lots of setup, and it&apos;ll be a great base to build off in the future.&lt;/p&gt;&lt;h2 id=&quot;whats-next&quot;&gt;What&apos;s next?&lt;/h2&gt;&lt;p&gt;If all goes well, my plan is to implement PiServer, a network boot server where each Pi is a thin-client. This means all the users and their data are in one central location. I think this will make it much easier to add new software, distribute files, etc... further down the line. At the moment each Pi would have to be configured individually for anything like that.&lt;/p&gt;</content:encoded><author>Jake Walker</author></item><item><title>Making a DJ Macro Pad</title><link>https://jakew.me/making-a-dj-macro-pad/</link><guid isPermaLink="true">https://jakew.me/making-a-dj-macro-pad/</guid><description>Building a custom macro pad to add missing buttons to my DJ controller.</description><pubDate>Fri, 28 Mar 2025 19:00:53 GMT</pubDate><content:encoded>&lt;p&gt;Recently, I&apos;ve picked up a new hobby - DJing. I find it&apos;s a great way to enjoy music, and it&apos;s pretty fun to learn too.&lt;/p&gt;&lt;p&gt;I&apos;ve got a great DJ controller, but I find it&apos;s missing a couple of buttons and knobs for extra functions - especially post effects which some other brands of controller do have. To fix that I&apos;ve been wanting to make a small &apos;add-on&apos; controller that has remappable buttons and knobs. I guess it&apos;s essentially a macro pad.&lt;/p&gt;&lt;h2 id=&quot;design&quot;&gt;Design&lt;/h2&gt;&lt;p&gt;For this project, I started out not really having much of a plan, but I knew I wanted to have a bunch of knobs (or potentiometers) to have analog control over effects, and a few buttons to turn effects on and off, or do other functions too. I decided that 6 potentiometers and 8 buttons would be plenty.&lt;/p&gt;&lt;p&gt;For the microcontroller, the brains behind the project, I went for a Seeed Studio XIAO RP2040. I liked the form factor of this from one of my previous projects and like the RP2040 in general. Although as you&apos;ll see below on the schematic, this microcontroller doesn&apos;t have many pins, but more on that later...&lt;/p&gt;&lt;p&gt;Then what is an electronics project without RGB lights? So I added WS2812 LEDs for each of the buttons which can show the status of each. These LEDs take in power and a single data line for controlling the colour. They daisy chain together, so you can have a big long line of LEDs that are individually addressable (i.e. you can change the colour of each individually), still with just one data line from the microcontroller. Pretty neat! I also added in a resistor for the power line, as it was suggested best practice on the datasheet, although it would still work fine without.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/03/Screenshot-2025-03-25-at-1.13.58-pm.png&quot; class=&quot;kg-image&quot; alt=&quot;KiCad schematic showing LEDs&quot; loading=&quot;lazy&quot; width=&quot;346&quot; height=&quot;558&quot;&gt;&lt;/figure&gt;&lt;p&gt;Back to the pins problem. I could have chosen a bigger microcontroller, but even with something a little bigger, I might have struggled for pins, with 14 being needed for all the inputs. After a bit of research, I found that using some multiplexers would help me out here. I&apos;m glad I got to try them out because they&apos;re pretty cool. For the potentiometers, I used a CD4051B chip which uses 3 pins on the microcontroller to set an address. This address then sets which input is given to the microcontroller. So the 6 potentiometers are hooked up to the chip, and the chip only uses 4 pins on the microcontroller (although I could have had an extra 2).&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/03/Screenshot-2025-03-25-at-1.15.07-pm.png&quot; class=&quot;kg-image&quot; alt=&quot;KiCad schematic showing 6 potentiometers connected to a multiplexer chip&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;728&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/03/Screenshot-2025-03-25-at-1.15.07-pm.png 600w, https://ghost.jakew.me/content/images/size/w1000/2025/03/Screenshot-2025-03-25-at-1.15.07-pm.png 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/03/Screenshot-2025-03-25-at-1.15.07-pm.png 1600w, https://ghost.jakew.me/content/images/size/w2400/2025/03/Screenshot-2025-03-25-at-1.15.07-pm.png 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;For the buttons, I used a 74HC165 shift register which takes the button inputs (in parallel) and sends them to the microcontroller over serial. This takes up only 3 extra pins on the microcontroller for the 8 inputs. When I was looking into using the shift register, I found some conflicting advice on how to hook it up, so I added in some jumpers that I could connect or disconnect later on if the way I had wired it up didn&apos;t work. (But spoiler alert, it was fine in the end).&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/03/Screenshot-2025-03-25-at-1.16.09-pm.png&quot; class=&quot;kg-image&quot; alt=&quot;KiCad schematic showing 8 buttons connected to a shift register chip&quot; loading=&quot;lazy&quot; width=&quot;1466&quot; height=&quot;1010&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/03/Screenshot-2025-03-25-at-1.16.09-pm.png 600w, https://ghost.jakew.me/content/images/size/w1000/2025/03/Screenshot-2025-03-25-at-1.16.09-pm.png 1000w, https://ghost.jakew.me/content/images/2025/03/Screenshot-2025-03-25-at-1.16.09-pm.png 1466w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;For good measure and future-proofing, I also added in a Grove connector, which will let me plug in I2C devices. The idea being if I needed even more inputs, I could add another board that plugs in.&lt;/p&gt;&lt;p&gt;Here&apos;s my final schematic for all of that. It&apos;s a bit messy!&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/03/djaddon.svg&quot; class=&quot;kg-image&quot; alt=&quot;Complete KiCad schematic&quot; loading=&quot;lazy&quot; width=&quot;1123&quot; height=&quot;794&quot;&gt;&lt;/figure&gt;&lt;p&gt;I would normally test on a breadboard at this stage to make sure I have all the connections right, and it would have meant avoiding adding the jumpers. However, I didn&apos;t have a lot of the parts on hand, so I took a risk and designed the PCB.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-embed-card&quot;&gt;&lt;blockquote class=&quot;bluesky-embed&quot; data-bluesky-uri=&quot;at://did:plc:ywwkf35adpspwj5un2qofcnf/app.bsky.feed.post/3lipn2e36fc2k&quot; data-bluesky-cid=&quot;bafyreigtplbrtfn6nf6hjhfktmz2yayyk543yinpngbuadiltfz6nlndji&quot;&gt;&lt;p lang=&quot;en&quot;&gt;Some casual Friday night PCB designing 😎. This is the most complicated I&apos;ve made for while so this routing is pretty tricky&lt;/p&gt;— &lt;a href=&quot;https://bsky.app/profile/did:plc:ywwkf35adpspwj5un2qofcnf?ref_src=embed&amp;#x26;ref=jakew.me&quot;&gt;Jake Walker (@jakew.me)&lt;/a&gt; &lt;a href=&quot;https://bsky.app/profile/did:plc:ywwkf35adpspwj5un2qofcnf/post/3lipn2e36fc2k?ref_src=embed&amp;#x26;ref=jakew.me&quot;&gt;2025-02-21T20:22:41.250Z&lt;/a&gt;&lt;/blockquote&gt;&lt;script async src=&quot;https://embed.bsky.app/static/embed.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;&lt;/figure&gt;&lt;p&gt;It took me a good few hours to get everything routed, and it was certainly one of the more complex boards I&apos;ve designed for a while. But I didn&apos;t help myself by wanting to put the resistors all in a row in the middle and the two chips either side.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/03/Screenshot-2025-03-11-at-10.59.50-pm.png&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;789&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/03/Screenshot-2025-03-11-at-10.59.50-pm.png 600w, https://ghost.jakew.me/content/images/size/w1000/2025/03/Screenshot-2025-03-11-at-10.59.50-pm.png 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/03/Screenshot-2025-03-11-at-10.59.50-pm.png 1600w, https://ghost.jakew.me/content/images/size/w2400/2025/03/Screenshot-2025-03-11-at-10.59.50-pm.png 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;After having tried it out, I would have spaced or positioned the potentiometers differently to give a bit more room as they&apos;re a little cramped. Although I&apos;ve got some ideas for 3D printed parts that may help.&lt;/p&gt;&lt;p&gt;If you want to make your own, below is a list of the parts I used as well as a download of the KiCad project, but if you check out my &lt;a href=&quot;https://jakew.me/how-to-create-pcbs/&quot; rel=&quot;noreferrer&quot;&gt;previous post on designing PCBs&lt;/a&gt;, you could make your own which is definitely the more fun route!&lt;/p&gt;&lt;div class=&quot;kg-card kg-file-card&quot;&gt;&lt;a class=&quot;kg-file-card-container&quot; href=&quot;https://jakew.me/content/files/2025/03/dj-boost.zip&quot; title=&quot;Download&quot; download&gt;&lt;div class=&quot;kg-file-card-contents&quot;&gt;&lt;div class=&quot;kg-file-card-title&quot;&gt;KiCad Project Files&lt;/div&gt;&lt;div class=&quot;kg-file-card-caption&quot;&gt;&lt;/div&gt;&lt;div class=&quot;kg-file-card-metadata&quot;&gt;&lt;div class=&quot;kg-file-card-filename&quot;&gt;dj-boost.zip&lt;/div&gt;&lt;div class=&quot;kg-file-card-filesize&quot;&gt;163 KB&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;kg-file-card-icon&quot;&gt;&lt;svg viewBox=&quot;0 0 24 24&quot;&gt;&lt;defs&gt;&lt;style&gt;.a{fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5px;}&lt;/style&gt;&lt;/defs&gt;&lt;title&gt;download-circle&lt;/title&gt;&lt;polyline class=&quot;a&quot; points=&quot;8.25 14.25 12 18 15.75 14.25&quot;&gt;&lt;/polyline&gt;&lt;line class=&quot;a&quot; x1=&quot;12&quot; y1=&quot;6.75&quot; x2=&quot;12&quot; y2=&quot;18&quot;&gt;&lt;/line&gt;&lt;circle class=&quot;a&quot; cx=&quot;12&quot; cy=&quot;12&quot; r=&quot;11.25&quot;&gt;&lt;/circle&gt;&lt;/svg&gt;&lt;/div&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class=&quot;kg-card kg-toggle-card&quot; data-kg-toggle-state=&quot;close&quot;&gt;
            &lt;div class=&quot;kg-toggle-heading&quot;&gt;
                &lt;h4 class=&quot;kg-toggle-heading-text&quot;&gt;&lt;span style=&quot;white-space: pre-wrap;&quot;&gt;Parts List&lt;/span&gt;&lt;/h4&gt;
                &lt;button class=&quot;kg-toggle-card-icon&quot; aria-label=&quot;Expand toggle to read content&quot;&gt;
                    &lt;svg id=&quot;Regular&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 24 24&quot;&gt;
                        &lt;path class=&quot;cls-1&quot; d=&quot;M23.25,7.311,12.53,18.03a.749.749,0,0,1-1.06,0L.75,7.311&quot;&gt;&lt;/path&gt;
                    &lt;/svg&gt;
                &lt;/button&gt;
            &lt;/div&gt;
            &lt;div class=&quot;kg-toggle-content&quot;&gt;&lt;p&gt;&lt;b&gt;&lt;strong style=&quot;white-space: pre-wrap;&quot;&gt;Parts&lt;/strong&gt;&lt;/b&gt;&lt;/p&gt;&lt;ul&gt;&lt;li value=&quot;1&quot;&gt;&lt;span style=&quot;white-space: pre-wrap;&quot;&gt;8x WS2812 5050 SMD LEDs&lt;/span&gt;&lt;/li&gt;&lt;li value=&quot;2&quot;&gt;&lt;span style=&quot;white-space: pre-wrap;&quot;&gt;1x 470Ω resistor&lt;/span&gt;&lt;/li&gt;&lt;li value=&quot;3&quot;&gt;&lt;span style=&quot;white-space: pre-wrap;&quot;&gt;1x 100µF capacitor&lt;/span&gt;&lt;/li&gt;&lt;li value=&quot;4&quot;&gt;&lt;span style=&quot;white-space: pre-wrap;&quot;&gt;1x CD4051BE Analog Multiplexer/Demultiplexer&lt;/span&gt;&lt;/li&gt;&lt;li value=&quot;5&quot;&gt;&lt;span style=&quot;white-space: pre-wrap;&quot;&gt;1x 74HC165 Shift Register&lt;/span&gt;&lt;/li&gt;&lt;li value=&quot;6&quot;&gt;&lt;span style=&quot;white-space: pre-wrap;&quot;&gt;6x potentiometers (RK0938N)&lt;/span&gt;&lt;/li&gt;&lt;li value=&quot;7&quot;&gt;&lt;span style=&quot;white-space: pre-wrap;&quot;&gt;8x Cherry MX-style switches&lt;/span&gt;&lt;/li&gt;&lt;li value=&quot;8&quot;&gt;&lt;span style=&quot;white-space: pre-wrap;&quot;&gt;8x 10k resistors&lt;/span&gt;&lt;/li&gt;&lt;li value=&quot;9&quot;&gt;&lt;span style=&quot;white-space: pre-wrap;&quot;&gt;Seeed Studio XIAO RP2040 Microcontroller&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;
        &lt;/div&gt;&lt;h2 id=&quot;getting-the-pcb&quot;&gt;Getting the PCB&lt;/h2&gt;&lt;p&gt;&lt;a href=&quot;https://www.pcbway.com/?ref=jakew.me&quot; rel=&quot;noreferrer&quot;&gt;PCBWay&lt;/a&gt; kindly offered their support for this project by providing their PCB prototyping services. As I talked about in &lt;a href=&quot;https://jakew.me/how-to-create-pcbs/&quot; rel=&quot;noreferrer&quot;&gt;my previous post&lt;/a&gt;, the ordering process with PCBWay is really quick and easy, and is as simple as using their plugin in KiCad, or uploading the exported files.&lt;/p&gt;&lt;p&gt;Shortly after ordering, one of their engineers sent me a message, asking for clarification on some of the drill holes for the potentiometers. I think the footprint I used for them may have been a bit ambiguous, but I was able to clarify that the large holes were not electrical connections to the engineer. It was great to have an engineer check out the design and it could have potentially saved me from an irritating problem.&lt;/p&gt;&lt;p&gt;After ordering, I think the manufacturing process took a few days and on the PCBWay website, I was able to check what stage of manufacturing that the board was in. I always end up choosing the most economical shipping when getting PCBs as I&apos;m never in a rush. So after it was complete the finished boards arrived in just under two weeks.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/03/IMG_4189.jpeg&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;1936&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/03/IMG_4189.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2025/03/IMG_4189.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/03/IMG_4189.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2025/03/IMG_4189.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;The boards arrived in a nicely packaged box and were protected in some bubble wrap packaging. The boards are great quality, the solder mask and finish of the board is really nice feeling and they were a joy to solder on.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/03/IMG_4190.jpeg&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;1008&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/03/IMG_4190.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2025/03/IMG_4190.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/03/IMG_4190.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2025/03/IMG_4190.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;h2 id=&quot;soldering-up&quot;&gt;Soldering Up&lt;/h2&gt;&lt;p&gt;While I was waiting for the boards to arrive, I ordered up all the parts I needed. I found it particularly difficult finding the multiplexer and shift register chips somewhere where I didn&apos;t have to buy in bulk and wasn&apos;t crazy expensive for small orders. And I did even have to switch out a few of the components before ordering the PCB for these reasons.&lt;/p&gt;&lt;p&gt;After the parts arrived, I got to soldering on my not very safe cardboard work surface.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/03/IMG_4194.jpeg&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;2066&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/03/IMG_4194.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2025/03/IMG_4194.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/03/IMG_4194.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2025/03/IMG_4194.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;I found it difficult to find a grove connector that was a reasonable price, plus I didn&apos;t really have a use for one yet, so I left that part off. Along with the smoothing capacitor, which I thought I would add later if I had any issues.&lt;/p&gt;&lt;h2 id=&quot;testing&quot;&gt;Testing&lt;/h2&gt;&lt;p&gt;The potentiometers were a tight fit, so they made a decent enough electrical connection without having to solder them. For the switches I could short the two contacts on the board with tweezers to give them a test.&lt;/p&gt;&lt;p&gt;I wrote some very basic code to check everything was working okay before I soldered everything in. Luckily there was no issues. I used &lt;a href=&quot;https://circuitpython.org/?ref=jakew.me&quot; rel=&quot;noreferrer&quot;&gt;CircuitPython&lt;/a&gt; for the testing, as it&apos;s super easy to throw together some code, plus they have some great built-in libraries for this use case, specifically a keypad matrix library that worked with the shift register to read the button inputs easily.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/03/IMG_4196.jpeg&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;1500&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/03/IMG_4196.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2025/03/IMG_4196.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/03/IMG_4196.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2025/03/IMG_4196.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import analogio
import board
import digitalio
import keypad
import neopixel

# setup pins for the potentiometer multiplexer
pots_a = digitalio.DigitalInOut(board.A3)
pots_a.direction = digitalio.Direction.OUTPUT
pots_b = digitalio.DigitalInOut(board.A2)
pots_b.direction = digitalio.Direction.OUTPUT
pots_c = digitalio.DigitalInOut(board.A1)
pots_c.direction = digitalio.Direction.OUTPUT
# this is an array of our three input pins for the multiplexer
pots_s = [pots_a, pots_b, pots_c]
# this is the pin for reading the multiplexer value
pots_z = analogio.AnalogIn(board.A0)

# fancy built-in library for reading the shift register button presses
k = keypad.ShiftRegisterKeys(
    clock=board.D9,
    data=board.D7,
    latch=board.D10,
    key_count=8,
    value_when_pressed=True,
)

# another fancy built-in library for controlling the LEDs
pixels = neopixel.NeoPixel(board.D6, 8)


# this function helps select a specific potentiometer by converting the `p` argument to binary that is sent to the pins
def select_pot_pin(p: int, pins: list[digitalio.DigitalInOut]):
    for i in range(len(pins)):
        pins[i].value = (p &gt;&gt; i) &amp;#x26; 1


# example code for setting colours on the LEDs
for i in range(8):
    pixels[i] = (10, 10, 10)
pixels.show()


while True:
    # example code for reading the potentiometer values
    for i in range(8):
        select_pot_pin(i, pots_s)
        print(f&quot;{pots_z.value:06}  &quot;, end=&quot;&quot;)
    print()

    # example code for reading button events
    event = k.events.get()
    if event:
        print(event)
    
    sleep(0.25)&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;software&quot;&gt;Software&lt;/h2&gt;&lt;p&gt;Once I was happy everything was working, I wrote a bit of code to get it to work in my DJ software. Unfortunately, my software doesn&apos;t have any custom MIDI support, which is absolutely what I would try to use. That would be as simple as sending MIDI codes from the microcontroller using CircuitPython&apos;s &lt;a href=&quot;https://docs.circuitpython.org/en/latest/shared-bindings/usb_midi/?ref=jakew.me&quot; rel=&quot;noreferrer&quot;&gt;usb_midi package&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;I&apos;m hoping to have a try with the MIDI approach in the future. However, for now, I had to go for a bit of a hacky approach using a Python program running on my computer. This uses &lt;a href=&quot;https://pyautogui.readthedocs.io/en/latest/?ref=jakew.me&quot; rel=&quot;noreferrer&quot;&gt;pyautogui&lt;/a&gt; to adjust UI elements in the software using mouse and keyboard shortcuts. I did a quick proof-of-concept where I found the coordinates of the effect knob on screen, then the script will click and adjust the control up and down.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;I used &lt;code&gt;pyautogui.position()&lt;/code&gt; to get the current position of the mouse, then hovered over the knob I wanted to control, then noted the position.&lt;/li&gt;&lt;li&gt;Then it&apos;s as easy as using &lt;code&gt;pyautogui.moveTo(x, y)&lt;/code&gt; to move the mouse cursor to that position on screen, then &lt;code&gt;pyautogui.dragTo(x, y, button=&apos;left&apos;)&lt;/code&gt; to click and drag the control.&lt;/li&gt;&lt;li&gt;For the buttons, I could send keyboard shortcuts using pyautogui and map them to the buttons I wanted to in my DJ software.&lt;/li&gt;&lt;li&gt;The microcontroller had some basic code to send the input states (potentiometer readings and button presses) over USB serial to my computer where the local program listened then changed controls and performed keypresses as appropriate.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;I&apos;m hoping to work on this a little more over the coming weeks to get it to be more reliable, but as it is now, it works okay but is a little janky and can break easily, so I won&apos;t share where I&apos;ve got to with the code, but will hopefully revisit this in another post.&lt;/p&gt;&lt;p&gt;I like the idea of trying out embedded Rust for the next iteration of the code. I really enjoy working with Rust and all the features I need are pretty well supported at this point.&lt;/p&gt;&lt;h2 id=&quot;giving-it-a-go&quot;&gt;Giving it a go&lt;/h2&gt;&lt;p&gt;With the slightly janky code written up, it was time to give it a proper test. You can see a short demo video below. &lt;em&gt;Unfortunately no music here, I don&apos;t want to get into any trouble!&lt;/em&gt;&lt;/p&gt;&lt;video src=&quot;https://ghost.jakew.me/content/media/2025/03/Untitled-video.mp4&quot; controls&gt;&lt;/video&gt;&lt;p&gt;The video isn&apos;t in perfect sync, but as you can see, the buttons are pretty responsive - immediately turning on and off the effects. However, the potentiometers need to actually move the mouse, which takes a little time. I also had to slow down the mouse movements because the software wasn&apos;t picking it up when it was any faster. Most of these effects I think I&apos;d want to have pretty fast control over, so the potentiometers are definitely not as responsive as I would like, especially when turned from one extreme to the other. Luckily this is all just a software problem which can be iterated upon to make gradual improvements.&lt;/p&gt;&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;&lt;p&gt;Although this has been my journey making a &apos;DJ&apos;-type macropad, this could absolutely work for anything else, maybe a video editor with too many keyboard shortcuts or needing fine control over scrubbing over a timeline? Maybe add an ESP32 instead of an RP2040 and have it as an internet-connected home automation shortcut hub. You could even use the open-source project, &lt;a href=&quot;https://github.com/omriharel/deej?ref=jakew.me&quot; rel=&quot;noreferrer&quot;&gt;deej&lt;/a&gt;, which lets you make a physical volume mixer, useful if you&apos;re into gaming. The possibilities are pretty endless and being able to create your own thing that fits your needs exactly is pretty awesome.&lt;/p&gt;&lt;p&gt;Let me know if you make anything cool based on this, I&apos;d love to see it! And make sure to keep an eye out as there may be follow up posts to this project in the future.&lt;/p&gt;</content:encoded><author>Jake Walker</author></item><item><title>How to create PCBs</title><link>https://jakew.me/how-to-create-pcbs/</link><guid isPermaLink="true">https://jakew.me/how-to-create-pcbs/</guid><description>Over the past couple of years, I&apos;ve had a couple of projects where I&apos;ve designed and ordered PCBs. It&apos;s a great way to make more permanent electronics projects look and feel a lot less janky.

I&apos;ve also come across a couple of open-source electronics projects where I had ordered PCBs from a project. For example, these drone timers (which I unfortunately am still yet to finish), and this drone controller screen adapter. It&apos;s super easy to download the design files from the project and upload them</description><pubDate>Sat, 08 Feb 2025 13:00:59 GMT</pubDate><content:encoded>&lt;p&gt;Over the past couple of years, I&apos;ve had a couple of projects where I&apos;ve designed and ordered PCBs. It&apos;s a great way to make more permanent electronics projects look and feel a lot less janky.&lt;/p&gt;&lt;p&gt;I&apos;ve also come across a couple of open-source electronics projects where I had ordered PCBs from a project. For example, &lt;a href=&quot;https://github.com/voroshkov/Chorus-RF-Laptimer?ref=jakew.me&quot; rel=&quot;noreferrer&quot;&gt;these drone timers&lt;/a&gt; (which I unfortunately am still yet to finish), and &lt;a href=&quot;https://www.rcgroups.com/forums/showthread.php?3660773-BetaFPV-LiteRadio2-advanced-modifications=&amp;#x26;ref=jakew.me&quot; rel=&quot;noreferrer&quot;&gt;this drone controller screen adapter&lt;/a&gt;. It&apos;s super easy to download the design files from the project and upload them to a site and get them ordered - it&apos;s often a lot cheaper than finding them sold anywhere online (if they are at all).&lt;/p&gt;&lt;p&gt;I&apos;ve also adventured into designing my own like a tiny DIY keyboard based on the &lt;a href=&quot;https://artsey.io/?ref=jakew.me&quot; rel=&quot;noreferrer&quot;&gt;ARTSEY.io&lt;/a&gt; layout.&lt;/p&gt;&lt;p&gt;For this post, I&apos;ll go over the basic principles I&apos;ve learned for creating a PCB from scratch - from design, all the way to ordering. I like to use KiCad which is free and open-source, but there&apos;s lots of other great PCB design programs, both free and paid, even ones that work in your web browser. As far as I know, they all work pretty similarly, so these basics should transfer across.&lt;/p&gt;&lt;p&gt;The project I&apos;ll make here, is going to be a with a microcontroller, button and LED.&lt;/p&gt;&lt;h2 id=&quot;building-a-schematic&quot;&gt;Building a schematic&lt;/h2&gt;&lt;p&gt;First up, we need to build the circuit&apos;s schematic, this is a high-level overview of components and how they connect.&lt;/p&gt;&lt;p&gt;&lt;em&gt;I&apos;d actually usually start with a breadboard design before doing this, just to check it works in the real-world first, but you could also do this part after designing the schematic. It all depends how you like to work.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;To get started, launch KiCad and create a new project. I&apos;ve called mine &lt;code&gt;simple&lt;/code&gt; here. You&apos;ll see it&apos;s automatically created files for the schematic (&lt;code&gt;simple.kicad_sch&lt;/code&gt;), as well as the PCB (&lt;code&gt;simple.kicad_pcb&lt;/code&gt;).&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card kg-card-hascaption&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/02/Screenshot-2025-02-07-at-3.17.00-pm.png&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;1367&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/02/Screenshot-2025-02-07-at-3.17.00-pm.png 600w, https://ghost.jakew.me/content/images/size/w1000/2025/02/Screenshot-2025-02-07-at-3.17.00-pm.png 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/02/Screenshot-2025-02-07-at-3.17.00-pm.png 1600w, https://ghost.jakew.me/content/images/2025/02/Screenshot-2025-02-07-at-3.17.00-pm.png 2130w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;figcaption&gt;&lt;span style=&quot;white-space: pre-wrap;&quot;&gt;A new project created in KiCad&lt;/span&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;This post is just going to touch on the Schematic Editor and PCB Editor, but I&apos;ll give a super quick explanation for a couple of the other options:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Symbol Editor&lt;/strong&gt; - this is for editing symbols or components for use in the Schematic Editor. There&apos;s lots of built-in parts, but if a part you want isn&apos;t in there, you can use this to import it from someone else who has designed the part online, or create your own part from scratch.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Footprint Editor&lt;/strong&gt; - the same as above but for the PCB Editor instead. These footprints will likely be linked to symbols in the Symbol Editor.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Gerber Viewer&lt;/strong&gt; - Gerbers are the exported design files that we&apos;ll eventually send off when ordering. You can view them using this tool.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Plugin and Content Manager &lt;/strong&gt;- this lets you add extra components and plugins. For example, when I was designing my keyboard, I was able to find a component library for keyboard switches in here.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;If you open up the Schematic Editor, you&apos;ll see a blank page with a &lt;em&gt;&lt;strong&gt;lot&lt;/strong&gt; &lt;/em&gt;of buttons around the outside.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/02/Screenshot-2025-02-07-at-3.38.48-pm.png&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;1508&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/02/Screenshot-2025-02-07-at-3.38.48-pm.png 600w, https://ghost.jakew.me/content/images/size/w1000/2025/02/Screenshot-2025-02-07-at-3.38.48-pm.png 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/02/Screenshot-2025-02-07-at-3.38.48-pm.png 1600w, https://ghost.jakew.me/content/images/2025/02/Screenshot-2025-02-07-at-3.38.48-pm.png 2128w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;First off we need to add components to the schematic. Use the &apos;Add Symbols&apos; button on the right toolbar, which will bring up this window to select a symbol. A symbol is just a component in the schematic. For simplicity, I&apos;ll use a built-in one here, an Arduino Nano. The top right of the window shows the symbol as it will appear in the schematic, and the bottom right shows the footprint, which is as it will appear on the PCB. The dropdown in between the two previews lets you select a flavour of the footprint. For this Arduino Nano, there&apos;s just with mounting holes or without, but for more complicated components, you might need to select the size of component you&apos;re planning to use (e.g. size of a surface mount resistor or type of switch) - essentially make sure the footprint matches the real component you want to use.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/02/Screenshot-2025-02-07-at-3.41.41-pm.png&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;1378&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/02/Screenshot-2025-02-07-at-3.41.41-pm.png 600w, https://ghost.jakew.me/content/images/size/w1000/2025/02/Screenshot-2025-02-07-at-3.41.41-pm.png 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/02/Screenshot-2025-02-07-at-3.41.41-pm.png 1600w, https://ghost.jakew.me/content/images/2025/02/Screenshot-2025-02-07-at-3.41.41-pm.png 2372w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;After you press OK, click anywhere on the schematic to place the symbol. Now I&apos;ll add in a battery, button, LED and a resistor for the LED. The battery and button didn&apos;t have footprints for me, so I&apos;ve left them blank and we&apos;ll sort them later. Here&apos;s where we&apos;re at:&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/02/Screenshot-2025-02-07-at-3.52.23-pm.png&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;1508&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/02/Screenshot-2025-02-07-at-3.52.23-pm.png 600w, https://ghost.jakew.me/content/images/size/w1000/2025/02/Screenshot-2025-02-07-at-3.52.23-pm.png 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/02/Screenshot-2025-02-07-at-3.52.23-pm.png 1600w, https://ghost.jakew.me/content/images/2025/02/Screenshot-2025-02-07-at-3.52.23-pm.png 2128w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;I&apos;ve moved all the components around so they are laid out better, then finally we&apos;ll add in a ground connection using the symbol underneath the one we just added. You could also add in a +5v connection if you wanted. &lt;em&gt;I&apos;m not sure if this is strictly necessary, but it makes the diagram slightly clearer and it may help out later on when we&apos;re putting a ground plane on our PCB (but more on that later...)&lt;/em&gt;&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/02/Screenshot-2025-02-07-at-3.54.39-pm.png&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;1508&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/02/Screenshot-2025-02-07-at-3.54.39-pm.png 600w, https://ghost.jakew.me/content/images/size/w1000/2025/02/Screenshot-2025-02-07-at-3.54.39-pm.png 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/02/Screenshot-2025-02-07-at-3.54.39-pm.png 1600w, https://ghost.jakew.me/content/images/2025/02/Screenshot-2025-02-07-at-3.54.39-pm.png 2128w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;Now use the next icon down on the right to create some wires to connect all the components. Just click on one of the circles at the connection points on a component, then click one of the circles on another component.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/02/Screenshot-2025-02-07-at-3.57.31-pm.png&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;1508&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/02/Screenshot-2025-02-07-at-3.57.31-pm.png 600w, https://ghost.jakew.me/content/images/size/w1000/2025/02/Screenshot-2025-02-07-at-3.57.31-pm.png 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/02/Screenshot-2025-02-07-at-3.57.31-pm.png 1600w, https://ghost.jakew.me/content/images/2025/02/Screenshot-2025-02-07-at-3.57.31-pm.png 2128w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;Here&apos;s my final schematic:&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/02/Screenshot-2025-02-07-at-3.59.10-pm.png&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;1508&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/02/Screenshot-2025-02-07-at-3.59.10-pm.png 600w, https://ghost.jakew.me/content/images/size/w1000/2025/02/Screenshot-2025-02-07-at-3.59.10-pm.png 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/02/Screenshot-2025-02-07-at-3.59.10-pm.png 1600w, https://ghost.jakew.me/content/images/2025/02/Screenshot-2025-02-07-at-3.59.10-pm.png 2128w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;That&apos;s pretty much all we need to do for the schematic, now just save it. You can also add labels to your diagram, using the label and global net label tools near the bottom on the right which can give your connections nicer names. There&apos;s not much going on here, but for more complex schematics it can help a lot with laying out things. It lets you have connections between multiple places on the schematic without drawing wires everywhere, just connect a global net label at each end.&lt;/p&gt;&lt;h2 id=&quot;designing-the-pcb&quot;&gt;Designing the PCB&lt;/h2&gt;&lt;p&gt;So now we&apos;ve got the schematic designed, we can move onto the PCB. Head back to the KiCad project window and open the PCB editor. This window also has a lot of buttons and things.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/02/Screenshot-2025-02-07-at-4.04.06-pm.png&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;1272&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/02/Screenshot-2025-02-07-at-4.04.06-pm.png 600w, https://ghost.jakew.me/content/images/size/w1000/2025/02/Screenshot-2025-02-07-at-4.04.06-pm.png 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/02/Screenshot-2025-02-07-at-4.04.06-pm.png 1600w, https://ghost.jakew.me/content/images/size/w2400/2025/02/Screenshot-2025-02-07-at-4.04.06-pm.png 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;We can use this button at the top to import the components from the schematic. It&apos;ll give you a quick summary of what it&apos;s going to change (as you can also go back to the schematic and add more things later, then do this again to update the changes that have been made). Once done, you&apos;ll see the footprints added to the PCB.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-gallery-card kg-width-wide&quot;&gt;&lt;div class=&quot;kg-gallery-container&quot;&gt;&lt;div class=&quot;kg-gallery-row&quot;&gt;&lt;div class=&quot;kg-gallery-image&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/02/Screenshot-2025-02-07-at-4.05.21-pm.png&quot; width=&quot;2000&quot; height=&quot;1256&quot; loading=&quot;lazy&quot; alt=&quot;&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/02/Screenshot-2025-02-07-at-4.05.21-pm.png 600w, https://ghost.jakew.me/content/images/size/w1000/2025/02/Screenshot-2025-02-07-at-4.05.21-pm.png 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/02/Screenshot-2025-02-07-at-4.05.21-pm.png 1600w, https://ghost.jakew.me/content/images/size/w2400/2025/02/Screenshot-2025-02-07-at-4.05.21-pm.png 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/div&gt;&lt;div class=&quot;kg-gallery-image&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/02/Screenshot-2025-02-07-at-4.06.57-pm.png&quot; width=&quot;2000&quot; height=&quot;1272&quot; loading=&quot;lazy&quot; alt=&quot;&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/02/Screenshot-2025-02-07-at-4.06.57-pm.png 600w, https://ghost.jakew.me/content/images/size/w1000/2025/02/Screenshot-2025-02-07-at-4.06.57-pm.png 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/02/Screenshot-2025-02-07-at-4.06.57-pm.png 1600w, https://ghost.jakew.me/content/images/size/w2400/2025/02/Screenshot-2025-02-07-at-4.06.57-pm.png 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;p&gt;For the few components that aren&apos;t shown, we can go back to the schematic editor and change the footprint to be used by double clicking on the symbol, then change the footprint by clicking the lines in the value box to bring up the footprint selector.&lt;/p&gt;&lt;p&gt;For anything missing, you might need to find something close, or find footprints others have designed online, or create your own. For the battery, I&apos;ll select a 2-pin header which will allow the circuit to be powered from an external 5v supply. Strangely, there is push button footprints available, they just weren&apos;t linked to the symbol earlier for some reason.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-gallery-card kg-width-wide&quot;&gt;&lt;div class=&quot;kg-gallery-container&quot;&gt;&lt;div class=&quot;kg-gallery-row&quot;&gt;&lt;div class=&quot;kg-gallery-image&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/02/Screenshot-2025-02-07-at-4.11.52-pm.png&quot; width=&quot;2000&quot; height=&quot;1899&quot; loading=&quot;lazy&quot; alt=&quot;&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/02/Screenshot-2025-02-07-at-4.11.52-pm.png 600w, https://ghost.jakew.me/content/images/size/w1000/2025/02/Screenshot-2025-02-07-at-4.11.52-pm.png 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/02/Screenshot-2025-02-07-at-4.11.52-pm.png 1600w, https://ghost.jakew.me/content/images/size/w2400/2025/02/Screenshot-2025-02-07-at-4.11.52-pm.png 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/div&gt;&lt;div class=&quot;kg-gallery-image&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/02/Screenshot-2025-02-07-at-4.11.57-pm.png&quot; width=&quot;1984&quot; height=&quot;1584&quot; loading=&quot;lazy&quot; alt=&quot;&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/02/Screenshot-2025-02-07-at-4.11.57-pm.png 600w, https://ghost.jakew.me/content/images/size/w1000/2025/02/Screenshot-2025-02-07-at-4.11.57-pm.png 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/02/Screenshot-2025-02-07-at-4.11.57-pm.png 1600w, https://ghost.jakew.me/content/images/2025/02/Screenshot-2025-02-07-at-4.11.57-pm.png 1984w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/div&gt;&lt;div class=&quot;kg-gallery-image&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/02/Screenshot-2025-02-07-at-4.16.01-pm.png&quot; width=&quot;1984&quot; height=&quot;1584&quot; loading=&quot;lazy&quot; alt=&quot;&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/02/Screenshot-2025-02-07-at-4.16.01-pm.png 600w, https://ghost.jakew.me/content/images/size/w1000/2025/02/Screenshot-2025-02-07-at-4.16.01-pm.png 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/02/Screenshot-2025-02-07-at-4.16.01-pm.png 1600w, https://ghost.jakew.me/content/images/2025/02/Screenshot-2025-02-07-at-4.16.01-pm.png 1984w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;p&gt;After making these symbol changes, redo the previous step again to import the new footprints. You&apos;ll notice some thin blue lines going between the components. This is how they should be connected together according to the schematic.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/02/Screenshot-2025-02-07-at-4.18.33-pm.png&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;1272&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/02/Screenshot-2025-02-07-at-4.18.33-pm.png 600w, https://ghost.jakew.me/content/images/size/w1000/2025/02/Screenshot-2025-02-07-at-4.18.33-pm.png 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/02/Screenshot-2025-02-07-at-4.18.33-pm.png 1600w, https://ghost.jakew.me/content/images/size/w2400/2025/02/Screenshot-2025-02-07-at-4.18.33-pm.png 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;Now I would lay out the components to near the pins they are going to connect to. Once you&apos;re happy with the layout, click on the &lt;code&gt;Edge.Cuts&lt;/code&gt; layer on the right side. As the name suggests, this is cuts that the manufacturer will make, so we want to put one around the edge of the board. If you&apos;re feeling extra fancy, you could add in rounded corners, mounting holes or other things. You might notice some of the components, for example, the LED already have holes where the legs go through the board.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/02/Screenshot-2025-02-07-at-4.21.46-pm.png&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;1272&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/02/Screenshot-2025-02-07-at-4.21.46-pm.png 600w, https://ghost.jakew.me/content/images/size/w1000/2025/02/Screenshot-2025-02-07-at-4.21.46-pm.png 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/02/Screenshot-2025-02-07-at-4.21.46-pm.png 1600w, https://ghost.jakew.me/content/images/size/w2400/2025/02/Screenshot-2025-02-07-at-4.21.46-pm.png 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;Some of the layers have F &amp;#x26; B prefixes, which means front and back. We&apos;ll use the &lt;code&gt;F.Cu&lt;/code&gt;  (front copper) and &lt;code&gt;B.Cu&lt;/code&gt; (back copper) for traces between components and you can add printed text or images to the board using &lt;code&gt;F.Silkscreen&lt;/code&gt; and &lt;code&gt;B.Silkscreen&lt;/code&gt; (although remember to reverse text if using the back). Click on one of the copper layers then use the first blue line on the right toolbar to create a trace. You&apos;ll notice it&apos;ll helpfully highlight where it needs to go. Make sure to leave the ground connections for now, because we&apos;re going to make a ground plane to connect them all. Here I&apos;ve used the back copper layer to run a trace along the back of the board because I&apos;ve got two tracks that overlap. With more complex PCBs it can be a bit tricky to route everything around, but it&apos;s all part of the fun!&lt;/p&gt;&lt;figure class=&quot;kg-card kg-gallery-card kg-width-wide&quot;&gt;&lt;div class=&quot;kg-gallery-container&quot;&gt;&lt;div class=&quot;kg-gallery-row&quot;&gt;&lt;div class=&quot;kg-gallery-image&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/02/Screenshot-2025-02-07-at-4.34.17-pm.png&quot; width=&quot;2000&quot; height=&quot;1272&quot; loading=&quot;lazy&quot; alt=&quot;&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/02/Screenshot-2025-02-07-at-4.34.17-pm.png 600w, https://ghost.jakew.me/content/images/size/w1000/2025/02/Screenshot-2025-02-07-at-4.34.17-pm.png 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/02/Screenshot-2025-02-07-at-4.34.17-pm.png 1600w, https://ghost.jakew.me/content/images/size/w2400/2025/02/Screenshot-2025-02-07-at-4.34.17-pm.png 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/div&gt;&lt;div class=&quot;kg-gallery-image&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/02/Screenshot-2025-02-07-at-4.36.17-pm.png&quot; width=&quot;2000&quot; height=&quot;1272&quot; loading=&quot;lazy&quot; alt=&quot;&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/02/Screenshot-2025-02-07-at-4.36.17-pm.png 600w, https://ghost.jakew.me/content/images/size/w1000/2025/02/Screenshot-2025-02-07-at-4.36.17-pm.png 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/02/Screenshot-2025-02-07-at-4.36.17-pm.png 1600w, https://ghost.jakew.me/content/images/size/w2400/2025/02/Screenshot-2025-02-07-at-4.36.17-pm.png 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;p&gt;Now we&apos;ll use the copper zone tool which is the blue shape on the right that has a wire going into it. This creates a zone of copper on the board. We should add a ground zone on the board when we&apos;re done. This can help with any external interference on the circuit. Select either one of the copper layers in the window that appears, then select the GND net and draw a box around the board again. Finally, go to &quot;Edit&quot; then &quot;Fill All Zones&quot;. You&apos;ll see the board now has copper everywhere, except where we&apos;ve got the blue trace going through. And it connects up the ground pins on the components that need it.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-gallery-card kg-width-wide&quot;&gt;&lt;div class=&quot;kg-gallery-container&quot;&gt;&lt;div class=&quot;kg-gallery-row&quot;&gt;&lt;div class=&quot;kg-gallery-image&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/02/Screenshot-2025-02-07-at-4.39.18-pm.png&quot; width=&quot;2000&quot; height=&quot;1122&quot; loading=&quot;lazy&quot; alt=&quot;&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/02/Screenshot-2025-02-07-at-4.39.18-pm.png 600w, https://ghost.jakew.me/content/images/size/w1000/2025/02/Screenshot-2025-02-07-at-4.39.18-pm.png 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/02/Screenshot-2025-02-07-at-4.39.18-pm.png 1600w, https://ghost.jakew.me/content/images/size/w2400/2025/02/Screenshot-2025-02-07-at-4.39.18-pm.png 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/div&gt;&lt;div class=&quot;kg-gallery-image&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/02/Screenshot-2025-02-07-at-4.43.21-pm.png&quot; width=&quot;2000&quot; height=&quot;1272&quot; loading=&quot;lazy&quot; alt=&quot;&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/02/Screenshot-2025-02-07-at-4.43.21-pm.png 600w, https://ghost.jakew.me/content/images/size/w1000/2025/02/Screenshot-2025-02-07-at-4.43.21-pm.png 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/02/Screenshot-2025-02-07-at-4.43.21-pm.png 1600w, https://ghost.jakew.me/content/images/size/w2400/2025/02/Screenshot-2025-02-07-at-4.43.21-pm.png 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/div&gt;&lt;div class=&quot;kg-gallery-image&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/02/Screenshot-2025-02-07-at-4.45.04-pm.png&quot; width=&quot;2000&quot; height=&quot;1272&quot; loading=&quot;lazy&quot; alt=&quot;&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/02/Screenshot-2025-02-07-at-4.45.04-pm.png 600w, https://ghost.jakew.me/content/images/size/w1000/2025/02/Screenshot-2025-02-07-at-4.45.04-pm.png 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/02/Screenshot-2025-02-07-at-4.45.04-pm.png 1600w, https://ghost.jakew.me/content/images/size/w2400/2025/02/Screenshot-2025-02-07-at-4.45.04-pm.png 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;p&gt;Now I like to go to the 3D viewer (icon left of our schematic import tool) and give the board a check to make sure it looks okay and all the traces are connected up where they need to go.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-gallery-card kg-width-wide&quot;&gt;&lt;div class=&quot;kg-gallery-container&quot;&gt;&lt;div class=&quot;kg-gallery-row&quot;&gt;&lt;div class=&quot;kg-gallery-image&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/02/Screenshot-2025-02-07-at-4.47.05-pm.png&quot; width=&quot;2000&quot; height=&quot;1195&quot; loading=&quot;lazy&quot; alt=&quot;&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/02/Screenshot-2025-02-07-at-4.47.05-pm.png 600w, https://ghost.jakew.me/content/images/size/w1000/2025/02/Screenshot-2025-02-07-at-4.47.05-pm.png 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/02/Screenshot-2025-02-07-at-4.47.05-pm.png 1600w, https://ghost.jakew.me/content/images/size/w2400/2025/02/Screenshot-2025-02-07-at-4.47.05-pm.png 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/div&gt;&lt;div class=&quot;kg-gallery-image&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/02/Screenshot-2025-02-07-at-4.47.19-pm.png&quot; width=&quot;2000&quot; height=&quot;1195&quot; loading=&quot;lazy&quot; alt=&quot;&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/02/Screenshot-2025-02-07-at-4.47.19-pm.png 600w, https://ghost.jakew.me/content/images/size/w1000/2025/02/Screenshot-2025-02-07-at-4.47.19-pm.png 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/02/Screenshot-2025-02-07-at-4.47.19-pm.png 1600w, https://ghost.jakew.me/content/images/size/w2400/2025/02/Screenshot-2025-02-07-at-4.47.19-pm.png 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;p&gt;There&apos;s also an automated checking tool named &lt;strong&gt;DRC&lt;/strong&gt; which can help spot any issues with your board which I&apos;d recommend using before ordering. But, I&apos;m happy with this board, so lets go and order it!&lt;/p&gt;&lt;h2 id=&quot;ordering-the-design&quot;&gt;Ordering the design&lt;/h2&gt;&lt;p&gt;Once your happy with your PCB design, it&apos;s time to get it ordered. You can find a bunch of manufacturers online and can easily find one to get boards created and shipped for under £10, albeit they take a little while.&lt;/p&gt;&lt;p&gt;Regardless of where you choose to order, we need to export the PCB to gerber files. This is what the manufacturer uses to make your PCB. In the PCB editor, go to &quot;File&quot;, then &quot;Fabrication Outputs&quot;, then &quot;Gerbers (.gbr)&quot;. The settings in here depend on your manufacturer so make sure to check their website for suggested settings. Once dialed in, click &quot;Plot&quot; to export, then &quot;Generate Drill Files...&quot; and do the same there. You should have a bunch of files generated now. If you choose to use PCBWay like me, they actually have a &lt;a href=&quot;https://www.pcbway.com/blog/News/PCBWay_Plug_In_for_KiCad_3ea6219c.html?ref=jakew.me&quot; rel=&quot;noreferrer&quot;&gt;KiCad extension&lt;/a&gt; which allows you to upload your design to their site with one click, so there&apos;s no need to faff around with these Gerber settings.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-gallery-card kg-width-wide&quot;&gt;&lt;div class=&quot;kg-gallery-container&quot;&gt;&lt;div class=&quot;kg-gallery-row&quot;&gt;&lt;div class=&quot;kg-gallery-image&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/02/Screenshot-2025-02-07-at-4.55.38-pm.png&quot; width=&quot;2000&quot; height=&quot;1375&quot; loading=&quot;lazy&quot; alt=&quot;&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/02/Screenshot-2025-02-07-at-4.55.38-pm.png 600w, https://ghost.jakew.me/content/images/size/w1000/2025/02/Screenshot-2025-02-07-at-4.55.38-pm.png 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/02/Screenshot-2025-02-07-at-4.55.38-pm.png 1600w, https://ghost.jakew.me/content/images/size/w2400/2025/02/Screenshot-2025-02-07-at-4.55.38-pm.png 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/div&gt;&lt;div class=&quot;kg-gallery-image&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/02/Screenshot-2025-02-07-at-4.56.07-pm.png&quot; width=&quot;2000&quot; height=&quot;1362&quot; loading=&quot;lazy&quot; alt=&quot;&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/02/Screenshot-2025-02-07-at-4.56.07-pm.png 600w, https://ghost.jakew.me/content/images/size/w1000/2025/02/Screenshot-2025-02-07-at-4.56.07-pm.png 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/02/Screenshot-2025-02-07-at-4.56.07-pm.png 1600w, https://ghost.jakew.me/content/images/size/w2400/2025/02/Screenshot-2025-02-07-at-4.56.07-pm.png 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/div&gt;&lt;div class=&quot;kg-gallery-image&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/02/Screenshot-2025-02-07-at-4.58.42-pm.png&quot; width=&quot;2000&quot; height=&quot;1509&quot; loading=&quot;lazy&quot; alt=&quot;&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/02/Screenshot-2025-02-07-at-4.58.42-pm.png 600w, https://ghost.jakew.me/content/images/size/w1000/2025/02/Screenshot-2025-02-07-at-4.58.42-pm.png 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/02/Screenshot-2025-02-07-at-4.58.42-pm.png 1600w, https://ghost.jakew.me/content/images/2025/02/Screenshot-2025-02-07-at-4.58.42-pm.png 2020w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;p&gt;You&apos;ll want to ZIP up the Gerber files now. I like to then take it to a couple of online Gerber viewers to double check the design again. For example, &lt;a href=&quot;https://www.pcbway.com/project/OnlineGerberViewer.html?ref=jakew.me&quot; rel=&quot;noreferrer&quot;&gt;this one from PCBWay&lt;/a&gt;. It&apos;s a good idea to check on multiple viewers to make sure there&apos;s not an issue that arises with just one of them.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/02/image.png&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;1436&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/02/image.png 600w, https://ghost.jakew.me/content/images/size/w1000/2025/02/image.png 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/02/image.png 1600w, https://ghost.jakew.me/content/images/size/w2400/2025/02/image.png 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;Once I&apos;m happy that the design looks fine, I&apos;ll upload the design to the manufacturer. PCBWay have kindly given me some credit towards my next project, so I&apos;m going over the process of ordering with them. There&apos;s also many other choices for manufacturers, both local and abroad, basically it depends on how fast you want your design and the quality you&apos;d like.&lt;/p&gt;&lt;p&gt;On PCBWay, when going to order the board, you can use the &apos;Quick-order PCB&apos; button to upload your files straight away, which will automatically calculate the size and show a preview at the top of the page. Underneath we&apos;ve got a bunch of options to select. There should be similar options for other manufacturers too.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/02/image-7.png&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;1457&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/02/image-7.png 600w, https://ghost.jakew.me/content/images/size/w1000/2025/02/image-7.png 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/02/image-7.png 1600w, https://ghost.jakew.me/content/images/size/w2400/2025/02/image-7.png 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;So first thing we&apos;ll do is upload our zipped up Gerber files. You should see a preview of the board after uploading, and it&apos;s another good chance to double check your design looks as it should. The site should automatically calculate the size of the board.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/02/image-11.png&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;1695&quot; height=&quot;230&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/02/image-11.png 600w, https://ghost.jakew.me/content/images/size/w1000/2025/02/image-11.png 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/02/image-11.png 1600w, https://ghost.jakew.me/content/images/2025/02/image-11.png 1695w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;For the board type, there&apos;s a few choices. Unless you&apos;re doing lots of PCB assembly, you don&apos;t need a panel. You can see an example of a panel on the right of this screenshot, it&apos;s where boards come stuck together on a big board and you would separate them off after assembly.&lt;/p&gt;&lt;p&gt;For quantities, many manufacturers have a minimum order quantity of 5 boards. It might seem like a waste but it&apos;s always good to have a spare or two in case you mess up.&lt;/p&gt;&lt;p&gt;For layers, we&apos;re using 2, this is because we&apos;ve got the front &amp;#x26; back copper. You&apos;ll find anything higher will cost more money, but for most projects you should be able to get away with 2 layers. For more complex projects, you might want layers of copper sandwiched in the middle to give more space for routing.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/02/image-12.png&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;1704&quot; height=&quot;510&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/02/image-12.png 600w, https://ghost.jakew.me/content/images/size/w1000/2025/02/image-12.png 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/02/image-12.png 1600w, https://ghost.jakew.me/content/images/2025/02/image-12.png 1704w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;ul&gt;&lt;li&gt;For material, FR-4 is standard, with the others being used for special applications.&lt;/li&gt;&lt;li&gt;For the FR4-TG, this is the kind of quality for the board, particularly the temperature threshold of the board. The pre-selected one should be okay for most use cases.&lt;/li&gt;&lt;li&gt;For thickness, you might need to choose something else depending on your project. For example, if you&apos;re making a board that fits into a USB port it&apos;ll need to be 2mm. Otherwise, the normal 1.6mm thickness is great.&lt;/li&gt;&lt;li&gt;For minimum track/spacing and hole size, you&apos;ll need to double check your design. You can set constraints in KiCad for this by going to File &gt; Board Setup &gt; Design Rules &gt; Constraints, after setting you can run a DRC check to check the spacing of everything.&lt;/li&gt;&lt;li&gt;For solder mask, you can choose whatever you like, this is the main colour of the board. Here I&apos;ve gone for black, but strange colours can adjust the price of the board.&lt;/li&gt;&lt;li&gt;For silkscreen, again, you can choose whatever, but it may affect the price.&lt;/li&gt;&lt;li&gt;UV printing allows multi-colour prints on a board. I&apos;m not going to cover that here, but it&apos;s a feature PCBWay has that I&apos;ve not noticed at any other manufacturer.&lt;/li&gt;&lt;li&gt;For edge connector, this is if you have connection points on the edge of the board. Think like how a graphics card has the gold plated connector on the bottom edge of the board. Again, you&apos;ll only need to select this for specific applications.&lt;/li&gt;&lt;/ul&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/02/image-13.png&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;1691&quot; height=&quot;1176&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/02/image-13.png 600w, https://ghost.jakew.me/content/images/size/w1000/2025/02/image-13.png 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/02/image-13.png 1600w, https://ghost.jakew.me/content/images/2025/02/image-13.png 1691w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;ul&gt;&lt;li&gt;For surface finish, there&apos;s a bunch of options. For the most part, HASL lead, and HASL lead free are the two standard ones. I like to choose lead free just so I feel a little better about all the solder fumes when assembling my board later on! ENIG offers a stronger finish while costing a little extra, and may of the others you probably won&apos;t need for normal use cases.&lt;/li&gt;&lt;li&gt;For via process, tenting vias is the standard, but as you can see this option for PCBWay is dictated by the Gerber files, so no need to make any changes to this. KiCad uses tented vias by default.&lt;/li&gt;&lt;li&gt;For finished copper, this is how much copper that is used on the outer sides of the board. A higher number is stronger, but for most cases, 1oz is fine.&lt;/li&gt;&lt;li&gt;At PCBWay, and other manufacturers, you can chose to remove or specify a location for the product number. This is just a code that identifies your order. Removing it completely costs a little extra, but you can move it to anywhere you like for free. I like to put it in one of the back corners of the board. Make sure you check with your manufacturer about how to specify the location. For example, for PCBWay I need to add the text &apos;WayWayWay&apos; to my board.&lt;/li&gt;&lt;/ul&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2025/02/image-14.png&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;1678&quot; height=&quot;757&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2025/02/image-14.png 600w, https://ghost.jakew.me/content/images/size/w1000/2025/02/image-14.png 1000w, https://ghost.jakew.me/content/images/size/w1600/2025/02/image-14.png 1600w, https://ghost.jakew.me/content/images/2025/02/image-14.png 1678w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;Now you can head to the checkout to place your order. I&apos;d recommend to double check the shipping method when you checkout. PCBWay, for example, offers a bunch of shipping methods that cater to different price points. I&apos;ve chosen the Global Direct shipping which is the most cost effective.&lt;/p&gt;&lt;p&gt;After ordering, your design will most likely be checked by an engineer who will get in touch to clarify if there&apos;s any potential issues. Then it&apos;s just a matter of waiting!&lt;/p&gt;&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;&lt;p&gt;Hopefully this quick crash course in PCB design has been useful and inspiring. I had always thought it was quite intimidating until I went to a talk on how someone designed conference PCB badges with some other PCB design software, and it&apos;s super cool to build something on your computer and eventually have a physical PCB! Keep an eye out for my next blog post where I&apos;ll be assembling my next project. Until next time...&lt;/p&gt;</content:encoded><author>Jake Walker</author></item><item><title>Repairing Electronics with Dead Batteries on the Cheap</title><link>https://jakew.me/repairing-electronics-with-dead-batteries/</link><guid isPermaLink="true">https://jakew.me/repairing-electronics-with-dead-batteries/</guid><description>I was recently testing out some software called Grocy which lets you keep track of food in your kitchen. I&apos;ve previously had difficulty adding getting started for various reasons, and on Reddit, someone mentioned a barcode scanner made using Grocy easier and more satisfying.

I kept an eye out on eBay for a cheap wireless barcode scanner and eventually got lucky. Unfortunately, when it arrived and I tested it, it didn&apos;t work at all unless it was plugged in. I tried troubleshooting quickly by mea</description><pubDate>Thu, 06 Jun 2024 17:32:17 GMT</pubDate><content:encoded>&lt;p&gt;I was recently testing out some software called &lt;a href=&quot;https://grocy.info/?ref=jakew.me&quot; rel=&quot;noreferrer&quot;&gt;Grocy&lt;/a&gt; which lets you keep track of food in your kitchen. I&apos;ve previously had difficulty adding getting started for various reasons, and on Reddit, someone mentioned a barcode scanner made using Grocy easier and more satisfying.&lt;/p&gt;&lt;p&gt;I kept an eye out on eBay for a cheap wireless barcode scanner and eventually got lucky. Unfortunately, when it arrived and I tested it, it didn&apos;t work at all unless it was plugged in. I tried troubleshooting quickly by measuring the battery voltage and found it was at 0 volts. For reference the lowest a Lithium Polymer (LiPo) battery can go is 3.2 volts before potentially becoming damaged, so 0 volts is as bad as it gets and would need replacing. I contacted the seller and we worked out a partial refund, then I got to work replacing the battery.&lt;/p&gt;&lt;p&gt;This post is designed to talk about the process of replacing a battery in something quite generally so it should be applicable to anything with a dead rechargeable battery in.&lt;/p&gt;&lt;div class=&quot;kg-card kg-callout-card kg-callout-card-red&quot;&gt;&lt;div class=&quot;kg-callout-emoji&quot;&gt;⚠️&lt;/div&gt;&lt;div class=&quot;kg-callout-text&quot;&gt;&lt;b&gt;&lt;strong style=&quot;white-space: pre-wrap;&quot;&gt;There&apos;s risks involved with working with batteries&lt;/strong&gt;&lt;/b&gt;, make sure you do your homework and know what you&apos;re doing! For example, it&apos;s pretty dangerous to overcharge or short circuit a battery.&lt;/div&gt;&lt;/div&gt;&lt;p&gt;The first step was disassembling the barcode scanner to get to the battery itself.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2024/05/IMG_2950.jpeg&quot; class=&quot;kg-image&quot; alt=&quot;The wireless barcode scanner with the top taken off, revealing the motherboard with various components on it.&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;2110&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2024/05/IMG_2950.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2024/05/IMG_2950.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2024/05/IMG_2950.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2024/05/IMG_2950.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;I had to take the scanner apart completely to get to the battery. The board here on the right has all the electronics on, and the board on the left has the battery, a button and a connector for the charging dock.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2024/05/IMG_2951.jpeg&quot; class=&quot;kg-image&quot; alt=&quot;The barcode scanner even more disassembled with two circuit boards.&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;2774&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2024/05/IMG_2951.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2024/05/IMG_2951.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2024/05/IMG_2951.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2024/05/IMG_2951.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;Next, I removed the battery. The one here is an 18650 battery, which are super common in battery banks and other consumer electronics.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2024/05/IMG_2952.jpeg&quot; class=&quot;kg-image&quot; alt=&quot;The 18650 battery extracted from the second board in the barcode scanner.&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;1500&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2024/05/IMG_2952.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2024/05/IMG_2952.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2024/05/IMG_2952.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2024/05/IMG_2952.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;The blue plastic is just a cover, so removing it gets me access to the battery terminals as well as a battery protection circuit.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2024/05/IMG_2953.jpeg&quot; class=&quot;kg-image&quot; alt=&quot;The battery with the plastic cover taken off and the protection circuit visible.&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;1500&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2024/05/IMG_2953.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2024/05/IMG_2953.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2024/05/IMG_2953.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2024/05/IMG_2953.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;The battery protection circuit here is a really good salvage. I can simply attach this to a battery which doesn&apos;t have one to make it much safer. These little circuits make sure the battery doesn&apos;t get too much current, and doesn&apos;t get charged or discharged too much.&lt;/p&gt;&lt;p&gt;I removed the battery protection circuit and found a spare LiPo battery. It&apos;s worth pointing out the battery I&apos;m using here is quite a lot smaller than the original at around a quarter of the capacity. That&apos;s okay for me as I&apos;m not using this for short periods of time. If you&apos;re doing the same, you should check that the device your putting it into has an appropriate charging current. I managed to find this in the manual for the scanner, but you may need to crack out a multimeter.&lt;/p&gt;&lt;p&gt;As I mentioned earlier, 18650 batteries are super common, and in the past I&apos;ve bought really cheap power banks (e.g. from Poundland) to harvest some. This probably gives the best results, but if I can re-use this smaller one it helps the environment at least 🌿.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2024/05/IMG_2954.jpeg&quot; class=&quot;kg-image&quot; alt=&quot;The harvested battery protection circuit with a smaller battery beneath.&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;1500&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2024/05/IMG_2954.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2024/05/IMG_2954.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2024/05/IMG_2954.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2024/05/IMG_2954.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;Next, I soldered the battery onto the protection circuit.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2024/05/IMG_2955.jpeg&quot; class=&quot;kg-image&quot; alt=&quot;The battery protection circuit soldered onto the smaller battery.&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;1500&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2024/05/IMG_2955.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2024/05/IMG_2955.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2024/05/IMG_2955.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2024/05/IMG_2955.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;Finally, I put the battery back into the scanner and reassembled the scanner. With some very janky electrical tape work to hide the exposed battery connections and to keep everything in place.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2024/05/IMG_2956.jpeg&quot; class=&quot;kg-image&quot; alt=&quot;The complete battery (with protection circuit) wrapped in quite a lot of electrical tape and put back onto the original circuit board.&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;1500&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2024/05/IMG_2956.jpeg 600w, https://ghost.jakew.me/content/images/size/w1000/2024/05/IMG_2956.jpeg 1000w, https://ghost.jakew.me/content/images/size/w1600/2024/05/IMG_2956.jpeg 1600w, https://ghost.jakew.me/content/images/size/w2400/2024/05/IMG_2956.jpeg 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;Finally, I reassembled everything and gave it a quick test. Now it works perfectly, and I saved a couple of quid by using stuff I already had.&lt;/p&gt;&lt;p&gt;This is a great example of repairing some broken electronics to keep it ticking still, and hopefully this serves as good inspiration for you!&lt;/p&gt;</content:encoded><author>Jake Walker</author></item><item><title>Repurpose Your Old Laptop Into a Home Server</title><link>https://jakew.me/home-server/</link><guid isPermaLink="true">https://jakew.me/home-server/</guid><description>I&apos;ve got a really nice old laptop that I can&apos;t bare to sell, mainly because of the sticker collection on the back. There&apos;s no point it collecting dust though, so I&apos;ve been using it as a home server.

Unfortunately the other day I managed to accidentally corrupt the drive on my home server. Luckily though, all the important stuff was on a second drive and it&apos;s a good opportunity to start again.

In this post I&apos;m hoping to give you a bit of inspiration and go through the initial setup of mine, inc</description><pubDate>Sat, 11 Nov 2023 17:26:03 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve got a really nice old laptop that I can&apos;t bare to sell, mainly because of the sticker collection on the back. There&apos;s no point it collecting dust though, so I&apos;ve been using it as a home server.&lt;/p&gt;&lt;p&gt;Unfortunately the other day I managed to accidentally corrupt the drive on my home server. Luckily though, all the important stuff was on a second drive and it&apos;s a good opportunity to start again.&lt;/p&gt;&lt;p&gt;In this post I&apos;m hoping to give you a bit of inspiration and go through the initial setup of mine, including a Plex media server with external drive and how to remotely access it from anywhere in the world.&lt;/p&gt;&lt;p&gt;&lt;em&gt;These same steps can be used on anything else you have laying around. In fact, I used to have a Raspberry Pi server until I was having trouble transcoding videos with Plex (which is why I switched to my slightly more beefy old laptop).&lt;/em&gt;&lt;/p&gt;&lt;h1 id=&quot;operating-system&quot;&gt;Operating System&lt;/h1&gt;&lt;p&gt;One of the important choices is choosing an operating system. This is mostly dictated by how much effort you want to put in and how much knowledge you have with Linux. Here are some popular options:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://www.truenas.com/?ref=jakew.me&quot;&gt;TrueNAS&lt;/a&gt; &lt;/strong&gt;- although I&apos;ve not tried it, this seems to be a great option with what looks like all the setup done through a web browser (after first installation). You can go with &lt;a href=&quot;https://www.truenas.com/truenas-core/?ref=jakew.me&quot;&gt;CORE&lt;/a&gt; or &lt;a href=&quot;https://www.truenas.com/truenas-scale/?ref=jakew.me&quot;&gt;SCALE&lt;/a&gt;, the latter offering Docker container support (which I&apos;m going to talk about a lot in this post).&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://flatcar-linux.org/?ref=jakew.me&quot;&gt;Flatcar&lt;/a&gt;&lt;/strong&gt; - again, I&apos;ve never used this but it looks like a great option for running only container workloads.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://ubuntu.com/download/server?ref=jakew.me&quot;&gt;Ubuntu&lt;/a&gt;/&lt;a href=&quot;https://getfedora.org/en/server/?ref=jakew.me&quot;&gt;Fedora Server&lt;/a&gt;&lt;/strong&gt; - the standard option is using one of these. They both have great support and Fedora Server has a lovely web dashboard.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://www.proxmox.com/en/proxmox-ve?ref=jakew.me&quot;&gt;Proxmox&lt;/a&gt;&lt;/strong&gt; - if you want to run VM workloads this is an awesome option, you can separate any projects you may have into separate VMs.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;In my case, I&apos;m going with Ubuntu Server. One of my limitations is having to use a Wi-Fi connection and Ubuntu/Fedora server has the best support for that (but do use Ethernet if you can, I have run into so many problems). TrueNAS was my second option, but it has to be installed on an entire drive and none of the storage on the install drive can be used for media &amp;#x26; files (unless you want to deal with moving around partitions and things which makes it quite a bit of effort to get initially setup).&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2022/12/Screenshot-2022-12-13-at-5.41.15-pm.png&quot; class=&quot;kg-image&quot; alt=&quot;Flashing the Ubuntu Server ISO to a flash drive using balenaEtcher&quot; loading=&quot;lazy&quot; width=&quot;1824&quot; height=&quot;1184&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2022/12/Screenshot-2022-12-13-at-5.41.15-pm.png 600w, https://ghost.jakew.me/content/images/size/w1000/2022/12/Screenshot-2022-12-13-at-5.41.15-pm.png 1000w, https://ghost.jakew.me/content/images/size/w1600/2022/12/Screenshot-2022-12-13-at-5.41.15-pm.png 1600w, https://ghost.jakew.me/content/images/2022/12/Screenshot-2022-12-13-at-5.41.15-pm.png 1824w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;Install whichever OS as normal, there are plenty of great guides elsewhere on installing if you get stuck.&lt;/p&gt;&lt;p&gt;Once you&apos;ve got it installed, I&apos;d recommend setting up SSH (which I was able to do during setup on Ubuntu Server) so you can get access for the rest of the setup from another computer. You should also give it an ol&apos; update.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2022/12/Screenshot-2022-12-13-at-6.04.29-pm.png&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;1524&quot; height=&quot;1250&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2022/12/Screenshot-2022-12-13-at-6.04.29-pm.png 600w, https://ghost.jakew.me/content/images/size/w1000/2022/12/Screenshot-2022-12-13-at-6.04.29-pm.png 1000w, https://ghost.jakew.me/content/images/2022/12/Screenshot-2022-12-13-at-6.04.29-pm.png 1524w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;You can ignore this part if using a desktop, but for a laptop it&apos;s important to disable the lid switch to prevent the system from sleeping or powering off when shut. To do that, edit &lt;code&gt;/etc/systemd/logind.conf&lt;/code&gt; and edit the three lines shown below:&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2022/12/Screenshot-2022-12-14-at-5.30.44-pm.png&quot; class=&quot;kg-image&quot; alt=&quot;Setting &amp;#x60;HandleLidSwitch&amp;#x60;, &amp;#x60;HandleLidSwitchExternalPower&amp;#x60; and &amp;#x60;HandleLidSwitchDocked&amp;#x60; to &amp;#x60;ignore&amp;#x60; in the &amp;#x60;/etc/systemd/logind.conf&amp;#x60; file&quot; loading=&quot;lazy&quot; width=&quot;1492&quot; height=&quot;1250&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2022/12/Screenshot-2022-12-14-at-5.30.44-pm.png 600w, https://ghost.jakew.me/content/images/size/w1000/2022/12/Screenshot-2022-12-14-at-5.30.44-pm.png 1000w, https://ghost.jakew.me/content/images/2022/12/Screenshot-2022-12-14-at-5.30.44-pm.png 1492w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;h1 id=&quot;setting-up-docker&quot;&gt;Setting up Docker&lt;/h1&gt;&lt;p&gt;Docker makes it super easy to deploy and update any applications you might want to run. Getting started is easy and the following command sets up everything you need on any operating system:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl -fsSL https://get.docker.com | sh&lt;/code&gt;&lt;/pre&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2022/12/Screenshot-2022-12-13-at-6.09.59-pm.png&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;1524&quot; height=&quot;1090&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2022/12/Screenshot-2022-12-13-at-6.09.59-pm.png 600w, https://ghost.jakew.me/content/images/size/w1000/2022/12/Screenshot-2022-12-13-at-6.09.59-pm.png 1000w, https://ghost.jakew.me/content/images/2022/12/Screenshot-2022-12-13-at-6.09.59-pm.png 1524w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;Once that&apos;s complete, you will want to give your current user permission to use Docker with the following command, then log out and back in for it to take effect. Also, we would like Docker compose for later.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo usermod -aG docker ${USER}
sudo apt install docker-compose -y&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That&apos;s it, Docker is now installed and ready to go.&lt;/p&gt;&lt;h1 id=&quot;installing-plex-on-docker&quot;&gt;Installing Plex on Docker&lt;/h1&gt;&lt;p&gt;One of my main reasons for running a home server is for Plex. This allows you to dump all your music, movies, photos and TV shows onto a hard drive and be able to watch them easily on your TV, phone, laptop and whatever else.&lt;/p&gt;&lt;p&gt;&lt;em&gt;A very cool additional feature of Plex is that you can set up a TV tuner and record TV shows to watch later.&lt;/em&gt;&lt;/p&gt;&lt;h2 id=&quot;mounting-an-external-drive&quot;&gt;Mounting an External Drive&lt;/h2&gt;&lt;p&gt;Although not necessary, I have all my media on an external drive, this is just so I have more storage on the main drive for Docker images and other stuff.&lt;/p&gt;&lt;p&gt;First up, run &lt;code&gt;lsblk -f&lt;/code&gt; to find the drive that looks correct and note down it&apos;s UUID. In my case, the drive I&apos;m using is 1TB, so &lt;code&gt;sdb1&lt;/code&gt; is the one that I want.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2022/12/Screenshot-2022-12-13-at-6.33.09-pm.png&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;1046&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2022/12/Screenshot-2022-12-13-at-6.33.09-pm.png 600w, https://ghost.jakew.me/content/images/size/w1000/2022/12/Screenshot-2022-12-13-at-6.33.09-pm.png 1000w, https://ghost.jakew.me/content/images/size/w1600/2022/12/Screenshot-2022-12-13-at-6.33.09-pm.png 1600w, https://ghost.jakew.me/content/images/2022/12/Screenshot-2022-12-13-at-6.33.09-pm.png 2084w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;Should you want to reformat the drive, run &lt;code&gt;sudo mkfs -t ext4 /dev/sdXX&lt;/code&gt; (editing to the correct device). Then check with the &lt;code&gt;lsblk -f&lt;/code&gt; command to make sure the UUID hasn&apos;t changed.&lt;/p&gt;&lt;p&gt;Next up, create a folder to mount the drive into (this can be anywhere you like): &lt;code&gt;sudo mkdir -p /media/data&lt;/code&gt;. Then run &lt;code&gt;sudo nano /etc/fstab&lt;/code&gt; to edit the config for the permanent drive mounts. Create a new line at the bottom with the following content (remembering to replace your drive&apos;s UUID):&lt;/p&gt;&lt;pre&gt;&lt;code&gt;UUID=XXXX /media/data ext4 defaults 0 1&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For any additional drives, create separate folders for mounting and add more lines to this file. Here is mine below as an example.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2022/12/Screenshot-2022-12-13-at-6.34.24-pm.png&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;1588&quot; height=&quot;1090&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2022/12/Screenshot-2022-12-13-at-6.34.24-pm.png 600w, https://ghost.jakew.me/content/images/size/w1000/2022/12/Screenshot-2022-12-13-at-6.34.24-pm.png 1000w, https://ghost.jakew.me/content/images/2022/12/Screenshot-2022-12-13-at-6.34.24-pm.png 1588w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;Save and exit by pressing Control-X then Y. Finally, run &lt;code&gt;sudo mount -a&lt;/code&gt; for the changes to take affect, mounting the drive you added. You can run &lt;code&gt;lsblk&lt;/code&gt; to check the mount point of the drive is expected.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2022/12/Screenshot-2022-12-13-at-6.34.00-pm.png&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;1588&quot; height=&quot;1090&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2022/12/Screenshot-2022-12-13-at-6.34.00-pm.png 600w, https://ghost.jakew.me/content/images/size/w1000/2022/12/Screenshot-2022-12-13-at-6.34.00-pm.png 1000w, https://ghost.jakew.me/content/images/2022/12/Screenshot-2022-12-13-at-6.34.00-pm.png 1588w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;h2 id=&quot;installing-plex&quot;&gt;Installing Plex&lt;/h2&gt;&lt;p&gt;To install Plex, I&apos;ll use a Docker compose file. A compose file is a bit like a recipe for creating one or more Docker containers, as well as networks and volumes. This makes it super easy to configure and upgrade in the future rather than remembering long commands.&lt;/p&gt;&lt;p&gt;To get started, make a new folder anywhere called &lt;code&gt;plex&lt;/code&gt; or something similar with &lt;code&gt;mkdir plex&lt;/code&gt;. This folder will contain the Docker compose file and any folders that we want on this drive. Then open the folder with &lt;code&gt;cd plex&lt;/code&gt; and run &lt;code&gt;nano docker-compose.yml&lt;/code&gt; to create the compose file.&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2022/12/Screenshot-2022-12-14-at-4.42.41-pm.png&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;1362&quot; height=&quot;930&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2022/12/Screenshot-2022-12-14-at-4.42.41-pm.png 600w, https://ghost.jakew.me/content/images/size/w1000/2022/12/Screenshot-2022-12-14-at-4.42.41-pm.png 1000w, https://ghost.jakew.me/content/images/2022/12/Screenshot-2022-12-14-at-4.42.41-pm.png 1362w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;Then copy the following content into the file:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;version: &quot;3.4&quot;
services:
  plex:
    container_name: plex
    image: lscr.io/linuxserver/plex:latest
    restart: unless-stopped
    network_mode: host
    environment:
      - PUID=1000
      - PGID=1000
      - VERSION=docker
    volumes:
      - /media/data/tv:/tv
      - /media/data/movies:/movies
      - /media/data/music:/music
    devices:
      - /dev/dri:/dev/dri&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;Note: remove the devices section if you receive errors later on - this is for using Intel hardware acceleration. If you have an NVIDIA GPU see &lt;a href=&quot;https://github.com/linuxserver/docker-plex?ref=jakew.me#nvidia&quot;&gt;here&lt;/a&gt; to get it working with Plex. Also with devices and volumes, the left side is the host and right side is the container.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;Then, run &lt;code&gt;docker-compose up -d&lt;/code&gt; to start Plex. It will take a few moments to download the image and it will launch Plex.&lt;/p&gt;&lt;div class=&quot;kg-card kg-toggle-card&quot; data-kg-toggle-state=&quot;close&quot;&gt;&lt;div class=&quot;kg-toggle-heading&quot;&gt;&lt;h4 class=&quot;kg-toggle-heading-text&quot;&gt;Other useful commands&lt;/h4&gt;&lt;button class=&quot;kg-toggle-card-icon&quot;&gt;&lt;svg id=&quot;Regular&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 24 24&quot;&gt;&lt;path class=&quot;cls-1&quot; d=&quot;M23.25,7.311,12.53,18.03a.749.749,0,0,1-1.06,0L.75,7.311&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/button&gt;&lt;/div&gt;&lt;div class=&quot;kg-toggle-content&quot;&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Logs:&lt;/strong&gt; &lt;code&gt;docker-compose logs --follow --tail 10&lt;/code&gt; - get the last 10 lines of container logs and any future lines. Press Control-C to exit.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Stop:&lt;/strong&gt; &lt;code&gt;docker-compose down&lt;/code&gt; - stop the container.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Update: &lt;/strong&gt;&lt;code&gt;docker-compose pull &amp;#x26;&amp;#x26; docker-compose up -d&lt;/code&gt; - pull the latest container image and recreate the container with the new image.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;Once running, give it a few moments to warm up, then go to the IP address of your server with port &lt;code&gt;32400&lt;/code&gt;. Make sure to add &lt;code&gt;/web&lt;/code&gt; to the path if you get an XML response. You will be taken to a page where you can sign in, then you will get a setup wizard like this:&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2022/12/Screenshot-2022-12-14-at-5.06.18-pm.png&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;1535&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2022/12/Screenshot-2022-12-14-at-5.06.18-pm.png 600w, https://ghost.jakew.me/content/images/size/w1000/2022/12/Screenshot-2022-12-14-at-5.06.18-pm.png 1000w, https://ghost.jakew.me/content/images/size/w1600/2022/12/Screenshot-2022-12-14-at-5.06.18-pm.png 1600w, https://ghost.jakew.me/content/images/size/w2400/2022/12/Screenshot-2022-12-14-at-5.06.18-pm.png 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;h1 id=&quot;remote-access&quot;&gt;Remote Access&lt;/h1&gt;&lt;p&gt;For remote access, I like to use Tailscale. It creates a peer-to-peer, private WireGuard VPN and has great apps for any computer or mobile device for connecting remotely. It&apos;s free for up to 20 devices and it&apos;s much better than opening ports on your router. To get started, &lt;a href=&quot;https://tailscale.com/?ref=jakew.me&quot;&gt;sign up on their website&lt;/a&gt; then run the following comamnd to install Tailscale.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl -fsSL https://tailscale.com/install.sh | sh&lt;/code&gt;&lt;/pre&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2022/12/Screenshot-2022-12-14-at-5.09.06-pm.png&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;1362&quot; height=&quot;930&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2022/12/Screenshot-2022-12-14-at-5.09.06-pm.png 600w, https://ghost.jakew.me/content/images/size/w1000/2022/12/Screenshot-2022-12-14-at-5.09.06-pm.png 1000w, https://ghost.jakew.me/content/images/2022/12/Screenshot-2022-12-14-at-5.09.06-pm.png 1362w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;Once complete, run &lt;code&gt;sudo tailscale up&lt;/code&gt; and log in with your account. Once logged in, you can go back to your terminal and run &lt;code&gt;ip a&lt;/code&gt; to see a new &lt;code&gt;tailscale0&lt;/code&gt; device has been added with an IP address starting with &lt;code&gt;100&lt;/code&gt;. Your new device will also show up in the Tailscale dashboard:&lt;/p&gt;&lt;figure class=&quot;kg-card kg-image-card&quot;&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2023/11/Machines---Tailscale.png&quot; class=&quot;kg-image&quot; alt=&quot;&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;911&quot; srcset=&quot;https://ghost.jakew.me/content/images/size/w600/2023/11/Machines---Tailscale.png 600w, https://ghost.jakew.me/content/images/size/w1000/2023/11/Machines---Tailscale.png 1000w, https://ghost.jakew.me/content/images/size/w1600/2023/11/Machines---Tailscale.png 1600w, https://ghost.jakew.me/content/images/size/w2400/2023/11/Machines---Tailscale.png 2400w&quot; sizes=&quot;(min-width: 720px) 720px&quot;&gt;&lt;/figure&gt;&lt;p&gt;You&apos;ll notice my server has &apos;expiry disabled&apos; this means that I won&apos;t have to reauthenticate on the server every now and again. To do this, click on the dropdown at the end of the server and select &apos;disable key expiry&apos;.&lt;/p&gt;&lt;h1 id=&quot;fin&quot;&gt;Fin&lt;/h1&gt;&lt;p&gt;That&apos;s it, a simple home server setup with Plex installed as an example. If you want any other apps, there&apos;s a list of suggestions below, the process for setting them up with Docker will be similar to the above with Plex.&lt;/p&gt;&lt;p&gt;Having a home server makes a great way to share files between your own devices or family, it also gives you more control over your data, especially when you opt to self-host an application yourself rather than going to an online app run by a company.&lt;/p&gt;&lt;h2 id=&quot;other-services-to-setup&quot;&gt;Other services to setup&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://jakew.me/zigbee-homeassistant/&quot;&gt;Home Assistant&lt;/a&gt; - Smart Home&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://docs.linuxserver.io/images/docker-nextcloud?ref=jakew.me&quot;&gt;NextCloud&lt;/a&gt; - File Storage (similar to OneDrive, Google Drive or iCloud)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://jakew.me/wireguard-docker/&quot;&gt;WireGuard VPN&lt;/a&gt;&lt;/li&gt;&lt;li&gt;See &lt;a href=&quot;https://github.com/awesome-selfhosted/awesome-selfhosted?ref=jakew.me&quot;&gt;awesome-selfhosted&lt;/a&gt; for more apps sorted into categories&lt;/li&gt;&lt;/ul&gt;</content:encoded><author>Jake Walker</author></item><item><title>An Introduction to Serverless Functions with Cloudflare Workers</title><link>https://jakew.me/cloudflare-workers/</link><guid isPermaLink="true">https://jakew.me/cloudflare-workers/</guid><description>What is the point of serverless, why Cloudflare Workers and writing your first Cloudflare Worker function.</description><pubDate>Tue, 11 Jan 2022 20:00:00 GMT</pubDate><content:encoded>&lt;!--kg-card-begin: markdown--&gt;&lt;p&gt;One of my new favorite services is Cloudflare Workers. It&apos;s a platform for running serverless code with quite nice developer experience. In this post, I&apos;d share what I like so much and how you can get started.&lt;/p&gt;
&lt;p&gt;Firstly, what is serverless? Serverless is a way of deploying code to the cloud where you do not manage the infrastructure, it is all managed by your chosen provider. With serverless, you only pay for the time that your code is executed which makes it great for applications that get low traffic or only get used certain times. Instances of your code are created and destroyed on demand which your code can &apos;infinitely scale&apos;.&lt;/p&gt;
&lt;p&gt;Sounds great right? Let&apos;s have a deeper look. If you&apos;re not too interested in the details, &lt;a href=&quot;#writing-your-first-function&quot;&gt;skip down to the code&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;serverlessbenefitsvsothermethods&quot;&gt;Serverless Benefits vs. Other Methods&lt;/h2&gt;
&lt;p&gt;To put that into context, let&apos;s talk about some of the ways you might deploy some code that you have written.&lt;/p&gt;
&lt;p&gt;One of the obvious options is to rent a virtual machine or dedicated server from a hosting company. This gives you a &lt;strong&gt;lot of control&lt;/strong&gt;, you get to install any operating system that you want and fine tune the power you need (like wanting  more CPU over RAM). The provider of the machine is maintaining the actual hardware, networking, cooling and other things. The downside for virtual machines, are that you need to &lt;strong&gt;constantly maintain&lt;/strong&gt; this machine, updating the OS, deploying new code, etc...&lt;/p&gt;
&lt;p&gt;Another option might be to use something like &lt;a href=&quot;https://www.heroku.com/?ref=jakew.me&quot;&gt;Heroku&lt;/a&gt; or &lt;a href=&quot;https://aws.amazon.com/elasticbeanstalk/?ref=jakew.me&quot;&gt;AWS Elastic Beanstalk&lt;/a&gt; where you just upload your code to the platform, and it gets &lt;strong&gt;provisioned onto machines by the provider&lt;/strong&gt;. This gives you &lt;strong&gt;less control&lt;/strong&gt; over the hardware that your code is running on, usually just the environment (i.e. the version of runtime your code runs and dependencies, not the operating system). The provider is responsible for the physical hardware as well as the operating system. Most providers offer Continuous Deployment which will automatically deploy any new versions of code.&lt;/p&gt;
&lt;p&gt;The last option might be serverless, using something like &lt;a href=&quot;https://workers.cloudflare.com/?ref=jakew.me&quot;&gt;Cloudflare Workers&lt;/a&gt; or &lt;a href=&quot;https://aws.amazon.com/lambda/?ref=jakew.me&quot;&gt;AWS Lambda&lt;/a&gt;. Similarly to the last option, you only upload your code to the platform and the provider sorts out the servers and running of the code. You usually get even less control over serverless, where the provider might only allow certain runtimes or no choice at all. You are usually limited on the size of your code too, 50MB on AWS Lambda, and 1MB on Cloudflare Workers. However, the big benefit of serverless is that you only pay for the time your code is running, this makes it great for applications that have light traffic or traffic at only certain times in the day. With some providers, you get &apos;infinite&apos; scaling, so more and more instances of your code will be created as necessary to saturate the incoming requests. &lt;em&gt;There are other pros and cons depending on the provider, but those are some of the major ones.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&quot;whycloudflareworkers&quot;&gt;Why Cloudflare Workers?&lt;/h2&gt;
&lt;p&gt;Cloudflare released Workers back in September 2017 (with public access in March 2018), which is relatively new compared to AWS who have been offering their serverless Lambda functions since November 2014.&lt;/p&gt;
&lt;h3 id=&quot;runsglobally&quot;&gt;Runs Globally&lt;/h3&gt;
&lt;p&gt;Cloudflare run Workers at their existing &lt;strong&gt;edge locations&lt;/strong&gt;, which are data centers which are placed close to end users so they can offer &lt;strong&gt;fast, snappy responses&lt;/strong&gt;. By comparison, if you used AWS Lambda, your function would be limited to a single region, if someone from the other side of the world made a request, they&apos;d get a slow response. Cloudflare would give great performance anywhere in the world.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I should point out, AWS offers a product called Lambda@Edge, which runs at edge locations, but I can&apos;t find much information on them. If you&apos;re interested Cloudflare has some good articles comparing like &lt;a href=&quot;https://www.cloudflare.com/en-gb/learning/serverless/serverless-performance/?ref=jakew.me&quot;&gt;this one&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&quot;faststartups&quot;&gt;Fast Startups&lt;/h3&gt;
&lt;p&gt;Cloudflare Workers have &lt;strong&gt;extremely fast startup times&lt;/strong&gt;, usually milliseconds after a request, where other providers have seconds or minutes. This is very important for user experience, especially since 10 seconds is the limit for people keeping attention on the ongoing interaction.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;After 1 second, users get impatient and notice that they&apos;re waiting for a slow computer to respond. The longer the wait, the more this impatience grows; after about 10 seconds, the average attention span is maxed out. At that point, the user&apos;s mind starts wandering and doesn&apos;t retain enough information in short-term memory to easily resume the interaction once the computer finally loads the next screen.&quot;&lt;/p&gt;
&lt;p&gt;- &lt;a href=&quot;https://www.nngroup.com/articles/author/jakob-nielsen/?ref=jakew.me&quot;&gt;Jakob Nielsen&lt;/a&gt; (&lt;a href=&quot;https://www.nngroup.com/articles/powers-of-10-time-scales-in-ux/?ref=jakew.me&quot;&gt;source&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Other providers, like AWS Lambda, tend to use lightweight containers for running code, these provide a secure environment for running untrusted code. Once a request comes in, a container is started and takes a few seconds to boot up (this is called a slow start). Any subsequent requests will use the existing container running the code so a response is sent back quickly. Once there are no requests for a while, the container is destroyed. In summary, any new requests often need to wait for a container to start, but any subsequent requests are much faster.&lt;/p&gt;
&lt;p&gt;Cloudflare achieves super fast startup times because they run code in &apos;Isolates&apos;, which are a way of running lots of JavaScript functions independently while using a single runtime. They always have the runtime ready which means there are almost zero startup times. A downside of this approach is that you &lt;strong&gt;can only run JavaScript code&lt;/strong&gt; on Cloudflare Workers, however you can compile other code into &lt;a href=&quot;https://webassembly.org/?ref=jakew.me&quot;&gt;WebAssembly&lt;/a&gt; so that it can run in a Worker (WebAssembly unfortunately has lower performance than running native code).&lt;/p&gt;
&lt;h3 id=&quot;affordablepricinggenerousfreetier&quot;&gt;Affordable Pricing &amp;#x26; Generous Free Tier&lt;/h3&gt;
&lt;p&gt;Cloudflare gives 100,000 requests for free each day, with any extra requests being charged at $0.15 per million requests (with a minimum of $5/month). AWS, for comparison, gives you 1 million requests each month, but charges $0.20 per million for anything over ($0.60 per million for Lambda@Edge).&lt;/p&gt;
&lt;p&gt;&lt;small&gt;&lt;i&gt;&lt;br&gt;
Prices as of 4th Jan 2022 in the UK West (Ireland) region for AWS. I am ignoring execution time pricing here as there&apos;s more considerations than just price for it. Cloudflare bundles them into their price up to a certain amount and have only one option for memory (128MB). AWS charges execution time for each request.&lt;br&gt;
&lt;/i&gt;&lt;/small&gt;&lt;/p&gt;
&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;Cloudflare Workers has great performance around the world, have affordable pricing and a great free tier. This all makes Workers a great offering in the serverless space, but there&apos;s still advantages for going elsewhere.&lt;/p&gt;
&lt;h2 id=&quot;writingyourfirstfunction&quot;&gt;Writing your first function&lt;/h2&gt;
&lt;p&gt;To get started, you will need &lt;a href=&quot;https://nodejs.org/en/?ref=jakew.me&quot;&gt;Node.js&lt;/a&gt; installed on your machine and a new folder for your project.&lt;/p&gt;
&lt;p&gt;I&apos;m going to be using &lt;a href=&quot;https://yarnpkg.com/?ref=jakew.me&quot;&gt;Yarn&lt;/a&gt; which is package manager for Node.js which I like to use.&lt;/p&gt;
&lt;h3 id=&quot;installingwranglercli&quot;&gt;Installing Wrangler CLI&lt;/h3&gt;
&lt;p&gt;First, you will need to install the Wrangler CLI. This is a tool which Cloudflare has made for deploying code to Cloudflare Workers as well as working with some other Cloudflare services. After installing the CLI, you will need to login to your Cloudflare account.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;yarn global add @cloudflare/wrangler
wrangler login
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;If you get an error saying Wrangler is not found, you may need to add the Yarn bin directory to your path. Get the path of the Yarn bin with &lt;code&gt;yarn global bin&lt;/code&gt;. For more information see &lt;a href=&quot;https://stackoverflow.com/questions/40317578/yarn-global-command-not-working?ref=jakew.me&quot;&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&quot;creatinganewproject&quot;&gt;Creating a new project&lt;/h3&gt;
&lt;p&gt;Next up, you will need to make a new Yarn project.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;yarn init --yes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now create a file called &lt;code&gt;wrangler.toml&lt;/code&gt;, this is the configuration file that tells Wrangler what to do with this project.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-toml&quot;&gt;name = &quot;hello-world&quot;
type = &quot;webpack&quot;

account_id = &quot;&quot;
workers_dev = true

compatibility_date = &quot;2022-01-11&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;type&lt;/code&gt; can be &lt;code&gt;javascript&lt;/code&gt; or &lt;code&gt;webpack&lt;/code&gt;. Webpack means you can use Node.js packages in your project. The &lt;code&gt;compatibility_date&lt;/code&gt; is the set of features that are enabled for your project.&lt;/p&gt;
&lt;p&gt;Then, create a file called &lt;code&gt;main.js&lt;/code&gt; with this code. This simply listens for new requests and responds with &apos;Hello World!&apos;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;addEventListener(&apos;fetch&apos;, (event) =&gt; {
  event.respondWith(new Response(&quot;Hello World!&quot;));
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this stage, you can run &lt;code&gt;wrangler dev&lt;/code&gt; and open &lt;code&gt;http://127.0.0.1:8787&lt;/code&gt; to see your message.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2022-01-03-cloudflare-workers/wrangler-dev.png&quot; alt=&quot;Wrangler Dev command output and web browser&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;Great, you&apos;ve just written your first Cloudflare Worker!&lt;/p&gt;
&lt;h3 id=&quot;usingnpmpackages&quot;&gt;Using npm packages&lt;/h3&gt;
&lt;p&gt;As with most Node.js projects, you will probably want to make use of some npm packages. As an example, I&apos;ll show using a package which will return an inspirational quote.&lt;/p&gt;
&lt;p&gt;First, install the package.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;yarn add inspirational-quotes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, use it in your code like normal. In this case, we import the package at the top of the file and return the output of one of the functions.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const quotes = require(&apos;inspirational-quotes&apos;);

addEventListener(&apos;fetch&apos;, (event) =&gt; {
  const quote = quotes.getRandomQuote();
  event.respondWith(new Response(quote));
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running the development server again, the response is now an inspirational quote!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2022-01-03-cloudflare-workers/wrangler-dev-quote.png&quot; alt=&quot;Wrangler dev command with web browser showing a quote&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;publishing&quot;&gt;Publishing&lt;/h3&gt;
&lt;p&gt;Finally, we can publish this project. Cloudflare gives you a free &lt;code&gt;workers.dev&lt;/code&gt; domain which you can publish your Workers to, however you can also publish to another domain that you manage on Cloudflare.&lt;/p&gt;
&lt;p&gt;Simply, choose a subdomain and then publish. You only need to run the subdomain command once.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;wrangler subdomain &amp;#x3C;name&gt;
wrangler publish
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You will then be given the URL of your published Worker! Notice the start of the domain is the &lt;code&gt;name&lt;/code&gt; value in your &lt;code&gt;wrangler.toml&lt;/code&gt; which in this case is &lt;code&gt;hello-world&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2022-01-03-cloudflare-workers/wrangler-publish.png&quot; alt=&quot;Publishing the Worker using wrangler publish and then fetching the response using curl&quot; loading=&quot;lazy&quot;&gt;&lt;br&gt;
That&apos;s it, you have just written and published your first Cloudflare Worker and perhaps your first serverless code!&lt;/p&gt;
&lt;p&gt;I hope this post was useful and inspired you to check out Cloudflare Workers. I&apos;m going to continue this adventure with my next post on Cloudflare Workers KV, a way of storing persistent data in a Cloudflare Worker.&lt;/p&gt;
&lt;!--kg-card-end: markdown--&gt;</content:encoded><author>Jake Walker</author></item><item><title>Starship</title><link>https://jakew.me/starship/</link><guid isPermaLink="true">https://jakew.me/starship/</guid><description>Pimp your shell with a custom prompt!</description><pubDate>Tue, 28 Dec 2021 16:23:00 GMT</pubDate><content:encoded>&lt;!--kg-card-begin: markdown--&gt;&lt;p&gt;&lt;a href=&quot;https://starship.rs/?ref=jakew.me&quot;&gt;Starship&lt;/a&gt; is a cool little open-source project, which gives you a nice customizable prompt for your shell.&lt;/p&gt;
&lt;h2 id=&quot;whatisaprompt&quot;&gt;What is a prompt?&lt;/h2&gt;
&lt;p&gt;A prompt is the text at the start of your shell/terminal that tells you that you need to type a command. Usually it&apos;s something boring like this (the &lt;code&gt;pi@raspberrypi:~ $&lt;/code&gt;):&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2021-12-28-starship/raspberrypi.png&quot; alt=&quot;Default Raspberry Pi Prompt&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;installingstarship&quot;&gt;Installing Starship&lt;/h2&gt;
&lt;h3 id=&quot;linux&quot;&gt;Linux&lt;/h3&gt;
&lt;p&gt;Simply run this one-liner, to install Starship.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sh -c &quot;$(curl -fsSL https://starship.rs/install.sh)&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You then need to add the init script to your shell config. This is usually bash, but on some distros could be zsh or something else.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# For bash
echo &apos;eval &quot;$(starship init bash)&quot;&apos; &gt;&gt; ~/.bashrc
source ~/.bashrc

# For zsh
echo &apos;eval &quot;$(starship init zsh)&quot;&apos; &gt;&gt; ~/.zshrc
source ~/.bashrc
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;windowspowershell&quot;&gt;Windows (PowerShell)&lt;/h3&gt;
&lt;p&gt;On Windows, the easiest way to install Starship is using Chocolatey, a package manager for Windows. Although, if you prefer to not install that, there are other ways listed &lt;a href=&quot;https://starship.rs/guide?ref=jakew.me&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;# Install Chocolatey
Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString(&apos;https://community.chocolatey.org/install.ps1&apos;))

# Install Starship
choco install starship -y

# Get the location of your PowerShell profile
echo $PROFILE
# e.g. /Users/jakew/.config/powershell/Microsoft.PowerShell_profile.ps1

# Create the directory
New-Item -ItemType Directory /Users/jakew/.config/powershell/

# Add the init script to the profile
echo &quot;Invoke-Expression (&amp;#x26;starship init powershell)&quot; &gt;&gt; $PROFILE

# Reload the profile
&amp;#x26; $PROFILE
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;macos&quot;&gt;MacOS&lt;/h3&gt;
&lt;p&gt;Simply install Starship with brew on MacOS. Then add the init script to your zsh config.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;brew install starship
echo &apos;eval &quot;$(starship init zsh)&quot;&apos; &gt;&gt; ~/.zshrc
source ~/.zshrc
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;customize&quot;&gt;Customize!&lt;/h2&gt;
&lt;p&gt;Now already after installing, your prompt should look a lot more interesting than before. I&apos;m not a huge fan of all the extra stuff though, like the Python version when in a Python project, I could just run &lt;code&gt;python --version&lt;/code&gt; if I needed.&lt;/p&gt;
&lt;p&gt;You can edit &lt;strong&gt;&lt;code&gt;~/.config/starship.toml&lt;/code&gt;&lt;/strong&gt; to configure how Starship looks. You can see the options available &lt;a href=&quot;https://starship.rs/config/?ref=jakew.me&quot;&gt;here&lt;/a&gt;, but this is my go to configuration which is very minimal:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-toml&quot;&gt;add_newline = false
format = &quot;&quot;&quot;$username\
$hostname\
$directory\
$character
&quot;&quot;&quot;

[character]
success_symbol = &quot;[➜](bold green)&quot;
error_symbol = &quot;[➜](bold red)&quot;

[username]
format = &quot;[$user]($style)&quot;

[hostname]
format = &quot;:[$hostname]($style) &quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The format variable sets the order of what is displayed. In this case, I have the username, hostname, directory and then character. The character I have set so that it is usually green, but goes red when the last command failed. The username and hostname only show when SSHing in.&lt;/p&gt;
&lt;p&gt;Even though I could do this by changing my shell&apos;s prompt variable, I like Starship because there is a lot more control over things and does some things better. For example, you can see the path here changes where it is relative to once I enter my Git repository, whereas the default prompt might only show 2 directories higher:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2021-12-28-starship/starship.png&quot; alt=&quot;Starship Prompt&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;I find if I have no GUI whatsoever (e.g. on a headless Raspberry Pi), it&apos;s useful to turn on some of the other bits, like &lt;a href=&quot;https://starship.rs/config/?ref=jakew.me#git-branch&quot;&gt;Git&lt;/a&gt;, &lt;a href=&quot;https://starship.rs/config/?ref=jakew.me#memory-usage&quot;&gt;memory usage&lt;/a&gt;, &lt;a href=&quot;https://starship.rs/config/?ref=jakew.me#time&quot;&gt;time&lt;/a&gt; or perhaps &lt;a href=&quot;https://starship.rs/config/?ref=jakew.me#sudo&quot;&gt;sudo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thanks for reading, hopefully this very short blog post helped you make your shell feel more at home.&lt;/p&gt;
&lt;!--kg-card-end: markdown--&gt;</content:encoded><author>Jake Walker</author></item><item><title>Using Zigbee devices with Home Assistant</title><link>https://jakew.me/zigbee-homeassistant/</link><guid isPermaLink="true">https://jakew.me/zigbee-homeassistant/</guid><description>How I got more control from an IKEA Trådfri motion sensor with Home Assistant and a Zigbee dongle.</description><pubDate>Sun, 15 Aug 2021 14:30:00 GMT</pubDate><content:encoded>&lt;p&gt;A few years ago, I had cheap &apos;smart&apos; light bulbs in my room which were controlled with a remote control. To make them smarter, I decided to make an internet-connected Arduino relay the commands for turning the lights on and off, and changing the colour. I think I then hooked them up to &lt;a href=&quot;https://www.home-assistant.io/?ref=jakew.me&quot;&gt;Home Assistant&lt;/a&gt; so that I could control them through my Alexa or phone.&lt;/p&gt;
&lt;p&gt;Eventually I moved away from those light bulbs, as they were so cheap that I ended up not being able to see some things when it was dark. For various reasons, I went for &lt;a href=&quot;https://www.ikea.com/gb/en/cat/smart-lighting-36812/?ref=jakew.me&quot;&gt;IKEA&apos;s Trådfri&lt;/a&gt; ecosystem, rather than the more expensive (at the time), &lt;a href=&quot;https://www.philips-hue.com/en-gb?ref=jakew.me&quot;&gt;Phillips Hue&lt;/a&gt;. These days there&apos;s lots more good quality choices, but I&apos;m still very happy with my IKEA lights.&lt;/p&gt;
&lt;p&gt;After a recent trip to IKEA, I bought some new Trådfri gadgets, like &lt;a href=&quot;https://www.ikea.com/gb/en/p/tradfri-wireless-control-outlet-00364477/?ref=jakew.me&quot;&gt;a plug&lt;/a&gt; and &lt;a href=&quot;https://www.ikea.com/gb/en/p/tradfri-wireless-motion-sensor-white-70429913/?ref=jakew.me&quot;&gt;a motion sensor&lt;/a&gt;. I thought the motion sensor would be good for turning the lights off if I&apos;m not in the room. But I didn&apos;t do any research before buying it, so I wasn&apos;t sure how it would work. After getting home and setting it up, it turned out that the motion sensor turned the lights on if there was movement, and off if there was no movement. That&apos;s alright for most use cases, but I wanted it in my bedroom, and I don&apos;t really want it turning the lights on and off while I was sleeping, so my motion sensor has been sitting doing nothing for a few months.&lt;/p&gt;
&lt;h2 id=&quot;z-wave-and-zigbee&quot;&gt;Z-Wave and Zigbee&lt;/h2&gt;
&lt;p&gt;Earlier this week, I was inspired by &lt;a href=&quot;https://www.youtube.com/watch?v=xm740EdgOTM&amp;#x26;ref=jakew.me&quot;&gt;a video by Linus Tech Tips&lt;/a&gt; where he was showing his idea for smartening up his home with &lt;a href=&quot;https://www.home-assistant.io/?ref=jakew.me&quot;&gt;Home Assistant&lt;/a&gt;. He is using &lt;a href=&quot;https://www.z-wave.com/?ref=jakew.me&quot;&gt;Z-Wave&lt;/a&gt; light switches around his house so he can control ordinary lights from a phone or voice assistant. The light switches also have motion sensors inside them, so they can detect if nobody is in the room, and turn off the lights.&lt;/p&gt;
&lt;p&gt;Z-Wave is a standard for smart home devices, which creates a mesh network, meaning each device will relay messages to devices that are further away from the gateway. Z-Wave check every device to ensure that it has a good standard of security and quality.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2021-08-15-zigbee-homeassistant/Z-Wave-Example.jpeg&quot; alt=&quot;Z-Wave Mesh Routing Diagram&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;There is also a similar standard called &lt;a href=&quot;https://zigbeealliance.org/solution/zigbee/?ref=jakew.me&quot;&gt;Zigbee&lt;/a&gt;. Zigbee do not check every device which makes it a more open standard. Zigbee devices are much cheaper than a similar Z-Wave one. IKEA Trådfri devices are actually using Zigbee under-the-hood, so theoretically, you can actually buy any Zigbee gateway and pair a Trådfri gadget to it.&lt;/p&gt;
&lt;p&gt;After a quick Google search to make sure that it was possible, I thought that it would be a great idea to use my Trådfri motion sensor directly with a Zigbee gateway, so I could get more control over what it does.&lt;/p&gt;
&lt;h2 id=&quot;the-dongle&quot;&gt;The Dongle&lt;/h2&gt;
&lt;p&gt;Buying a Zigbee dongle with Home Assistant seemed like the best way to move forward.&lt;/p&gt;
&lt;p&gt;After a search on Amazon, I found &lt;a href=&quot;https://www.amazon.co.uk/Aideepen-Protocol-Analyzer-Assistant-Bluetooth-multicolor/dp/B08F9F276S?ref=jakew.me&quot;&gt;this cheap dongle&lt;/a&gt; which had the word &apos;Home Assistant&apos; in the title, so surely it should work with Home Assistant right? That&apos;s what I thought, so I bought the dongle, and &lt;a href=&quot;https://www.amazon.co.uk/SNZB-02-Temperature-Humidity-Compatible-Including/dp/B08BFW697F?ref=jakew.me&quot;&gt;a cheap temperature and humidity sensor&lt;/a&gt; to bring my order up to the free shipping limit.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2021-08-15-zigbee-homeassistant/adapter.png&quot; alt=&quot;Zigbee USB Dongle on Amazon.co.uk&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;Unfortunately, I missed the sentence in the description of the dongle&apos;s product page which said &quot;After flashing the firmware [...] it can work with Zigbee2mqtt&quot;. I eventually found out that I would need to flash it after plugging it into my computer and not seeing anything come up (I was expecting a serial port), which means that it would need flashing before it would work.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you end up buying a dongle, I would recommend buying a pre-flashed one &lt;strong&gt;and&lt;/strong&gt; a slightly more powerful one, as I later found out mine can&apos;t handle networks over 50 devices. That shouldn&apos;t be a problem, but it&apos;s nice to be future-proof.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;flashing-the-dongle&quot;&gt;Flashing the Dongle&lt;/h2&gt;
&lt;p&gt;As I found out, my dongle needed flashing with firmware that would let it work with Home Assistant. You can follow the instructions from the Zigbee2MQTT project &lt;a href=&quot;https://www.zigbee2mqtt.io/information/flashing_the_cc2531.html?ref=jakew.me&quot;&gt;here&lt;/a&gt;. Even though I&apos;m not using that project, the instructions are still valid.&lt;/p&gt;
&lt;p&gt;After looking at the instructions, I saw that I would need to buy a debugger and special cable to flash the dongle. I didn&apos;t fancy buying those for a one-time job, so I tried looking at the more janky &lt;a href=&quot;https://www.zigbee2mqtt.io/information/alternative_flashing_methods.html?ref=jakew.me&quot;&gt;alternative flashing methods&lt;/a&gt;. I went down the Raspberry Pi route, so I wouldn&apos;t have to install anything on my laptop.&lt;/p&gt;
&lt;h3 id=&quot;brief-flashing-instructions-with-a-raspberry-pi&quot;&gt;Brief Flashing Instructions with a Raspberry Pi&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;These instructions are shortened from the Zigbee2MQTT website, make sure to check them out if you would like more detail.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Below is the pinout of the dongle from the product page. You do not need to connect pin 2 (Vdd) if you plug the dongle into USB.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2021-08-15-zigbee-homeassistant/dongle-pinout.png&quot; alt=&quot;Dongle Pinout&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;The debug pins on the dongle are very tightly packed together, so to be able to fit crocodile clips on them without shorting out, I needed to bend the pins to the side. Only 4 of the 10 pins need to be connected, so they don&apos;t need to all be bent. I was quite worried I would snap the pins, so be careful if you take this approach.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2021-08-15-zigbee-homeassistant/IMG_0662.jpeg&quot; alt=&quot;Photo showing the bent debug pins on the dongle&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;Here is my dongle connected to the Raspberry Pi with crocodile clips. The wires are stuck down to stop them from moving around accidentally and shorting.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2021-08-15-zigbee-homeassistant/IMG_0661.jpeg&quot; alt=&quot;Flashing Setup&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;After connecting up the dongle, and nothing blowing up, I moved onto actually flashing it with the instructions below.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Install dependencies
sudo apt update
sudo apt install wiringpi git -y

# Download flashing scripts
git clone https://github.com/jmichault/flash_cc2531.git
cd flash_cc2531

# Checking the chip ID
./cc_chipid  # Should return b524, if not your wiring could be wrong

# Download the firmware
wget https://github.com/Koenkk/Z-Stack-firmware/raw/master/coordinator/Z-Stack_Home_1.2/bin/default/CC2531_DEFAULT_20201127.zip
unzip CC2531_DEFAULT_20201127.zip

# Flash the dongle
./cc_erase
./cc_write CC2531ZNP-Prod.hex
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After about 3 minutes, the dongle will have flashed successfully. The dongle should now create a serial port when plugged into a computer.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2021-08-15-zigbee-homeassistant/flashing.png&quot; alt=&quot;Dongle flashing output&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;This picture shows the output of &lt;code&gt;dmesg&lt;/code&gt; on Linux, which lets you see that the CC2531 dongle has been detected and assigned serial port &lt;code&gt;/dev/ttyACM0&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2021-08-15-zigbee-homeassistant/dmesg.png&quot; alt=&quot;dmesg output&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;setting-up-home-assistant&quot;&gt;Setting up Home Assistant&lt;/h2&gt;
&lt;p&gt;Now the dongle is good to go, it&apos;s time to setup Home Assistant. I&apos;m going to walk through setting up Home Assistant inside a Docker container because I find it a little bit easier. I&apos;m using an old laptop as a server, but I&apos;d recommend a Raspberry Pi if you&apos;re not running much else.&lt;/p&gt;
&lt;p&gt;Firstly, you will need to install Docker and Docker Compose:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Install Docker
curl -fsSL https://get.docker.com | sh

# Install Docker Compose
sudo apt update
sudo apt install docker-compose -y

# Add yourself to the docker group
sudo usermod -aG docker $USER
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now Docker is installed, we&apos;ll make a new folder for storing the Docker Compose file (which is like a recipe for creating Docker containers).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# create a new folder in your home folder
mkdir ~/homeassistant
cd ~/homeassistant

# open docker-compose.yml for editing
nano docker-compose.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Inside the &lt;code&gt;docker-compose.yml&lt;/code&gt; file, put the following. Notice that under &lt;code&gt;devices&lt;/code&gt;, &lt;code&gt;/dev/ttyACM0&lt;/code&gt; is being passed into the container, this is so Home Assistant can use the dongle. You may need to change the device that is being passed in, you can check by looking at the output of &lt;code&gt;dmesg&lt;/code&gt; as shown in the previous section. Also, this container uses &lt;code&gt;network_mode: host&lt;/code&gt; which essentially means no network isolation is in place, this is important so that Home Assistant can more easily advertise itself to other things on your network (e.g. for the HomeKit integration).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;version: &apos;3.4&apos;
services:
    homeassistant:
        container_name: homeassistant
        restart: unless-stopped
        image: ghcr.io/linuxserver/homeassistant
        network_mode: host
        environment:
            - PUID=1000
            - GUID=1000
            - TZ=Europe/London
        devices:
            - &quot;/dev/ttyACM0:/dev/ttyACM0&quot;
        volumes:
            - &quot;./config:/config&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Save the file, then start up the container:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker-compose up -d
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After starting up the container, go to port 8123 to see the Home Assistant dashboard and follow the instructions to get set up.&lt;/p&gt;
&lt;h2 id=&quot;setting-up-zigbee-in-home-assistant&quot;&gt;Setting up Zigbee in Home Assistant&lt;/h2&gt;
&lt;p&gt;Once into Home Assistant, head to &apos;Configuration&apos; on the sidebar, then &apos;Integrations&apos;. In the bottom right, click &apos;Add Integration&apos; and search for &apos;Zigbee&apos;. You should see &apos;Zigbee Home Automation&apos; come up.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2021-08-15-zigbee-homeassistant/zha-install.png&quot; alt=&quot;image-20210814124537657 pm&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;You should be prompted to select the serial port. You should see the device that was passed in from the Docker Compose file, and the chipset of the dongle, which in my case is CC2531.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2021-08-15-zigbee-homeassistant/zha-setup.png&quot; alt=&quot;image-20210814124635889 pm&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;If you don&apos;t get prompted to add a device, you can click on the devices link underneath Zigbee Home Automation on the Integrations page, then &apos;Add Device&apos; in the bottom right. You will then be asked to put your device into pairing mode. You will need to follow your specific devices instructions for that.&lt;/p&gt;
&lt;h3 id=&quot;temperature-and-humidity-sensor&quot;&gt;Temperature and Humidity Sensor&lt;/h3&gt;
&lt;p&gt;I first tried to setup my temperature and humidity sensor. I bought this because &lt;strong&gt;I know&lt;/strong&gt; that it works with Zigbee, and should help me troubleshoot whether my Trådfri device isn&apos;t working or whether the Home Assistant or Zigbee dongle isn&apos;t working.&lt;/p&gt;
&lt;p&gt;Following the instructions for it, I put into pairing mode, and a red light began to flash.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2021-08-15-zigbee-homeassistant/IMG_0663.jpeg&quot; alt=&quot;Temperature and Humidity Sensor in Pairing Mode&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;Almost instantly, the device was detected in Home Assistant. It takes just under a minute for it to be fully &apos;interviewed&apos;, where the device is asked what information it can give.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2021-08-15-zigbee-homeassistant/zha-interview.png&quot; alt=&quot;Home Assistant ZHA Integration Interview&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;The temperature and humidity are now shown on the Overview page.&lt;/p&gt;
&lt;h3 id=&quot;tr%C3%A5dfri-motion-sensor&quot;&gt;Trådfri Motion Sensor&lt;/h3&gt;
&lt;p&gt;Now for the main event, the motion sensor that has been sitting on my desk for months.&lt;/p&gt;
&lt;p&gt;The motion sensor can be put into pairing mode by pressing the link button on the back 4 times. This process is similar for other Trådfri devices with buttons, otherwise for things like light bulbs without buttons, they need to be reset. It&apos;s worth Googling if your not sure, there&apos;s lots of documentation out there for connecting these Trådfri devices to a Zigbee gateway.&lt;/p&gt;
&lt;p&gt;The motion sensor shows a red light, to indicate it is in pairing mode, just like the temperature sensor.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2021-08-15-zigbee-homeassistant/IMG_0664.jpeg&quot; alt=&quot;IKEA Tradfri Motion Sensor in Pairing Mode&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;The motion sensor also gets instantly picked up by Home Assistant, and after the quick interview process, shows up in the Overview tab.&lt;/p&gt;
&lt;h2 id=&quot;next-steps&quot;&gt;Next Steps&lt;/h2&gt;
&lt;p&gt;Inside Home Assistant, the temperature and humidity sensor has 3 separate &apos;entities&apos;, one for the temperature reading, one for the humidity reading and one with the battery level. The motion sensor has 2 &apos;entities&apos;, one indicating whether there is motion or not, and the other with the battery level.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2021-08-15-zigbee-homeassistant/hass-sensors.png&quot; alt=&quot;The three sensors being shown in the Home Assistant Overview&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;The motion sensor now gives me a lot more control, I can setup automations within Home Assistant which can use the motion sensor to do things. For example, I could make it so that if the light is on, and no motion has been detected for 5 minutes, the light turns off. That way, if I&apos;m sleeping, the light won&apos;t turn on when it detects motion. I haven&apos;t gotten around to setting up automations yet, because I&apos;m having some issues (more on that below), although I expect I will do a blog post if I learn anything cool about them.&lt;/p&gt;
&lt;p&gt;In the future, I might also switch over all of my IKEA lights over to use Home Assistant, depending on how well the reliability holds up.&lt;/p&gt;
&lt;p&gt;I hope this post has inspired you to look into home automation and smart devices. I&apos;d love to know if you do anything cool!&lt;/p&gt;
</content:encoded><author>Jake Walker</author></item><item><title>Using the Elecrow 5&quot; TFT display with Balena</title><link>https://jakew.me/elecrow-5inch-balena/</link><guid isPermaLink="true">https://jakew.me/elecrow-5inch-balena/</guid><description>How to use a cheap 5 inch display with Balena without pulling your hair out.</description><pubDate>Tue, 01 Jun 2021 18:30:00 GMT</pubDate><content:encoded>&lt;!--kg-card-begin: markdown--&gt;&lt;p&gt;Balena is a really cool cloud platform for managing your IoT projects. Essentially, you can write some code, and Balena will manage your devices, keeping track of versions and automatically updating your devices.&lt;/p&gt;
&lt;p&gt;There are a few Balena projects that make use of displays, there&apos;s lots of documentation for the official Raspberry Pi display, but for other displays it&apos;s a little hard to find the right things to change. This post should help you quickly get your Elecrow 5&quot; TFT display working with Balena with touch support.&lt;/p&gt;
&lt;p&gt;Under your device, go to &apos;Device Configuration&apos;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Set &apos;Define DT overlays&apos; to &lt;code&gt;&quot;ads7846,cs=1,penirq=25,penirq_pull=2,speed=50000,keep_vref_on=0,swapxy=0,pmax=255,xohms=150,xmin=200,xmax=3900,ymin=200,ymax=3900&quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Set &apos;Define a custom CVT mode for the HDMI&apos; to &lt;code&gt;800 480 60 6 0 0 0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Set &apos;Force the HDMI hotplug signal&apos; to on&lt;/li&gt;
&lt;li&gt;Set &apos;Define the HDMI output group&apos; to 2&lt;/li&gt;
&lt;li&gt;Set &apos;Define the HDMI output format&apos; to 87&lt;/li&gt;
&lt;li&gt;Set &apos;Define the rotation or flip of the display&apos; to 0&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Underneath in the &apos;Custom Configuration Variables&apos; section, add the following:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BALENA_HOST_CONFIG_hdmi_drive&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BALENA_HOST_CONFIG_max_usb_current&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;And that&apos;s it, wait for the changes to propogate to your device (which includes a restart) and enjoy!&lt;/p&gt;
&lt;!--kg-card-end: markdown--&gt;</content:encoded><author>Jake Walker</author></item><item><title>Create a WireGuard VPN in 10 minutes with Docker</title><link>https://jakew.me/wireguard-docker/</link><guid isPermaLink="true">https://jakew.me/wireguard-docker/</guid><description>Creating a quick, easy and most importantly, secure VPN using Docker and WireGuard.</description><pubDate>Mon, 19 Oct 2020 20:16:00 GMT</pubDate><content:encoded>&lt;!--kg-card-begin: markdown--&gt;&lt;p&gt;I&apos;m a big fan of Docker, it really easily lets you set up applications without much effort and keeps everything organised on my server. I already run an OpenVPN server for the few times that I find myself on public Wi-Fi these days and I&apos;ve been wanting to replace it with WireGuard which seems like a better solution.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It intends to be considerably more performant than OpenVPN. [...] It might be regarded as the most secure, easiest to use, and simplest VPN solution in the industry.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I&apos;ve heard more and more good things about WireGuard, so I thought it would be finally a good time to switch over this weekend.&lt;/p&gt;
&lt;p&gt;I set up WireGuard on my existing Linux cloud server, but this could also be replicated on a Raspberry Pi at home if you don&apos;t want to splash cash on a cloud server.&lt;/p&gt;
&lt;h2 id=&quot;installingdocker&quot;&gt;Installing Docker&lt;/h2&gt;
&lt;p&gt;These Docker installation instructions will only work on Linux machines, if you are working on Windows or macOS go to the Docker website for instructions on &lt;a href=&quot;https://www.docker.com/get-started?ref=jakew.me&quot;&gt;downloading Docker Desktop&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This two-liner installs Docker on most major Linux distributions.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-10-19-wireguard-docker/docker-install.png&quot; alt=&quot;Installing Docker&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;After installing Docker, you will need to add your user to the &lt;code&gt;docker&lt;/code&gt; group to allow you to run Docker commands without using sudo or logging in as a root user.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;sudo usermod -aG docker &amp;#x3C;username&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-10-19-wireguard-docker/docker-installed.png&quot; alt=&quot;Installing Docker&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;You will most likely need to sign out and in again (or restart your SSH session) in order to be able to run Docker commands.&lt;/p&gt;
&lt;h2 id=&quot;settingupwireguard&quot;&gt;Setting up WireGuard&lt;/h2&gt;
&lt;p&gt;Now that Docker has been installed, we can move on to setting up the VPN itself. For this, it is best to have a domain pointing to your machine that you can use for VPN purposes. If you do not have one, use &lt;code&gt;auto&lt;/code&gt; instead of a domain in the command below.&lt;/p&gt;
&lt;p&gt;You will also need to know how many devices that you will have that will use the VPN at the same time. In my case, I have a laptop, tablet and phone that could be using the VPN at the same time. From these devices, you will need to make a comma separated list of device names (e.g. &lt;code&gt;laptop,tablet,phone&lt;/code&gt;) these can be named anything you&apos;d like.&lt;/p&gt;
&lt;p&gt;Now run the command below. Make sure to put your domain in for &lt;code&gt;SERVERURL=&lt;/code&gt; and your list of devices for &lt;code&gt;PEERS=&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;docker run -d \
  --name wireguard \
  --cap-add=NET_ADMIN \
  --cap-add=SYS_MODULE \
  -e PUID=1000 -e PGID=1000 \
  -e TZ=Europe/London \
  -e SERVERURL=wireguard.yourdomain.com \
  -e PEERS=laptop,tablet,phone \
  -e PEERDNS=auto \
  -p 51820:51820/udp \
  -v wireguard_config:/config \
  -v /lib/modules:/lib/modules \
  --sysctl=&quot;net.ipv4.conf.all.src_valid_mark=1&quot; \
  --restart=unless-stopped \
  linuxserver/wireguard
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here&apos;s what all of that means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;--name wireguard&lt;/code&gt;&lt;/strong&gt; - This names the container which makes it easier for using in commands later (rather than using the container&apos;s ID).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;--cap-add=NET_ADMIN --cap-add=SYS_MODULE&lt;/code&gt;&lt;/strong&gt; - This gives the container extra permissions on the host system. Specifically, it is allowed to modify network interfaces and install kernel modules respectively (more on that below).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;-e ...&lt;/code&gt;&lt;/strong&gt; - Each of the &lt;code&gt;-e&lt;/code&gt; arguments sets an environment variable inside the container, these are used to configure the application that is running inside the container.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;-p 51820:51820/udp&lt;/code&gt;&lt;/strong&gt; - This listens for traffic on UDP port 51820 (left of the colon) on the host and passes it through to the container on port 51820 (right of the colon). If something else was using that port, you could change the one on the left of the colon to something else (for WireGuard, you&apos;d also have to set &lt;code&gt;-e SERVERPORT=&lt;/code&gt; to reflect the new port too)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;-v wireguard_config:/config&lt;/code&gt;&lt;/strong&gt; - This mounts the &lt;code&gt;/config&lt;/code&gt; folder in the container to a Docker volume called &lt;code&gt;wireguard_config&lt;/code&gt; (this could also be changed to a folder on the host, for example &lt;code&gt;/home/jakew/wireguard&lt;/code&gt;). This particular volume would be stored at &lt;code&gt;/var/lib/docker/volumes/wireguard_config/_data&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;-v /lib/modules:/lib/modules&lt;/code&gt;&lt;/strong&gt; - This is the same as before, but we are just mounting the folder &lt;code&gt;/lib/modules&lt;/code&gt; to the same place on the host (more on that below).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;--sysctl=&quot;net.ipv4.conf.all.src_valid_mark=1&quot;&lt;/code&gt;&lt;/strong&gt; - This sets a kernel parameter.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;--restart=unless-stopped&lt;/code&gt;&lt;/strong&gt; - This makes sure that the container starts again on crashes and host restarts (unless it is manually stopped in which case it will not be started again when the host reboots).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;linuxserver/wireguard&lt;/code&gt;&lt;/strong&gt; - This is the image that is being used. This is the name of the image which will be downloaded from Docker Hub which is a repository of Docker images.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pressing enter will pull (download) the image and start the container in the background. You will get a long string of characters which is the container&apos;s ID. The VPN will initially take a little while to start up.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-10-19-wireguard-docker/docker-run.png&quot; alt=&quot;Creating the container&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;whythesys_moduleandlibmodules&quot;&gt;Why the SYS_MODULE and /lib/modules?&lt;/h3&gt;
&lt;p&gt;The reason that the VPN takes a little while to start up for the first time and the reason that we give the container permission to install kernel modules and mount the &lt;code&gt;/lib/modules&lt;/code&gt; folder is because WireGuard is actually run inside the Linux kernel. This is why it has such great performance and security.&lt;/p&gt;
&lt;h3 id=&quot;gettingclientconfigurations&quot;&gt;Getting client configurations&lt;/h3&gt;
&lt;p&gt;WireGuard will generate a configuration for each peer that we defined earlier on. These configurations contain connection information like the IP/domain of the VPN server and certificates for authentication.&lt;/p&gt;
&lt;p&gt;The easiest way to get the client configuration onto your device is to scan the generated QR codes when the container starts. These are only shown on the first run, so if you missed them you&apos;ll need to get the files instead. We can look at them by using the following command and simply scanning the QR code with your phone or tablet.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;docker logs wireguard
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-10-19-wireguard-docker/configs.png&quot; alt=&quot;WireGuard configurations as QR codes&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;It&apos;s best to also save the configurations to your local machine so that you have them for future use. To use these, simply open the &lt;code&gt;.conf&lt;/code&gt; file onto your device and open it in the WireGuard app. This command will copy the configurations to your home directory.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;sudo cp /var/lib/docker/volumes/wireguard_config/_data/peer*/*.conf ~
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now set up a device with a configuration and make sure that it works correctly. If it connects it will most likely be working properly, but it might be worth checking your IP address to make sure that it matches what you would expect (e.g. the IP address of your cloud server).&lt;/p&gt;
&lt;h2 id=&quot;settingupautomaticupdates&quot;&gt;Setting up automatic updates&lt;/h2&gt;
&lt;p&gt;Although the VPN is now working, it may be worth setting up automatic updates to make sure you have the best security. There is a tool called watchtower that is run inside a Docker container that checks all of your other Docker containers for updates and will automatically download the update and restart the container seamlessly.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;docker run -d \
           --name watchtower \
           -v /var/run/docker.sock:/var/run/docker.sock \
           containrrr/watchtower wireguard
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you have more containers that you would like to keep updated, it would be worth checking out the &lt;a href=&quot;https://containrrr.dev/watchtower/?ref=jakew.me&quot;&gt;watchtower documentation&lt;/a&gt; to make sure that you have it set up properly for how you would expect. I have mine set up by adding a label to each container that I would like to have updates on and leaving critical containers to be updated manually. You can also configure other useful things like notifications when containers get updated.&lt;/p&gt;
&lt;h2 id=&quot;wrapup&quot;&gt;Wrap Up&lt;/h2&gt;
&lt;p&gt;Maybe the ease of setting up this VPN has shown you how cool Docker can be. I have become a big fan of it over the last couple of years that I&apos;ve been experimenting with it.&lt;/p&gt;
&lt;p&gt;I haven&apos;t had much time to test out WireGuard yet except quickly at home, but I can notice that it&apos;s slightly more snappy than OpenVPN when browsing websites. The app is much more simple though and much more easy to use with just one tap to turn on the VPN after set up.&lt;/p&gt;
&lt;!--kg-card-end: markdown--&gt;</content:encoded><author>Jake Walker</author></item><item><title>Setting up a Raspberry Pi Zero W without a Display, Keyboard and Mouse</title><link>https://jakew.me/setting-up-a-raspberry-pi-headless/</link><guid isPermaLink="true">https://jakew.me/setting-up-a-raspberry-pi-headless/</guid><description>The right way to set up a Raspberry Pi Zero headlessly.</description><pubDate>Thu, 16 Apr 2020 12:53:27 GMT</pubDate><content:encoded>&lt;!--kg-card-begin: markdown--&gt;&lt;p&gt;I often find myself setting up a Raspberry Pi and I always forget the right way to set it up headlessly (without anything plugged in but power) so I thought I&apos;d write a quick post about how I end up setting them up.&lt;/p&gt;
&lt;p&gt;Although, I&apos;m referring to a Pi Zero W for this post, these same instructions can be applied to any Raspberry Pi with a built-in Wi-Fi adapter or a compatible USB Wi-Fi adapter.&lt;/p&gt;
&lt;h2 id=&quot;choosingansdcard&quot;&gt;Choosing an SD Card&lt;/h2&gt;
&lt;p&gt;Unlike most other things, it&apos;s quite important to have a good quality SD card for your Raspberry Pi. I have a small collection of ones that work fine in Raspberry Pis but if you&apos;re looking for a new one it&apos;s good to look out for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Class&lt;/strong&gt;. SD cards are given a class which summarises what kind of speeds you should expect to get from the SD card. Most places online recommend a class of 10 or higher. The SD card that I have below is class 10 (which you can see by the 10 in the small circle).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Size&lt;/strong&gt;. SD cards also come in a variety of sizes. 8GB is the recommended minimum for Raspbian but you could possibly fit it all on a smaller SD card.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-04-16-setting-up-a-raspberry-pi-headless/rpi-009.jpg&quot; alt=&quot;Micro SD Card&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;flashingthesdcard&quot;&gt;Flashing the SD card&lt;/h2&gt;
&lt;p&gt;The next step is to install the operating system onto the SD card. For this step you&apos;ll need a separate computer.&lt;/p&gt;
&lt;p&gt;Raspberry Pi have recently released a new tool called &apos;Raspberry Pi Imager&apos; that simplifies the process of flashing SD cards with the operating system.&lt;/p&gt;
&lt;p&gt;Firstly, you&apos;ll need to download the Raspberry Pi Imager for your specific operating system, you can find all of the links at the top of the &lt;a href=&quot;https://www.raspberrypi.org/downloads/?ref=jakew.me&quot;&gt;Raspberry Pi Downloads Page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;After you&apos;ve downloaded the imager, install and launch it:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-04-16-setting-up-a-raspberry-pi-headless/imager1.jpg&quot; alt=&quot;Raspberry Pi Imager&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;The next step is then to select which operating system you would like. There are many different official varieties. Most of the time you&apos;ll just want ordinary Raspbian which is the first item in the list.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-04-16-setting-up-a-raspberry-pi-headless/imager2.jpg&quot; alt=&quot;Raspberry Pi Imager Operating System Selection&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;As I&apos;m walking through a headless setup, I&apos;d recommend using Raspbian Lite which has no desktop (just a command line). You can find this by clicking &apos;Raspbian (other)&apos; and then selecting &apos;Raspbian Lite&apos;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-04-16-setting-up-a-raspberry-pi-headless/imager3.png&quot; alt=&quot;Raspberry Pi Imager with Selected Operating System&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;The next step is to connect the SD card that you want to flash. There might be only one device to choose from, but if not make sure that you are selecting one with a similar size to your SD card (it might not be exactly the same, mine shows up as 7GB in the list even though it is 8GB).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-04-16-setting-up-a-raspberry-pi-headless/imager4.png&quot; alt=&quot;Raspberry Pi Imager SD Card Selection&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;Finally, after selecting the operating system and SD card, you can hit the write button and wait.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-04-16-setting-up-a-raspberry-pi-headless/imager5.png&quot; alt=&quot;Raspberry Pi Imager&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;It could take a couple of minutes to download the image and flash it depending on your internet speed.&lt;/p&gt;
&lt;h2 id=&quot;settingupheadless&quot;&gt;Setting up headless&lt;/h2&gt;
&lt;p&gt;At this point, you could stop and plug in the SD card to your Raspberry Pi with a screen, keyboard and mouse and use it as usual. Or, you could add a couple of files to the SD card to make the Raspberry Pi connect to your Wi-Fi network and start SSH (which both would normally require you to configure manually).&lt;/p&gt;
&lt;p&gt;The Raspberry Pi Imager automatically ejects the SD card after it has been flashed so you might need to unplug and plug back in your SD card in order to see the files stored on it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-04-16-setting-up-a-raspberry-pi-headless/boot.png&quot; alt=&quot;SD Card in Windows Explorer showing as boot&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;enablingssh&quot;&gt;Enabling SSH&lt;/h3&gt;
&lt;p&gt;Open up the &apos;boot&apos; drive that shows up and in Windows you can hold down &lt;kbd&gt;Shift&lt;/kbd&gt; while right-clicking to bring up a menu with the option &apos;Open PowerShell window here&apos;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-04-16-setting-up-a-raspberry-pi-headless/ssh1.png&quot; alt=&quot;Selecting &amp;#x27;Open PowerShell window here&amp;#x27; on the &amp;#x27;boot&amp;#x27; drive&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;On Windows, you can then type the command &lt;code&gt;echo $null &gt;&gt; ssh&lt;/code&gt; to create a blank file called &apos;ssh&apos;. On Linux and MacOS, you can use the command &lt;code&gt;touch ssh&lt;/code&gt; in order to create the same blank file.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-04-16-setting-up-a-raspberry-pi-headless/ssh2.png&quot; alt=&quot;PowerShell window with the command to create a new blank file called &amp;#x27;ssh&amp;#x27;&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;Creating a blank file called &apos;ssh&apos; tells Raspbian to enable SSH.&lt;/p&gt;
&lt;h3 id=&quot;wifi&quot;&gt;Wi-Fi&lt;/h3&gt;
&lt;p&gt;The next step is to enable Wi-Fi on the Pi. This can be done by creating a new file called &apos;wpa_supplicant.conf&apos;.&lt;/p&gt;
&lt;p&gt;Make a new blank file in your text editor of choice, ensuring that line endings are set to &apos;LF&apos;, then copy the text below into the file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-conf&quot;&gt;ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1

network={
    ssid=&quot;network&quot;
    psk=&quot;password&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Replace &apos;network&apos; with your Wi-Fi network&apos;s name and &apos;password&apos; with your Wi-Fi network&apos;s password. Then save the file as &apos;wpa_supplicant.conf&apos; to the root of the SD card.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-04-16-setting-up-a-raspberry-pi-headless/wifi1.png&quot; alt=&quot;Visual Studio Code with &amp;#x27;wpa_supplicant.conf&amp;#x27; open&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;After doing both of those steps, you should now have 2 new files in the root of the SD card like so:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-04-16-setting-up-a-raspberry-pi-headless/files.png&quot; alt=&quot;The 2 new files in on the SD card&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;bootingup&quot;&gt;Booting Up&lt;/h2&gt;
&lt;p&gt;Now you&apos;re good to pop the SD card into the Raspberry Pi and connect power.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-04-16-setting-up-a-raspberry-pi-headless/rpi-011.jpg&quot; alt=&quot;Raspberry Pi Zero with SD card&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-04-16-setting-up-a-raspberry-pi-headless/rpi-012.jpg&quot; alt=&quot;Raspberry Pi Zero with SD card and Power&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;Then you can use an app like &lt;a href=&quot;https://www.fing.com/products/fing-app?ref=jakew.me&quot;&gt;Fing&lt;/a&gt; to scan your Wi-Fi network for a Raspberry Pi. Looking at the app, I found that my Pi&apos;s IP address is 192.168.1.125.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-04-16-setting-up-a-raspberry-pi-headless/fing.jpg&quot; alt=&quot;Screenshot of Fing showing the new Raspberry Pi&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;connectingviassh&quot;&gt;Connecting via SSH&lt;/h2&gt;
&lt;p&gt;To connect via SSH, you&apos;ll first need an SSH client. &lt;a href=&quot;https://termius.com/?ref=jakew.me&quot;&gt;Termius&lt;/a&gt; is a great cross-platform client, and is one that I use most of the time.&lt;/p&gt;
&lt;p&gt;To connect with Termius, click the &apos;New Host&apos; button at the top and type in the IP address in the &apos;Address&apos; box.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-04-16-setting-up-a-raspberry-pi-headless/termius1.png&quot; alt=&quot;Adding a new host for the Raspberry Pi in Termius&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;Then scroll down to the SSH configuration section and type in the username and password. By default the username is &apos;pi&apos; and the password is &apos;raspberry&apos; (you should probably change these when you get logged in).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-04-16-setting-up-a-raspberry-pi-headless/termius2.png&quot; alt=&quot;Setting the username and password for the new host in Termius&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;Finally, double-click on the new host that you&apos;ve created and wait for it to connect.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-04-16-setting-up-a-raspberry-pi-headless/termius3.png&quot; alt=&quot;Connecting to the new host in Termius&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;And bam, we&apos;re in. Now you can use your Raspberry Pi from anywhere in Wi-Fi range and without plugging anything in but power.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-04-16-setting-up-a-raspberry-pi-headless/termius4.png&quot; alt=&quot;Terminal over SSH in Termius&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;!--kg-card-end: markdown--&gt;</content:encoded><author>Jake Walker</author></item><item><title>Running WannaCry in a Virtual Machine</title><link>https://jakew.me/wannacry-vm/</link><guid isPermaLink="true">https://jakew.me/wannacry-vm/</guid><description>Setting up a virtual machine for safely running the WannaCry ransomware (and other malware).</description><pubDate>Fri, 28 Feb 2020 16:30:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;m part of a club in my Sixth Form called &lt;a href=&quot;https://cadscheme.co.uk/?ref=jakew.me&quot;&gt;CADS&lt;/a&gt; which among other things, involves learning about soft skills and cyber skills and applying those in the real world. Next week, we are attending an event where we are trying to spread awareness about Cyber Security and to achieve that we have decided to do a couple of demonstrations.&lt;/p&gt;
&lt;p&gt;I am in charge of setting up a demonstration about &lt;a href=&quot;https://en.wikipedia.org/wiki/WannaCry_ransomware_attack?ref=jakew.me&quot;&gt;WannaCry&lt;/a&gt; which is a &lt;a href=&quot;https://en.wikipedia.org/wiki/Ransomware?ref=jakew.me&quot;&gt;ransomware&lt;/a&gt; attack that targets Windows computers using an exploit called &lt;a href=&quot;https://en.wikipedia.org/wiki/EternalBlue?ref=jakew.me&quot;&gt;EternalBlue&lt;/a&gt;. It&apos;s the one that was prominent in the news during 2017 when it hit the UK&apos;s NHS and took down a lot of their computer network. You can read up some more about WannaCry on the &lt;a href=&quot;https://en.wikipedia.org/wiki/WannaCry_ransomware_attack?ref=jakew.me&quot;&gt;Wikipedia page&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;research&quot;&gt;Research&lt;/h2&gt;
&lt;p&gt;There are samples of malware (like WannaCry) available online to allow security researchers to reverse engineer them to find out how they work, how they can stop them and to add them to their anti-virus databases. During my research, &lt;a href=&quot;https://github.com/ytisf/theZoo?ref=jakew.me&quot;&gt;theZoo on GitHub&lt;/a&gt; seemed to be a great place for finding these malware samples.&lt;/p&gt;
&lt;p&gt;It is important to setup a safe environment for running the WannaCry sample. After doing some research from multiple sources, I found that the best way to run the sample of malware would be within a virtual machine with no internet connection and some extra settings to ensure that it is isolated from the host machine. I was also planning to use snapshots of the virtual machine so that it could easily be reset to a working version of Windows after it has been infected with the ransomware.&lt;/p&gt;
&lt;p&gt;Unpatched Windows XP and Windows 7 machines are vulnerable to WannaCry so I would need to use one of those operating systems. In the end I decided to go for Windows 7 because it&apos;s slightly newer and WannaCry works better on Windows 7 according to the &lt;a href=&quot;https://en.wikipedia.org/wiki/WannaCry_ransomware_attack?ref=jakew.me#Attack&quot;&gt;Wikipedia page&lt;/a&gt;. I found an Windows 7 disc which was older than 2016 so it wouldn&apos;t be patched against the EternalBlue exploit.&lt;/p&gt;
&lt;h2 id=&quot;setup&quot;&gt;Setup&lt;/h2&gt;
&lt;p&gt;Now that I had completed my research, I was ready to create a virtual machine for running WannaCry safely.&lt;/p&gt;
&lt;h3 id=&quot;setting-up-the-virtual-machine&quot;&gt;Setting up the Virtual Machine&lt;/h3&gt;
&lt;p&gt;I am using &lt;a href=&quot;https://www.vmware.com/uk/products/workstation-pro.html?ref=jakew.me&quot;&gt;VMware Workstation 15 Pro&lt;/a&gt; for creating the virtual machine, but &lt;a href=&quot;https://www.virtualbox.org/?ref=jakew.me&quot;&gt;VirtualBox&lt;/a&gt; would also work fine and can also create snapshots of virtual machines.&lt;/p&gt;
&lt;p&gt;I started off by creating a new Typical Virtual Machine:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-02-28-wannacry-vm/vm-wizard-1.png&quot; alt=&quot;VMware Virtual Machine Wizard&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;Then although I had in ISO file to use, I picked &apos;I will install the operating system&apos; later so that VMware didn&apos;t use it&apos;s Easy Install feature which skips through the installation wizard.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-02-28-wannacry-vm/vm-wizard-2.png&quot; alt=&quot;VMware Virtual Machine Wizard&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;Next up, I picked the version of Windows that I was using:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-02-28-wannacry-vm/vm-wizard-3.png&quot; alt=&quot;VMware Virtual Machine Wizard&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;And gave it a name&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-02-28-wannacry-vm/vm-wizard-4.png&quot; alt=&quot;VMware Virtual Machine Wizard&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;Then I created the virtual hard drive. The recommended size is 60 GB, but I decided to go for half of that as I wouldn&apos;t be doing anything other than copying some files into the VM and running WannaCry.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-02-28-wannacry-vm/vm-wizard-5.png&quot; alt=&quot;VMware Virtual Machine Wizard&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;Then I needed to give the VM slightly more processor, RAM and setup the ISO to be in the virtual machine&apos;s CD drive by clicking &apos;Customize Hardware...&apos;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-02-28-wannacry-vm/vm-wizard-6.png&quot; alt=&quot;VMware Virtual Machine Wizard&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;In the Memory tab, I increased the memory from 1024 MB to 2048 MB:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-02-28-wannacry-vm/vm-hardware-1.png&quot; alt=&quot;VMware Virtual Machine Hardware Configuration&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;In the Processors tab, I increased the number of processors from 1 to 2:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-02-28-wannacry-vm/vm-hardware-2.png&quot; alt=&quot;VMware Virtual Machine Hardware Configuration&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;Then I removed the Network Adapter by selecting it and hitting the remove button at the bottom of the menu.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-02-28-wannacry-vm/vm-hardware-3.png&quot; alt=&quot;VMware Virtual Machine Hardware Configuration&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;Then on the &apos;CD/DVD (SATA)&apos; tab, I selected the &apos;Use ISO image file&apos; and opened the ISO image that I had for Windows 7:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-02-28-wannacry-vm/vm-hardware-4.png&quot; alt=&quot;VMware Virtual Machine Hardware Configuration&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;Finally, I checked over the new VM&apos;s settings and hit Finish:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-02-28-wannacry-vm/vm-wizard-7.png&quot; alt=&quot;VMware Virtual Machine Wizard&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;setting-up-windows&quot;&gt;Setting up Windows&lt;/h3&gt;
&lt;p&gt;After creating the virtual machine, it was time to setup and install Windows 7. The ISO image was already mounted to the VM from the previous steps, so it is ready to boot.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-02-28-wannacry-vm/vm-boot.png&quot; alt=&quot;Initial Virtual Machine boot&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;Then I just went through the setup wizard as on a normal computer.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-02-28-wannacry-vm/vm-setup-1.png&quot; alt=&quot;Virtual Machine installing Windows 7&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;I created a new user called &apos;CADS&apos; and gave it a suitable name:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-02-28-wannacry-vm/vm-setup-2.png&quot; alt=&quot;Virtual Machine installing Windows 7&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;I clicked next when it asked for a product key as it doesn&apos;t matter that this VM isn&apos;t activated. Then I selected &apos;Use recommended settings&apos; and set my time zone, date and time. Then I was greeted with the Windows 7 desktop. At this point, it&apos;s a good idea to make sure that the virtual machine &lt;strong&gt;does not&lt;/strong&gt; have an internet connection.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-02-28-wannacry-vm/vm-done.png&quot; alt=&quot;Virtual Machine with Windows 7 Desktop&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;From my earlier research, it is recommended that you &lt;strong&gt;don&apos;t&lt;/strong&gt; install VMware tools, so if you are prompted by VMware to add them, it&apos;s best to ignore the message.&lt;/p&gt;
&lt;p&gt;One of the downsides of not installing VMware tools is that the screen is tiny. As I&apos;m doing a demonstration I can&apos;t really use it with a tiny screen resolution. I fixed this by going into the settings and changing the screen resolution and also turning on screen stretching in VMware so that it would scale the screen to the size of the window.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-02-28-wannacry-vm/vm-screen-res.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;I then customised Windows 7 a bit by adding a wallpaper, user avatar and adding some random files to the desktop, documents and pictures folders which would demonstrate the files getting encrypted. I copied all of the things I&apos;d need on the virtual machine to a USB drive plugged into my host machine.&lt;/p&gt;
&lt;p&gt;I then attached the USB drive to the virtual machine and copied all of the files off to their relevant places:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-02-28-wannacry-vm/vm-drive.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;malware-sample&quot;&gt;Malware Sample&lt;/h2&gt;
&lt;p&gt;Now that the virtual machine is setup, it&apos;s time to download the malware sample and copy it into the virtual machine.&lt;/p&gt;
&lt;p&gt;To download the sample, I went to &lt;a href=&quot;https://github.com/ytisf/theZoo/tree/master/malwares/Binaries/Ransomware.WannaCry?ref=jakew.me&quot;&gt;theZoo on GitHub&lt;/a&gt; on my host machine and went into &lt;code&gt;malwares&lt;/code&gt; → &lt;code&gt;Binaries&lt;/code&gt; → &lt;code&gt;Ransomware.WannaCry&lt;/code&gt; and then downloaded the &lt;code&gt;Ransomware.WannaCry.zip&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-02-28-wannacry-vm/wannacry-download.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;After downloading, Windows Defender should immediately detect that you have just downloaded malware and quarantine it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-02-28-wannacry-vm/defender.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;We need to allow the malware to be on our host computer. The malware is safe as long as it is inside the ZIP file. To do this, open up Windows Defender and find where the threat has been blocked and hit the allow button:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-02-28-wannacry-vm/defender-allow.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;Now that it is allowed, we can copy it onto the USB drive like with the other files (you might need to disconnect it from the virtual machine for it to show up on the host machine). Then connect the USB drive back to the virtual machine and copy the newly downloaded file onto the desktop (or another convenient folder).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-02-28-wannacry-vm/wannacry-copy.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;Now disconnect the USB drive from the virtual machine. It&apos;s best not to unzip the malware until it is about to be used.&lt;/p&gt;
&lt;h2 id=&quot;snapshot&quot;&gt;Snapshot&lt;/h2&gt;
&lt;p&gt;It&apos;s a good idea to take a snapshot of the virtual machine at this point. A snapshot is a &apos;copy&apos; of the virtual machine at it&apos;s current state with all of it&apos;s files and settings. Doing this will allow us to restore a snapshot after we have executed the malware.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-02-28-wannacry-vm/vm-snapshot.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;Give the snapshot a suitable name and then hit &apos;Take Snapshot&apos;. At the bottom of VMware, there will be a percentage of the progress of the snapshot (e.g. &apos;Saving state... 42%&apos;). Ensure that you wait until this message has disappeared before continuing. It&apos;s also best to not use the virtual machine whilst it is saving a snapshot.&lt;/p&gt;
&lt;h2 id=&quot;final-checks&quot;&gt;Final Checks&lt;/h2&gt;
&lt;p&gt;Now that the virtual machine is loaded with the malware and we have a snapshot of before the malware is executed, it is worth shutting down the virtual machine and checking the settings. Shut down the virtual machine by shutting down Windows 7 as normal.&lt;/p&gt;
&lt;p&gt;Go into the menu for editing the virtual machine&apos;s settings in VMware and double check the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;There is no &lt;strong&gt;network adapter&lt;/strong&gt;. If there is one, make sure that it has been removed.&lt;/li&gt;
&lt;li&gt;There is no &lt;strong&gt;CD/DVD drive&lt;/strong&gt; or &lt;strong&gt;printer&lt;/strong&gt;. Although this isn&apos;t important to remove, it&apos;s best to remove all of the ways that the malware could escape the virtual machine.&lt;/li&gt;
&lt;li&gt;In the &apos;Guest Isolation&apos; tab, uncheck &apos;Enable drag and drop&apos; and &apos;Enable copy and paste&apos;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then hit &apos;Save&apos;.&lt;/p&gt;
&lt;h2 id=&quot;the-demonstration&quot;&gt;The demonstration&lt;/h2&gt;
&lt;p&gt;Boot the virtual machine back up and double click on the &lt;code&gt;Ransomware.WannaCry.zip&lt;/code&gt; file on the Desktop and unzip the EXE that is contained inside. The password for unzipping is on the GitHub download page.&lt;/p&gt;
&lt;p&gt;Then simply double click on the EXE to start WannaCry and watch the carnage.&lt;/p&gt;
&lt;video src=&quot;https://ghost.jakew.me/content/media/2025/03/wannacry.mp4&quot; controls&gt;&lt;/video&gt;&lt;p&gt;Then to reset the virtual machine, click the restore snapshot button at the top of the window and wait for a couple of seconds.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2020-02-28-wannacry-vm/restore-snapshot.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;improvements&quot;&gt;Improvements&lt;/h2&gt;
&lt;p&gt;For the final demonstration at the event, I am planning on having a host-only network between two virtual machines so that I can show the malware spreading through a network of computers.&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;Hopefully this has been a small introduction on how to safely run malware on your own computer using a virtual machine. From here, you can do some analysis on the running malware using tools like &lt;a href=&quot;https://www.wireshark.org/download.html?ref=jakew.me&quot;&gt;Wireshark&lt;/a&gt; to check network activity, &lt;a href=&quot;https://github.com/volatilityfoundation?ref=jakew.me&quot;&gt;Volatility&lt;/a&gt; to look inside memory or &lt;a href=&quot;https://www.howtogeek.com/198679/how-to-use-regshot-to-monitor-your-registry/?ref=jakew.me&quot;&gt;Regshot&lt;/a&gt; for comparing the registry before and after executing the malware.&lt;/p&gt;
&lt;p&gt;Thanks for reading :)&lt;/p&gt;
</content:encoded><author>Jake Walker</author></item><item><title>Drone - Continuous Integration with Containers</title><link>https://jakew.me/drone/</link><guid isPermaLink="true">https://jakew.me/drone/</guid><description>Setting up and exploring Drone, a container focused continuous integration tool.</description><pubDate>Sat, 23 Nov 2019 20:24:29 GMT</pubDate><content:encoded>&lt;!--kg-card-begin: markdown--&gt;&lt;p&gt;In the past month, I have been playing around with &lt;a href=&quot;https://drone.io/?ref=jakew.me&quot;&gt;Drone&lt;/a&gt; which is probably my new favourite CI tool! In this article, I&apos;m going to talk about why I like it so much and how you can use it to test your own code.&lt;/p&gt;
&lt;p&gt;Lots of code projects will have tests (which are small functions that test a section of the project to check that it is working as intended). It would be very time consuming for maintainers of a project to keep running these tests after contributors have changed it, so this job can be automated using continuous integration. CI tools can detect when code has been changed, run all of the tests, alerting the project maintainers if there are any problems.&lt;/p&gt;
&lt;p&gt;As I mentioned before, Drone is one of these tools that automatically runs tests. However, there are many other tools like this available, such as &lt;a href=&quot;https://jenkins.io/?ref=jakew.me&quot;&gt;Jenkins&lt;/a&gt;, &lt;a href=&quot;http://travis-ci.com/?ref=jakew.me&quot;&gt;Travis CI&lt;/a&gt; and the new &lt;a href=&quot;https://github.com/features/actions?ref=jakew.me&quot;&gt;GitHub Actions&lt;/a&gt;. Lots of these have their advantages and some are more specialized for specific use cases, so if you don&apos;t feel like Drone is for you, maybe do some research!&lt;/p&gt;
&lt;h2 id=&quot;whydrone&quot;&gt;Why Drone?&lt;/h2&gt;
&lt;p&gt;Unlike the majority of CI tools, Drone uses &lt;a href=&quot;https://www.docker.com/resources/what-container?ref=jakew.me&quot;&gt;containers&lt;/a&gt; to run it&apos;s tests. This is a huge benefit because once the test is run, everything that was changed in the container is gone, so there are no changes to the host&apos;s operating system.&lt;/p&gt;
&lt;p&gt;I also like Drone because of how simple it is to get up and running as well as how easy it is to setup a testing pipeline (as we&apos;ll see later in this post).&lt;/p&gt;
&lt;p&gt;The last point I like about Drone is that you host it all yourself. Travis CI and GitHub Actions are hosted which means that the company manages the infrastructure, not you. This does have it&apos;s advantages with excellent uptime and an &apos;it just works&apos; experience. However, once you need to start needing building closed source projects or have large or lots of projects, you start running into problems with pricing. As with Drone you&apos;re always in control of your own infrastructure, you can always scale up when you need to build more simultaneous projects and you will probably be paying less than buying a hosted CI.&lt;/p&gt;
&lt;h2 id=&quot;settingupdrone&quot;&gt;Setting up Drone&lt;/h2&gt;
&lt;p&gt;Drone is run inside &lt;a href=&quot;https://opensource.com/resources/what-docker?ref=jakew.me&quot;&gt;Docker&lt;/a&gt; which there are only a couple of commands to get it up and running. I&apos;ll be showing how you install it on a fresh Ubuntu server, but these commands should work on most major Linux distributions and even on a VM or spare computer.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Specifically, I&apos;m using a CX11 instance from &lt;a href=&quot;https://www.hetzner.com/cloud?ref=jakew.me&quot;&gt;Hetzner Cloud&lt;/a&gt; running Ubuntu Server 18.04. On the server where I run my main instance of Drone, I also run other applications with no problems.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;You&apos;ll need a server with a public IP address, there are no minimum requirements to run Drone, but I&apos;d recommend at least 1 GB of RAM and 1 CPU core. &lt;em&gt;At idle, both the server and runner use about 15MB RAM and little CPU, so extremely lightweight.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&quot;installingdocker&quot;&gt;Installing Docker&lt;/h3&gt;
&lt;p&gt;The quickest and easiest way to install Docker is using their installer script. This script works on most major Linux distros (even Raspbian). &lt;strong&gt;Make sure you run this as root!&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you have any ordinary user accounts that you&apos;d like to be able to use Docker, you can use this command to give them permission to do so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;sudo usermod -aG docker username
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;installingdrone&quot;&gt;Installing Drone&lt;/h3&gt;
&lt;p&gt;Next up, installing Drone itself. Drone is installed differently depending on which provider you use for your source code (at the moment GitHub, GitLab, Gitea, Gogs, Bitbucket Cloud, Bitbucket Server are supported). I&apos;m going to be setting up a GitHub instance, but if you want to setup something else take a look at the relevant installation instructions &lt;a href=&quot;https://docs.drone.io/installation/providers/?ref=jakew.me&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/settings/applications/new?ref=jakew.me&quot;&gt;Create a OAuth Application&lt;/a&gt; on GitHub.&lt;/strong&gt; For the application name, put something like &apos;Jake&apos;s Drone&apos;, for homepage URL, put the URL of where you will be hosting your Drone instance whether it&apos;s an IP address or domain name (e.g. &lt;code&gt;http://drone.example.com/&lt;/code&gt; or &lt;code&gt;http://10.20.30.40&lt;/code&gt;) and finally for the Authorization URL put the domain of your drone instance plus &lt;code&gt;/login&lt;/code&gt; at the end (e.g. &lt;code&gt;http://drone.example.com/login&lt;/code&gt; or &lt;code&gt;http://10.20.30.40/login&lt;/code&gt;). After that, take a note of the client ID and client secret that you get.&lt;/p&gt;
&lt;p&gt;Here&apos;s the information for my instance when creating the OAuth application:&lt;br&gt;
&lt;img src=&quot;https://ghost.jakew.me/content/images/2019-11-23-drone/github_oauth_1.png&quot; alt=&quot;Creating an OAuth Application on GitHub&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;And here&apos;s the client ID and secret that we get:&lt;br&gt;
&lt;img src=&quot;https://ghost.jakew.me/content/images/2019-11-23-drone/github_oauth_2.png&quot; alt=&quot;Client ID and secret from GitHub&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now we need a secret code will be used for encrypting communications between the runner (which we&apos;ll get onto later) and the server. Run this command to get a suitable key:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;openssl rand -hex 16
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;And now we need to download Drone through Docker, we need to use the command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;docker pull drone/drone:1
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Finally, we are going to start up the Drone server through Docker with this command. Make sure to substitute all of the placeholders in angle brackets &lt;code&gt;&amp;#x3C;&gt;&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;docker run --volume /var/lib/drone:/data --env DRONE_AGENTS_ENABLED=true --env DRONE_GITHUB_SERVER=https://github.com --env DRONE_GITHUB_CLIENT_ID=&amp;#x3C;GITHUB_CLIENT_ID&gt; --env DRONE_GITHUB_CLIENT_SECRET=&amp;#x3C;GITHUB_CLIENT_SECRET&gt; --env DRONE_RPC_SECRET=&amp;#x3C;SECRET_KEY&gt; --env DRONE_SERVER_HOST=&amp;#x3C;YOUR_DOMAIN_OR_IP&gt; --env DRONE_SERVER_PROTO=http --env DRONE_USER_CREATE=username:&amp;#x3C;YOUR_GITHUB_USERNAME&gt;,admin:true --env DRONE_USER_FILTER=&amp;#x3C;YOUR_GITHUB_USERNAME&gt; --publish=80:80 --publish 443:443 --restart=always --detach=true --name drone drone/drone:1
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That&apos;s a long command right! Here&apos;s a breakdown of what the different parameters are doing:&lt;/p&gt;
&lt;details&gt;
  &lt;summary&gt;Parameter explanations&lt;/summary&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;code&gt;--volume /var/lib/drone:/data&lt;/code&gt; tells Docker to permanently store the folder &lt;code&gt;/data&lt;/code&gt; in the container to &lt;code&gt;/var/lib/drone&lt;/code&gt; on the host. If this wasn&apos;t set, whenever the container is restarted, data might be lost and we&apos;d have to setup Drone again.&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;--env ...&lt;/code&gt; tells Docker to set an environment variable inside of the container. It&apos;s a way of giving the application inside the container some basic configuration.&lt;/li&gt;
    &lt;ul&gt;
    &lt;li&gt;&lt;code&gt;DRONE_AGENTS_ENABLED=true&lt;/code&gt; sets that we want the server to communicate with the agents/runners that we&apos;ll setup later.&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;DRONE_GITHUB_SERVER=https://github.com&lt;/code&gt; tells Drone which GitHub we are talking to (this would change if you were hosting your own GitHub Enterprise instance, but for most people we just want to be talking to the normal GitHub)&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;DRONE_GITHUB_CLIENT_ID=...&lt;/code&gt; is used for handling authentication with GitHub, allowing users to login with their GitHub credentials to Drone.&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;DRONE_GITHUB_CLIENT_SECRET=...&lt;/code&gt; same as above.&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;DRONE_RPC_SECRET=...&lt;/code&gt; is a secret key that is used for the server when communicating to the runners and vice-versa.&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;DRONE_SERVER_HOST=...&lt;/code&gt; is the place where the Drone instance is being hosted.&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;DRONE_SERVER_PROTO=http&lt;/code&gt; tells Drone whether we are using HTTP or HTTPS. It makes sure that all of the links that it uses are either HTTP or HTTPS.&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;DRONE_USER_CREATE=username:...,admin=true&lt;/code&gt; will make sure that our GitHub account is an adminstrator of the Drone instance. It will allow you to use the Drone CLI with your account to do things with the server.&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;DRONE_USER_FILTER=...&lt;/code&gt; only let&apos;s this list of users to make accounts, if you want to add more you can separate the list with commas (e.g. &lt;code&gt;bob,jeff,alex&lt;/code&gt;)&lt;/li&gt;
    &lt;/ul&gt;
    &lt;li&gt;&lt;code&gt;--publish=80:80&lt;/code&gt; tells Docker that any traffic coming in or out of port 80 on the host should be forwarded to port 80 inside the container.&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;--publish=443:443&lt;/code&gt; is the same as above but for port 443 which is HTTPS.&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;--restart=always&lt;/code&gt; makes sure that the container is kept running all the time. If this wasn&apos;t set and the server was rebooted, the Drone server would not automatically start, additionally, if the Drone server crashes it will be restarted automatically.&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;--detach=true&lt;/code&gt; makes this container run in the background.&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;--name drone&lt;/code&gt; gives this container a name, useful for running any commands on the container (e.g. &lt;code&gt;docker logs drone&lt;/code&gt; will get the logs of the container).&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;drone/drone:1&lt;/code&gt; tells Docker which image that should be used for this container.&lt;/li&gt;
  &lt;/ul&gt;
&lt;/details&gt;
&lt;p&gt;OK, now you&apos;ve got a &lt;em&gt;hopefully&lt;/em&gt; working Drone server! Open up your favourite web browser and head over your your server&apos;s IP address.&lt;/p&gt;
&lt;p&gt;You&apos;ll be redirected to a GitHub authorization page:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2019-11-23-drone/github_auth.png&quot; alt=&quot;GitHub Authorization Page&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;And then you&apos;ll get a list of your repositories after a couple of seconds:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2019-11-23-drone/drone_home.png&quot; alt=&quot;Drone Homepage with list of repositories&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;You can click the &apos;Activate&apos; button by any of your repositories to let Drone build them automatically, but nothing will happen without a runner.&lt;/p&gt;
&lt;h3 id=&quot;creatingadronerunner&quot;&gt;Creating a Drone Runner&lt;/h3&gt;
&lt;p&gt;To make the Drone instance functional, we need a runner. This is a small application that runs all of the tests that we define in our project. We&apos;ll be setting up a Docker runner which runs the tests inside Docker containers (you can also get a Kubernetes Runner, Exec Runner (runs commands inside a shell on a host), SSH Runner (runs commands on a remote host) and Digital Ocean Runner (runs tests inside a fresh Digital Ocean server)). There are reasons why you might want to use a different type of runner, so you might want to research which one might be best for you and your project, however the Docker runner will work best for the majority of projects if you are just building on Linux. You can read the installation instructions for all of the different runner types &lt;a href=&quot;https://docs.drone.io/installation/runners/?ref=jakew.me&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;For Windows builds, an Exec runner is the best to use as Docker is a bit janky on Windows.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;These instructions are for installing the Docker runner on Linux. It does not need to be run on the same server as the Drone server, but I will be doing that.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Download the runner image through Docker:&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;docker pull drone/drone-runner-docker:1
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;Run the runner:&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;docker run -d -v /var/run/docker.sock:/var/run/docker.sock -e DRONE_RPC_PROTO=http -e DRONE_RPC_HOST=&amp;#x3C;YOUR_DRONE_INSTANCE_DOMAIN_OR_IP&gt; -e DRONE_RPC_SECRET=&amp;#x3C;SECRET_KEY_FROM_INSTALLING_DRONE_SERVER&gt; -e DRONE_RUNNER_CAPACITY=2 -e DRONE_RUNNER_NAME=${HOSTNAME} -p 3000:3000 --restart always --name runner drone/drone-runner-docker:1
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The parameters have the same meanings as when we installed the Drone server. &lt;code&gt;DRONE_RUNNER_CAPACITY&lt;/code&gt; tells the server how many things can be built at once, unless you will be building lots of projects at the same time &lt;code&gt;2&lt;/code&gt; is a good value to go for.&lt;/p&gt;
&lt;p&gt;Now we are ready to build something!&lt;/p&gt;
&lt;h2 id=&quot;usingdrone&quot;&gt;Using Drone&lt;/h2&gt;
&lt;p&gt;To demonstrate how Drone works, I&apos;m going to walk through how to setup a project to test. You should be able to follow along with this demonstration as all of my code is available &lt;a href=&quot;https://github.com/jake-walker/python-ci-example/?ref=jakew.me&quot;&gt;here&lt;/a&gt;, I&apos;ll link to the specific version of the repository that I&apos;m at throughout this section.&lt;/p&gt;
&lt;h3 id=&quot;thetestsubject&quot;&gt;The Test Subject&lt;/h3&gt;
&lt;p&gt;I don&apos;t want to go much into coding with this blog post, but I&apos;ll be testing a simple Python application:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;sum.py&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def sum(a, b):
    return a +b

print(sum(4,6))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So in this example, I&apos;m using the Python library &lt;code&gt;flake8&lt;/code&gt; to test that my code is conforming to a set of nice styles. I&apos;ve purposely formatted my code badly so that the tests will not pass.&lt;/p&gt;
&lt;p&gt;To use this code on my own computer, I&apos;ll need to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Install the Python libraries that I&apos;m using: &lt;code&gt;python -m pip install -r requirements.txt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Run flake8 to check for errors: &lt;code&gt;flake8&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;With the code above, I&apos;m getting 3 errors from flake8 on my computer:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;➜ flake8
./sum.py:2:15: E225 missing whitespace around operator
./sum.py:4:1: E305 expected 2 blank lines after class or function definition, found 1
./sum.py:4:12: E231 missing whitespace after &apos;,&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;OK, now that I&apos;ve got something to test, I&apos;m going to commit my code to GitHub. Take a look at my &lt;a href=&quot;https://github.com/jake-walker/python-ci-example/tree/026b0c8a9ed7890a1f5d200b88ae80a104451d96?ref=jakew.me&quot;&gt;commit here&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;activatingtherepository&quot;&gt;Activating the Repository&lt;/h3&gt;
&lt;p&gt;Now let&apos;s head over to Drone and activate that repository:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2019-11-23-drone/drone_activate.png&quot; alt=&quot;Activating my repository through Drone&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;After clicking Activate, you&apos;ll get the settings of your repository:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2019-11-23-drone/drone_repo_config.png&quot; alt=&quot;Repository Configuration on Drone&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;Here you can set:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Project Webhooks&lt;/strong&gt; where you can configure to disable checking for pull requests or forks of your repo.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Project Settings&lt;/strong&gt; where you can set:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Protected&lt;/strong&gt; - let&apos;s you sign your build configuration and don&apos;t run it unless it is signed. Read more &lt;a href=&quot;https://docs.drone.io/configure/signature/?ref=jakew.me&quot;&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Trusted&lt;/strong&gt; - let&apos;s your Docker containers run in privileged mode, which allows them to have unlimited access to the host.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Timeout&lt;/strong&gt; - the time for one build before Drone goes &quot;Welp, this isn&apos;t going to finish. Might as well stop&quot;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Configuration&lt;/strong&gt; - the name of the file to look for instruction in (we&apos;ll come onto this later).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Secrets&lt;/strong&gt; - variables that can be used in your test which you don&apos;t want people to see (e.g. passwords).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cron Jobs&lt;/strong&gt; - running tests on a regular schedule (e.g. hourly, daily, weekly, monthly)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Badges&lt;/strong&gt; - little status icons that you can add onto your repository.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You might have noticed under the &apos;Activity Feed&apos; tab that nothing has happened since we have added this repository. That&apos;s because we actually haven&apos;t told Drone what to do with all of this code. Let&apos;s make a configuration file to do so.&lt;/p&gt;
&lt;h3 id=&quot;creatingapipeline&quot;&gt;Creating a Pipeline&lt;/h3&gt;
&lt;p&gt;Back in our code we&apos;ll make this simple config file with all of the instructions that we need. I&apos;m not going to go over every single option you can put in this config file so you can have a look at the extra awesome things you can do &lt;a href=&quot;https://docker-runner.docs.drone.io/configuration/?ref=jakew.me&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;.drone.yml&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;---
kind: pipeline
type: docker
name: default

steps:
  - name: lint
    image: python:3.7-alpine
    commands:
      - python -m pip install -r requirements.txt
      - flake8
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This file is pretty small right? Here&apos;s what everything means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;kind: pipeline&lt;/code&gt; - I&apos;m not totally sure what else this would be, but this tells Drone that we are defining a new pipeline.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;type: docker&lt;/code&gt; - This tells Drone to run this pipeline on a Docker runner.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;name: default&lt;/code&gt; - This is the name of the pipeline, this can be anything really.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;steps:&lt;/code&gt; - This is the list of instructions that are executed &lt;strong&gt;in order&lt;/strong&gt; to test the code.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;- name: lint&lt;/code&gt; - The hyphen at the start indicates a new &apos;step&apos; block. Also in this line we are defining the name of this step, this can also be anything.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;image: python:3.7-alpine&lt;/code&gt; - This is the Docker image that is going to be used for building this code. This particular image &lt;code&gt;python:3.7-alpine&lt;/code&gt; has Python pre-installed.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;commands:&lt;/code&gt; is a list of commands that should be executed in this step&lt;/li&gt;
&lt;li&gt;&lt;code&gt;python -m pip install -r requirements.txt&lt;/code&gt; this runs that exact command inside the container which installs all of the extra packages that we need for the program to run (this would be specific to your project)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;flake8&lt;/code&gt; is the command that tests our code.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So how does Drone know that the test failed? Every command on Linux has an exit code which can be any number. If the command finishes successfully the command sends an exit code of &lt;code&gt;0&lt;/code&gt;, otherwise it might give an exit code of &lt;code&gt;1&lt;/code&gt;. Any code which isn&apos;t &lt;code&gt;0&lt;/code&gt; indicates the command didn&apos;t run successfully, and hence Drone can tell whether the test fails. Any commands that fail in the &lt;code&gt;commands&lt;/code&gt; section of the config will stop the test and mark it as a failure.&lt;/p&gt;
&lt;p&gt;After &lt;a href=&quot;https://github.com/jake-walker/python-ci-example/tree/7616f1dd6a5f408fe13c0a18702f45525ca91c45?ref=jakew.me&quot;&gt;committing this config file&lt;/a&gt;, Drone will actually start running the test. If you look at Drone now you&apos;ll be able to see the test running:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2019-11-23-drone/drone_build.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;And when we click on the test above, we can see the separate steps (along the sidebar) that this build contains and whether they have failed or passed. The yellow cog shows that the &lt;code&gt;lint&lt;/code&gt; step is in progress:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2019-11-23-drone/drone_build_1.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;Oh no! Our test has failed:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2019-11-23-drone/drone_build_fail.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;If you take a look closer, we are getting the same output as I got when I ran the test command on my computer.&lt;/p&gt;
&lt;p&gt;OK now that we know that our test fails properly, &lt;a href=&quot;https://github.com/jake-walker/python-ci-example/tree/b081863a2b9d214eeb2011ddaee880168dd361ff?ref=jakew.me&quot;&gt;let&apos;s fix the code&lt;/a&gt;. After committing the changes, we can see that Drone starts another build and it passes!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2019-11-23-drone/drone_build_success.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;wrappingup&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;So hopefully I&apos;ve been able to show you how Drone is so awesome and how to use it. Even if you don&apos;t have a server to host your own instance of Drone, you can still use their &lt;strong&gt;free&lt;/strong&gt;, &lt;strong&gt;hosted&lt;/strong&gt; one &lt;a href=&quot;https://cloud.drone.io/?ref=jakew.me&quot;&gt;here&lt;/a&gt;, or alternatively you could use a similar CI tool which is free and hosted (such as &lt;a href=&quot;https://github.com/features/actions?ref=jakew.me&quot;&gt;GitHub Actions&lt;/a&gt; (which also uses containers, but different config files) and &lt;a href=&quot;http://travis-ci.com/?ref=jakew.me&quot;&gt;Travis CI&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;If you need any help setting up your own instance or making config files or anything like that, don&apos;t hesitate to &lt;a href=&quot;https://jakew.me/contact/?ref=jakew.me&quot;&gt;ask me for help&lt;/a&gt;! Thanks for reading :)&lt;/p&gt;
&lt;!--kg-card-end: markdown--&gt;</content:encoded><author>Jake Walker</author></item><item><title>Getting a ST7735 TFT Display to work with a Raspberry Pi</title><link>https://jakew.me/st7735-pi/</link><guid isPermaLink="true">https://jakew.me/st7735-pi/</guid><description>Getting an ST7735 TFT Display working with a Raspberry Pi.</description><pubDate>Fri, 19 Jan 2018 18:37:00 GMT</pubDate><content:encoded>&lt;!--kg-card-begin: markdown--&gt;&lt;p&gt;I recently bought a cheap TFT display for a project I was doing and after a bit of research I decided upon a &lt;a href=&quot;https://www.ebay.co.uk/itm/282469570479?ref=jakew.me&quot;&gt;1.8&quot; 128x160 ST7735 Display&lt;/a&gt; from &lt;a href=&quot;https://www.ebay.co.uk/usr/karens_e-shop?ref=jakew.me&quot;&gt;Karen&apos;s eShop&lt;/a&gt; on eBay, it&apos;s very affordable and looked great for my project. On the product page, the seller has got some really great, detailed instructions for getting the screen to work on an Arduino, however, I couldn&apos;t find anywhere online for getting this screen to work on a Raspberry Pi. This post will quickly save you the trial and error of getting a ST7735 working with a Raspberry Pi.&lt;/p&gt;
&lt;h2 id=&quot;wiring&quot;&gt;Wiring&lt;/h2&gt;
&lt;p&gt;Firstly, the display needs to be wired correctly to the Raspberry Pi. &lt;a href=&quot;https://pinout.xyz/?ref=jakew.me&quot;&gt;This site&lt;/a&gt; will come in handy for finding which pins are where.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Screen Pin&lt;/th&gt;
&lt;th&gt;Raspberry Pi Pin&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GND&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Ground (pins 6, 9, 14, 20, 25, 30, 34 or 39)&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VCC&lt;/td&gt;
&lt;td&gt;&lt;em&gt;5v Power (pins 2 or 4)&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SCL&lt;/td&gt;
&lt;td&gt;GPIO 11 (pin 23)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SDA&lt;/td&gt;
&lt;td&gt;GPIO 10 (pin 19)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RES&lt;/td&gt;
&lt;td&gt;GPIO 25 (pin 22)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DC&lt;/td&gt;
&lt;td&gt;GPIO 24 (pin 18)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CS&lt;/td&gt;
&lt;td&gt;GPIO 8 (pin 24)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BL&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Not connected&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;When powering on your Raspberry Pi, the screen will show white by default. You will need to use Python to now display images, more on that below.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2018-01-19-st7735-pi/IMG_0363.JPG&quot; alt=&quot;Screen showing white&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;software&quot;&gt;Software&lt;/h2&gt;
&lt;p&gt;I found a &lt;a href=&quot;https://github.com/pimoroni/st7735-python?ref=jakew.me&quot;&gt;library&lt;/a&gt; for the screens using the ST7735 chip on GitHub. The instructions and examples on GitHub will work fine, but this is for a different size of the screen, the instructions and example code here will work from the 128x160 display from Karen&apos;s eShop.&lt;/p&gt;
&lt;p&gt;Firstly, you need to enable SPI on your Raspberry Pi. You can do this by using the Raspberry Pi configuration tool:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash{promptUser:&quot;&gt;sudo raspi-config
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once in the configuration, select &apos;3 - Interface Options&apos;, then &apos;P4 - SPI&apos;, select &apos;Yes&apos;&lt;/p&gt;
&lt;p&gt;Now, you&apos;ll need to install the dependencies and the ST7735 library itself:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash{promptUser:&quot;&gt;sudo python3 -m pip install RPi.GPIO spidev Pillow numpy
sudo python3 -m pip install st7735
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;examplecode&quot;&gt;Example Code&lt;/h2&gt;
&lt;p&gt;Here is some example code that I used to print out &apos;Hello World!&apos; to my display, modified from one of the examples in the GitHub repository.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont

import ST7735

disp = ST7735.ST7735(port=0, cs=0, dc=24, backlight=None, rst=25, width=128, height=160, rotation=0, invert=False)

WIDTH = disp.width
HEIGHT = disp.height

img = Image.new(&apos;RGB&apos;, (WIDTH, HEIGHT))
draw = ImageDraw.Draw(img)

# Load default font.
font = ImageFont.load_default()

# Write some text
draw.text((5, 5), &quot;Hello World!&quot;, font=font, fill=(255, 255, 255))

# Write buffer to display hardware, must be called to make things visible on the
# display!
disp.display(img)
&lt;/code&gt;&lt;/pre&gt;
&lt;!--kg-card-end: markdown--&gt;</content:encoded><author>Jake Walker</author></item><item><title>Introduction to Electron Apps</title><link>https://jakew.me/electron-introduction/</link><guid isPermaLink="true">https://jakew.me/electron-introduction/</guid><description>Creating a simple desktop app with JavaScript, HTML and CSS using Electron.</description><pubDate>Sun, 24 Jul 2016 09:21:00 GMT</pubDate><content:encoded>&lt;!--kg-card-begin: markdown--&gt;&lt;p&gt;Yesterday, I was downloading Visual Studio and then I thought to myself: &lt;em&gt;What if I could write desktop apps with Node.JS, HTML and CSS?&lt;/em&gt;. After a bit of Googling around I found something called Electron.&lt;/p&gt;
&lt;p&gt;Electron is made by GitHub and it&apos;s Atom editor uses it. Essentially it is an iframe in a window. But of coarse nothing is as simple as that. It also manages the processes.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This tutorial was written for Windows but I&apos;m sure it&apos;s not too hard to replicate on Linux &lt;em&gt;(or other)&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;installingnodejs&quot;&gt;Installing Node.JS&lt;/h2&gt;
&lt;p&gt;First your going to want to &lt;a href=&quot;https://nodejs.org/en/?ref=jakew.me&quot;&gt;Download Node.JS&lt;/a&gt;. Download the one on the right.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2016-07-24-electron-introduction/3mGIZfV.png&quot; alt=&quot;Download Node.JS&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;Then once you&apos;ve installed, go to a command line &lt;code&gt;WIN-R&lt;/code&gt; then type &lt;code&gt;cmd&lt;/code&gt;. Then type&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;node -v
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and you should get something back like&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;v6.2.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And also you might want to get Git &lt;a href=&quot;https://git-scm.com/download/win?ref=jakew.me&quot;&gt;here for Windows&lt;/a&gt; (&lt;a href=&quot;https://git-scm.com/download/linux?ref=jakew.me&quot;&gt;Linux&lt;/a&gt;, &lt;a href=&quot;https://git-scm.com/download/mac?ref=jakew.me&quot;&gt;Mac&lt;/a&gt;).&lt;/p&gt;
&lt;h2 id=&quot;writinganapp&quot;&gt;Writing an App&lt;/h2&gt;
&lt;p&gt;Firstly, navigate to where ever you want your app to live:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;mkdir &quot;My Electron Apps&quot;
cd &quot;My Electron Apps&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Clone the quick start repository:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;git clone https://github.com/electron/electron-quick-start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Navigate to the folder where it cloned to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;cd electron-quick-start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Install the node packages: &lt;em&gt;(This may take a while)&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;npm install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Start the application:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;npm start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and you should see this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2016-07-24-electron-introduction/JymmzQn.png&quot; alt=&quot;Application&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;Now let&apos;s dive into the code and make some changes. I am using &lt;a href=&quot;http://atom.io/?ref=jakew.me&quot;&gt;Atom&lt;/a&gt; to edit these files.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2016-07-24-electron-introduction/Oml6eCp.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;Opening &lt;code&gt;index.html&lt;/code&gt; shows the HTML shown in the main screen and it looks like it has Node inline.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;#x3C;!DOCTYPE html&gt;
&amp;#x3C;html&gt;
  &amp;#x3C;head&gt;
    &amp;#x3C;meta charset=&quot;UTF-8&quot;&gt;
    &amp;#x3C;title&gt;Hello World!&amp;#x3C;/title&gt;
  &amp;#x3C;/head&gt;
  &amp;#x3C;body&gt;
    &amp;#x3C;h1&gt;Hello World!&amp;#x3C;/h1&gt;
    &amp;#x3C;!-- All of the Node.js APIs are available in this renderer process. --&gt;
    We are using node &amp;#x3C;script&gt;document.write(process.versions.node)&amp;#x3C;/script&gt;,
    Chromium &amp;#x3C;script&gt;document.write(process.versions.chrome)&amp;#x3C;/script&gt;,
    and Electron &amp;#x3C;script&gt;document.write(process.versions.electron)&amp;#x3C;/script&gt;.
  &amp;#x3C;/body&gt;

  &amp;#x3C;script&gt;
    // You can also require other files to run in this process
    require(&apos;./renderer.js&apos;)
  &amp;#x3C;/script&gt;
&amp;#x3C;/html&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;OK, now let&apos;s try and add a library and see if it works. Go back to the command line and type:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;npm install request --save
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;--save&lt;/code&gt; make the package manager save the library to the dependencies so if someone else wanted to use your app it would automagically install for them.&lt;/p&gt;
&lt;p&gt;After it&apos;s installed, you can now include it into the project. Go to &lt;code&gt;index.html&lt;/code&gt; then at the bottom there is a &lt;code&gt;&amp;#x3C;script&gt;&lt;/code&gt; tag that looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;#x3C;script&gt;
  // You can also require other files to run in this process
  require(&apos;./renderer.js&apos;)
&amp;#x3C;/script&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Add &lt;code&gt;var request = require(&apos;request&apos;)&lt;/code&gt; to include the request library.&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;request(&apos;http://random.cat/meow&apos;, function (error, response, body) { })&lt;/code&gt; to fetch the cat image.&lt;/li&gt;
&lt;li&gt;Then inside the request function add &lt;code&gt;if (!error &amp;#x26;&amp;#x26; response.statusCode == 200) { }&lt;/code&gt; which will check that there are no errors like the site being down.&lt;/li&gt;
&lt;li&gt;Then inside the if statement add &lt;code&gt;var jsonObj = JSON.parse(body)&lt;/code&gt; which will parse the output from the website and make it easy to extract the image URL.&lt;/li&gt;
&lt;li&gt;Then underneath the last line add &lt;code&gt;var image = document.getElementById(&apos;myImage&apos;)&lt;/code&gt; which will search the file for an element with the id of &apos;myImage&apos;.&lt;/li&gt;
&lt;li&gt;Then, again underneath the last line add &lt;code&gt;image.setAttribute(&quot;src&quot;, jsonObj.file)&lt;/code&gt; which will set the image URL of the image tag to the image of the cat.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All that code should look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;#x3C;script&gt;
  // You can also require other files to run in this process
  require(&apos;./renderer.js&apos;)
  var request = require(&apos;request&apos;)

  request(&apos;http://random.cat/meow&apos;, function (error, response, body) {
    if (!error &amp;#x26;&amp;#x26; response.statusCode == 200) {
      var jsonObj = JSON.parse(body)
      var image = document.getElementById(&apos;myImage&apos;)
      image.setAttribute(&quot;src&quot;, jsonObj.file)
    }
  })
&amp;#x3C;/script&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Make sure to add the img tag to the HTML like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;#x3C;h1&gt;Hello World!&amp;#x3C;/h1&gt;
&amp;#x3C;!-- All of the Node.js APIs are available in this renderer process. --&gt;
&amp;#x3C;img id=&quot;myImage&quot;&gt;
We are using node &amp;#x3C;script&gt;document.write(process.versions.node)&amp;#x3C;/script&gt;,
Chromium &amp;#x3C;script&gt;document.write(process.versions.chrome)&amp;#x3C;/script&gt;,
and Electron &amp;#x3C;script&gt;document.write(process.versions.electron)&amp;#x3C;/script&gt;.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;the whole &lt;code&gt;index.html&lt;/code&gt; should look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;#x3C;!DOCTYPE html&gt;
&amp;#x3C;html&gt;
  &amp;#x3C;head&gt;
    &amp;#x3C;meta charset=&quot;UTF-8&quot;&gt;
    &amp;#x3C;title&gt;Hello World!&amp;#x3C;/title&gt;
  &amp;#x3C;/head&gt;
  &amp;#x3C;body&gt;
    &amp;#x3C;h1&gt;Hello World!&amp;#x3C;/h1&gt;
    &amp;#x3C;!-- All of the Node.js APIs are available in this renderer process. --&gt;
    &amp;#x3C;img id=&quot;myImage&quot;&gt;
    We are using node &amp;#x3C;script&gt;document.write(process.versions.node)&amp;#x3C;/script&gt;,
    Chromium &amp;#x3C;script&gt;document.write(process.versions.chrome)&amp;#x3C;/script&gt;,
    and Electron &amp;#x3C;script&gt;document.write(process.versions.electron)&amp;#x3C;/script&gt;.
  &amp;#x3C;/body&gt;

  &amp;#x3C;script&gt;
    // You can also require other files to run in this process
    require(&apos;./renderer.js&apos;)
    var request = require(&apos;request&apos;)

    request(&apos;http://random.cat/meow&apos;, function (error, response, body) {
      if (!error &amp;#x26;&amp;#x26; response.statusCode == 200) {
        var jsonObj = JSON.parse(body)
        var image = document.getElementById(&apos;myImage&apos;)
        image.setAttribute(&quot;src&quot;, jsonObj.file)
      }
    })

  &amp;#x3C;/script&gt;
&amp;#x3C;/html&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run it again by doing:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;npm start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and it works!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2016-07-24-electron-introduction/4DOlyTq.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I think the JavaScript we put in could have gone into &lt;code&gt;renderer.js&lt;/code&gt; but I didn&apos;t get it to work.&lt;/p&gt;
&lt;p&gt;Now for awesome some new Node.JS apps huh?&lt;/p&gt;
&lt;!--kg-card-end: markdown--&gt;</content:encoded><author>Jake Walker</author></item><item><title>Pi Zero Impressions</title><link>https://jakew.me/pi-zero-impressions/</link><guid isPermaLink="true">https://jakew.me/pi-zero-impressions/</guid><description>First Impressions of the new tiny £5 Raspberry Pi Zero.</description><pubDate>Mon, 29 Feb 2016 17:54:00 GMT</pubDate><content:encoded>&lt;!--kg-card-begin: markdown--&gt;&lt;p&gt;I brought a Raspberry Pi Zero on Friday because it was really hyped up and it was also very cheap. I brought mine from &lt;a href=&quot;http://thepihut.com/?ref=jakew.me&quot;&gt;The Pi Hut&lt;/a&gt;, I got the &lt;a href=&quot;http://thepihut.com/collections/raspberry-pi-zero/products/raspberry-pi-zero?variant=14062725188&amp;#x26;ref=jakew.me&quot;&gt;Zero + Essentials Kit&lt;/a&gt; variation (which costed £10.25 at the time of purchase).&lt;/p&gt;
&lt;h2 id=&quot;firstimpressions&quot;&gt;First Impressions&lt;/h2&gt;
&lt;p&gt;My first impression of the Pi Zero was that it was extremely small. The only place I had seen it is in magazines which made it look bigger. When I first booted it up it took a while making me think it was going to be quite slow but once it was booted up it seemed quite snappy (in bash/command line mode).&lt;/p&gt;
&lt;h2 id=&quot;setup&quot;&gt;Setup&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;I first plugged in the HDMI adapter then a (powered) HDMI to VGA adapter as my monitor is VGA.&lt;/li&gt;
&lt;li&gt;Next, I plugged in my OTG cable then plugged that into a powered USB Hub which I had my keyboard, mouse and Wi-Fi dongle connected.&lt;/li&gt;
&lt;li&gt;Then, I inserted my microSD card which I had put &lt;a href=&quot;https://www.raspberrypi.org/downloads/raspbian/?ref=jakew.me&quot;&gt;Raspbian&lt;/a&gt; on (I used the lite version (which has no desktop GUI) as I like to run my Pi&apos;s headless and its more fun to learn bash :))&lt;/li&gt;
&lt;li&gt;I plugged in the power then waited for it to boot up for the first time.&lt;/li&gt;
&lt;li&gt;I typed in &lt;code&gt;pi&lt;/code&gt; for the username and &lt;code&gt;raspberry&lt;/code&gt; for the password.&lt;/li&gt;
&lt;li&gt;I then expanded the filesystem to use the whole of my SD card by typing &lt;code&gt;sudo raspi-config&lt;/code&gt; then selecting &apos;Expand Filesystem&apos;.&lt;/li&gt;
&lt;li&gt;I then finished by rebooting the Pi.&lt;/li&gt;
&lt;li&gt;I then set up the Wi-Fi by: &lt;em&gt;(using &lt;a href=&quot;https://www.raspberrypi.org/documentation/configuration/wireless/wireless-cli.md?ref=jakew.me&quot;&gt;this&lt;/a&gt; article)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Scanning for networks using &lt;code&gt;sudo iwlist wlan0 scan&lt;/code&gt; but as I already knew my Wi-Fi SSID and Password it was unnecessary. &lt;em&gt;The &lt;code&gt;ESSID&lt;/code&gt; part is the SSID you need for later&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Opening the configuration file by typing &lt;code&gt;sudo nano /etc/wpa_supplicant/wpa_supplicant.conf&lt;/code&gt;. Then going to the bottom of the file and adding:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-conf&quot;&gt;network={
    ssid=&quot;ssid of your network&quot;
    psk=&quot;password of your network&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Next, I updated the apt packages by typing &lt;code&gt;sudo apt-get update&lt;/code&gt;. &lt;em&gt;You could also do &lt;code&gt;sudo apt-get upgrade&lt;/code&gt; if your image is a bit old.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;As I had no desktop, I decided to install Lynx. A web browser that can be used in the command line. You can install it by typing &lt;code&gt;sudo apt-get install lynx&lt;/code&gt;. Then opening it by typing &lt;code&gt;lynx&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Overall the Raspberry Pi Zero is great for the £5 price tag but can set  you back a little more for the adapters. It is very small so can be used for projects when a traditional Pi is too big.&lt;/p&gt;
&lt;!--kg-card-end: markdown--&gt;</content:encoded><author>Jake Walker</author></item><item><title>Installing a Custom ROM onto the 5th Generation Kindle Fire</title><link>https://jakew.me/kindle-5g-custom-rom/</link><guid isPermaLink="true">https://jakew.me/kindle-5g-custom-rom/</guid><description>Installing a custom Android version onto the 5th Generation Kindle Fire</description><pubDate>Sun, 27 Dec 2015 17:36:55 GMT</pubDate><content:encoded>&lt;!--kg-card-begin: markdown--&gt;&lt;p&gt;After playing with my Kindle for a few weeks, I thought how much bloatware was installed and how locked down the device was. I then decided to install a custom ROM.&lt;/p&gt;
&lt;h2 id=&quot;prep&quot;&gt;Prep&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Fastboot and ADB&lt;/strong&gt;. You may already have this installed but it is very simple to install from scratch. Use this guide written by Lifehacker &lt;a href=&quot;http://goo.gl/SLpJXX?ref=jakew.me&quot;&gt;http://goo.gl/SLpJXX&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ROM Image&lt;/strong&gt;. You will need to get this from &lt;a href=&quot;https://goo.gl/UMaqln?ref=jakew.me&quot;&gt;https://goo.gl/UMaqln&lt;/a&gt;. Put this (don’t unzip it) onto an SD card in your Kindle.&lt;br&gt;
TWRP (Recovery) Image. You will need to get this from &lt;a href=&quot;https://goo.gl/AfVCun?ref=jakew.me&quot;&gt;https://goo.gl/AfVCun&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Google Apps (gapps)&lt;/strong&gt;. You will need to download a version from the list here &lt;a href=&quot;https://goo.gl/6AKGhp?ref=jakew.me&quot;&gt;https://goo.gl/6AKGhp&lt;/a&gt;. It is recommended that you download the mini version (I downloaded Slim_mini_gapps.BETA.5.1.build.0.x-20151215.zip, however, it will be most likely outdated so make sure you get the latest. Put this (don’t unzip it) onto an SD card in your Kindle.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;bootingintorecovery&quot;&gt;Booting into Recovery&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Once you have downloaded and installed Fastboot and ADB, you will need to plug the Kindle Fire into your Desktop/Laptop and fire up a command prompt.&lt;/li&gt;
&lt;li&gt;Navigate to where you have saved the TWRP Image using &lt;code&gt;cd&lt;/code&gt; (for Windows) and &lt;code&gt;ls&lt;/code&gt; (for Linux).&lt;/li&gt;
&lt;li&gt;Turn off the kindle and press down volume button and the power button until there is a little message on the screen saying Fastboot Mode (not Recovery Mode) OR type in the shell adb shell reboot bootloader.&lt;br&gt;
Type in the shell &lt;code&gt;fastboot boot TWRP_Fire_2.8.7.0_adb.img&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;installingtherom&quot;&gt;Installing the ROM&lt;/h2&gt;
&lt;p&gt;Now you should be in recovery. There should be some buttons on the screen.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Press the Wipe button and swipe the arrow across to initiate the wipe (make sure you wipe dalvik cache, data, cache and system).&lt;br&gt;
Go back to the main menu and press Install and find the ROM Image. Flash it and when successful go back to the Main Menu and flash Google Apps.&lt;/li&gt;
&lt;li&gt;Reboot (to System)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You should boot up and find a setup menu. I have found some apps won’t install from the Google Play Store (I found Inbox (by Gmail) and Amazon Shopping don’t work), however, if you sideload these apps they work fine.&lt;/p&gt;
&lt;!--kg-card-end: markdown--&gt;</content:encoded><author>Jake Walker</author></item><item><title>Hacking the 5th Generation Kindle Fire</title><link>https://jakew.me/hacking-the-5th-generation-kindle-fire/</link><guid isPermaLink="true">https://jakew.me/hacking-the-5th-generation-kindle-fire/</guid><description>Adding Google Apps, Rooting, Removing Bloatware and Changing the Launcher of a 5th Generation Kindle Fire</description><pubDate>Sun, 04 Oct 2015 21:05:00 GMT</pubDate><content:encoded>&lt;!--kg-card-begin: markdown--&gt;&lt;p&gt;On Black Friday I bought a Kindle Fire (5th Gen) for £35. I decided to hack it. My main aim was to install Google Play, change the launcher and remove the bloatware, however, to achieve this I also needed to root the device.&lt;/p&gt;
&lt;p&gt;Prerequisites: ADB and Fastboot (guide: &lt;a href=&quot;http://goo.gl/SLpJXX?ref=jakew.me&quot;&gt;http://goo.gl/SLpJXX&lt;/a&gt;), Drivers (&lt;a href=&quot;http://goo.gl/sqJgpL?ref=jakew.me&quot;&gt;http://goo.gl/sqJgpL&lt;/a&gt;)&lt;/p&gt;
&lt;h2 id=&quot;1googleappsnorootrequired&quot;&gt;1. Google Apps (no root required)&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Source: &lt;a href=&quot;http://goo.gl/btPy9W?ref=jakew.me&quot;&gt;http://goo.gl/btPy9W&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Install ES File Explorer from Amazon’s App Store for free on the Kindle Fire.&lt;/li&gt;
&lt;li&gt;Open Settings &gt; Security &gt; Unknown Sources and check it or turn it on.&lt;/li&gt;
&lt;li&gt;Download the Google Core APK .zip archive here: &lt;a href=&quot;https://goo.gl/ansjr1?ref=jakew.me&quot;&gt;https://goo.gl/ansjr1&lt;/a&gt; to the device and find it in ES File Explorer under the internal SD card in the Downloads folder&lt;br&gt;
Install each of the below in order by tapping on the particular file name in ES File Explorer.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;com.android.vending-5.9.12-80391200-minAPI9.apk&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;com.google.android.gms-6.6.03_(1681564-036)-6603036-minAPI9.apk&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GoogleLoginService.apk&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GoogleServicesFramework.apk&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Reboot the Kindle Fire&lt;br&gt;
Open Settings &gt; Device Options and repeatedly tap the Serial Number until it says something like “No need you are already a developer”.&lt;br&gt;
Open Settings &gt; Device Options &gt; Developer Options and check or turn the option saying Enable ADB&lt;/li&gt;
&lt;li&gt;Plug the device into the computer you have ADB and Fastboot installed on.&lt;/li&gt;
&lt;li&gt;Open a prompt and type adb devices. If nothing show up in the list something is wrong with your drivers. If everything is correct you should get a code with numbers and letters then ‘device’ after.&lt;/li&gt;
&lt;li&gt;Type &lt;code&gt;adb shell pm grant com.google.android.gms android.permission.INTERACT_ACROSS_USERS&lt;/code&gt;. There will be no text returned after running this&lt;/li&gt;
&lt;li&gt;Check if Google Play works by finding it in the apps list. If it immediately closes after opening it follow step 12 if not you’re done!&lt;br&gt;
Reinstall ‘com.android.vending-5.9.12-80391200-minAPI9.apk’ in ES File Explorer.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;2rooting&quot;&gt;2. Rooting&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Source: &lt;a href=&quot;http://goo.gl/FQhGl0?ref=jakew.me&quot;&gt;http://goo.gl/FQhGl0&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Enable ADB on the Device (see section 1, steps 6 and 7)&lt;/li&gt;
&lt;li&gt;Download the .zip archive of all the tools used for rooting from here: &lt;a href=&quot;http://goo.gl/wvUMgI?ref=jakew.me&quot;&gt;http://goo.gl/wvUMgI&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Unzip the file and plug in the Kindle Fire to your PC.&lt;br&gt;
Windows: Double click root.bat and follow instructions. Linux: Open a terminal and navigate to the place you unzipped the archive and type &lt;code&gt;sh root.sh&lt;/code&gt; and wait for the script to complete. The Kindle Fire will restart multiple times, don’t worry this is normal. The Kindle Fire may say Installing Firmware, this is also normal and just means it it is optimising apps.&lt;/li&gt;
&lt;li&gt;You should be rooted you can try this by installing a Root Checker app from the Google Play Store.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;3removebloatrootrequired&quot;&gt;3. Remove Bloat (root required)&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Source: &lt;a href=&quot;http://goo.gl/ud7HWU?ref=jakew.me&quot;&gt;http://goo.gl/ud7HWU&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Install an app called “System App Remover (ROOT)” from the Google Play Store.&lt;/li&gt;
&lt;li&gt;Remove any app in the list below by opening the app and checking the apps and tapping remove at the bottom of the app.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;com.amazon.webapp | Bookstore&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;com.amazon.zico | Documents | 2.0_205111610&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;com.amazon.mp3 | Amazon music&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;com.android.music |Music | 10.0.51-D-20151009-NA-7&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;com.amazon.kindle.ottercom.amazon.kindle | Kindle Reader&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;com.audible.application.kindle | Audible&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;com.amazon.tahoe | FreeTime | 1.5.345.26-FreeTimeForTabletGen6_201365610&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;com.amazon.kindle.otter.oobe | Device Setup | 1.0.171.0_3337310&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;com.amazon.client.metrics | Amazon Metrics Service Application | 1.0.175.0_9610&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;com.amazon.device.backup | Amazon Backup and Restore | 5.0.410.0_5107610&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;com.amazon.platform | Amazon platform | 1.0.9.2_1009710&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;com.amazon.photos | photos FOS | 20115210 (Needed to change the wallpaper)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;com.nuance.edr.androidservice.service&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;com.amazon.whisperlink.core.android | Whisperlinkplay Daemon | 2.0.100021.00&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;com.amazon.camera | Camera | 1.0.088.0_1002120310&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;com.amazon.csapp | Help App&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;com.amazon.legalsettings | Legal Settings App&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;com.amazon.venezia | Amazon App Store&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;com.amazon.h2settingsfortablet | Profiles &amp;#x26; Family Library App&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;com.amazon.weather | Weather App&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;com.goodreads.kindle | Goodreads | 1.6.1+1060100710&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;com.android.calendar | Calendar App&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;com.amazon.kindle.kso	| Special Offers / Ads (lockscreen ads)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;com.android.email | Email App&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;com.amazon.kindle.personal_video | Video App&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;com.amazon.avod | Video App&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;com.amazon.contacts | Contacts App&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;com.amazon.windowshop | Amazon Windowshop (Amazon Shopping App)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;com.amazon.cloud9	| Silk Browser App&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;com.amazon.ags.app | Amazon Game Circle App&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;com.amazon.kindle.otter.settings	| Amazon Registration Service&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Restart the Device.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;4changinglauncherrequiresroot&quot;&gt;4. Changing Launcher (requires root)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Make sure you have another launcher before continuing.&lt;/li&gt;
&lt;li&gt;Open ES File Explorer and open the side menu and open the Tools part then turn on the Root Explorer option&lt;/li&gt;
&lt;li&gt;Navigate to the root of the device and open the system folder then the priv-app folder [ /system/priv-app/ ]&lt;/li&gt;
&lt;li&gt;Copy the folder called com.amazon.firelauncher to the SD card of the device and then go back to the priv-app folder and delete the folder you just copied. You should then end up with no folder called  com.amazon.firelauncher in the priv-app folder and a folder called  com.amazon.firelauncher in the SD card.&lt;/li&gt;
&lt;li&gt;Restart the device.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;Thanks for reading. I hope this helped you enhance your £50 Kindle Fire. I found some more articles at: &lt;a href=&quot;http://goo.gl/cITO3g?ref=jakew.me&quot;&gt;http://goo.gl/cITO3g&lt;/a&gt; including the articles below.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Remove Lock Screen Ads (no root required): &lt;a href=&quot;http://goo.gl/xlxQrp?ref=jakew.me&quot;&gt;http://goo.gl/xlxQrp&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the future I hope to install CyanogenMod when a stable release comes out.&lt;/p&gt;
&lt;!--kg-card-end: markdown--&gt;</content:encoded><author>Jake Walker</author></item><item><title>Arduino Weather Logger</title><link>https://jakew.me/arduino-weather-logger/</link><guid isPermaLink="true">https://jakew.me/arduino-weather-logger/</guid><description>Creating an Arduino Weather Station.</description><pubDate>Fri, 10 Apr 2015 19:57:00 GMT</pubDate><content:encoded>&lt;!--kg-card-begin: markdown--&gt;&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;This was originally a way to test out an app I found called ‘Blynk’ which I first tested on the Raspberry Pi. I then decided to try and get it working with an Arduino and a cheap Wi-Fi module then I plugged in my BMP180 and a light sensor and successfully got it working. I then decided to remove the components I was testing it with and turn it into a data logging weather station.&lt;/p&gt;
&lt;h2 id=&quot;wiring&quot;&gt;Wiring&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://ghost.jakew.me/content/images/2015-04-10-arduino-weather-logger/circuit.png&quot; alt=&quot;Circuit Diagram&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://fritzing.org/projects/arduino-weather-logger?ref=jakew.me&quot;&gt;View on Fritzing&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;code&quot;&gt;Code&lt;/h2&gt;
&lt;p&gt;Virtual Pin 1 is Temperature (degC), Virtual Pin 2 is Pressure (Pa).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;/**************************************************************
 * Blynk is a platform with iOS and Android apps to control
 * Arduino, Raspberry Pi and the likes over the Internet.
 * You can easily build graphic interfaces for all your
 * projects by simply dragging and dropping widgets.
 *
 *   Downloads, docs, tutorials: http://www.blynk.cc
 *   Blynk community:            http://community.blynk.cc
 *   Social networks:            http://www.fb.com/blynkapp
 *                               http://twitter.com/blynk_app
 *
 * Blynk library is licensed under MIT license
 * This example code is in public domain.
 *
 **************************************************************
 *
 * This example shows how to use ESP8266 Shield via Hardware Serial
 * (on Mega, Leonardo, Micro...) to connect your project to Blynk.
 *
 * Note: Ensure a stable serial connection to ESP8266!
 *       Firmware version 1.0.0 (AT v0.22) or later is needed.
 *       You can change ESP baud rate. Connect to AT console and call:
 *           AT+UART_DEF=115200,8,1,0,0
 *
 * Change WiFi ssid, pass, and Blynk auth token to run :)
 * Feel free to apply it to any other example. It&apos;s simple!
 *
 **************************************************************/
//#define BLYNK_DEBUG
#define BLYNK_PRINT Serial    // Comment this out to disable prints and save space
#include &amp;#x3C;ESP8266_HardSer.h&gt;
#include &amp;#x3C;BlynkSimpleShieldEsp8266_HardSer.h&gt;

#include &amp;#x3C;Wire.h&gt;
#include &amp;#x3C;Adafruit_BMP085.h&gt;

// Set ESP8266 Serial object
#define EspSerial Serial

ESP8266 wifi(EspSerial);
Adafruit_BMP085 bmp;

// You should get Auth Token in the Blynk App.
// Go to the Project Settings (nut icon).
char auth[] = &quot;__BLYNK-API-KEY__&quot;;

void setup()
{
  // Set console baud rate
  //Serial.begin(9600);
  //delay(10);
  // Set ESP8266 baud rate
  EspSerial.begin(115200);
  delay(10);

  Blynk.begin(auth, wifi, &quot;__SSID__&quot;, &quot;__PASSWORD__&quot;);
  bmp.begin();
}

void loop()
{
  Blynk.run();
  Blynk.virtualWrite(1, bmp.readTemperature());//temp
  Blynk.virtualWrite(2, bmp.readPressure());//Pressure
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;inthefuture&quot;&gt;In the Future&lt;/h2&gt;
&lt;p&gt;In the future I am planning to put it actually outside (and provide a thumbnail) and possibly improve code or switch to another platform.&lt;/p&gt;
&lt;!--kg-card-end: markdown--&gt;</content:encoded><author>Jake Walker</author></item><item><title>Raspberry Pi PVR</title><link>https://jakew.me/raspberry-pi-pvr/</link><guid isPermaLink="true">https://jakew.me/raspberry-pi-pvr/</guid><description>Creating a digital TV recorder and viewer with a Raspberry Pi and OpenELEC.</description><pubDate>Sun, 26 Oct 2014 20:59:00 GMT</pubDate><content:encoded>&lt;!--kg-card-begin: markdown--&gt;&lt;p&gt;In this project I used a Raspberry Pi (I used a model B) with OpenELEC on the SD card, a USB TV Receiver and a cheap Indoor Ariel to create a PVR.&lt;/p&gt;
&lt;p&gt;These instructions were done on Windows.&lt;/p&gt;
&lt;p&gt;This is what I did: I burnt OpenELEC on the SD card. I got the OpenELEC disk image build from here and I followed the instructions to install from here. Once I was done I plugged in a monitor, keyboard and mouse into the Pi and booted it up. Once I was on the Main Menu I went to the Add-on Menu in Settings and got two add-ons: TVheadend (which I found under Get Add-ons &gt; services) and TVheadend HTSP Client (which I found under Disabled Add-ons &gt; PVR Clients). Once I had done that I went into the TVheadend configuration interface on my laptop which was at: &lt;code&gt;http://[Pi IP]:9981&lt;/code&gt;. Then I went to the Configuration &gt; TV Adapters menu. Then I selected the drop-down menu and selected my TV receiver and then I clicked the ‘Add DVB Network by Location’ button. I selected where my closest TV repeater was and clicked the &apos;Add DVB network’ button at the bottom of the menu. At the side there is some text. Under the &apos;Muxes awaiting initial scan:’ heading there is a number and I waited for it to go to 0. It took a couple of minutes. After it had finished I clicked the &apos;Map DVB services to channels’ button and opened the console at the bottom. After it looked like it was finished I configured the frontend. On OpenELEC I went to Settings &gt; Live TV and checked the &apos;Live TV Enabled’ option.&lt;/p&gt;
&lt;p&gt;I have added a hard drive to record to. It has a USB connection to the raspberry Pi. I have had lots of power issues but got there in the end. Also I have brought the MPEG2 codec to decode the video. I have to wait for a reply from the raspberry Pi team...&lt;/p&gt;
&lt;!--kg-card-end: markdown--&gt;</content:encoded><author>Jake Walker</author></item></channel></rss>