<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Adventures of a Technomancer]]></title><description><![CDATA[If it ain't broke, fix it till it is!]]></description><link>https://jcode.me/</link><image><url>https://jcode.me/favicon.png</url><title>Adventures of a Technomancer</title><link>https://jcode.me/</link></image><generator>Ghost 2.9</generator><lastBuildDate>Thu, 27 Apr 2023 10:41:16 GMT</lastBuildDate><atom:link href="https://jcode.me/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Laravel, Dropzone.js & S3 the right way]]></title><description><![CDATA[Problem All right, here's your motivation: Your name is Lucas, you're an average developer who wants to create beautiful things you can be proud of. One day, you'll think about hitting your clients with a "computers for dummies" book. No, forget that part. We'll improvise... just keep it kind of loosey-goosey. You want to create an form to allow clients to upload files and you need it done yesterday! ACTION. Most developers start out by doing something like this; Uploads hit the server, prob]]></description><link>https://jcode.me/laravel-dropzone-the-right-way/</link><guid isPermaLink="false">Ghost__Post__59e18ee53c9e4f0deb8984f5</guid><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Sun, 17 Nov 2019 00:14:10 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2019/11/dropzone.jpg" medium="image"/><content:encoded><![CDATA[<h2 id="problem">Problem</h2><img src="https://ghost.jcode.me/content/images/2019/11/dropzone.jpg" alt="Laravel, Dropzone.js & S3 the right way"/><p>All right, here's your motivation: Your name is Lucas, you're an average developer who wants to create beautiful things you can be proud of. One day, you'll think about hitting your clients with a "computers for dummies" book. </p><p>No, forget that part. We'll improvise... just keep it kind of loosey-goosey. You want to create an form to allow clients to upload files and you need it done yesterday! <em>ACTION</em>.</p><p>Most developers start out by doing something like this;</p><figure class="kg-card kg-image-card"><img src="https://ghost.jcode.me/content/images/2019/11/upload.png" class="kg-image" alt="Laravel, Dropzone.js & S3 the right way" loading="lazy"/></figure><p>Uploads hit the server, probably in some web accessible directory like <code>/uploads/</code> and figure everything is great.</p><p>Some might even learn about attack vectors and move the uploads directory to a non web accessible directory.</p><p>Then comes the day when you learn about S3. Storing files on someone else's computer? Sign me up.</p><p>So the easiest solution is often to copy files from your server to S3 after an upload.</p><figure class="kg-card kg-image-card"><img src="https://ghost.jcode.me/content/images/2019/11/upload-2.png" class="kg-image" alt="Laravel, Dropzone.js & S3 the right way" loading="lazy"/></figure><p>But this is the <em>wrong</em> thing to do, now you're touching the file <em>twice</em>, paying for <em>double</em> the amount of bandwidth and on large files adding a decent amount of latency that any user would get sick of.</p><p/><h2 id="so-how-do-we-fix-it">So, how do we fix it?</h2><p>Assuming you have set up <a href="https://laravel.com/docs/5.5/filesystem?ref=ghost.jcode.me#driver-prerequisites">an S3 filesystem</a> then the first step is to create an endpoint to generate a signed URL for the client to upload files to.</p><p/><p>In this example I'm calling it `s3-url`.</p><pre><code class="language-php">Route::get('/s3-url', 'SignedS3UrlCreatorController@index');</code></pre><p>And the relevant controller logic.</p><pre><code class="language-php">class SignedS3UrlCreatorController extends Controller { public function index() { return response()->json([ 'error' => false, 'url' => $this->get_amazon_url(request('name')), 'additionalData' => [ // Uploading many files and need a unique name? UUID it! //'fileName' => Uuid::uuid4()->toString() ], 'code' => 200, ], 200); } private function get_amazon_url($name) { $s3 = Storage::disk('s3'); $client = $s3->getDriver()->getAdapter()->getClient(); $expiry = "+90 minutes"; $command = $client->getCommand('PutObject', [ 'Bucket' => config('filesystems.disks.s3.bucket'), 'Key' => $name, ]); return (string) $client->createPresignedRequest($command, $expiry)->getUri(); } }</code></pre><p/><p>This means that any GET request sending through a filename parameter generates a signed URL that anyone can use.</p><p>It's probably a good idea to keep track of these URLs and/or file names in a database just in case you want to query or remove them programmatically later on.</p><p>The second step is to use something like DropzoneJS.</p><blockquote>DropzoneJS is an open source library that provides drag’n’drop file uploads with image previews.</blockquote><p/><p>Dropzone will find all form elements with the class dropzone, automatically attach itself, and upload files dropped into it to the specified action attribute. </p><p>The uploaded files can be handled just as if there would have been a regular html form. </p><pre><code class="language-html"><form action="/file-upload" class="dropzone"> <div class="fallback"> <input name="file" type="file" multiple /> </div> </form></code></pre><p/><p>But we don't want to just handle it like a regular form, so we need to disable the auto discover function.</p><pre><code class="language-javascript">Dropzone.autoDiscover = false; </code></pre><p>Then create a custom configuration that watches for files added to the queue, like so;</p><pre><code class="language-javascript">var dropzone = new Dropzone('#dropzone',{ url: '#', method: 'put', autoQueue: false, autoProcessQueue: false, init: function() { /* When a file is added to the queue - pass it along to the signed url controller - get the response json - set the upload url based on the response - add additional data (such as the uuid filename) to a temporary parameter - start the upload */ this.on('addedfile', function(file) { fetch('/s3-url?&name='+file.name, { method: 'get' }).then(function (response) { return response.json(); }).then(function (json) { dropzone.options.url = json.url; file.additionalData = json.additionalData; dropzone.processFile(file); }); }); /* When uploading the file - make sure to set the upload timeout to near unlimited - add all the additional data to the request */ this.on('sending', function(file, xhr, formData) { xhr.timeout = 99999999; for (var field in file.additionalData) { formData.append(field, file.additionalData[field]); } }); /* Handle the success of an upload */ this.on('success', function(file) { // Let the Laravel application know the file was uploaded successfully }); }, sending: function(file, xhr) { var _send = xhr.send; xhr.send = function() { _send.call(xhr, file); }; }, });</code></pre><p/><p>This provides the following upload flow. </p><figure class="kg-card kg-image-card"><img src="https://ghost.jcode.me/content/images/2019/11/upload-3.png" class="kg-image" alt="Laravel, Dropzone.js & S3 the right way" loading="lazy"/></figure><p>No double handling and secure S3 uploads, nice.</p><p>From here it's possible to expand the uploading and handling logic to update database records. But I'll leave that to you.</p><figure class="kg-card kg-image-card"><img src="https://ghost.jcode.me/content/images/2019/11/upload-4.png" class="kg-image" alt="Laravel, Dropzone.js & S3 the right way" loading="lazy"/></figure><p/><p>All source is available on GitHub for your viewing pleasure. </p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/JasonMillward/laravel-dropzone?ref=ghost.jcode.me"><div class="kg-bookmark-content"><div class="kg-bookmark-title">JasonMillward/laravel-dropzone</div><div class="kg-bookmark-description">Contribute to JasonMillward/laravel-dropzone development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/favicon.ico" alt="Laravel, Dropzone.js & S3 the right way"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">JasonMillward</span></img></div></div><div class="kg-bookmark-thumbnail"><img src="https://avatars2.githubusercontent.com/u/1149781?s=400&v=4" alt="Laravel, Dropzone.js & S3 the right way"/></div></a></figure><h3 id="nb-">NB:</h3><p>Don't forget the CORS config for your S3 bucket</p><pre><code class="language-xml"><?xml version="1.0" encoding="UTF-8"?> <CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> <CORSRule> <AllowedOrigin>*</AllowedOrigin> <AllowedMethod>GET</AllowedMethod> <AllowedMethod>PUT</AllowedMethod> <AllowedMethod>POST</AllowedMethod> <AllowedMethod>DELETE</AllowedMethod> <MaxAgeSeconds>3000</MaxAgeSeconds> <ExposeHeader>Access-Control-Allow-Origin</ExposeHeader> <AllowedHeader>*</AllowedHeader> </CORSRule> </CORSConfiguration></code></pre>]]></content:encoded></item><item><title><![CDATA[My wedding party knives]]></title><description><![CDATA[Although we had a little help with things that required special tools such as the water jet cutter and the laser engraving these knives are hand made from start to finish by myself and my bride to be. ]]></description><link>https://jcode.me/wedding-knives/</link><guid isPermaLink="false">Ghost__Post__5c36b78fc08e2a00017ae095</guid><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Fri, 29 Mar 2019 00:06:00 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2019/05/knifes.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://ghost.jcode.me/content/images/2019/05/knifes.jpg" alt="My wedding party knives"/><p>Although we had a little help with things that required specialty tools such as the water jet cutting and the laser engraving these knives are hand made from start to finish by myself and my bride to be. </p><p>This is the process we undertook in order to create them.</p><p/><p/><p>Knives are brought into this world as steel, rolled out into flat bars which are then cut into the rough shape of the knife using high pressure water jet cutter - imagine a pressure washer on steroids. </p><p>The edges on these knives are ground in the style of a full flat grind, which is thickest at the spine for strength, but tapers down into a relatively thin edge for excellent slicing. </p><figure class="kg-card kg-image-card"><img src="https://ghost.jcode.me/content/images/2019/03/Redone-Blade-Grinds-1400-x-800-min.png" class="kg-image" alt="My wedding party knives" loading="lazy"/></figure><p/><p>A full flat grind is often stronger than a hollow grind, and will cut better than a sabre grind.</p><p>Once the grinding is completed, sanding starts at 80 grit and progresses to 180 grit in preparation for heat treating. Doing a little work now saves about two hours per knife later on.</p><p/><figure class="kg-card kg-gallery-card kg-width-wide"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://ghost.jcode.me/content/images/2019/01/20190112_071359.jpg" width="2000" height="1500" loading="lazy" alt="My wedding party knives" srcset="https://ghost.jcode.me/content/images/size/w600/2019/01/20190112_071359.jpg 600w, https://ghost.jcode.me/content/images/size/w1000/2019/01/20190112_071359.jpg 1000w, https://ghost.jcode.me/content/images/size/w1600/2019/01/20190112_071359.jpg 1600w, https://ghost.jcode.me/content/images/size/w2400/2019/01/20190112_071359.jpg 2400w" sizes="(min-width: 720px) 720px"/></div><div class="kg-gallery-image"><img src="https://ghost.jcode.me/content/images/2019/01/IMG_20181117_135401.jpg" width="2000" height="2000" loading="lazy" alt="My wedding party knives" srcset="https://ghost.jcode.me/content/images/size/w600/2019/01/IMG_20181117_135401.jpg 600w, https://ghost.jcode.me/content/images/size/w1000/2019/01/IMG_20181117_135401.jpg 1000w, https://ghost.jcode.me/content/images/size/w1600/2019/01/IMG_20181117_135401.jpg 1600w, https://ghost.jcode.me/content/images/size/w2400/2019/01/IMG_20181117_135401.jpg 2400w" sizes="(min-width: 720px) 720px"/></div><div class="kg-gallery-image"><img src="https://ghost.jcode.me/content/images/2019/01/20190113_204636_014.jpg" width="2000" height="2667" loading="lazy" alt="My wedding party knives" srcset="https://ghost.jcode.me/content/images/size/w600/2019/01/20190113_204636_014.jpg 600w, https://ghost.jcode.me/content/images/size/w1000/2019/01/20190113_204636_014.jpg 1000w, https://ghost.jcode.me/content/images/size/w1600/2019/01/20190113_204636_014.jpg 1600w, https://ghost.jcode.me/content/images/2019/01/20190113_204636_014.jpg 2305w" sizes="(min-width: 720px) 720px"/></div></div><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://ghost.jcode.me/content/images/2019/01/IMG_20181117_135415.jpg" width="2000" height="2000" loading="lazy" alt="My wedding party knives" srcset="https://ghost.jcode.me/content/images/size/w600/2019/01/IMG_20181117_135415.jpg 600w, https://ghost.jcode.me/content/images/size/w1000/2019/01/IMG_20181117_135415.jpg 1000w, https://ghost.jcode.me/content/images/size/w1600/2019/01/IMG_20181117_135415.jpg 1600w, https://ghost.jcode.me/content/images/size/w2400/2019/01/IMG_20181117_135415.jpg 2400w" sizes="(min-width: 720px) 720px"/></div><div class="kg-gallery-image"><img src="https://ghost.jcode.me/content/images/2019/01/20181125_175748.jpg" width="2000" height="2000" loading="lazy" alt="My wedding party knives" srcset="https://ghost.jcode.me/content/images/size/w600/2019/01/20181125_175748.jpg 600w, https://ghost.jcode.me/content/images/size/w1000/2019/01/20181125_175748.jpg 1000w, https://ghost.jcode.me/content/images/size/w1600/2019/01/20181125_175748.jpg 1600w, https://ghost.jcode.me/content/images/size/w2400/2019/01/20181125_175748.jpg 2400w" sizes="(min-width: 720px) 720px"/></div></div></div></figure><p/><hr><p>From here each blade is heated up to non-magnetic temperatures and allowed to cool down naturally. </p><p>This is performed twice before having clay slathered all over the blade to allow for differentially hardening and to produce a rather fetching hamon - the difference between hard and soft steel.</p><p>Once the clay has dried the blades are once again heated up to a non-magnetic temperature - about 900°c - and quenched in warm oil. </p><p>The blades in this state are incredibly fragile and if they were dropped they would shatter. </p><p>To counter this the blades are throw into an oven and tempered at 200 degrees for 2 hours. </p><p>When everything has cooled down the knives are ground again, this time to a closer finish and hand sanded, starting from 80 grit sandpaper and progressing through grits 120, 180, 220, 400 to finally end up at 600 grit. </p><p>The last step is finishing them with varying grades of a scotchbrite like material.</p><p/><figure class="kg-card kg-gallery-card kg-width-wide"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://ghost.jcode.me/content/images/2019/05/clay-1.jpg" width="1080" height="736" loading="lazy" alt="My wedding party knives" srcset="https://ghost.jcode.me/content/images/size/w600/2019/05/clay-1.jpg 600w, https://ghost.jcode.me/content/images/size/w1000/2019/05/clay-1.jpg 1000w, https://ghost.jcode.me/content/images/2019/05/clay-1.jpg 1080w" sizes="(min-width: 720px) 720px"/></div><div class="kg-gallery-image"><img src="https://ghost.jcode.me/content/images/2019/01/20190113_205001.jpg" width="2000" height="1500" loading="lazy" alt="My wedding party knives" srcset="https://ghost.jcode.me/content/images/size/w600/2019/01/20190113_205001.jpg 600w, https://ghost.jcode.me/content/images/size/w1000/2019/01/20190113_205001.jpg 1000w, https://ghost.jcode.me/content/images/size/w1600/2019/01/20190113_205001.jpg 1600w, https://ghost.jcode.me/content/images/size/w2400/2019/01/20190113_205001.jpg 2400w" sizes="(min-width: 720px) 720px"/></div></div><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://ghost.jcode.me/content/images/2019/01/20190113_205249_021.jpg" width="2000" height="1500" loading="lazy" alt="My wedding party knives" srcset="https://ghost.jcode.me/content/images/size/w600/2019/01/20190113_205249_021.jpg 600w, https://ghost.jcode.me/content/images/size/w1000/2019/01/20190113_205249_021.jpg 1000w, https://ghost.jcode.me/content/images/size/w1600/2019/01/20190113_205249_021.jpg 1600w, https://ghost.jcode.me/content/images/size/w2400/2019/01/20190113_205249_021.jpg 2400w" sizes="(min-width: 720px) 720px"/></div><div class="kg-gallery-image"><img src="https://ghost.jcode.me/content/images/2019/01/20190114_080058.jpg" width="2000" height="1500" loading="lazy" alt="My wedding party knives" srcset="https://ghost.jcode.me/content/images/size/w600/2019/01/20190114_080058.jpg 600w, https://ghost.jcode.me/content/images/size/w1000/2019/01/20190114_080058.jpg 1000w, https://ghost.jcode.me/content/images/size/w1600/2019/01/20190114_080058.jpg 1600w, https://ghost.jcode.me/content/images/2019/01/20190114_080058.jpg 2175w" sizes="(min-width: 720px) 720px"/></div></div></div></figure><p/><hr><p>The handles are made out of wood and made in pairs;</p><ul><li>Two handles made from <em>red river gum </em>that was slightly eaten by termites with gaps filled with resin and black dye, bolsters made from <em>jarrah.</em></li><li>Two are made from African mahogany with <em>gidgee</em> bolsters.</li><li>The last two handles are <em>ancient bog oak</em>, donated by one of the groomsmen, the gaps filled with resin and a purple pearlescent dye and bolsters made from <em>jarrah.</em></li></ul><p>Holes are drilled down the middle of the handle to a length of 120mm, whereas the bolster has the shape carved out by hand using two of my grandfathers chisels.</p><p>To test the fit of everything the knife is inserted into the bolster and handle. If it looks good to go the wood is glued together temporarily using CA glue.</p><p/><figure class="kg-card kg-gallery-card kg-width-wide"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://ghost.jcode.me/content/images/2019/01/20181203_184410.jpg" width="2000" height="2667" loading="lazy" alt="My wedding party knives" srcset="https://ghost.jcode.me/content/images/size/w600/2019/01/20181203_184410.jpg 600w, https://ghost.jcode.me/content/images/size/w1000/2019/01/20181203_184410.jpg 1000w, https://ghost.jcode.me/content/images/size/w1600/2019/01/20181203_184410.jpg 1600w, https://ghost.jcode.me/content/images/size/w2400/2019/01/20181203_184410.jpg 2400w" sizes="(min-width: 720px) 720px"/></div><div class="kg-gallery-image"><img src="https://ghost.jcode.me/content/images/2019/01/20190112_071619.jpg" width="2000" height="1500" loading="lazy" alt="My wedding party knives" srcset="https://ghost.jcode.me/content/images/size/w600/2019/01/20190112_071619.jpg 600w, https://ghost.jcode.me/content/images/size/w1000/2019/01/20190112_071619.jpg 1000w, https://ghost.jcode.me/content/images/size/w1600/2019/01/20190112_071619.jpg 1600w, https://ghost.jcode.me/content/images/size/w2400/2019/01/20190112_071619.jpg 2400w" sizes="(min-width: 720px) 720px"/></div><div class="kg-gallery-image"><img src="https://ghost.jcode.me/content/images/2019/01/20190111_193711-1.jpg" width="2000" height="1604" loading="lazy" alt="My wedding party knives" srcset="https://ghost.jcode.me/content/images/size/w600/2019/01/20190111_193711-1.jpg 600w, https://ghost.jcode.me/content/images/size/w1000/2019/01/20190111_193711-1.jpg 1000w, https://ghost.jcode.me/content/images/size/w1600/2019/01/20190111_193711-1.jpg 1600w, https://ghost.jcode.me/content/images/size/w2400/2019/01/20190111_193711-1.jpg 2400w" sizes="(min-width: 720px) 720px"/></div></div><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://ghost.jcode.me/content/images/2019/01/20190111_203332-1.jpg" width="2000" height="1500" loading="lazy" alt="My wedding party knives" srcset="https://ghost.jcode.me/content/images/size/w600/2019/01/20190111_203332-1.jpg 600w, https://ghost.jcode.me/content/images/size/w1000/2019/01/20190111_203332-1.jpg 1000w, https://ghost.jcode.me/content/images/size/w1600/2019/01/20190111_203332-1.jpg 1600w, https://ghost.jcode.me/content/images/size/w2400/2019/01/20190111_203332-1.jpg 2400w" sizes="(min-width: 720px) 720px"/></div><div class="kg-gallery-image"><img src="https://ghost.jcode.me/content/images/2019/01/20190109_202409.jpg" width="2000" height="1500" loading="lazy" alt="My wedding party knives" srcset="https://ghost.jcode.me/content/images/size/w600/2019/01/20190109_202409.jpg 600w, https://ghost.jcode.me/content/images/size/w1000/2019/01/20190109_202409.jpg 1000w, https://ghost.jcode.me/content/images/size/w1600/2019/01/20190109_202409.jpg 1600w, https://ghost.jcode.me/content/images/size/w2400/2019/01/20190109_202409.jpg 2400w" sizes="(min-width: 720px) 720px"/></div><div class="kg-gallery-image"><img src="https://ghost.jcode.me/content/images/2019/01/20190109_203344.jpg" width="2000" height="1500" loading="lazy" alt="My wedding party knives" srcset="https://ghost.jcode.me/content/images/size/w600/2019/01/20190109_203344.jpg 600w, https://ghost.jcode.me/content/images/size/w1000/2019/01/20190109_203344.jpg 1000w, https://ghost.jcode.me/content/images/size/w1600/2019/01/20190109_203344.jpg 1600w, https://ghost.jcode.me/content/images/size/w2400/2019/01/20190109_203344.jpg 2400w" sizes="(min-width: 720px) 720px"/></div></div></div></figure><p/><hr><p>When the CA glue has cured, the handle is sanded down into a hexagonal shape with the butt and bolster getting slightly rounded on the tips. </p><p>Sanding is done progressively from 80 grit to 600 grit, the handle is then given a sizable dosage of food grade mineral oil and beeswax to seal the wood.</p><p>Given a few days for the oil to seep into the wood, it gets a quick polish using a sisal buffing wheel.</p><p/><figure class="kg-card kg-gallery-card kg-width-wide"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://ghost.jcode.me/content/images/2019/01/20190110_182024-4.jpg" width="2000" height="2000" loading="lazy" alt="My wedding party knives" srcset="https://ghost.jcode.me/content/images/size/w600/2019/01/20190110_182024-4.jpg 600w, https://ghost.jcode.me/content/images/size/w1000/2019/01/20190110_182024-4.jpg 1000w, https://ghost.jcode.me/content/images/size/w1600/2019/01/20190110_182024-4.jpg 1600w, https://ghost.jcode.me/content/images/2019/01/20190110_182024-4.jpg 2268w" sizes="(min-width: 720px) 720px"/></div><div class="kg-gallery-image"><img src="https://ghost.jcode.me/content/images/2019/01/20190110_183318-2.jpg" width="2000" height="2000" loading="lazy" alt="My wedding party knives" srcset="https://ghost.jcode.me/content/images/size/w600/2019/01/20190110_183318-2.jpg 600w, https://ghost.jcode.me/content/images/size/w1000/2019/01/20190110_183318-2.jpg 1000w, https://ghost.jcode.me/content/images/size/w1600/2019/01/20190110_183318-2.jpg 1600w, https://ghost.jcode.me/content/images/size/w2400/2019/01/20190110_183318-2.jpg 2400w" sizes="(min-width: 720px) 720px"/></div><div class="kg-gallery-image"><img src="https://ghost.jcode.me/content/images/2019/01/20190110_183625-2.jpg" width="2000" height="1500" loading="lazy" alt="My wedding party knives" srcset="https://ghost.jcode.me/content/images/size/w600/2019/01/20190110_183625-2.jpg 600w, https://ghost.jcode.me/content/images/size/w1000/2019/01/20190110_183625-2.jpg 1000w, https://ghost.jcode.me/content/images/size/w1600/2019/01/20190110_183625-2.jpg 1600w, https://ghost.jcode.me/content/images/size/w2400/2019/01/20190110_183625-2.jpg 2400w" sizes="(min-width: 720px) 720px"/></div></div><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://ghost.jcode.me/content/images/2019/01/20190110_183948.jpg" width="2000" height="1125" loading="lazy" alt="My wedding party knives" srcset="https://ghost.jcode.me/content/images/size/w600/2019/01/20190110_183948.jpg 600w, https://ghost.jcode.me/content/images/size/w1000/2019/01/20190110_183948.jpg 1000w, https://ghost.jcode.me/content/images/size/w1600/2019/01/20190110_183948.jpg 1600w, https://ghost.jcode.me/content/images/size/w2400/2019/01/20190110_183948.jpg 2400w" sizes="(min-width: 720px) 720px"/></div><div class="kg-gallery-image"><img src="https://ghost.jcode.me/content/images/2019/01/20190110_192043-5.jpg" width="2000" height="1537" loading="lazy" alt="My wedding party knives" srcset="https://ghost.jcode.me/content/images/size/w600/2019/01/20190110_192043-5.jpg 600w, https://ghost.jcode.me/content/images/size/w1000/2019/01/20190110_192043-5.jpg 1000w, https://ghost.jcode.me/content/images/size/w1600/2019/01/20190110_192043-5.jpg 1600w, https://ghost.jcode.me/content/images/size/w2400/2019/01/20190110_192043-5.jpg 2400w" sizes="(min-width: 720px) 720px"/></div></div></div></figure><p/><hr><p>Each knife is now glued in to the handle securely using a two part epoxy which is left to cure in an upright position for 3 days to achieve maximum bond strength. </p><p>Using varying grades of diamond hones and a leather strop, the blade eventually come to a razor sharp edge. </p><p>Finally everything is given a once over, any blemishes polished out using a microfibre cloth and cleaned up with a thin layer of food grade mineral oil and packaged, ready for the big day.</p><p/><figure class="kg-card kg-image-card kg-width-wide"><img src="https://ghost.jcode.me/content/images/2019/03/20190316_152336.jpg" class="kg-image" alt="My wedding party knives" loading="lazy"/></figure></hr></hr></hr></hr>]]></content:encoded></item><item><title><![CDATA[Octoprint: Push notifications]]></title><description><![CDATA[Since I'm touching on a few things Octoprint this week I thought I'd also post my method of getting regular updates from my printer when it's printing. At the time of posting I'm currently writing an update to the Octoprint plugin [https://github.com/thijsbekke/OctoPrint-Pushover] to do this all internally, but in the meantime this will have to do. EDIT: Pull request merged! [https://github.com/thijsbekke/OctoPrint-Pushover/pull/28] You should use the plugin instead of the script below. ]]></description><link>https://jcode.me/octoprint-push-notifications/</link><guid isPermaLink="false">Ghost__Post__5c863c1eeaeffb0001272e14</guid><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Tue, 12 Mar 2019 20:00:00 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2019/03/rawpixel-617368-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://ghost.jcode.me/content/images/2019/03/rawpixel-617368-unsplash.jpg" alt="Octoprint: Push notifications"/><p>Since I'm touching on a few things Octoprint this week I thought I'd also post my method of getting regular updates from my printer when it's printing. </p><p>At the time of posting I'm currently writing an update to the <a href="https://github.com/thijsbekke/OctoPrint-Pushover?ref=ghost.jcode.me">Octoprint plugin</a> to do this all internally, but in the meantime this will have to do.</p><p>EDIT: </p><p><a href="https://github.com/thijsbekke/OctoPrint-Pushover/pull/28?ref=ghost.jcode.me">Pull request merged!</a> You should use the plugin instead of the script below.</p><p/><hr><p/><p>Cron this up as often as you'd like to get notifications, I personally chose once an hour. </p><p>It doesn't send notifications unless the printer is printing.</p><pre><code class="language-python">import json import urllib from pushover import Client from urllib2 import Request, urlopen def octoprint(path): q = Request("http://10.0.0.60/api/{}".format(path)) q.add_header("X-Api-Key", "[OCTOPRINT API KEY]") a = urlopen(q).read() return json.loads(a) def sendSnapshot(): client = Client("[PUSHOVER CLIENT KEY]", api_token="[PUSHOVER API TOKEN]") urllib.urlretrieve("http://10.0.0.60/webcam/?action=snapshot", "/tmp/snapshop.jpg") job = octoprint("job") message = "{}% Complete".format(round(job['progress']['completion'], 2)) with open("/tmp/snapshop.jpg", "rb") as image: client.send_message(message, attachment=image) if __name__ == "__main__": printer = octoprint("printer?history=false&limit=0") if printer["state"]["text"] == "Printing": sendSnapshot() </code></pre><p/></hr>]]></content:encoded></item><item><title><![CDATA[Octoprint: turn off your webcam automatically]]></title><description><![CDATA[I'm not a fan of leaving a webcam running unattended in my home, even if its pointed at my printer and a wall.]]></description><link>https://jcode.me/octoprint-turning-off-webcam-automatically/</link><guid isPermaLink="false">Ghost__Post__5c845393f22ccd0001fe0dc2</guid><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Mon, 11 Mar 2019 10:40:00 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2019/03/20190305_082800.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://ghost.jcode.me/content/images/2019/03/20190305_082800.jpg" alt="Octoprint: turn off your webcam automatically"/><p>I'm not a fan of <em>leaving</em> a webcam running unattended in my home, even if its pointed at my printer and a wall.</p><p>So I made a little self-contained python script that reads the printers status from Octoprint. When the printer is printing, the webcam is on, allowing for timelapses and snapshots to be sent. If the printer is off or has just completed printing the webcam is also off. </p><p>Running every minute via Cron is a timely way to trigger the script and poll the printers status.</p><pre><code class="language-bash">* * * * * python /home/pi/autoWebcam.py</code></pre><pre><code class="language-python">import json from urllib2 import Request, urlopen import psutil import os def octoprint(path): q = Request("http://10.0.0.60/api/{}".format(path)) q.add_header("X-Api-Key", "[YOUR API KEY GOES HERE]") a = urlopen(q).read() return json.loads(a) def checkWebcamD(): for proc in psutil.process_iter(): if proc.name() == "mjpg_streamer": return True return False if __name__ == "__main__": printer = octoprint("job") if "Offline" in printer['state'] or "Operational" in printer['state']: if checkWebcamD(): os.system("/etc/init.d/webcamd stop") if "Printing" in printer['state']: # If we are printing, check the webcam # if the webcam service is not running, turn it on if not checkWebcamD(): os.system("/etc/init.d/webcamd start")</code></pre>]]></content:encoded></item><item><title><![CDATA[Find missing content with wget spider]]></title><description><![CDATA[After moving my blog from digital ocean a month ago I've had Google Search Console send me a few emails about broken links and missing content. And while fixing those was easy enough once pointed out to me, I wanted to know if there was any missing content that GSC had not found yet. I've used wget before to create an offline archive (mirror) of websites and even experimented with the spider flag but never put it to any real use. For anyone not aware, thespider flag allows wget to function in ]]></description><link>https://jcode.me/find-missing-content-with-wget-spider/</link><guid isPermaLink="false">Ghost__Post__5c2d54d507503f00017d6b22</guid><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Thu, 03 Jan 2019 23:00:00 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2019/1/25IL1PLQXSZVLXKIE4Z8.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://ghost.jcode.me/content/images/2019/1/25IL1PLQXSZVLXKIE4Z8.jpg" alt="Find missing content with wget spider"/><p>After moving my blog from digital ocean a month ago I've had Google Search Console send me a few emails about broken links and missing content. And while fixing those was easy enough once pointed out to me, I wanted to know if there was any missing content that GSC had not found yet.</p><p>I've used wget before to create an offline archive (<code>mirror</code>) of websites and even experimented with the <code>spider</code> flag but never put it to any real use.</p><p>For anyone not aware, the <code>spider</code> flag allows wget to function in an extremely basic web crawler, similar to Google's search/indexing technology and it can be used to follow every link it finds (including those of assets such as stylesheets etc) and log the results.</p><p>Turns out, it’s a pretty effective broken link finder.</p><p/><p/><h2 id="installing-wget-with-debug-mode">Installing wget with debug mode</h2><p>Debug mode is required for the command I'm going to run.</p><p>On OSX, using a package manager like Homebrew allows for the <code>--with-debug</code> option, but it doesn't appear to be working for me at the moment, luckily installing it from source is still an option.</p><p>Thankfully cURL is installed by default on OSX, so it's possible to use that to download and install wget.</p><p>Linux users should be able to use wget with debug mode without any additional work, so feel free to skip this part.</p><h3 id="download-the-source">Download the source</h3><pre><code class="language-bash">cd /tmp curl -O https://ftp.gnu.org/gnu/wget/wget-1.19.5.tar.gz tar -zxvf wget-1.19.5.tar.gz cd wget-1.19.5/ </code></pre><p/><h3 id="configure-with-openssl">Configure with openSSL</h3><pre><code class="language-bash">./configure --with-ssl=openssl --with-libssl-prefix=/usr/local/ssl</code></pre><p/><h3 id="make-and-install">Make and install</h3><pre><code class="language-bash">make sudo make install</code></pre><p/><p>With the installation complete, now it's time to find all the broken things.</p><p/><h2 id="checking-your-site">Checking your site</h2><p>The command to give wget is as follows, this will output the resulting file to your home directory <code>~/</code> so it may take a little while depending on the size of your website.</p><pre><code class="language-bash">wget --spider --debug -e robots=off -r -p http://jcode.me 2>&1 \ | egrep -A 1 '(^HEAD|^Referer:|^Remote file does not)' > ~/wget.log</code></pre><p>Let’s break this command down so you can see what wget is being told to do:</p><ul><li><code>--spider</code>, this tells wget not to download anything. </li><li><code>--debug</code>, gives extra information that we need.</li><li><code>-e robots=off</code>, this one tells wget to ignore the <code>robots.txt</code> file.</li><li><code>-r</code>, this means recursive so wget will keep trying to follow links deeper into your sites until it can find no more.</li><li><code>-p</code>, get all page requisites such as images, styles, etc. </li><li><code>https://jcode.me</code>, the website url. Replace this with your own.</li><li><code>2>&1</code>, take <code>stderr</code> and merge it with <code>stdout</code>. </li><li><code>|</code>, this is a pipe, it sends the output of one program to another program for further processing.</li><li><code>egrep -A 1 '(^HEAD|^Referer:|^Remote file does not)'</code>, find instances of the strings "HEAD", "Referer" and "Remote file does not". Print out these lines and the ones above it.</li><li><code>> ~/wget.log</code>, output everything to a file in your home directory.</li></ul><p/><h2 id="reading-the-log">Reading the log</h2><p>Using grep we can take a look inside the log file, filtering out all the successful links and resources, and only find references to the lines which contain the phrase <code>broken link</code>.</p><pre><code class="language-bash">grep -B 5 'broken' ~/wget.log</code></pre><p>It will also return the 5 lines below that line so that you can see the resource concerned (<code>HEAD</code>) and the page where the resource was referenced (<code>Referer</code>). </p><p>An example of the output;</p><pre><code class="language-bash">-- HEAD /autorippr-update/ HTTP/1.1 Referer: https://jcode.me/makemkv-auto-ripper/ User-Agent: Wget/1.16.3 (darwin18.2.0) -- Remote file does not exist -- broken link!!! -- HEAD /content/images/2019/01/tpvd27rgco7ssa21.jpg HTTP/1.1 Referer: https://jcode.me/makemkv-auto-ripper/ User-Agent: Wget/1.16.3 (darwin18.2.0) -- Remote file does not exist -- broken link!!!</code></pre>]]></content:encoded></item><item><title><![CDATA[How much power does a 3D printer use?]]></title><description><![CDATA[There are three greatest questions a man can ask. These questions have no known answer and have confused geologists the world over. Today, I add another great question to the mix; How much power does my 3D printer use?]]></description><link>https://jcode.me/how-much-power-does-a-3d-printer-use/</link><guid isPermaLink="false">Ghost__Post__5c2350817476b30001331c5b</guid><category><![CDATA[3D printing]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Thu, 27 Dec 2018 06:30:00 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2019/01/IMG_1414.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://ghost.jcode.me/content/images/2019/01/IMG_1414.jpg" alt="How much power does a 3D printer use?"/><p>The three greatest questions a man can ask; What is OK short for? Why do I have nipples? and How does my mum know I'm lying? </p><p>These questions have no known answer and have confused geologists the world over. </p><p>Today, I add another great question to the mix; How much power does my 3D printer use?</p><hr><p>In this post I'll be measuring arbitrary things that get hot or move on my Prusa i3 Mk3. The printer is running firmware version: <code>3.5.1</code>, not that it will matter much be it's better to have too many points of data rather than none. </p><p>The current outside temperature is 39°C, inside it's only cooler in a few rooms, but not in the room dedicated to noise and electronics. </p><p>The printer tells me that it's bed and hot end temperature is sitting at 35<strong>°</strong></p><p>Conditions in this room are pretty stable today, no fans or AC so there should be no external influences interfering with the science.</p><p>I'm using a "Smart Wi-Fi Plug with Energy Monitoring" the HS110 from TP-Link. </p><p>Lets measure some watts!</p><hr><h2 id="off">Off</h2><p>First up is the printer off, only smart-switch using power. </p><figure class="kg-card kg-image-card"><img src="https://ghost.jcode.me/content/images/2018/12/Screen-Shot-2018-12-26-at-9.56.56-pm.png" class="kg-image" alt="How much power does a 3D printer use?" loading="lazy"/></figure><p/><p/><h2 id="idle">Idle</h2><p>Then we have the printer on, finished its boot sequence and sitting pretty, no fans spinning, no heaters engaged. </p><figure class="kg-card kg-image-card"><img src="https://ghost.jcode.me/content/images/2018/12/Screen-Shot-2018-12-26-at-9.58.31-pm.png" class="kg-image" alt="How much power does a 3D printer use?" loading="lazy"/></figure><p/><h2 id="stepper-motors">Stepper motors</h2><p>Turning on the X, Y, or Z steppers add an additional 2w each, if they are moving or not doesn't matter due to the way stepper motors work.</p><p/><figure class="kg-card kg-image-card"><img src="https://ghost.jcode.me/content/images/2018/12/Screen-Shot-2018-12-26-at-10.00.14-pm.png" class="kg-image" alt="How much power does a 3D printer use?" loading="lazy"/></figure><p/><p/><h2 id="printer-bed">Printer bed</h2><p>Cranking to bed temperature to regular PLA settings (60<strong>°</strong>) shows the printer drawing 240 at peak, slowly fading off until, after 3 minutes, the desired temperature has been reached.</p><p>As the bed cools down the printer draws between 19w and 100w as the heater kicks in.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://ghost.jcode.me/content/images/2018/12/Screen-Shot-2018-12-26-at-10.08.18-pm.png" class="kg-image" alt="How much power does a 3D printer use?" loading="lazy"/></figure><p/><h2 id="hot-end">Hot end</h2><p>The hot end takes 2 minutes to heat up to a toasty 195 degrees, drawing a maximum of 60w which tapers off quickly. Using between 19w and 40w in similar fashion to the heated bed. </p><p/><h2 id="fan">Fan</h2><p>Honestly the fan barely made a bump in the overall watts being drawn, at 100% speed it added between 1 - 2 watts.</p><figure class="kg-card kg-image-card"><img src="https://ghost.jcode.me/content/images/2018/12/Screen-Shot-2018-12-26-at-10.12.57-pm.png" class="kg-image" alt="How much power does a 3D printer use?" loading="lazy"/></figure><p/><p/><p/><h2 id="starting-a-print">Starting a print</h2><p>With both the bed and the hot end heating up the printer draws 286 watts at the start. Exactly as before it quickly fades as they reach the desired temperatures. </p><figure class="kg-card kg-image-card"><img src="https://ghost.jcode.me/content/images/2018/12/Screen-Shot-2018-12-26-at-10.18.10-pm.png" class="kg-image" alt="How much power does a 3D printer use?" loading="lazy"/></figure><p/><p>After the printer has concluded its pre-heating phase it begins mesh bed leveling. The moving, probing and calculating of meshes doesn't even register on the watt meter as the bed needs to be continuously warmed during the process. </p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://ghost.jcode.me/content/images/2018/12/Screen-Shot-2018-12-26-at-10.19.45-pm.png" class="kg-image" alt="How much power does a 3D printer use?" loading="lazy"/></figure><p/><p/><h2 id="printer-in-action">Printer in action</h2><p>Now that the printer has been warmed up for a few minutes it's time to start an actual print and produce the results that are useful. </p><p>Using #3D Benchy as a test print for this part of the experiment gives a total run time of 1.5 hours. </p><p>The smart switch I'm using can export average data for every minute </p><p>Removing idle and null power usage we get an average of 86.07 watts, removing the warm up power usage reduces the average to 84.13 watts.</p><p>Since power is often paid for in kilowatts we need to break out the calculator. </p><p>Divide the number of watts by 1,000. </p><p>Remember to show your working. The result is 0.08413kW</p><p>Now turn that number into kWh multiply by hours </p><figure class="kg-card kg-image-card"><img src="https://ghost.jcode.me/content/images/2018/12/Screen-Shot-2018-12-27-at-4.09.27-pm.png" class="kg-image" alt="How much power does a 3D printer use?" loading="lazy"/></figure><p/><p>The following might not help out so much, since it's only for South Australia's power prices. But if you happen to live in the area...</p><p>SA Power Networks thinks power grows on trees like oranges, and requires extra work to juice out the sweet sweet fuel it uses to run the generators. Current prices are 43.67 cents per kWh.</p><figure class="kg-card kg-image-card"><img src="https://ghost.jcode.me/content/images/2018/12/Screen-Shot-2018-12-27-at-4.12.37-pm.png" class="kg-image" alt="How much power does a 3D printer use?" loading="lazy"/></figure><p>That's a total of 5.5 cents of power for each benchy. Or 3.6 cents per hour of run time.</p><p>Handy when calculating how much to charge for parts.</p><p/><figure class="kg-card kg-image-card kg-width-full"><img src="https://ghost.jcode.me/content/images/2018/12/Screen-Shot-2018-12-27-at-10.58.24-am-1.png" class="kg-image" alt="How much power does a 3D printer use?" loading="lazy"/></figure></hr></hr>]]></content:encoded></item><item><title><![CDATA[Laravel + Android push notifications]]></title><description><![CDATA[Webviews. Android developers dislike using them as an entire app replacement and I'm no different. But when your client has no money left after spending it all on iOS development do you tell them no, or do you offer an alternative solution? I would hazard a guess, and say this is where a decent chunk of webview apps come from. There's a project in the pipeline at my work where this has happened and I can foresee it happening again as Android users are still seen as filthy peasants who don't ]]></description><link>https://jcode.me/laravel-android-push-notifications/</link><guid isPermaLink="false">Ghost__Post__5c1b2988f7c6990001490793</guid><category><![CDATA[Android]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Mon, 24 Dec 2018 04:52:29 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2018/12/smartmockups_jpw5uyah.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://ghost.jcode.me/content/images/2018/12/smartmockups_jpw5uyah.jpg" alt="Laravel + Android push notifications"/><p><strong>Webviews.</strong> Android developers dislike using them as an entire app replacement and I'm no different. But when your client has no money left after spending it all on iOS development do you tell them no, or do you offer an alternative solution? </p><p>I would hazard a guess, and say this is where a decent chunk of webview apps come from. </p><p>There's a project in the pipeline at my work where this has happened and I can foresee it happening again as Android users are still seen as filthy peasants who don't deserve a native application. </p><figure class="kg-card kg-image-card"><img src="https://ghost.jcode.me/content/images/2018/12/maxresdefault.jpg" class="kg-image" alt="Laravel + Android push notifications" loading="lazy"/></figure><p>While webview apps are easy enough to build I'm looking at making the experience a little nicer with some push notifications, default to device caching and a splash screen while loading.</p><p>I'm going to update the template I'm building over <a href="https://github.com/JasonMillward/android-laravel-webview-push-notifications?ref=ghost.jcode.me">on GitHub</a>, and I'll post a few more updates as I add features.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/JasonMillward/android-laravel-webview-push-notifications?ref=ghost.jcode.me"><div class="kg-bookmark-content"><div class="kg-bookmark-title">JasonMillward/android-laravel-webview-push-notifications</div><div class="kg-bookmark-description">Contribute to JasonMillward/android-laravel-webview-push-notifications development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/favicon.ico" alt="Laravel + Android push notifications"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">JasonMillward</span></img></div></div><div class="kg-bookmark-thumbnail"><img src="https://avatars2.githubusercontent.com/u/1149781?s=400&v=4" alt="Laravel + Android push notifications"/></div></a></figure>]]></content:encoded></item><item><title><![CDATA[Auto-purging Cloudflare's cache with Zapier]]></title><description><![CDATA[It sounds nasty but with my aggressive caching I need a way to purge the cache when I publish a new blog post. An automated way would be best. Thus; auto-purge.]]></description><link>https://jcode.me/auto-purging-cloudflare-cache/</link><guid isPermaLink="false">Ghost__Post__5c16e5299de85300014875f7</guid><category><![CDATA[cloudflare]]></category><category><![CDATA[Ghost]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Mon, 17 Dec 2018 21:30:00 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2018/12/S9WG5D2CMC6UY0U3EEFH.jpg" medium="image"/><content:encoded><![CDATA[<h2 id="autopurge">Autopurge</h2><img src="https://ghost.jcode.me/content/images/2018/12/S9WG5D2CMC6UY0U3EEFH.jpg" alt="Auto-purging Cloudflare's cache with Zapier"/><p>It sounds nasty but with my <a href="https://jcode.me/speeding-up-ghost/?ref=ghost.jcode.me">aggressive caching</a> I need a way to purge the cache when I publish a new blog post. An automated way would be best. Thus; auto-purge.</p><p/><p>Enter Zapier. The <a href="https://ifttt.com/?ref=ghost.jcode.me">IFTTT</a> for webhooks. </p><figure class="kg-card kg-image-card"><img src="https://ghost.jcode.me/content/images/2018/12/Screen-Shot-2018-12-17-at-11.12.13-am.png" class="kg-image" alt="Auto-purging Cloudflare's cache with Zapier" loading="lazy"/></figure><p/><p>The process works like this:</p><!--kg-card-begin: markdown--><ol> <li>I publish a new post</li> <li>Ghost fires off a webhook to Zapier</li> <li>Zapier receives this webhook, and sends a request to Cloudflare via API</li> <li>Cloudflare purges</li> </ol> <!--kg-card-end: markdown--><p/><p>In order to set this up like I have, you'll need a few things;</p><!--kg-card-begin: markdown--><ol> <li>A Zapier account</li> <li>A Cloudflare account</li> <li>The email address for your Cloudflare account</li> <li>Your site’s Zone ID</li> <li>Your Cloudflare API key</li> </ol> <!--kg-card-end: markdown--><p/><p/><p>Once you've got everything prepared, head on over to Zapier and create a new Zap.</p><p/><h2 id="trigger">Trigger</h2><p/><p>You'll want to find Ghost as the trigger app.</p><figure class="kg-card kg-image-card"><img src="https://ghost.jcode.me/content/images/2018/12/Screen-Shot-2018-12-17-at-6.18.38-pm.png" class="kg-image" alt="Auto-purging Cloudflare's cache with Zapier" loading="lazy"/></figure><p>And set the trigger to a new story. Defining the published status comes a bit later in the setup.</p><figure class="kg-card kg-image-card"><img src="https://ghost.jcode.me/content/images/2018/12/Screen-Shot-2018-12-17-at-6.18.51-pm-1.png" class="kg-image" alt="Auto-purging Cloudflare's cache with Zapier" loading="lazy"/></figure><p/><p>Connect to your Ghost instance. You'll need to define a full URL, <code>https://</code> and all.</p><figure class="kg-card kg-image-card"><img src="https://ghost.jcode.me/content/images/2018/12/Screen-Shot-2018-12-17-at-6.21.24-pm.png" class="kg-image" alt="Auto-purging Cloudflare's cache with Zapier" loading="lazy"/></figure><p/><p>And here is where we set the trigger status to be published. There are others available but published is the status we care about for this Zap.</p><figure class="kg-card kg-image-card"><img src="https://ghost.jcode.me/content/images/2018/12/Screen-Shot-2018-12-17-at-6.21.36-pm.png" class="kg-image" alt="Auto-purging Cloudflare's cache with Zapier" loading="lazy"/></figure><p/><h2 id="action">Action</h2><p/><p/><p>Cloudflare doesn't have an app in Zapier, so we'll have to make do with a webhook...</p><figure class="kg-card kg-image-card"><img src="https://ghost.jcode.me/content/images/2018/12/Screen-Shot-2018-12-17-at-6.22.09-pm.png" class="kg-image" alt="Auto-purging Cloudflare's cache with Zapier" loading="lazy"/></figure><p/><p>... a custom webhook!</p><figure class="kg-card kg-image-card"><img src="https://ghost.jcode.me/content/images/2018/12/Screen-Shot-2018-12-17-at-6.22.15-pm.png" class="kg-image" alt="Auto-purging Cloudflare's cache with Zapier" loading="lazy"/></figure><p/><p>When setting up the webhook it needs to be a POST request to URL:<code>https://api.cloudflare.com/client/v4/zones/<Zone ID>/purge_cache</code></p><p>Ignore data pass-through.</p><p>Set the data to the following JSON;</p><!--kg-card-begin: markdown--><pre><code>{ "purge_everything":true } </code></pre> <!--kg-card-end: markdown--><p>And add in two headers, their keys are <code>X-Auth-Email</code> and <code>X-Auth-Key</code>.</p><figure class="kg-card kg-image-card"><img src="https://ghost.jcode.me/content/images/2018/12/Screen-Shot-2018-12-17-at-6.23.47-pm.png" class="kg-image" alt="Auto-purging Cloudflare's cache with Zapier" loading="lazy"/></figure><p/><p>Once that's done hit continue and test the webhook.</p><figure class="kg-card kg-image-card"><img src="https://ghost.jcode.me/content/images/2018/12/Screen-Shot-2018-12-17-at-6.23.09-pm.png" class="kg-image" alt="Auto-purging Cloudflare's cache with Zapier" loading="lazy"/></figure><p/><p>Done! </p><p/><p>With all of that hard work out of the way your super cache purging adventure can begin. </p><p>Happy blogging!</p><p/><figure class="kg-card kg-image-card kg-width-wide"><img src="https://ghost.jcode.me/content/images/2018/12/Screen-Shot-2018-12-17-at-6.24.36-pm.png" class="kg-image" alt="Auto-purging Cloudflare's cache with Zapier" loading="lazy"/></figure>]]></content:encoded></item><item><title><![CDATA[Speeding up Ghost or; how I moved everything to the cloud]]></title><description><![CDATA[My Blog used to be hosted over at DigitalOcean, on one of the smallest droplets available. But as times changed I wanted to move it in house - like actually into my house. ]]></description><link>https://jcode.me/speeding-up-ghost/</link><guid isPermaLink="false">Ghost__Post__5c122b8ad012490001e4c1fe</guid><category><![CDATA[Ghost]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Fri, 14 Dec 2018 11:49:46 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2018/12/W16CG6K0GREV0JEAVIA7.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://ghost.jcode.me/content/images/2018/12/W16CG6K0GREV0JEAVIA7.jpg" alt="Speeding up Ghost or; how I moved everything to the cloud"/><p>My Blog used to be hosted over at DigitalOcean, on one of the smallest droplets available. But as times changed I wanted to move it in house - like actually into my house. </p><p>I have an UnRaid server serving my Plex library and handling my device backups via Docker, so I figured why not bring Ghost into the mix as well. </p><p>'Installing' Ghost was straight forward and it was pretty fast locally, but externally the site took over 5 seconds to fully load, sometimes even taking in excess of 15 seconds. </p><p>Google has said a few things on page load times in the past, here are two of the most popular quotes;</p><blockquote>"<strong>The average time it takes to fully load the average mobile landing page is 22 seconds. However, research also indicates 53% of people will leave a mobile page if it takes longer than 3 seconds to load.</strong>"</blockquote><blockquote><em><strong>"</strong><em><strong><strong>2 seconds is the threshold for ecommerce website acceptability. At Google, we aim for under a half second.</strong></strong></em><strong>"</strong></em></blockquote><p>2 - 3 seconds, this sounds reasonable to me. </p><p>While this blog is not a commercial website it seems like I have a goal.</p><p/><hr><p/><!--kg-card-begin: markdown--><h2 id="phase0initialloadtimes">Phase 0; Initial load times</h2> <!--kg-card-end: markdown--><p>What would a little optimisation be without a baseline?</p><p>I'm going to be using GTmetrix throughout this experiment. There are many sites out there to test a sites speed. Using one or the other doesn't make too much difference, but using one and only one will produce consistent and meaningful results. </p><figure class="kg-card kg-image-card"><img src="https://ghost.jcode.me/content/images/2018/12/Screen-Shot-2018-12-14-at-6.08.22-pm.png" class="kg-image" alt="Speeding up Ghost or; how I moved everything to the cloud" loading="lazy"/></figure><p>As you can see above, the main page takes 10+ seconds to load 4MB over 32 requests. Not going to win any records for speed, that's for sure.</p><p/><!--kg-card-begin: markdown--><h2 id="phase1cloudflareandgzip">Phase 1; Cloudflare and GZIP</h2> <!--kg-card-end: markdown--><p>I've had a Cloudflare account for a while now, it's my goto DNS manager even on the free tier. </p><p>My preference for configuring rules in Cloudflare is to use what they call "Page Rules" which gives access to all the relevant cache settings on one page.</p><p>I set up a rule for all pages <code>*</code> using the follow rules. Not much to it, just bumped up the maximum cache/TTL settings and a medium security level.</p><figure class="kg-card kg-image-card"><img src="https://ghost.jcode.me/content/images/2018/12/Screen-Shot-2018-12-16-at-10.10.15-am.png" class="kg-image" alt="Speeding up Ghost or; how I moved everything to the cloud" loading="lazy"/></figure><p/><p>GZip is a must, but in my haste to set up the new environment I didn't enable it. </p><p>After enabling both GZip and setting Cloudflare to minify what it could the results were looking a little better. But there is still room for improvement. </p><p/><figure class="kg-card kg-image-card"><img src="https://ghost.jcode.me/content/images/2018/12/Screen-Shot-2018-12-14-at-6.09.06-pm.png" class="kg-image" alt="Speeding up Ghost or; how I moved everything to the cloud" loading="lazy"/></figure><p/><!--kg-card-begin: markdown--><h2 id="phase2cdncloudinary">Phase 2; CDN? Cloudinary</h2> <!--kg-card-end: markdown--><p>This one is obvious when looking at the total page size of the last result. And when minifying HTML only saved 0.02MB it's time to look at what we look at the images.</p><p>Now I've covered <a href="https://jcode.me/adventures-in-wordpress-vol-5-cdn/?ref=ghost.jcode.me">content delivery networks before</a>. But I'm cheap and lazy, this blog doesn't get the traffic to warrant large paying a monthly fee, so my hunt for cheap or even free CDNs was on.</p><p>One of the <a href="https://docs.ghost.org/integrations/cloudinary/?ref=ghost.jcode.me">Ghost integrations</a> is Cloudinary. </p><p>Utilising Cloudinary's API/Fetch URLs means I can upload images in any format/size and have them resized and optimised without having to run anything extra, as a bonus their free plan provides more than enough storage/bandwidth for what I need from it.</p><p>Because I'm running Ghost in a Docker container only files in the <code>content</code> folder are using persistent storage, everything else gets wiped when the docker container is updated or restarted. </p><p>Themes are persistent, which is why I chose to make use of their <a href="https://cloudinary.com/documentation/fetch_remote_images?ref=ghost.jcode.me">fetch</a> and <a href="https://cloudinary.com/documentation/image_transformations?ref=ghost.jcode.me">transformation</a> URLs by editing a few key areas such as <code>post-card.hbs</code>, <code>post.hbs</code> and <code>index.hbs</code>. This <em>doesn't</em> cover every image I upload but it does cover the index page where a lot of images are loaded and the headers of individual posts.</p><p/><p>The fetch URL allows for flags to be set;</p><pre><code class="language-bash">https://res.cloudinary.com/<USER_NAME>/image/fetch/<FLAGS>/<IMAGE URL> </code></pre><p>After some trial and error the flags I ended up with were;</p><pre><code class="language-bash">w_600 => width 600px h_400 => height 400px c_fit => fit the image into these bounds q_auto => set quality level to auto f_auto => convert to the most optimal image type available dpr_auto => automatically scale based on the devices pixel ratio</code></pre><p>Now I was worried about <code>f_auto</code> a little bit, would Cloudinary serve the most optimal format even if I include an extension? The answer is yes.</p><blockquote>Even with the jpg extension the image will still be encoded using WebP so long as you include the <code>f_auto</code> parameter.</blockquote><p/><p>To handle the image inside posts I threw in a little javascript;</p><pre><code class="language-javascript"><script> // For every image tag $('img').each(function(key, obj) { // Get the src of the image var src = $(obj).attr('src'); // If the image src doesn't have http... // we can assume it's a relative path if (src.indexOf('http') === -1) { src = 'https://jcode.me/' + src; } // If the image src doesn't have // cloudinary in the string // Make it use cloudinary if (src.indexOf('res.cloudinary') === -1) { $(obj).attr('src', 'https://res.cloudinary.com/<USER_NAME>/image/fetch/<FLAGS>/' + src ); } }); </script></code></pre><p>Prepared to see some minor savings at best, the results actually surprised me. </p><p>With the aid of Cloudinary I'm able upload any image in any format and have it get transformed into an optimised copy and let people load my blog in just under 500ms.</p><figure class="kg-card kg-image-card kg-width-full"><img src="https://ghost.jcode.me/content/images/2018/12/Screen-Shot-2018-12-14-at-7.07.30-pm.png" class="kg-image" alt="Speeding up Ghost or; how I moved everything to the cloud" loading="lazy"/></figure><p/><p/><!--kg-card-begin: markdown--><h2 id="afterthought">Afterthought;</h2> <!--kg-card-end: markdown--><p>Companies come and go, CDNs are no different. Some even disappear overnight.</p><p>Putting all my faith into an external entity is not something I do lightly, which is why I opted to use the URL method instead of using the storage adapter where "images are uploaded directly to Cloudinary and integrated into its media library". </p><p>In the event that Cloudinary does disappear or remove their free plan, all I have to do is remove their URL prefix from my theme and I still have a working albeit slower website. </p><p/></hr>]]></content:encoded></item><item><title><![CDATA[Git hooks and Freshdesk]]></title><description><![CDATA[When your team is using Freshdesk to manage support requests that require changes in a Git repository, tracking changes manually can be annoying and time consuming. With Git 2.9+ global hooks can take care of the trouble by adding a folder and simply letting Git know about it. At my company, all Freshdesk users install the hooks with 3 lines from the repositories readme . cd ~/repos git clone git@gitlab.com:jcode/freshdesk-commit-hook.git git config --global core.hooksPath /Users/$USER/repo]]></description><link>https://jcode.me/git-hooks-and-freshdesk/</link><guid isPermaLink="false">Ghost__Post__5c108461a27a880001ad6d85</guid><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Wed, 12 Dec 2018 09:29:01 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2018/12/TKIG8R0ILOBYIYQJ1BNK.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://ghost.jcode.me/content/images/2018/12/TKIG8R0ILOBYIYQJ1BNK.jpg" alt="Git hooks and Freshdesk"/><p>When your team is using Freshdesk to manage support requests that require changes in a Git repository, tracking changes manually can be annoying and time consuming. </p><p>With Git 2.9+ global hooks can take care of the trouble by adding a folder and simply letting Git know about it. </p><p>At my company, all Freshdesk users install the hooks with 3 lines from the repositories <code>readme</code> .</p><!--kg-card-begin: markdown--><pre><code>cd ~/repos git clone git@gitlab.com:jcode/freshdesk-commit-hook.git git config --global core.hooksPath /Users/$USER/repos/freshdesk-commit-hook </code></pre> <!--kg-card-end: markdown--><p/><p>The hook itself is a <code>post-commit</code> that reads the most recent commit, and, as long as the developer has followed the outlined commit message structure (<code>FD#[0-9]+</code>) the hook picks it up and leaves a private note on that ticket providing a link to the repos commit diff and the output of <code>git log -1 --format=medium</code>.</p><p/><p>An example commit;</p><!--kg-card-begin: markdown--><pre><code>git commit -m "FD#0001 - Addressing clients request and making a change" </code></pre> <!--kg-card-end: markdown--><p/><p/><p>And of course, the result.</p><figure class="kg-card kg-image-card"><img src="https://ghost.jcode.me/content/images/2018/12/Screen-Shot-2018-12-12-at-7.37.37-pm.png" class="kg-image" alt="Git hooks and Freshdesk" loading="lazy"/></figure><p/>]]></content:encoded></item><item><title><![CDATA[A rant on the naming of projects]]></title><description><![CDATA[Developers are an interesting group when it comes to naming creations. In my opinion you have a few distinct groups, and they are developers who name the application after something it does (Transcoder, Mediator, Console, make), name it after something it does and affix a verb (Autorippr), call it something seemingly nonsensical (Yarn, Laravel, Composer) and those who abbreviate a short description of what it does (NPM, RVM). > NB: Dropping vowels is optional Most of the above are descriptiv]]></description><link>https://jcode.me/a-rant-on-the-naming-of-projects/</link><guid isPermaLink="false">Ghost__Post__598301a21bef1564f80a83c1</guid><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Sun, 06 Aug 2017 01:30:00 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2017/08/a-rant-on-the-naming-of-projects.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2017/08/a-rant-on-the-naming-of-projects.jpg" alt="A rant on the naming of projects"/><p>Developers are an interesting group when it comes to naming creations.</p> <p>In my opinion you have a few distinct groups, and they are developers who name the application after something it does (<code>Transcoder</code>, <code>Mediator</code>, <code>Console</code>, <code>make</code>), name it after something it does and affix a verb (<code>Autorippr</code>), call it something seemingly nonsensical (<code>Yarn</code>, <code>Laravel</code>, <code>Composer</code>) and those who abbreviate a short description of what it does (<code>NPM</code>, <code>RVM</code>).</p> <blockquote> <p>NB: Dropping vowels is optional</p> </blockquote> <p>Most of the above are descriptive enough in their own name. Just by looking at them you could guess at what most of them do. Transcoder transcodes, Autorippr rips automatically, <strong>N</strong>ode <strong>P</strong>ackage <strong>M</strong>anager manages node packages. You don't need a a short paragraph to explain what they do.</p> <p>Recently I've been seeing a trend in the tech world, take something boring but simple and make it sound hip and cool. Appeal to the developers to show how laid back the business is. This ain't your daddy's corporate environment any more.</p> <p>There are some things that these names work well for, sprints for example.</p> <p>Sprints come and go, most lasting 2 weeks or less and get an incrementing number.</p> <ul> <li>Sprint 1</li> <li>Sprint 2</li> <li>Sprint 3</li> </ul> <p>Now you can make things fun and name them after Pokemon, the doctors of Dr. Who or elements from the periodic table...</p> <ul> <li>Koffing</li> <li>Cadmium</li> <li>Christopher Eccleston</li> </ul> <p>These work because you almost never refer to the sprint number as part of your development cycle. Tickets yes, sprint numbers, not really.</p> <p>This is the perfect spot to inject some fun.</p> <p>Streams or projects on the other hand can't handle that sort of renaming.</p> <p>Take these 100% original, boring names</p> <ul> <li>Mobile stream</li> <li>Web stream</li> <li>Ops stream</li> <li>QA stream</li> </ul> <p>These are boring, but you know exactly what they contain.</p> <p>You're not writing a fantasy novel, or creating a hip new start up, you need something people understand today, tomorrow, and whenever people talk about it to others out of the loop.</p> <p>If you were to read these, could you tell me what kind of work went on in the streams?</p> <ul> <li>Baratheon Stream</li> <li>Lannister Stream</li> <li>Targaryen Stream</li> <li>Tully Stream</li> <li>Stark Stream</li> </ul> <p>They're not fun, they're not hip. <strong>They</strong>. <strong>are</strong>. <strong>confusing</strong>.</p> <p>If you have to describe the stream as "Baratheon (Ops)" then why waste time? Just say "Ops".</p> <h1 id="keepitsimplestupid">Keep It Simple, Stupid</h1> <p>I'm not against fun.</p> <p><img src="https://ghost.jcode.me/content/images/2013/Sep/1335581378629.jpg" alt="A rant on the naming of projects" loading="lazy"/></p> <p>Fun is good, it keeps the team together. Just have fun in the right places.</p> <!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Bulk remove time from JIRA]]></title><description><![CDATA[ If delete an entry and copy the request as cURL, and repeat it, changing only the time entry ID you can remove your own entires in bulk. Here's a simple bash script to do exactly that.]]></description><link>https://jcode.me/bulk-remove-time-from-jira/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff74</guid><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Sat, 29 Jul 2017 10:32:15 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2017/06/bulk-remove-time-from-jira.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2017/06/bulk-remove-time-from-jira.jpg" alt="Bulk remove time from JIRA"/><p><img src="https://ghost.jcode.me/content/images/2017/06/jira-curl.jpg" alt="Bulk remove time from JIRA" loading="lazy"/></p> <p>If delete an entry and copy the request as cURL, and repeat it, changing only the time entry ID you can remove your own entires in bulk.</p> <p>Here's a simple bash script to do exactly that.</p> <pre><code>#!/bin/bash START=31052 END=$(($START + 356)) ARGS="-H 'Pragma: no-cache' -H 'Origin: https://app.tempo.io' ..." for i in `seq $START $END`; do curl "https://app.tempo.io/rest/tempo-timesheets/4/worklogs/$i" -X DELETE $ARGS & done </code></pre> <!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Overcoming 5MB of localStorage with LZW compression]]></title><description><![CDATA[In one part of my recent projects I was asked to build a web based interface for a student learning platform. The client had specified that the app was intended for use in regional Australia, where the internet is mostly slow or non-existent and frequent API calls were not something to rely upon. So I started work on the app, having it preload the required content and store it in localStorage. Part of the preloaded content was a dictionary of translations which ended up being very large. When]]></description><link>https://jcode.me/overcoming-5mb-of-localstorage-with-lzw-compression/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff73</guid><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Fri, 16 Jun 2017 01:40:00 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2017/06/overcoming-5mb-of-localstorage-with-lzw-compression.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2017/06/overcoming-5mb-of-localstorage-with-lzw-compression.jpg" alt="Overcoming 5MB of localStorage with LZW compression"/><p>In one part of my recent projects I was asked to build a web based interface for a student learning platform.</p> <p>The client had specified that the app was intended for use in regional Australia, where the internet is mostly slow or non-existent and frequent API calls were not something to rely upon.</p> <p>So I started work on the app, having it preload the required content and store it in localStorage. Part of the preloaded content was a dictionary of translations which ended up being very large.</p> <p>When development was nearing completion, we started cross-browser testing. Chrome and FireFox both performed perfectly, but Safari couldn't get past the preloading stage.</p> <p>As it turns out, Safari has a limit of 5mb on localStorage and the data being pulled down was easily going over that.</p> <pre><code>classrooms = 1577.72 KB dictionary = 4946.14 KB story = 59.66 KB Total = 6583.65 KB </code></pre> <p>Since I was storing the data as JSON I thought that using some compression would be the way to go since there's a lot of duplicate strings and plain text.</p> <p>As it turns out, compressing the data was easy enough and reduced the data being stored by a massive 85%.</p> <pre><code>classrooms = 617.89 KB dictionary = 373.52 KB story = 14.10 KB Total = 1005.64 KB </code></pre> <p>This also has an added bonus of allowing <em>more</em> customer created data to come through the API without hitting that 5MB limit right away.</p> <p>Here's a quick rundown of what I did.</p> <hr> <h4 id="adddependency">Add dependency</h4> <pre><code>yarn add lz-string </code></pre> <h4 id="import">Import</h4> <pre><code>window.LZString = require('lz-string'); </code></pre> <h4 id="setter">Setter</h4> <pre><code>window.setStorage = function(key, value) { localStorage.setItem( key, LZString.compress( JSON.stringify( value ) ) ); }; </code></pre> <h4 id="getter">Getter</h4> <pre><code>window.getStorage = function(key) { return JSON.parse( LZString.decompress( localStorage.getItem(key) ) ); }; </code></pre> <p>And you're done. Just reference <code>getStorage</code>/<code>setStorage</code> instead of <code>localStorage.getItem</code>/<code>localStorage.setItem</code> and its compressed data all day every day.</p> <p><br><br/></br></p> <blockquote> <p>WebSQL was looked at in conjunction with using localStorage but W3C ceased working on the specification in November 2010.</p> </blockquote> <!--kg-card-end: markdown--></hr>]]></content:encoded></item><item><title><![CDATA[Extracting .png files from a .bin sprite sheet]]></title><description><![CDATA[Here's a little script I wrote to extract a whole lot of .png files from a compiled sprite sheet. The python script reads the .bin file as binary, finds the starting header of a .png file (89504E47) and the footer (49454E44AE426082) and separates it into individual images. This may not be the worlds best or most useful script, but it saved me several hours of copy and paste. import binascii import re import os for directory, subdirectories, files in os.walk('.'): for file in files: ]]></description><link>https://jcode.me/extracting-png-files-from-a-bin-sprite-sheet/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff72</guid><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Fri, 12 May 2017 03:27:37 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2017/05/spritesheet.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2017/05/spritesheet.jpg" alt="Extracting .png files from a .bin sprite sheet"/><p>Here's a little script I wrote to extract a whole lot of <code>.png</code> files from a compiled sprite sheet.</p> <p>The python script reads the <code>.bin</code> file as binary, finds the starting header of a <code>.png</code> file (<code>89504E47</code>) and the footer (<code>49454E44AE426082</code>) and separates it into individual images.</p> <p>This may not be the worlds best or most useful script, but it saved me several hours of copy and paste.</p> <!--kg-card-end: markdown--><pre><code class="language-python">import binascii import re import os for directory, subdirectories, files in os.walk('.'): for file in files: if not file.endswith('.bin'): continue filenumber = 0 with open(os.path.join(directory, file)) as f: hexaPattern = re.compile( r'(89504E47.*?49454E44AE426082)', re.IGNORECASE ) for match in hexaPattern.findall(binascii.hexlify(f.read())): with open('{}-{}.png'.format(file, filenumber), 'wb+') as f: f.write(binascii.unhexlify(match)) filenumber += 1</code></pre>]]></content:encoded></item><item><title><![CDATA[Printing @ 0.2mm & 100 microns]]></title><description><![CDATA[I bought a 0.2mm Micro Swiss nozzle for my 3D printer after the provided 0.4mm wasn't achieving the resolution I required for my up and coming keycap printing job, and I was having a painful time getting prints to come out cleanly or stick to the bed for the first few prints. After playing around with the settings and generally getting frustrated, I found that raising the printing temperature by 10-20°C and tweaking the some of my slicer settings I found something that worked for my Flash Forge]]></description><link>https://jcode.me/printing-at-02mm-and-100-microns/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff71</guid><category><![CDATA[3D printing]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Tue, 29 Nov 2016 11:31:00 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2016/11/IMG_20161130_072904-01-01.jpeg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2016/11/IMG_20161130_072904-01-01.jpeg" alt="Printing @ 0.2mm & 100 microns"/><p>I bought a 0.2mm Micro Swiss nozzle for my 3D printer after the provided 0.4mm wasn't achieving the resolution I required for my up and coming keycap printing job, and I was having a painful time getting prints to come out cleanly or stick to the bed for the first few prints.</p> <p>After playing around with the settings and generally getting frustrated, I found that raising the printing temperature by 10-20°C and tweaking the some of my slicer settings I found something that worked for my Flash Forge Pro.</p> <!--kg-card-end: markdown--><pre><code class="language-none">Nozzle Diameter: 0.2mm Extrusion Multiplier: 0.9 Extrusion Width: 0.2mm Primary Layer Height: 0.1mm Perimeter Shells: 4 First Layer Height: 110% First Layer Width: 130% First Layer Speed: 40%</code></pre><p>These settings on top of a freshly leveled printing bed gave the print a rather strong grip to the printing bed that insured no curling would happen.</p>]]></content:encoded></item><item><title><![CDATA[The Begärlig terrarium]]></title><description><![CDATA[In early October I was approached by a teacher and friend to help create a simple low-cost terrarium for high-school students taking biology 101 or what might eventually become biotech 101. The idea behind the request was to teach students about photosynthesis and to give a little more hands-on experience using a terrarium and a couple of sensors enabling it to save all captured data in a format that could be easily graphed. Luckily I have some experience [http://jcode.me/ingress-badges-sour]]></description><link>https://jcode.me/begarlig/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff6e</guid><category><![CDATA[Arduino]]></category><category><![CDATA[Begarlig]]></category><category><![CDATA[terrarium]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Sun, 06 Nov 2016 20:00:00 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2016/11/Beg-rlig-terrarium.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2016/11/Beg-rlig-terrarium.jpg" alt="The Begärlig terrarium"/><p>In early October I was approached by a teacher and friend to help create a simple low-cost terrarium for high-school students taking biology 101 or what might eventually become bio<em>tech</em> 101.</p> <p><img src="https://ghost.jcode.me/content/images/2016/11/terrarium-layers.jpg" alt="The Begärlig terrarium" loading="lazy"/></p> <p>The idea behind the request was to teach students about photosynthesis and to give a little more hands-on experience using a terrarium and a couple of sensors enabling it to save all captured data in a format that could be easily graphed.</p> <p>Luckily I have <a href="http://jcode.me/ingress-badges-source/?ref=ghost.jcode.me">some experience</a> with <a href="http://jcode.me/witcher-wi-fi-sensing-medallion-part-1/?ref=ghost.jcode.me">microcontrollers</a> and other sensors;</p> <p><img src="https://ghost.jcode.me/content/images/2016/10/what-a-mess-of-wires.jpg" alt="The Begärlig terrarium" loading="lazy"/></p> <h3 id="sensors">Sensors</h3> <p>Now this got me thinking, what kind of sensors would be:</p> <p>a) useful in this situation; and<br> b) simple enough to include in this project?</br></p> <br> <p>The first one on my list would have to be some sort of light sensor because photosynthesis only happens during the day. But just how much is the minimal amount of light required to kick off the process and does brighter or more light increase the reaction?</p> <p>Temperature and/or humidity monitoring would probably be good to watch as well from more of a botanical perspective. If the terrarium is too hot or too cold it puts the plant life at risk and potentially ruins the experiment. Another thing that may be monitored are environmental conditions to determine if they affect the rate of photosynthesis.</p> <p>To prevent students from drowning their plants a soil hydrometer might be useful.</p> <p>And the most important of all sensors for photosynthesis would have to be either a CO<sub>2</sub> or O<sub>2</sub>. Without one of these the experiment wouldn't have any measurable results.</p> <p>Overall, materials that are required include the sensors, the glass container for the terrarium, soil, activated carbon or charcoal and some plants.</p> <h4 id="dht22">DHT22</h4> <p><img src="https://ghost.jcode.me/content/images/2016/10/dht22.jpg" alt="The Begärlig terrarium" loading="lazy"/></p> <blockquote> <p>The DHT22 is a low-cost digital temperature and humidity sensor. It uses a capacitive humidity sensor and a thermistor to measure the surrounding air.</p> </blockquote> <p>Both DHT11 and DHT22 are very common sensors which have a lot of documentation and presence on the web.<br> These sensors are more or less identical, though one measures more precisely, but in this experiment precision is not essential.</br></p> <h4 id="ldr">LDR</h4> <p><img src="https://ghost.jcode.me/content/images/2016/10/ldr.jpg" alt="The Begärlig terrarium" loading="lazy"/></p> <blockquote> <p>A photoresistor (or light-dependent resistor, LDR, or photocell) is a light-controlled variable resistor.</p> </blockquote> <p>Light-dependent resistors increase their resistance based on the amount of light visible, making them incredibly cheap, easy, but while they're not inaccurate they don't provide any quantifiable output like lux.</p> <h4 id="mq135">MQ135</h4> <p><img src="https://ghost.jcode.me/content/images/2016/10/MQ135.jpg" alt="The Begärlig terrarium" loading="lazy"/></p> <blockquote> <p>The MQ series of gas sensors use a small heater inside with an electro-chemical sensor. They are sensitive for a range of gasses and are used indoors at room temperature.</p> </blockquote> <p>The MQ135 is mainly sensitive for benzene, alcohol and smoke, but with the right calibration it can also detect CO<sub>2</sub>, making it probably the cheapest CO<sub>2</sub> sensor available.</p> <h3 id="plants">Plants</h3> <p>At the time of writing moss is available in large quantities thanks to a very wet Australian winter and to obtain some you required only a spade.</p> <p>However, local Garden Centres have a variety of other suitable plants such as:</p> <ul> <li>Hen and chick</li> <li>Golden Clubmoss</li> <li>Asplenium Bulbiferum</li> <li>Small succulents</li> <li>Cacti</li> <li>Moss</li> <li>Philodendron</li> <li>Peperomia</li> <li>Pilea</li> <li>Bromeliads</li> <li>Small orchids and Ferns</li> <li>Rex Begonia</li> <li>Aluminum Plant</li> <li>Pothos</li> <li>Baby Tears</li> <li>Mini English Ivy</li> </ul> <h3 id="theterrarium">The terrarium</h3> <p>After searching for a suitable container that would meet the required height and width.</p> <p>I settled on the <a href="http://www.ikea.com/au/en/catalog/products/90309783/?ref=ghost.jcode.me">Begärlig</a> vase from Ikea, giving this terrarium its official name.</p> <h3 id="carbon">Carbon</h3> <p>Pet stores have activated carbon readily available by the kg at a low cost.</p> <h3 id="costbreakdown">Cost breakdown</h3> <p>This is what I paid when I purchased almost everything from eBay. Admittedly the Arduino is a clone and the parts were from China so I had to wait a few weeks for shipping, but it kept the cost down.</p> <ul> <li>$09.99 - Begärlig vase</li> <li>$01.09 - Activated carbon <small>(3 KG @ $32.90 / 30 terrariums @ 100g ea)</small></li> <li>$02.88 - DHT22</li> <li>$04.15 - Arduino Uno R3</li> <li>$02.68 - Data logger shield, SD and RTC</li> <li>$00.99 - Light sensor</li> <li>$01.75 - MQ135</li> </ul> <p><strong>Total cost = $23.53 AUD</strong></p> <h2 id="3ddesign">3D Design</h2> <p>The philosophy behind the design was more or less, keep it simple. The plan was to create a lid type structure that would sit securely on the rim of the vase and hold the Arduino, shield and the 3 sensors but not necessarily keep it air tight.</p> <p>After measuring the inner and outer diameter of the vase and the Arduino mounting holes I ended up with this;</p> <p><img src="https://ghost.jcode.me/content/images/2016/10/terrarium_model.jpg" alt="The Begärlig terrarium" loading="lazy"/></p> <p><img src="https://ghost.jcode.me/content/images/2016/10/Assmbly.jpg" alt="The Begärlig terrarium" loading="lazy"/></p> <p>The bottom structure holds onto the rim of the vase with the sensors sitting at the bottom and Arduino secured in by screws in the middle. An opening allows for a USB cable to be inserted and provide power.</p> <p>The top is merely a dust cover with some ventilation holes. Reducing the risk of fire hazards and all that.</p> <h2 id="softwareand3dmodels">Software and 3D models</h2> <p>Built on the foundation of sharing knowledge, all Arduino microcontroller code is available for free. The project is open source, 100% free and everything is available on <a href="https://github.com/JasonMillward/Begarlig-terrarium?ref=ghost.jcode.me">GitHub</a>.</p> <p>Everything that is part of the Begärlig terrarium is licenced under the MIT licence. Which means...<br> <img src="https://ghost.jcode.me/content/images/2016/11/tldrMIT.JPG" alt="The Begärlig terrarium" loading="lazy"/></br></p> <p>In keeping with the educational motif of this project I have kept track of all of the code and 3D design changes from beginning to end using git, a version control system.</p> <h2 id="assembly">Assembly</h2> <p>Terrariums are built on layers, each layer adds something of value to the terrarium. The 3 base layers are;</p> <ul> <li>Gravel</li> <li>Activated charcoal/carbon</li> <li>Soil of some kind</li> </ul> <p>With that in mind are a extensions of ones creative side and there are no wrong ways to design the uppermost layers.</p> <p>My initial creation was a simple design with an ornament placed roughly in the middle. Over time I may add or remove ornaments, change plant life or create changes in the terrain, but for now my terrarium looks a little like this:<br> <img src="https://ghost.jcode.me/content/images/2016/11/Beg-rlig-terrarium.jpg" alt="The Begärlig terrarium" loading="lazy"/></br></p> <p><em>Watch this space - detailed wiring diagram coming soon.</em></p> <h2 id="data">Data</h2> <p>As I've written it, the Arduino outputs all information in CSV format which means it can be opened in Excel, Calc by OpenOffice, R or almost any other application out there.</p> <p>Below are some examples of the data and how it can be used.</p> <p><img src="https://ghost.jcode.me/content/images/2016/11/lightco2.png" alt="The Begärlig terrarium" loading="lazy"/></p> <!--kg-card-end: markdown--><pre><code class="language-csv">Datetime, Temp, Humidity, Light, CO2 PPM 2016/11/02 08:08:55, 26, 83, 639, 250 2016/11/02 08:08:57, 26, 83, 637, 250 2016/11/02 08:08:59, 26, 83, 635, 250 2016/11/02 08:09:01, 26, 83, 638, 232 2016/11/02 08:09:03, 26, 83, 640, 250 2016/11/02 08:09:05, 26, 83, 643, 232 2016/11/02 08:09:07, 26, 83, 636, 250 2016/11/02 08:09:09, 26, 83, 639, 250 2016/11/02 08:09:11, 26, 83, 644, 250 2016/11/02 08:09:13, 26, 83, 644, 250 2016/11/02 08:09:15, 26, 83, 645, 250 2016/11/02 08:09:17, 26, 83, 639, 250 2016/11/02 08:09:19, 26, 83, 638, 250 2016/11/02 08:09:21, 26, 83, 641, 250 2016/11/02 08:09:23, 26, 83, 650, 250 </code></pre></br>]]></content:encoded></item><item><title><![CDATA[The peculiar case of the malfunctioning keyboard]]></title><description><![CDATA[Have you ever thought to yourself ... > Gee, I wish I could focus more on my work and not get distracted by all these Slack and e-mail notifications and done something about it? By any chance did you install heyfocus [https://heyfocus.com/]? Because if you did, there's a good chance that just like Brad, you might have experienced some peculiar happenings to do with your keyboard. -------------------------------------------------------------------------------- Gather round and I'll tell yo]]></description><link>https://jcode.me/the-peculiar-case-of-the-malfunctioning-keyboard/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff70</guid><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Tue, 01 Nov 2016 00:00:00 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2016/11/codekeyboard.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2016/11/codekeyboard.jpg" alt="The peculiar case of the malfunctioning keyboard"/><p>Have you ever thought to yourself ...</p> <blockquote> <p>Gee, I wish I could focus more on my work and not get distracted by all these Slack and e-mail notifications</p> </blockquote> <p>and done something about it? By any chance did you install <a href="https://heyfocus.com/?ref=ghost.jcode.me">heyfocus</a>?</p> <p>Because if you did, there's a good chance that just like Brad, you might have experienced some peculiar happenings to do with your keyboard.</p> <hr> <p>Gather round and I'll tell you a story, a story of keyboards and gremlins, a story that starts many moons ago when young Brad bought himself a brand new <a href="https://codekeyboards.com/?ref=ghost.jcode.me">CODE keyboard</a>, elevating his status from lowly database guy to peerless Database Engineer - among other things - in one simple purchase.</p> <p>But the rise to power was not an easy one, you see, the keyboard was damaged in transit from over the ocean! It required repair. However Brad lacked the tools and ability to fix his keyboard, so he sought the aid of his newly acquired and handsome young friend, Jason, who fixed the problem without having to search Google for the answer.</p> <blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">Yo <a href="https://twitter.com/bradleyfalzon?ref=ghost.jcode.me">@bradleyfalzon</a>, your keyboard is fixed. Can't even tell anything happened to it. (Teensy keylogger not included) <a href="https://t.co/ryrKf16gFz?ref=ghost.jcode.me">pic.twitter.com/ryrKf16gFz</a></p>— Jason Millward (@majorlawnlids) <a href="https://twitter.com/majorlawnlids/status/764791352676618240?ref=ghost.jcode.me">August 14, 2016</a></blockquote> <script async="" src="//platform.twitter.com/widgets.js" charset="utf-8"/> <p>Once the keyboard was fixed, work could begin again with renewed vigour.</p> <p>But it did not last long, like a peaceful summers night in the suburbs only to be interrupted with a child's caterwauling there was a constant loss of focus.</p> <p>Thus, Brad went on a quest to find a shield that repelled distractions. It was a long and perilous quest, filled with many dead ends, tricks, traps and unhelpful reddit threads.</p> <p>Eventually he found solace in a field near the town of <a href="https://heyfocus.com/?ref=ghost.jcode.me">heyfocus</a>. The towns people greeted him as one of their own, guided him to their temple and showed him the way to remove all distraction from his mind.</p> <p>In the things he learned was a scroll of magic, and on the scroll were symbols which when copied and written into the panel of "scripting and focusing" perform the spell known as "Do not disturb".</p> <pre><code>if [[ $(plutil -convert xml1 -o - ~/Library/Preferences/ByHost/com.apple.notificationcenterui.*.plist | grep false) ]]; then osascript <<EOD tell application "System Events" to tell process "SystemUIServer" key down option click menu bar item 1 of menu bar 2 key up option end tell EOD fi </code></pre> <p>Brad performed this spell hourly and was not disturbed for months on end, but like most stories there is one last cruel twist of fate to come.</p> <p>You see, around 4 months later there was an update to the macOS platform known as the <a href="https://en.wikipedia.org/wiki/MacOS_Sierra?ref=ghost.jcode.me">Sierra</a> update. That fateful update broke the spells binding, and caused the mighty CODE keyboard to malfunction in strange ways.</p> <p>Once Brad tried to focus and begin typing, strange things would happen. Keys would no longer work, often leaving him locked out of his computer. Menus would appear randomly as if some unknown hand was holding down the <code>⌥</code> key.</p> <p>These malfunctions bamboozled his co-workers, leaving them perplexed and unable to explain the situation.</p> <p>But one early spring morning, Brad noticed that it was his spell causing all of this confusion and upon further research found the answer to be very simple. One that only required a single digit changed. One that should not have been required but was.</p> <p>Apple had changed their menu bar indexing.</p> <h4 id="elcapitan">El Capitan</h4> <pre><code>click menu bar item 1 of menu bar 2 </code></pre> <h4 id="sierra">Sierra</h4> <pre><code>click menu bar item 1 of menu bar 1 </code></pre> <hr> <p>This brings our story to a close but it does not perhaps, leave things very clear for our readers so I will attempt to explain a little more.</p> <p>In the script Brad was running we need to look at these 3 lines;</p> <pre><code> key down option click menu bar item 1 of menu bar 2 key up option </code></pre> <p>The script starts out by holding down the option key.</p> <p>Then clicks menu bar item 1 of menu bar 2. In simple terms this is normally the do not disturb toggle.</p> <p><img src="https://ghost.jcode.me/content/images/2016/11/Screen-Shot-2016-11-01-at-3.46.38-pm.png" alt="The peculiar case of the malfunctioning keyboard" loading="lazy"/></p> <p>But the recent Sierra update changed the indexing of this button. Which in turn causes the script to break while still holding down the option button as it never triggered the line to release it.</p> <p>There may be more adventures of Brad the Peerless Database Engineer, but for now he works on in silence, using a keyboard that works, being disturbed no more.</p> <!--kg-card-end: markdown--></hr></hr>]]></content:encoded></item><item><title><![CDATA[Adventures in WordPress Vol. 6 - Summary]]></title><description><![CDATA[Here we are, after a few changes to the hardware, software and network it's time to see if all the hard work has paid off. In summary the things I changed where; * Decreased apache mpm servers * Installed fail2ban * Removed 13 plugins * Optimised images using jpegoptim * Converted stock WP to Bedrock for better manageability * Changed MySQL to MariaDB * Updated PHP from 5 to 7 * Reconfigured a WP cache * Added an extra CPU and 1GB of RAM for a total of 2 CPUs and 2GB of RAM * Install]]></description><link>https://jcode.me/adventures-in-wordpress-vol-6-summary/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff6f</guid><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Tue, 04 Oct 2016 19:30:00 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2016/11/wordpress-summary.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2016/11/wordpress-summary.jpg" alt="Adventures in WordPress Vol. 6 - Summary"/><p>Here we are, after a few changes to the hardware, software and network it's time to see if all the hard work has paid off.</p> <p>In summary the things I changed where;</p> <ul> <li>Decreased apache mpm servers</li> <li>Installed fail2ban</li> <li>Removed 13 plugins</li> <li>Optimised images using <code>jpegoptim</code></li> <li>Converted stock WP to Bedrock for better manageability</li> <li>Changed MySQL to MariaDB</li> <li>Updated PHP from 5 to 7</li> <li>Reconfigured a WP cache</li> <li>Added an extra CPU and 1GB of RAM for a total of 2 CPUs and 2GB of RAM</li> <li>Installed Memcached</li> <li>Enabled a CDN</li> </ul> <p>These are the results...</p> <h3 id="before">Before</h3> <p><img src="https://ghost.jcode.me/content/images/2016/09/travelbug_before.png" alt="Adventures in WordPress Vol. 6 - Summary" loading="lazy"><br> <small>Unfortunately I didn't grab one before I started, so I disabled most of the modifications and CDN to get a rough picture.</small></br></img></p> <h3 id="after">After</h3> <p><img src="https://ghost.jcode.me/content/images/2016/09/travelbug_after.png" alt="Adventures in WordPress Vol. 6 - Summary" loading="lazy"/></p> <p>Take a look at that load time! Wowza.</p> <p>From 9.2s to 1.0s, that's a crazy <strong>89.1%</strong> reduction in load time!</p> <p>I managed to keep the mobile user experience at a reasonable 100/100 too.</p> <p><img src="https://ghost.jcode.me/content/images/2016/09/travelbug_ux.png" alt="Adventures in WordPress Vol. 6 - Summary" loading="lazy"/></p> <p>The only thing that's bringing down the score is the unscaled images.</p> <p><img src="https://ghost.jcode.me/content/images/2016/09/scaled_images.png" alt="Adventures in WordPress Vol. 6 - Summary" loading="lazy"/></p> <!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Adventures in WordPress Vol. 5 - CDN]]></title><description><![CDATA[One of the last things to tackle for the moment is setting up and managing a CDN or Content Delivery Network. Before picking a CDN provider I needed to get an estimate on how much traffic the site was using. Luckily there's a console based network traffic monitor for Linux called vnStat. To enable vnStat just requires one command; vnstat -u -i eth0 Checking the estimate using vnstat -d showed that we'd be sending about 300GB this month, yikes. rx | tx | tot]]></description><link>https://jcode.me/adventures-in-wordpress-vol-5-cdn/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff6c</guid><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Sat, 01 Oct 2016 19:30:00 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2016/11/wordpress-cdn.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2016/11/wordpress-cdn.png" alt="Adventures in WordPress Vol. 5 - CDN"/><p>One of the last things to tackle for the moment is setting up and managing a CDN or Content Delivery Network.</p> <p>Before picking a CDN provider I needed to get an estimate on how much traffic the site was using. Luckily there's a console based network traffic monitor for Linux called vnStat.</p> <p>To enable vnStat just requires one command;</p> <pre><code class="language-language-bash">vnstat -u -i eth0 </code></pre> <p>Checking the estimate using <code>vnstat -d</code> showed that we'd be sending about 300GB this month, yikes.</p> <pre><code class="language-language-bash"> rx | tx | total estimated 345.95 MiB | 9.04 GiB | 9.37 GiB </code></pre> <p>But thankfully after a few days of gathering data the estimate became a more reasonable 120GB per month.</p> <pre><code class="language-language-bash">➜ ~ vnstat --months eth0 / monthly month rx | tx | total | avg. rate ------------------------+-------------+-------------+--------------- Jun 16 3.50 GiB | 119.66 GiB | 123.17 GiB | 398.61 kbit/s Jul 16 6.51 GiB | 147.34 GiB | 153.85 GiB | 481.84 kbit/s Aug 16 3.94 GiB | 62.99 GiB | 66.93 GiB | 209.62 kbit/s <- CDN enabled here Sep 16 20.39 GiB | 31.93 GiB | 52.32 GiB | 182.52 kbit/s ------------------------+-------------+-------------+--------------- </code></pre> <p>After doing some searching and consulting with Nicole, we decided to give MaxCDN a try. Their 9$ for 100GB looked to be a reasonable price for the amount of traffic the server was receiving.</p> <p>The entire process from signing up to enabling the CDN was super easy and only required 2 settings to be configured.</p> <p><img src="https://ghost.jcode.me/content/images/2016/09/cdn-setup.png" alt="Adventures in WordPress Vol. 5 - CDN" loading="lazy"/></p> <p>The only step that wasn't required was adding a CNAME DNS entry for <code>cdn.bittenbythetravelbug</code> instead of <code>[long hash].netdna-cdn.com</code>, this keeps things tidy and well presented to end users who snoop around the sites source code.</p> <p><img src="https://ghost.jcode.me/content/images/2016/08/cdn_dns.png" alt="Adventures in WordPress Vol. 5 - CDN" loading="lazy"/></p> <!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Adventures in WordPress Vol. 4 - Hardware and Software]]></title><description><![CDATA[With the foundation (or 'Bedrock') of this upgrade complete it's time to see what some upgrades will do. The plan of attack is to change MySQL to MariaDB, upgrade PHP from 5.6 to 7.0 and upgrade the DO Droplet to something with more power. MySQL -> MariaDB Why MariaDB? For a few reasons, mostly for the features and to support OSS. There are some speed enhancements if the benchmarks are to be believed. Like before, I'm backing up all databases before I change anything. Just in case. $ mysqldu]]></description><link>https://jcode.me/adventures-in-wordpress-vol-4-hardware-and-software/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff6b</guid><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Sat, 17 Sep 2016 02:30:00 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2016/11/wordpress-software.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2016/11/wordpress-software.jpg" alt="Adventures in WordPress Vol. 4 - Hardware and Software"/><p>With the foundation (or 'Bedrock') of this upgrade complete it's time to see what some upgrades will do.</p> <p>The plan of attack is to change MySQL to MariaDB, upgrade PHP from 5.6 to 7.0 and upgrade the DO Droplet to something with more power.</p> <h3 id="mysqlmariadb">MySQL -> MariaDB</h3> <h4 id="whymariadb">Why MariaDB?</h4> <p>For a few reasons, mostly for the features and to support OSS. There are some speed enhancements if the benchmarks are to be believed.</p> <p>Like before, I'm backing up all databases before I change anything. Just in case.</p> <pre><code class="language-bash">$ mysqldump --all-databases -u root -p | bzip2 -c > /root/backups/all-$(date +%Y-%m-%d-%H.%M.%S).sql.bz2 </code></pre> <pre><code class="language-bash">$ apt-get remove --purge mysql-server mysql-client mysql-common $ apt-get autoremove $ apt-get autoclean $ apt-get install mariadb-server </code></pre> <h3 id="php5xphp7">PHP 5.x -> PHP 7</h3> <pre><code class="language-bash">$ add-apt-repository ppa:ondrej/php $ apt-get update $ apt-get install php7.1 php7.1-mysql php7.1-mbstring php7.1-curl php7.1-gd php7.1-intl php7.1-xml php7.1-zip php7.1-fpm $ php -v $ a2dismod php5 $ a2enmod proxy_fcgi setenvif $ a2enconf php7.1-fpm $ service apache2 restart </code></pre> <h4 id="whyphp7">Why PHP7?</h4> <p>This one's easy.</p> <p>Twice the speed. Zoom zoom baby!</p> <p><img src="https://ghost.jcode.me/content/images/2016/07/wp-php7-performance.jpg" alt="Adventures in WordPress Vol. 4 - Hardware and Software" loading="lazy"/></p> <h3 id="resizingthedroplet">Resizing the droplet</h3> <p>Originally Bitten by the Travel bug was hosted on a simple 512MB RAM Digital Ocean droplet and was able to deal with the 40k hits per month.</p> <p><img src="https://ghost.jcode.me/content/images/2016/07/13576391_1038836496207109_1839372557_n.jpg" alt="Adventures in WordPress Vol. 4 - Hardware and Software" loading="lazy"/></p> <p>Once the site started getting even popular (upwards of 100k hits per month) that original 512MB of RAM was not enough, so we picked the 2GB droplet and decided to monitor resource usage over 3 - 4 months and see if the smaller droplets would suffice.</p> <p>Thanks to DO's Flexible Resize feature this can be done with minimal downtime.</p> <!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Adventures in WordPress Vol. 3 - Converting WordPress to Bedrock]]></title><description><![CDATA[ > Bedrock is a modern WordPress stack that gets you started with the best development tools, practices, and project structure. This post will be a little light on descriptions as it's fairly straightforward to move an existing WordPress project into a Bedrock stack. It's mostly moving directories around. Backup the existing database Always a good idea before messing with the database and performing a lot of updates. $ mysqldump [database] -u [user] -p | bzip2 -c > [database]-$(date +%Y-%m-]]></description><link>https://jcode.me/adventures-in-wordpress-vol-3-converting-wordpress-to-bedrock/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff5b</guid><category><![CDATA[bedrock]]></category><category><![CDATA[MySQL]]></category><category><![CDATA[PHP]]></category><category><![CDATA[Wordpress]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Thu, 25 Aug 2016 05:00:00 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2016/11/wordpress-converting.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2016/11/wordpress-converting.jpg" alt="Adventures in WordPress Vol. 3 - Converting WordPress to Bedrock"/><p><img src="https://ghost.jcode.me/content/images/2015/05/bedrock.PNG" alt="Adventures in WordPress Vol. 3 - Converting WordPress to Bedrock" loading="lazy"/></p> <blockquote> <p>Bedrock is a modern WordPress stack that gets you started with the best development tools, practices, and project structure.</p> </blockquote> <p>This post will be a little light on descriptions as it's fairly straightforward to move an existing WordPress project into a Bedrock stack. It's mostly moving directories around.</p> <h3 id="backuptheexistingdatabase">Backup the existing database</h3> <p>Always a good idea before messing with the database and performing a lot of updates.</p> <pre><code class="language-bash">$ mysqldump [database] -u [user] -p | bzip2 -c > [database]-$(date +%Y-%m-%d-%H.%M.%S).sql.bz2 </code></pre> <h3 id="updatethedatabase">Update the database</h3> <p>Replace all instances of wp-content with app</p> <pre><code class="language-sql">> UPDATE `wp_posts` set `guid` = replace(`guid`,'wp-content/uploads/','app/uploads/'); </code></pre> <p>And again for post_content:</p> <pre><code class="language-sql">> UPDATE `wp_posts` set `post_content` = replace(`post_content`,'wp-content/uploads/','app/uploads/'); </code></pre> <h3 id="movingpluginstocomposerjson">Moving plugins to composer.json</h3> <pre><code class="language-json"> "wpackagist-plugin/wp-instagram-widget": "^1.9", "wpackagist-plugin/akismet": "^3.1", "wpackagist-plugin/amazon-link-engine": "^1.2", "wpackagist-plugin/google-analytics-for-wordpress": "^5.5", "wpackagist-plugin/jetpack": "^4.0", "wpackagist-plugin/w3-total-cache": "0.9.4.1", "wpackagist-plugin/wordpress-seo": "^3.3", ... </code></pre> <h3 id="themes">Themes</h3> <pre><code class="language-bash">$ cp -r ./wp-content/themes/[theme name] [bedrock]/web/wp/wp-content/themes </code></pre> <p>And just like that we're now running a Bedrock based wordpress installation.</p> <!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[CDPATH with zsh]]></title><description><![CDATA[Here's some awesome knowledge passed along from @bradleyfalzon [https://twitter.com/bradleyfalzon]. If you add CDPATH to ~/.zshrc you can tab complete into those directories without typing the full path. Very handy if you happen to run several vagrant instances or a verbose directory structure to mimic production. typeset -U path cdpath fpath setopt auto_cd cdpath=($HOME/scripts /mnt) zstyle ':completion:*' group-name '' zstyle ':completion:*:descriptions' format %d zstyle ':completion:*:d]]></description><link>https://jcode.me/cdpath-with-zsh/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff6d</guid><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Mon, 22 Aug 2016 05:32:44 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2018/12/1I260QFQQELTFKA18ELX.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2018/12/1I260QFQQELTFKA18ELX.jpg" alt="CDPATH with zsh"/><p>Here's some awesome knowledge passed along from <a href="https://twitter.com/bradleyfalzon?ref=ghost.jcode.me">@bradleyfalzon</a>.</p> <p>If you add CDPATH to <code>~/.zshrc</code> you can tab complete into those directories without typing the full path.</p> <p>Very handy if you happen to run several vagrant instances or a verbose directory structure to mimic production.</p> <pre><code class="language-language-bash">typeset -U path cdpath fpath setopt auto_cd cdpath=($HOME/scripts /mnt) zstyle ':completion:*' group-name '' zstyle ':completion:*:descriptions' format %d zstyle ':completion:*:descriptions' format %B%d%b zstyle ':completion:*:complete:(cd|pushd):*' tag-order \ 'local-directories named-directories' </code></pre> <p>When tab completing a <code>cd</code> you'll end up with something like this;</p> <p><img src="https://ghost.jcode.me/content/images/2016/08/cdpath.png" alt="CDPATH with zsh" loading="lazy"/></p> <p>The same can be done is bash by adding the following to your <code>~/.bashrc</code>.</p> <pre><code class="language-language-bash">export CDPATH=.:$HOME/scripts:/mnt </code></pre> <!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Adventures in WordPress Vol. 2 - Analysis & Clean up]]></title><description><![CDATA[Nicole of Bitten By the Travel Bug [http://bittenbythetravelbug.com/] has reported some issues with her WordPress install but is unsure as to what is breaking. The only message she sees is; Error establishing a database connection So into the terminal I dive, ssh-ing into the server it's time to probe logs and run diagnostics. This takes a while as I soon find out from htop that 100% CPU being used up by Apache, all RAM and swap is being used too. How is this server even functioning at this p]]></description><link>https://jcode.me/adventures-in-wordpress-vol-2-analysis-clean-up/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff6a</guid><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Sat, 02 Jul 2016 05:08:41 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2016/11/wordpress-cleanup.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://ghost.jcode.me/content/images/2016/11/wordpress-cleanup.jpg" alt="Adventures in WordPress Vol. 2 - Analysis & Clean up"/><p>Nicole of <a href="http://bittenbythetravelbug.com/?ref=ghost.jcode.me">Bitten By the Travel Bug</a> has reported some issues with her WordPress install but is unsure as to what is breaking. The only message she sees is;</p><p>Error establishing a database connection</p><p>So into the terminal I dive, ssh-ing into the server it's time to probe logs and run diagnostics.</p><p>This takes a while as I soon find out from <code>htop</code> that 100% CPU being used up by Apache, all RAM and swap is being used too. How is this server even functioning at this point?</p><h3 id="apache">Apache</h3><p>Got to stop Apache from taking over</p><pre><code class="language-bash">/etc/init.d/apache2 stop</code></pre><p>Once that's done I check out <code>/var/log/error.log</code> for any recurring themes. I find mostly timeouts, a few mysql database connection errors and horribly coded WordPress plugins breaking. Those will be the first to go when the purge comes along.</p><p>Just to be careful, I run <code>last</code> and <code>cat /var/log/auth.log | grep "invalid user" | wc -l</code> to see if there has been anyone logging in. (Only 1302 attempts today, not bad)</p><p>Since this is a small Digital Ocean droplet I'm going to aggressively limit Apache to allow the site to function but not overload the server.</p><pre><code class="language-nginx"><IfModule mpm_prefork_module> StartServers 1 MinSpareServers 2 MaxSpareServers 5 MaxRequestWorkers 50 MaxConnectionsPerChild 1000 </IfModule></code></pre><p>And install fail2ban even though the only enabled method of ssh authentication is keys.</p><pre><code class="language-bash">apt-get install fail2ban </code></pre><h3 id="plugins">Plugins</h3><p>WordPress is known for being extensible via plugins, unfortunately these plugins had no peer review or standards to be held to. I have found many plugins which use <code>file_get_contents</code> on a long expired domain for whatever reason, leading to an extra 30 seconds of load time.</p><p>Plugins can cause trouble, but not all of them are bad. <code>tail</code>-ing error logs to find troublesome plugins makes it easy to identify some, while others need to be disabled through trial and error.</p><p>Originally Bitten By the Travel Bug had 33 <em>active</em> plugins, I was able to bring it down to 20, at least 5 of which I believe should not be plugins and should be part of the theme, but that's a job for another day.</p><h3 id="images">Images</h3><p>Uploading 5mb images and letting WordPress create thumbnails is a good idea in theory, but the WP core doesn't optimise the files for slow connections.</p><p>Here is where <code>jpegoptim</code> saves the day. <a href="https://jcode.me/optimise-jpeg-via-cli/?ref=ghost.jcode.me">Optimising jpeg files through the command line</a> saves hard drive space and gives the end user less to download for an imperceptible loss of quality.</p><p>1 command and the uploads directory has been reduced by over half. And more can be saved by tweaking the output quality.</p><pre><code class="language-bash">$ cd /wp-content/uploads/ $ du -s 754492 . $ find . -regextype sed -regex ".*/*.jp[e]\?g$" -exec jpegoptim --all-progressive -o -m90 --strip-all {} \; $ du -s 361112 . </code></pre>]]></content:encoded></item><item><title><![CDATA[Adventures in WordPress Vol. 1 - An Introduction]]></title><description><![CDATA[A good friend of mine, Nicole, runs a travel blog called Bitten By the Travel Bug [http://bittenbythetravelbug.com/] recently came to me with some WordPress troubles. Her WordPress install that deals with just under half a million hits per month is having a bit of trouble keeping up with the demand. In this series I'll be investigating and documenting my findings, mistakes and fixes in the hopes that future developers - and maybe even future me - will find the information useful even if it only]]></description><link>https://jcode.me/adventures-in-wordpress-vol-1-an-introduction/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff69</guid><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Sat, 02 Jul 2016 04:43:00 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2016/11/wordpress-intro.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2016/11/wordpress-intro.jpg" alt="Adventures in WordPress Vol. 1 - An Introduction"/><p>A good friend of mine, Nicole, runs a travel blog called <a href="http://bittenbythetravelbug.com/?ref=ghost.jcode.me">Bitten By the Travel Bug</a> recently came to me with some WordPress troubles. Her WordPress install that deals with just under half a million hits per month is having a bit of trouble keeping up with the demand.</p> <p>In this series I'll be investigating and documenting my findings, mistakes and fixes in the hopes that future developers - and maybe even future me - will find the information useful even if it only points them in the right direction.</p> <!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Witcher Wi-Fi sensing medallion - Part 5]]></title><description><![CDATA[Circuit cleaning time! Once you are satisfied with how prototype performs it's time to remove all the wires from the ESP8266. With a clean ESP8266; * Connect VCC to CH_PD. * Then connect GPIO15 to GND. * GPIO12 goes to the middle pin (B) on the transistor. * VCC connects to C on the transistor. * E on the transistor goes to positive on the motor. * Ground on the motor goes to ground on the ESP8266. * Positive battery lead to VCC or CH_PD. * Negative battery lead to GND. Battery cab]]></description><link>https://jcode.me/witcher-wi-fi-sensing-medallion-part-5/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff67</guid><category><![CDATA[Arduino]]></category><category><![CDATA[ESP8266]]></category><category><![CDATA[Medallion]]></category><category><![CDATA[Wi-Fi]]></category><category><![CDATA[Witcher]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Tue, 08 Sep 2015 01:30:00 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2018/12/witcher3medallionjpg-652946_1280w.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2018/12/witcher3medallionjpg-652946_1280w.jpg" alt="Witcher Wi-Fi sensing medallion - Part 5"/><p>Circuit cleaning time!</p> <p>Once you are satisfied with how prototype performs it's time to remove all the wires from the ESP8266.</p> <p>With a clean ESP8266;</p> <ul> <li>Connect VCC to CH_PD.</li> <li>Then connect GPIO15 to GND.</li> <li>GPIO12 goes to the middle pin (B) on the transistor.</li> <li>VCC connects to C on the transistor.</li> <li>E on the transistor goes to positive on the motor.</li> <li>Ground on the motor goes to ground on the ESP8266.</li> <li>Positive battery lead to VCC or CH_PD.</li> <li>Negative battery lead to GND.</li> </ul> <p><img src="https://ghost.jcode.me/content/images/2015/09/final-diagram-neat.png" alt="Witcher Wi-Fi sensing medallion - Part 5" loading="lazy"/></p> <p>Battery cables, transistor and motor all attached in a compact package.<br> <img src="https://ghost.jcode.me/content/images/2015/09/IMG_20150905_165942.jpg" alt="Witcher Wi-Fi sensing medallion - Part 5" loading="lazy"/></br></p> <p>To prevent any exposed connections from touching the medallion wrapping everything is heatshrink is a good idea.</p> <p><img src="https://ghost.jcode.me/content/images/2015/09/IMG_20150905_170637.jpg" alt="Witcher Wi-Fi sensing medallion - Part 5" loading="lazy"/></p> <p>And with that it's all done.</p> <p>Mounting the motor and wireless module inside a medallion can be done with hot glue or an epoxy resin. I opted for hot glue so I can remove it and make improvements such as CR2032 battery instead of LiPo or glowing LEDs for eyes.</p> <!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Witcher Wi-Fi sensing medallion - Part 4]]></title><description><![CDATA[Time to start coding! But not to worry, I have done all of that for you. Head over to my GitHub repo [https://github.com/JasonMillward/Witcher-WiFi-Medallion], click on releases [https://github.com/JasonMillward/Witcher-WiFi-Medallion/releases] and download the latest one. From there, unzip and double click on the vibration.ino file to open the Arduino IDE. Hit the tick icon in the top left to compile and check the code, make sure it has no errors. If it does, raise an issue on the GitHub ]]></description><link>https://jcode.me/witcher-wi-fi-sensing-medallion-part-4/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff66</guid><category><![CDATA[Arduino]]></category><category><![CDATA[ESP8266]]></category><category><![CDATA[Medallion]]></category><category><![CDATA[Wi-Fi]]></category><category><![CDATA[Witcher]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Sun, 06 Sep 2015 22:00:00 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2018/12/witcher3medallionjpg-652946_1280w.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2018/12/witcher3medallionjpg-652946_1280w.jpg" alt="Witcher Wi-Fi sensing medallion - Part 4"/><p>Time to start coding!</p> <p><img src="https://ghost.jcode.me/content/images/2015/07/Arduino-IDE---Code-it-up.png" alt="Witcher Wi-Fi sensing medallion - Part 4" loading="lazy"/></p> <p>But not to worry, I have done all of that for you.</p> <p>Head over to <a href="https://github.com/JasonMillward/Witcher-WiFi-Medallion?ref=ghost.jcode.me">my GitHub repo</a>, click on <a href="https://github.com/JasonMillward/Witcher-WiFi-Medallion/releases?ref=ghost.jcode.me">releases</a> and download the latest one.</p> <p>From there, unzip and double click on the <code>vibration.ino</code> file to open the Arduino IDE.</p> <p>Hit the tick icon in the top left to compile and check the code, make sure it has no errors. If it does, raise an issue on the <a href="https://github.com/JasonMillward/Witcher-WiFi-Medallion/issues?ref=ghost.jcode.me">GitHub issues tracker</a> and it will be taken care of.</p> <p>If everything compiled correctly and no errors were encountered, plug in the RS232 adapter and hit upload.</p> <p>Once the upload is complete it should execute the code. Create a hotspot on your phone and be sure to turn off all security to make it 'free', just be sure to turn it off when you're done testing.</p> <p>Open up the console window and watch for output.</p> <pre><code>Setup done Scan start Scan done Open network not found Scan start Scan done Open network not found Scan start Scan done Open network found - vibrating </code></pre> <p>If you see something like this then it's time to produce a <a href="http://jcode.me/witcher-wi-fi-sensing-medallion-part-5/?ref=ghost.jcode.me">cleaner circuit</a>.</p> <!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Witcher Wi-Fi sensing medallion - Part 3]]></title><description><![CDATA[Now its time to wire up the programming circuit. These are the pinouts for the ESP8266-03. * VCC is power. * GND is ground. * CH_PD is connected to power. * URXD goes to TX on the USB to Serial adapter. * UTXD goes to RX on the USB to Serial adapter. * GPIO0 is tied low (to ground) when programming. * GPIO12 goes to the transistor which powers the vibration motor. * GPIO15 always goes to ground. After the code has been flashed a lot of these wires can be removed, simplifying the f]]></description><link>https://jcode.me/witcher-wi-fi-sensing-medallion-part-3/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff64</guid><category><![CDATA[Arduino]]></category><category><![CDATA[ESP8266]]></category><category><![CDATA[Medallion]]></category><category><![CDATA[Wi-Fi]]></category><category><![CDATA[Witcher]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Sun, 06 Sep 2015 21:30:00 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2018/12/witcher3medallionjpg-652946_1280w.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2018/12/witcher3medallionjpg-652946_1280w.jpg" alt="Witcher Wi-Fi sensing medallion - Part 3"/><p>Now its time to wire up the programming circuit.</p> <p>These are the pinouts for the ESP8266-03.</p> <p><img src="https://ghost.jcode.me/content/images/2015/09/esp-03.jpg" alt="Witcher Wi-Fi sensing medallion - Part 3" loading="lazy"/></p> <ul> <li>VCC is power.</li> <li>GND is ground.</li> <li>CH_PD is connected to power.</li> <li>URXD goes to TX on the USB to Serial adapter.</li> <li>UTXD goes to RX on the USB to Serial adapter.</li> <li>GPIO0 is tied low (to ground) when programming.</li> <li>GPIO12 goes to the transistor which powers the vibration motor.</li> <li>GPIO15 always goes to ground.</li> </ul> <p><img src="https://ghost.jcode.me/content/images/2015/09/layout-of-components.png" alt="Witcher Wi-Fi sensing medallion - Part 3" loading="lazy"/></p> <p>After the code has been flashed a lot of these wires can be removed, simplifying the final product.</p> <p>Don't worry if it looks a little complicated, following each wire one at a time makes placing them all down very easy.</p> <p>This is what is needed to program the ESP8266.</p> <p><img src="https://ghost.jcode.me/content/images/2015/09/IMG_20150905_100024-01.jpeg" alt="Witcher Wi-Fi sensing medallion - Part 3" loading="lazy"/></p> <p>When everything is wired up, it's time to <a href="http://jcode.me/witcher-wi-fi-sensing-medallion-part-4/?ref=ghost.jcode.me">flash the code</a>.</p> <!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Witcher Wi-Fi sensing medallion - Part 2]]></title><description><![CDATA[Starting with version 1.6.4, the Arduino IDE allows installation of third-party platform packages using Boards Manager. ESP8266 has packages available for Windows, OSX, and Linux. Install Arduino 1.6.5 from the Arduino website [http://www.arduino.cc/en/main/software]. Start the Arduino IDE and open the preferences window. Enter http://arduino.esp8266.com/package_esp8266com_index.json into Additional Board Manager URLs field. You can add multiple URLs, separated by commas. Open Boards Ma]]></description><link>https://jcode.me/witcher-wi-fi-sensing-medallion-part-2/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff61</guid><category><![CDATA[Arduino]]></category><category><![CDATA[ESP8266]]></category><category><![CDATA[Medallion]]></category><category><![CDATA[Wi-Fi]]></category><category><![CDATA[Witcher]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Sat, 05 Sep 2015 22:30:00 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2018/12/witcher3medallionjpg-652946_1280w.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2018/12/witcher3medallionjpg-652946_1280w.jpg" alt="Witcher Wi-Fi sensing medallion - Part 2"/><p>Starting with version 1.6.4, the Arduino IDE allows installation of third-party platform packages using Boards Manager.</p> <p>ESP8266 has packages available for Windows, OSX, and Linux.</p> <p>Install Arduino 1.6.5 from the <a href="http://www.arduino.cc/en/main/software?ref=ghost.jcode.me">Arduino website</a>.</p> <p><img src="https://ghost.jcode.me/content/images/2015/07/Arduino-IDE.png" alt="Witcher Wi-Fi sensing medallion - Part 2" loading="lazy"/></p> <p>Start the Arduino IDE and open the preferences window.<br> Enter <code>http://arduino.esp8266.com/package_esp8266com_index.json</code> into <em>Additional Board Manager URLs</em> field.</br></p> <p>You can add multiple URLs, separated by commas.</p> <p><img src="https://ghost.jcode.me/content/images/2015/07/Arduino-IDE---Boards-Manager.png" alt="Witcher Wi-Fi sensing medallion - Part 2" loading="lazy"/></p> <p>Open Boards Manager from Tools > Board menu and click install <em>esp8266</em> module.</p> <p><img src="https://ghost.jcode.me/content/images/2015/07/Arduino-IDE---Boards-Manager---Install.png" alt="Witcher Wi-Fi sensing medallion - Part 2" loading="lazy"/></p> <p>Wait for it to complete.</p> <p><img src="https://ghost.jcode.me/content/images/2015/07/Arduino-IDE---Boards-Manager---Installing-progress.png" alt="Witcher Wi-Fi sensing medallion - Part 2" loading="lazy"/></p> <p>Select your ESP8266 board from Tools > Board menu after installation.</p> <p><img src="https://ghost.jcode.me/content/images/2015/07/Arduino-IDE---Select-board.png" alt="Witcher Wi-Fi sensing medallion - Part 2" loading="lazy"/></p> <p><a href="https://jcode.me/witcher-wi-fi-sensing-medallion-part-3/?ref=ghost.jcode.me">Time to assemble the bits on a breadboard</a>!</p> <!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Witcher Wi-Fi sensing medallion - Part 1]]></title><description><![CDATA[ Parts you'll need: * A USB To RS232 adapter [http://www.banggood.com/New-Upgrade-PL2303HX-USB-To-RS232-TTL-Chip-Converter-Adapter-Module-p-85993.html?p=MX242016807672015050] to program the ESP8266. * A ESP8266 Model-03 [http://www.banggood.com/ESP8266-ESP-03-Remote-Serial-Port-WIFI-Transceiver-Wireless-Module-p-961244.html?p=MX242016807672015050] board. This will be the base for the rest of the parts. * A 2N222A Transistor [http://www.banggood.com/20Pcs-Swi]]></description><link>https://jcode.me/witcher-wi-fi-sensing-medallion-part-1/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff62</guid><category><![CDATA[Arduino]]></category><category><![CDATA[ESP8266]]></category><category><![CDATA[Medallion]]></category><category><![CDATA[Wi-Fi]]></category><category><![CDATA[Witcher]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Sat, 05 Sep 2015 08:03:00 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2018/12/witcher3medallionjpg-652946_1280w.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2018/12/witcher3medallionjpg-652946_1280w.jpg" alt="Witcher Wi-Fi sensing medallion - Part 1"/><p><img src="https://ghost.jcode.me/content/images/2015/09/IMG_20150905_093415-01.jpeg" alt="Witcher Wi-Fi sensing medallion - Part 1" loading="lazy"/></p> <p>Parts you'll need:</p> <ul> <li> <p>A <a href="http://www.banggood.com/New-Upgrade-PL2303HX-USB-To-RS232-TTL-Chip-Converter-Adapter-Module-p-85993.html?p=MX242016807672015050&ref=ghost.jcode.me">USB To RS232 adapter</a> to program the ESP8266.</p> </li> <li> <p>A <a href="http://www.banggood.com/ESP8266-ESP-03-Remote-Serial-Port-WIFI-Transceiver-Wireless-Module-p-961244.html?p=MX242016807672015050&ref=ghost.jcode.me">ESP8266 Model-03</a> board. This will be the base for the rest of the parts.</p> </li> <li> <p>A <a href="http://www.banggood.com/20Pcs-Switching-Transistor-MOTON-2N2222A-NPN-40V-0_8A-Component-p-943224.html?p=MX242016807672015050&ref=ghost.jcode.me">2N222A Transistor</a> to drive the motor. The link is for a pack of 20 but you can find these at most electronic parts stores.</p> </li> <li> <p>1 or 2 <a href="http://www.banggood.com/Original-Vibration-Motor-Repair-Part-For-ThL-T100s-p-924434.html?p=MX242016807672015050&ref=ghost.jcode.me">3 volt vibrating motor</a> depending on vibration strength and personal taste.</p> </li> <li> <p>A <a href="http://www.banggood.com/Eachine-H8-Mini-RC-Quadcopter-Spare-Parts-3_7V-150mAh-Battery-H8mini-003-p-977953.html?p=MX242016807672015050&ref=ghost.jcode.me">3.7v LiPo battery</a> and <a href="http://www.banggood.com/Eachine-H8-Mini-RC-Quadcopter-Spare-Parts-USB-Charging-Cable-p-988862.html?p=MX242016807672015050&ref=ghost.jcode.me">LiPo battery charger</a>. Along with some <a href="http://www.banggood.com/RC-helicopter-Parts-Silicone-Battery-Charging-Cable-p-936833.html?p=MX242016807672015050&ref=ghost.jcode.me">battery cables</a> to attach to the board and supply the power.</p> </li> <li> <p>Breadboard or perfboard to flash the software.</p> </li> <li> <p>Some wire (maybe 22AWG) and a soldering iron + solder to join at all together.</p> </li> </ul> <p>Total of all parts should be around $15 AUD or just over $11 USD, not bad for a quick mod.</p> <p>Next we <a href="http://jcode.me/witcher-wi-fi-sensing-medallion-part-2/?ref=ghost.jcode.me">set up the Ardino IDE</a>.</p> <!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Migrate from Google Authenticator to Authy - Android 5+]]></title><description><![CDATA[Google Authenticator hasn't been updated in 2 years and it's starting to show its age. It's an ugly, single device, no backups allowed authenticator with a tiny font. Authy on the other hand is a better looking, multi-device authenticator with a large font. I can also access the tokens for any account from a desktop computer when I flash new ROMs or Developer Previews which require user data to be reset. So I'm making the switch, and here is how you can as well. In order to migrate the 2F]]></description><link>https://jcode.me/migrate-from-google-authenticator-to-authy-android-5/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff63</guid><category><![CDATA[2FA]]></category><category><![CDATA[Android]]></category><category><![CDATA[Authy]]></category><category><![CDATA[Root]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Fri, 24 Jul 2015 10:07:00 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2018/12/VBZC891L8LEMQC8OH1FC.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2018/12/VBZC891L8LEMQC8OH1FC.jpg" alt="Migrate from Google Authenticator to Authy - Android 5+"/><p>Google Authenticator hasn't been updated in 2 years and it's starting to show its age. It's an ugly, single device, no backups allowed authenticator with a tiny font.</p> <p>Authy on the other hand is a better looking, multi-device authenticator with a large font.</p> <p>I can also access the tokens for any account from a desktop computer when I flash new ROMs or Developer Previews which require user data to be reset.</p> <p><img src="https://ghost.jcode.me/content/images/2015/07/authy-desktop.png" alt="Migrate from Google Authenticator to Authy - Android 5+" loading="lazy"/></p> <p>So I'm making the switch, and here is how you can as well.</p> <br> <p>In order to migrate the 2FA tokens over to Authy <em>without invalidating the old tokens</em> you only need an Android phone with root access, and adb installed locally.</p> <p>So plugin your phone and fire up a terminal/command prompt and enter.</p> <pre><code>adb shell su sqlite3 /data/data/com.google.android.apps.authenticator2/databases/databases </code></pre> <p>If you are running Android 5.0 or greater you'll most likely see this error message:</p> <blockquote> <p>Error: only position independent executables (PIE) are supported.</p> </blockquote> <p>The easiest way around this is to download an sqlite binary that's been compiled for Lollipop. I got mine from <a href="http://forum.xda-developers.com/showpost.php?p=57143465&postcount=15&ref=ghost.jcode.me">XDA</a>.</p> <p>Download and copy it to your downloads folder, then head back to the terminal/command prompt and copy it over.</p> <pre><code>su mount -o remount,rw /system cp /sdcard/Download/sqlite /system/xbin mount -o remount,ro /system </code></pre> <p>Now we're ready to get our data from the sqlite database.</p> <pre><code>sqlite3 /data/data/com.google.android.apps.authenticator2/databases/databases </code></pre> <p>The shell prompt will change from <code>#</code> to <code>sqlite></code></p> <p>Enter in the select query</p> <pre><code>select * from accounts; </code></pre> <p>And you should get something similar to</p> <p><code>1|gmail.com|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|0|0|0|Google|Google</code></p> <p><code>2|github.com|xxxxxxxxxxxxxxxx|0|0|0|GitHub|GitHub</code></p> <p><code>3|LastPass|xxxxxxxxxxxxxxxx|0|0|0|LastPass|LastPass</code></p> <br> <p>Now head over to <a href="http://goqr.me/?ref=ghost.jcode.me#t=url">http://goqr.me/#t=url</a> and generate the a QR code for each account by using following URL pattern:</p> <p><code>otpauth://totp/screenconnect?secret=XXXXXXXXXXXXXXXX</code></p> <p>From here just scan each QR in to Authy, and select the relevant logo.</p> <p><img src="https://ghost.jcode.me/content/images/2015/07/authy-N5.png" alt="Migrate from Google Authenticator to Authy - Android 5+" loading="lazy"/></p> <!--kg-card-end: markdown--></br></br>]]></content:encoded></item><item><title><![CDATA[SME]]></title><description><![CDATA[> Subject matter expert = he who touched it last]]></description><link>https://jcode.me/sme/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff60</guid><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Mon, 06 Jul 2015 00:13:07 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2018/12/RT0HA2RDG17T1FB7DT5C.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><blockquote> <img src="https://ghost.jcode.me/content/images/2018/12/RT0HA2RDG17T1FB7DT5C.jpg" alt="SME"/><p>Subject matter expert = he who touched it last</p> </blockquote> <!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Australian lyrics as a word cloud]]></title><description><![CDATA[A word cloud made from the lyrics of songs played on Australian radio from the last year.]]></description><link>https://jcode.me/australian-lyrics-as-a-word-cloud/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff5f</guid><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Wed, 24 Jun 2015 21:47:00 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2018/12/BS5MO4H2M6UGSA2LLL8G.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2018/12/BS5MO4H2M6UGSA2LLL8G.jpg" alt="Australian lyrics as a word cloud"/><p>A word cloud made from the lyrics of songs played on Australian radio from the last year.</p> <p><img src="https://ghost.jcode.me/content/images/2015/06/australian-lyrics-wordcloud.png" alt="Australian lyrics as a word cloud" loading="lazy"/></p> <!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Gombert: A modular Python readability API]]></title><description><![CDATA[ Gombert is an extensible and modular Python readability API which runs any text sent to it through popular readability metrics [https://en.wikipedia.org/wiki/Readability], some lesser known metrics and any custom made formula modules. It was created in order to help analyse song lyrics gathered by lyrer [https://ghost.jcode.me/lyrer-finder-of-lyrics] because I needed an API capable of handling a large volume of rapid requests. Gombert is written in Python, using flask as a base to handle dat]]></description><link>https://jcode.me/gombert-a-modular-python-readability-api/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff5d</guid><category><![CDATA[Adelaide radio]]></category><category><![CDATA[API]]></category><category><![CDATA[Australian radio]]></category><category><![CDATA[Gombert]]></category><category><![CDATA[Lyrer]]></category><category><![CDATA[lyrics]]></category><category><![CDATA[Python]]></category><category><![CDATA[Radio]]></category><category><![CDATA[readability]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Sun, 07 Jun 2015 13:11:00 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2018/12/26YB2DMB71IY57J8GOBY.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2018/12/26YB2DMB71IY57J8GOBY.jpg" alt="Gombert: A modular Python readability API"/><p><img src="https://ghost.jcode.me/content/images/2015/06/gombert.png" alt="Gombert: A modular Python readability API" loading="lazy"/></p> <p>Gombert is an extensible and modular Python readability API which runs any text sent to it through popular <a href="https://en.wikipedia.org/wiki/Readability?ref=ghost.jcode.me">readability metrics</a>, some lesser known metrics and any custom made formula modules.</p> <p>It was created in order to help analyse song lyrics gathered by <a href="https://ghost.jcode.me/lyrer-finder-of-lyrics">lyrer</a> because I needed an API capable of handling a large volume of rapid requests.</p> <p>Gombert is written in Python, using flask as a base to handle data and dynamic routes.</p> <p>Source is available on <a href="https://github.com/Zeppelin-and-Pails/Gombert?ref=ghost.jcode.me">Github</a>.</p> <p>Gombert was created with the help of <a href="http://kamikkels.net/?ref=ghost.jcode.me">Kerry MR</a></p> <!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Lyrer: Finder of lyrics]]></title><description><![CDATA[ As a continuation of my analysis into FM radio repetition [https://ghost.jcode.me/analysis-into-radio-repetition/] I've wanted to have a look at song lyrics and analyse them, compare and rank them against each other and possibly group them by radio station, but that's all for another blog post. In my search for an easy to use lyric API I found out that many lyric databases don't like to give away access to their APIs to just anyone. Most require you to become an approved partner, which requir]]></description><link>https://jcode.me/lyrer-finder-of-lyrics/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff5e</guid><category><![CDATA[Adelaide]]></category><category><![CDATA[Adelaide radio]]></category><category><![CDATA[API]]></category><category><![CDATA[Gombert]]></category><category><![CDATA[Lyrer]]></category><category><![CDATA[lyrics]]></category><category><![CDATA[Python]]></category><category><![CDATA[Radio]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Sat, 06 Jun 2015 11:48:47 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2018/12/X1CQXFEBWZCEQ4MAZTRF.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2018/12/X1CQXFEBWZCEQ4MAZTRF.jpg" alt="Lyrer: Finder of lyrics"/><p><img src="https://ghost.jcode.me/content/images/2015/06/lyrer.png" alt="Lyrer: Finder of lyrics" loading="lazy"/></p> <p>As a continuation of my <a href="https://ghost.jcode.me/analysis-into-radio-repetition/">analysis into FM radio repetition</a> I've wanted to have a look at song lyrics and analyse them, compare and rank them against each other and possibly group them by radio station, but that's all for another blog post.</p> <p>In my search for an easy to use lyric API I found out that many lyric databases don't like to give away access to their APIs to just anyone. Most require you to become an approved partner, which requires a significant down payment before you can use them, and others work sporadically or not at all.</p> <blockquote> <p>Welcome to the [redacted] API. These APIs are for approved partners only.</p> </blockquote> <p>So I joined forces with <a href="http://kamikkels.net/?ref=ghost.jcode.me">Kerry MR</a> to set out and make an easy to use API that can gather lyrics from multiple sources and switch between them on the fly.</p> <p>Thus Lyrer was created.</p> <p>Lyrer currently has 2 main methods of obtaining lyrics; other APIs and web page scraping.</p> <p>Lyrer is currently under active development and may change the method(s) it uses to gather lyrics but source is still available on <a href="https://github.com/Zeppelin-and-Pails/Lyrer?ref=ghost.jcode.me">Github</a>.</p> <!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[How much does a bee weigh?]]></title><description><![CDATA[According to COLOSS (Prevention of honey bee COlony LOSSes) [http://www.coloss.org/beebook/I/misc-methods/2/2]: > A single bee weighs roughly 277 - 290 mg And with that it's time to educate the general populous of reddit! Introducing: KG to Bee KG2Bee as it is more commonly known is an extremely tiny bot that tirelessly trawls through reddit comments in search for any comments containing an amount and a unit of measurement. Why? Seeing imperial measurements on reddit and having no idea what]]></description><link>https://jcode.me/how-much-does-a-bee-weigh/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff57</guid><category><![CDATA[automation]]></category><category><![CDATA[Bees]]></category><category><![CDATA[bots]]></category><category><![CDATA[Python]]></category><category><![CDATA[reddit]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Sun, 15 Mar 2015 09:39:07 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2018/12/3IO2JDK7ERJ56NN2QHDT.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2018/12/3IO2JDK7ERJ56NN2QHDT.jpg" alt="How much does a bee weigh?"/><p>According to <a href="http://www.coloss.org/beebook/I/misc-methods/2/2?ref=ghost.jcode.me">COLOSS (Prevention of honey bee COlony LOSSes)</a>:</p> <blockquote> <p>A single bee weighs roughly 277 - 290 mg</p> </blockquote> <p>And with that it's time to educate the general populous of reddit!</p> <h3 id="introducingkgtobee">Introducing: KG to Bee</h3> <p>KG2Bee as it is more commonly known is an extremely tiny bot that tirelessly trawls through reddit comments in search for any comments containing an amount and a unit of measurement.</p> <h3 id="why">Why?</h3> <p>Seeing imperial measurements on reddit and having no idea what they mean is bad (being Australian and all), so what if there was a bot that tirelessly trawled reddit, converting all the imperial units to metric ones?</p> <p>Too bad this idea already exists in the form of <a href="http://reddit.com/u/MetricConversionBot?ref=ghost.jcode.me">/u/MetricConversionBot</a>.</p> <p>But then, as our discussions about the imperial system normally do, we started talking about how ridiculous the imperial system really is - seriously, <a href="http://en.wikipedia.org/wiki/Conversion_of_units?ref=ghost.jcode.me#Length">look at all of these units</a>.</p> <p>This obviously led onto the most ridiculous of measurements: <strong>bees</strong>.</p> <p>And so a bot was created, which served two purposes;</p> <ul> <li>A conversion from any unit of mass to kilograms</li> <li>Converting that unit of kilograms to the amount of bees (the more useful part)</li> </ul> <p>After a week of running the bot it ended up with 5200+ comment karma, reddit gold, banned from over 50 subreddits and one fan account ( <a href="http://reddit.com/u/bee2kg?ref=ghost.jcode.me">/u/bee2kg</a> ).</p> <p><a href="http://www.reddit.com/r/funny/comments/2wulr8/his_face_says_it_all/coudbql?context=3&ref=ghost.jcode.me"><img src="https://ghost.jcode.me/content/images/2015/03/bee_gold.PNG" alt="How much does a bee weigh?" loading="lazy"/></a></p> <h3 id="how">How?</h3> <p>The <a href="https://github.com/Zeppelin-and-Pails/kg2bee?ref=ghost.jcode.me">bot itself</a> is a relatively simple affair.</p> <p>It uses PRAW to get a stream of comments starting with the newest ones. Trawls through them, looking for something that might be a unit of mass. When it finds it, it tries to stuff it into a units module, convert it a few times and post the converted measurements as a reply.</p> <p><small>Built in collaboration with <a href="http://kamikkels.net/?ref=ghost.jcode.me">Kerry MR</a></small>.</p> <!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Autorippr update]]></title><description><![CDATA[Prevously known as "MakeMKV Auto ripper", Autorippr is an automated DVD ripper. It combines several well known applications into one automated bundle that rips content from discs onto your hard drive, compresses the video content, looks up subtitles and finally renames if required. Built using Python, Autorippr is able to be run on almost all linux distributions and OSX. It is able to search multiple disc drives, check video length, eject discs after completion and run without user intervention]]></description><link>https://jcode.me/autorippr-update/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff59</guid><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Sat, 14 Mar 2015 03:38:21 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2019/1/2DSR8S8OY9IYJL3VYMZP.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2019/1/2DSR8S8OY9IYJL3VYMZP.jpg" alt="Autorippr update"/><p>Prevously known as "MakeMKV Auto ripper", Autorippr is an automated DVD ripper. It combines several well known applications into one automated bundle that rips content from discs onto your hard drive, compresses the video content, looks up subtitles and finally renames if required.</p> <p>Built using Python, Autorippr is able to be run on almost all linux distributions and OSX. It is able to search multiple disc drives, check video length, eject discs after completion and run without user intervention.</p> <p>Since the original post in October 2012 there's been a huge change code wise with a few more features including FFmpeg support, completion commands and TV show support (in testing).</p> <p>Autorippr is open source and welcomes any sensible additions. It is available on <a href="https://github.com/JasonMillward/autorippr?ref=ghost.jcode.me">GitHub</a>.</p> <!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Making the open Wi-Fi map]]></title><description><![CDATA[Like most of my projects this one started with a question: how much of my city was covered by open Wi-Fi signals and how strong those signals are. What I wanted to create was something close to a phone signal coverage map but for open Wi-Fi.]]></description><link>https://jcode.me/making-the-open-wifi-map/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff53</guid><category><![CDATA[Adelaide]]></category><category><![CDATA[Geolocation]]></category><category><![CDATA[Geospacial]]></category><category><![CDATA[GIS]]></category><category><![CDATA[leaflet]]></category><category><![CDATA[Maps]]></category><category><![CDATA[QGIS]]></category><category><![CDATA[Signal strength]]></category><category><![CDATA[Wi-Fi]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Mon, 23 Feb 2015 00:12:16 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2018/12/HWXHSHE8FTGTDKCXEAUO.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2018/12/HWXHSHE8FTGTDKCXEAUO.jpg" alt="Making the open Wi-Fi map"/><p>If you haven't seen the map, go and check it out: <a href="http://lab.jcode.me/openWiFiMap/?ref=ghost.jcode.me"> lab.jcode.me/openWiFiMap/</a></p> <hr> <h3 id="introduction">Introduction</h3> <p>Like most of my projects this one started with a question: how much of my city was covered by open Wi-Fi signals and how strong those signals are.</p> <p>What I wanted to create was something close to a phone signal coverage map but for open Wi-Fi.</p> <p>I would need to:</p> <ul> <li>Gather data</li> <li>Filter that data</li> <li>Visualise the data using some form of map</li> <li>Make it interactive</li> </ul> <h3 id="thefirststep">The first step</h3> <p>To answer my own question I needed to gather data, hopefully without lugging around a laptop or other heavy hardware.</p> <p>I've done wardriving in the past, using a laptop running Linux and a wireless USB adapter, however this seemed overkill and would likely start to draw too much attention.</p> <p>After some quick testing I chose to gather my data using a spare Android phone I keep around for tinkering. I designed the Android app to be very minimal in both the user interface and the code behind it.</p> <h3 id="theapp">The app</h3> <p><img src="https://ghost.jcode.me/content/images/2015/02/wifiscanner.png" alt="Making the open Wi-Fi map" loading="lazy"/></p> <p>On the surface the app has two buttons; "Close WiFi scanner" and "Upload data". Behind the scenes it's only a little cooler. Upon opening the app it starts a background service that hooks into the phones location services - this covers GPS and network location methods - and listens for updates or movements. After testing different movement configurations I decided to use a minimum of 10 meters or 5 seconds since the last update.</p> <p>Upon 'hearing' a change in location the background service then triggers a Wi-Fi scan, which returns the most important part, names and signal strength. It then stores the following data for later filtering;</p> <ul> <li>BSSID - Access point MAC address, used for unique identification purposes</li> <li>SSID - Access point name</li> <li>RSSI - Access point signal strength</li> <li>Security - Access point encryption methods, for filtering open connections</li> </ul> <p>This all runs in the background until the "Upload data" button is pressed. Once pressed it compresses the collected information and uploads it to one of my servers which checks the integrity of the information then stores and filters the raw information into a SpatiaLite database ready for QGIS to interpret.</p> <p><strong>NB:</strong> I didn't start with QGIS, my first test used Google Maps and heatmap.js to create an overlay of the signal strength map, but I quickly ran into issues like disappearing points, heatmap scaling errors and slow maps with far too many points.</p> <h3 id="quantumgeographicinformationsystemqgis">Quantum Geographic Information System (QGIS)</h3> <p>Geographic Information Systems or GIS is something I've <em>never</em> used before, so this is where I had most of my fun, where I started yelling incoherently, where I did most of my learning and finally got the result I wanted from the start.</p> <p>Luckily for me QGIS can read SpatiaLite files, so importing the first round of data was as simple as opening the file and selecting the correct CRS (WGS 84 for this project, GDA94 did not play nice with other layers) and watching the points fill up the screen.</p> <p>By this time I had given the Android app to some awesome friends who helped me gather data, and I was amassing enough to start the visualisation process.</p> <p>The first import was quite impressive but single colour data points look so boring, so I threw on a graduated colour style using signal strength as the index.</p> <p><img src="https://ghost.jcode.me/content/images/2015/02/firstImport.jpg" alt="Making the open Wi-Fi map" loading="lazy"/></p> <p><img src="https://ghost.jcode.me/content/images/2015/02/colour-points.jpg" alt="Making the open Wi-Fi map" loading="lazy"/></p> <p>With data being accurately plotted my next goal was create a hexbin map.</p> <p>Creating a hexbin map inside QGIS requires only 1 plugin; MMQGIS.</p> <p>MMQGIS allows you to create a grid polygon layer of any size over a selected area, the larger the area and the smaller the hexagons the longer it takes to generate a polygon layer.</p> <p><img src="https://ghost.jcode.me/content/images/2015/02/hexpolygon-layer.JPG" alt="Making the open Wi-Fi map" loading="lazy"/></p> <p>After the hexagonal layer was drawn I used a "Points in Polygon" method and had success, but the amount of points in a polygon isn't indicative of Wi-Fi signal strength.</p> <p><img src="https://ghost.jcode.me/content/images/2015/02/hexbin-pip.jpg" alt="Making the open Wi-Fi map" loading="lazy"/></p> <p>One suggested solution was to use "Zonal Statistics". This did not work out the way it should have...the blasted map it made was upside-down!</p> <p><img src="https://ghost.jcode.me/content/images/2015/02/upsidedown_heatmap.jpg" alt="Making the open Wi-Fi map" loading="lazy"/></p> <p>Just before I was going to give up on hexbins and settle for a generic heatmap I stumbled upon "Join attributes by location".</p> <p>This looked promising. Using the point layer with the polygon vector layer I took a mean and median summary of intersecting attributes, discarding all empty polygons and stored it in a new layer.</p> <p>Applying the same graduated colour style as before created the map I wanted. I was almost done.</p> <p><img src="https://ghost.jcode.me/content/images/2015/02/near-final-result-1.JPG" alt="Making the open Wi-Fi map" loading="lazy"/></p> <h3 id="creatingtiles">Creating tiles</h3> <p>QGIS maps are not particularly portable, in order to read <code>.shp</code> layers dedicated programs must be installed. These programs are hundreds of megabytes in size, and while it's not a worry for some, downloading large applications just to view a map isn't worth it. So I sought other methods of sharing my map.</p> <p><a href="http://leafletjs.com/?ref=ghost.jcode.me">Leaflet</a>, a modern open-source JavaScript library for mobile-friendly interactive maps would allow me to create my interactive map using custom tile layers.</p> <p>There are many methods of creating tile layers; QGIS Server, TileCache and MAPNIK allow users to create and server their own maps, <a href="https://www.mapbox.com/?ref=ghost.jcode.me">Mapbox</a> even handles <code>.shp</code> files and makes a whole map ready to show off with very little effort, but I wanted to do it and host it myself.</p> <p>Using another plugin (QTiles this time) I was able to create my own tiles.</p> <p>QTiles offers some customisation with the tiles it outputs, PNG or JPG, opaque or transparent and z-levels.</p> <p>Everything worked well apart from transparency for an unknown reason, so I had to settle with tiles that had a white background where no hexagons were present.</p> <p>I fixed this using ImageMagick to convert all white pixels to transparent ones using the following script:</p> <pre><code>#!/bin/bash find . -type f -iname "*.png" -print0 | while IFS= read -r -d $'\0' file; do isWhite=`convert $file -colorspace HSL -channel g -separate +channel -format "%[fx:mean]" info:` if [ "$isWhite" == "0" ] then rm $file else convert $file -bordercolor white -border 1x1 \ -alpha set -channel RGBA -fuzz 10% \ -fill none -floodfill +0+0 white \ -shave 1x1 $file convert $file -channel rgba -alpha set -fuzz 10% -fill none -opaque white $file fi done </code></pre> <p>It also deleted completely blank files saving me a lot of hard drive space.</p> <pre><code>Original size: 505 MB File count: 126,388 After optimisation: 19 MB File count: 2,780 </code></pre> <p>With tiles created & optimised and leaflet layer options configured the map was complete, ready to be shared publicly.</p> <!--kg-card-end: markdown--></hr>]]></content:encoded></item><item><title><![CDATA[Thunderbolt and lightning]]></title><description><![CDATA[Over 80,000 [http://www.newscientist.com/article/dn26459-australian-skies-lit-up-by-80000-lightning-strikes.html] lightning strikes were recorded in South Australia during the biggest thunderstorm this year. While the storm raged, creating fires and causing power outages I stood out in the wind and rain to capture my new favourite photo.]]></description><link>https://jcode.me/thunderbolt-and-lightning/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff52</guid><category><![CDATA[Lightning]]></category><category><![CDATA[Photography]]></category><category><![CDATA[Thunderstorm]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Tue, 25 Nov 2014 00:30:09 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2018/12/15016435963_529b48411f_k.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2018/12/15016435963_529b48411f_k.jpg" alt="Thunderbolt and lightning"/><p><a href="http://www.newscientist.com/article/dn26459-australian-skies-lit-up-by-80000-lightning-strikes.html?ref=ghost.jcode.me">Over 80,000</a> lightning strikes were recorded in South Australia during the biggest thunderstorm this year.</p> <p>While the storm raged, creating fires and causing power outages I stood out in the wind and rain to capture my new favourite photo.</p> <!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Radio station comparison]]></title><description><![CDATA[The latest addition to the radio statics suite is colour coordinated comparisons [http://lab.jcode.me/radioStatistics/compare/]. Select any two tracked radio stations and within seconds glorious radio station statistics will appear.]]></description><link>https://jcode.me/radio-station-comparison/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff50</guid><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Sat, 27 Sep 2014 02:42:32 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2018/12/0P07W8LYTU7812YDHI9N.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2018/12/0P07W8LYTU7812YDHI9N.jpg" alt="Radio station comparison"/><p>The latest addition to the radio statics suite is <a href="http://lab.jcode.me/radioStatistics/compare/?ref=ghost.jcode.me">colour coordinated comparisons</a>.</p> <p>Select any two tracked radio stations and within seconds glorious radio station statistics will appear.</p> <p><img src="https://ghost.jcode.me/content/images/2014/Sep/radiostats-compare.jpg" alt="Radio station comparison" loading="lazy"/></p> <!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Radio station reactions]]></title><link>https://jcode.me/reactions/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff51</guid><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Sun, 14 Sep 2014 09:30:00 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2018/12/PQ4G82BZVA99G1URWI0B.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2018/12/PQ4G82BZVA99G1URWI0B.jpg" alt="Radio station reactions"/><p><img src="https://ghost.jcode.me/content/images/2014/Sep/musicNewsTweet.jpg" alt="Radio station reactions" loading="lazy"/></p> <!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Analysis into FM radio repetition]]></title><description><![CDATA[Originally, this project started out with me, stuck in the car listening to the same music to and from work every day. I got sick of it and wanted to find out just how much music was being over played so I designed some scripts to gather data from each radio station in South Australia. It started out with a single Python script pulling data from each stations' online media player (which shows the current song playing) and storing it in a database. Using some simple database rules it was easy to]]></description><link>https://jcode.me/analysis-into-radio-repetition/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff4d</guid><category><![CDATA[Adelaide radio]]></category><category><![CDATA[Australian radio]]></category><category><![CDATA[Levenshtein]]></category><category><![CDATA[Maths]]></category><category><![CDATA[Python]]></category><category><![CDATA[Radio]]></category><category><![CDATA[Statistics]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Sat, 13 Sep 2014 09:55:27 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2018/12/FNO0VL2585O54BUOUAUV.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2018/12/FNO0VL2585O54BUOUAUV.jpg" alt="Analysis into FM radio repetition"/><p>Originally, this project started out with me, stuck in the car listening to the same music to and from work every day. I got sick of it and wanted to find out just how much music was being over played so I designed some scripts to gather data from each radio station in South Australia.</p> <p>It started out with a single Python script pulling data from each stations' online media player (which shows the current song playing) and storing it in a database. Using some simple database rules it was easy to prevent duplicate entries, resulting in reasonably accurate data.</p> <p>On Thursday 14th of August I posted a link to my findings on various social media websites and on Friday 15th of August <a href="http://adelaidenow.com.au/news/south-australia/one-man-finally-proves-just-how-few-songs-adelaide-commercial-radio-stations-actually-play/story-fni6uo1m-1227029105104?ref=ghost.jcode.me">The Advertiser</a> and <a href="http://www.tonedeaf.com.au/416669/the-truth-about-how-repetitive-aussie-radio-station-playlists-actually-are.htm?ref=ghost.jcode.me">Tone Deaf</a> published my findings.</p> <p><img src="https://ghost.jcode.me/content/images/2014/Sep/2014-08-19.jpg" alt="Analysis into FM radio repetition" loading="lazy"/></p> <p>The feedback was astounding. Depressingly almost no-one was surprised by the results of the monitoring, as even listening to most radio stations infrequently it's easy to tell repetition is constant.</p> <p>Within 2 days of being posted on social media and published on news websites, Nova 91.9 reduced the information it provided publicly to the point where Nova no longer updated its 'now playing' feed.</p> <p><img src="https://ghost.jcode.me/content/images/2014/Aug/nova-postpublishing.PNG" alt="Analysis into FM radio repetition" loading="lazy"/></p> <p>I do not know if this was a mistake, a technical issue or perhaps a deliberate choice made by the team at Nova, but the feed came back online after 6 days, or 2 hours after a quick tweet.</p> <p><img src="https://ghost.jcode.me/content/images/2014/Sep/novatwitter.PNG" alt="Analysis into FM radio repetition" loading="lazy"/></p> <p>After releasing the initial findings to the public there was a large amount of interest in tracking more stations, and a huge response from people in other states to start tracking their radio stations.</p> <p>However my simple database structure was not up to the task of tracking all these new stations, it needed reworking to store more fields like <code>state</code> and <code>callsign</code> and multiple tables to keep everything clean.</p> <p>With the suggestions from people all over Australia I've increased the amount of stations monitored from 6 to 32.</p> <p>There was a complication however; most radio stations don't sanitise their output causing (among other things) incorrect play counts and duplicate artists.</p> <p><img src="https://ghost.jcode.me/content/images/2014/Sep/wrong-artist-and-tracktitle-1.PNG" alt="Analysis into FM radio repetition" loading="lazy"/></p> <p>In order to solve this I used the <a href="https://musicbrainz.org/doc/Development/JSON_Web_Service?ref=ghost.jcode.me">MusicBrainz JSON Web Service</a> and checked every unique occurrence of every track and artist combo.</p> <p>Using Levenshtein distance I then calculated how closely the original data was to what Spotify suggested. If the result was close enough I used the new data, and in the rare cases where it did not match anything I used my own sanitisation and look-up methods to keep data identical between the radio stations.</p> <p>Currently the <a href="http://lab.jcode.me/radioStatistics/?ref=ghost.jcode.me">radio statistics</a> pages show data from the last 30 days in order to keep things fresh.</p> <p><strong>September 10 update:</strong> Nova 91.9 has once again stop updating their "On Air" feed. All other stations (Nova interstate included) are still updating normally.</p> <p><strong>September 22 update:</strong> Nova 91.9 has started updating their "On Air" feed again.</p> <p><img src="https://ghost.jcode.me/content/images/2014/Sep/mostplayed-1.PNG" alt="Analysis into FM radio repetition" loading="lazy"/></p> <!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Royal Adelaide Hospital time lapse]]></title><description><![CDATA[For 858 days (2 years and 4 months) I've been following the construction of the new Royal Adelaide Hospital via webcam. Over that period I've downloaded 1 image every 15 minutes, 24 hours a day for a total of 80,724 images. On the 2nd of May 2014 the camera - which is operated by a 3rd party - has stopped working due to unknown reasons. Enjoy the following video because it may be the last one I can upload, which is a shame since I never got to complete it. 19th June edit: The webcam has been ]]></description><link>https://jcode.me/royal-adelaide-hospital-timelapse/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff4c</guid><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Fri, 23 May 2014 08:04:48 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2018/12/97O20BQ5C9V385SWYBXA.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2018/12/97O20BQ5C9V385SWYBXA.jpg" alt="Royal Adelaide Hospital time lapse"/><p>For 858 days (2 years and 4 months) I've been following the construction of the new Royal Adelaide Hospital via webcam.<br> Over that period I've downloaded 1 image every 15 minutes, 24 hours a day for a total of 80,724 images.</br></p> <p>On the 2nd of May 2014 the camera - which is operated by a 3rd party - has stopped working due to unknown reasons.</p> <p>Enjoy the following video because it may be the last one I can upload, which is a shame since I never got to complete it.</p> <p><strong>19th June edit:</strong> The webcam has been fixed! I shall be continuing my timelapse until completion.</p> <iframe width="960" height="720" src="//www.youtube.com/embed/0IRvUE6Cwq8" frameborder="0" allowfullscreen=""/> <!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Hexacopter fails]]></title><description><![CDATA[During my time testing the hexacopter from Flexbot [http://jcode.me/flexbot-multicopter/] I failed to keep control of the copter due to strong winds, gravitational fluxes, signal failures and software glitches. My 2 camera people were injured severely, losing limbs and an eye, but that doesn't matter since the hexacopter survived all attempts to destroy it.]]></description><link>https://jcode.me/hexacopter-fails/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff4b</guid><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Tue, 06 May 2014 01:18:39 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2018/12/2FMKVG6MA1KXYR7PWDGZ.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2018/12/2FMKVG6MA1KXYR7PWDGZ.jpg" alt="Hexacopter fails"/><p>During my time testing the <a href="http://jcode.me/flexbot-multicopter/?ref=ghost.jcode.me">hexacopter from Flexbot</a> I failed to keep control of the copter due to strong winds, gravitational fluxes, signal failures and software glitches.</p> <p>My 2 camera people were injured severely, losing limbs and an eye, but that doesn't matter since the hexacopter survived all attempts to destroy it.</p> <iframe width="853" height="480" src="//www.youtube.com/embed/tUjJDSOx7sM" frameborder="0" allowfullscreen=""/><!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Flexbot multicopter]]></title><description><![CDATA[Today I received my Hex Combo Set from the Hex copter [https://www.kickstarter.com/projects/1387330585/hex-a-copter-that-anyone-can-fly] Kickstarter project. Upon opening the package I was greeted by 2 very well presented multi-copters, and in this post I'll be opening and constructing the hexacopter, a multicopter with 6 motors. Assembly The following was inside the box: * 1x Instruction manual * 1x Hexacopter * 2x 3.7v 500mah batteries * 6x motor & propeller combos * 2x spare moto]]></description><link>https://jcode.me/flexbot-multicopter/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff48</guid><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Sat, 03 May 2014 01:00:00 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2018/12/R7JVS9E50RB0VZEU0NUR.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2018/12/R7JVS9E50RB0VZEU0NUR.jpg" alt="Flexbot multicopter"/><p>Today I received my Hex Combo Set from the <a href="https://www.kickstarter.com/projects/1387330585/hex-a-copter-that-anyone-can-fly?ref=ghost.jcode.me">Hex copter</a> Kickstarter project.</p> <p>Upon opening the package I was greeted by 2 very well presented multi-copters, and in this post I'll be opening and constructing the hexacopter, a multicopter with 6 motors.</p> <br> <h3 id="assembly">Assembly</h3> <p><img src="https://ghost.jcode.me/content/images/2014/May/IMG_5757.jpg" alt="Flexbot multicopter" loading="lazy"><br> <img src="https://ghost.jcode.me/content/images/2014/May/IMG_5758.jpg" alt="Flexbot multicopter" loading="lazy"/></br></img></p> <p>The following was inside the box:</p> <ul> <li>1x Instruction manual</li> <li>1x Hexacopter</li> <li>2x 3.7v 500mah batteries</li> <li>6x motor & propeller combos</li> <li>2x spare motors and propellers</li> <li>and 1 Flexbot story and thank you card</li> </ul> <p><img src="https://ghost.jcode.me/content/images/2014/May/IMG_5759.jpg" alt="Flexbot multicopter" loading="lazy"/></p> <p>To build the hexacopter I removed the protective cap from the frame.<br> Starting with the top right motor, work anti-clockwise and insert the motors into the clips provided.</br></p> <p><img src="https://ghost.jcode.me/content/images/2014/May/IMG_5761.jpg" alt="Flexbot multicopter" loading="lazy"/></p> <p>Following the instructions resulted in a fully assembled hexacopter minus battery.</p> <p><img src="https://ghost.jcode.me/content/images/2014/May/IMG_5763.jpg" alt="Flexbot multicopter" loading="lazy"/></p> <p>But it's not like installing the battery was hard.</p> <p>After attaching the battery to the battery holder, and the battery holder to the multicopter assembly is complete.</p> <p><img src="https://ghost.jcode.me/content/images/2014/May/IMG_5770.jpg" alt="Flexbot multicopter" loading="lazy"/></p> <p><br><br/></br></p> <h3 id="flighttestvideos">Flight test videos</h3> <p>These videos are showing how easy it is to control with little to no experience with multicopters.</p> <iframe src="//www.youtube.com/embed/am1jN2tIOAo" frameborder="0" allowfullscreen=""/> <br> <iframe src="//www.youtube.com/embed/z7YIBW9zbzs" frameborder="0" allowfullscreen=""/> <p><br><br/></br></p> <h3 id="thoughts">Thoughts</h3> <p><em>Note: All testing was done with the original firmware.</em></p> <p><strong>Build quality</strong></p> <p>The frame of the hexacopter has been printed from a <a href="http://en.wikipedia.org/wiki/3D_printing?ref=ghost.jcode.me">3D printer</a> which means that it's a little rough. The sides are smooth but flat surfaces look like a child learning to colour in between the lines.</p> <p>However the frame is incredibly flexible compared to molded plastic, which comes in handy when you have a crash, and if you're brand new to multicopters (like I am) you'll crash often.</p> <p>The propellers are surprisingly durable considering the comments on the kickstarters page, multiple times the blades have come into contact with solid objects and only been scuffed by the contact.</p> <p><strong>Controlls</strong></p> <p>The hexacopter is controlled by a smartphone (iOS or Android) over bluetooth.</p> <p><img src="https://ghost.jcode.me/content/images/2014/May/Screenshot_2014-05-04-14-08-22--1-.png" alt="Flexbot multicopter" loading="lazy"/></p> <p>Connecting sometimes takes a minute or two, other times it connects in seconds.</p> <p>After a connection has been made controlling the flight of the hexacopter is incredibly responsive, almost like a dedicated remote control.</p> <p>I had no trouble flying the hexacopter around my backyard which is about 300m squared. Since the overall size is rather small it's possible to lose sight of the hexacopter before it reaches the end of the 50m BLE single range.</p> <p><strong>Flight time</strong></p> <p>The Flexbot has an advertised flight time of 7 minutes. My flight times average 6 minutes which is nearly spot on.</p> <p>The only problem is that it takes 30 minutes to charge a battery to full capacity, so repetitive flights (and therefore fun) is somewhat lacking.</p> <p><strong>Overall</strong></p> <p>Considering the negative feedback from Kickstarter I was expecting a <a href="http://goo.gl/5lW1wm?ref=ghost.jcode.me">horrible time with something that didn't work</a> and <a href="http://goo.gl/Wddtx2?ref=ghost.jcode.me">spontaneously broke</a> into a <a href="http://goo.gl/uw4hQE?ref=ghost.jcode.me">few million pieces</a> but I was pleasantly surprised with a working product. The product might have a few issues but it's still impressive considering everything is open source, including the hardware and design.</p> <p>I'm certainly happy with it, and if Flexbot continue to upgrade the design I'll be happy to recommend it to people who are interested in something a little different and completely hackable.</p> <p><img src="https://ghost.jcode.me/content/images/2014/May/IMG_5920.jpg" alt="Flexbot multicopter" loading="lazy"/></p> <!--kg-card-end: markdown--></br></br>]]></content:encoded></item><item><title><![CDATA[Ghost theme: Ratatoskr]]></title><description><![CDATA[ Introducing Ratatoskr, a simple news theme for Ghost. Ratatoskr focuses on text heavy content, delivering it with clean and simple fonts. Ratatoskr is based on the light weight Casper [https://github.com/TryGhost/Casper] theme created by the Ghost team, allowing for a ridiculously light theme. Its load times are often under a second, even after scoring 77% on the Pingdom [http://tools.pingdom.com/fpt/] test, with the only shortcoming being an external link to Google Fonts. GTmetrix [http://]]></description><link>https://jcode.me/ghost-theme-ratatoskr/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff45</guid><category><![CDATA[Ghost Theme]]></category><category><![CDATA[Ratatoskr]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Sat, 07 Dec 2013 00:19:41 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2018/12/KMFGTDT4G22B9BNWXCEE.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2018/12/KMFGTDT4G22B9BNWXCEE.jpg" alt="Ghost theme: Ratatoskr"/><p><img src="https://ghost.jcode.me/content/images/2013/Dec/ratatoskr_mockup.png" alt="Ghost theme: Ratatoskr" loading="lazy"/></p> <p>Introducing Ratatoskr, a simple news theme for Ghost. Ratatoskr focuses on text heavy content, delivering it with clean and simple fonts.</p> <p>Ratatoskr is based on the light weight <a href="https://github.com/TryGhost/Casper?ref=ghost.jcode.me">Casper</a> theme created by the Ghost team, allowing for a ridiculously light theme.</p> <p>Its load times are often under a second, even after scoring 77% on the <a href="http://tools.pingdom.com/fpt/?ref=ghost.jcode.me">Pingdom</a> test, with the only shortcoming being an external link to Google Fonts.</p> <p><a href="http://gtmetrix.com/?ref=ghost.jcode.me">GTmetrix</a> Also gives it a clean bill of health, complaining about CDNs as it does for everything.</p> <p><img src="https://ghost.jcode.me/content/images/2013/Dec/pingdom_ratatoskr.jpg" alt="Ghost theme: Ratatoskr" loading="lazy"/></p> <p><img src="https://ghost.jcode.me/content/images/2013/Dec/gtmetrix_ratatoskr.jpg" alt="Ghost theme: Ratatoskr" loading="lazy"/></p> <br> <p>If you are interested in seeing more, head on over to <a href="http://jasonmillward.github.io/Ratatoskr/?ref=ghost.jcode.me">the demo site</a> for a live preview, or <a href="https://github.com/JasonMillward/Ratatoskr?ref=ghost.jcode.me">github</a> for the free theme download.</p> <!--kg-card-end: markdown--></br>]]></content:encoded></item><item><title><![CDATA[Ghost theme: Nyx]]></title><description><![CDATA[ Two days ago I tried my hand at a more custom Ghost theme and I created Nyx, named after the Greek goddess of the night. Nyx uses HTML5 Boilerplate for its core, snugly wrapped in Bootstrap and Masonry layers with just a hint of jQuery on the side. All custom stylesheets are constructed in LESS and compressed to save precious bytes for mobile users. As you can see, Nyx is very fast and light, Pingdom [http://tools.pingdom.com/fpt/] gave Nyx a Performance Grade of 100%. It also has fantast]]></description><link>https://jcode.me/ghost-theme-nyx/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff44</guid><category><![CDATA[Ghost Theme]]></category><category><![CDATA[Nyx]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Tue, 19 Nov 2013 10:43:06 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2018/12/MX8FXG9FCIFJ44VTPUEJ.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2018/12/MX8FXG9FCIFJ44VTPUEJ.jpg" alt="Ghost theme: Nyx"/><p><img src="https://ghost.jcode.me/content/images/2013/Nov/nyx_mockup.png" alt="Ghost theme: Nyx" loading="lazy"><br> Two days ago I tried my hand at a more <em>custom</em> Ghost theme and I created Nyx, named after the Greek goddess of the night.</br></img></p> <p>Nyx uses HTML5 Boilerplate for its core, snugly wrapped in Bootstrap and Masonry layers with just a hint of jQuery on the side.</p> <p>All custom stylesheets are constructed in LESS and compressed to save precious bytes for mobile users.</p> <p><img src="https://ghost.jcode.me/content/images/2013/Nov/nyx_page_test.png" alt="Ghost theme: Nyx" loading="lazy"/></p> <p>As you can see, Nyx is very fast and light, <a href="http://tools.pingdom.com/fpt/?ref=ghost.jcode.me">Pingdom</a> gave Nyx a Performance Grade of 100%.</p> <p>It also has fantastic Page Speed and YSlow ratings thanks to <a href="http://gtmetrix.com/?ref=ghost.jcode.me">GTmetrix</a></p> <p><img src="https://ghost.jcode.me/content/images/2013/Nov/metrix.png" alt="Ghost theme: Nyx" loading="lazy"/></p> <br> <p>If you are interested in seeing more, head on over to <a href="http://jasonmillward.github.io/Nyx/?ref=ghost.jcode.me">the demo site</a> for a live preview, or <a href="https://github.com/JasonMillward/Nyx?ref=ghost.jcode.me">github</a> for the free theme download.</p> <!--kg-card-end: markdown--></br>]]></content:encoded></item><item><title><![CDATA[YouTube+ comments]]></title><description><![CDATA[The recent change in the YouTube comment system, more specifically, the merge with Google+ has created a tremendous amount of backlash. Top YouTubers such as Yogscasts [http://www.youtube.com/user/BlueXephos], Nerd³ [http://www.youtube.com/user/OfficialNerdCubed], Totalbiscuit [http://www.youtube.com/user/TotalHalibut] and Pewdiepie [http://www.youtube.com/user/Pewdiepie] have disabled comments all together on their new videos, one citing increased spam being submitted to their videos. > "Due ]]></description><link>https://jcode.me/youtube-comments/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff43</guid><category><![CDATA[adventure time]]></category><category><![CDATA[comments]]></category><category><![CDATA[google+]]></category><category><![CDATA[youtube]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Mon, 11 Nov 2013 03:48:51 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2018/12/M0CJXSTXWC5NG5UZ2703.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2018/12/M0CJXSTXWC5NG5UZ2703.jpg" alt="YouTube+ comments"/><p>The recent change in the YouTube comment system, more specifically, the merge with Google+ has created a tremendous amount of backlash.</p> <p>Top YouTubers such as <a href="http://www.youtube.com/user/BlueXephos?ref=ghost.jcode.me">Yogscasts</a>, <a href="http://www.youtube.com/user/OfficialNerdCubed?ref=ghost.jcode.me">Nerd³</a>, <a href="http://www.youtube.com/user/TotalHalibut?ref=ghost.jcode.me">Totalbiscuit</a> and <a href="http://www.youtube.com/user/Pewdiepie?ref=ghost.jcode.me">Pewdiepie</a> have disabled comments all together on their new videos, one citing increased spam being submitted to their videos.</p> <blockquote> <p>"Due to the recent changes of the YouTube comment system I'm forced to disable comments. Since front page/top comments are filled with: Links to virus sites, Advertisers, Self Advertisers, Spam, Copy and paste pics of dogs"</p> </blockquote> <p><a href="http://www.youtube.com/feather_beta?ref=ghost.jcode.me">"Feather"</a> is an inbuilt YouTube project which cuts down on all of the excess layouts and javascript to deliver a faster YouTube experience. Dedicating the entire screen to your chosen content instead of sidebars.</p> <p><a href="http://www.youtube.com/watch?v=p_HxKReSSlA&ref=ghost.jcode.me"><img src="https://ghost.jcode.me/content/images/2013/Nov/full_youtube_feather.jpg" alt="YouTube+ comments" title="No side bars, only awesome blacksmithing" loading="lazy"/></a></p> <p>One <s>side effect</s> benefit is the complete removal of any and all comments.</p> <p>The comments rarely add anything of value to the video you are watching and most of the time end up being an argument about something totally unrelated, so for me they won't be missed at all.</p> <p>However, YouTube have added an option to enable comments on a single video temporarily just in case you wanted to dive into the comment cesspool.</p> <br> <p>Happy YouTubing</p> <!--kg-card-end: markdown--></br>]]></content:encoded></item><item><title><![CDATA[Ingress Badges - Source]]></title><description><![CDATA[In total I created 8 badges and all have given to various resistance members. I won't be making any more badges as I have moved on from Ingress related activities, however if you wish to create your own I've put the source up on Github [https://github.com/JasonMillward/Ingress-hacking-badge] for anyone to use. Parts list: * ATtiny85 * 6x 1.8mm blue LED * CR2032 battery * CR2032 battery holder * PCB to mount everything on * Aligator clips or saftey pins to attach to clothes Purchasing ]]></description><link>https://jcode.me/ingress-badges-source/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff3a</guid><category><![CDATA[Badges]]></category><category><![CDATA[Hacking]]></category><category><![CDATA[Ingress]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Fri, 20 Sep 2013 00:30:00 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2018/12/ingressbattle.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2018/12/ingressbattle.jpg" alt="Ingress Badges - Source"/><p>In total I created 8 badges and all have given to various resistance members.</p> <p>I won't be making any more badges as I have moved on from Ingress related activities, however if you wish to create your own I've put the source up on <a href="https://github.com/JasonMillward/Ingress-hacking-badge?ref=ghost.jcode.me">Github</a> for anyone to use.</p> <p><strong>Parts list:</strong></p> <ul> <li>ATtiny85</li> <li>6x 1.8mm blue LED</li> <li>CR2032 battery</li> <li>CR2032 battery holder</li> <li>PCB to mount everything on</li> <li>Aligator clips or saftey pins to attach to clothes</li> </ul> <p>Purchasing 2 part epoxy resin to secure LEDS and artwork would be a good idea, but it is not essential.</p> <p><strong>Build process:</strong></p> <ul> <li><a href="http://jcode.me/ingress-badges-pt-1/?ref=ghost.jcode.me">Part 1</a></li> <li><a href="http://jcode.me/ingress-badges-pt-2/?ref=ghost.jcode.me">Part 2</a></li> <li><a href="http://jcode.me/ingress-badges-pt-3/?ref=ghost.jcode.me">Part 3</a></li> <li><a href="http://jcode.me/ingress-badges-pt-4/?ref=ghost.jcode.me">Part 4</a></li> <li><a href="http://jcode.me/ingress-badges-pt-5/?ref=ghost.jcode.me">Part 5</a></li> </ul> <!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Ingress Badges - Pt. 5]]></title><description><![CDATA[After counting down 5 minutes, the badges go into a deep sleep mode. Previously that was using around 225μA, which was a little bit too much for something that was supposed to be in a deep sleep. After some additional tweaking of the existing code I managed to get the ATTiny using just over 6μA. That's 37 times less power being used. Using a standard CR2032 battery 40 times a day, every day, puts the estimated battery life at: 80 days! Obviously if used more frequently it wouldn't last as lon]]></description><link>https://jcode.me/ingress-badges-pt-5/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff39</guid><category><![CDATA[Badges]]></category><category><![CDATA[Hacking]]></category><category><![CDATA[Ingress]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Thu, 19 Sep 2013 02:30:00 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2018/12/ingressbattle.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2018/12/ingressbattle.jpg" alt="Ingress Badges - Pt. 5"/><p>After counting down 5 minutes, the badges go into a deep sleep mode.</p> <p>Previously that was using around 225μA, which was a little bit too much for something that was supposed to be in a deep sleep.</p> <p>After some additional tweaking of the existing code I managed to get the ATTiny using just over 6μA. That's 37 times less power being used.</p> <p>Using a standard CR2032 battery 40 times a day, every day, puts the estimated battery life at: <strong>80 days!</strong><br> Obviously if used more frequently it wouldn't last as long.</br></p> <hr> <p><img src="https://ghost.jcode.me/content/images/2013/Sep/IMG_20130915_125239.jpg" alt="Ingress Badges - Pt. 5" loading="lazy"/></p> <p><img src="https://ghost.jcode.me/content/images/2013/Sep/IMG_20130920_163712.jpg" alt="Ingress Badges - Pt. 5" loading="lazy"/></p> <!--kg-card-end: markdown--></hr>]]></content:encoded></item><item><title><![CDATA[Optimise jpeg via CLI]]></title><description><![CDATA[When testing the performance of a website one of the most common recommendations is to optimise images. When you have a large stash of images uploaded over years of blogging, downloading and optimising them all might not seem worth it, but the results are astounding. To optimise a large amount of jpeg images via command line we can use a tool called jpegoptim. To install jpegoptim v1.3.0 on Debian stable all previous installations of libjpeg must be removed. Don't worry though, it will be r]]></description><link>https://jcode.me/optimise-jpeg-via-cli/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff3b</guid><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Sun, 15 Sep 2013 02:30:00 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2018/12/2512CL381RYGVP32NFTZ.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2018/12/2512CL381RYGVP32NFTZ.jpg" alt="Optimise jpeg via CLI"/><p>When testing the performance of a website one of the most common recommendations is to optimise images.</p> <p>When you have a large stash of images uploaded over years of blogging, downloading and optimising them all might not seem worth it, but the results are astounding.</p> <br> <p>To optimise a large amount of jpeg images via command line we can use a tool called <em>jpegoptim</em>.</p> <p>To install <em>jpegoptim v1.3.0</em> on Debian stable all previous installations of <em>libjpeg</em> must be removed. Don't worry though, it will be replaced very shortly.</p> <pre><code>$ sudo apt-get remove --purge libjpeg* </code></pre> <p>After everything is removed it's time to install <em>libjpeg v9</em></p> <pre><code>wget http://www.ijg.org/files/jpegsrc.v9.tar.gz tar -zxvf jpegsrc.v9.tar.gz cd jpeg-9/ ./configure make sudo make install </code></pre> <p>Now download and install <em>jpegoptim v1.3.0</em></p> <pre><code>wget https://github.com/tjko/jpegoptim/archive/RELEASE.1.3.0.zip unzip RELEASE.1.3.0.zip cd jpegoptim-RELEASE.1.3.0/ ./configure make make strip sudo make install </code></pre> <p>Check the version number using <code>jpegoptim --version</code>, you should see <code>jpegoptim v1.3.0</code>.</p> <br> <p>For single directories it is possible to use a simple for loop</p> <pre><code>for i in *.jpeg; do jpegoptim "$i"; done </code></pre> <p>But this is not ideal if you upload images into folders by year and/or month, similar to the WordPress structure.</p> <pre><code>-- uploads |-- 2010 | |-- 05 | |-- 06 | |-- 10 | `-- 12 |-- 2011 | |-- 01 | |-- 06 | |-- 07 </code></pre> <p>Travelling into all of the above directories would be tiresome, but not if we use our buddy <em>find</em>.</p> <p><em>Find</em> can recursively look through a folder to find all files or folders and execute commands on just those it found.</p> <p>Finding all jpeg files is pretty simple.</p> <pre><code>find . -regextype sed -regex ".*/*.jp[e]\?g$" </code></pre> <p>Using the exec option it is possible to specify what to do with the files found.</p> <p>First up is the dry run, it won't affect any files, only show how much optimisation could be done.</p> <pre><code>find . -regextype sed -regex ".*/*.jp[e]\?g$" \ -exec jpegoptim --all-progressive -o -n -m90 --strip-all {} \; </code></pre> <p>Feel free to play around with the maximum image quality setting <code>m[0-100]</code>, I like to keep mine at 90.</p> <p>Once you are happy with the results, remove the dry run option <code>-n</code> to set it optimising your images.</p> <pre><code>find . -regextype sed -regex ".*/*.jp[e]\?g$" \ -exec jpegoptim --all-progressive -o -m90 --strip-all {} \; </code></pre> <br> <p>This has reduced my jpegs size by 7-25%, reduced load time by a second and increased my Page Speed Grade from 75% to 90%.</p> <p>For a little under 10 minutes work, I'd say that's a success.</p> <!--kg-card-end: markdown--></br></br></br>]]></content:encoded></item><item><title><![CDATA[Ingress Badges - Pt. 4]]></title><description><![CDATA[Almost complete! Please excuse the overly bright lights, it doesn't look that bad in real life. The lights count down the 5 minutes until your next hack. Currently showing off; * Flashing an updated program to the ATtiny85 after it's been soldered in place * A simple startup animation * The 5 minutes cool down timer (Shortened for demonstration) * Low power mode after countdown is complete ( 0.3mA )]]></description><link>https://jcode.me/ingress-badges-pt-4/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff38</guid><category><![CDATA[Badges]]></category><category><![CDATA[Hacking]]></category><category><![CDATA[Ingress]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Thu, 12 Sep 2013 02:30:00 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2018/12/ingressbattle.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2018/12/ingressbattle.jpg" alt="Ingress Badges - Pt. 4"/><p>Almost complete!</p> <p>Please excuse the overly bright lights, it doesn't look that bad in real life.</p> <p>The lights count down the 5 minutes until your next hack.</p> <p>Currently showing off;</p> <ul> <li>Flashing an updated program to the ATtiny85 after it's been soldered in place</li> <li>A simple startup animation</li> <li>The 5 minutes cool down timer (Shortened for demonstration)</li> <li>Low power mode after countdown is complete ( 0.3mA )</li> </ul> <div class="embed-container"><iframe src="https://www.youtube.com/embed/IMo2eCgIXwg" frameborder="0" allowfullscreen=""/></div><!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Ingress Badges - Pt. 3]]></title><description><![CDATA[After applying resin to the badge, it seems to have smudged the ink. I will have to test sealing methods to prevent the ink from running.]]></description><link>https://jcode.me/ingress-badges-pt-3/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff37</guid><category><![CDATA[Badges]]></category><category><![CDATA[Hacking]]></category><category><![CDATA[Ingress]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Tue, 10 Sep 2013 02:30:00 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2018/12/ingressbattle.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2018/12/ingressbattle.jpg" alt="Ingress Badges - Pt. 3"/><p>After applying resin to the badge, it seems to have smudged the ink.</p> <p>I will have to test sealing methods to prevent the ink from running.</p> <p><img src="https://ghost.jcode.me/content/images/2013/Sep/IMG_20130909_175645.jpg" alt="Ingress Badges - Pt. 3" loading="lazy"><br> <img src="https://ghost.jcode.me/content/images/2013/Sep/IMG_20130910_082230.jpg" alt="Ingress Badges - Pt. 3" loading="lazy"/></br></img></p> <!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Ingress Badges - Pt. 2]]></title><description><![CDATA[Starting phase two of development. This time with colour copies. Pushing the button on the back starts a 5 minute 'hacking' cool down timer. Project not complete just yet, still have to coat with epoxy resin on the front and back to give it some strength. --------------------------------------------------------------------------------]]></description><link>https://jcode.me/ingress-badges-pt-2/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff36</guid><category><![CDATA[Badges]]></category><category><![CDATA[Hacking]]></category><category><![CDATA[Ingress]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Sat, 07 Sep 2013 02:30:00 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2018/12/ingressbattle.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2018/12/ingressbattle.jpg" alt="Ingress Badges - Pt. 2"/><p>Starting phase two of development. This time with colour copies.</p> <p>Pushing the button on the back starts a 5 minute 'hacking' cool down timer.</p> <p>Project not complete just yet, still have to coat with epoxy resin on the front and back to give it some strength.</p> <hr> <p><img src="https://ghost.jcode.me/content/images/2013/Sep/IMG_20130906_205255.jpg" alt="Ingress Badges - Pt. 2" loading="lazy"><br> <img src="https://ghost.jcode.me/content/images/2013/Sep/IMG_20130906_220031.jpg" alt="Ingress Badges - Pt. 2" loading="lazy"><br> <img src="https://ghost.jcode.me/content/images/2013/Sep/IMG_20130906_223118.jpg" alt="Ingress Badges - Pt. 2" loading="lazy"><br> <img src="https://ghost.jcode.me/content/images/2013/Sep/IMG_20130909_090222.jpg" alt="Ingress Badges - Pt. 2" loading="lazy"/></br></img></br></img></br></img></p> <!--kg-card-end: markdown--></hr>]]></content:encoded></item><item><title><![CDATA[Ingress Badges - Pt. 1]]></title><description><![CDATA[Had fun making progress on the (Hacking Timer) Ingress Badges. Todays adventure involved picking a size and experimenting with wiring and LED frequency. Just a new timing bugs to sort out before working on the colour test run. -------------------------------------------------------------------------------- Rough cutout Smooth cutout Testing charlieplexing]]></description><link>https://jcode.me/ingress-badges-pt-1/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff35</guid><category><![CDATA[Badges]]></category><category><![CDATA[Hacking]]></category><category><![CDATA[Ingress]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Sat, 24 Aug 2013 02:30:00 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2018/12/ingressbattle.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2018/12/ingressbattle.jpg" alt="Ingress Badges - Pt. 1"/><p>Had fun making progress on the (Hacking Timer) Ingress Badges.</p> <p>Todays adventure involved picking a size and experimenting with wiring and LED frequency.</p> <p>Just a new timing bugs to sort out before working on the colour test run.</p> <hr> <p><strong>Rough cutout</strong><br> <img src="https://ghost.jcode.me/content/images/2013/Sep/IMG_20130824_134317-1.jpg" alt="Ingress Badges - Pt. 1" loading="lazy"><br> <br/></br></img></br></p> <p><strong>Smooth cutout</strong><br> <img src="https://ghost.jcode.me/content/images/2013/Sep/IMG_20130824_140311.jpg" alt="Ingress Badges - Pt. 1" loading="lazy"><br> <br/></br></img></br></p> <p><strong>Testing charlieplexing</strong><br> <img src="https://ghost.jcode.me/content/images/2013/Sep/IMG_20130824_174717.jpg" alt="Ingress Badges - Pt. 1" loading="lazy"/></br></p> <!--kg-card-end: markdown--></hr>]]></content:encoded></item><item><title><![CDATA[New RAH Timelapse, 1 year later]]></title><link>https://jcode.me/new-rah-timelapse-1-year-later/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff33</guid><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Wed, 23 Jan 2013 02:30:00 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2018/12/AEFWV7URH7R57JCGP9HH.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><div class="embed-container"><iframe width="700" height="500" src="//www.youtube.com/embed/6gDqGU-Sjjw?fs=1&feature=oembed" frameborder="0" allowfullscreen=""/></div><!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[MakeMKV Auto ripper]]></title><description><![CDATA[We're now in the age of digital movies and media players such as Plex, and for those of us who bought DVDs and BDs getting up and inserting a physical disk can be such a task when you want to watch a movie.]]></description><link>https://jcode.me/makemkv-auto-ripper/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff31</guid><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Wed, 10 Oct 2012 02:30:00 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2018/12/G1YHCJK9W8B7HEPMJ0EK.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2018/12/G1YHCJK9W8B7HEPMJ0EK.jpg" alt="MakeMKV Auto ripper"/><p>MakeMKV Auto ripper has been updated a lot since this post. It is now known as Autorippr.</p> <p>An update post can be found <a href="http://jcode.me/autorippr-update/?ref=ghost.jcode.me">here</a>.</p> <hr> <p>We're now in the age of digital movies and media players such as <a href="http://plexapp.com/?ref=ghost.jcode.me">Plex</a>, and for those of us who bought DVDs and BDs getting up and inserting a physical disk can be such a task when you want to watch a movie.</p> <p>Moving over to the digital age of MKVs with an extensive library can be a daunting task. Manually inserting a disc, clicking "Rip to mkv" and waiting for it to finish before repeating it for every DVD you own can take forever.</p> <p>However, if you could put a disc in and have it automatically look up the proper title on IMDb, rip to a chosen destination and idle when complete and the only task left would be swapping the discs every so often would be a great comprimise to a fully autonomos creation.</p> <p>This auto ripping tool made in python does exactly that, it watches and waits for a DVD, scrapes IMDb for the proper title and uses MakeMKV to rip to the selected directory.</p> <p>I've tested it running inside a simple cron running every 5 minutes..</p> <pre><code>*/5 * * * * python ~/scripts/github/makeMKV-Autoripper/autoRip.py </code></pre> <p>And it successfully renamed and ripped 4/5 movies, and with some additional <abbr title="Extended Edition in the title throws the IMDb scraper off, so I remove it from the search string">tweaking </abbr> it ripped the last one.<br> The DVD in question was <em>Die Hard 4</em> Extended Edition, which gets renamed to <em>Live Free or Die Hard</em>.</br></p> <p>So after a successful testing, and letting it run without user intervention for nearly a month I'm releasing it to the public (That's you guys) to use and improve.</p> <p>As always, source is available on <a href="https://github.com/JasonMillward/Autorippr?ref=ghost.jcode.me">GitHub</a></p> <!--kg-card-end: markdown--></hr>]]></content:encoded></item><item><title><![CDATA[New RAH time-lapse]]></title><description><![CDATA[Ever since I was told that the New Royal Adelaide hospital had a webcam available for public I decided to create a time-lapse from begining to end. I will be following the construction of the New RAH, saving the live webcam feed once every 15 minutes for the next few years. I will be using some Linux magic with a crontab or two to save and generate a nice time-lapse which is automaticly encoded and uploaded to YouTube. With the current settings it's generating a 640x480 avi, enough to show eve]]></description><link>https://jcode.me/new-rah-time-lapse/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff2d</guid><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Sat, 21 Apr 2012 02:30:00 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2018/12/24A7RTFW20VR5POJQJPK.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2018/12/24A7RTFW20VR5POJQJPK.jpg" alt="New RAH time-lapse"/><p>Ever since I was told that the New Royal Adelaide hospital had a webcam available for public I decided to create a time-lapse from begining to end. I will be following the construction of the New RAH, saving the live webcam feed once every 15 minutes for the next few years.</p> <p>I will be using some Linux magic with a crontab or two to save and generate a nice time-lapse which is automaticly encoded and uploaded to YouTube.</p> <p>With the current settings it's generating a 640x480 avi, enough to show everything that's going on without using a lot of resources. As the project nears completion I will strip out all of the dud frames and increase the resolution and maybe throw in some special effects.</p> <p>My MicroServer currently takes 20 minutes to compress over <del datetime="2012-04-02T07:06:40+00:00">1750</del> 7600 images into a sizeable 250MB avi.</p> <p>Here is a link to the raw footage so far: <a href="http://www.youtube.com/watch?v=_pLN_YMWo8g&ref=ghost.jcode.me">YouTube</a><br> Includes all the dirt moving and night time footage. It's not pretty, not at all.</br></p> <p>If you wish to know the mencoder settings used, here they are;</p> <pre><code>mencoder "mf://*.jpg" -mf fps=60:type=jpg -ovc lavc -lavcopts vcodec=mpeg4:mbd=2:trell:vbitrate=7000 -vf scale=640:480 -oac copy -o The-new-Royal-Adelaide-Hospital-timelapse.avi</code></pre> <!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[How to create SSH Keys]]></title><description><![CDATA[Too many websites give long winded explanations of creating SSH keys for a password-less login, following all of the useless extra commands can be tedious but it's actually really simple. In the end it boils down to; Create ssh keys, if you've done this don't do it again ssh-keygen Copy to server you want to login to ssh-copy-id user@server Don't forget to check your permissions! Permissions of .ssh should be 700 Permissions of .ssh/authorized_keys should be 0600]]></description><link>https://jcode.me/how-to-create-ssh-keys/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff2f</guid><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Mon, 09 Apr 2012 02:30:00 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2018/12/TF9L2X4BB99D2KC2ZHJK.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2018/12/TF9L2X4BB99D2KC2ZHJK.jpg" alt="How to create SSH Keys"/><p>Too many websites give long winded explanations of creating SSH keys for a password-less login, following all of the useless extra commands can be tedious but it's actually really simple.</p> <p>In the end it boils down to;</p> <p><strong>Create ssh keys, if you've done this don't do it again</strong></p> <pre><code>ssh-keygen </code></pre> <p><strong>Copy to server you want to login to</strong></p> <pre><code>ssh-copy-id user@server </code></pre> <p>Don't forget to check your permissions!</p> <p>Permissions of <code>.ssh</code> should be 700<br> Permissions of <code>.ssh/authorized_keys</code> should be 0600</br></p> <!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Problems with expect & cron]]></title><description><![CDATA[One of the problems with using a cron to automate tasks in Linux is losing the user environment. With one of my recent projects I've been using expect [http://linux.die.net/man/1/expect] to automate some uploading to my webserver rather then setting up SSH keys (out of pure laziness). I've been taking a picture from a webcam and uploading it to my web server every 10 minutes of every day, something that I don't want to do manually. Using expect without the user environment for some reason also]]></description><link>https://jcode.me/problems-with-expect-and-cron/</link><guid isPermaLink="false">Ghost__Post__597a84334c50b41f8629ff2b</guid><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Sat, 07 Apr 2012 02:30:00 GMT</pubDate><media:content url="https://ghost.jcode.me/content/images/2018/12/4O3Y3EOTZJ3L55QUBTKW.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.jcode.me/content/images/2018/12/4O3Y3EOTZJ3L55QUBTKW.jpg" alt="Problems with expect & cron"/><p>One of the problems with using a cron to automate tasks in Linux is losing the user environment.</p> <p>With one of my recent projects I've been using <a href="http://linux.die.net/man/1/expect?ref=ghost.jcode.me" target="_blank">expect</a> to automate some uploading to my webserver rather then setting up SSH keys (out of pure laziness). I've been taking a picture from a webcam and uploading it to my web server every 10 minutes of every day, something that I don't want to do manually.</p> <p>Using expect without the user environment for some reason also means the loss of the <em>interact;</em> command. I've yet to discover why<a title="To be honest it's not high on my list of things to do.">*</a>, so instead of using <em>interact;</em> I had to use <em>expect eof; exit;</em><br> <br/></br></p> <p><strong>What I have been using</strong></p> <pre class="prettyprint lang-bash">spawn "scp skycam.jpg [User]@[IP]:./uploads/skycam" expect Password send "[Password]" interact</pre> <br> <strong>What I should be using</strong> <pre class="prettyprint lang-bash">spawn "scp skycam.jpg [User]@[IP]:./uploads/skycam" expect Password send "[Password]" expect eof exit</pre> <br> And just for the record, I've now switched to using SSH keys.<!--kg-card-end: markdown--></br></br>]]></content:encoded></item></channel></rss>