<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Kevin Norman</title>
    <link>http://kn100.me/</link>
    <description>Recent content on Kevin Norman</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-gb</language>
    <lastBuildDate>Sun, 21 Apr 2024 00:00:00 -0500</lastBuildDate>
    
      <atom:link href="http://kn100.me/index.xml" rel="self" type="application/rss+xml" />
    
    
    <item>
      <title>DeskOps: Commanding My Desk with HTTP - How I Brought Hysteresis Problems to the Desk Where I Solve Hysteresis Problems</title>
      <link>http://kn100.me/desksniffer/</link>
      <pubDate>Sun, 21 Apr 2024 00:00:00 -0500</pubDate>
      
      <guid>http://kn100.me/desksniffer/</guid>
      <description><![CDATA[<p>I have a standing desk, but I’ve always wanted a way to automate it, so that I
could set up a schedule for “standing” vs “sitting&quot;. The controller box itself
contains absolutely no smarts, and has no obvious external control mechanism, so
I wasn&rsquo;t really sure how I&rsquo;d achieve this. This post documents my experience
building a system that allows me to do this. It is not intended entirely as a
build guide, but more an exploration of what hardware hacking is like for
somebody with very little previous experience. Hopefully you&rsquo;ll learn something
from this and I&rsquo;ll save you some of the time I spent learning this stuff!</p>
<h1 id="what-this-is">
  <a class="Heading-link u-clickable" href="/desksniffer/#what-this-is">What this is</a>
</h1>
<p>I made a custom circuit board that kind of zombifies the original standing desk
controller. It is able to read the current height of the desk from the original
controller, as well as pilot the desk up and down. It is controllable via HTTP
requests, as well as from buttons on the front of the controller.</p>
<figure><img src="/posts/desksniffer/tease.webp"
         alt="A photo of a very obviously 3d printed standing desk controller with 3 mechanical keyboard switches on the front"/>
</figure>


<pre tabindex="0"><code>GET http://esp32-abcde/desk
{&#34;height&#34;:720}

GET http://esp32-abcde/desk?height=780
`OK`</code></pre>
<h1 id="initial-exploration">
  <a class="Heading-link u-clickable" href="/desksniffer/#initial-exploration">Initial exploration</a>
</h1>
<p>The standing desk I have is a <code>VIVO DESK-V122EB</code> frame with a 2.5m IKEA
countertop placed atop it. More importantly, the controller box my standing desk
has looks like this (Apologies for the terrible photo, I didn’t take any photos
before doing what I did, so this one was cropped from a much larger photo).</p>
<figure><img src="/posts/desksniffer/original-controller.webp"
         alt="The original desks controller"/>
</figure>

<p>After doing some Googling, nothing seemed particularly relevant to my specific
standing desk, and other solutions I found involved strapping motorized button
pressing robots to the controller, so I decided to open up the controller box
and take a look to see if I could do something smarter.</p>
<figure><img src="/posts/desksniffer/original-controller-circuitboard.webp"
         alt="The original desks controller circuitboard"/>
</figure>

<p>Looking at the circuit board, I realised it would be trivial to ‘press’ the up
and down buttons from an external microcontroller. All I’d need to do is use a
transistor to bridge the contacts of the switch. I really wanted something a bit
smarter than that though. I wanted whatever I built to be able to know the
height of the desk. After studying the board for a bit and googling the random
part numbers on the chips I could see on the board, I identified the AIP650 as
the chip responsible for driving the LCD. Fortunately, a data sheet was
available for this part, but unfortunately it was in Chinese, a language I do
not speak.</p>
<p>According to the data sheet, translated with DeepL, it receives data in an i2c
‘like’ format, and then drives a 3 segment LCD just like the one on this board
based on what data it received. I was unable to determine from the data sheet
what the data being sent to the controller actually “meant”, but wasn&rsquo;t worried
about that for the moment. I just wanted to capture the data.</p>
<p>For those not familiar with hardware, I2C is the scheme by which most circuits
use for inter-chip communication. In my desk controller, there is a
microcontroller which writes data to a LCD driver chip, which in turn lights the
correct segments on a 3 digit segmented LCD. How would I capture the data
though? Easy, I thought! All I had do to was hook up to the same two pins the
data sheet reported as being the i2c pins along with the grounds of the standing
desk controller and my esp32, and I could then “sniff” the data that was being
sent across the i2c bus, decode it, and then use that for something useful.</p>
<p>So away I went! I soldered two wires directly to the pins on the chip that were
marked as the data pins (I am deeply sorry to anybody offended by my soldering
job!), and soldered a third wire to a ground point on the board. I then
connected those wires to an ESP32, and then went looking for somebody elses code
to try to sniff the i2c data to see if this was even possible.</p>
<figure><img src="/posts/desksniffer/terrible-soldering-to-aip650.webp"
         alt="My terrible soldering to the SDA and SCL pins on the AiP650 chip"/>
</figure>

<p>I came across <a href="https://github.com/WhitehawkTailor/I2C-sniffer">one such project
online</a>, which just about
worked. It would output what looked like plausible i2c data, but would
constantly crash or start outputting total nonsense. At this point, I hadn&rsquo;t
really understood I2C, nor did I understand the code here at all, so the fact it
worked even a little bit I took as a win.</p>
<p>At this point, I still hadn’t really considered how the code worked at all, so I
then took the plunge into the code and figured out its method of operation, so
next I took a dive into the world of I2C.</p>
<h1 id="a-quick-look-into-i2c">
  <a class="Heading-link u-clickable" href="/desksniffer/#a-quick-look-into-i2c">A quick look into I2C</a>
</h1>
<blockquote>
<p>If you understand I2C even a little bit, you probably understand it better
than I, so you can skip this section.</p>
</blockquote>
<p>In I2C communication, there are two pins involved. One is the clock pin, usually
referred to <code>SCL</code>, and the data pin, referred to as <code>SDA</code>. The participants on
an I2C bus are known as controller devices, and target devices (although are
commonly referred to as master/slave in data sheets and the such!). Controller
devices write to the I2C bus, and target devices listen for writes that are
targeting them. Target devices do respond on the I2C bus, but this is to just
acknlowledge they correctly received data. Remember that this is digital data -
so the controller can only pull either of the pins high or low, which literally
means “emitting voltage” or “connected to ground”. It emits a clock signal
(continuous pulses on the SCL pin), along with varying pulses on the SDA pin for
signalling.</p>
<p>Let&rsquo;s review one I2C communication session:</p>
<ol>
<li>
<p>A controller wants to communicate with a target. IT first must assert control
of the bus.  To do this, it first pulls SCL high, and then pulls the SDA line
high, then pulls it low. This transition of SDA from high to low while SCL
has been held high implies a master is about to broadcast data, and targets
know to listen for incoming data.</p>
</li>
<li>
<p>The controller needs to let all those target devices listening know which
device it wants to talk to. It does this by sending 7 bits of data which
correspond to a device address, along with 1 bit to indicate whether it would
like to read or write to that specific device. It transmits these bits of
data by pulling SDA either high or low to indicate a 1 or 0, along with a
clock pulse (pulling the clock from low to high). This triggers the target
device to read the value that is present on the SDA pin, thus transmitting a
byte.</p>
</li>
</ol>
<blockquote>
<p>A note about addresses: They’re not always 7 bits, and sometimes can be 10
bit. They’re usually hardcoded into whatever target device is receiving them -
and for hobbyist grade Arduino attachments, you can even configure the address
through jumper pins or switches, allowing you to connect more than one of the
same device to an i2c bus.</p>
</blockquote>
<ol start="3">
<li>
<p>Next, the target with the correct address must ACK this transmission, to
signal to the controller that it is ready to receive data. The target does
this by pulling the SDA pin low, which the controller interprets as an ACK.
If no target ever does this, the controller should assume it wasn&rsquo;t heard for
some reason.</p>
</li>
<li>
<p>Assuming the target ACKed the transmission, meaning it recognised the next
data on the I2C bus is intended for it, the controller proceeds to transmit a
data packet. IT transmits 8 bytes of data following the same scheme as the
address transmission above, and again waits for an ACK condition the same
way. If there is more than a single byte of data that needs to be
transferred, it just transmits the next 8 bits after receiving the ACK. This
cycle can happen an arbitrary number of times.</p>
</li>
<li>
<p>The controller is now done transmitting to that particular I2C device, so it
needs to signal that. It sends a stop condition. This is similar to the start
condition described above, except this time we pull SCL high, and then pull
SDA high. The transition of SDA from low to high while SCL was held high
indicates the transmission is over.</p>
</li>
</ol>
<h1 id="digging-deeper-into-the-desk">
  <a class="Heading-link u-clickable" href="/desksniffer/#digging-deeper-into-the-desk">Digging deeper into the desk</a>
</h1>
<p>Knowing all of this now, I decided to refactor the code sample above into a form
that was better understood by me, which ended up <a href="https://github.com/kn100/I2C-sniffer/">turning into this
project</a>.</p>
<p>A quick discussion on how this code works. Since we only really care when the
state changes on the pins we are sniffing (SDA and SCL) we can get away with
using interrupts. An interrupt causes the CPU to halt execution of whatever it
was doing, switch to some other context, do some other processing, and then
return back to the main program loop. In our context, we want interrupts that
trigger on state changes for the I2C Pins. Realistically, there are only two
events we only really care about:</p>
<p><strong>How we detect start and stop conditions</strong>: SDA changing from <code>HIGH &gt; LOW</code> or
from <code>LOW &gt; HIGH</code> triggers the interrupt <code>i2cTriggerOnChangeSDA()</code>. If SCL was
LOW, this isn’t a start or stop, so just return. If SCL was high, depending on
the current bus state (discussed later), along with whether SDA is high or low,
we record a Start or Stop to a buffer. Concerning the bus state: we initially
assume the bus is IDLE, and move to TX if there is a start condition detected.
If a stop condition is detected, we move to IDLE)</p>
<p><strong>How we detect data being transmitted</strong>: SCL changing from <code>Low &gt; High</code>
indicates that there is data for us to read. so, the transition triggers the
interrupt <code>i2cTriggerOnRaisingSCL()</code>, which checks the bus state. If it was set
to TX by <code>i2cTriggerOnChangeSDA()</code>, we read whatever data is present on SDA pin
and write it into the buffer</p>
<p>This results in a stream of data, and very quickly a buffer overflow. To resolve
this, we need some code that processes that buffer and does something useful
with it. We can only do this processing with the I2C bus is idle, so, in the
main program loop, we wait for the IDLE bus condition to be reported by the
interrupts above. Once we have that, we can safely parse through the buffer, and
do something with the data. In the example code above, it just prints whatever
it got to Serial. Once it’s caught up with the last written position of the
buffer, it resets the variables that keep track of where the buffer was last
written to and read to, so that we can continue to use that same memory over
and over.</p>
<p>The code did as I asked and outputted streams of 1s and 0s along with start and
stop conditions to the screen, but I had no idea what any of it meant, nor if
the code even worked. What I could see is that there seemed to be bursts of
data, and if the data was to be believed, at least 5 I2C addresses receiving
data.</p>
<p>I lamented this fact in a conversation with a colleague (if you’re reading this,
you know who you are, THANKS!), and he suggested I pick up a Logic Analyzer. I
had no idea what a logic analyzer was, but for years I’d looked on in envy as
others used their logic analyzers for interesting hacking related tasks, and
thought that stuff was way too complicated for me. I’d never gotten one because
I was afraid of buying it, realizing I was way out of my depth, and it becoming
“that thing I bought that I have no idea how to use”.</p>
<figure><img src="/posts/desksniffer/logic-analyzer.webp"
         alt="The wonderfully cheap and cheerful logic analyzer I picked up - an 8 channel 20mhz jobby"/>
</figure>

<p>This time however I bit the bullet and bought the cheapest logic analyzer I
could. I had no idea how to use the thing when it arrived, but I discovered that
Pulseview is a piece of software that can be used for logic analysis. I
connected the SDA and SCL pins of my desk up to my logic analyzer, connected it
to my computer, fired up Pulseview, and clicked around a bit. Almost
immediately, I figured out how to “sample” the data the logic analyzer was
getting, as well as that Pulseview includes a super handy I2C decoder built
right in!</p>
<p>So, I sampled, and lo and behold, roughly what I’d been observing with the esp32
sniffing code was visible here too! Short, sporadic bursts of data. Again, sorry
for the photos, you might want to right click and open them in a new tab so you
can arbitrarily zoom them!</p>
<figure><img src="/posts/desksniffer/pulseview1.png"
         alt="A screenshot from Pulseview showing bursts of i2c communication"/>
</figure>

<p>Zooming in, even more success! Comparing what my code was outputting to what was
being seen on the logic analyzer, I could see the same addresses and the same
data packets. Incredible. It seemed that in my particular case, each address
received exactly one byte of data, and that the NACK/ACK bits didn&rsquo;t seem to
make a lot of sense. I wonder if this is what the creators of the AIP650 meant
when they said it was ‘i2c-like’.</p>
<figure><img src="/posts/desksniffer/pulseview2.png"
         alt="A screenshot from Pulseview showing a single burst of communication, showing data being sent to at least 5 i2c devices"/>
</figure>

<p>I <em>still</em> had no idea what any of this meant, so I flailed about for a bit.
After a bit of thinking, I theorised that perhaps the AIP650 chip was not “one”
i2c device, but instead was at least three, one per segment. That way, the
microcontroller that was asking it to display stuff could update each segment as
it needs to. It kind of made sense. A segment of the LCD consists of seven
segments, along with a period that appears beside that segment. That’s 8 bits of
information, so maybe each segment is linked to a specific bit in the byte.</p>
<p>To test that theory, I got the rightmost segment to display a zero, and sampled
using the logic analyzer. I dumped the data out of Pulseview, and proceeded to
make the segment display a 1, rinse and repeat all 9 digits. I wrote all the
data I got into a spreadsheet (this took a while) in the hopes I could spot a
pattern.</p>
<figure><img src="/posts/desksniffer/desk-sheet.png"
         alt="A spreadsheet showing the 8 bit data packets recieved for each i2c address while the display was displaying a known value"/>
</figure>

<p>This allowed me to confirm that a specific pattern of bits corresponded to the
numbers 0 through 9, and also allowed me to identify which i2c address
corresponded to which position on the display (the digit), as well as allowing
me to identify that the most significant bit in the byte corresponded to the
period portion of the digit. This is decent progress, I thought!</p>
<p>What it didn’t do however was tell me which bit corresponded to which individual
segment of a digit. Figuring that out was kind of like solving a Sudoku. I sat
there with a sheet of paper, drawing the digits as segments 0 through 9, trying
to find the commonalities.</p>
<figure><img src="/posts/desksniffer/digitdiag.png"
         alt="A diagram showing the segments of a single LCD digit labelled with letters A through G, with a period labelled DP"/>
</figure>

<p>As an example of what this particular puzzle took, let&rsquo;s work through
identifying two segments of a digit. We see that when the digit <code>1</code> is present
on the LCD, the binary <code>00000110</code> is present. That must mean that the 6th and
7th bit correspond to the segments <code>B</code> and <code>C</code>, but we don’t know which is which. To
determine which, we now have to find a digit which only had either <code>B</code> or <code>C</code>
illuminated.</p>
<p>The digit <code>5</code> proves to be a good candidate here, since <code>C</code> is illuminated, but
<code>B</code> is not. Looking at the data we collected, <code>5</code> seems to be represented by
<code>01101101</code>. The 6th bit is 1, the seventh is 0, meaning that the 6th bit must
then be <code>C</code>, and <code>B</code> must therefore be represented by the 7th bit. Continue this
process of deduction all the way and you’ll eventually figure out which bit
represents which segment. In my case, each bit in the array represented the
following segment:</p>
<ul>
<li>Position 0: DP</li>
<li>Position 1: G</li>
<li>Position 2: F</li>
<li>Position 3: E</li>
<li>Position 4: A</li>
<li>Position 5: C</li>
<li>Position 6: B</li>
<li>Position 7: D</li>
</ul>
<p>Now that I knew what the binary data meant, I was finally ready to decode it. I
wrote some code that would parse each I2C frame in the buffer into the address
portion to figure out which segment was being written to, and then parse the
data portion into a character. I don’t have the code I wrote back then, but
here’s the most <a href="https://github.com/kn100/desksniffer/blob/master/src/aip650decoder.cpp">recent revision showing how we turn the data portion into a digit</a>, along with the <a href="https://github.com/kn100/desksniffer/blob/38bc83aec1b2d621fa62d38e1a7db19cd8baad45/src/deskheight.cpp#L100">code that parses through the buffer</a></p>
<p>Next up came actually raising and lowering the desk. I’d already done this sort
of thing before, having <a href="https://www.printables.com/model/638432-wifi-control-mod-for-ugreen-switching-usb-hub">previously tasked an ESP32 with pressing the button on
my USB switching
hub</a>
so this part was going to be relatively simple.</p>
<p>All you need to do really is take a transistor, connect the collector and
emitter to your switch contacts, along with connecting the base to one of your
ESP32s GPIO pins (through a resistor to limit current, 10k will do!). Then, in
your ESP32 or Arduino code, all you need to do is configure your pin as an
output pin, ie: <code>pinMode(yourPinInt, OUTPUT);</code>, and then when you want to press
the button, you just bring the pin high by using <code>digitalWrite(yourPinInt, HIGH);</code>. To stop pressing the button, you bring the pin low
<code>digitalWrite(yourPinInt, LOW);</code>.</p>
<h1 id="the-electronics-assembly">
  <a class="Heading-link u-clickable" href="/desksniffer/#the-electronics-assembly">The electronics assembly</a>
</h1>
<p>On my particular standing desk, the switch contacts consisted of an outer and
inner ring. I determined that the outer ring was positive 5v, and was common
across the switches. The inner ring however was not common. Therefore, if I
wanted my desk to go up or down, I needed to bridge ANY of the outer rings to
the center contact of the up and down switch. I firstly tested my design on a
Breadboard:</p>
<figure><img src="/posts/desksniffer/breadboard.webp"
         alt="A photo of a breadboard that shows two transistors, attached to two resistors, attached to an ESP32. The wiring is chaotic."/>
</figure>

<p>After validating this worked, I soldered together a protoboard. I firstly
attached my ESP32. I then soldered two resistors to D33 and D32, and then
connected the center pin of both transistors (the base) to each resistor. I
connected the collectors of both transistors together (the leftmost leg, with
the flat side of the transistor facing you), and connected those legs to the
outer ring of one of the switches. I then connected each Emitter leg to the
center ring of the up and down rings.</p>
<p>I also soldered <code>D12</code> and <code>D13</code> to the <code>SDA</code> and <code>SCL</code> pins on the AIP650 present on the
desk controller, along with a wire to <code>VIN</code> which would later supply 5 volts,
along with a ground pin.</p>
<figure><img src="/posts/desksniffer/pbfront.webp"
         alt="A photo of the front of a protoboard with the same wiring as the breadboard"/>
</figure>

<p>I also attached wires to <code>D15</code>, <code>D2</code>, and <code>D4</code>, along with a second ground connection</p>
<ul>
<li>which I will later attach to three switches, allowing me to have buttons on
the outside of my new controller to control the desk manually, as I could
before. I didn’t bother connecting any of the other buttons of my desk to the
ESP32 since I didn’t care about the functionality they exposed (presets and the
such).</li>
</ul>
<figure><img src="/posts/desksniffer/pbback.webp"
         alt="A photo of the back of a protoboard with the same wiring as the breadboard"/>
</figure>

<p>Once all this was complete, and it continued to work, it was time to write some code.</p>
<h1 id="the-code">
  <a class="Heading-link u-clickable" href="/desksniffer/#the-code">The code</a>
</h1>
<p>My first revision of the code was a rats nest. I have no idea what I am doing in
C++ (I’m a Golang/Ruby baby!), but its method of operation was very simple. It
had two main variables responsible for control, the <code>currentHeight</code>, and
<code>requestedHeight</code>. If they were not equal, the code would press the button that
would cause <code>currentHeight</code> to move in the direction of <code>requestedHeight</code>, and
release it once they were equal. Can you see the problem with this technique?</p>
<p>If not, no worries, I clearly didn’t see it coming either, and I really should
have, given it&rsquo;s a very common problem in DevOps land. We regularly deal with a
concept called hysteresis, which is effectively a lag between an input and a
desired output. If you&rsquo;re more familiar with servers, maybe this example will
help. Imagine you scale the number of replicas in a particular deployment based
on a really simple metric like CPU utilisation. You might have some sort of rule
like “If the CPU utilisation average hits 80%, add a replica. If it is below
80%, remove a replica. Then, when your clever autoscaler recognises that the
average utilisation has exceeded 80%, it dutifully adds a replica. The
utilisation then drops below 80%, and a replica gets removed, bringing the
utilisation back up over 80%. This cycle repeats ad nauseum until you just use
an autoscaler written by someone else.</p>
<p>Well friends, I had hysteresis in the form of a 2.5m long oscillating vibrating
desk. The code frantically tried its best to make <code>currentHeight</code> equal
<code>requestedHeight</code>, but all it did was cause my desk to oscillate up and down
frantically until it eventually overheated and refused to do anything for around
an hour after this event.</p>
<p>As hysterically funny as I found this, I really didn’t want to break my desk, so
I went back to the code and tried to fix this.</p>
<p>The fix I came up with was stupid, but it works. The ‘proper’ solution is
probably a <a href="https://en.wikipedia.org/wiki/Proportional%E2%80%93integral%E2%80%93derivative_controller">PID
controller</a>,
but the solution I came up with is far simpler to write and works perfectly, so
I don’t really care. I just implemented a form of crude PWM on the button
pushes! Conceptually, instead of holding the button until we see the target
height, we instead hold the button until we are near the target height, at which
point we start rapidly pressing and depressing the button, which has the effect
of causing the desk to move more slowly. We keep doing this until we reach the
target height, and then we’re done, without overshooting or undershooting the
target.</p>
<p>Something else I learned the hard way is that my desk controller really does not
appreciate having its button pressed and depressed at the clock speed of the
ESP32, and it gets really confused and sad. This was simple to resolve though
just by modifying the code to press and depress the button with a 50ms cycle
time.</p>
<p>Through all this, I ended up with some code where I could program the ESP32 with
a given height, and after I reflashed the ESP32, it would command the desk to a
particular height.</p>
<p>I obviously wanted a way of setting this value arbitrarily, so I set up a
simple HTTP web server that would do this for me, hosted on the ESP32. It sits
there, waiting for requests, and if it receives a height change command, it does
exactly as I described above and it worked beautifully.</p>
<h1 id="3d-printing-an-enclosure">
  <a class="Heading-link u-clickable" href="/desksniffer/#3d-printing-an-enclosure">3D printing an enclosure</a>
</h1>
<p>CAD modelling is something I also have no professional experience with. I’ve
been teaching myself over the last few months, and decided this was the perfect
opportunity to model something relatively complex. What I wanted was a place to
put the original controller board so the LCD was still visible, spots to put 3
real mechanical keyboard switches (yes, the kind you find in a keyboard :P), and
a spot to screw in the protoboard construction documented above.</p>

<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
  <iframe src="https://www.youtube.com/embed/GkeDahJedlc" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" allowfullscreen title="YouTube Video"></iframe>
</div>

<p>I came up with this. Beauty is in the eye of the beholder, I guess! I threw the
job to my trust Sovol SV07 and waited a few hours. It turns out I designed
something that is a nightmare to print, but the printer did an admirable job
even with my ridiculous design. It&rsquo;s impressive what modern 3D printers are
capable of!</p>
<p>I then assembled everything pretty much as described, just with the addition of
3 mechanical keyboard switches, and it all went together mostly as designed. The
print quality is frankly terrible. This was down to a misconfiguration of the
printer, as well as the fact that this is a fairly difficult to print model. I
was also being impatient and printing it at ludicrous speeds. 3D printing is a
hobby that really benefits from patience, something I sometimes lack.</p>
<p>I have not released the STL for this particular enclosure, because it is an
absolute nightmare to print, and has some design errors I haven&rsquo;t addressed. If
you really want it for some reason though, I will give it to you. Just ping me
on Mastodon.</p>
<figure><img src="/posts/desksniffer/result1.webp"
         alt="The resultant enclosure, showing the protoboard from above as well as the original circuit board installed"/>
</figure>

<figure><img src="/posts/desksniffer/result2.webp"
         alt="The top cover, installed"/>
</figure>

<figure><img src="/posts/desksniffer/result3.webp"
         alt="Showing the controller powered up"/>
</figure>

<h1 id="im-finished-right">
  <a class="Heading-link u-clickable" href="/desksniffer/#im-finished-right">I&rsquo;m finished, right?</a>
</h1>
<p>Great! Install and done right? I thought so too. I installed it, and revelled in telling anyone who would listen that now desk go up and down when I send HTTP request to it! Cool right? <em>RIGHT?!</em>. It worked great.</p>
<p>Until disaster struck. A few hours after I installed it, my desk started going up on its own. I was happily coding away, and it started going up totally on its own. No command. WTF?!</p>
<p>I frantically unplugged it and started trying to think about why this happened.
I racked my brain, but I couldn’t think of any reason. Through sheer luck it
happened a second time while I was watching the serial output from the ESP32. I
had it logging out the state of the <code>currentHeight</code> and <code>requestedHeight</code>
variables if they changed, and the <code>currentHeight</code> randomly changed from <code>720</code>
to <code>1520</code>! I had focussed all my thinking about reasons why <code>requestedHeight</code>
might change. I hadn&rsquo;t considered the actual height of the desk was being
misread.</p>
<p>It turns out, writing your standing desk controller the same way you write
reconciliation loops in Kubernetes-land is not a great approach. It also turns
out, that just by installing your project in an enclosure, you can introduce a
brand new problem that was not present previously.</p>
<p>Enough foreshadowing, the problem was pretty obvious in hindsight. Sniffing I2C
is not a perfect art. Electromagnetic interference can cause you to incorrectly
read a 0 as a 1, or a 1 as a 0. In my case, if this happens, in the best
case, we read an invalid I2C address, which just means we ignore that frame of
data since we only care about a subset of i2c addresses (the ones that
correspond to each portion of the LCD). In the next best case, the actual data
portion gets corrupted somehow, and we get an arrangement of segments that does
not correspond to a valid digit. In this case, we again just ignore that
particular frame, and nothing bad happens. The most insidious cases though are
where a data frame gets corrupted, and <strong>we end up reading the wrong digit</strong>.</p>
<p>Given my code works like a reconciliation loop, it notices that the current
height does not equal the previously requested height, and frantically tries to
correct for that; but of course, it’s reading invalid data, so desk go up even
though I didn’t ask it to. Oh noooo.</p>
<p>I have made this situation extremely unlikely by adding a number of safety
checks:</p>
<ul>
<li>If a new currentHeight comes in that is substantially different to the
previous reading, we assume that new reading was invalid. Usually, the next
frame of data is read correctly, so this catches most cases.</li>
<li>If a new currentHeight comes in that is out of bounds (ie, is a value the desk
should never display), we just ignore it. This happens quite frequently,
since one of the most common errors I’ve seen is reading the first digit as a
‘1’ instead of a ‘7’ (a single bit different!).</li>
<li>We only move if we have received a command to do so. CurrentHeight can change
however it wants outside of a move command, we don’t care, unless the user
has issued a move request. Once the desk has reached its requested height, we
make sure the desk has stopped moving, and then stop caring about
currentHeight changes until the next request comes in.</li>
</ul>
<p>This has resolved that specific problem! It’s always good to remember to
anticipate your hardware misbehaving and adding basic sanity checks to your
code.</p>
<h1 id="the-code-today">
  <a class="Heading-link u-clickable" href="/desksniffer/#the-code-today">The code today</a>
</h1>
<p>I reiterate from earlier, I do not really know C++/C or hardware, so this code
is probably a litany of sins. Correct them in PRs if you like, I might even
flash them to my desk, because those are words I can say without sounding insane
now! So, when reviewing this code, bear that in mind.
<a href="https://github.com/kn100/desksniffer/">https://github.com/kn100/desksniffer/</a></p>
<h1 id="but-why">
  <a class="Heading-link u-clickable" href="/desksniffer/#but-why">But why?</a>
</h1>
<p><a href="/why">Read this.</a></p>
]]></description>
    </item>
    
    <item>
      <title>Posts</title>
      <link>http://kn100.me/posts/</link>
      <pubDate>Sun, 21 Apr 2024 00:00:00 -0500</pubDate>
      
      <guid>http://kn100.me/posts/</guid>
      <description><![CDATA[]]></description>
    </item>
    
    <item>
      <title>A Brief Aside on &#34;Why&#34;</title>
      <link>http://kn100.me/why/</link>
      <pubDate>Sat, 13 Apr 2024 00:00:00 -0500</pubDate>
      
      <guid>http://kn100.me/why/</guid>
      <description><![CDATA[<p>Sometimes I find myself spending lots of time on personal hobby projects that
appear to have very little real world purpose to others. Those I share the
project with ask me why I bothered. Either that or they nervously smile and
change the topic. As to the question of why, I understand the impulse to ask it.
I too have heard about projects that lead me to ask the same question. In the
past, I’ve struggled to answer this question in a way that wasn’t an attempt to
exaggerate the utility of the thing I was working on, or to promise some future
utility “because it’s cool! This will one day open up the possibility of X for
me, and it solves problem Y&hellip;”. I have been thinking about this a lot recently,
and think I can finally answer the question in a coherent manner.</p>
<p>I’ve been working on a hardware project that involved connecting my standing
desk to the internet. If I describe that project in pure utilitarian terms, its
utility can be boiled down to “I want my desk to go up when I make a HTTP request to a
specific endpoint”. How useful is this, really? Isn’t the previous solution of
just leaning over to the right around 6 inches and pressing the button for a few
seconds convenient enough for me? This is a perfectly reasonable question - if
you assume my goal was to solve a real problem that mattered.</p>
<p>In the last few years, I’ve found that my love of tech has been dying a bit.
Earlier in my life, I’d constantly experiment with Linux distributions, window
managers, programming languages, text editors, hardware - really whatever was
interesting that day. Spending the day compiling my kernel to test some silly
tweak was never an issue. In the last couple years however I’d noticed that this
experimentalist nature in me had died. I used a completely conventional Linux
distro. I didn&rsquo;t even bother to change the wallpaper. I had a bone stock phone.
Electronics experimentalism was limited to projects that I could justify as
having some real utility. If they weren’t useful, why would I waste my time on
them? I learned Golang - and fell in love with its very utilitarian “get shit
done” nature. No tabs versus spaces wars, no nine ways of doing the same thing,
all of which are somehow not best practice according to somebody somewhere. I
grew to dislike working in almost any other language.</p>
<p>More recently, that experimentalism seems to have bloomed in me again. I think
that what originally killed it was a marriage to utility - the idea that
everything I build should have some inherent useful output. Maybe I envision
turning it into a startup one day. Maybe it solves a problem that others care
about? Why experiment with a different Linux distribution when I can get by just
fine with the one I have? Isn&rsquo;t that just a total waste of time? Why bother
solving some tiny niggle with a standing desk through weeks of research and
learning - when the time could be better spent watching Star Trek? Maybe I could
have just bought a different standing desk.</p>
<p>In addition, my love of tech is back in full force again. I&rsquo;m now trying
<code>NixOS</code>, a Linux distribution that bills itself <code>a reproducible, declarative and reliable operating system</code>. Some expressed ridicule when I mentioned this to
them. Isn&rsquo;t that just a waste of time? To some degree, they&rsquo;re right. Everything
I do on my new shiny NixOS install is possible on any other distro, and is
almost certainly possible on a Mac. What they probably didn&rsquo;t understand though
is that this wasn&rsquo;t a utilitarian decision. I didn&rsquo;t switch to NixOS with any
hope of improved productivity. I switched because I wanted to see what it was
all about. I <em>wanted</em> to struggle to figure it out. I wanted to see what the
benefits are. In that process, I have learned far more about how Linux works at
a low level than I have in my years just sticking with the known working thing.
The skills I acquired along the way will be helpful even if I decide to abandon
NixOS one day. I didn&rsquo;t switch because it was the prudent thing to do - I
switched for switchings sake.</p>
<p>I recently acquired a 3D Printer. Many people buy a 3D printer with the goal of
eventually turning it into a side gig - producing parts or whatever. I didn&rsquo;t. I
bought a 3D printer because I wanted to learn to model in 3D. I didn&rsquo;t have a
specific utilitarian goal in mind, but I felt that owning a 3D printer would
push me to learn to design things in CAD in a way that doing it without having a
way to make it physical would not. It did not improve my productivity in any
way. My day job in DevOps is about as far from CAD as one can get - and I&rsquo;m not
considering any sort of career change. I don&rsquo;t think I&rsquo;ll ever design something
that I intend to sell. I wanted to learn CAD because I thought it&rsquo;d be fun, so I
am, and I was right. It is fun, and it turns out 3D printers are hilariously
useful machines to have around if you are willing to spend the time to use them
properly. I didn&rsquo;t buy a 3D printer because it solved a particular problem, or
because I intend to become a product designer or artist. I bought it because I
wondered how it worked.</p>
<p>So, revisiting the standing desk project one more time. Why did I do it? One
skill I&rsquo;ve always wished I had was knowing how to &ldquo;sniff&rdquo; data from consumer
grade hardware so I could ship it off somewhere more useful. I&rsquo;ve always wanted
to develop some competency in C++ too - given it&rsquo;s a language I&rsquo;ve never had the
opportunity to really write any significant amount of it. I looked around my
desk, and decided that the nearest thing to me to had some data I could sniff
was actually my desk. What if I could make it go up and down from HTTP requests?
What if I could query its height from the internet? Thus, a project with very
little apparent utility turns into an amazing puzzle, with endless learning
opportunities. It involved learning some C++, how to operate a Logic Analyzer,
how to decode i2c, some basic electronics, there was even a fairly hilarious
reminder about hysteresis I&rsquo;ll cover in a future post.</p>
<p>This might be obvious to some. I believe the reason why experimentalism like
this captures my soul so completely when it does is because the goal is rarely
the point. It’s the exploration. The learning. The sheer joy at making something
work. The surprise when some previously insurmountable obstacle is surmounted.
The dopamine rush as a whole new world of possibilities opens up to you. The new
skills acquired along the way. The friends made when seeking help in random
internet forums. It turns out that sometimes, the journey taken is far more
important than the end goal.</p>
<p>I really hope I can keep feeling okay to taking my time working on things that
have no obvious point, because the real answer to the question of why, is why
not?</p>
<h2 id="other-writing">
  <a class="Heading-link u-clickable" href="/why/#other-writing">Other writing</a>
</h2>
<ul>
<li><strong><a href="https://sigbovik.org/">https://sigbovik.org/</a></strong> <em>the home of the Association for Computational Heresy</em> - I specifically recommend <a href="https://www.youtube.com/watch?v=JcJSW7Rprio">YT: Harder drives</a> as a good starting point. Imagine the worst storage mediums you can think of, and then marvel at the fact that these are somehow worse.</li>
<li><strong><a href="http://antirez.com/news/123">http://antirez.com/news/123</a></strong> - Antirez on the value of projects with “Hack value”.</li>
<li><strong>Hackers: Heroes of the Computer Revolution - Steven Levy</strong> - A fantastic book looking back at early hacker culture</li>
</ul>
]]></description>
    </item>
    
    <item>
      <title>Getting the mutant home server gaming in a VM</title>
      <link>http://kn100.me/erying-11800h-p2/</link>
      <pubDate>Sun, 03 Sep 2023 00:00:00 -0500</pubDate>
      
      <guid>http://kn100.me/erying-11800h-p2/</guid>
      <description><![CDATA[<p>This post is the second part in a series I am working on. See the first part here: <a href="https://kn100.me/erying-11800h/">Home Lab Upgrades: Why This Mutant Motherboard/CPU Combo Could Be the Perfect Solution</a>)</p>
<p>The biggest most exciting use case for me with my new home server was a virtualised gaming environment. I&rsquo;ve always wanted a living room gaming PC, but didn&rsquo;t want to have a PC just for that purpose. I&rsquo;ve had a home server of some sort for the last couple years, usually running some Docker containers, and I always wondered if one day, I could run Windows in a VM, with a graphics card. I can confirm that, yes, it is possible, and it works absolutely great. Before I discuss how to set this up, Let&rsquo;s discuss what it&rsquo;s like.</p>

<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
  <iframe src="https://www.youtube.com/embed/_fgFSnAqSYM" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" allowfullscreen title="YouTube Video"></iframe>
</div>

<h1 id="whats-it-like">
  <a class="Heading-link u-clickable" href="/erying-11800h-p2/#whats-it-like">What&rsquo;s it like?</a>
</h1>
<p>In short, it&rsquo;s great! In my setup, I&rsquo;ve put the server in a cupboard, and have run a rather long fiber optic HDMI cable from the cupboard out to a television I have in my living room. I then control it using a small media centre style keyboard and mouse, as well as an old Xbox 360 controller to use when gaming. I&rsquo;ve configured the windows VM to automatically start Steam Big Picture, and from there, I just select a game to play with the Xbox controller, and the game launches, and runs with near native performance. The media centre keyboard/mouse is mainly just to be able to click on dialog boxes and the such. So far, I&rsquo;ve successfully gotten the following games to run with zero extra effort:</p>
<ul>
<li>Red Dead Redemption 2 + a bunch of mods</li>
<li>Cyberpunk 2077</li>
<li>Portal 2</li>
<li>Jackbox Games (I mean, these run on a potato, but it&rsquo;s still nice!)</li>
<li>Far Cry 6</li>
</ul>
<p>I&rsquo;ve not really used it for any multiplayer games - and one form of trouble I expect in the future is bullshit DRM causing problems. Games that use anti-cheat I am guessing are not going to be too happy running in a virtualised environment, so I wouldn&rsquo;t necessarily recommend this setup for that - but if you mostly play single player campaigns - or you&rsquo;re willing to engage in a bit of&hellip; ✨Creativity✨ in how you get your games running - you should have few to no problems.</p>
<p>The benefits of this setup are great. There is absolutely no console to have to hide in my living room and no screaming fan to enjoy the sound of. It&rsquo;s like Google Stadia, except without the latency, without the quality penalty, and most importantly, it still exists.</p>
<p>One slight pain point - This is a HUNGRY vm. You&rsquo;re going to want to have plenty of RAM to throw at it, and you&rsquo;ll want more than if you just had a Windows install on the machine natively, since you&rsquo;ll need some for Proxmox, and some for all the other stuff you&rsquo;ll do with Proxmox. I went with 32GB - and give 16GB to Windows. Even this feels cramped some days.</p>
<p>Another slight pain point - You need some way to start and stop the VM! I came up with what I thought was a rather nice solution for this. My wife and I both use Telegram, and Telegram bots are an absolute joy to write. I wrote a simple Go service that runs directly on the Proxmox host (managed by Systemd), that spins up a web server. When it receives a request with a ✨Super Secure Secret Key✨, it executes <code>qm start &lt;vmid&gt;</code> - which starts the virtual machine. I then wrote a Telegram bot which only speaks to my wife and I (using Telegram as the authentication mechanism). If it receives a command to start the VM, it starts the VM. This Telegram bot of course lives on my Proxmox node inside a container, and therefore is able to make a web request to that Go service running on the Proxmox host I mentioned earlier. You could write these as one service for sure, but my Telegram bot does a lot more than just this for me, hence the separation. I haven&rsquo;t shared the code for these yet because it&rsquo;s terrible, it&rsquo;s really nothing special.</p>
<p>This setup means that I can sit on my couch, fire up a telegram chat, and press a button, and about a minute later I am sitting in front of the Steam Big Picture UI, ready to play some games. I then use the Xbox controller to navigate the Big Picture UI, and I game. It&rsquo;s probably as close to a console experience as one could have.</p>
<p>Final pain point - If you shut down the Windows VM, the GPU gets left in a really weird high power state, and produces a lot of heat considering it is literally doing nothing. It actually seems to run cooler while the VM is running. I did not want to rely on leaving a Windows VM running all the time, so I created the worlds most sad Linux VM. It is a really stripped down Ubuntu VM, configured exactly the same as my Windows VM, except it is given 1GB of ram and 1 core, as well as having absolutely no network access. It runs no services, it hosts no websites, it plays no games, it doesn&rsquo;t even have the ability to be accessed in any way. Its only task is to hold the GPU. That same Go server I described above actually constantly polls the status of the windows VM with <code>qm status &lt;vmid&gt;</code>, and if it isn&rsquo;t running, it&rsquo;ll start the GPU holder VM. That then spins up, initialises the GPU, and does absolutely nothing else. It sits there, lonely, and sad. When I request the Windows VM start, the GPU holder VM unceremoniously gets stopped with <code>qm stop &lt;vmid&gt;</code>, and the windows VM gets started. A neat solution that keeps the power consumption of the GPU as low as possible. Better solutions to this are definitely possible, but I like my hacky solution.</p>
<h1 id="how-to-set-this-up">
  <a class="Heading-link u-clickable" href="/erying-11800h-p2/#how-to-set-this-up">How to set this up:</a>
</h1>
<p>I had no idea before I set this up whether it would work or not (see my previous post: <a href="https://kn100.me/erying-11800h/">Home Lab Upgrades: Why This Mutant Motherboard/CPU Combo Could Be the Perfect Solution</a>). There&rsquo;s no technical reason why GPU passthrough shouldn&rsquo;t work. It&rsquo;s a relatively well established technology these days. It can be finnicky, but especially on the AMD GPU side things have worked pretty well for a while, bar some strange <a href="https://github.com/inga-lovinde/RadeonResetBugFix">hardware bugs</a> affecting earlier AMD graphics cards. However, as I discussed in my previous post, the platform I picked is anything but typical. It is a CPU that should never have seen a consumer, strapped to a motherboard that would prefer to not exist, running a hacked bios holding it all together, sold on Aliexpress at an extremely discounted price. The odds definitely needed to be in my favour for everything to hold together well enough for this to work. I already had an AMD Radeon RX-6700xt from my main desktop, so I decided to test with that.</p>
<h2 id="bios-settings">
  <a class="Heading-link u-clickable" href="/erying-11800h-p2/#bios-settings">BIOS Settings</a>
</h2>
<p>After installing the GPU, I entered the BIOS of the Erying board, in order to ensure the settings were set up as needed. These are the settings that you&rsquo;ll need to tweak, should you decide to do this. Do this before you attempt to set anything up.</p>
<ul>
<li>Advanced &gt; CPU Configuration &gt; Enable Intel (VMX) Virtualisation Technology - This is what allows Proxmox to do virtualisation on your CPU</li>
<li>Advanced &gt; Graphics Configuration &gt; Enable VT-D (This is essentially IOMMU - tech that lets you virtualise hardware like your GPU)</li>
<li>Advanced &gt; Graphics Configuration &gt; Disable Internal Graphics (Just to keep things simple, you can re-enable it later!)</li>
<li>Advanced &gt; Graphics Configuration &gt; Enable Above 4GB MMIO BIOS assignment</li>
<li>Advanced &gt; PCI Subsystem Settings &gt; Enable Above 4G Decoding</li>
<li>Advanced &gt; PCI Subsystem Settings &gt; Disable Re-Size BAR Support (This caused crashing for me)</li>
</ul>
<h2 id="proxmox-initial-setup">
  <a class="Heading-link u-clickable" href="/erying-11800h-p2/#proxmox-initial-setup">Proxmox initial setup</a>
</h2>
<p>When you boot into proxmox next, it will boot and display a shell on your GPU. We don&rsquo;t want this. We want Proxmox to ignore the GPU completely, so that a virtual machine can initialise it. To this end, ssh into your Proxmox node for the next steps. I should note for these steps I am heavily summarizing the excellent guide you can find here: <a href="https://pve.proxmox.com/wiki/PCI_Passthrough">Proxmox PCI Passthrough</a>, and here: <a href="https://www.reddit.com/r/homelab/comments/b5xpua/the_ultimate_beginners_guide_to_gpu_passthrough/">cjalas: The Ultimate Beginner&rsquo;s Guide to GPU Passthrough</a> - taking only the steps I needed.</p>
<p>Firstly, you&rsquo;ll want to modify /etc/default/grub, specifically the line starting <code>GRUB_CMDLINE_LINUX_DEFAULT</code>. Add it if it doesn&rsquo;t exist.</p>

<pre tabindex="0"><code>GRUB_CMDLINE_LINUX_DEFAULT=&#34;quiet intel_iommu=on iommu=pt pcie_acs_override=downstream,multifunction initcall_blacklist=sysfb_init video=simplefb:off video=vesafb:off video=efifb:off video=vesa:off disable_vga=1 vfio_iommu_type1.allow_unsafe_interrupts=1 kvm.ignore_msrs=1 modprobe.blacklist=radeon,nouveau,nvidia,nvidiafb,nvidia-gpu,snd_hda_intel,snd_hda_codec_hdmi,i915&#34;</code></pre>
<p>Then, run <code>update-grub</code>.</p>
<p>You&rsquo;ll then want to add the following lines to your /etc/modules file:</p>

<pre tabindex="0"><code># Modules required for PCI passthrough
vfio
vfio_iommu_type1
vfio_pci
vfio_virqfd</code></pre>
<p>Next, you need to blacklist your GPU. This means that Proxmox will no longer attempt to initialise your GPU, and it will effectively become useless until we pass it to a VM.</p>

<pre tabindex="0"><code>echo &#34;blacklist amdgpu&#34; &gt;&gt; /etc/modprobe.d/blacklist.conf
echo &#34;blacklist radeon&#34; &gt;&gt; /etc/modprobe.d/blacklist.conf</code></pre>
<p>Next, you want to add your GPU to VFIO. Run <code>lspci -v</code> and hunt for the output that relates to your GPU. Here&rsquo;s what it looks like for mine:</p>

<pre tabindex="0"><code>03:00.0 VGA compatible controller: Advanced Micro Devices, Inc. [AMD/ATI] Navi 22 [Radeon RX 6700/6700 XT/6750 XT / 6800M/6850M XT] (rev c5) (prog-if 00 [VGA controller])
        Subsystem: Sapphire Technology Limited Sapphire Radeon RX 6700
        Flags: bus master, fast devsel, latency 0, IRQ 139, IOMMU group 17
        Memory at 4000000000 (64-bit, prefetchable) [size=256M]
        Memory at 4010000000 (64-bit, prefetchable) [size=2M]
        I/O ports at 4000 [size=256]
        Memory at 50000000 (32-bit, non-prefetchable) [size=1M]
        Expansion ROM at 50100000 [disabled] [size=128K]
        Capabilities: [48] Vendor Specific Information: Len=08 &lt;?&gt;
        Capabilities: [50] Power Management version 3
        Capabilities: [64] Express Legacy Endpoint, MSI 00
        Capabilities: [a0] MSI: Enable+ Count=1/1 Maskable- 64bit+
        Capabilities: [100] Vendor Specific Information: ID=0001 Rev=1 Len=010 &lt;?&gt;
        Capabilities: [150] Advanced Error Reporting
        Capabilities: [200] Physical Resizable BAR
        Capabilities: [240] Power Budgeting &lt;?&gt;
        Capabilities: [270] Secondary PCI Express
        Capabilities: [2a0] Access Control Services
        Capabilities: [2d0] Process Address Space ID (PASID)
        Capabilities: [320] Latency Tolerance Reporting
        Capabilities: [410] Physical Layer 16.0 GT/s &lt;?&gt;
        Capabilities: [440] Lane Margining at the Receiver &lt;?&gt;
        Kernel driver in use: vfio-pci
        Kernel modules: amdgpu</code></pre>
<p>Note the numbers in the beginning, <code>03:00.0</code> in my case. You&rsquo;ll next want to run <code>lspci -n -s 03:00</code>, in order to determine the vendor ID for the GPU. It may output two - one will be the GPU, the second will be the audio subsystem of your GPU. You&rsquo;ll want to create the file <code>/etc/modprobe.d/vfio.conf</code> with these IDs, like so</p>

<pre tabindex="0"><code>root@kevs-server:/etc/modprobe.d# cat vfio.conf 
options vfio-pci ids=1002:73df,1002:ab28 disable_vga=1</code></pre>
<p>Then run <code>update-initramfs -u</code>.</p>
<p>Finally, fully reboot your system, and check the monitor you&rsquo;ve got attached to your system. As soon as the BIOS finishes loading, you should be left with a screen that says the following</p>

<pre tabindex="0"><code>Loading Linux &lt;whatever version of the kernel&gt; ...
Loading initial ramdisk ...
_</code></pre>
<p>If so, congratulations, you&rsquo;re ready to start configuring your VM!</p>
<h2 id="proxmox-vm-setup">
  <a class="Heading-link u-clickable" href="/erying-11800h-p2/#proxmox-vm-setup">Proxmox VM setup</a>
</h2>
<p>It&rsquo;s time to create your VM! Go ahead and hit the <code>Create VM</code> button in Proxmox. Don&rsquo;t start the VM until the guide suggests you should. Start off by giving it a nice name.</p>
<ul>
<li>OS: select your OS. I had a Windows 11 ISO, so I selected that. Ensure you also select Guest OS Type <code>Microsoft Windows</code>.</li>
<li>System: Machine should be set to <code>q35</code>, Bios to OVMF (UEFI). Tick the <code>Add EFI Disk</code> option and select somewhere to store the disk. Tick the <code>Add TPM</code> box and select the disk to store your TPM on.</li>
<li>Disks: This is up to you, but I went with SATA, but you probably want VirtIO SCSI for better performance. If you&rsquo;re running on an SSD, tick the SSD emulation box, and if your SSD supports Discard (It probably does!), tick the Discard box.</li>
<li>CPU: Again, up to you, but I recommend giving it virtually all the cores in your system - and for Type you probably want to select <code>Host</code>, although any will work.</li>
<li>Memory: Up to you, but I wouldn&rsquo;t recommend Ballooning unless you&rsquo;re really tight on RAM, given this a VM where you&rsquo;re going to want as much performance as possible. Ballooning just means the VM can request more RAM and get it, but it requires further setup. I didn&rsquo;t enable this.</li>
<li>Network: Up to you</li>
</ul>
<p>Next, it&rsquo;s time to pass your graphics card through to the VM. To do this, select your newly created VM from the left in Proxmox, and select the Hardware tab. Click the Add button, and select PCI Device. Select <code>Raw Device</code>, and select your GPU. Tick <code>All Functions</code>, <code>Primary GPU</code>, <code>PCI-Express</code>, and <code>Rom Bar</code>.</p>
<p>You are also free at this point to pass through some USB ports to your soon to be running Windows VM. I for example pass through a wireless keyboard/mouse dongle, and an Xbox controller dongle. Plug these into your machine if you haven&rsquo;t already, then select <code>Add</code>, and <code>USB Device</code>. I then suggest ticking <code>Use USB Port</code>, and selecting the ports they&rsquo;re plugged in to. This means you can in future plug other stuff into these ports and it&rsquo;ll just work. You can add more later, so no worries here.</p>
<p>Once you&rsquo;re done, you should end up with a setup that looks something like this:</p>
<figure><img src="/posts/erying2/proxmox-1.png"
         alt="A screenshot of the Proxmox UI showing the setup described above"/>
</figure>

<p>And finally, it is time to start the VM, and install Windows. You&rsquo;ll need to do it from a monitor you attach to the GPU, as well as with a keyboard/mouse you passed through to the VM earlier. I&rsquo;m sure you don&rsquo;t need help there!</p>
<p>The experience of others has not been as smooth as mine, so be warned should you follow my path here. People with literally identical setups to mine have failed - seemingly because some of the engineering sample CPUs are capable of it, whereas others are not. Real PC hardware will probably work more reliably.</p>
]]></description>
    </item>
    
    <item>
      <title>Home Lab Upgrades: Why This Mutant Motherboard/CPU Combo Could Be the Perfect Solution</title>
      <link>http://kn100.me/erying-11800h/</link>
      <pubDate>Mon, 17 Apr 2023 23:46:00 -0500</pubDate>
      
      <guid>http://kn100.me/erying-11800h/</guid>
      <description><![CDATA[<p>I&rsquo;ve always liked strange hardware. Odd prototype mobile phones, weirdo computer
parts that only exist because of Bitcoin mining etc. You name it, I enjoy looking at it and imagining the possibilities.
Recently, thanks to
<a href="https://www.youtube.com/watch?v=FR6AkUx-q8g">CraftComputing</a> on Youtube, I
became aware of a strange sort of motherboard/CPU combo that features an Engineering Sample CPU that purports to be an Intel 11800h. The retail version of this chip would have originally been destined to go into a laptop. Here however, it was somehow mounted to a desktop motherboard.</p>
<h2 id="what-is-an-engineering-sample-mutant">
  <a class="Heading-link u-clickable" href="/erying-11800h/#what-is-an-engineering-sample-mutant">What is an Engineering Sample? Mutant?</a>
</h2>
<p>It turns out, CPU manufacturers like Intel churn out a fair few &ldquo;Engineering
Sample&rdquo; CPUs, which they then distribute to OEMs and ISVs so that they can test
their hardware or software against some soon to be released CPU. For whatever
reason, there appears to be a glut of these Engineering Sample CPUs from Intels
11th and 12th generation CPU product line, and they&rsquo;re now finding their way
into various strange products on Aliexpress, like this motherboard. I&rsquo;ve also seen
entire &lsquo;Mac Mini&rsquo; style computers on Aliexpress that claim to use these same chips.</p>
<p>These boards have been nicknamed &ldquo;mutants&rdquo; in the communities which already knew about them. I&rsquo;ve heard that these strange mutant boards usually find their way into countries where consumers cannot afford or do not have access to &ldquo;normal&rdquo; parts. I&rsquo;ve seen reviews of this board from Brazil and Russia, implying they&rsquo;re somewhat popular there. They&rsquo;re not a new concept as I thought, and have been seen before.</p>
<p>Why might we want such a motherboard, though? For starters, the price is pretty fantastic. For 220$, we get a board which features the following:</p>
<ul>
<li>A CPU equivalent to an Intel i7-11800h (8 cores, 16 threads, turbo frequency of 4.60 GHz, TDP of 35w)</li>
<li>4x SATA ports</li>
<li>PCI-Express x16 slot, PCI-Express x1 slot</li>
<li>1Gbit Ethernet</li>
<li>A NVME slot</li>
</ul>
<p>For 220 CAD, you&rsquo;d be hard pressed to find a CPU/Motherboard combination that performs as well as this board <em>should</em>. It&rsquo;s a great deal, theoretically.</p>
<p>Having a destined-for-mobile CPU is nice for other reasons too. It&rsquo;s low power! These mobile chips are optimised to run as fast as possible, in as small a power envelope as possible. For a home server use case, this is a desirable property, since this will lead to lower bills, and less heat to dissipate.</p>
<h2 id="why-would-i-want-one">
  <a class="Heading-link u-clickable" href="/erying-11800h/#why-would-i-want-one">Why would I want one</a>
</h2>
<p>?
For the past few years, I&rsquo;ve been hosting my own services using a &lsquo;home lab&rsquo; setup, which has gone through several evolutions. I started with an old netbook, then moved to Raspberry Pis, working my way through the generations of Pi, all the way to the Pi 4 8GB. I then added a consumer grade NAS to my setup, which I featured in my article about <a href="https://kn100.me/declouding-replacing-google-photos-part-1/">replacing Google Photos with a self hosted alternative</a>.</p>
<p>After a few months with the Pi 4, I got tired of managing an ARM machine. This isn&rsquo;t a slight against the Raspberry Pi. They&rsquo;re amazing little boards and are fantastic for a lot of use cases - but they&rsquo;re not perfect. Not all software is ready for ARM, and the hardware can be a little flaky if you use the wrong power supply. That flakiness can corrupt your MicroSD card, leaving you offline.</p>
<p>So, I decided to level up to a Tiny PC - the Lenovo M710q. These Tiny PCs are available for fairly cheap online second hand. I used this machine in my article about using OCR to <a href="https://kn100.me/taking-back-data-from-eufy/">extract data from a set of Eufy weighing scales</a>. This machine and NAS setup satisfied my needs for around a year, but eventually, I decided I wanted more.</p>
<p>For a lot of people who are considering how far to take their own home lab, I&rsquo;d suggest stopping here. This setup was mostly great for me! At the time, I had a fairly average internet connection (80mbit down, 20mbit up), and this setup managed to keep up with that fairly well. It was enough to host all the home services I wanted to host. If you were to purchase, say, a Synology NAS, instead of a Terramaster one, it would probably be fine. My Terramaster NAS however began to worry me. Users <a href="https://nvd.nist.gov/vuln/detail/CVE-2021-30127">including myself</a> were regularly finding security holes in the NAS, but the real cherry in the icing was discovering data corruption due to my choosing to format my drives as BTRFS. Turns out, there was some nasty bug in the Terramaster firmware which lead to data corruption for users who picked BTRFS. Terramaster of course provided no migration path away from BTRFS to EXT4 (the other supported filesystem) when they quietly announced that newer versions of this NAS firmware would <a href="https://forum.terra-master.com/en/viewtopic.php?t=2380">stop offering BTRFS as an option</a>, so I decided enough was enough. No more rinky dink NAS for me.</p>
<p>I have since moved to Canada, and now have significantly faster internet. This is where the shortcomings of mounting a NAS SMB share on another machine became apparent to me. For no reason at all, the NAS would entirely lock up when downloading stuff from the internet at more than 50MB/s. Transfers would just stop occasionally, and resume about 30 seconds later. Further comically awful security flaws in TOS were discovered, so I finally decided to action what I&rsquo;d previously decided, and got rid of the NAS + the mini machine. I started researching options for something better.</p>
<h2 id="enter-erying">
  <a class="Heading-link u-clickable" href="/erying-11800h/#enter-erying">Enter Erying</a>
</h2>
<p>I had some features I really wanted to try to get on the new home server. These were:</p>
<ul>
<li>Host a larger number of drives than 2, as my current 4TB mirrored array is getting pretty full</li>
<li>Be x86 - They&rsquo;re just easier to manage than ARM boards, in my opinion</li>
<li>Easily handle gigabit network traffic.</li>
<li>Offer the ability to go higher than Gigabit in future</li>
<li>Run Proxmox</li>
<li>(Stretch) GPU Passthrough to a virtual machine</li>
</ul>
<p>I&rsquo;d been thinking about this project for a little while, but finally decided to go ahead and do it when I spotted this Erying board. It ticked all of these boxes exceptt he one regarding GPU Passthrough. There were reports that some users (including CraftComputing) were unable to get this working. That was not an absolute necessity for me, so I took the plunge anyway. I placed the order, and anxiously awaited my motherboard.</p>
<figure><img src="/posts/erying/box.webp"
         alt="A photo showing a slightly beat up Erying Motherboard box."/>
</figure>

<p>The board arrived. A little beat up from delivery, but still mostly structurally intact. Hopefully, what&rsquo;s inside survived!</p>
<figure><img src="/posts/erying/backofbox.webp"
         alt="A photo showing the back of a slightly beat up Erying Motherboard box."/>
</figure>

<p>The back of the box makes a bunch of claims about the motherboard. I particularly enjoy the &ldquo;Anti-thunder Design (double protection)&rdquo;. Sounds extremely dramatic! Let&rsquo;s open it up and take a look!</p>
<figure><img src="/posts/erying/openbox.webp"
         alt="A photo showing the inside of an Erying Motherboard box, showing a motherboard packaged in an antistatic bag, a very basic looking IO shield, and a black SATA cable."/>
</figure>

<p>As promised, there&rsquo;s what looks like a motherboard, an IO shield, a SATA cable, and that&rsquo;s it! I didn&rsquo;t even get a manual. Let&rsquo;s take a closer look at the board.</p>
<figure><img src="/posts/erying/mobofront.webp"
         alt="The front face of the Erying motherboard"/>
</figure>

<p>Interesting! We have a somewhat unusual setup here around the CPU, where there appears to be a copper shim between the world and the CPU die itself. This&rsquo;ll be because mobile CPUs don&rsquo;t usually have Integrated Heat Spreaders like their Desktop CPU bretherenm and silicon dies are very fragile. There&rsquo;s also a metal plate around the copper shim to hold the it to the die at some pressure. The mounting holes for the CPU cooler appear to be LGA17XX style, so most CPU coolers will fit to the board with no issue. Nothing special is required in this department, given the low power usage of this CPU.</p>
<p>Other points that appear interesting to me are the extremely weedy VRM heatsinks surrounding the CPU socket. We&rsquo;ll measure those later. Other than this, everything looks in order.</p>
<figure><img src="/posts/erying/io.webp"
         alt="The IO ports on the Erying motherboard"/>
</figure>

<p>The IO is basic, but it&rsquo;s all there. You&rsquo;ve got your four USB 3 ports, two USB 2 ports, a Gigabit Ethernet port, 2x Displayport + 1x HDMI so you can use the onboard GPU, if you wish (this CPU features Intel HD 630 graphics). There&rsquo;s also audio jacks.</p>
<h2 id="the-build">
  <a class="Heading-link u-clickable" href="/erying-11800h/#the-build">The build</a>
</h2>
<p>I decided to throw it into a spare case I had, and upgraded some components in my main desktop to free up some parts for this build. It ended up being the following:</p>
<ul>
<li>DEEPCOOL Macube 100 case</li>
<li>DEEPCOOL Gammaxx 400S CPU Cooler with 4-Heatpipes (overkill, but the cheapest at the time!)</li>
<li>32GB Corsair Vengeance 3200MT/s RAM</li>
<li>Erying motherboard</li>
<li>Intel ES 0000 CPU which is pretty much an 11800h</li>
<li>Sapphire AMD Radeon 6700xt GPU</li>
<li>Sabrent Rocket 1TB NVME SSD</li>
<li>2x Toshiba N300 4TB Hard Drives</li>
<li>Seasonic Core GC-500w PSU</li>
</ul>
<figure><img src="/posts/erying/build.webp"
         alt="The build, partially completed."/>
</figure>

<p>The lack of manual posed a bit of a challenge, since I didn&rsquo;t know where to connect the power switch! They hadn&rsquo;t labelled the ports on the motherboard, nor was there any documentation on the internet I could find. Thankfully <a href="https://www.reddit.com/r/EryingMotherboard/comments/1211nps/looking_for_a_manual_or_at_least_how_the_front/">/r/EryingMotherboard</a> came to my rescue, and I was able to continue.</p>
<p>After I hooked it up to my TV for testing, I was somewhat surprised to see it boot! Then, I remembered that the SSD I used already had an OpenSuSE Tumbleweed install present, since I&rsquo;d migrated it from this SSD to a new one, so naturally it immediately booted to that.</p>
<figure><img src="/posts/erying/itboots.webp"
         alt="A photo of the assembled computer, connected to a television, which is showing a KDE Login screen."/>
</figure>

<p>Now I&rsquo;d confirmed the board worked, it was time to set it up properly, and get it into service!</p>
<h2 id="the-scary-bios-update">
  <a class="Heading-link u-clickable" href="/erying-11800h/#the-scary-bios-update">The Scary BIOS update</a>
</h2>
<p>The seller offered me a &ldquo;more powerful&rdquo; BIOS, but I had no idea what &ldquo;more powerful&rdquo; meant exactly. When I asked for further clarification, the seller directed me to a Microsoft Onedrive link that seemed a little sketchy, but I decided to take a risk and download it. The ZIP file contained instructions to flash an included BIOS, but they were in Chinese. I was able to translate them with the help of ChatGPT (how topical!). I followed the instructions and was presented with a screen of scrolling text that lasted about 10 minutes. Eventually, the board rebooted, and I was relieved to see that the BIOS had been successfully updated. It was a bit of a nerve-wracking experience, but I&rsquo;m glad it worked out in the end.</p>
<p>Let&rsquo;s take a look at how the BIOS looked before and after the flashing process. Let&rsquo;s first look at the before photo:</p>
<figure><img src="/posts/erying/oldbios.webp"
         alt="The standard blue background, white text BIOS everyone has seen a million times"/>
</figure>

<p>As yes, old familiar. Blue borders with large grey areas with black text. Incomprehensible settings. Press F10 to Save and Exit. Everybody who has tooled around with a BIOS has probably seen this one. How quaint. Now let&rsquo;s take a look at the &ldquo;more powerful&rdquo; BIOS.</p>
<figure><img src="/posts/erying/newbios.webp"
         alt="The standard blue background, white text BIOS everyone has seen a million times"/>
</figure>

<p>Whoa! Now there&rsquo;s LIGHTNING in the background that makes portions of text unreadable! That IS more powerful looking! I Love it. As to what other changes were made, I have no idea. Oh well!</p>
<h2 id="bios-configuration">
  <a class="Heading-link u-clickable" href="/erying-11800h/#bios-configuration">Bios configuration</a>
</h2>
<p>Through tooling around in the BIOS, I was able to get my RAM to run at a maximum of 2933 MT/s, but no higher. For some reason, this board would NOT let me go to 3200 MT/s no matter how much I messed around with memory timings, but whatever, no big deal. I also enabled Intel VT-d, Intel Virtualisation, and IOMMU - all technologies related to virtualisation that are essential for what I&rsquo;m about to do with this system. I did not however enable Resizable BAR, since this caused issues which I&rsquo;ll elaborate on later.</p>
<h2 id="proxmox">
  <a class="Heading-link u-clickable" href="/erying-11800h/#proxmox">Proxmox</a>
</h2>
<p>Next, I installed Proxmox on the system, but I&rsquo;m going to leave that fun for a second part to this blog post. See you then!</p>
]]></description>
    </item>
    
    <item>
      <title>How the Pixel 6 Pro charges - In Graphs</title>
      <link>http://kn100.me/pixel6pro-charging/</link>
      <pubDate>Sat, 31 Dec 2022 16:00:00 -0500</pubDate>
      
      <guid>http://kn100.me/pixel6pro-charging/</guid>
      <description><![CDATA[<p>I was bored, and was curious to know what the charging behaviour of my Pixel 6 Pro looked like. I decided to use a Ugreen Nexode 100w USB-C charging brick with a Macbook charging cable as the power supply, and left my phone in its case. I note this because I will in the future perform this same test with the phone artificially cooled to see how much of a difference temperature makes to charging rate. I picked the Ugreen Nexode PSU because it has the widest support for quick charging standards I&rsquo;ve ever seen. It supports all sorts of weirdo standards, and I highly recommend owning one. It supports (and I am sure this is not an exhaustive list):</p>
<ul>
<li>Huaweis&rsquo; weirdo Supercharge standard</li>
<li>USB PD 2.0 and 3.0</li>
<li>Qualcomm Quickcharge 2.0, 3.0, and 4.0</li>
<li>Programmable Power Supply (PPS) charging as used by various Samsung phones and laptops</li>
<li>Adaptive Fast Charging (AFC) as used by various other Samsung devices</li>
<li>Super Fast Charging (SFC) as used by various other more different Samsung devices,</li>
</ul>
<p>Every single device I&rsquo;ve plugged into this absolute unit of a power supply has charged as fast as I&rsquo;ve ever seen it charge, and its 3 USB C ports can even charge multiple laptops. <a href="https://www.amazon.co.uk/gp/search?ie=UTF8&amp;tag=kn100-21&amp;linkCode=ur2&amp;linkId=10bdf26aabcc71667fdb0fc8ca3e232d&amp;camp=1634&amp;creative=6738&amp;index=computers&amp;keywords=Ugreen%C2%A0Nexode%C2%A0Charger">Buy one from this here affiliate link</a>. Highly recommended.</p>
<p>I decided I&rsquo;d let the phone drain to roughly 10%, and then not touch it for a full charging session. It was placed in a room that is held pretty steadily at 22 degrees centigrade. I should also note this Pixel 6 Pro is only around 8 months old, but I&rsquo;d estimate the battery is a little more worn than usual due to some extremely heavy use it sustained while it was my only connection to the internet.</p>
<p>I quickly whipped up a terrible shell script that used <code>adb</code> wirelessly to log out the values. I can&rsquo;t take all the credit for the terribleness, it was actually authored by Chat GPT - my new favourite tool for quickly hacking up prototypes of terrible shell scripts. When it does the terribleness, I feel less bad about it!</p>

<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/bash
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">while</span> true; <span style="color:#66d9ef">do</span>
</span></span><span style="display:flex;"><span>  timestamp<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>date +%s<span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>  adb shell dumpsys battery | grep -Eo <span style="color:#e6db74">&#39;[0-9]+&#39;</span> | tr <span style="color:#e6db74">&#39;\n&#39;</span> <span style="color:#e6db74">&#39;,&#39;</span> &gt;&gt; battery.csv
</span></span><span style="display:flex;"><span>  echo <span style="color:#e6db74">&#34;</span>$timestamp<span style="color:#e6db74">&#34;</span> &gt;&gt; battery.csv
</span></span><span style="display:flex;"><span>  echo &gt;&gt; battery.csv
</span></span><span style="display:flex;"><span>  sleep <span style="color:#ae81ff">30</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">done</span></span></span></code></pre></div>
<p>One complete charging session later, I wished I&rsquo;d sampled more frequently than every 30 seconds, but the resolution was high enough to observe a couple interesting things. Firstly, a charge from 11% to 100% reported took 1 Hour, 43 Minutes and 35 Seconds. Internet reports suggest 2 hours for a full charge, so this seems to indicate it is charging as fast as it can.</p>
<p>Firstly, I decided to take a look at how the self reported charge level (in percent) compared to the temperature the battery reported.</p>
<figure><img src="/posts/pixelcharge/tempvslevel.png"
         alt="A graph showing Charge level vs Battery Temperature"/>
</figure>

<p>This graph showed something I was expecting. We can see at around 50% charge, the temperature of the battery peaks at around 34.7c. Then, the charging speed drops a bit, and the temperature drops.</p>
<p>Next up, I was curious how the battery voltage changed over time. I decided to compare it against the self reported level.</p>
<figure><img src="/posts/pixelcharge/levelvsvoltage.png"
         alt="A graph showing Charge level vs Battery Voltage"/>
</figure>

<p>Here, again, when the battery hits 50%, the battery voltage drops precipitously, indicating a drop in charging power. It then steadily rises to its observed maximum of 4.42v, and stops, indicating the cell is full. Another interesting detail we can see in this graph is the cells voltage keeps rising even as the phone reports itself 100% full, indicating that the phones own estimations were slightly off. I decided to dig deeper.</p>
<p>Next I graphed the mAh figure that the phone exposes against its voltage.</p>
<figure><img src="/posts/pixelcharge/mahvsvoltage.png"
         alt="A graph showing Battery mAh vs Battery Voltage"/>
</figure>

<p>With Li-ion cells such as the one we find inside the Pixel 6 Pro, we expect to observe a fairly steady voltage through a significant portion of the charge cycle, as these cells try their best to hold a high voltage for as long as possible before rapidly dropping when the cell is almost empty. This is only true when they&rsquo;re charged with a relatively constant current. Modern phones do not charge with a constant current however, and vary it in various ways to try to preserve the health of the battery and to control their heat output. We can however observe this behavior somewhat when the battery cell voltage hits roughly 4400mV.</p>
<p>Indeed, the phone did keep charging when its own battery gauge reported 100%, gaining a whole 144mAh of charge, and the battery voltage rising another 6mV. It&rsquo;s hard to know exactly why this is, but it could just be a case of the phone trying to encourage you to remove the phone from the charger slightly below 100% to avoid extra battery wear, or just the charging gauge calibration being slightly off. I also should note that the phones &ldquo;adaptive charging&rdquo; options were enabled, which assumedly will have some effect on charging. I will perhaps perform this test again with these options disabled in future to see how that affects the charge curve.</p>
<p>To really see this, I graphed battery level against battery mAh.</p>
<figure><img src="/posts/pixelcharge/mahvslevel.png"
         alt="A graph showing Battery mAh vs Battery Level"/>
</figure>

<p>Sure enough, we can see this behavior here too. If you&rsquo;re interested in the raw data, you can find it <a href="https://docs.google.com/spreadsheets/d/1wYNaqomdeV5HMvHmbBONzXdz_tkzHq3CcJR1tpj9uZc/edit?usp=sharing">here</a>.</p>
]]></description>
    </item>
    
    <item>
      <title>Surviving Hotel Hell and Visa Chaos: A Cautionary Tale</title>
      <link>http://kn100.me/moving-to-canada/</link>
      <pubDate>Fri, 30 Dec 2022 20:00:00 -0500</pubDate>
      
      <guid>http://kn100.me/moving-to-canada/</guid>
      <description><![CDATA[<p>As a 16-year-old, I made the decision to leave the UK due to my dislike of the politics and direction of Great Britain under the Conservative party, particularly under the leadership of Theresa May as Home Secretary. This decision was further solidified by the Brexit referendum and the actions of subsequent leaders such as Boris Johnson and Priti Patel. Over the past decade, my views have only been reinforced by the events and developments in British politics.</p>
<p>Several years ago, my wife and I decided to immigrate to Canada and applied for the Express Entry program, which allows those with certain qualifications to obtain permanent residence in the country. We anticipated the process to take between six and twelve months. However, the pandemic caused Express Entry to be effectively halted and we eventually gave up on the process. I then periodically would look into alternative routes in.</p>
<p>I applied for various jobs in Toronto and eventually found an employer who was willing to hire me, even though I planned to relocate. The work permit for this position was supposed to be processed through the &ldquo;Global Talent Stream,&rdquo; which typically takes only two weeks, but due to the pandemic and other global issues, we were told to expect a processing time of two to eight weeks. I decided to move out of my rental apartment in the UK after four weeks and stay in an Airbnb if the visa took the full eight weeks. However, the visa ended up taking 17 weeks and during that time, I had a number of unexpected experiences and learned valuable lessons.</p>
<h1 id="my-experiences-living-in-hotels">
  <a class="Heading-link u-clickable" href="/moving-to-canada/#my-experiences-living-in-hotels">My experiences living in hotels.</a>
</h1>
<p>After four weeks of waiting, we were forced to move out of our apartment and into a hotel. In selecting a place to stay, our priority was to find a location that would allow us to bring our pet cat and was as affordable as possible. We wanted to save our money for furnishing our future apartment in Toronto.</p>
<p>We initially stayed at a budget hotel chain (which I will not name) as a temporary place to stay while traveling. We just needed a place to <em>lodge</em> while <em>travel</em>ling. The experience was terrible. The mattress had a large urine stain, there was a bloody handprint (actual blood) on the ceiling, and there was no hot water for multiple days. As a result, breakfast was not served on several days. One night, the elevator near our room was constantly alarming and kept us awake. The next morning, one of the two elevators had an &ldquo;out of order&rdquo; sign on it, but when I tried to use the other one, the &ldquo;out of order&rdquo; lift showed up and the sign fell off. I took the stairs.</p>
<p>The straw that forced us to change hotels however was that one night a young girl attempted to walk into my locked room at 9pm, while I was undressed. It was 40c on that day, with no AC, hence my state of undress. She succeeded in opening the door, seemingly having a valid key card for it. Thankfully my wife was fast enough to quickly get to the door and stop the child from walking in and seeing anything. When we got to reception to try to figure out why some other family was trying to occupy our room, reception didn&rsquo;t even seem surprised, apologized, and that was that. After experiencing the issues described above, and going round and round in circles with reception, we decided we would leave the hotel that night. The hotel eventually refunded us for the three nights we didn&rsquo;t have hot water from our our 16-night stay, but refused to refund the final four days of our booked stay, claiming that the issues we experienced were not serious enough to warrant a refund.</p>
<p>We moved to a second hotel with this same chain, which was far better. Nothing much to note there, apart from at this point the reality of living in a hotel had set in. There are no cooking facilities, so I pretty much just lived on supermarket meal deals, and stuff that could be cooked with nothing other than hot water. Luckily, this particular hotel was near to a friends place however, and I am forever thankful for the meals he cooked us, and allowing us to hang at his whenever we needed some brief respite. Thanks Pete, you helped keep me sane and scurvy free. We stayed here for approximately 3 weeks.</p>
<figure><img src="/posts/ircc/1.webp"
         alt="Me, attempting to use my terrible tablet to play Magic the Gathering remotely. It *barely* worked."/>
</figure>

<p>Every day without fail, I&rsquo;d be calling a number I found online to get updates on the status of my visa. At one point, I wrote an autodialer where I could press a button, and it would navigate through the Canadian Immigration phone tree and alert me when it got to the point where the update would be delivered. The estimate had now been updated to 12 weeks, so we assumed that we only needed one more hotel stay, and we would finally be on our way to Canada.</p>
<p>Our finances really were taking a beating here too. I was lucky enough to have been able to save up a decent chunk of cash through the pandemic, but at approximately 1000 GBP per week, I was fast running low, so we had to find a cheaper option.</p>
<figure><img src="/posts/ircc/2.webp"
         alt="A sign that reads `MAIL THEFT IS MET POLICE NOT ROYAL MAIL. You&#39;ve stolen my winter coat, my prescription glasses, my headphones, my hair care products that don&#39;t work on your race, my prescription meds &amp; my catheters. Today, you tore my package open &amp; left it as you have no use for it. I&#39;ve already reported these thefts to the police. I have an incident number. If you steal another 48 GBP, it will be a felony. I am keeping the bag you opened today &amp; have photos &#43; evidence. I&#39;m watching YOU now. Steal something else. I will have the police come &amp; I will take them to your door. Get treatment, klepto.`"/>
</figure>

<p>We decided to stay at an Airbnb in London because we thought it would be more cost-effective to cook our own meals rather than eating out at restaurants. However, our experience ended up being a disaster. The Airbnb was a converted house with 18 tiny units, and the entrance was a broken gate leading to a garbage-filled garden that reeked of a strong smell. Upon entry to the building, there was a lovely sign written by one of the other occupants complaining of the constant parcel theft issues that were apparently rampant in that building. The kitchen was so small that it was barely larger than a shower stall, and there was no room for a microwave, so it was placed on a bedside table without any nearby outlets to use it. Despite these challenges, my wife was able to make the most of the situation and we were able to eat well. I am SO grateful to her for her resilience in such a difficult and unpleasant environment.</p>
<figure><img src="/posts/ircc/3.webp"
         alt="Me, now trying to use my laptop, on a terrible table and chairs in our AirBNB. There is a microwave under my desk."/>
</figure>

<p>After our stay at the previous location ended and it became apparent that the 12-week estimate was inaccurate, we moved to a more expensive place. The increased cost was partly due to the sudden increase in demand for accommodation in London following the death of Queen Elizabeth II. We were fortunate to find a slightly more expensive location that had not yet adjusted their prices and stayed there for two weeks. Unfortunately, we were unable to extend our stay because the place was fully booked, so we had to move again.</p>
<p>Our final hotel stay was at a budget brand of a more upscale hotel located near Heathrow Airport, which made it very affordable. While I eventually became accustomed to the constant smell of jet fuel, this hotel was one of the most difficult to stay at. This wasn&rsquo;t due to the hotel really, it was fine. It was in the middle of nowhere making eating difficult, and it was intensely boring, but the real problem was our patience had just run out at this point. They overcharged us 1000 GBP and it took me around three months to resolve the issue with my bank after disputing the charge. As I had already maxed out my credit, this had a greater impact on me than you might expect. Thanks, hotel! On top of this, there was still not a clear idea as to when the visa would be issued, meaning the impending homelessness was feeling more and more possible.</p>
<figure><img src="/posts/ircc/4.webp"
         alt="Our cat, on an airplane."/>
</figure>

<p>We eventually got the visa, and departed a few days later, landing in Toronto with very little money to our name. Thankfully, things are slowly sorting themselves out now both I and my wife are bringing in a salary, but it&rsquo;ll be while before we&rsquo;re back at 0.</p>
<h1 id="lessons-learned">
  <a class="Heading-link u-clickable" href="/moving-to-canada/#lessons-learned">Lessons learned</a>
</h1>
<p>I learned a few important lessons during my experience with short-term accommodation. First, never trust a visa estimate and don&rsquo;t make any plans until you have the actual visa in hand. This mistake was entirely my own and I regret it deeply. Second, living in a hotel is not enjoyable. Being unable to cook or clean for oneself is miserable. Third, Airbnb is no longer a reliable option for reasonable, short-term accommodation. Even via other avenues, most landlords are hesitant to rent out their properties for a short, undefined period. Fourth, I learned that it is not advisable to rely solely on a smartphone for technology, as I was without a computer for the first eight weeks and had to purchase a laptop. Finally, it is important to be prepared for unexpected events, such as the death of a monarch, which can have an impact on the availability and cost of accommodation.</p>
<p>Also worth noting is a lot of this article was written by OpenGPT, for fun.</p>
<figure><img src="/posts/ircc/5.webp"
         alt="A screenshot from the OpenAI Chat thing that shows me asking the question `what should I have done` and it responding `To avoid the challenges and delays described in the story, you could have waited until you received the actual visa before making any plans or moving out of your apartment. You could also have ensured that you had access to a computer for the duration of your stay, rather than relying solely on a smartphone. Additionally, you could have done more research and prepared for unexpected events, such as the death of a monarch, which can impact the availability and cost of accommodation.`"/>
</figure>

]]></description>
    </item>
    
    <item>
      <title>Convincing a Scammer That They’re Going Crazy</title>
      <link>http://kn100.me/gumtree-scammer/</link>
      <pubDate>Thu, 13 Oct 2022 22:00:00 +0100</pubDate>
      
      <guid>http://kn100.me/gumtree-scammer/</guid>
      <description><![CDATA[<p>According to 419eater.com, scambaiting is &ldquo;enter[ing] into a dialogue with scammers, simply to waste their time and resources. Whilst you are doing this, you will be helping to keep the scammers away from real potential victims and screwing around with the minds of deserving thieves&rdquo;. These scammers aim to take advantage of the elderly, people with disabilities, and others. I occasionally engage in scambaiting, but particularly enjoyed the encounter I document here.</p>
<p>I received a message concerning a computer case I was attempting to sell on Gumtree. I&rsquo;d listed it about 15 minutes prior to the message arriving.</p>
<p>Note: If you&rsquo;d prefer to read this as a series of screenshots, <a href="https://imgur.com/a/qzfMEDl">go ahead</a>.</p>
<blockquote>
<p>Scammer: I was interested in computer case</p>
<p>Scammer: Good afternoon, I&rsquo;m about an announcement on gumtree</p>
<p>Scammer: Is it convenient for you to speak now?</p>
</blockquote>
<p>No suspicions yet, seems legit!</p>
<blockquote>
<p>Me: Hi, sure, are you interested?</p>
<p>Scammer: is he in good condition?</p>
<p>Me: Particularly good condition yes, it was used for around a year but just lived on my desk. There&rsquo;s a few scuffs here and there but nothing particularly noticeable. All screws/fittings that were included are still there, only thing that&rsquo;s missing are the shields where you&rsquo;ll probably be putting a graphics card anyway.</p>
<p>Scammer: okay, are there fans included?</p>
<p>Me: No, it doesn&rsquo;t come with any fans</p>
<p>Me: It can fit a 120mm or a 140mm fan</p>
</blockquote>
<p>I decided to be extra helpful and link him to a LinusTechTips video reviewing this computer case. Can&rsquo;t say my customer service isn&rsquo;t on point. <a href="/those-damn-romanians/">At least he wasn&rsquo;t questioning whether I was Romanian or not!</a></p>
<blockquote>
<p>Me: <a href="https://youtu.be/8ptbpKpObzs">https://youtu.be/8ptbpKpObzs</a> this is the case, if you&rsquo;re curious.</p>
<p>Scammer: well, for such a price everything suits me</p>
<p>Scammer: when can you pick it up?</p>
</blockquote>
<p>Wait&hellip; When can <em>I</em> pick it up? Strange, although it could have been a language barrier thing. Whatever.</p>
<blockquote>
<p>Scammer: or will you send it by mail?</p>
<p>Me: Pickup only unfortunately, not cost effective to send computer cases by post!</p>
<p>Me: You can pick up anytime today from Mayfair - just let me know when to expect you</p>
<p>Scammer: I&rsquo;m at work today, I&rsquo;ll be free only next weekend</p>
<p>Scammer: Can I pay for the item now and pick it up next weekend?</p>
<p>Me: That would be fine yes! I&rsquo;ll hold it for you. You want to pay via PayPal/bank transfer?</p>
<p>Me: You can also pick it up tomorrow, or any day after Friday</p>
<p>Me: It&rsquo;s up to you</p>
<p>Scammer: It will be convenient for me to pick up the food next Saturday</p>
</blockquote>
<p>Pick up&hellip; The food? I&rsquo;m not sure who eats computer cases, but I&rsquo;m not sure I want to cross them! Still I suspected nothing so far.</p>
<blockquote>
<p>Scammer: yes, I can transfer to the card and paypal</p>
<p>Scammer: as you prefer?</p>
<p>Me: <a href="https://monzo.me/kevinnorman/10.00?d=Gib%20me%20money%20for%20writing%20article">https://monzo.me/kevinnorman/10.00?d=Gib%20me%20money%20for%20writing%20article</a></p>
</blockquote>
<p>I sent him a link requesting money as generated by my bank. That&rsquo;s a real link, by the way, feel free to send me money, I certainly would be grateful!</p>
<p>Given this is an ITX case (a small one which only fits certain hardware), and because I&rsquo;m a nice guy, I thought I&rsquo;d ask him what he&rsquo;s putting inside of it to double check he was aware that the case was an ITX case.</p>
<blockquote>
<p>Me: Out of interest, what are you going to put inside it?</p>
<p>Scammer: Intel core i7 and gtx 2070</p>
<p>Scammer: or it is possible through PayPal, I have money there</p>
<p>Me: Sure, <a href="mailto:kn100+paypal@kn100.me">kn100+paypal@kn100.me</a></p>
<p>Scammer: more precisely core i7 9700k</p>
<p>Me: Very nice</p>
<p>Me: Will be a very powerful tiny computer!</p>
<p>Scammer: Yes</p>
<p>Scammer: I will send a check, do you mind?</p>
</blockquote>
<p>At this point my scam senses started tingling. A check? Does he mean a cheque? Does he mean a security check? For a £10 case?</p>
<blockquote>
<p>Me: I&rsquo;m not sure I understand</p>
<p>Scammer: one second</p>
<p>Scammer: https://paypal DOT 3sds DOT site/pay/?id=49688501 (I have intentionally garbled this link. Visit at your own risk.)</p>
<p>Scammer: this is check</p>
<p>Scammer: fill in the data to receive money</p>
<p>Scammer: understand?</p>
</blockquote>
<p>Ah. A Scammer. Visiting this page took me to a page that looked like the following. An obvious scam, but it&rsquo;s pretty reasonable to expect that a lot of people might fall for this.</p>
<figure><img src="/posts/gumtree-scammer/1-the-payment-form.webp"
         alt="The payment form I was sent by the scammer, asking for bank details from &#39;mark&#39;"/>
</figure>

<p>I started to consider what to do, so I stalled a bit by just telling him the link didn&rsquo;t work. In this time I would quickly spin up a virtual machine, and do a bit of recon.</p>
<blockquote>
<p>Me: It&rsquo;s not working</p>
<p>Scammer: what does not work?</p>
<p>Me: That link</p>
<p>Me: It just 404s</p>
<p>Scammer: One second</p>
<p>Scammer: https://paypal DOT 3sds DOT site/pay/?id=49688501</p>
<p>Me: Give me a minute my dog started screaming</p>
<p>Scammer: Ok</p>
<p>Scammer: try to copy the link and paste it into the browser</p>
</blockquote>
<p>Still trying to stall for time, I try to delay by asking more PC related questions to waste his time and to give me time to think.</p>
<blockquote>
<p>Me: What power supply are you planning to use?</p>
<p>Scammer: Corsair 750w</p>
<p>Me: The link still isn&rsquo;t loading, but now I&rsquo;m getting error 509</p>
<p>Me: 500*</p>
</blockquote>
<p>I decided to claim a different error message was occurring, really get him worried. I was honestly stalling for time.</p>
<blockquote>
<p>Scammer: I&rsquo;ll write to technical support now</p>
<p>Scammer: https://paypal DOT 3sds DOT site/paychekoff/?id=49688501</p>
<p>Scammer: they stuck this link</p>
<p>Scammer: they said everything is fine</p>
</blockquote>
<p>Ah, a nugget of information! With a lot of these scams, the scammer you speak to does not manage the tool they use to scam you. The tool is purchased from some skiddie, and is then run by the scammer. The use of the word &ldquo;they&rdquo; in his messages above led me to believe that he was in direct contact with the person responsible for hosting the tool they were attempting to use to scam me.</p>
<p>At this point, I decided to fire up an image editing tool.</p>
<blockquote>
<p>Me: Okay now it&rsquo;s refusing to accept my details</p>
<p>Scammer: try with another browser</p>
<p>Me: It says error 418 now&hellip; PayPal is really unreliable today maybe we could try another way?</p>
<p>Scammer: I only keep money on paypal</p>
<p>Me: It loaded this time at least, but I&rsquo;ve tried submitting like 5 times</p>
<p>Me: Maybe you can withdraw 10 pounds and bank transfer me</p>
<p>Scammer: send a screenshot of the error, I will send to those support</p>
<p>Me: One second</p>
<p>Scammer: Well?</p>
</blockquote>
<p>So I edited an image with an error 418. For the uninitiated, according to Mozilla, Error 418 &ldquo;indicates that the server refuses to brew coffee because it is, permanently, a teapot.&rdquo;. I used this error for two reasons. Firstly because it was hilarious to me, and secondly any person who knows anything about web development is very likely to know this error and realise what is going on. If this person catches on, I was unlikely to be able to have much fun with them anyway.</p>
<figure><img src="/posts/gumtree-scammer/2-error-418.webp"
         alt="The payment form as above, but now apparently reporting an error 418."/>
</figure>

<blockquote>
<p>Scammer: if it doesn&rsquo;t work now, write to those support, at the bottom right</p>
<p>Scammer: it just works for me</p>
</blockquote>
<p>It just works for you huh? You&rsquo;re just paying yourself money? Seems legit Mr. Scammer!</p>
<blockquote>
<p>Me: I tried writing to the chat but it seems its gone offline</p>
<p>Me: also, the payment boxes have disappeared completely now, maybe too many attempts?</p>
<p>Scammer: Maybe</p>
<p>Scammer: all this is strange</p>
</blockquote>
<p>To really confuse this guy, I decided I&rsquo;d edit the screenshot so the online chat appeared offline. You&rsquo;ll notice the little circle on the chat thing turned from green to red. I also hid the &lsquo;payment&rsquo; boxes, as promised. What I wouldn&rsquo;t give to see the conversation between this guy and the tech guy running it.</p>
<figure><img src="/posts/gumtree-scammer/3-missing-form.webp"
         alt="The payment form as above, but now fields are strangely missing and the chat bot is apparently offline!"/>
</figure>

<p>I wanted to create a sense of urgency, and to force this scammer to regenerate their payment link, so I engineered a situation where he&rsquo;d increase the price.</p>
<blockquote>
<p>Me: I think I might have another buyer for the case, I might see if they can pay for it in a different way, I just want shot of it</p>
<p>Scammer: as you wish</p>
<p>Scammer: technical support wrote: the server was restored</p>
<p>Scammer: can you try again?</p>
<p>Me: Hmnn, this other buyer is offering me cash today. Perhaps if you want it we can say 15 pounds?</p>
<p>Scammer: okay let&rsquo;s go for 15</p>
<p>Me: The link still says 10</p>
<p>Scammer: now we will create a new translation</p>
<p>Scammer: https://paypal DOT 3sds DOT site/paychekoff/?id=49688501</p>
</blockquote>
<p>Hey, a new link, with a name! Thanks Chekoff!</p>
<blockquote>
<p>Scammer: 15£</p>
<p>Scammer: works?</p>
<p>Me: I don&rsquo;t know what the hell is going on</p>
</blockquote>
<p>Now, I decided to give the Script kiddie behind this nightmares. I edited the payment page to read <code>NaN</code> for the payment amount.</p>
<blockquote>
<p>Me: this says: To Receive: NaN</p>
</blockquote>
<figure><img src="/posts/gumtree-scammer/4-nan.webp"
         alt="The payment form as above, but now the payment amount says NaN!"/>
</figure>

<blockquote>
<p>Scammer: I do not know what&rsquo;s happening</p>
<p>Scammer: try to go through another browser</p>
<p>Scammer: I&rsquo;m already so tired, I&rsquo;m ready to give 20 £</p>
</blockquote>
<p>Next, I decided to engage with the scammer via their &ldquo;tech support&rdquo;. This screenshot is undoctored.</p>
<figure><img src="/posts/gumtree-scammer/5-chat.webp"
         alt="A chat between me and the &#39;support&#39;. Support: Hi, Me: I keep getting errors when trying to request payment, Is this PayPal support?, Support: hi. let me help you. enter your bank card in the chat. her full details, me: No, that is not secure"/>
</figure>

<blockquote>
<p>Scammer: how will tech support answer write me</p>
</blockquote>
<p>Hilariously, after I refused to provide my card details via chat, the scammer messaged me again on Whatsapp to ask why I wasn&rsquo;t providing the card details!</p>
<blockquote>
<p>Me: I have no idea but I am not typing my card details into a text box for 20 quid</p>
<p>Me: oh man I just went to reply to support and got this</p>
</blockquote>
<p>Next, I threw him a curveball. I edited the screenshot of the chat to have a fake warning indicating that the &ldquo;free trial period&rdquo; had expired. Next, I asked him if he&rsquo;d like to sign up to my Onlyfans. I also linked him to a nursery rhyme for no good reason.</p>
<figure><img src="/posts/gumtree-scammer/6-chat.webp"
         alt="The same chat window as above, but now an apparent error appeared &#39;Your free chat trial period has expired! Please contact us for more info.&#39;"/>
</figure>

<blockquote>
<p>Me: Here&rsquo;s a weird idea&hellip; I actually have an Onlyfans, and Onlyfans can be paid for with Paypal. What if you sign up to my Onlyfans and I make a tier that is 20 pounds?</p>
<p>Me: <a href="https://www.youtube.com/watch?v=B6en-O5yF0o">https://www.youtube.com/watch?v=B6en-O5yF0o</a></p>
<p>Me: oh sorry that link wasn&rsquo;t for you</p>
</blockquote>
<p>Here, he went quiet. I decided a few hours later to try to re-engage him by letting him know other buyers had lost interest. What he didn&rsquo;t know is I&rsquo;d already sold the case to someone else.</p>
<blockquote>
<p>Me: this other buyer has fallen through, so it&rsquo;s just you now. Not sure what to do.</p>
<p>Scammer: how paypal works, i will transfer you £ 20</p>
<p>Scammer: Ok?</p>
<p>Me: how?</p>
<p>Me: Hello? Do you still want it?</p>
<p>Scammer: Yes</p>
<p>Me: Then how shall we conduct this business transaction sir</p>
<p>Me: I&rsquo;ve got the case, you&rsquo;ve got the cash, can I make it any more obvious?</p>
<p>Scammer: I&rsquo;m going to try now</p>
<p>Scammer: try now</p>
<p>Scammer: https://paypal DOT wis3 DOT site/pay/?id=73531466</p>
</blockquote>
<p>Ha! He&rsquo;s given me another domain to report. Excellent. More sleuthing indicates both domains were registered with the same email. A tiny bit more looking showed that there were around 50 of these domains all serving the same content. Reg.ru of course got a list.</p>
<blockquote>
<p>Scammer: work?</p>
</blockquote>
<p>Another curveball. My sleuthing through WHOIS records lead me to believe the scammer in question was Russian too, and the domain was registered with Reg.ru. I decided to mock up a fake domain suspension notice. The Russian reads &ldquo;This website has been blocked. Get in contact with the support team&rdquo;.</p>
<figure><img src="/posts/gumtree-scammer/7-regru.webp"
         alt="A doctored reg.ru error message stating the domain had been suspended, on a mobile phone."/>
</figure>

<blockquote>
<p>Scammer: I don’t know, try using another browser</p>
<p>Me: sorry let me try on a computer, this is tiresome</p>
<p>Me: Same thing</p>
</blockquote>
<p>Of course, the same thing on the desktop! Left a nice easter egg in the tab bar though, which wasn&rsquo;t noticed of course.</p>
<figure><img src="/posts/gumtree-scammer/8-regru.webp"
         alt="The same error as the above reg.ru screenshot, except this time on a computer, and the tab reads &#39;OFFICIAL PAYPAL LEGIT aXXO&#39;"/>
</figure>

<blockquote>
<p>Scammer: so is the deal canceled?</p>
<p>Me: I can&rsquo;t seem to get payment from you so I&rsquo;m not sure what you want me to do</p>
<p>Me: maybe you have other suggestions</p>
<p>Scammer: there is a suggestion to put an end to this</p>
</blockquote>
<p>HE&rsquo;S FINALLY CLOCKED ON!</p>
<blockquote>
<p>Me: go onnnn</p>
<p>Scammer: Goodbye</p>
</blockquote>
<p>At this point, I&rsquo;d done a fair bit of sleuthing, and determined his name was probably not Mark. I&rsquo;d also reported both domains to their registrars, reported abuse to their host, reported them to the various safe browsing blacklist services, etc.</p>
<blockquote>
<p>Me: Have a lovely evening Mark :)</p>
<p>Me: Does your scam work often?</p>
<p>Scammer: almost always</p>
<p>Me: how much do you make?</p>
<p>Scammer: it&rsquo;s a secret</p>
<p>Me: welp, I hope you find some joy in life that doesn&rsquo;t involve destroying others lives, and I hope you enjoyed having so much of your time wasted, I certainly had a whale of a time in Photoshop :)</p>
</blockquote>
]]></description>
    </item>
    
    <item>
      <title>The Perils of RSS</title>
      <link>http://kn100.me/rss-perils/</link>
      <pubDate>Wed, 09 Mar 2022 22:00:00 +0100</pubDate>
      
      <guid>http://kn100.me/rss-perils/</guid>
      <description><![CDATA[<p>RSS, or RDF Site Summary, Rich Site Summary and most recently
Really Simple Syndication, is a standard that allows readers to aggregate
multiple blogs into one &lsquo;reader&rsquo; interface, which displays these blogs posts in
a standardised way. It&rsquo;s an old standard, developed in 1999 at Netscape for use
on their <code>my.netscape.com</code> portal. It allowed users to import RSS feeds from
other websites and have their content appear on Netscapes feed syndicator.</p>
<p>The concept is was simple. Sites which wished to have their content
aggregated would publish an RSS feed - which was an XML document that had a
standard set of tags to describe the content on the site in a standard fashion.
You can see an example of an RSS feed <a href="https://kn100.me/rss/">right here</a>.
Many, many websites published RSS feeds through the years after the death of
Netscape in 2003, and similarly many pieces of self hosted software that did not
require users to make use of a website to view their RSS feeds, and for a while
this seems to be the main consumption model for RSS. Tools like <a href="https://en.wikipedia.org/wiki/Mozilla_Thunderbird">Mozilla
Thunderbird</a>, <a href="https://en.wikipedia.org/wiki/AOL_Explorer">AOL
Explorer</a>, <a href="https://en.wikipedia.org/wiki/Claws_Mail">Claws
Mail</a>, <a href="https://en.wikipedia.org/wiki/IBM_Lotus_Notes">IBM Lotus
Notes</a> (Which crazily was
discontinued almost exactly 2 years ago, and saw its first release 33 years
ago!), <a href="https://en.wikipedia.org/wiki/Firefox">Firefox</a>, <a href="https://en.wikipedia.org/wiki/Microsoft_Office_Outlook">Microsoft
Outlook</a> and many more
all supported RSS feed syndication of one form or another.</p>
<p>Then came Google Reader in 2005. Those who love RSS generally speak fondly of
Google Reader. It was a <a href="https://killedbygoogle.com/">fairly long lived tool by Google
standards</a> - lasting 7 years, and eventually being
killed in 2013. It is hard to estimate just how many people were using Google
Reader, but competitor in the space Feedly announced that in the 2 weeks after
Readers discontinuation over 3 million people joined.</p>
<p>What is interesting to note here is the flip-flopping between &lsquo;cloud&rsquo; based
readers, like <code>my.netscape.com</code>, and offline readers like Thunderbird. I&rsquo;m not
certain why this is, but RSS definitely lends itself very well to server side
processing, especially in the days prior to the enormous slow React apps and the
browser per application Electron apps of today. Power users were also probably more
open to the idea of giving up the keys to who controls their data in the name of
convenience with tools like Google Reader, but that is just a guess.</p>
<p>Also of note is the fact that RSS is very alive and well today.
<a href="https://feedly.com/">Feedly</a> seems to be one of the largest feed readers in use
by users today (at least based on the server logs for <code>kn100.me</code>). I can tell
this because when I check my Nginx server logs, when Feedly requests my feed,
their crawler reports the number of subscribers to my feed  in its user agent via
Feedly. This is a lovely feature, by the way!</p>
<p>Feedly and other cloud based RSS aggregators pose an interesting problem
however, and therein lies the peril of RSS. Firstly, I need to quickly describe
how my RSS feed comes to exist. I myself am not a user of RSS, but enough
readers complained about the lack of a feed that I eventually added one.
<a href="https://gohugo.io">Hugo</a>, the static site generator I use, can easily generate
RSS feeds, if provided a template for doing so. Such templates look like this:</p>

<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span>{{- $pctx := . -}}
</span></span><span style="display:flex;"><span>{{- if .IsHome -}}{{ $pctx = .Site }}{{- end -}}
</span></span><span style="display:flex;"><span>{{- $pages := slice -}}
</span></span><span style="display:flex;"><span>{{- if or $.IsHome $.IsSection -}}
</span></span><span style="display:flex;"><span>{{- $pages = $pctx.RegularPages -}}
</span></span><span style="display:flex;"><span>{{- else -}}
</span></span><span style="display:flex;"><span>{{- $pages = $pctx.Pages -}}
</span></span><span style="display:flex;"><span>{{- end -}}
</span></span><span style="display:flex;"><span>{{- $limit := .Site.Config.Services.RSS.Limit -}}
</span></span><span style="display:flex;"><span>{{- if ge $limit 1 -}}
</span></span><span style="display:flex;"><span>{{- $pages = $pages | first $limit -}}
</span></span><span style="display:flex;"><span>{{- end -}}
</span></span><span style="display:flex;"><span><span style="color:#f92672">&lt;rss</span> <span style="color:#a6e22e">version=</span><span style="color:#e6db74">&#34;2.0&#34;</span> <span style="color:#a6e22e">xmlns:atom=</span><span style="color:#e6db74">&#34;http://www.w3.org/2005/Atom&#34;</span><span style="color:#f92672">&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&lt;channel&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;title&gt;</span>{{ if eq  .Title  .Site.Title }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{.}} on {{ end }}{{ .Site.Title }}{{ end }}<span style="color:#f92672">&lt;/title&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;link&gt;</span>{{ .Permalink }}<span style="color:#f92672">&lt;/link&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;description&gt;</span>Recent content {{ if ne  .Title  .Site.Title }}{{ with .Title }}in {{.}} {{ end }}{{ end }}on {{ .Site.Title }}<span style="color:#f92672">&lt;/description&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;generator&gt;</span>Hugo -- gohugo.io<span style="color:#f92672">&lt;/generator&gt;</span>{{ with .Site.LanguageCode }}
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;language&gt;</span>{{.}}<span style="color:#f92672">&lt;/language&gt;</span>{{end}}{{ with .Site.Author.email }}
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;managingEditor&gt;</span>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}<span style="color:#f92672">&lt;/managingEditor&gt;</span>{{end}}{{ with .Site.Author.email }}
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;webMaster&gt;</span>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}<span style="color:#f92672">&lt;/webMaster&gt;</span>{{end}}{{ with .Site.Copyright }}
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;copyright&gt;</span>{{.}}<span style="color:#f92672">&lt;/copyright&gt;</span>{{end}}{{ if not .Date.IsZero }}
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;lastBuildDate&gt;</span>{{ .Date.Format &#34;Mon, 02 Jan 2006 15:04:05 -0700&#34; | safeHTML }}<span style="color:#f92672">&lt;/lastBuildDate&gt;</span>{{ end }}
</span></span><span style="display:flex;"><span>    {{ with .OutputFormats.Get &#34;RSS&#34; }}
</span></span><span style="display:flex;"><span>      {{ printf &#34;<span style="color:#f92672">&lt;atom:link</span> <span style="color:#a6e22e">href=</span><span style="color:#e6db74">%q</span> <span style="color:#a6e22e">rel=</span><span style="color:#e6db74">\&#34;self\&#34;</span> <span style="color:#a6e22e">type=</span><span style="color:#e6db74">%q</span> <span style="color:#f92672">/&gt;</span>&#34; .Permalink .MediaType | safeHTML }}
</span></span><span style="display:flex;"><span>    {{ end }}
</span></span><span style="display:flex;"><span>    {{ range $pages }}
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;item&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&lt;title&gt;</span>{{ .Title }}<span style="color:#f92672">&lt;/title&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&lt;link&gt;</span>{{ .Permalink }}<span style="color:#f92672">&lt;/link&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&lt;pubDate&gt;</span>{{ .Date.Format &#34;Mon, 02 Jan 2006 15:04:05 -0700&#34; | safeHTML }}<span style="color:#f92672">&lt;/pubDate&gt;</span>
</span></span><span style="display:flex;"><span>      {{ with .Site.Author.email }}<span style="color:#f92672">&lt;author&gt;</span>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}<span style="color:#f92672">&lt;/author&gt;</span>{{end}}
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&lt;guid&gt;</span>{{ .Permalink }}<span style="color:#f92672">&lt;/guid&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&lt;description&gt;</span>{{ safeHTML &#34;<span style="color:#75715e">&lt;![CDATA[&#34; }}{{- .Content | safeHTML -}}{{ safeHTML &#34;]]&gt;</span>&#34; }}<span style="color:#f92672">&lt;/description&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;/item&gt;</span>
</span></span><span style="display:flex;"><span>    {{ end }}
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&lt;/channel&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">&lt;/rss&gt;</span></span></span></code></pre></div>
<p>In this template, I expose my entire post content for all posts on my blog. The
RSS feed output seems to work just fine, and those passionate RSS users I
mentioned earlier were perfectly happy with this, until recently. I just rebuilt
this blog from the ground up. I did this because when I first built <code>kn100.me</code>,
I had very little idea what I was doing with Hugo, and the codebase (if you can
call it that!) had gotten very messy, the CSS had gotten messed up, and it was
getting more and more difficult to manage. I created a brand new Hugo blog,
ported all my content across, reorganised all my static assets, ensured all the
permalinks hadn&rsquo;t changed, actually properly customised my templates, and was
pretty pleased with the result. I deployed it, and all seemed well.</p>
<p>A few days later, another project of mine, the <a href="/cybiko-archive/">Cybiko Game
Archive</a> microservice I&rsquo;d written to serve up a catalogue of
games that existed for the Cybiko was reporting errors. Unfortunately, a Script
Kiddie had found it, and was bombarding it with dumbass requests to
<code>/wp-admin.php</code>, <code>/admin</code>, etc, and the code was dutifully doing exactly what I
told it to, which was to log it. I didn&rsquo;t really see the point of reworking this
microservice to deal with this, but didn&rsquo;t want to spend much time on it, and I
didn&rsquo;t want to risk turning logging off, so I decided to do something more
drastic.</p>
<p>The Cybiko Game Archive service is fairly basic. I give it a directory full of
content, and it creates an in memory index of it, which is then served using Go
templates. The pages it produces do not change often, so are a good candidate
for being static content, so I rewrote it to optionally export Markdown, which I
could import into a new section of my blog, where Hugo would be responsible for
generating the pages. The result of that work is what you now see on the site.
This reduced the administrative overhead of hosting it, and meant that I could
deploy it along with my blog, no problem.</p>
<p>This all went very well, and I ended up with a fairly acceptable result, which I
later deployed. The Cybiko Archive sans a microservice was live, improving
administrability, security and performance.</p>
<p>A few hours later, a reader tweeted at me asking why their RSS reader was
bombarding them with literally hundreds of pieces of content that weren&rsquo;t my
usual kind of content. I investigated, and came across the fact that my RSS feed
now listed every single Cybiko game, along with my regular posts.
These Cybiko game posts were never intended to go into the RSS feed, and I
incorrectly assumed that my RSS generation logic would not generate entries for
posts that were not part of the main section of the blog.</p>
<figure><img src="/posts/rss-perils/intro.jpg"
         alt="A photo showing Feedly reader containing hundreds of Cybiko game posts"/>
</figure>

<p>This should be an easy fix, I thought, I&rsquo;ll quickly check out the template for
RSS generation and add a filter, so that it would only generate entries for
pages that are part of the main section of my blog, called <code>posts</code>.</p>

<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">Find</span>: {{ <span style="color:#66d9ef">range</span> <span style="color:#960050;background-color:#1e0010">$</span><span style="color:#a6e22e">pages</span> }}
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">Replace</span>: {{ <span style="color:#66d9ef">range</span> (<span style="color:#a6e22e">where</span> .<span style="color:#a6e22e">Site</span>.<span style="color:#a6e22e">Pages</span> <span style="color:#e6db74">&#34;.Section&#34;</span> <span style="color:#e6db74">&#34;posts&#34;</span>) }}</span></span></code></pre></div>
<p>I regenerated the static pages, deployed them, and called it a night. The next
day, another reader contacted me reporting exactly the same thing. And a third.
And a fourth. I quickly figured out they were all using Feedly, and then later
learned that while Feedly is quite happy to add whatever content you add
to your RSS feed to their internal store, they do not remove content that you
remove. This is fairly similar to what a lot of RSS reader software that the
user installs on their computer does, however the cloudy nature of Feedly removes an important
avenue of control from the user. With RSS reader software, it is possible
to remove and re-add a feed, thereby grabbing a brand new copy of it. When a
content creator like myself makes an error like this, users can fairly easily do
this after the mistake had been corrected, mark everything below their most
recently read post as read, and get on with their life. Not so with Feedly,
since it does not appear they ever remove content that was removed from an RSS
feed. It&rsquo;s been a week since this issue was first reported to me, and there does
not appear to be anything I can do about it.</p>
<p>I attempted contacting Feedly via Twitter and via Email, but have not heard back
from them. Unfortunately, any Feedly users who were subscribed or now subscribe
to my blog now just have to deal with it, and there is as far as I know
absolutely nothing I can do about it from my end. I just have to apologize to my
readers. Therein lies the peril of RSS.</p>
<p>The point I hope to make clear is that if you&rsquo;ve never checked, your RSS
readership is likely larger than you realise. It was certainly far larger than I
realized. kn100.me is a tiny blog, and I&rsquo;ve been enjoying the slow growth that
being on the internet for a while brings. I do this for fun, and have made
exactly no money from doing it. Losing a few RSS users is sad (and I&rsquo;ve
definitely seen a dip in my Feedly readership), but is not a disaster for me. If
you live off of blogging, be very careful with your RSS feed. One mistake can
permanently wreck your RSS feed for users of cloudy platforms you have no
control over.</p>
]]></description>
    </item>
    
    <item>
      <title>kn100.me has been rebuilt!</title>
      <link>http://kn100.me/site-rebuild/</link>
      <pubDate>Mon, 28 Feb 2022 22:00:00 +0100</pubDate>
      
      <guid>http://kn100.me/site-rebuild/</guid>
      <description><![CDATA[<p>Just a quick note to say kn100.me was fully rebuilt. I&rsquo;ve done my best to keep all links the same, but it&rsquo;s possible I&rsquo;ve missed something. If you notice something broken or missing, let me know.</p>
<p>Thanks!</p>
]]></description>
    </item>
    
    <item>
      <title>Dealing with a Cybiko Classic in 2022</title>
      <link>http://kn100.me/interfacing-with-cybiko-2022/</link>
      <pubDate>Sat, 26 Feb 2022 22:00:00 +0100</pubDate>
      
      <guid>http://kn100.me/interfacing-with-cybiko-2022/</guid>
      <description><![CDATA[<h1 id="the-battery-problem">
  <a class="Heading-link u-clickable" href="/interfacing-with-cybiko-2022/#the-battery-problem">The battery problem</a>
</h1>
<p>Unfortunately old, long term unused Cybiko Classics suffer from exploded
batteries. Worse, the residue will eat away at the original battery contacts,
and will even begin to damage the motherboard if left in for long enough.</p>
<figure><img src="/posts/cybiko-classic-2022/cybiko-intro.jpg"
         alt="A neon green Cybiko"/>
</figure>

<p>Get the batteries that are in there out as soon as possible, and clean the
battery contacts as well as the Cybikos own contacts as best you can. You may
also consider disassembling the Cybiko to clean any residue that may have gotten
to the motherboard. Do NOT mess with the LCD, the ribbon is incredibly flexible
and I have killed a unit fiddling. You could attempt to reuse the batteries, but
I would strongly suggest instead either finding replacements, or as a cheaper
and worse alternative doing what I did and attaching an alternative power
source. Two Ni-MH 1.2v AAA or AA batteries in a holder soldered to the battery
contacts will work just fine. It is also STRONGLY recommended you do NOT solder
directly to the batteries, and instead purchase a battery holder instead. Doing
a poor job of soldering to the batteries at best can cause irreparable damage
to the battery, and at worst can cause a catastrophic failure.</p>
<h1 id="hardware-to-connect-to-cybiko-classic">
  <a class="Heading-link u-clickable" href="/interfacing-with-cybiko-2022/#hardware-to-connect-to-cybiko-classic">Hardware to connect to Cybiko Classic</a>
</h1>
<p>If you do not have the serial cable bundled with the Cybiko, you should know
that they&rsquo;re practically impossible to find. If do not mind making a small
after-market modification to the Cybiko, you will need to do some soldering work
to add some wires, which we will later attach to an RS232 port. This is
necessary because Cybiko used a proprietary connector and as far as I can tell no
other device used this connector, and therefore the cables are becoming quite
rare.</p>
<p>The Cybiko motherboard has a number of test points, and thankfully four of them
are exactly what we need to solder to in order to bring the serial connection it
uses to connectors we can actually connect to. See the diagram below which
highlights each test point. I suggest using the thinnest insulated wire you can
get your hands on, in order to make running them inside the Cybiko as easy as
possible. One possible egress point for these wires would be the hole on the
right hand side of the battery compartment. I also suggest at this point making
a note of whichever colour you soldered to each test point, so once you
reassemble the Cybiko, you know which is which.</p>
<figure><img src="/posts/cybiko-classic-2022/cybiko-serial-testpoints.jpg"
         alt="the test points you need to solder to (TP47,48,49, and somewhere for ground)"/>
</figure>

<p>TP47 is TXDL, which goes to the RXD of your RS232 adaptor, TP48 is RXDS, which
goes to TXD of your serial adaptor, TP49 is RTS, and the black pin that I&rsquo;ve
stuck on the side of the reset switch is GND, because the outside of the reset
switch is grounded, so you can just solder there, or anywhere else you can find
ground.&quot; title=&ldquo;TP47 is TXDL, which goes to the RXD of your RS232 adaptor, TP48
is RXDS, which goes to TXD of your serial adaptor, TP49 is RTS, and the black
pin that I&rsquo;ve stuck on the side of the reset switch is GND, because the outside
of the reset switch is grounded, so you can just solder there, or anywhere else
you can find ground.</p>
<p>Once the wires are outside of the Cybiko, you can then attach them to an RS232
DB-9 male connector. I suggest picking up a DB-9 break out board, <a href="https://www.amazon.co.uk/gp/product/B09LXQW2YK/ref=as_li_tl?ie=UTF8&amp;camp=1634&amp;creative=6738&amp;creativeASIN=B09LXQW2YK&amp;linkCode=as2&amp;tag=kn100-21&amp;linkId=0dc4a3c82607536e8ce794b037cd457a">like this
one</a>.
Attach each of the 4 wires to the 4 points on the DB-9 connector matching TX,
RX, RTS, and GND.</p>
<p>If in the very likely case your computer does not have a serial port, you can
pick up a DB-9 Male Serial to USB adaptor <a href="https://www.amazon.co.uk/gp/product/B00QUZY4UG/ref=as_li_tl?ie=UTF8&amp;camp=1634&amp;creative=6738&amp;creativeASIN=B00QUZY4UG&amp;linkCode=as2&amp;tag=kn100-21&amp;linkId=f3112c2b43c76feb0a0c360c18693186">like this
one</a>.</p>
<p>With all this, you&rsquo;re ready to connect.</p>
<figure><img src="/posts/cybiko-classic-2022/cybiko-connected.jpg"
         alt="A photo showing a Cybiko connected via the soldering method described above"/>
</figure>

<h1 id="if-you-have-a-windows-xp-computer-to-hand">
  <a class="Heading-link u-clickable" href="/interfacing-with-cybiko-2022/#if-you-have-a-windows-xp-computer-to-hand">If you have a Windows XP computer to hand</a>
</h1>
<p><strong>A safety note regarding Windows XP</strong>: I strongly encourage you to not connect
it to the internet if you can help it. It is NOT a secure operating system and
should not have unfettered internet access. Copy files you need to and from it
via USB sticks or similar.</p>
<p>Plug in your Cybiko via your serial adaptor or similar. If you are using the
official Cybiko cable and are connecting to a real RS232 port on your computer,
you can stop here. If you are using a USB to serial device, you might need to
install its drivers: Open device manager (Start &gt; Run &gt; <code>devmgmt.msc</code> &gt; OK), and
verify that you can see your serial device (It&rsquo;ll be under Ports (COM &amp; LPT) if
it is installed correctly and no yellow triangle will appear on it). If you find
it under Unknown devices or there is a yellow triangle, you will need to install
the drivers for your serial adaptor. Follow your vendors instructions for how to
do that.</p>
<h1 id="if-you-do-not-have-a-windows-xp-computer-to-hand">
  <a class="Heading-link u-clickable" href="/interfacing-with-cybiko-2022/#if-you-do-not-have-a-windows-xp-computer-to-hand">If you do not have a Windows XP computer to hand</a>
</h1>
<p>One solution to not having a computer of this vintage is to virtualise one. This
isn&rsquo;t too difficult.</p>
<ol>
<li>
<p>Plug in your Cybiko via your serial adaptor or similar.</p>
</li>
<li>
<p>Install Virtualbox. It can be gotten from your favourite Linux package
manager, or see <a href="https://www.virtualbox.org/wiki/Downloads">here</a> for
download links for less good operating systems.</p>
</li>
<li>
<p>Acquire installation media for Windows XP. I can&rsquo;t help you with this one.</p>
</li>
<li>
<p>From within Virtualbox, select the &ldquo;New&rdquo; option.</p>
</li>
<li>
<p>Give your virtual machine a descriptive name, and ensure that Type is set to
&ldquo;Windows XP&rdquo; and Version is set to &ldquo;Windows XP (32 bit)&rdquo;. The 32 bit option
is important. Ask me how I know.</p>
</li>
<li>
<p>Select whichever RAM size you would like, 1GB or greater recommended.</p>
</li>
<li>
<p>Create the virtual disk with whichever size you like.</p>
</li>
<li>
<p>After the wizard has finished, right click it and select settings. Select USB
on the left, and then click the USB Plus (+) icon on the right. A list should
pop up listing all your USB devices. One of these should be your serial
adaptor. Select it.</p>
</li>
</ol>
<p><strong>Note</strong>: If you do not see any USB devices listed, you may be running Linux and
therefore may need to run <code>sudo adduser $USER vboxusers</code> in a terminal in order
to add your user to the Virtualbox users group. If you&rsquo;re not on Linux, dunno
lol.</p>
<ol start="9">
<li>
<p>Select &ldquo;OK&rdquo;. Double click your virtual machine.</p>
</li>
<li>
<p>From the popup that appears, select your Windows XP installation media and
follow through with the Windows XP installation procedure.</p>
</li>
<li>
<p>Once you are at the desktop, you&rsquo;ll want to install the Virtualbox Guest
Additions. You do this by selecting &ldquo;Devices&rdquo; at the top of the Virtualbox
window, and selecting &ldquo;Insert Guest Additions CD&rdquo;. Then open &ldquo;My Computer&rdquo;
from within the virtual machine, open the CD that is now there, and select
&ldquo;VBoxWindowsAdditions.exe&rdquo;.</p>
</li>
<li>
<p>Follow the installation prompts, and reboot when prompted.</p>
</li>
<li>
<p>Open device manager (Start &gt; Run &gt; <code>devmgmt.msc</code> &gt; OK), and verify that you
can see your serial device (It&rsquo;ll be under Ports (COM &amp; LPT) if it is
installed correctly and no yellow triangle will appear on it. If you find it
under Unknown devices or there is a yellow triangle, you will need to
install the drivers for your serial adaptor. Follow your vendors
instructions for how to do that.</p>
</li>
</ol>
<h1 id="cyberload-setup-and-initial-connection">
  <a class="Heading-link u-clickable" href="/interfacing-with-cybiko-2022/#cyberload-setup-and-initial-connection">Cyberload setup and initial connection</a>
</h1>
<p>Cyberload is a piece of software released with the Classic. It was superseded by
EZ Loader, however I actually prefer Cyberload for Classics. Its purpose was to
download the game/app catalogue from Cybiko.com (long gone), and to send games
to the Cybiko.</p>
<figure><img src="/posts/cybiko-classic-2022/cybiko-cyberload.jpg"
         alt="A screenshot showing the Cyberload software running in a virtual machine. Memories!"/>
</figure>

<ol>
<li>
<p>Get a copy of the UK Cyberload from <a href="https://kn100.me/posts/cybiko-classic-2022/Cybikofiles/Cybiko-CyberloadSetupUK.exe">here</a>, or the US version of Cyberload from <a href="https://kn100.me/posts/cybiko-classic-2022/Cybikofiles/Cybiko-CyberLoadSetup.exe">here</a>.</p>
</li>
<li>
<p>After installation, open Cyberload. If it doesn&rsquo;t pick up on your Cybiko
being plugged in, try powering the Cybiko entirely off (hold the escape key
for around 10 seconds) and switching it back on while connected to the
computer. You&rsquo;ll know you&rsquo;re connected once the Cybiko displays &ldquo;Connected to
PC&rdquo; and the software shows your Cybikos ID on the right hand side. If you
cannot get past this step, ensure your connections to your RS232 adaptor are
correct!</p>
</li>
<li>
<p>You can now peruse the &lsquo;Help&rsquo; section within Cyberload to learn about the
various features of Cyberload.</p>
</li>
</ol>
<h1 id="uploading-software">
  <a class="Heading-link u-clickable" href="/interfacing-with-cybiko-2022/#uploading-software">Uploading software</a>
</h1>
<p>To upload software to the Cybiko that you have found (for example, from <a href="https://archive.org/details/cybiko">here</a>), follow the below instructions. I should also note I built the <a href="https://kn100.me/cybiko-archive/">Cybiko Game Archive</a> which lists every game that was ever released for the Cybiko, along with a description.</p>
<ol>
<li>
<p>Counterintuitively, we need to move a file from your Cybiko to your PC, so
that Cyberload creates directories on your local machine. To do this, select
a file from the right hand side of Cyberload, and then press <code>&lt;= Move</code>.</p>
</li>
<li>
<p>Check <code>C:\Program Files\Cybiko\CyberLoad\Local Files</code> and you should now see
a folder called <code>From Cybiko Computer XXXXXXXX</code> where XXXXXXXX is your Cybiko
ID. Inside there will be a folder called Device Flash. Copy your desired
software into there.</p>
</li>
<li>
<p>Close and reopen Cyberload. The software should now appear on the left, and
you can move it using the <code>move</code> button.</p>
</li>
<li>
<p>Close Cyberload, and disconnect your Cybiko.</p>
</li>
</ol>
<h1 id="updating-your-cybiko-to-the-latest-cyos">
  <a class="Heading-link u-clickable" href="/interfacing-with-cybiko-2022/#updating-your-cybiko-to-the-latest-cyos">Updating your Cybiko to the latest CYOS</a>
</h1>
<p>The latest version of CYOS for the Classic is 1.3 System Pack version 57/58. 58
is for the UK, 57 is for the US (this determines which RF profile your Cybiko
uses, since apparently there are different laws between the US and the UK). You
can see which version your Cybiko Classic is running right now by pressing the
<code>?</code> button at the desktop.</p>
<p>If you need to update it or simply would like to factory reset it, the process
is relatively simple.</p>
<ol>
<li>
<p>Connect your Cybiko to a power source and also your computer.</p>
</li>
<li>
<p>Grab the Cybiko-Autoupdate-UK.exe file <a href="https://kn100.me/posts/cybiko-classic-2022/Cybikofiles/Cybiko-AutoUpdateUK.exe">here</a> or
the US version <a href="https://kn100.me/posts/cybiko-classic-2022/Cybikofiles/Cybiko-AutoUpdate.exe">here</a></p>
</li>
<li>
<p>Run the update and wait patiently for the update to complete.</p>
</li>
</ol>
<h1 id="dealing-with-the-dark-lcd-problem">
  <a class="Heading-link u-clickable" href="/interfacing-with-cybiko-2022/#dealing-with-the-dark-lcd-problem">Dealing with the Dark LCD problem</a>
</h1>
<p>There is a problem I have seen with numerous Classic Cybiko units where the LCD
becomes almost unreadable with extremely poor contrast. Not entirely black, but
only viewable from extreme viewing angles. It seems to me this problem is
afflicting many Cybikos. I believe is due to their age, but have no further
diagnostic insight sadly.</p>
<p>One solution I came across was to use a piece of Cybiko software I recently came
across called LCD Setup 2,</p>
<ol>
<li>
<p>Grab LCD Setup 2, which can be downloaded
<a href="https://kn100.me/posts/cybiko-classic-2022/Cybikofiles/Cybiko-LCD_setup_2.app">here</a>. Push that to your Cybiko using your
preferred method.</p>
</li>
<li>
<p>Run the software on the Cybiko and be careful, the wrong options can result
in a white screen, requiring a full software reset to fix.</p>
</li>
<li>
<p>There are 5 values that are configurable here. I suggest firstly moving the
cursor to the fifth value (the contrast), and increasing it to something like
11. The other 4 values control the &lsquo;colours&rsquo; the cybiko has. For my
particular unit, the following values work well (31, 18, 14, 00, 11).</p>
</li>
<li>
<p>Press enter to persist, and then escape out of the app. Enjoy!</p>
</li>
</ol>
]]></description>
    </item>
    
    <item>
      <title>Putting a high end DAC inside your ITX desktop computer</title>
      <link>http://kn100.me/putting-usb-dac-inside-computer/</link>
      <pubDate>Sat, 26 Feb 2022 21:00:00 +0100</pubDate>
      
      <guid>http://kn100.me/putting-usb-dac-inside-computer/</guid>
      <description><![CDATA[<p><code>Tl;Dr</code>: In this blog post, I discuss the problem I have with having an external
DAC (cable clutter), and the hacky solution I came up with (putting a USB DAC
inside the chassis of my computer and hijacking the HD-Audio solution built into
most PC cases).</p>
<h1 id="the-problem">
  <a class="Heading-link u-clickable" href="/putting-usb-dac-inside-computer/#the-problem">The problem</a>
</h1>
<p>I recently became dissatisfied with my motherboard audio solution, so I
investigated DACs and found the <a href="https://www.amazon.co.uk/gp/product/B09B35TCC7/ref=as_li_tl?ie=UTF8&amp;camp=1634&amp;creative=6738&amp;creativeASIN=B09B35TCC7&amp;linkCode=as2&amp;tag=kn100-21&amp;linkId=1fe9b64c8de5e65f68db04b9e66ca0d5">Fiio K3</a>.
The difference compared to the onboard motherboard audio was that the headphones
could be driven louder (unsurprising) but the bass specifically seemed &rsquo;tighter'
and more defined. I hate describing sound, but it sounded (ha!) as if the
headphones were more &ldquo;alive&rdquo;. I&rsquo;ll leave further discussion of how headphones
sound to the audiophiles, but suffice to say I fell in love with this setup.
What I did not love however was the cable clutter. The Fiio K3 connects to your
computer via a USB-C cable, and your headphones plug into the DAC. This means
you have the DAC/AMP sitting on your desk somewhere, and you must have a free
USB port at all times for it, as well as the space to manage the cable that
comes off the headphones (Most higher end headphones come with comically long
cords, and unfortunately one downside to the <a href="https://www.amazon.co.uk/gp/product/B000F2BLTM/ref=as_li_tl?ie=UTF8&amp;camp=1634&amp;creative=6738&amp;creativeASIN=B000F2BLTM&amp;linkCode=as2&amp;tag=kn100-21&amp;linkId=be3feac311ddd1ad1a73d188c173fefa">Beyerdynamic DT880s</a>
(my headphones of choice) is their lack of removable cord).</p>
<p>I also use a <a href="https://www.amazon.co.uk/gp/product/B07YN26PBT/ref=as_li_tl?ie=UTF8&amp;camp=1634&amp;creative=6738&amp;creativeASIN=B07YN26PBT&amp;linkCode=as2&amp;tag=kn100-21&amp;linkId=9b22e11d768ae1d5ded99ff7821fb8ef">ModMic USB</a>. It is a fantastic microphone that sticks to the side of
your nice headphones with a magnet, and plugs in via USB. This of course means
you&rsquo;ve now got two cables coming from your headphones, which of course you are
going to want to cable manage together so they act as one cord, and on the other
end you&rsquo;ve got the headphone port which plugs into the DAC, and the ModMic USB
port which plugs into a USB port. This means you now have two USB ports you have
to have free on your computer at all times, which is not ideal. Ideally, I&rsquo;d plug
both the mic, and the headphones into my cases front IO ports.</p>
<p>What if instead of this mess, I just got a PCI-E sound card? That way, I&rsquo;d just
plug the headphones into the back of my PC like a normal person and be done with
it. This solution is almost certainly the better solution for those who have a
spare PCI-E slot, but I do not. My desktop computer is an ITX build (really
small!), and has exactly one PCI-e slot which is used for a graphics card.</p>
<h1 id="my-awful-solution">
  <a class="Heading-link u-clickable" href="/putting-usb-dac-inside-computer/#my-awful-solution">My awful solution</a>
</h1>
<p><strong>Before I continue to explain what I did, I think you probably shouldn&rsquo;t do this.</strong> It&rsquo;s a dumb idea, and is potentially dangerous for your hardware. If you&rsquo;re not completely confident with what you&rsquo;re doing, a number of things could go wrong. If you connect your DAC to your motherboard incorrectly, you could damage your motherboard or your DAC, or both. I obviously take no responsibility. If you do end up doing something like this, let me know and tweet at me!</p>
<p>I wanted to make use of my cases front headphone port, as well as an internal
USB header. They weren&rsquo;t seeing any use anyway, so I set about creating two
cables which would allow me to mount my DAC inside my computer, so I could
forget about it and just plug one headphone port and one USB port into the front
panel of my computer.</p>
<p>Firstly, I needed to connect the DAC to a USB header. Of course, internally most
motherboards do not have standard USB ports, but what they do have is USB
headers which are usually used to connect to your cases USB ports. In fact, my AMD
motherboard has what it calls a &ldquo;AMD USB LED&rdquo; header, which as far as I can tell
is designed so those with certain types of AMD coolers with LED lighting can plug their
cooler into that USB header, allowing software control of the lighting.</p>
<figure><img src="/posts/dachack/dachack-usb-on-mobo.png"
         alt="A photo showing the `USB-5` header on an Asrock B450m ITX-ac motherboard"/>
</figure>

<p>I do not personally bother with lighting inside my computer, so this port was
free. I hacked together a USB-C to USB2 header connector as shown in the diagram
below, and tested it worked by connecting my phone to it. Surprise, it&rsquo;s just a
USB header. This meant I could now connect my DAC to a USB header inside the
case, freeing up a USB port.</p>
<p>All you need to determine on any USB header to know which way to connect the
wires is which is the 5v and which is the ground pin. They will both be on the
edges of the header, so you can easily check this with a multimeter.</p>
<figure><img src="/posts/dachack/dachack-usbc-cable.png"
         alt="The USB C to female Dupont connectors cable thing I made"/>
</figure>

<p>Next, I needed some way of connecting the &ldquo;HD audio&rdquo; connector from my case to
my DAC. It turns out HD audio is just normal headphone audio (one wire for left,
one for right, one for ground), plus a bunch of other pins unnecessary for my
purposes (the &ldquo;sense&rdquo; pins which sense when a device is plugged into the
headphone port, as well as a second set of identical pins but for the microphone
port, both unnecessary for our purposes here). So I soldered together a cable
which has a male AUX port on one end that is plugged into the DAC, and a few
male Dupont pins on the other end (the kind you&rsquo;d plug into a breadboard) that
we&rsquo;ll jam into the HD audio connector.</p>
<p>I spliced an aux cable, and used a multimeter and a diagram of which part of the
aux jack was for what to determine which wire was which. There doesn&rsquo;t seem to
be a standard colour scheme as there is for USB so this step definitely requires
a multimeter. This one is probably safe to try to determine via trial and error,
however. YMMV.</p>
<figure><img src="/posts/dachack/dachack-hd-audio-cable.png"
         alt="The Aux to male dupont cable I made, as well a diagram of which pins you plug in to jack into the HD audio port"/>
</figure>

<p>I then used some double sided tape to mount the DAC inside my case, and plugged
my headphones and USB microphone into the front panel ports. This setup is
significantly cleaner, and means I&rsquo;ve effectively added a high end sound card to
a build that definitely couldn&rsquo;t fit a proper PCI-E sound-card.</p>
<p>I&rsquo;d love to know if anyone else pulls off this silly trick, so please tweet me or
send me an email if you do!</p>
]]></description>
    </item>
    
    <item>
      <title>Taking my data back from Eufy</title>
      <link>http://kn100.me/taking-back-data-from-eufy/</link>
      <pubDate>Fri, 02 Jul 2021 21:00:00 +0100</pubDate>
      
      <guid>http://kn100.me/taking-back-data-from-eufy/</guid>
      <description><![CDATA[<p>Firstly, you might want to check out <a href="https://kn100.me/weight-loss/">the dashboard</a>.</p>
<p>I recently started a diet. One thing I decided this time around was to be
heavily data driven in my weight loss, and therefore I decided to get some
&lsquo;smart&rsquo; weighing scales. I got the Eufy P1 scales which seemed to be reasonably
well reviewed, and it turns out that Eufy is another name for Anker, another
Chinese brand I&rsquo;ve had success with in the past. I was a little irritated though
when I figured out there was absolutely no way to actually get your data out of
the app they provide. I wanted to get the data out of the app since the graphs
that the app provides were woefully inadequate, and to me were useless. This set me
on the path of building a pretty fun Goldberg-esque system to extract the data.</p>
<figure><img src="/posts/fu-eufy/flow.png"
         alt="A diagram showing the flow of data through each component of what is described in this blog post"/>
</figure>

<h2 id="what-it-does">
  <a class="Heading-link u-clickable" href="/taking-back-data-from-eufy/#what-it-does">What it does</a>
</h2>
<p>The whole process is started when I weigh myself in the morning. After I
conclude weighing myself, I take a screenshot of the screen which shows my stats
from that weighing session.</p>
<figure><img src="/posts/fu-eufy/1.jpg"
         alt="A screenshot from the eufyLife app"/>
</figure>

<p>Next, I have Nextcloud set up locally on a home server, and my phone is
configured to automatically upload all screenshots to my Nextcloud instance. My home
server then will occasionally (via a cron job) upload the screenshots to my
server that hosts kn100.me.</p>
<p>Once the screenshot has found its way to my server, a Go service I wrote notices
the new file, and kicks into action. It immediately calls out to a Python script
which does some pretty crazy things which I will describe in more detail later,
and then returns a JSON representation of the data in the screenshot. The Go
program then takes that JSON from stdout (sorry), and writes it to a Sqlite DB,
and eventually the frontend will make a call out to an endpoint which returns
more different JSON for the graphs to display.</p>
<h2 id="what-about-that-python-script">
  <a class="Heading-link u-clickable" href="/taking-back-data-from-eufy/#what-about-that-python-script">What about that Python script?</a>
</h2>
<p>This is fun. My first attempt was completely written in Go. As you can see from
the screenshot above, there are metrics spread evenly throughout the image. What
I decided to do was to attempt to perform Optical Character Recognition on each
metric. I found Tesseract to be a pretty good candidate for OCR, but running the
entire screenshot through Tesseract was not an option, since the results of
that, were&hellip;not fantastic. Tesseract seems mostly optimised for parsing blocks
of text like pages from a book or receipts. Not so good for parsing freeform
screenshots with text in various colours.</p>
<p>Instead, I firstly wrote some code that given some measurements from a
screenshot, calculates where each metric is, and then crops out a square around
each.</p>
<figure><img src="/posts/fu-eufy/2.png"
         alt="An example of what gets cropped out of the screenshot, 3 numbers which read &#39;82.4kg, 25.4, and 72.2%"/>
</figure>

<p>I then ran these through Tesseract, and got merely OK results. It seemed that
Tesseract wasn&rsquo;t getting on with the fact the unit text was smaller than the
metric itself, and was occasionally confusing the last digit and the first part
of the unit text to be part of the same character.</p>
<p>I thought about it for a while, and decided I needed some way of trimming off
that unit from the end. All I cared about were the numbers, given I knew what
each number at each position meant. I firstly tried a Tesseract whitelist, which
would tell Tesseract only to look for certain characters. I set the whitelist to
only allow digits and periods. This helped a bit, but occasionally the unit text
would then be picked up as numbers, messing everything up. I really didn&rsquo;t want
to get into writing my own bounding box logic however, so I thought about cheap
hacks to get rid of the smaller text to the right.</p>
<p>What I was doing in the beginning was silly. What I&rsquo;d do is enormously gaussian
blur the image, increase the contrast, and then sharpen it. Because the smaller
details in the image (the unit text) would be less dark on the blurred image,
when I re-sharpened it, those details would mostly be gone. By fine tuning how
much I blurred the image along with how much brighter I made it, I was able to
achieve surprisingly acceptable results. This improved OCR accuracy
significantly. This approach had its own problems though, and would either
remove the periods, or the remnants of what was left of the unit text would be
interpreted as characters. I did try fudging it by attempting to parse the
output to add in the dot where it would likely be, and to trim trailing dots,
but still the accuracy wasn&rsquo;t up to the standard I expected. Below is a visualisation of the blur, increase contrast, sharpen approach I was taking</p>
<figure><img src="/posts/fu-eufy/3.png"
         alt="A visualisation of the blur, increase contrast, sharpen approach I was taking"/>
</figure>

<p>For illustrative purposes I tried to reproduce the effect in Gimp,
but wasn&rsquo;t as successful as I was in code. The above above was as good as I got.
You can see all four stages of the process here though. Firstly we blur the crap
out of the metric. Then, we jack the contrast way up, and then we sharpen. I was
actually able to get much better results programatically by fine tuning the
numbers. The results I got from the blur, increase contrast, and sharpen approach were far better than the above visualisation lets on.</p>
<figure><img src="/posts/fu-eufy/4.png"
         alt="The results I got from the blur, increase contrast, and sharpen approach were far better than the above visualisation lets on"/>
</figure>

<p>I was at my wits end at this point, and in my frustration joined <code>#tesseract</code> on
Libera, an IRC network. I immediately asked my question, and had an absolutely
hilarious conversation with a very kind soul there about my problem. Why
hilarious you ask? Because <code>#tesseract</code> has absolutely nothing to do with the
OCR software, and instead is a community around an open source FPS game.
Amusingly, one of the members there still provided me with an absolutely genius
solution which almost completely solved my problem.</p>
<blockquote>
<p><strong>kn100:</strong> I&rsquo;ve got images which look like this
<a href="https://nextcloud.kn100.me/s/8enRb4d9KQ2z2oE">https://nextcloud.kn100.me/s/8enRb4d9KQ2z2oE</a> and all I want to do is detect the
numbers, not the &lsquo;kcal&rsquo; portion. With a whitelist set to just numbers, it
occasionally detects the &lsquo;kcal&rsquo; portion as numbers - I&rsquo;m guessing Tesseract is
choking on the fact there is a size difference in the font. Any suggestions on
how I can configure Tesseract to better handle this? It&rsquo;s not really an option
to crop off the kcal before feeding it to tesseract - without something smart
since it is unknown where exactly the numbers and the text appear in the image</p>
<p><strong>@graphitemaster:</strong> has changed the topic to: Tesseract |
<a href="http://tesseract.gg">http://tesseract.gg</a> | Open-Source FPS Game | First Edition (May 11, 2014)
released! | <em>not</em> tesseract-ocr | <strong>Calinou:</strong> we do OCR using pulse rifles here</p>
<p><strong>@graphitemaster:</strong> <strong>kn100</strong>, Afraid you got the wrong channel. The topic
wasn&rsquo;t up to date since the channel moved from Freenode to Liberachat. This is a
video game, not the OCR software. Though to answer your question, you could
preprocess the input with opencv in Python first to remove the kcal before
passing to Tesseract.</p>
<p><strong>@graphitemaster:</strong> I&rsquo;ve done some OCR stuff before to scrape codes from
still frames and had similar problems. I found it was easier to just preprocess
the input first with some CV to normalize and make Tesseract&rsquo;s job easier.
Something like that, if you use cv2, transform to greyscale, then use
cv2.findContours, you&rsquo;ll get actual bounding boxes around each character, you
can very quickly tell that some of the characters will have a smaller area
(maybe height is a better criteron) and you can just throw them out.</p>
<p><strong>kn100:</strong> <strong>graphitemaster</strong> colour me surprised that I got help from you at all
then :D Thank you so much, and sorry for misunderstanding :P</p>
<p><strong>@graphitemaster:</strong> Stick around, might be some code in a minute XD</p>
<p><strong>kn100:</strong> that is rather hilarious though. I might have to give Tesseract a go!</p>
<p><strong>@graphitemaster:</strong> <strong>kn100</strong>, <a href="https://pastebin.com/raw/i6Zj9ebZ">https://pastebin.com/raw/i6Zj9ebZ</a> - t.jpg is your input</p>
</blockquote>
<p>Their suggested approach was to use OpenCV. With OpenCV, I could do something a
little more clever than just blurring and resharpening the image. Instead, I
could use OpenCV to to find contours in the image (the edges of areas of high
contrast), and measure the area of the bounding box around each contour. If a
given contours bounding box wasn&rsquo;t more than half of the area of the largest
we&rsquo;ve seen, we can throw away the pixels at that location. What was left were
just the characters of the metric, which could then be stitched together again.
This left me with just the numbers.</p>
<p>This was absolutely wonderful, and I immediately tried to port this approach to
Go. Problem is, (and I mean no disrespect to the OpenCV devs!), OpenCV is a
large beast. The lib that interfaces with it for Go was one called GoCV, and I
just couldn&rsquo;t get it to work. It was definitely something I was doing wrong but
I was more interested in solving the problem at hand, so I quickly decided
to do all the data processing in a Python script, which I would call out to from
Go. It was a little messy, but whatever, it would work!</p>
<p>Onto Python, it worked great! There&rsquo;s a package called <code>opencv-python</code> which
brings along some precompiled binaries, which worked for my use case. Upon
testing the approach that was kindly suggested to me in <code>#tesseract</code>, I realised
it only partially solved my problem, as it omitted periods in metrics. This was
easily rectifiable however, by adding a lower bound to the area of characters
that get thrown away. Again, by fine tuning, I was able to reliably extract just
the number portion of the screenshot.</p>
<p>Next up was the date parsing. This would be straight forward, but the EufyLife
app has a fairly insane date format that seems to arbitrarily change. Sometimes
days would have one digit, sometimes they&rsquo;d have two. Sometimes hours would have
one digit, sometimes they&rsquo;d have two. This led to me writing some fairly
horrific date parsing logic that I&rsquo;m not proud of to get the date into a regular
format to be parsed.</p>
<p>Past this point, the OCR results were almost perfect, but still not 100%. Often,
the OCR would confuse the digit &lsquo;5&rsquo; for &lsquo;9&rsquo;. What seemed to help a lot here was
&rsquo;eroding&rsquo; the text to reduce how &lsquo;bold&rsquo; it was, as well as introducing more
space between each character. After this, the output from Tesseract has been
entirely accurate for every metric I&rsquo;ve checked.</p>
<h2 id="deploying-it-somewhere">
  <a class="Heading-link u-clickable" href="/taking-back-data-from-eufy/#deploying-it-somewhere">Deploying it somewhere</a>
</h2>
<p>One thing I was not prepared for is what a pain in the proverbials deploying
Python code is. I&rsquo;ve gotten so used to compiling a Go binary and having it just
work, that I wasn&rsquo;t prepared for deploying Python. Suffice to say, I ended up
containerising it, but for reasons I am currently not completely aware of, the
Docker container has to have a bunch of X11 related dependencies installed for
OpenCV to work. I know that I&rsquo;ve got some learning to do here. Python is not a
language I&rsquo;ve ever really used before, but writing Python was fairly enjoyable.
I can see why Data people seem to really like it. I might end up rewriting the
entire lot in Python, or I might end up trying harder to get OpenCV to work in
Go, but for now this solution means I can weigh myself in the morning, take a
screenshot, and about 20 seconds later it&rsquo;s in a dashboard with graphs that
aren&rsquo;t entirely useless. Result!</p>
<h2 id="a-message-to-eufy">
  <a class="Heading-link u-clickable" href="/taking-back-data-from-eufy/#a-message-to-eufy">A message to Eufy</a>
</h2>
<p>Please, please provide some easy way to access this data. I don&rsquo;t care if it
gets exported as a CSV or if I have to query some API, just please give your
users their data on request. Your in app graphs are absolutely useless, and even
if they weren&rsquo;t, why not just give this data up? For people like me who are a
bit insane, we like data. we collect stupid amounts about ourselves. Your
product becomes infinitely more valuable when you allow the user to extend it as
they see fit. I know you&rsquo;ve got the data, you&rsquo;re logging it to some form of API
somewhere, and even exporting it to Google Fit/Fitbit/Apple Health. Why not just
allow me to query that API? The data is mine, give it to me!</p>
]]></description>
    </item>
    
    <item>
      <title>Ok, Ok, I&#39;m Turning uPnP off.</title>
      <link>http://kn100.me/turning-upnp-off/</link>
      <pubDate>Tue, 06 Apr 2021 00:00:00 +0100</pubDate>
      
      <guid>http://kn100.me/turning-upnp-off/</guid>
      <description><![CDATA[<p>I recently wrote a blog post concerning my discovery that my <a href="https://kn100.me/terramaster-nas-exposing-itself-over-upnp/">NAS had decided to expose itself all over the internet</a>. If you haven&rsquo;t seen that, it&rsquo;s probably worth reading first. That post took off in a way I wasn&rsquo;t quite expecting, and of course I didn&rsquo;t quite dot my Is and cross my T&rsquo;s as well as I should have, leading to a fair bit of misunderstanding in the HN post that initially kicked all this off. Hopefully I can address some of this here. I certainly didn&rsquo;t think this would <a href="https://nvd.nist.gov/vuln/detail/CVE-2021-30127">get a CVE</a>!</p>
<figure><img src="/posts/upnp-off/turning-upnp-off-3.png"
         alt="A graph showing the number of visitors to my blog, showing a significant uptick when the last blog post was published"/>
</figure>

<p>Firstly, I&rsquo;ll recount my contact with Terramaster. On Feb 21st 2021 I contacted Terramaster informing them of the issue. I contacted them again soon after again letting them know that changing the web server port does not help. I then contacted them a third time letting them know that this could be fixed fairly easily with a simple change to <code>/etc/upnp.json</code> as I mentioned in my previous post.</p>
<p>Terramaster replied on Feb 22nd saying they&rsquo;d get someone to look into it, but also stated that this was intentional behavior:</p>
<blockquote>
<p>Hello Kevin,</p>
<p>Thank you for contacting TerraMaster.</p>
<p>As for the mentioned security issue, it has been transferred to the R&amp;D department, they will test here and fix it if it’s confirmed.</p>
<p>For port 8181 and 5443, it’s opened by default for it’s the default port of TOS webpage access.</p>
</blockquote>
<p>I replied:</p>
<blockquote>
<p>Hi,</p>
<p>Thanks for the confirmation that somebody will look into this.</p>
<p>Opening these ports via UPNP is not necessary for local access, and in fact makes the port accessible worldwide, externally.</p>
<p>I can also access my NAS via http://some-public-ip:8181 - on the public internet.</p>
<p>I hope that makes things clearer. Please me me know if you decide to change this behavior and if you do plan to fix this, please let me know what kind of timeline you&rsquo;re working to. I&rsquo;d like to publish a blog post about this (specifically around the danger of UPNP) once you&rsquo;re happy everything is secure.</p>
</blockquote>
<p>I then made the blog post 6 weeks later, hoping it might motivate them into fixing it. It sparked a lot of interesting discussion on <a href="https://news.ycombinator.com/item?id=26681984">Hacker News</a> and <a href="https://www.reddit.com/r/DataHoarder/comments/mk3l0i/terramaster_nas_exposing_itself_with_upnp_over/">Reddit /r/datahoarder</a>. It blew up way bigger than I expected, and is by far the best performing blog post I&rsquo;ve written. Pretty sad considering I threw it together in under an hour and didn&rsquo;t even spell check it!</p>
<p>The issue specifically is that while the NAS claims its web interface is only locally accessible, it is actually accessible from anywhere in the world to anyone who knows the public IP of the network it is sitting on. This is because most consumer grade routers like the one I use have a feature called uPnP, which allows applications on your internal network to punch open ports that they need to operate. This feature is supposed to be useful for things like VOIP applications and gaming. The NAS for some reason decided to punch itself some ports open so that it could be internet accessible.</p>
<p>An interesting detail is that it punched more ports than you&rsquo;d expect. It punched the default port that the web interface runs on (8181), a port for an SSL configured web server (5443), and then it also punches ports for 9091, as well as port 8800, which I&rsquo;m not sure what the purpose is. My guess is that these ports were being used by a Terramaster developer, and they didn&rsquo;t bother to remove the config to punch them before releasing the software.</p>
<p>Some suggested I just disable uPnP on my router, but I personally believe this kind of misses the point. I am a software engineer who regularly dabbles in infrastructure, and therefore could reasonably be expected to figure out how to disable uPnP. Thinking about the average consumer, who wouldn&rsquo;t have any idea what port forwarding or uPnP is, I&rsquo;m not so sure they could.  Disabling uPnP definitely closes this particular security hole, but the vast majority of consumer routers are going to have this feature on, and therefore the vast majority of these NAS products in the wild are likely exposing themselves unwittingly. I am hoping that Terramaster will fix this issue in their product, and therefore unwitting users who aren&rsquo;t aware of what uPnP is suddenly find themselves more secure, assuming they update!</p>
<p>Others suggest buying a new router which would support having two separate LAN networks so that this gear could be put on a LAN that is denied direct access to the internet/not have uPnP. This again I don&rsquo;t see regular consumers doing en masse. I could do it, and very well may in the future, but I don&rsquo;t really feel the need to upgrade my networking gear currently. It works, and I am happy with it.</p>
<p>This is why I went spelunking into the device itself. Thankfully Terramaster provides root on these boxes, which is nice of them. This led me to discover the fix I did.</p>
<p>Either way, one detail I got the most comments about was whether disabling uPnP was really a hit to convenience. Somebody emailed me questioning this specifically, as well as pointing out that <a href="https://www.grc.com/default.htm">GRC has a check</a> to make sure your router isn&rsquo;t allowing services on the internet to punch open ports on your router to hosts behind it. I had no idea this was a problem, and facepalmed pretty hard that this ever was, but nevertheless I replied:</p>
<blockquote>
<p>If I&rsquo;m being totally honest with you, this was more of a &lsquo;feeling&rsquo; than it was an analytical decision. I should have known better than to assume I&rsquo;d get away with that in a blog post like this, but I wasn&rsquo;t quite expecting it to blow up the way it did!</p>
<p>I am reasonably confident in my router <em>(author sidenote: lol)</em>, it&rsquo;s actually not the trashiest ISP provisioned router I&rsquo;ve ever had and the GRC uPnP exposure check is not flagging anything up.</p>
<p>I am strongly considering writing a blog post in future addressing this though - essentially my plan is to disable uPnP without making any other changes and seeing what breaks, if anything. It is completely possible I am very wrong that this is necessary these days. I am already port forwarding certain stuff I need externally accessible, but I&rsquo;d be curious if the various consumer level stuff I&rsquo;ve got floating around manages to keep working.</p>
<p>Thanks for contacting me!</p>
</blockquote>
<p>So here I am, money where my mouth is. I&rsquo;ve disabled uPnP and NAT-PMP on my router entirely.</p>
<figure><img src="/posts/upnp-off/turning-upnp-off-1.png"
         alt="A screenshot showing both UPnP and NAT-PMP being disabled in my router"/>
</figure>

<p>Turning uPnP off led to some rather amusing error messages in my routers debug log</p>
<figure><img src="/posts/upnp-off/turning-upnp-off-2.png"
         alt="A screenshot of some error messages which read &#39;Why did you run me anyway?, could not open lease file: /var/upnp.leases, Reloading rules from lease file&#39;"/>
</figure>

<p>I&rsquo;ll come back to this in a months time and see whether there has really been a hit to &lsquo;convenience&rsquo; or whether I was truly wrong on this. I&rsquo;m always happy to learn!</p>
]]></description>
    </item>
    
    <item>
      <title>Terramaster NAS exposing itself with UPNP</title>
      <link>http://kn100.me/terramaster-nas-exposing-itself-over-upnp/</link>
      <pubDate>Sat, 03 Apr 2021 00:00:00 +0100</pubDate>
      
      <guid>http://kn100.me/terramaster-nas-exposing-itself-over-upnp/</guid>
      <description><![CDATA[<blockquote>
<p>Addendum: The most controversial detail in this blog post so far has been my claim that disabling uPnP is a significant hit to convenience. I wanted to be fully transparent and say that I do not have data to back this claim up, and this is more of a feeling than it is an analytical decision. This criticism somewhat misses the point however, since the vast majority of consumer routes I&rsquo;ve encountered personally have uPnP enabled by default, and the consumers purchasing this NAS might have no idea what uPnP is or how to disable it. I plan a blog post in future where I will disable uPnP without making any other changes, to see if anything breaks at all, because it is completely possible my opinion on this is wrong.</p>
<p>I have released another blog post related to this one, which provides more detail: <a href="/turning-upnp-off/">Ok, Ok, I&rsquo;m Turning uPnP off.</a></p>
<p>Terramaster appear to have released a fix for this, although I have not tested it yet. <a href="https://forum.terra-master.com/en/viewtopic.php?f=28&amp;t=1813&amp;sid=253a445ed3e80962022508229df8b37a">Read more</a></p>
</blockquote>
<p>I recently bought a Terramaster F2-210. It&rsquo;s a reasonably nice NAS that does what I ask of it. I however discovered something which unsettled me. As I&rsquo;ve discussed in previous articles <a href="/exploiting-upnp-literally-childsplay/">uPnP is a convenience that can be particularly dangerous</a>. These NAS products are generally administrated using a web interface and The Terramaster TOS software is no different. The software requests you visit the hostname of the device on your network port 8181 in order to access the NAS interface, and the interface openly claims the NAS is not publicly accessible.</p>
<figure><img src="/posts/tnas-upnp/terramaster-upnp-0.png"
         alt="A screenshot from within the NAS user interface that reads &#39;Notes: Enter the following address in your browser to access TOS; Tt is only available only on the local network. http://horse.local Or http://192.168.0.198&#39;"/>
</figure>

<p>A few days after installing the NAS, I discovered I could access the NAS using my public IP, even though I hadn&rsquo;t port forwarded anything! Upon inspecting my routers port forwarding rules, I identified that the NAS was punching 4 ports using uPnP. It was punching 8181 as we just discovered, but also 5443 which is for SSL access should you have configured it, and inexplicably port 9091, which normally is for Portainer, a container management tool for Docker, as well as 8800 - I&rsquo;m not sure what this port is. It seems that potentially some of these rules were left in from the development process. I trust this NAS to be reliable hardware, however I am dubious of trusting its web interface to the open internet. Generally good practice to expose as little as possible to the public internet anyway!</p>
<figure><img src="/posts/tnas-upnp/terramaster-upnp-1.png"
         alt="A screenshot from a router admin interface that shows 4 port forwarding rules, forwarding traffic on ports 5443, 8181, 9091, and 8800 to the NAS"/>
</figure>

<p>Unfortunately, disabling uPnP these days is too much of a hit to convenience, so I looked for other solutions. My router is an ISP provisioned one so the feature-set there is somewhat limited, so I wanted to prevent the NAS from opening these ports rather than firewalling them off at the router.</p>
<p>I dug through the NAS interface and was not able to discover a way to disable this behaviour, since I didn&rsquo;t really want my NAS administration interface publicly accessible. What I did discover was that the default web server port was at least configurable, so I changed it, and checked the uPnP port mappings again. Somewhat surprisingly, it punched holes for these new ports, but didn&rsquo;t clear the existing ports! The NAS uPnP rules it punches are 5443, 8181, 9091, 8800, 54633, and 54632&quot;.</p>
<figure><img src="/posts/tnas-upnp/terramaster-upnp-2.png"
         alt="A screenshot from a router admin interface that shows 4 port forwarding rules, forwarding traffic on ports 5443, 8181, 9091, 54633, 54632 and 8800 to the NAS"/>
</figure>

<p>This annoyed me, so I contacted Terramaster about this 6 weeks ago, hoping they&rsquo;d have a suggestion or something as a fix, but this wasn&rsquo;t supplied, and therefore I went digging myself.</p>
<p>Upon SSHing into the NAS and having a dig around the file system, I discovered a file that could be modified. /etc/upnp.json seems to contain a list of port forwarding rules. Thank you to Terramaster for providing root access to these at least. Simply change <code>bEnable</code> to 0 for whatever ports you don&rsquo;t want exposed, reboot the NAS, and check the port forwarding rules.</p>

<pre tabindex="0"><code>   &#34;triestimes&#34;: 3,
   &#34;mapList&#34;: [
       {
           &#34;desc&#34;: &#34;ftp&#34;,
           &#34;nExternalPort&#34;: 6221,
           &#34;nInternalPort&#34;: 21,
           &#34;sProtocol&#34;: &#34;TCP&#34;,
           &#34;bEnable&#34;: 0
       },
       {
           &#34;desc&#34;: &#34;ftp_data&#34;,
           &#34;nExternalPort&#34;: 2000,
           &#34;nInternalPort&#34;: 20,
           &#34;sProtocol&#34;: &#34;TCP&#34;,
           &#34;bEnable&#34;: 0
       },
       {
           &#34;desc&#34;: &#34;sshd&#34;,
           &#34;nExternalPort&#34;: 22,
           &#34;nInternalPort&#34;: 22,
           &#34;sProtocol&#34;: &#34;TCP&#34;,
           &#34;bEnable&#34;: 1
       },
       {
           &#34;desc&#34;: &#34;telnetd&#34;,
           &#34;nExternalPort&#34;: 23,
           &#34;nInternalPort&#34;: 23,
           &#34;sProtocol&#34;: &#34;TCP&#34;,
           &#34;bEnable&#34;: 0
       },
       {
           &#34;desc&#34;: &#34;http_ssl&#34;,
           &#34;nExternalPort&#34;: 54633,
           &#34;nInternalPort&#34;: 54633,
           &#34;sProtocol&#34;: &#34;TCP&#34;,
           &#34;bEnable&#34;: 0
       },
       {
           &#34;desc&#34;: &#34;http&#34;,
           &#34;nExternalPort&#34;: 54632,
           &#34;nInternalPort&#34;: 54632,
           &#34;sProtocol&#34;: &#34;TCP&#34;,
           &#34;bEnable&#34;: 0
       },
       {
           &#34;desc&#34;: &#34;pt&#34;,
           &#34;nExternalPort&#34;: 9091,
           &#34;nInternalPort&#34;: 9091,
           &#34;sProtocol&#34;: &#34;TCP&#34;,
           &#34;bEnable&#34;: 0
       },
       {
           &#34;desc&#34;: &#34;http_pri&#34;,
           &#34;nExternalPort&#34;: 8800,
           &#34;nInternalPort&#34;: 8800,
           &#34;sProtocol&#34;: &#34;TCP&#34;,
           &#34;bEnable&#34;: 0
       }
   ]</code></pre>
<p>This almost completely resolves the problem, however it seems the ports 8181 and 5443 remain punched, but result in a 404, since we moved the web server earlier. This is better than before, but still not perfect. I await further instructions from Terramaster/a software update, and will update this blog post should I get this.</p>
]]></description>
    </item>
    
    <item>
      <title>Declouding my life - Replacing Google Photos</title>
      <link>http://kn100.me/declouding-replacing-google-photos-part-1/</link>
      <pubDate>Mon, 01 Feb 2021 00:00:00 +0100</pubDate>
      
      <guid>http://kn100.me/declouding-replacing-google-photos-part-1/</guid>
      <description><![CDATA[<p>Over the last couple of years, I&rsquo;ve been trying to reduce my dependence on
cloud services. This is purely ideological, I have no real reason to distrust
the cloud, nor am I going to attempt to convince you to. I just made a decision a
while ago that all my mission critical/sensitive stuff needed to be moved off of
centralised cloud storage. <a href="https://www.pcgamer.com/uk/terraria-creator-cancels-stadia-port-after-being-locked-out-of-google-account/">The stories of people losing their Google accounts
for practically no reason scare me</a>.</p>
<p>One particularly scary change that happened recently was Google Photos deciding
to change their &lsquo;unlimited&rsquo; storage plan to <a href="https://blog.google/products/photos/storage-changes/">not be unlimited anymore</a>. This was
obviously going to happen at some point, but frankly I feel like companies need
to stop pretending like they can offer unlimited use of something unless they
plan to honour that for the lifetime of the service. Either way, I&rsquo;ve always
felt pretty uncomfortable with having my photos being hosted and managed by
someone else, so I started to explore alternatives.</p>
<h2 id="storing-the-photos">
  <a class="Heading-link u-clickable" href="/declouding-replacing-google-photos-part-1/#storing-the-photos">Storing the photos</a>
</h2>
<p>The first problem I had to solve is storing the photos. All in all, after doing
a <a href="https://takeout.google.com/settings/takeout?pli=1">Google Takeout</a> (to get the photos out of Google Photos) - it turns out I have
about 50GB of media. A lot of it is crap for sure. I didn&rsquo;t want to store these
files on my main computer, since I regularly wipe and reinstall the operating
system on my main computer, and therefore having to shift 50GB of data around to
avoid wiping it would prove to be a pain. I also don&rsquo;t back the entirety of my
computer up, given I feel no need to. I back up the most important stuff, and
that is it.</p>
<p>The solution I came upon was a NAS. I ended up purchasing the <a href="https://amzn.to/2N9qvoN">Terramaster F2-210
NAS</a>. It&rsquo;s pretty much one of the cheapest NAS units one can get here in the UK,
costing me around 100 GBP. I also picked up 2 <a href="https://amzn.to/3oZt2PT">4TB Toshiba N300</a> drives, being the
cheapest CMR drives at that size range you could get. You absolutely should make
sure you don&rsquo;t end up with SMR drives, they&rsquo;re inappropriate for this sort of
use case and your NAS performance will suffer. To understand why, read <a href="https://www.servethehome.com/wd-red-smr-vs-cmr-tested-avoid-red-smr/">this
excellent blog post from ServeTheHome</a>. If you need larger CMR drives,
maybe consider the large <a href="https://amzn.to/3jwgP3O">8GB Toshiba N300 drive</a> or if
money isn&rsquo;t too much of an issue they <a href="https://amzn.to/36T9iHf">go all the way to
14TB</a>. The 4TB drives I got were around 100 GBP
each, bringing the total cost for this setup to around 300GBP. Not cheap, but
given I had no decent redundant storage, I felt it a price worth paying.</p>
<p>This particular NAS is an ARM based unit, with a ARM V8 64-bit quad-core
processor, with a frequency of up to 1.4 GHz, 1GB of RAM, and comes running
Terramaster OS, which seems to just be a pretty light distribution of Linux. I&rsquo;d
much prefer running my own OS on such a unit, but part of the reason I purchased
a NAS rather than building my own was I wanted something that &ldquo;Just worked&rdquo; out
of the box. It&rsquo;s got two hard drive bays, and supports both EXT4 and BTRFS for
the underlying file system.</p>
<p>If you&rsquo;re going deeper down this rabbit hole and want something a bit more
extreme, you could get one of the Intel 5 bay NAS units, Like the <a href="https://amzn.to/2Oe8rul">Terramaster
F5-220 unit</a> which sports an Intel processor which will
be significantly faster, and some are even <a href="https://www.avforums.com/threads/unraid-on-terramaster-bargain-nas-f4-220-chassis.2245155/">experimenting with running FreeNAS on
this unit!</a>.
These Intel based NASes may even come with the possibility of RAM upgrades, as
well as some interesting hardware hackability possibilities.</p>
<figure><img src="/posts/degoogling-p1/tos.jpg"
         alt="A screenshot of the Terramaster OS UI, showing a Docker container for Duplicati running"/><figcaption>
            <h4>Terramaster OS does its best to look like Windows 10, in a browser. It works fine, though!</h4>
        </figcaption>
</figure>

<p>It was pretty painless to set up, just pop the drives in, select whether you
want RAID 0 (where all the storage is available to you) or RAID 1 (where the
data is mirrored between both drives so if a drive fails the data is safe on the
other drive), create some shares, and configure your users access to said
shares. It also supports encryption of particular shares, although please note
an encrypted share will have somewhat lower performance than an unencrypted
share and also you cannot mount unencrypted shares via NFS. You&rsquo;ve gotta stick
with SMB for them. Performance of the NAS was surprisingly good, easily maxing
out the Gigabit ethernet connection it has.</p>
<p>I then migrated all my photos over to the NAS, and was happy, as now they were
somewhat more safely stored than they were prior.</p>

<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
  <iframe src="https://www.youtube.com/embed/0QDENx0-PSg" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" allowfullscreen title="YouTube Video"></iframe>
</div>

<h2 id="what-about-a-house-fire-what-if-the-nas-gets-stolen">
  <a class="Heading-link u-clickable" href="/declouding-replacing-google-photos-part-1/#what-about-a-house-fire-what-if-the-nas-gets-stolen">What about a house fire? What if the NAS gets stolen?</a>
</h2>
<p>One problem with storing your data locally is that if your NAS gets destroyed or
goes missing, your data goes with it. This is why off site backup is important.
I wanted to back my data up but for it to have the following properties:</p>
<ul>
<li>Important personal data is encrypted at rest on the NAS</li>
<li>All data is encrypted at rest on the cloud backup solution</li>
<li>The cloud backup solution is as cheap as possible</li>
<li>The cloud backup solution doesn&rsquo;t do the whole Amazon Glacier thing of
charging you a fortune to retrieve your data</li>
<li>The backing up of data happens automatically</li>
</ul>
<p>For the cloud backup solution, I settled on Backblaze B2. The cost for backing
your data up there is supremely cheap in comparison to Amazon S3, and Backblaze
to me anyway seem like cool people. The cost for backing up 4 whole terabytes of
information there with no retrieval over the course of a year would be around
240$.</p>
<p>I split the data on my nas into two categories: Stuff I&rsquo;d be okay to lose in a
house fire, and stuff I would not be okay with losing in a house fire. The stuff
I&rsquo;m worried about gets backed up to Backblaze, which is around 65GB of data
total. The cost for backing this up over a year is $3.90 - a cost I can
definitely stomach. <a href="https://www.backblaze.com/b2/cloud-storage-pricing.html">You can calculate the cost yourself
here</a>.</p>
<figure><img src="/posts/degoogling-p1/nas.jpg"
         alt="A photo of a Toshiba N300 3.5 inch Hard Drive being inserted into a Silver 2 drive NAS"/><figcaption>
            <h4>The Terramaster F2-210. A Nice nas!</h4>
        </figcaption>
</figure>

<p>As for getting the data to Backblaze, I picked <a href="https://www.duplicati.com/">Duplicati</a>. Duplicati is a great
tool that provides support for many backends (places you back stuff up) along
with many options for how you&rsquo;d like the backed up data to be managed, such as
versioning of files and the such. It encrypts the data before it sends it to
your chosen backend, and can run on a schedule, so it meets all of my
requirements set out above.</p>
<figure><img src="/posts/degoogling-p1/duplicati.png"
         alt="A screenshot of the Duplicati web interface, showing the 4 back ups I have configured"/><figcaption>
            <h4>Duplicati runs in a browser</h4>
        </figcaption>
</figure>

<p>I initially tried to run Duplicati on a <a href="https://amzn.to/3q7edME">Raspberry Pi</a>, which turned out to be a
hellish experience because of the dependencies it needs. I then tried running it
on my main desktop computer, so it would read the files from the NAS and back
them up to Backblaze, but it turns out Duplicati <em>really</em> doesn&rsquo;t like reading
from SMB shares. I tried instead mounting the NAS share via NFS, but as
mentioned previously you can&rsquo;t as far as I can tell mount NFS shares that are
encrypted on the NAS. It was then I remembered that this NAS incredibly supports
Docker, and therefore I could run Duplicati directly on the NAS itself.</p>
<figure><img src="/posts/degoogling-p1/rpi.jpg"
         alt="A photo of a Raspberry Pi, in a red aluminium case"/><figcaption>
            <h4>The Raspberry Pi in question</h4>
        </figcaption>
</figure>

<p>I actually accidentally deleted all my photos from my local computer recently,
which is what spurred this whole project in the first place. Duplicati did a
wonderful job of restoring them, which I was very grateful for given I had not
tested restoring from a backup yet.</p>
<p>Terramaster kindly provides us with Root on the NAS, so setting up the
LinuxServer Duplicati Docker image was a doddle, and the NAS quite happily runs
this Docker image and backs my stuff up to Backblaze early every morning. I&rsquo;m
honestly quite impressed with this little unit. It&rsquo;s built very well, the OS
seems quite reliable, and it&rsquo;s done everything I&rsquo;ve asked of it well so far.</p>
<h2 id="photo-accesssearch">
  <a class="Heading-link u-clickable" href="/declouding-replacing-google-photos-part-1/#photo-accesssearch">Photo access/search</a>
</h2>
<p>One of my favourite things about Google Photos was the fact it did image
classification on images you supplied it so you could search for words like
&ldquo;Cat&rdquo; or &ldquo;Giraffe&rdquo; and it would show you a mostly accurate list of photos of
those specific things. Facial recognition was also creepy, but pretty useful
too. I wanted to have this locally, and the project that spurred this whole
thing was a wonderful open source project called Photoprism.</p>
<p>Photoprism is a piece of software written in Golang which promises to be a photo
organisation/access tool that can run on a Raspberry Pi. It does image
classification using a Tensorflow model, and can manage large libraries of
photos. Running it is as simple as pulling the Docker compose file and building
it.</p>
<p>It is a truly amazing piece of software, but where to run it? I purchased a
<a href="https://amzn.to/3q7edME">Raspberry Pi 4 8GB variant</a> (since Photoprism says it wants at least 4GB of RAM,
and I wanted to run other services on this Pi), and connected it up to a spare
SSD (<a href="https://amzn.to/2MDfrRi">A Kingston A400 480GB drive</a>) in a USB enclosure
(<a href="https://amzn.to/3cQE6fN">A Orico 2.5 inch USB 3 Enclosure</a>) I had lying around which it turns out Raspberry Pi can
boot from now. This offers much improved performance over a MicroSD card and
significantly better reliability. MicroSD cards are notorious for failing in a
Pi, and various blog posts have talked about the improvements in performance and
reliability that can be had by booting from an SSD.</p>
<figure><img src="/posts/degoogling-p1/photoprism.jpg"
         alt="A screenshot showing the Photoprism interface, which is currently showing a grid of images with automatically applied labels below"/><figcaption>
            <h4>Photoprism labelling works impressively well!</h4>
        </figcaption>
</figure>

<p>One thing you should bear in mind is the thermals of your Pi. The Pi 4 runs
pretty hot when working hard, and Photoprism will work it extremely hard. I
purchased a <a href="https://amzn.to/36QWXDK">passive aluminium case</a> for my Pi which does an amazing job of
keeping the Pi cool enough to continue operating at it&rsquo;s stock clock speed of
1.5GHz, but I wanted to overclock my Pi and at a 2.1GHz overclock the case is
overwhelmed. For this, I purchased a <a href="https://amzn.to/3cVGamC">50MM fan</a> which I attached to the GPIO pins
of the Pi and blu-tac&rsquo;d it to the case, and now it does not exceed 60 degrees
under extreme load and operates at 2.1GHz all day. You&rsquo;ll also want a decent
power supply.</p>
<p>After installing Raspbian and doing the standard hardening routine (disable root
account, make a fresh user, disable &lsquo;pi&rsquo; user, fail2ban, etc), I set Photoprism
up on it, and pointed it at my NAS as the photo storage directory, but told it
to store its cache locally on the SSD - since decent performance is probably a
good thing for that.</p>
<p>It took around 3 days total for Photoprism to finish churning through all my
photos and videos, where it generated almost 30GB of thumbnails, but after this
process was complete I ended up with a lovely searchable indexed version of my
photos, that while not quite as slick as Google Photos certainly meets my needs.</p>
<h2 id="adding-more-photos">
  <a class="Heading-link u-clickable" href="/declouding-replacing-google-photos-part-1/#adding-more-photos">Adding more photos</a>
</h2>
<p>Another problem is how do I back up new photos I&rsquo;ve taken? I decided to go
somewhat oldschool on this in the interest of keeping things simple, and set up
Shotwell, a piece of free software which can import photos from your camera or
phone, and organises them into a YYYY/MM/DD folder structure. It avoids
reimporting duplicates, and seems somewhat okay with reading and writing to a
SMB share. This means about once a week, I&rsquo;ll dock my phone, import the photos
using Shotwell, which will then put them on my NAS. At 4am the next day, those
photos are backed up to Backblaze safely, and Photoprism will index them, making
them available for search/retrieval.</p>
<p>This solution isn&rsquo;t ideal, since Shotwell takes forever to be ready to import
photos (assumedly because it&rsquo;s reading the file list and checking nothing has
changed from the SMB share), but it works well enough for me right now.</p>
<h2 id="deleting-the-photos-from-google-photos">
  <a class="Heading-link u-clickable" href="/declouding-replacing-google-photos-part-1/#deleting-the-photos-from-google-photos">Deleting the photos from Google Photos</a>
</h2>
<p>Once I&rsquo;d done all this and was happy with the solution, I decided it was time to
delete all the photos I had from Google Photos. Turns out, there is no button
you can press to just delete all the photos you have, and instead I found a
lovely script on Github called
<a href="https://github.com/mrishab/google-photos-delete-tool">google-photos-delete-tool</a>
which when pasted in the dev tools of your browser, will delete your photos by
automating the process of selecting them and moving them to trash. Be careful
while doing this, since photos which were previously stored under the
&ldquo;Unlimited&rdquo; photo storage plan will now take up space in your bin, and therefore
you&rsquo;ll need to empty your bin every now and then while the script is working. I
had to regularly refresh the page and restart the script too, as it seemed to
hang after deleting about 6 months of photos.</p>
<h2 id="conclusion">
  <a class="Heading-link u-clickable" href="/declouding-replacing-google-photos-part-1/#conclusion">Conclusion</a>
</h2>
<p>Thanks to a combination of a NAS, Raspberry Pi, Duplicati and Backblaze B2, I&rsquo;ve
got a photo management solution that I am fully in control of, that is housefire
proof, and is encrypted at rest everywhere. I&rsquo;m pretty happy about this, and
given I use the Raspberry Pi and the NAS for other uses too (Plex, Home
Assistant, etc) - it is fairly cost effective.</p>
]]></description>
    </item>
    
    <item>
      <title>Declouding Chinese WIFi plugs</title>
      <link>http://kn100.me/declouding-chinese-wifi-plugs/</link>
      <pubDate>Sun, 01 Nov 2020 00:00:00 +0100</pubDate>
      
      <guid>http://kn100.me/declouding-chinese-wifi-plugs/</guid>
      <description><![CDATA[<p>So, it turns out that a lot of smart gear from many manufacturers, including ones
you&rsquo;ve almost certainly heard of, comes from a company called Tuya. They
seem to make all sorts of fun IOT gear, which all connects to the Tuya cloud.
What Tuya seem to do is sell whitelabeled &lsquo;versions&rsquo; of their products to various
brands who then sell them as if they&rsquo;d manufactured them themselves. Very
interesting right?</p>
<p>There exist many projects to de-cloud these products.
One such project is called Tuya-convert. Tuya convert is a tool which emulates
the update server these plugs connect to in order to deliver custom firmware to
the plug that it can run. This project is amazing, since it gives you the option
of declouding IOT gear without having to do any hardware modification at all.
Unfortunately, it seems this project is dead in the water right now since Tuya
is playing the typical cat and mouse game with the developers, and currently
Tuya is winning.</p>
<p>I wanted to open my plug up next, in order to figure out what made it tick.
Opening it was fairly difficult, given that it is held together with nothing but
clips. After running a guitar pick around the seam a few times, I finally
managed to pop the cover. What I found really surprised me. There was a board in
there that looked suspiciously like an ESP based platform. Further searching led
me to realise that the board in there that handles all the &lsquo;smart&rsquo; of the plug
is actually an implementation of the ESP8285 - which is a cheaper (but just as
hackable) variant of the ESP8286, which is related to the ESP32.</p>
<p>Consulting the easily accessible datasheet for the TYWE-2s - we can quickly
identify the serial pins, and solder wires to them. Then, we can connect it up
to some Serial to USB adaptor and could flash whatever code we wanted to the ESP.
I however wanted a nicer solution. I found Tasmota. Tasmota is another open source
project that runs on these plugs that allows you to connect them up to HomeAssistant
or similar. It works really well. Read on for the process:</p>
<ol>
<li>Buy a WiFi plug.</li>
</ol>
<figure><img src="/posts/plug-hack/plug-package.jpg"
         alt="A photo of a white square plug inside blue retail packaging"/>
</figure>

<ol start="2">
<li>Open it up, in order to figure out where the serial pins are. We can see on mine,
there is a nice TYWE-2S module which unfortunately due the construction of this plug
has awkward to access serial pins.</li>
</ol>
<figure><img src="/posts/plug-hack/plug-board-1.jpeg"
         alt="A photo of the circuit board inside the plug, showing the relay, the esp8285, and the bare pass through terminals for mains power"/>
</figure>

<figure><img src="/posts/plug-hack/plug-board-2.jpeg"
         alt="A photo showing the pins we need to access on the esp8285"/>
</figure>

<p>Above is a view of the pins we need access to. Unfortunately, the two options you have for getting access to them are to desolder the enormous blobs of solder holding the mains plug pins on, or to cut into the case. I went with cutting into the case. Ugly, but works.</p>
<ol start="3">
<li>Put it back together, and cut a hole where the pins you need are. Below is a view of the hole I cut. I cut it using a hacksaw and a hot knife. Uuuugly.</li>
</ol>
<figure><img src="/posts/plug-hack/plug-hole.jpg"
         alt="A photo showing the hideous hole that was cut into the corner of the plug to access the pins"/>
</figure>

<figure><img src="/posts/plug-hack/plug-pins.jpg"
         alt="A close up showing the pins through the hole I cut earlier"/>
</figure>

<p>See <a href="https://developer.tuya.com/en/docs/iot/device-development/module/wifi-module/we-series-module/wifie2smodule?id=K9605u79tgxug">here</a> for a data sheet to help identify which pins are which.</p>
<ol start="4">
<li>Solder some female jumper wires to the pins we need access to, and connect them to
the serial interface. You&rsquo;ll want to make especially super sure that your interface
is clever enough to support 3.3v logic level input. Otherwise you risk frying the
board or just failing to flash the board repeately. Ask me how I know.</li>
</ol>
<figure><img src="/posts/plug-hack/plug-serial.jpg"
         alt="A photo showing the soldered wires coming through the hole I cut earlier and attaching to a black USB to Serial interface"/>
</figure>

<ol start="5">
<li>
<p>Grab <a href="https://github.com/tasmota/tasmotizer">Tasmotiser</a> and follow the instructions. Make very sure your wifi details are correct, otherwise you&rsquo;ll end up having to flash the plug a second time.</p>
</li>
<li>
<p>Once your Tasmotised plug is up, and you can control it from a web interface, it&rsquo;s time
to interface it with your HomeAssistant install. Your Home Assistant install must already
have the MQTT integration. Ensure you enable <em>discovery</em> in your Home Assistant MQTT config.</p>
</li>
</ol>
<figure><img src="/posts/plug-hack/plug-mqtt-discovery.png"
         alt="A screenshot showing MQTT Discovery in Home Assistant as being enabled"/>
</figure>

<ol start="7">
<li>
<p>Go back to the web server for your WiFi Plug. Configure the MQTT server to connect to
the MQTT server your Home Assistant is connected to.</p>
</li>
<li>
<p>In this same interface, head to console, and type <code>SetOption19 1</code>. This causes the plug
to emit an autodiscovery message which should mean Home Assistant picks up on your plug and
you should now be able to control it in Home Assistant, sans cloud!</p>
</li>
</ol>
]]></description>
    </item>
    
    <item>
      <title>Improving Bluetooth Audio Quality on Ubuntu Linux</title>
      <link>http://kn100.me/improving-bluetooth-audio-linux/</link>
      <pubDate>Mon, 12 Oct 2020 00:25:47 +0100</pubDate>
      
      <guid>http://kn100.me/improving-bluetooth-audio-linux/</guid>
      <description><![CDATA[<p><strong>Note</strong>: While the general themes in this post are still accurate, this blog post has predictably fallen out of date. I will eventually write a follow up for Pipewire, but for now, here be dragons. Yee hath been warned!</p>
<p>Bluetooth Audio is generally considered convenient, but not &lsquo;audiophile&rsquo;. Any self respecting audiophile is probably connecting their fancy headphones to some Digital to Analog Converter/Amp combo which might be connected to their computer using some fancy gold plated USB Cable. More power to them, I am not self respecting. I made a decision a few months ago to switch entirely to Bluetooth Audio as the convenience and tidiness of having a wireless headset just appealed to me so much.</p>
<p>I purchased the M-Pow H21s - which are a fairly budget noise cancelling pair of Bluetooth headphones. They appealed to me because they weren&rsquo;t enormously expensive, and other M-Pow stuff I&rsquo;d purchased in the past was of good enough quality for me to trust these would be too. I wasn&rsquo;t wrong. The noise cancelling is reasonably good, and the sound profile, while not &rsquo;neutral&rsquo; - is totally fine for me to use while working.</p>
<p>One thing that really did annoy me about these headphones (and I&rsquo;m not sure if the fault lies with Bluetooth Audio on Linux or with the headphones themselves) is that if I walked away from my desk and then returned, the audio quality would perceptibly drop, but never quite recover. It seemed to get &lsquo;stuck&rsquo; at a lower quality, until the headset was rebooted. One day, I got tired of this, so decided to research what was going on.</p>
<h2 id="codecs-in-general">
  <a class="Heading-link u-clickable" href="/improving-bluetooth-audio-linux/#codecs-in-general">Codecs in General</a>
</h2>
<p>In the beginning, there was A2DP - or the Advanced Audio Distribution Profile. This is a Bluetooth profile - which describes a method by which audio can be transmitted between a sender and a receiver. The profile mandates that all Bluetooth devices support a codec called SBC - or Low Complexity Subband Codec. A codec defines exactly how the device that is sending the audio should compress it. SBC is what&rsquo;s known as a Low Complexity codec. Low Complexity codecs have the design goal of being easy to encode for the sender and being easy to decode for the receiver. It&rsquo;s a fairly old codec, being the precursor to MP2 - itself the precursor of MP3. When consumers started demanding better quality audio out of their Bluetooth hardware, various other codecs were added to hardware. Some hardware added MP3 encoding and decoding - which was a small improvement in two ways. Firstly, if the source material was already MP3, and the encoder was smart enough, the MP3 data could directly be sent to the receiver, meaning that no encoding step was necessary. Secondly, MP3 is generally considered to be a better codec in terms of the resulting audio quality, so even if there was an encoding step, the results were generally better. Apple added AAC support to their hardware - which itself is a far superior codec to MP3 lends much the same benefits. The freshest codec which seems to be available is from a company that Qualcomm gobbled up - aptX. This codec is interesting in the sense that it only offers the second advantage talked about above. Nobody has music in aptX format, which meant that all source material must be re-encoded on the device sending the audio.</p>
<h2 id="a-better-codec-or-a-higher-bitrate">
  <a class="Heading-link u-clickable" href="/improving-bluetooth-audio-linux/#a-better-codec-or-a-higher-bitrate">A Better Codec, or a Higher Bitrate?</a>
</h2>
<p>The first step to identifying if improvements can be made to your own Bluetooth Audio is to figure out which codecs your Bluetooth Headset supports. You can generally find this out by finding the manufacturers information. My particular headset, the h21 - is dumb as rocks, and only supports SBC. If your headset supports a better codec, especially if it supports a codec called LDAC - you should very much consider trying that before trying to do what we&rsquo;ll be doing in this article, which is improving the quality available to us using the SBC codec. <a href="https://www.nextpit.com/bluetooth-audio-codecs">This article does a reasonably good job of covering all the codecs.</a></p>
<p>No matter what your headphones support, the next thing you&rsquo;re going to want to do is install a custom PulseAudio module which both enables support for a bunch of more fancy codecs, and also allows you to configure the codecs. Go grab <a href="https://github.com/EHfive/pulseaudio-modules-bt/wiki/Packages">EHfive/pulseaudio-modules-bt</a> and restart PulseAudio. You should already see in your systems sound settings you can now actually select between AAC, APTX, APTX HD, and LDAC - if your headset supports it. You can probably stop here if you&rsquo;re able to switch to APTX or LDAC. If not, it&rsquo;s time to configure SBC to be less sucky.</p>
<figure><img src="/posts/bt-audio/bt-audio-cli.png"
         alt="A screenshot of default.pa, showing the changes described below"/><figcaption>
            <h4>default.pa, with the changes</h4>
        </figcaption>
</figure>

<p>So, a quick review of the options the SBC encoder has that we likely care about. Firstly, the Stereo mode. Generally in audio the two stereo modes we have are.. well Stereo and Joint Stereo. Stereo transmits two distinct channels of audio in the same stream, but completely distinct from one another. Joint stereo makes the assumption that the left and right channels are probably similar enough that just encoding how the right channel differs from the left will result in a more efficient packing of data. Your headset is likely already using one of these two modes. SBC actually supports a third mode, called dual - which essentially sends two audio streams to your headset, completely independently of one another. This effectively doubles the bit-rate your headset can operate at, since you now will have two separate streams of audio being encoded, rather than just one. If your headset supports this dual mode, and it most likely does, you definitely want it. In fact, this unofficial feature has been added to a popular custom Android OS - called LineageOS, <a href="https://www.lineageos.org/engineering/Bluetooth-SBC-XQ/">and they describe the benefit of this very well here</a>.</p>
<p>The next option we likely care about is the bitpool. The bitpool effectively determines the bitrate the audio will be encoded at. The higher, the better. <a href="https://btcodecs.valdikss.org.ru/sbc-bitrate-calculator/">A calculator is available here</a> but what is shows is that at the default highest available Bitpool value (53) in Joint Stereo mode - the maximum available bitrate is 328kbps. If we do nothing else, and just switch to Dual Channel mode - the bitrate predictably roughly doubles to 617.4kbps. Some headsets, like my H21s, actually seem to do fine at higher bitpool values. Through experimentation, I found that around 70 was as much as my headset could manage without hearing the audio equivalent of a buffer overflow. At a bitpool value of 70, in dual channel mode, the bitrate now gets to 804.8kbps!</p>
<p>If you are experimenting with a different headset, I suggest starting with a bitpool value of 53 and dual stereo enabled. You can then experiment with increasing the bitpool value, as described below.</p>
<p>To modify these values, you&rsquo;ll need to edit <code>/etc/pulse/default.pa</code>. Firstly, find the lind that begins <code>load-module module-bluetooth-discover</code>. Modify it to add these flags, like so:</p>
<p><code>load-module module-bluetooth-discover a2dp_config=&quot;sbc_cmode=dual sbc_min_bp=53 sbc_max_bp=53 sbc_freq=44k&quot;</code></p>
<p>This will lock your SBC encoder to work at a bitpool size of 53 - in dual stereo mode. Restart PulseAudio by doing <code>pulseaudio -k</code> - and reconnect your Bluetooth headset. Hopefully, you&rsquo;ll perceive a large improvement in quality, as I did. You can now experiment by changing that value of 53 for a higher value. <a href="https://btcodecs.valdikss.org.ru/codec-compatibility/">There&rsquo;s a list of known compatibility available here</a>.</p>
<p>The one downside of this approach of locking the encoder to work at a particular bitpool is that you lose graceful degredation of quality as the link quality reduces. You could experiment with having a lower value for the <code>sbc_min_bp</code> so that the encoder can reduce quality.</p>
<p>Let me know if this helps you!</p>
<p><a href="https://news.ycombinator.com/item?id=24763593">Hacker News Discussion</a> | <a href="https://www.reddit.com/r/Ubuntu/duplicates/ja9kch/improving_bluetooth_audio_quality_on_ubuntu_linux/">Reddit Discussion</a></p>
]]></description>
    </item>
    
    <item>
      <title>Why Does My Computer Not Boot with a USB Hub Attached?</title>
      <link>http://kn100.me/usb-hub-bs/</link>
      <pubDate>Thu, 01 Oct 2020 21:00:00 +0100</pubDate>
      
      <guid>http://kn100.me/usb-hub-bs/</guid>
      <description><![CDATA[<p>My trusty old USB 3 hub has failed. It was only around a year old, but it was cheap and it worked. It wasn&rsquo;t powered, but most of the time it didn&rsquo;t need to be. One day I began having very strange issues with USB on my computer. Devices randomly disconnecting, devices never connecting until the computer was rebooted and so on. I eventually narrowed it down to the hub. I tossed it out after a quick internal inspection had revealed nothing obviously wrong, and went shopping for a new hub.</p>
<p>My requirements for a hub were very simple:</p>
<ul>
<li>it must be externally powered,</li>
<li>it must be relatively aesthetically neutral - no gamer aesthetics or hideous glossy plastic,</li>
<li>it must be USB 3,</li>
<li>it must offer between 3 and 7 extra ports - any less and what&rsquo;s the point, and more and the hub is unnecessarily big, and I treat my desk like Tokyo. Space is a at a premium,</li>
<li>the cost must not exceed 35 GBP (45 US Dollars at the time of writing),</li>
<li>and it must have a detachable cord that connects it to the computer, or offer a cord long enough to reach my computer under my desk.</li>
</ul>
<p>While shopping, almost every single hub I came across had some stupid design that either prioritised it looking space age with blue LEDS galore, and a lot of them even had individual power switches for each USB port. Why anyone would want that is beyond me. Other hubs had tiny 15cm cables, since they&rsquo;re designed to be used right beside a laptop.</p>
<p>I ended up settling on a very cleverly designed Orico 4 port Aluminium hub. I particularly liked this one because aesthetically speaking it was pretty neutral, and it even handles the problem of the USB hub floating off in the breeze by integrating a clamp so that you can clamp it to your desk or your monitor.</p>
<figure><img src="/posts/hub-bs/hub-product.jpg"
         alt="A photo of an Orico 4 port USB hub, which is silver in colour and has a clamp to attach it to a desk"/><figcaption>
            <h4>The hub I chose. A novel design!</h4>
        </figcaption>
</figure>

<p>It arrived, and seemed to work perfectly. USB transfer speeds were as expected, the external power source being MicroUSB meant I could attach any fairly dumb USB power supply to it to supply extra power, the cable leading to the PC itself was detachable and relatively standard (USB A to USB A) and all was good with the world.</p>
<p>That was until I put my computer to sleep. The next morning, I pressed a key on my keyboard to trigger the system to wake up, and nothing happened. The keyboard is not attached to the hub, nor is it attached to a port on the motherboard that is on the same controller, so this was perplexing. I pressed the power button on the tower, but still nothing! Very strange. I detached the USB hub, and immediately my computer came to life and worked as normal.</p>
<p>Through further experimentation I determined this strange phenomenon only happened when an external power source was connected. I toyed with the idea of returning what was obviously a faulty product, but reasoned that I didn&rsquo;t actually need it to be powered, and pretty much all the other hubs in the same price range were either aesthetically disgusting or lacked other features I liked about this one - so I kept it.</p>
<p>That is until today, when through some research, I found this isn&rsquo;t just a problem with <em>my</em> USB hub, it&rsquo;s a problem many have with powered hubs. Articles like <a href="https://www.pro-tools-expert.com/production-expert-1/2019/9/18/warning-your-usb-hub-may-be-harming-your-drives-and-you-may-lose-valuable-studio-work-heres-how-to-fix-it">this</a>, or forum posts like <a href="https://forums.tomshardware.com/threads/computer-wont-start-with-usb-hub-connected.2753255/">this</a> show that totally unrelated hubs cause similar issues for their owners. This got me thinking to past experiences of USB hubs I&rsquo;ve had. In the past, a less financially stable me used to buy the cheapest possible thing every time. At one time, I was experimenting with a Raspberry Pi along with external hard drives. I discovered that a powered hub was necessary for these experiments because the Pi itself was incapable of providing much USB power. I bought the cheapest USB 2 powered hub I could at the time from eBay, and it mostly worked fine. To describe the setup a little, I had the hub itself connected to its power supply, and the &lsquo;in&rsquo; usb port connected to one of the Pis USB ports. Imagine my surprise when the Pi switched on and booted up, without any power supply attached! I thought this was cool at the time, and chalked it up to &lsquo;It&rsquo;s not a bug, it&rsquo;s a feature&rsquo;, and forgot about it. Fast forward to today me, when I realised my experience back then was possibly related to the problems I was having with this new Orico USB hub today. After a little bit of thought, I realised that the hub was most likely &lsquo;backfeeding&rsquo; power to the connected computer.</p>
<h2 id="what-is-backfeeding">
  <a class="Heading-link u-clickable" href="/usb-hub-bs/#what-is-backfeeding">What is backfeeding?</a>
</h2>
<p>To keep things simple, let&rsquo;s stick to USB 2 for now. USB 2 consists of 4 conductors, known as VCC (this is the &lsquo;positive&rsquo; 5 volt connection), GND (this is the &rsquo;negative&rsquo; power connection), D+, and D- (the data transmission pins, no true power flows over these pins). This means that a connected USB device can both draw power and transfer data. When you see 5 volts on a power adaptor or something, this is usually an approximation. For various reasons which revolve mostly around cost saving, most USB power adaptors will actually output slightly more than 5 volts, and then when a load is applied (say, a charging phone), the voltage drops down a bit. The better the quality of the power supply, closer to 5 volts it&rsquo;ll start at generally and the smaller the drop when a load is applied.</p>
<figure><img src="/posts/hub-bs/usb-pinout.png"
         alt="A diagram showing the pinout of a USB"/>
</figure>

<p>This applies to your computer&rsquo;s USB ports too - except for one big difference: the power supply inside your computer is probably significantly better designed than the USB plug sockets that are mass produced. In fact, if I measure the voltage coming from my USB ports on my computer, I get a value of around 5.03v. If I measure the voltage coming from my phone charger, I get 5.21v. This is a pretty big difference!</p>
<p>The next thing to know is how a powered hub actually works. Essentially, a powered hub takes an external power source, and passes that power source through to the USB devices connected to the hub in place of your computer. One problem that these hubs face however is what if the user wishes to use the hub in its &lsquo;unpowered&rsquo; state - where they&rsquo;ve connected the hub to their computer with no power supply. In order to implement this properly, the hub would probably need some mechanism to switch between the computer power and the external PSU if it is connected. This would however add cost to the manufacture of the hub, so instead of doing this, they just connect the 5v of your computer through to the 5v of the external power supply. This is ridiculously bad because if there is even the smallest difference between the voltage being supplied by your computer and the voltage being supplied by the external power source, power will flow towards the device with the lower voltage! This is &lsquo;backfeeding&rsquo;. As a simple analogy, imagine you took two rechargeable batteries and connected them together. What you&rsquo;d find happens is that the batteries will eventually end up at exactly the same voltage, which will be slightly lower than what you&rsquo;d expect if you did the maths, as a small amount of power would be lost as heat. If the power source is &lsquo;infinite&rsquo; however - like our wall connected computer or our wall connected external hub, this power will just continue to flow. Where it goes or what happens with it is completely undefined. In the case of our Raspberry Pi earlier, the circuit was simple enough that it just led to the Pi being powered up. In the case of my computer, it screwed with the system so badly that hardware buttons like the power button literally stopped functioning altogether.</p>
<h2 id="why-would-they-design-it-like-this">
  <a class="Heading-link u-clickable" href="/usb-hub-bs/#why-would-they-design-it-like-this">Why would they design it like this</a>
</h2>
<p>This design seems stupid right? The problem is, it is the cheapest way of achieving the design goal of having a USB hub work with or without power. If we didn&rsquo;t care about this particular feature, we could either just not have an external power source and have a purely unpowered hub, or we could not connect the 5v pin from the computer and therefore only supply power from the external PSU. Both of these solutions in effect are more expensive than just living with the backfeeding &lsquo;feature&rsquo; since it&rsquo;d mean the company offering the hub would need to deal with support requests from people wondering why their hub doesn&rsquo;t work &lsquo;unpowered&rsquo; when their other one does, or vice versa. Instead, we get backfeeding. I&rsquo;m sure that some computers would not exhibit any real problems with this design, seeing as it&rsquo;s been around since as long as powered USB hubs have been around as far as I can tell, and it&rsquo;s likely some USB chipsets are designed with this in mind, but mine was not. Others computers, especially Macs, deal with this particular issue far more destructively, causing damage to the computer&rsquo;s USB chipset when a backfeeding USB hub is connected.</p>
<h2 id="disclaimer">
  <a class="Heading-link u-clickable" href="/usb-hub-bs/#disclaimer">Disclaimer</a>
</h2>
<p>Before we continue, you should know I am not an electrical engineer, if that wasn&rsquo;t clear from how vaguely I described things above. I am a software engineer who likes to dabble with electronics. This means my advice below does not come from someone who is qualified to give it. You should follow this advice at your own risk. I take no responsibility for damage you do to yourself, your USB hub, or your computer, or to anything else for that matter.</p>
<h2 id="fixing-this-bs-for-ourselves">
  <a class="Heading-link u-clickable" href="/usb-hub-bs/#fixing-this-bs-for-ourselves">Fixing this BS for ourselves</a>
</h2>
<ol>
<li>Firstly, I validated this was the problem. I engaged in a clever little trick where I took an old USB cable - USB 2 or 3 is fine, cut it in half, exposed the either 2 (cable is power only) or 4 wires, and looked up USB wire colouring to realise that the red wire is usually the VCC wire, and Black/White is usually GND. I then connected the cut up USB lead to the input port on my hub (where my computer connects), and connected the external power supply. I measured 5 volts across these two wires using the multimeter, meaning this hub does backfeed.</li>
</ol>
<figure><img src="/posts/hub-bs/hub-screw.jpg"
         alt="A photo showing the location of the 4 screws on the USB hub"/>
</figure>

<ol start="2">
<li>My hub comes apart very easily. Disconnect all wires. Four screws removed and we immediately get access to the circuit board. USB 3 is a little more complicated than USB 2 - featuring 9 conductors rather than USB 2s 4.</li>
</ol>
<figure><img src="/posts/hub-bs/hub-circuit.jpg"
         alt="A photo of the dark blue circuit board inside the USB hub"/>
</figure>

<ol start="3">
<li>I have no idea what the pinout of USB 3 is, nor how this specific connector orders things.  I connected the cut up USB lead to the input port on my hub (where my computer connects). We can then attach our multimeter in continuity mode to the red wire, and used the other probe to probe about the USB port to figure out which of the pins is VCC. In my case, it was the 8th pin from the left.</li>
</ol>
<figure><img src="/posts/hub-bs/hub-measure.jpg"
         alt="A photo of Kevin measuring the USB pins inside the USB hub with a yellow multimeter"/>
</figure>

<ol start="4">
<li>I snipped this pin using my wifes beauty scissors. If you do this same thing, I recommend not telling your wife.</li>
</ol>
<figure><img src="/posts/hub-bs/hub-cut-zoom.jpg"
         alt="A close up of the USB pin that was cut"/><figcaption>
            <h4>Cut the pin second from right, to break the connection.</h4>
        </figcaption>
</figure>

<ol start="5">
<li>I reassembled the hub, and connected it only to power. I connected my multimeter in voltage measuring mode across both wires of our cut up USB lead. I measured zero volts. This is to validate the work we have just done. Our hub no longer backfeeds!</li>
</ol>
]]></description>
    </item>
    
    <item>
      <title>Diyode.com published a feature about the air quality meter!</title>
      <link>http://kn100.me/diyode-air-quality-meter/</link>
      <pubDate>Tue, 01 Sep 2020 10:00:00 +0100</pubDate>
      
      <guid>http://kn100.me/diyode-air-quality-meter/</guid>
      <description><![CDATA[<figure><img src="/posts/diyode/intro.jpg"
         alt="A photo of a page in Diyode magazine, featuring a photo of the Air Quality Meter"/>
</figure>

<p>You may have seen my article on the <a href="https://kn100.me/aqi">air quality meter</a> I built a while ago. Diyode, a print magazine in Australia kindly published an article discussing the process I took to create the thing! It&rsquo;s a really cool feeling to be featured somewhere like this. The article is a great read, check it out over on Diyode: <a href="https://diyodemag.com/features/sense_of_smell_esp32-based_air_quality_meter">Sense of Smell</a>.</p>
]]></description>
    </item>
    
    <item>
      <title>Embedded Meets the Internet: Build Your Own Air Quality Meter</title>
      <link>http://kn100.me/where-embedded-meets-the-internet-building-your-own-air-quality-meter/</link>
      <pubDate>Sun, 05 Jul 2020 00:25:47 +0100</pubDate>
      
      <guid>http://kn100.me/where-embedded-meets-the-internet-building-your-own-air-quality-meter/</guid>
      <description><![CDATA[<figure><img src="/posts/aqm/arduair-artsy.jpg"
         alt="The finished Air Quality Meter, a purple food storage tub with a poking through the wooden lid"/><figcaption>
            <h4>The Finished Product - quite rustic!</h4>
        </figcaption>
</figure>

<p>This article was recently featured in <a href="https://hackaday.com/2020/07/20/a-portable-home-air-quality-meter-with-the-esp32/">Hackaday</a> and <a href="https://diyodemag.com/features/sense_of_smell_esp32-based_air_quality_meter">Diyode</a>!</p>
<p>I&rsquo;m bored. I&rsquo;m sure you are too. I&rsquo;m personally very thankful that I can still work through this strange period - but there are still hours in the day to fill. I wanted a personal project, so I thought about remote working and the health impacts. Most offices here have air handling units, air conditioners, and fans to keep air circulating. Most residential flats like the one I rent do not. Given I now spend close to 24 hours a day sat in this flat, air quality seems important. I looked up the cost of air quality meters and they&rsquo;re either fairly pricy or fairly useless. I wanted one that I could graph over time. I decided to DIY one! This wasn&rsquo;t intended to be a super serious project, just a bit of fun and an opportunity to learn more about embedded systems.</p>
<p>In the end, I built a little unit I think looks pretty nice given my limited DIY skills that:</p>
<ul>
<li>Displays realtime air quality data on a LCD</li>
<li>Reports realtime air quality data to a MQTT broker</li>
<li>Allows realtime monitoring on any device that can do MQTT.</li>
</ul>
<p>I also involved a Raspberry Pi in this party that:</p>
<ul>
<li>Persists data to an InfluxDB instance</li>
<li>Hosts a Grafana instance</li>
<li>Reboots occasionally and crashes if you look at it funny - just Raspberry Pi things.</li>
</ul>
<figure><img src="/posts/aqm/arduair-graphs.png"
         alt="A screenshot of the graphs in Grafana, showing metrics suck has the air quality index, the temperature, humidity, VOC concentration, and so on."/><figcaption>
            <h4>The IMO super pretty graphs it makes in Grafana - that huge dip in temperature is when I took it outside to take photos of it for this article!</h4>
        </figcaption>
</figure>

<h2 id="the-parts">
  <a class="Heading-link u-clickable" href="/where-embedded-meets-the-internet-building-your-own-air-quality-meter/#the-parts">The Parts</a>
</h2>
<h3 id="4mb-flash-miini-wemos-d1-lolin32-esp32-dev-board">
  <a class="Heading-link u-clickable" href="/where-embedded-meets-the-internet-building-your-own-air-quality-meter/#4mb-flash-miini-wemos-d1-lolin32-esp32-dev-board">4MB Flash MIINI WEMOS D1 Lolin32 <code>ESP32</code> Dev Board</a>
</h3>
<p>I have worked with Arduino in the past - and have always found them to be a really fun tool to have in the arsenal. They&rsquo;re cheap as hell and very useful in many electronics projects. I wanted something that was similar to work with in capability, but wanted more - namely I wanted to connect it to the internet! There are ways to connect an Arduino to the internet, but a far more seamless solution presented itself inside a gift my lovely fiance got for me a few months back - the Odroid Go! The Odroid Go is portable games console styled after the Gameboy and is capable of playing the games of many retro consoles. It also has a pretty prolific hacker community - since it is powered by a somewhat custom ESP32.</p>
<p>The <code>ESP32</code> sounded incredible! two 240MHz cores + a third &rsquo;low power&rsquo; core to keep things ticking while it&rsquo;s asleep, 520KB of SRAM, WiFi, Bluetooth, 34 GPIO Pins, 12 bit ADC, SPI and I2C support - it really sounded like the perfect hacker board. Even the <code>ESP32</code> data-sheet does a good job of selling the applications of a solution like this, listing IoT Sensor Hub as its top application. Best of all, it&rsquo;s unreasonably cheap! I picked up what I am pretty sure is a clone of another dev board manufactured by WEMOS from eBay for 7GBP delivered.</p>
<p>You can write code for the <code>ESP32</code> in numerous ways - but the way that I was interested in was using the Arduino IDE - since I already had some experience with it in the past.</p>
<h3 id="2004-i2c-lcd-display">
  <a class="Heading-link u-clickable" href="/where-embedded-meets-the-internet-building-your-own-air-quality-meter/#2004-i2c-lcd-display"><code>2004</code> I2C LCD Display</a>
</h3>
<p>I wanted to have some sort of external display that would show the realtime values at all times. I considered going for a full colour display here, but decided that for simplicities sake I&rsquo;d go for one of the old fashioned, text only ones that was probably intended to go into some industrial control hardware or a VCR. It also has the benefit of being fairly low power in comparison to a permanently backlit full colour TFT display.</p>
<p>The one I got has a lovely interface board already soldered on that allows you to talk to it using I2C - and also has a bright green backlight that my fiance has lovingly nicknamed the &lsquo;Shrek&rsquo; lamp.</p>
<h3 id="cjmcu-680-bme680-module">
  <a class="Heading-link u-clickable" href="/where-embedded-meets-the-internet-building-your-own-air-quality-meter/#cjmcu-680-bme680-module"><code>CJMCU-680</code> BME680 Module</a>
</h3>
<p>This component it what sparked this whole project off. My phones assistant fairly regularly notifies me of Kickstarter or IndieGoGo projects that are fundraising - and this time the advertised product looked fairly interesting. It was the Metriful Sense board - an Indoor Environment Monitor board. It features a Light sensor, microphone, and the Bosch BME680. I was considering backing the <a href="https://www.kickstarter.com/projects/metriful/sense-indoor-environment-monitor/faqs">Metriful Kickstarter</a> and I&rsquo;d encourage you to do so if this sounds good to you - however I decided I didn&rsquo;t care about light and sound, didn&rsquo;t want to wait months for it to shop, and only really cared about air quality, so I went to eBay and found that the BME680 is being sold on breakout boards for around 15GBP - shipped first class, so I got that instead. This component is actually the most expensive part of the build (I paid 16GBP) - but it is a pretty cool sensor.</p>
<p>The BME680 measures temperature, pressure, humidity, and gas resistance (how conductive the gas is) - and can offer you these values raw, or you can use a closed source Bosch library called BSEC which uses these 4 values and some magic sensor fusion tech to compute an Air Quality Index, as well as a guess at the Co2 and VOC concentrations in the room.</p>
<h3 id="enclosure">
  <a class="Heading-link u-clickable" href="/where-embedded-meets-the-internet-building-your-own-air-quality-meter/#enclosure">Enclosure</a>
</h3>
<p>For an enclosure, I decided I wanted something fairly roomy - having been burned by trying to make miniature projects in the past. I wanted something that would fit the aesthetic of my flat, and something that was fairly easy to work with. I ended up settling on a plastic lunchbox which has a lovely wooden lid that press fits into the plastic from a shop called Flying Tiger. Really, any box that will fit the components and you can drill will work. I think it turned out pretty nice even with my absolutely terrible woodworking skills.</p>
<h3 id="battery-choices">
  <a class="Heading-link u-clickable" href="/where-embedded-meets-the-internet-building-your-own-air-quality-meter/#battery-choices">Battery Choices</a>
</h3>
<p>I wanted to be able to transport this meter around the house and have it continuously measure for a period after it was unplugged. I wanted it to have reasonably good battery life. I also try to reuse things I have that would be &rsquo;e-waste&rsquo; otherwise and I settled on a 3000mAh 18650 battery that I had lying around that was in use for a high current application prior, but now had developed too high of an internal resistance to really push amps. Perfect for this project then, since the entire project will sip around 80mA in its completely unoptimised form, and this could easily be significantly reduced. In reality, the battery lasts around 30 hours. All I needed to purchase here was an 18650 holder.</p>
<h3 id="raspberry-pi-or-other-server-you-control">
  <a class="Heading-link u-clickable" href="/where-embedded-meets-the-internet-building-your-own-air-quality-meter/#raspberry-pi-or-other-server-you-control">Raspberry Pi or other server you control</a>
</h3>
<p>The <code>ESP32</code> is a very powerful dev board, but in order to persist large amounts of data and to make it accessible in something like Grafana we&rsquo;re going to need something a bit more powerful! I had a Raspberry Pi 3b+ sitting in the &lsquo;one day I&rsquo;ll find a use for you&rsquo; box I have and decided this was a perfect use. It can be my MQTT broker. It can even perform double duty and host Grafana for me. I did consider hosting all of this on the server that hosts this very website (A Scaleway instance) - but didn&rsquo;t want to pollute that environment too much if I ended up getting bored of this project.</p>
<p>I&rsquo;d like to stress here something that I learned the hard way - a <em>good quality</em> power supply is an absolute requirement for any Raspberry Pi. Lower quality power supplies just aren&rsquo;t beefy enough to really supply the Pi when things really get going and you&rsquo;ll find yourself chasing ghosts. Get a decent power supply!</p>
<h2 id="the-build">
  <a class="Heading-link u-clickable" href="/where-embedded-meets-the-internet-building-your-own-air-quality-meter/#the-build">The Build</a>
</h2>
<h3 id="i2c-components">
  <a class="Heading-link u-clickable" href="/where-embedded-meets-the-internet-building-your-own-air-quality-meter/#i2c-components">I2C Components</a>
</h3>
<p>I&rsquo;ll provide a quick and dirty primer on I2C: It&rsquo;s a way of attaching peripherals to microprocessors over short distances. It&rsquo;s pretty slow topping out at 5 Mbps - but is nice since it has extremely simple wiring (just 2 data wires!) and you can chain devices. Effectively, we just need to connect the SDA and SCL pins (along with power) between the components and the <code>ESP32</code> and the hardware portion is finished. When we want to connect two devices, we just connect all 3 in a chain (connect all the SDAs together, connect all the SCLs together). The only other thing we realistically need to take into account is the address. Each I2C Peripheral will have an address that identifies it on the bus. These addresses are not unique. If you purchase two of the same peripheral, you&rsquo;ll probably find they have the same address in their default configuration. This means that you cannot &rsquo;talk&rsquo; to each device independently if they&rsquo;re on the same bus. There are a couple of solutions to this problem: the simplest being peripherals that feature an &lsquo;address select&rsquo; jumper which will allow you to select a secondary address to use instead of the first, and other peripherals will be reprogrammable so you can change the address freely. YMMV.</p>
<p>So, to connect our I2C components up, just connect all the SDAs together, and all the SCLs together. Pretty simple!</p>
<figure><img src="/posts/aqm/arduair-conx.jpg"
         alt="A diagram showing the connections that need to be made between the components, connect all the SDAs up, connect all the SCLs up, connect VIN of the BME680 to the 3v pin on the esp32, connect the VIN of the LCD to the positive lead of your battery, connect the battery to the Lipo battery connector on the esp32, and wire up the grounds!"/><figcaption>
            <h4>Connections - basically connect all the SDAs up, connect all the SCLs up, connect VIN of the BME680 to the 3v pin on the esp32, connect the VIN of the LCD to the positive lead of your battery, connect the battery to the Lipo battery connector on the esp32, and wire up the grounds! Diagrams really aren&#39;t my strong suit.</h4>
        </figcaption>
</figure>

<p>Once we&rsquo;ve done this, it&rsquo;s time to power them.</p>
<h3 id="power-considerations">
  <a class="Heading-link u-clickable" href="/where-embedded-meets-the-internet-building-your-own-air-quality-meter/#power-considerations">Power considerations</a>
</h3>
<p>In order to figure out how to power this all, I needed to know the power requirements of each component. In the case of the <code>ESP32</code> - I am using a dev board which will have its own power draws on top of the <code>ESP32</code> itself for things like the serial to USB chip as well as for the charging circuitry since it features a LiPo charging circuit, so the current requirement is from the <code>ESP32</code> data sheet directly - since this weird clone board I got doesn&rsquo;t seem to have a datasheet that exactly matches it. Same goes for the LCD and the <code>CJMCU-680</code>. This is the downside of buying strange boards from Aliexpress I guess! For this specific project, I experimented a little and did some voltage/current testing and got these values.</p>
<table>
<thead>
<tr>
<th style="text-align:center">Component</th>
<th style="text-align:center">Input Voltage Requirement</th>
<th style="text-align:center">Current requirement</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center"><code>ESP32</code> Board</td>
<td style="text-align:center">3.7v from LiPo input</td>
<td style="text-align:center">70mA</td>
</tr>
<tr>
<td style="text-align:center"><code>2004</code> LCD</td>
<td style="text-align:center">5v but runs fine at LiPo voltages too in my case</td>
<td style="text-align:center">50mA with backlight</td>
</tr>
<tr>
<td style="text-align:center"><code>CJMCU-680</code></td>
<td style="text-align:center">3.3v</td>
<td style="text-align:center">10mA</td>
</tr>
</tbody>
</table>
<p>Let&rsquo;s start with the <code>CJMCU-680</code>. It runs at 3.3v and needs 10mA, but only very briefly. As far as I can tell, gas resistivity sensors like the one found in the BME680 heat up a plate inside the component to &lsquo;activate&rsquo; some material that reacts with various gasses - changing the resistance of this sensing material. The measurement of this resistance is how the sensor determines the gas resistivity. The initial heating phase which happens in less than 0.1s according to the datasheet is the highest current phase, at which point the current draw drops dramatically. I connected this component to the 3.3v and GND pins on the <code>ESP32</code> and it seemed to work so that&rsquo;s how I left it.</p>
<p>Next up is the <code>ESP32</code> itself. The dev board I got helpfully provides two ways to power it. You can either just connect a USB power supply to the onboard MicroUSB port which powers the whole lot, or you can connect a Lipo cell to the onboard battery connector, at which point the onboard MicroUSB port doubles as a pretty bad battery charger. The charger on my board seems to charge the battery at around 400mA with all other components attached and working - which means it&rsquo;s probably supplying 500mA max (respecting the USB spec for once!). I went with the Lipo option, but this has a small downside - the 5v rail that the board supplies then just doesn&rsquo;t seem to supply enough current to power the LCD.</p>
<p>For the LCD, what you&rsquo;d probably want to do is engineer your own power solution that bypasses the crappy one found on the dev board. You might get your own step up converter that will take the 3.7v from the Lipo and boost it to 5v with some current rating that makes sense (1 amp is more than sufficient). You can then also get your own LiPo charging circuitry that will charge the Lipo at whichever rate you like rather than the rather slow but safe 500mA charging rate. You could go belt and braces and add on a low dropout circuit which cuts the battery connection if its voltage drops below something like 3v - since anything below this and you&rsquo;ll start to enter explodey battery territory. In fact, you can buy a single circuit board that will do all of this for you in a single integrated solution. Something like <a href="https://www.aliexpress.com/item/4000048982405.html">this 5v power bank module</a> or <a href="https://www.aliexpress.com/item/892907292.html">this LiPo charge circuit</a> would likely do. What I did and do not recommend you do is just connect the LCD directly to the LiPo. This means I have completely worked around the low dropout protection the <code>ESP32</code> already has which is a Bad Thing(tm) - but for my given use case (almost always plugged in, rarely without power for more than 12 hours, very large battery) - I&rsquo;m happy with it. In either case, make sure you connect the ground of the LCD to a ground on the <code>ESP32</code> - you must always common (connect) grounds when using something like I2C.</p>
<p>Onto the battery! As previously discussed, I used an 18650 I had lying around along with an <a href="https://www.aliexpress.com/item/4001079428851.html?">18650 battery holder</a>.</p>
<h3 id="enclosure-1">
  <a class="Heading-link u-clickable" href="/where-embedded-meets-the-internet-building-your-own-air-quality-meter/#enclosure-1">Enclosure</a>
</h3>
<figure><img src="/posts/aqm/arduair-internal.jpg"
         alt="A photo of the inside of the device. Very messy wiring"/><figcaption>
            <h4>Messy internals never really bothered me!</h4>
        </figcaption>
</figure>

<p>Quick note: Woodwork is NOT my strong suit, so please forgive the tragic method that I describe. I purchased a lunchbox that is a plastic box with a piece of wood that fits into the plastic box as a lid. I cut a small hole in the bottom so the BME680 sensor could breathe by heating a drill bit and slowly drilling through the plastic. I cut another three of these holes on the back, one sort of MicroUSB shaped, another for a toggle switch that I use to toggle the backlight of the LCD, and another push button switch that pulls io0 low so that my dev board goes into flash mode. The hole I cut in the wooden lid is rough as hell because I do not own a jigsaw nor did I want to buy one. Instead, what I did was I drilled holes all along the edges of the wood I wanted to cut out, and then stuck a hacksaw blade through these holes and cut using the hacksaw. It took forever and there was probably a better way available to me, but this method worked in the end. Several minutes of sanding to get the LCD to press fit into the hole, and it was finished.</p>
<p>I glued the BME680 in place using a adhesive rubber furniture foot thing because that&rsquo;s what I had to hand. If this is a permanent installation, you may want to invest in some standoffs and screws so you can mount yours properly. I found on eBay a MicroUSB &rsquo;extender&rsquo; cable, that has a male MicroUSB port on one side and a female MicroUSB port on the other. I epoxied the female side to the microUSB shaped hole, and plugged the male side into the ESP32. I superglued the 18650 battery holder in place. The <code>ESP32</code> is currently just floating about on the inside of the case since I still frequently access pins on it.</p>
<p>It goes without saying, internally this is a bit of a mess. I don&rsquo;t really mind this as it is purely a hacker project for me, something to keep me sane during Covidpocalypse. It works well and externally I&rsquo;ve grown to like how it looks.</p>
<figure><img src="/posts/aqm/arduair-internal-2.jpg"
         alt="a photo of the epoxied in microUSB connector, along with the backlight control switch, the &#39;put it into flash mode&#39; switch, and the 18650 cell in its holder"/><figcaption>
            <h4>The epoxied in microUSB connector, along with the backlight control switch, the &#39;put it into flash mode&#39; switch, and the 18650 cell in its holder</h4>
        </figcaption>
</figure>

<figure><img src="/posts/aqm/arduair-back.jpg"
         alt="A photo showing the drilled out MicroUSB hole, which didn&#39;t turn out very well, not being the right shape."/><figcaption>
            <h4>The MicroUSB hole didn&#39;t quite go exactly to plan...</h4>
        </figcaption>
</figure>

<h2 id="esp32-software">
  <a class="Heading-link u-clickable" href="/where-embedded-meets-the-internet-building-your-own-air-quality-meter/#esp32-software"><code>ESP32</code> Software</a>
</h2>
<p>Next up, we should write the code that runs the show on the ESP32. As previously discussed, I wanted to use the Arduino IDE initially since that papers over a lot of the horribleness of working with embedded systems such as this one. There is a fair bit of horribleness still - which I shall elaborate on. Due to incompatibilities between the BSEC Library and the Arduino IDE, I recommend installing Arduino IDE v1.8.10. This one works for me. Once you&rsquo;ve got the Arduino IDE installed, you&rsquo;re going to want to install the board tools for your board. <a href="https://randomnerdtutorials.com/installing-the-esp32-board-in-arduino-ide-windows-instructions/">This blog post covers installing the <code>ESP32</code> dev tools</a>. Ensure you are able to write software to the board. On my first attempt with this, the <code>ESP32</code> point blank refused to accept code. I&rsquo;d constantly get &ldquo;ESP32: Timed out&hellip; Connecting&hellip;&rdquo;. All the instructions everywhere told me to press the &lsquo;Boot&rsquo; or &lsquo;EN&rsquo; buttons, but my board only had one button labelled &lsquo;reset&rsquo; and that didn&rsquo;t help. What did help (and I can&rsquo;t remember how or where I found to try this!) was while you are in that &lsquo;Connecting&hellip;&rsquo; phase briefly connect the IO0 pin to ground. This seemed to kick it into &lsquo;flash mode&rsquo;, and it flashes correctly every time.</p>
<p>Here is a list of the libraries I used:</p>
<ul>
<li><a href="https://github.com/BoschSensortec/BSEC-Arduino-library">BSEC Library by Bosch Sensortec (v1.5.1474)</a></li>
<li><a href="https://github.com/knolleary/pubsubclient/">PubSubClient by Nick O&rsquo;Leary (v2.8.0)</a></li>
<li><a href="https://github.com/fdebrabander/Arduino-LiquidCrystal-I2C-library">LiquidCrystal I2C by Frank de Brabander (v1.1.2)</a></li>
</ul>
<h3 id="talking-to-the-bme680">
  <a class="Heading-link u-clickable" href="/where-embedded-meets-the-internet-building-your-own-air-quality-meter/#talking-to-the-bme680">Talking to the BME680</a>
</h3>
<p>Getting the BSEC library set up is a bit of a pain. Once you&rsquo;ve successfully got <code>ESP32</code> code building and flashing, you&rsquo;ll need to add the BME680 library to your IDE (Download the release as a zip from the above Github Link and add it to the Arduino IDE by selecting &lsquo;Sketch&rsquo; from the menu bar, then head to &lsquo;Include Library&rsquo;, and then &lsquo;Add .ZIP Library&hellip;&rsquo;). Once you&rsquo;ve done this, close out of the Arduino IDE, navigate to where your Arduino IDE saved the <code>ESP32</code> platform (usually ~/Arduino/Hardware/expressif/esp32), and then open the platform.txt in a text editor (I suggest saving a copy elsewhere so you can restore if needed!). Look for a section that looks something like the below:</p>

<pre tabindex="0"><code>compiler.c.extra_flags=
compiler.c.elf.extra_flags=
#compiler.c.elf.extra_flags=-v
compiler.cpp.extra_flags=
compiler.S.extra_flags=
compiler.ar.extra_flags=
compiler.elf2hex.extra_flags=</code></pre>
<p>At the bottom of this section, add this line:</p>

<pre tabindex="0"><code>compiler.libraries.ldflags=</code></pre>
<p>Now that we&rsquo;ve added ldflags, we need to reference them. Find the line that starts with <code>recipe.c.combine.pattern</code> and replace it with this:</p>

<pre tabindex="0"><code>## Combine gc-sections, archives, and objects
recipe.c.combine.pattern=&#34;{compiler.path}{compiler.c.elf.cmd}&#34; {compiler.c.elf.flags} {compiler.c.elf.extra_flags} -Wl,--start-group {object_files} &#34;{archive_file_path}&#34; {compiler.c.elf.libs} {compiler.libraries.ldflags} -Wl,--end-group -Wl,-EL -o &#34;{build.path}/{build.project_name}.elf&#34;</code></pre>
<p>Once you have done all of this, you can select one of the now available to you BSEC Example sketches (File &gt; Examples &gt; BSEC Software Library) and check it compiles and uploads to your board. Then consult the serial monitor to see if it works as you expected. You&rsquo;ll probably find it doesn&rsquo;t because nothing in life is easy and life is suffering. You should at least get an error code. If you haven&rsquo;t, there is something more serious wrong. Here are a few pointers to get it working assuming you got a BSEC Error code. Well aren&rsquo;t you lucky you at least get an error code! It&rsquo;s nice to get an error code since you can go and look up the error code. In the documentation. Oh theres no error codes there? How about in the code? It&rsquo;s closed source? Crap.</p>
<p>Firstly, you&rsquo;ll most likely have to initialise the &lsquo;Wire&rsquo; library with specific pins. This is easy enough, just find the <code>Wire.begin()</code> line and specify your pins in here:</p>

<pre tabindex="0"><code># At the top of your sketch, below the #include lines
#define PIN_I2C_SDA 21
#define PIN_I2C_SCL 22
# Further down, in the setup() method
Wire.begin(PIN_I2C_SDA, PIN_I2C_SCL);</code></pre>
<p>Secondly, my particular BME680 breakout board was for some reason configured to use the secondary I2C address, so I had to explicitly tell the BSEC library to use the secondary I2C address. Again, easy enough, find the line that contains <code>BME680_I2C_ADDR_PRIMARY</code> and replace it with <code>BME680_I2C_ADDR_SECONDARY</code>. Nice.</p>
<h3 id="talking-to-the-lcd">
  <a class="Heading-link u-clickable" href="/where-embedded-meets-the-internet-building-your-own-air-quality-meter/#talking-to-the-lcd">Talking to the LCD</a>
</h3>
<p>This was simpler. Once again, you can head to the example code for the LCD to get a feel for what the library can do (File &gt; Examples &gt; INCOMPATIBLE &gt; LiquidCrystal I2C) (the library doesn&rsquo;t explicitly support the <code>ESP32</code> but it seems to work fine for me!).</p>
<h3 id="example-code-i-wrote---arduair">
  <a class="Heading-link u-clickable" href="/where-embedded-meets-the-internet-building-your-own-air-quality-meter/#example-code-i-wrote---arduair">Example code I wrote - Arduair</a>
</h3>
<p>Onto the good bit. I am not an Arduino expert, and <a href="https://www.youtube.com/watch?v=i2fhNVQPb5I">I am not a C programmer</a>, so I can&rsquo;t promise this code is good. It works for me for now and I would like to tidy it up, but I present it in its current form below. It borrows code from both the BSEC Examples and the LCD examples. I&rsquo;ve tried to document it with comments as well as possible.</p>
<figure><img src="/posts/aqm/arduair-connecting.jpg"
         alt="A photo of the air quality meter LCD displaying &#39;Connecting MQTT 192.168.0.17&#39;"/><figcaption>
            <h4>Something super satisfying seeing an oldschool LCD display an IP address...</h4>
        </figcaption>
</figure>

<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ino" data-lang="ino"><span style="display:flex;"><span><span style="color:#75715e">// kn100.me - Arduairs
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">// Available on Github: https://github.com/kn100/arduairs
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">// Note: This code is provided as an example only - it does not implement authentication, TLS or anything even remotely close to security. It&#39;s probably fine if all your infrastructure lives on your local network, but you might want to consider looking into security.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#include</span> <span style="color:#75715e">&lt;PubSubClient.h&gt;</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#include</span> <span style="color:#75715e">&#34;bsec.h&#34;</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#include</span> <span style="color:#75715e">&lt;LiquidCrystal_I2C.h&gt;</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#include</span> <span style="color:#75715e">&lt;WiFi.h&gt;</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#include</span> <span style="color:#75715e">&lt;EEPROM.h&gt;</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Controls which pins are the I2C ones.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#75715e">#define PIN_I2C_SDA 21
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#define PIN_I2C_SCL 22
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Controls how often the code persists the BSEC Calibration data
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#75715e">#define STATE_SAVE_PERIOD  UINT32_C(60 * 60 * 1000) </span><span style="color:#75715e">// every 60 minutes
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#66d9ef">char</span><span style="color:#f92672">*</span> mqtt_server <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;&lt;your-mqtt-server-IP&gt;&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#66d9ef">char</span><span style="color:#f92672">*</span> ssid <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;&lt;your-wifi-SSID&gt;&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#66d9ef">char</span><span style="color:#f92672">*</span> passphrase <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;&lt;your-wifi-password&gt;&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Controls the offset for the temperature sensor. My BME680 was reading 4 degrees higher than another thermometer I trusted more, so my offset is 4.0.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">const</span> <span style="color:#66d9ef">float</span> tempOffset <span style="color:#f92672">=</span> <span style="color:#ae81ff">4.0</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Configuration data for the BSEC library - telling it it is running on a 3.3v supply, that it is read from every 3 seconds, and that the sensor should take into account the last 4 days worth of data for calibration purposes.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">const</span> <span style="color:#66d9ef">uint8_t</span> bsec_config_iaq[] <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span><span style="color:#75715e">#include</span> <span style="color:#75715e">&#34;config/generic_33v_3s_4d/bsec_iaq.txt&#34;</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>};
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Create an object of the class Bsec
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>Bsec iaqSensor;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">String</span> output;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// This stores a question mark and is eventually replaced with a space character once the BSEC library has decided it has enough calibration data to persist. It is displayed in the bottom right of the LCD as a kind of debug symbol.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">String</span> sensorPersisted <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;?&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">uint8_t</span> bsecState[BSEC_MAX_STATE_BLOB_SIZE] <span style="color:#f92672">=</span> {<span style="color:#ae81ff">0</span>};
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">uint16_t</span> stateUpdateCounter <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">WiFiClient</span> espClient;
</span></span><span style="display:flex;"><span>PubSubClient <span style="color:#a6e22e">broker</span>(espClient);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// The I2C address of your LCD, it will likely either be 0x27 or 0x3F
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">const</span> <span style="color:#66d9ef">uint8_t</span> LCD_ADDR <span style="color:#f92672">=</span> <span style="color:#ae81ff">0x27</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// 20 characters across, 4 lines deep
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>LiquidCrystal_I2C <span style="color:#a6e22e">lcd</span>(LCD_ADDR, <span style="color:#ae81ff">20</span>, <span style="color:#ae81ff">4</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">void</span> <span style="color:#a6e22e">setup</span>(<span style="color:#66d9ef">void</span>)
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">Serial</span>.<span style="color:#a6e22e">begin</span>(<span style="color:#ae81ff">115200</span>);
</span></span><span style="display:flex;"><span>  broker.setServer(mqtt_server, <span style="color:#ae81ff">1883</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">Wire</span>.<span style="color:#a6e22e">begin</span>(PIN_I2C_SDA, PIN_I2C_SCL);
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// Your module MAY use the primary address, which is available as BME680_I2C_ADDR_PRIMARY
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  iaqSensor.<span style="color:#a6e22e">begin</span>(BME680_I2C_ADDR_SECONDARY, <span style="color:#a6e22e">Wire</span>);
</span></span><span style="display:flex;"><span>  output <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">BSEC library version &#34;</span> <span style="color:#f92672">+</span> <span style="color:#66d9ef">String</span>(iaqSensor.version.major) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;.&#34;</span> <span style="color:#f92672">+</span> <span style="color:#66d9ef">String</span>(iaqSensor.version.minor) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;.&#34;</span> <span style="color:#f92672">+</span> <span style="color:#66d9ef">String</span>(iaqSensor.version.major_bugfix) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;.&#34;</span> <span style="color:#f92672">+</span> <span style="color:#66d9ef">String</span>(iaqSensor.version.minor_bugfix);
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">Serial</span>.<span style="color:#a6e22e">println</span>(output);
</span></span><span style="display:flex;"><span>  checkIaqSensorStatus();
</span></span><span style="display:flex;"><span>  iaqSensor.setConfig(bsec_config_iaq);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">//loadState here refers to the BSEC Calibration state.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  loadState();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// List all the sensors we want the bsec library to give us data for
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  bsec_virtual_sensor_t sensorList[<span style="color:#ae81ff">10</span>] <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    BSEC_OUTPUT_RAW_TEMPERATURE,
</span></span><span style="display:flex;"><span>    BSEC_OUTPUT_RAW_PRESSURE,
</span></span><span style="display:flex;"><span>    BSEC_OUTPUT_RAW_HUMIDITY,
</span></span><span style="display:flex;"><span>    BSEC_OUTPUT_RAW_GAS,
</span></span><span style="display:flex;"><span>    BSEC_OUTPUT_IAQ,
</span></span><span style="display:flex;"><span>    BSEC_OUTPUT_STATIC_IAQ,
</span></span><span style="display:flex;"><span>    BSEC_OUTPUT_CO2_EQUIVALENT,
</span></span><span style="display:flex;"><span>    BSEC_OUTPUT_BREATH_VOC_EQUIVALENT,
</span></span><span style="display:flex;"><span>    BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE,
</span></span><span style="display:flex;"><span>    BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY,
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  iaqSensor.setTemperatureOffset(tempOffset);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// Receive data from sensor list above at BSEC_SAMPLE_RATE_LP rate (every 3 seconds). There is also BSEC_SAMPLE_RATE_ULP - which requires a configuration change above.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  iaqSensor.updateSubscription(sensorList, <span style="color:#ae81ff">10</span>, BSEC_SAMPLE_RATE_LP);
</span></span><span style="display:flex;"><span>  checkIaqSensorStatus();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  lcd.init();
</span></span><span style="display:flex;"><span>  lcd.backlight();
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// I wanted some iconography, so this function creates some icons in the LCDs memory. They were created using Maxpromers LCD Character Creator
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  createLCDSymbols();
</span></span><span style="display:flex;"><span>  connectToNetwork();
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Function that is looped forever
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">void</span> <span style="color:#a6e22e">loop</span>(<span style="color:#66d9ef">void</span>)
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span>broker.<span style="color:#a6e22e">connected</span>()) {
</span></span><span style="display:flex;"><span>    reconnectToBroker();
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  broker.<span style="color:#66d9ef">loop</span>();
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// iaqSensor.run() will return true once new data becomes available
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#66d9ef">if</span> (iaqSensor.<span style="color:#a6e22e">run</span>()) {
</span></span><span style="display:flex;"><span>    lcd.<span style="color:#a6e22e">clear</span>();
</span></span><span style="display:flex;"><span>    displayIAQ(<span style="color:#66d9ef">String</span>(iaqSensor.staticIaq));
</span></span><span style="display:flex;"><span>    displayTemp(<span style="color:#66d9ef">String</span>(iaqSensor.temperature));
</span></span><span style="display:flex;"><span>    displayHumidity(<span style="color:#66d9ef">String</span>(iaqSensor.humidity));
</span></span><span style="display:flex;"><span>    displayPressure(<span style="color:#66d9ef">String</span>(iaqSensor.pressure<span style="color:#f92672">/</span><span style="color:#ae81ff">100</span>));
</span></span><span style="display:flex;"><span>    displayCO2(<span style="color:#66d9ef">String</span>(iaqSensor.co2Equivalent));
</span></span><span style="display:flex;"><span>    displayVOC(<span style="color:#66d9ef">String</span>(iaqSensor.breathVocEquivalent));
</span></span><span style="display:flex;"><span>    displaySensorPersisted();
</span></span><span style="display:flex;"><span>    updateState();
</span></span><span style="display:flex;"><span>  } <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>    checkIaqSensorStatus();
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// checks to make sure the BME680 Sensor is working correctly.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">void</span> <span style="color:#a6e22e">checkIaqSensorStatus</span>(<span style="color:#66d9ef">void</span>)
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (iaqSensor.status <span style="color:#f92672">!=</span> BSEC_OK) {
</span></span><span style="display:flex;"><span>    lcd.<span style="color:#a6e22e">setCursor</span>(<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">0</span>);
</span></span><span style="display:flex;"><span>    lcd.<span style="color:#a6e22e">print</span>(<span style="color:#e6db74">&#34;Sensor error&#34;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (iaqSensor.status <span style="color:#f92672">&lt;</span> BSEC_OK) {
</span></span><span style="display:flex;"><span>      output <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;BSEC error code : &#34;</span> <span style="color:#f92672">+</span> <span style="color:#66d9ef">String</span>(iaqSensor.status);
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">Serial</span>.<span style="color:#a6e22e">println</span>(output);
</span></span><span style="display:flex;"><span>    } <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>      output <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;BSEC warning code : &#34;</span> <span style="color:#f92672">+</span> <span style="color:#66d9ef">String</span>(iaqSensor.status);
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">Serial</span>.<span style="color:#a6e22e">println</span>(output);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">delay</span>(<span style="color:#ae81ff">5000</span>);
</span></span><span style="display:flex;"><span>    lcd.<span style="color:#a6e22e">clear</span>();
</span></span><span style="display:flex;"><span>    checkIaqSensorStatus();
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (iaqSensor.bme680Status <span style="color:#f92672">!=</span> BME680_OK) {
</span></span><span style="display:flex;"><span>    lcd.<span style="color:#a6e22e">setCursor</span>(<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">0</span>);
</span></span><span style="display:flex;"><span>    lcd.<span style="color:#a6e22e">print</span>(<span style="color:#e6db74">&#34;Sensor error&#34;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (iaqSensor.bme680Status <span style="color:#f92672">&lt;</span> BME680_OK) {
</span></span><span style="display:flex;"><span>      output <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;BME680 error code : &#34;</span> <span style="color:#f92672">+</span> <span style="color:#66d9ef">String</span>(iaqSensor.bme680Status);
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">Serial</span>.<span style="color:#a6e22e">println</span>(output);
</span></span><span style="display:flex;"><span>    } <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>      output <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;BME680 warning code : &#34;</span> <span style="color:#f92672">+</span> <span style="color:#66d9ef">String</span>(iaqSensor.bme680Status);
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">Serial</span>.<span style="color:#a6e22e">println</span>(output);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">delay</span>(<span style="color:#ae81ff">5000</span>);
</span></span><span style="display:flex;"><span>    lcd.<span style="color:#a6e22e">clear</span>();
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// The below display functions display data as well as publishing it to the MQTT broker. They expect the area that they are rendering to to be free of characters.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">void</span> <span style="color:#a6e22e">displayIAQ</span>(<span style="color:#66d9ef">String</span> iaq)
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  lcd.<span style="color:#a6e22e">setCursor</span>(<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">0</span>);
</span></span><span style="display:flex;"><span>  lcd.<span style="color:#a6e22e">write</span>(<span style="color:#ae81ff">0</span>);
</span></span><span style="display:flex;"><span>  lcd.<span style="color:#a6e22e">print</span>(iaq);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">char</span> carr[iaq.<span style="color:#a6e22e">length</span>()];
</span></span><span style="display:flex;"><span>  iaq.<span style="color:#a6e22e">toCharArray</span>(carr, iaq.<span style="color:#a6e22e">length</span>());
</span></span><span style="display:flex;"><span>  broker.publish(<span style="color:#e6db74">&#34;bme680/iaq&#34;</span>, carr);
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">void</span> <span style="color:#a6e22e">displayTemp</span>(<span style="color:#66d9ef">String</span> tmp)
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  lcd.<span style="color:#a6e22e">setCursor</span>(<span style="color:#ae81ff">14</span>, <span style="color:#ae81ff">0</span>);
</span></span><span style="display:flex;"><span>  lcd.<span style="color:#a6e22e">print</span>(tmp);
</span></span><span style="display:flex;"><span>  lcd.<span style="color:#a6e22e">write</span>(<span style="color:#ae81ff">1</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">char</span> carr[tmp.<span style="color:#a6e22e">length</span>()];
</span></span><span style="display:flex;"><span>  tmp.<span style="color:#a6e22e">toCharArray</span>(carr, tmp.<span style="color:#a6e22e">length</span>());
</span></span><span style="display:flex;"><span>  broker.publish(<span style="color:#e6db74">&#34;bme680/temperature&#34;</span>, carr);
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">void</span> <span style="color:#a6e22e">displayHumidity</span>(<span style="color:#66d9ef">String</span> humidity)
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  lcd.<span style="color:#a6e22e">setCursor</span>(<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">1</span>);
</span></span><span style="display:flex;"><span>  lcd.<span style="color:#a6e22e">write</span>(<span style="color:#ae81ff">2</span>);
</span></span><span style="display:flex;"><span>  lcd.<span style="color:#a6e22e">print</span>(humidity <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;%&#34;</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">char</span> carr[humidity.<span style="color:#a6e22e">length</span>()];
</span></span><span style="display:flex;"><span>  humidity.<span style="color:#a6e22e">toCharArray</span>(carr, humidity.<span style="color:#a6e22e">length</span>());
</span></span><span style="display:flex;"><span>  broker.publish(<span style="color:#e6db74">&#34;bme680/humidity&#34;</span>, carr);
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">void</span> <span style="color:#a6e22e">displayPressure</span>(<span style="color:#66d9ef">String</span> pressure)
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  lcd.<span style="color:#a6e22e">setCursor</span>(<span style="color:#ae81ff">12</span>, <span style="color:#ae81ff">1</span>);
</span></span><span style="display:flex;"><span>  lcd.<span style="color:#a6e22e">print</span>(pressure);
</span></span><span style="display:flex;"><span>  lcd.<span style="color:#a6e22e">write</span>(<span style="color:#ae81ff">3</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">char</span> carr[pressure.<span style="color:#a6e22e">length</span>()];
</span></span><span style="display:flex;"><span>  pressure.<span style="color:#a6e22e">toCharArray</span>(carr, pressure.<span style="color:#a6e22e">length</span>());
</span></span><span style="display:flex;"><span>  broker.publish(<span style="color:#e6db74">&#34;bme680/pressure&#34;</span>, carr);
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">void</span> <span style="color:#a6e22e">displayCO2</span>(<span style="color:#66d9ef">String</span> co)
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  lcd.<span style="color:#a6e22e">setCursor</span>(<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">3</span>);
</span></span><span style="display:flex;"><span>  lcd.<span style="color:#a6e22e">print</span>(<span style="color:#e6db74">&#34;CO2 &#34;</span> <span style="color:#f92672">+</span> co <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;ppm&#34;</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">char</span> carr[co.<span style="color:#a6e22e">length</span>()];
</span></span><span style="display:flex;"><span>  co.<span style="color:#a6e22e">toCharArray</span>(carr, co.<span style="color:#a6e22e">length</span>());
</span></span><span style="display:flex;"><span>  broker.publish(<span style="color:#e6db74">&#34;bme680/co&#34;</span>, carr);
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">void</span> <span style="color:#a6e22e">displayVOC</span>(<span style="color:#66d9ef">String</span> voc)
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  lcd.<span style="color:#a6e22e">setCursor</span>(<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">2</span>);
</span></span><span style="display:flex;"><span>  lcd.<span style="color:#a6e22e">print</span>(<span style="color:#e6db74">&#34;VOC &#34;</span> <span style="color:#f92672">+</span> voc <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;ppm&#34;</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">char</span> carr[voc.<span style="color:#a6e22e">length</span>()];
</span></span><span style="display:flex;"><span>  voc.<span style="color:#a6e22e">toCharArray</span>(carr, voc.<span style="color:#a6e22e">length</span>());
</span></span><span style="display:flex;"><span>  broker.publish(<span style="color:#e6db74">&#34;bme680/voc&#34;</span>, carr);
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">void</span> <span style="color:#a6e22e">displaySensorPersisted</span>()
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  lcd.<span style="color:#a6e22e">setCursor</span>(<span style="color:#ae81ff">19</span>,<span style="color:#ae81ff">3</span>);
</span></span><span style="display:flex;"><span>  lcd.<span style="color:#a6e22e">print</span>(sensorPersisted);
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">void</span> <span style="color:#a6e22e">connectToNetwork</span>() {
</span></span><span style="display:flex;"><span>  lcd.<span style="color:#a6e22e">setCursor</span>(<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">0</span>);
</span></span><span style="display:flex;"><span>  lcd.<span style="color:#a6e22e">print</span>(<span style="color:#e6db74">&#34;Connecting to&#34;</span>);
</span></span><span style="display:flex;"><span>  lcd.<span style="color:#a6e22e">setCursor</span>(<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">1</span>);
</span></span><span style="display:flex;"><span>  lcd.<span style="color:#a6e22e">print</span>(ssid);
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">WiFi</span>.mode(WIFI_STA);
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">WiFi</span>.<span style="color:#a6e22e">begin</span>(ssid, passphrase);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">while</span> (<span style="color:#a6e22e">WiFi</span>.status() <span style="color:#f92672">!=</span> WL_CONNECTED) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">delay</span>(<span style="color:#ae81ff">1000</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Serial</span>.<span style="color:#a6e22e">println</span>(<span style="color:#e6db74">&#34;Establishing connection to WiFi..&#34;</span>);
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  lcd.<span style="color:#a6e22e">clear</span>();
</span></span><span style="display:flex;"><span>  lcd.<span style="color:#a6e22e">setCursor</span>(<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">0</span>);
</span></span><span style="display:flex;"><span>  lcd.<span style="color:#a6e22e">print</span>(<span style="color:#e6db74">&#34;Connected&#34;</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">Serial</span>.<span style="color:#a6e22e">println</span>(<span style="color:#e6db74">&#34;Connected to network&#34;</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">delay</span>(<span style="color:#ae81ff">1000</span>);
</span></span><span style="display:flex;"><span>  lcd.<span style="color:#a6e22e">clear</span>();
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">void</span> <span style="color:#a6e22e">reconnectToBroker</span>() {
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// Loop until we&#39;re reconnected
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#66d9ef">while</span> (<span style="color:#f92672">!</span>broker.<span style="color:#a6e22e">connected</span>()) {
</span></span><span style="display:flex;"><span>    lcd.<span style="color:#a6e22e">setCursor</span>(<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">0</span>);
</span></span><span style="display:flex;"><span>    lcd.<span style="color:#a6e22e">print</span>(<span style="color:#e6db74">&#34;Connecting MQTT&#34;</span>);
</span></span><span style="display:flex;"><span>    lcd.<span style="color:#a6e22e">setCursor</span>(<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">1</span>);
</span></span><span style="display:flex;"><span>    lcd.<span style="color:#a6e22e">print</span>(mqtt_server);
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Serial</span>.<span style="color:#a6e22e">print</span>(<span style="color:#e6db74">&#34;Attempting MQTT connection...&#34;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Create a random client ID
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">String</span> clientId <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;aqm-&#34;</span>;
</span></span><span style="display:flex;"><span>    clientId <span style="color:#f92672">+=</span> <span style="color:#66d9ef">String</span>(<span style="color:#a6e22e">random</span>(<span style="color:#ae81ff">0xffff</span>), <span style="color:#66d9ef">HEX</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Attempt to connect
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">if</span> (broker.<span style="color:#a6e22e">connect</span>(clientId.c_str())) {
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">Serial</span>.<span style="color:#a6e22e">println</span>(<span style="color:#e6db74">&#34;connected to broker&#34;</span>);
</span></span><span style="display:flex;"><span>      lcd.<span style="color:#a6e22e">clear</span>();
</span></span><span style="display:flex;"><span>      lcd.<span style="color:#a6e22e">setCursor</span>(<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">0</span>);
</span></span><span style="display:flex;"><span>      lcd.<span style="color:#a6e22e">print</span>(<span style="color:#e6db74">&#34;Connected&#34;</span>);
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">delay</span>(<span style="color:#ae81ff">1000</span>);
</span></span><span style="display:flex;"><span>    } <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>      lcd.<span style="color:#a6e22e">setCursor</span>(<span style="color:#ae81ff">0</span>,<span style="color:#ae81ff">2</span>);
</span></span><span style="display:flex;"><span>      lcd.<span style="color:#a6e22e">print</span>(<span style="color:#e6db74">&#34;Broker failed&#34;</span>);
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">Serial</span>.<span style="color:#a6e22e">print</span>(<span style="color:#e6db74">&#34;failed connecting to broker, rc=&#34;</span>);
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">Serial</span>.<span style="color:#a6e22e">print</span>(broker.state());
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">Serial</span>.<span style="color:#a6e22e">println</span>(<span style="color:#e6db74">&#34; try again in 5 seconds&#34;</span>);
</span></span><span style="display:flex;"><span>      <span style="color:#75715e">// Wait 5 seconds before retrying
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>      <span style="color:#a6e22e">delay</span>(<span style="color:#ae81ff">5000</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    lcd.<span style="color:#a6e22e">clear</span>();
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">void</span> <span style="color:#a6e22e">createLCDSymbols</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">byte</span> iaqsymbol[] <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x04</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x0A</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x1F</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x11</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x0E</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x0A</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x0E</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x02</span>
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>  lcd.<span style="color:#a6e22e">createChar</span>(<span style="color:#ae81ff">0</span>, iaqsymbol);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">byte</span> tempSymbol[] <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x18</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x18</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x07</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x04</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x04</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x04</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x04</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x07</span>
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>  lcd.<span style="color:#a6e22e">createChar</span>(<span style="color:#ae81ff">1</span>, tempSymbol);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">byte</span> humiditySymbol[] <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x04</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x04</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x0A</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x0A</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x11</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x11</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x0A</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x04</span>
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>  lcd.<span style="color:#a6e22e">createChar</span>(<span style="color:#ae81ff">2</span>, humiditySymbol);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">byte</span> airPressureSymbol[] <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x04</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x15</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x0E</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x04</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x01</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x1E</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x01</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x1E</span>
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>  lcd.<span style="color:#a6e22e">createChar</span>(<span style="color:#ae81ff">3</span>, airPressureSymbol);
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// loadState attempts to read the BSEC state from the EEPROM. If the state isn&#39;t there yet - it wipes that area of the EEPROM ready to be written to in the future. It&#39;ll also set the global variable &#39;sensorPersisted&#39; to a space, so that the question mark disappears forever from the LCD.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">void</span> <span style="color:#a6e22e">loadState</span>(<span style="color:#66d9ef">void</span>)
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">EEPROM</span>.<span style="color:#a6e22e">read</span>(<span style="color:#ae81ff">0</span>) <span style="color:#f92672">==</span> BSEC_MAX_STATE_BLOB_SIZE) {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Existing state in EEPROM
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">Serial</span>.<span style="color:#a6e22e">println</span>(<span style="color:#e6db74">&#34;Reading state from EEPROM&#34;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> (<span style="color:#66d9ef">uint8_t</span> i <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>; i <span style="color:#f92672">&lt;</span> BSEC_MAX_STATE_BLOB_SIZE; i<span style="color:#f92672">++</span>) {
</span></span><span style="display:flex;"><span>      bsecState[i] <span style="color:#f92672">=</span> <span style="color:#a6e22e">EEPROM</span>.<span style="color:#a6e22e">read</span>(i <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span>);
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">Serial</span>.<span style="color:#a6e22e">println</span>(bsecState[i], <span style="color:#66d9ef">HEX</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    sensorPersisted <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34; &#34;</span>;
</span></span><span style="display:flex;"><span>    iaqSensor.setState(bsecState);
</span></span><span style="display:flex;"><span>    checkIaqSensorStatus();
</span></span><span style="display:flex;"><span>  } <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Erase the EEPROM with zeroes
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">Serial</span>.<span style="color:#a6e22e">println</span>(<span style="color:#e6db74">&#34;Erasing EEPROM&#34;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> (<span style="color:#66d9ef">uint8_t</span> i <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>; i <span style="color:#f92672">&lt;</span> BSEC_MAX_STATE_BLOB_SIZE <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span>; i<span style="color:#f92672">++</span>)
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">EEPROM</span>.<span style="color:#a6e22e">write</span>(i, <span style="color:#ae81ff">0</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">EEPROM</span>.commit();
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// updateState waits for the in air quality accuracy to hit &#39;3&#39; - and will then write the state to the EEPROM. Then on every STATE_SAVE_PERIOD, it&#39;ll update the state.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">void</span> <span style="color:#a6e22e">updateState</span>(<span style="color:#66d9ef">void</span>)
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">bool</span> update <span style="color:#f92672">=</span> false;
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">/* Set a trigger to save the state. Here, the state is saved every STATE_SAVE_PERIOD with the first state being saved once the algorithm achieves full calibration, i.e. iaqAccuracy = 3 */</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (stateUpdateCounter <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (iaqSensor.iaqAccuracy <span style="color:#f92672">&gt;=</span> <span style="color:#ae81ff">3</span>) {
</span></span><span style="display:flex;"><span>      update <span style="color:#f92672">=</span> true;
</span></span><span style="display:flex;"><span>      stateUpdateCounter<span style="color:#f92672">++</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  } <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">/* Update every STATE_SAVE_PERIOD milliseconds */</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> ((stateUpdateCounter <span style="color:#f92672">*</span> STATE_SAVE_PERIOD) <span style="color:#f92672">&lt;</span> <span style="color:#a6e22e">millis</span>()) {
</span></span><span style="display:flex;"><span>      update <span style="color:#f92672">=</span> true;
</span></span><span style="display:flex;"><span>      stateUpdateCounter<span style="color:#f92672">++</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (update) {
</span></span><span style="display:flex;"><span>    iaqSensor.getState(bsecState);
</span></span><span style="display:flex;"><span>    checkIaqSensorStatus();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Serial</span>.<span style="color:#a6e22e">println</span>(<span style="color:#e6db74">&#34;Writing state to EEPROM&#34;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> (<span style="color:#66d9ef">uint8_t</span> i <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>; i <span style="color:#f92672">&lt;</span> BSEC_MAX_STATE_BLOB_SIZE ; i<span style="color:#f92672">++</span>) {
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">EEPROM</span>.<span style="color:#a6e22e">write</span>(i <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span>, bsecState[i]);
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">Serial</span>.<span style="color:#a6e22e">println</span>(bsecState[i], <span style="color:#66d9ef">HEX</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">EEPROM</span>.<span style="color:#a6e22e">write</span>(<span style="color:#ae81ff">0</span>, BSEC_MAX_STATE_BLOB_SIZE);
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">EEPROM</span>.commit();
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<h2 id="server-software">
  <a class="Heading-link u-clickable" href="/where-embedded-meets-the-internet-building-your-own-air-quality-meter/#server-software">Server Software</a>
</h2>
<h3 id="mqtt">
  <a class="Heading-link u-clickable" href="/where-embedded-meets-the-internet-building-your-own-air-quality-meter/#mqtt">MQTT</a>
</h3>
<p>If you&rsquo;re using the code above, you&rsquo;re going to need a MQTT broker somewhere to publish to. Assuming you have a Raspberry Pi or something set up on your local network, you want to <a href="https://appcodelabs.com/introduction-to-iot-build-an-mqtt-server-using-raspberry-pi">install Mosquitto</a>. Once you&rsquo;ve confirmed you can publish and subscribe to Mosquitto, you can put the IP of the MQTT broker into the code above. You should then be able to run the <code>ESP32</code> code above and have it start displaying stuff to the LCD and from another machine on your network that has the Mosquitto client installed do</p>
<p><code>mosquitto_sub -h &lt;mqtt-broker-ip-address&gt; -t &quot;bme680/#&quot;</code></p>
<p>and have it return the same data as is displayed on the LCD!</p>
<p>From this point, you could install an app on your phone like <a href="https://play.google.com/store/apps/details?id=net.routix.mqttdash&amp;hl=en_GB">MQTT Dash</a> which you can then configure with details of your MQTT broker so you can display your air quality data in real time on your phone.</p>
<h3 id="persisting-data-with-influxdb">
  <a class="Heading-link u-clickable" href="/where-embedded-meets-the-internet-building-your-own-air-quality-meter/#persisting-data-with-influxdb">Persisting data with Influxdb</a>
</h3>
<p>I wanted persistence, and that&rsquo;s where Influxdb comes in. Install <a href="https://pimylifeup.com/raspberry-pi-influxdb/">Influxdb</a> on your server and confirm that you can access your Influxdb instance (the <code>influx</code> command should result in an Influxdb shell).</p>
<p>Now you&rsquo;ll need some way to:</p>
<ol>
<li>Subscribe to your MQTT broker and receive events relating to the bme680 topic</li>
<li>Connect to Influxdb</li>
<li>Write this point data to Influxdb.</li>
</ol>
<p>I provide example code I wrote in Golang below. it is also available on Github - <a href="https://github.com/kn100/mqtt680influxbridge">See the mqtt680influxbridge project</a>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// kn100.me - Arduairs
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">// Available on Github: https://github.com/kn100/mqtt680influxbridge
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">// Note: This code is provided as an example only - it does not implement authentication, TLS or anything even remotely close to security. It&#39;s probably fine if all your infrastructure lives on your local network, but you might want to consider looking into security.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;log&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;os&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;os/signal&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;strconv&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;syscall&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;time&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">mqtt</span> <span style="color:#e6db74">&#34;github.com/eclipse/paho.mqtt.golang&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">influx</span> <span style="color:#e6db74">&#34;github.com/influxdata/influxdb1-client/v2&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">var</span> (
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">mqttAddress</span>     = <span style="color:#a6e22e">envString</span>(<span style="color:#e6db74">&#34;MQTT_ADDRESS&#34;</span>, <span style="color:#e6db74">&#34;127.0.0.1&#34;</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">mqttPort</span>        = <span style="color:#a6e22e">envString</span>(<span style="color:#e6db74">&#34;MQTT_ADDRESS&#34;</span>, <span style="color:#e6db74">&#34;1883&#34;</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">mqttTopic</span>       = <span style="color:#a6e22e">envString</span>(<span style="color:#e6db74">&#34;MQTT_TOPIC&#34;</span>, <span style="color:#e6db74">&#34;bme680/+&#34;</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">influxDbAddress</span> = <span style="color:#a6e22e">envString</span>(<span style="color:#e6db74">&#34;INFLUXDB_ADDRESS&#34;</span>, <span style="color:#e6db74">&#34;127.0.0.1&#34;</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">influxDbDb</span>      = <span style="color:#a6e22e">envString</span>(<span style="color:#e6db74">&#34;INFLUXDB_DB&#34;</span>, <span style="color:#e6db74">&#34;bme680&#34;</span>)
</span></span><span style="display:flex;"><span>	)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">sigs</span> <span style="color:#f92672">:=</span> make(<span style="color:#66d9ef">chan</span> <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Signal</span>, <span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">signal</span>.<span style="color:#a6e22e">Notify</span>(<span style="color:#a6e22e">sigs</span>, <span style="color:#a6e22e">syscall</span>.<span style="color:#a6e22e">SIGINT</span>, <span style="color:#a6e22e">syscall</span>.<span style="color:#a6e22e">SIGTERM</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">influxHost</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Sprintf</span>(<span style="color:#e6db74">&#34;http://%s:%d&#34;</span>, <span style="color:#a6e22e">influxDbAddress</span>, <span style="color:#ae81ff">8086</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">influxClient</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">influx</span>.<span style="color:#a6e22e">NewHTTPClient</span>(<span style="color:#a6e22e">influx</span>.<span style="color:#a6e22e">HTTPConfig</span>{<span style="color:#a6e22e">Addr</span>: <span style="color:#a6e22e">influxHost</span>, <span style="color:#a6e22e">Timeout</span>: <span style="color:#ae81ff">5</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>})
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Fatal</span>(<span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">opts</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">mqtt</span>.<span style="color:#a6e22e">NewClientOptions</span>().<span style="color:#a6e22e">AddBroker</span>(<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Sprintf</span>(<span style="color:#e6db74">&#34;tcp://%s:%s&#34;</span>, <span style="color:#a6e22e">mqttAddress</span>, <span style="color:#a6e22e">mqttPort</span>))
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">mqttClient</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">mqtt</span>.<span style="color:#a6e22e">NewClient</span>(<span style="color:#a6e22e">opts</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">token</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">mqttClient</span>.<span style="color:#a6e22e">Connect</span>(); <span style="color:#a6e22e">token</span>.<span style="color:#a6e22e">Wait</span>() <span style="color:#f92672">&amp;&amp;</span> <span style="color:#a6e22e">token</span>.<span style="color:#a6e22e">Error</span>() <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		panic(<span style="color:#a6e22e">token</span>.<span style="color:#a6e22e">Error</span>())
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">f</span> <span style="color:#a6e22e">mqtt</span>.<span style="color:#a6e22e">MessageHandler</span> = <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">client</span> <span style="color:#a6e22e">mqtt</span>.<span style="color:#a6e22e">Client</span>, <span style="color:#a6e22e">message</span> <span style="color:#a6e22e">mqtt</span>.<span style="color:#a6e22e">Message</span>) {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;received message on topic: %s - message: %s\n&#34;</span>, <span style="color:#a6e22e">message</span>.<span style="color:#a6e22e">Topic</span>(), <span style="color:#a6e22e">message</span>.<span style="color:#a6e22e">Payload</span>())
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">topic</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">message</span>.<span style="color:#a6e22e">Topic</span>()
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">val</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">strconv</span>.<span style="color:#a6e22e">ParseFloat</span>(string(<span style="color:#a6e22e">message</span>.<span style="color:#a6e22e">Payload</span>()), <span style="color:#ae81ff">32</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;invalid point data received from broker, ignoring&#34;</span>)
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">fields</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">map</span>[<span style="color:#66d9ef">string</span>]<span style="color:#66d9ef">interface</span>{}{
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">topic</span>: <span style="color:#a6e22e">val</span>,
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">influxPoint</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">influx</span>.<span style="color:#a6e22e">NewPoint</span>(<span style="color:#a6e22e">message</span>.<span style="color:#a6e22e">Topic</span>(), <span style="color:#66d9ef">nil</span>, <span style="color:#a6e22e">fields</span>, <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Now</span>())
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">bp</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">influx</span>.<span style="color:#a6e22e">NewBatchPoints</span>(<span style="color:#a6e22e">influx</span>.<span style="color:#a6e22e">BatchPointsConfig</span>{
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">Database</span>:        <span style="color:#a6e22e">influxDbDb</span>,
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">Precision</span>:       <span style="color:#e6db74">&#34;s&#34;</span>,
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">RetentionPolicy</span>: <span style="color:#e6db74">&#34;30_days&#34;</span>,
</span></span><span style="display:flex;"><span>		})
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Fatalln</span>(<span style="color:#e6db74">&#34;Error: &#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">bp</span>.<span style="color:#a6e22e">AddPoint</span>(<span style="color:#a6e22e">influxPoint</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">err</span> = <span style="color:#a6e22e">influxClient</span>.<span style="color:#a6e22e">Write</span>(<span style="color:#a6e22e">bp</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;couldn&#39;t write to influx for some reason - ignored&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;data written to influx&#34;</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">token</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">mqttClient</span>.<span style="color:#a6e22e">Subscribe</span>(<span style="color:#a6e22e">mqttTopic</span>, <span style="color:#ae81ff">0</span>, <span style="color:#a6e22e">f</span>); <span style="color:#a6e22e">token</span>.<span style="color:#a6e22e">Wait</span>() <span style="color:#f92672">&amp;&amp;</span> <span style="color:#a6e22e">token</span>.<span style="color:#a6e22e">Error</span>() <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Fatal</span>(<span style="color:#a6e22e">token</span>.<span style="color:#a6e22e">Error</span>())
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">sigs</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;Exiting&#34;</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">envString</span>(<span style="color:#a6e22e">key</span>, <span style="color:#a6e22e">fallback</span> <span style="color:#66d9ef">string</span>) <span style="color:#66d9ef">string</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">s</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Getenv</span>(<span style="color:#a6e22e">key</span>); <span style="color:#a6e22e">s</span> <span style="color:#f92672">!=</span> <span style="color:#e6db74">&#34;&#34;</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">s</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">fallback</span>
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div></p>
<p>This code is the most basic possible bridge I could think of. It immediately writes every data point that it gets from the subscription to Influx, completely ignoring the helpful batching logic that the library provides. A significant improvement to this code would be to do batching properly. I couldn&rsquo;t be bothered for now, maybe in a future blog post.</p>
<p>As soon as you&rsquo;ve gotten this code logging that is is writing to Influx, it&rsquo;s time to move onto Grafana.</p>
<h3 id="look-at-this-graphhttpswwwyoutubecomwatchvsilnivxpins---grafana">
  <a class="Heading-link u-clickable" href="/where-embedded-meets-the-internet-building-your-own-air-quality-meter/#look-at-this-graphhttpswwwyoutubecomwatchvsilnivxpins---grafana"><a href="https://www.youtube.com/watch?v=sIlNIVXpIns">Look at this graph</a> - Grafana</a>
</h3>
<p>Grafana is nice. You give it a database connection, it&rsquo;ll provide you with a nice visual query builder that you can use to build graphs. You probably want to install Grafana on the same instance that is hosting your Influxdb. <a href="https://pimylifeup.com/raspberry-pi-grafana/">Here&rsquo;s a nice tutorial</a> on getting Grafana set up.</p>
<p>I provide my Dashboard model below, which you are free to take and import into your Grafana instance. You&rsquo;ll firstly need to add your Influxdb as a data source to Grafana. On the left, select the cog wheel, and then Data sources. Then select &lsquo;Add data source&rsquo; and configure exactly as below other than the URL (this should either be localhost or the host where your InfluxDB instance lives.</p>
<figure><img src="/posts/aqm/arduair-influx-ds-settings.png"
         alt="A screenshot of the setting that need to be set for the Influx data source, specifically the URL should be configured to be 192.168.0.17:8086, and the database name needs to be set to bme680"/><figcaption>
            <h4>The settings you&#39;ll need for the Influxdb connection</h4>
        </figcaption>
</figure>

<p>Then create a new dashboard. On that blank dashboard, select the cog wheel in the top right, and paste <a href="https://gist.github.com/kn100/a004de9c66a515b72da347230509b7c0">this</a> in JSON Model. when you head back to your dashboard, you now should see some pretty graphs that look somewhat like what I promised.</p>
<p>Let me know how you got on!</p>
]]></description>
    </item>
    
    <item>
      <title>Gnome Shell Stuttering Caused by AppIndicator</title>
      <link>http://kn100.me/gnome-shell-stuttering-caused-by-appindicator/</link>
      <pubDate>Tue, 19 May 2020 17:51:48 +0100</pubDate>
      
      <guid>http://kn100.me/gnome-shell-stuttering-caused-by-appindicator/</guid>
      <description><![CDATA[<p>I recently installed Pop!_OS on my new desktop machine and have been loving it, but have been suffering with this really strange issue where the entire UI would stutter roughly every second for a few milliseconds. This got really, really annoying so I had a little dig around and found out the cause was actually a strange interaction between Ubuntu AppIndicator (a bundled extension for Gnome Shell in Pop!_OS) and YTMDesktop - an electron wrapper for Youtube Music.</p>
<p>I installed <a href="https://ytmdesktop.app/">YTMDesktop</a> using Snap - a tool I don&rsquo;t fully understand nor like very much. I installed it using Snap since as far as I can tell it&rsquo;s a nice way to install apps you don&rsquo;t care that much about and supposedly provides some security niceties. After I figured out that the stuttering only happened while Youtube Music was playing music, I erroneously reported the bug to the Gnome Shell team. I even attempted to record the issue using a screen recording tool, but the stutters were bad enough that the screen recording tool itself was stuttering too and therefore not picking up on the lost frames! Almost immediately someone there pointed out it might be related to AppIndicator - so I tried disabling it through the inbuilt extensions menu in Pop!_OS (but you can install the Gnome Tweak Tool if this isn&rsquo;t available for you!) the stuttering immediately stopped! You can see this for yourself if you&rsquo;re affected by using something like the <a href="https://www.testufo.com/framerates#count=1&amp;background=stars&amp;pps=960">Blur Busters UFO test</a>.</p>
<p>I dug a little deeper and found in my <code>journalctl</code> there were hundreds of entries from YTMDesktop where it was attempting to access some resource in a flatpak related directory!</p>

<pre tabindex="0"><code>May 18 13:39:15 pop-os audit[36663]: AVC apparmor=&#34;DENIED&#34; operation=&#34;open&#34; profile=&#34;snap.youtube-music-desktop-app.youtube-music-desktop-app&#34; name=&#34;/home/kn100/.local/share/flatpak/exports/share/icons/hicolor/48x48/apps/&#34; pid=36663 comm=&#34;youtube-music-d&#34; requested_mask=&#34;r&#34; denied_mask=&#34;r&#34; fsuid=1000 ouid=1000</code></pre>
<p>It seems like maybe (and I do not understand Snap or Flatpak at all - although both are in use on my system) the Youtube Music Desktop App was originally packaged for Flatpak and then stuffed into a Snap. It&rsquo;s trying to access nonexistent resources now which assumedly is why AppIndicator is crashing.</p>
<p>It seems absolutely crazy to me that an extension to Gnome Shell could cause such a UI disaster - and wonder how many other apps cause similar issues. Users everywhere might install one of these apps and decide that Gnome Shell is garbage, as I have a few times in the past. From a little Googling, it seems that Discord and Megasync are both also affected. Discord seems a little egregious given that it&rsquo;s mainly used while gaming - an activity where stuttering is unacceptable.</p>
<p>For now I have disabled AppIndicator - but now certain applications I use have significant usability issues. When you close them, they remain open in the &lsquo;system tray&rsquo; that no longer exists, and therefore you have no way to access them now!</p>
<p>I understand the Gnome teams design goal of getting rid of system icons, but given that basically every OS packages AppIndicator or something similar since some apps absolutely require you to have access to their tray icon - it seems like maybe this design goal has hit the rails of reality and needs to be reconsidered.</p>
<p>I&rsquo;ve added my issue to the already existing issue on Github <a href="https://github.com/ubuntu/gnome-shell-extension-appindicator/issues/226">here</a>.</p>
]]></description>
    </item>
    
    <item>
      <title>What My First ‘Real’ Software Engineering Job Taught Me</title>
      <link>http://kn100.me/whirlwind-what-my-first-real-software-engineering-job-taught-me/</link>
      <pubDate>Sun, 08 Sep 2019 00:00:00 +0100</pubDate>
      
      <guid>http://kn100.me/whirlwind-what-my-first-real-software-engineering-job-taught-me/</guid>
      <description><![CDATA[<h2 id="having-speed-makes-others-happy-in-the-short-term-having-velocity-makes-you-happy-in-the-long-term">
  <a class="Heading-link u-clickable" href="/whirlwind-what-my-first-real-software-engineering-job-taught-me/#having-speed-makes-others-happy-in-the-short-term-having-velocity-makes-you-happy-in-the-long-term">Having speed makes others happy in the short term. Having velocity makes you happy in the long term.</a>
</h2>
<p>One of the biggest lessons I learned was to prioritise &lsquo;velocity&rsquo; over &lsquo;speed&rsquo;. This probably sounds meaningless to you, but allow me to explain. At some point around a year ago, during my one to ones with my manager, we were discussing the fact that I was heavily distracted and finding it tough to concentrate on tasks I wanted to work on. We established that one of the main causes of this was the fact that I would agree to help anyone who happened to ask for my help. Sales has an issue with something? Yes sure I&rsquo;ll look now! Marketing wants some data on something else? Why not, I&rsquo;ll look now. A more junior engineer would like help in a particular area of the codebase that I happen to be more familiar with? Sure, no problem, lets pair! I had high &lsquo;speed&rsquo;. I was helping others to accomplish their goals with no consideration for how to prioritize tasks to provide my team and the wider company with the maximum value I could provide.</p>
<p>Learning to prioritize, knowing when and how to say no, and most importantly when to say yes are crucial skills in improving your &lsquo;velocity&rsquo;. Velocity in this sense is being used to describe the pace at which you are delivering your maximum value. By constantly saying yes to others requests for assistance, I was failing to perform a value judgement comparing whether helping them to continuing with the task I was originally doing. As we all know, context switching is expensive. This led me to be known as one of the most helpful people at the company (I had more high fives than anyone else at the company!), but I also became known for being constantly busy and giving bad time estimates.</p>
<p>I believe it is innate in most of us to be helpful, however being helpful can actually reduce <em>your</em> overall effectiveness as an engineer. The best advice I can offer on this is best given in the form of a proverb of highly contested origin: &ldquo;Give a man a fish, and you feed him for a day. Teach a man to fish, and you feed him for a lifetime.&rdquo;.</p>
<h2 id="code-bases-that-have-been-around-for-a-while-might-look-weird-but-they-might-just-be-solving-problems-youre-not-yet-aware-of">
  <a class="Heading-link u-clickable" href="/whirlwind-what-my-first-real-software-engineering-job-taught-me/#code-bases-that-have-been-around-for-a-while-might-look-weird-but-they-might-just-be-solving-problems-youre-not-yet-aware-of">Code bases that have been around for a while might look weird, but they might just be solving problems you&rsquo;re not yet aware of.</a>
</h2>
<p>I&rsquo;m sure every single fresh-out-of-university experience-lacking engineer has had that moment where they join a company and see the code that powers it for the first time. Seemingly simple routines seem more complex than they need to be. Seemingly small problems have huge solutions. Craziness, in short. It can be terrifying. You&rsquo;re fresh out of school, looking at this codebase, wondering what the heck is going on? Why is everything so complex?</p>
<p>It turns out that managing a huge codebase is a hard problem, and a junior engineer is rarely a good judge of the scale or scope of a given problem. It&rsquo;s not that there isn&rsquo;t bad code in the codebase, its just more likely that any junior engineer with 3 months on the clock has most likely not seen enough of the system to fully understand the problems it is solving.</p>
<p>As time went on, I learned more about the actual problems this codebase was addressing. What I perceived as overcomplications turned out to be problems I wasn&rsquo;t aware of being solved. What I thought was badly designed code was actually just code I didn&rsquo;t understand. It&rsquo;s intimidating to work on such a large system, especially when the most ambitious project you&rsquo;ve worked on prior to it was several orders of magnitude smaller and not subjected to the brutality of customers, but if you&rsquo;re anything like me, you&rsquo;ll learn ridiculously quickly.</p>
<h2 id="team-culture-is-a-real-not-bullshit-thing">
  <a class="Heading-link u-clickable" href="/whirlwind-what-my-first-real-software-engineering-job-taught-me/#team-culture-is-a-real-not-bullshit-thing">Team culture is a real not-bullshit thing.</a>
</h2>
<p>By far my favourite thing about my first job was the learning opportunities around me. I was surrounded by massively intelligent, kind, determined people who all were more than willing to help me get up to speed.</p>
<p>I believe this environment didn&rsquo;t happen accidentally. The company was very careful to create a culture where this kind of collaboration could happen. Much more senior engineers spent probably weeks of their lives showing me the ropes and teaching me about anything and everything I wanted to know, work related or not. Every single one of these people was hugely passionate about what they do and loved to share their knowledge.</p>
<p>Outside of work, these people held clubs and sessions relating to areas of interest to them personally. I was particularly fond of but sadly nowhere near involved enough in an electronics session - the last activity of which was to build Tetris with nothing but &rsquo;nand&rsquo; logic gates! This environment is perfect for a junior engineer to thrive in, and I am hugely grateful to have had a job where the conditions were so ripe for it.</p>
<p>As I became more and more established and newer members of the team were hired, I found myself in a position to pay it forward, if you will. What I learned during these encounters was that the educational aspect of it goes both ways. The field is such a vast one that everybody probably knows something somebody else doesn&rsquo;t, so learning new things from these engineers was a common occurrence for me. The learning wasn&rsquo;t always technical either. I developed people skills I didn&rsquo;t have before. I began delegating. I grasped the best attitude to have towards people and the problems we faced together.</p>
<p>You, as much as anyone else, foster this spirit of reciprocal learning. Represent and grow it the best way you can.</p>
<h2 id="look-for-opportunities-to-lead-a-project-and-just-do-it">
  <a class="Heading-link u-clickable" href="/whirlwind-what-my-first-real-software-engineering-job-taught-me/#look-for-opportunities-to-lead-a-project-and-just-do-it">Look for opportunities to lead a project, and just do it.</a>
</h2>
<p>The average engineer at a startup seems to have to wear many hats. I found myself working on the core of the system in one language and on the billing system in another within the same week. I maintained SDKs in multiple different languages. I worked on our frontend as well as backend. This is a natural requirement at a startup; there just aren&rsquo;t enough people to cleanly define peoples roles and have them stick to them. I think its fair to say anyone in this position will gain a diverse set of skills that don&rsquo;t necessarily join up neatly. It also provides many opportunities for identifying problems with areas of the system that others just haven&rsquo;t looked at recently.</p>
<p>These are amazing opportunities for the ambitious engineer. It is possible to pick one of these areas and become the leader of a project in a startup environment.</p>
<p>If you wish to succeed in convincing your team that the problem you&rsquo;ve identified is worth solving, you&rsquo;re going to want to become the domain expert on the topic. Read up on other solutions to the problem you&rsquo;ve found. Talk to other engineers in the company about whether they&rsquo;re aware of it or even if they&rsquo;ve attempted to solve it before. Quantify the problem. The most effective metrics I&rsquo;ve found are monetary savings or developer efficiency improvements. The question you should be answering is why it is valuable for <em>you</em> to spend <em>your</em> time on this project rather than something, or someone else. You&rsquo;re not solving a problem; you&rsquo;re instead executing on an opportunity you&rsquo;ve identified.</p>
<p>It is important to remember here though not to outgrow your boots. This fresh into the field you can easily find yourself chasing rabbits and trying to solve huge problems that much more experienced engineers would struggle with. The best opportunities are the low hanging fruit.</p>
<h2 id="imposter-syndrome-is-real-and-it-sucks">
  <a class="Heading-link u-clickable" href="/whirlwind-what-my-first-real-software-engineering-job-taught-me/#imposter-syndrome-is-real-and-it-sucks">Imposter syndrome is real, and it sucks.</a>
</h2>
<p>I&rsquo;m not going to write much about this since so much has been written on the topic already. In my experience everybody suffering from it likely thinks that <em>they&rsquo;re</em> not the ones suffering with imposter syndrome; they&rsquo;re actually imposters! They&rsquo;re somehow flying under the radar, holding on by the skin of their teeth.</p>
<p>This is most likely not the case. Think about it logically. This field is generally well paid. The company is paying you for your time. In order for it to make sense for them to do so, you must be delivering value to the company in one form or another. You&rsquo;re probably not special. You&rsquo;re not fooling anyone. You&rsquo;re a junior engineer who has probably passed through their probationary period and is doing their job well enough to warrant the money you receive.</p>
<h2 id="in-the-first-few-months-focus-on-a-year-from-now">
  <a class="Heading-link u-clickable" href="/whirlwind-what-my-first-real-software-engineering-job-taught-me/#in-the-first-few-months-focus-on-a-year-from-now">In the first few months, focus on a year from now.</a>
</h2>
<p>At a startup, you&rsquo;re likely being brought on to wear many hats. Different kinds of work require different levels of context and different levels of experience. Its natural to gravitate towards tasks that require lower context and little experience, since these are the ones you&rsquo;re most likely to deliver results on quickly. If you&rsquo;re like me, you&rsquo;ll feel that for the first few months you aren&rsquo;t really delivering all that much value to the company, and you may even feel a little guilty about it. Why are they paying me to sit here and not know what I&rsquo;m doing?!</p>
<p>It turns out that you aren&rsquo;t the first software engineer hired fresh out of university, and they&rsquo;re as aware as you are of your lack of experience. There&rsquo;s no reasonable way you could contribute immediately. It takes time to acclimate to the &lsquo;real world&rsquo; and to learn the code and company well enough to be able to deliver quickly.</p>
<p>Don&rsquo;t make the same mistake I did and just focus on delivering as much as possible as early as possible. This pays off early but hurts you in the long run, since you&rsquo;re not giving yourself the chance to properly dig into the code. Spend this early time figuring out what interests <em>you</em> in the code and how you can deliver value to <em>yourself</em> while delivering for the company. This might involve learning a type of system you&rsquo;ve never seen before, or a programming language you didn&rsquo;t know before. This is okay! That is what this period is for. You&rsquo;re focussing on your own personal development in this time so that later you can deliver significantly more value.</p>
<p>If you instead continue on the path of delivering as much as possible as quickly as possible, you&rsquo;ll reap the rewards early on but you&rsquo;ll probably lose motivation as you continually work on projects that don&rsquo;t get you excited.</p>
<h2 id="own-your-fuckups-theyre-probably-okay">
  <a class="Heading-link u-clickable" href="/whirlwind-what-my-first-real-software-engineering-job-taught-me/#own-your-fuckups-theyre-probably-okay">Own your fuckups. They&rsquo;re probably okay.</a>
</h2>
<p>Every engineer is likely to do something that negatively impacts other engineers or customers. I personally recall accidentally leaving debug logging on a service which caused that service to use much more CPU and run slower for a bit. Thankfully we had CPU to spare, but this really shook me up. What surprised me was the completely accepting atmosphere I found myself in. I wasn&rsquo;t told off, and instead was asked to look into making a mistake like this impossible to repeat. This meant what was a very minor mistake with no impact to anything really except the heat output of a data center turned into a real reliability improvement - since mistakes like this could very well turn into very serious issues if not mitigated.</p>
<p>I get that not every work environment is going to be like this, but if yours is, lucky you. Go forth, make new mistakes.</p>
<h2 id="productivity-is-not-what-you-likely-think-it-is">
  <a class="Heading-link u-clickable" href="/whirlwind-what-my-first-real-software-engineering-job-taught-me/#productivity-is-not-what-you-likely-think-it-is">Productivity is not what you likely think it is.</a>
</h2>
<p>Productivity in Engineering is really difficult to quantify. Lines of Code is not a good measure. Neither are commits. Your days where you write no code can be massively productive.</p>
<p>This may sound obvious, however it took a conversation between me and a colleague, <a href="https://jameshfisher.com/">Jim</a> to realise this.</p>
<p>One day a year or so ago, I was having an extremely hectic day. I was all over the place. I was pairing with Engineers who had just joined the team to get them up to speed. I was writing docs. I was managing a contractor. I was participating in meetings  As the day wore on, I became more and more aware of the fact I&rsquo;d made <em>very</em> little progress towards the goals I&rsquo;d set myself. I&rsquo;d written zero code.</p>
<p>I ranted to Jim about how I felt like I had got nothing done. He then told me in no uncertain terms that if all I was valued for was LoC, I&rsquo;d be a code monkey, and that in fact I&rsquo;d had a hugely productive day. This conversation made me think. Other colleagues who I thought of as massively productive rarely wrote code too! Maybe I really was productive!</p>
<h2 id="communication-is-hard">
  <a class="Heading-link u-clickable" href="/whirlwind-what-my-first-real-software-engineering-job-taught-me/#communication-is-hard">Communication is HARD.</a>
</h2>
<p>I&rsquo;d like to think that I am a good communicator. I have the gift of the gab and will talk for Wales if given the opportunity. I&rsquo;m well known for being immensely friendly and constantly happy. I found regularly that I was misunderstood however. How could that be? This job taught me that it is possible to over-communicate. I was talking a lot, but not conveying much meaningful concrete information. When it came to collaborating with other engineers, I was great at talking about what we were doing but terrible at co-ordinating the work we were doing.</p>
<p>The way I began to solve this was pretty simple. I decided that any concrete decisions needed to be communicated in as few words as possible via writing. Anything more visionary could be discussed in person, but the outcomes needed to be written down. If there was stuff to be done, I assigned people to action them. If there was stuff that needed to be discussed, I scheduled a meeting and had an agenda. Not exactly rocket science, but an extremely useful lesson to learn.</p>
<p>This led to less words, more effectively spoken.</p>
<h2 id="giving-talks-is-a-great-way-to-bring-up-others-perceived-value-of-you">
  <a class="Heading-link u-clickable" href="/whirlwind-what-my-first-real-software-engineering-job-taught-me/#giving-talks-is-a-great-way-to-bring-up-others-perceived-value-of-you">Giving talks is a great way to bring up others perceived value of you.</a>
</h2>
<p>This links closely with the communication point I discussed above. I started giving talks to introduce others to what I was doing, and to open lines of communication that weren&rsquo;t previously there.</p>
<p>One time, I gave a talk on a project I was working on related to Terraform. I was working on importing AWS resources into Terraform so that we wouldn&rsquo;t have to recreate infra. This task was tricky, and involved many fiddly parts that didn&rsquo;t quite work properly. Terraform is not really suited to import massive amounts of old infra. After the talk, a member of another team offered his assistance to me as he&rsquo;d been working on Terraform recently too. I had no idea that others had experience with Terraform, and his assistance proved invaluable.</p>
<p>Other ways this is incredibly useful is to get the rest of an organisation excited about what it is that <strong>you</strong> do. It&rsquo;s all well and good having your direct team-mates understanding the value of your work, but what about the salespeople? Marketing? The quickest way to pique their interest in my estimation was a talk.</p>
<p>When giving a talk to people who aren&rsquo;t directly related to the tech you&rsquo;re working on, it&rsquo;s important to remember they likely don&rsquo;t care that much or understand that well the &ldquo;how&rdquo;. They want the &ldquo;what&rdquo;, &ldquo;why&rdquo;, &ldquo;who&rdquo;, and &ldquo;when&rdquo;. Put yourself in the shoes of the audience member you most want to get through to. Ask these questions.</p>
<ul>
<li>What is this work to me?</li>
<li>Why should <em>I</em> and those <em>I</em> communicate with care about this?</li>
<li>Who does this benefit?</li>
<li>When is it available?</li>
</ul>
<p>Your talk doesn&rsquo;t even necessarily need to be about what you&rsquo;re doing. I gave a talk once on Silicon bugs. This was pretty irrelevant to the day-to-day, but people found it very interesting and that sparked a few conversations with people around the business I had never spoken to before. These cross-business links can prove invaluable.</p>
<h2 id="at-some-points-you-might-be-the-most-knowledgeable">
  <a class="Heading-link u-clickable" href="/whirlwind-what-my-first-real-software-engineering-job-taught-me/#at-some-points-you-might-be-the-most-knowledgeable">At some points, you might be the most knowledgeable.</a>
</h2>
<p>This is a scary realisation. As you dig more and more into the problems you&rsquo;re solving, you&rsquo;ll begin to specialise in particular areas of the product. As a junior engineer, you&rsquo;re likely used to the comfort of having a senior engineer behind you to tell you when you&rsquo;re doing well and when you&rsquo;re not.  Your specialties in understanding develop, and suddenly your senior engineer tells you that they don&rsquo;t know! They&rsquo;ve got useful general advice, but whether your approach is correct or not, they don&rsquo;t know. You look up, and there is nobody to defer to now.</p>
<p>At this moment, you are probably the most knowledgeable on the topic at hand. You become responsible for seeking the answer.</p>
<p>Even more scary is when more junior engineers than you start to look up to you in the way you looked up to your senior engineer in the past. You look up again, and there&rsquo;s nobody to ask for specifics! This is when you try to pass on to the junior engineers useful ways of figuring out the answer that you developed when you first realised there&rsquo;s nobody to look up to anymore. These can range from simple &ldquo;how to use Stackoverflow&rdquo; to more specific &ldquo;what&rsquo;s enough due diligence to verify you&rsquo;re not going to break production?&rdquo;.</p>
]]></description>
    </item>
    
    <item>
      <title>How Do I Make Breaking Changes in Go Without Annoying People?</title>
      <link>http://kn100.me/making-breaking-changes-in-go/</link>
      <pubDate>Mon, 22 Jul 2019 10:19:34 +0100</pubDate>
      
      <guid>http://kn100.me/making-breaking-changes-in-go/</guid>
      <description><![CDATA[<p>Knowing when and how to make breaking changes is tough. It is even tougher in the Go ecosystem. After being burned by making a breaking change and annoying people, I&rsquo;m going to investigate how best to mitigate this annoyance.</p>
<blockquote>
<p><strong>Disclaimer</strong>: This is mostly opinion, and only my opinion. This post is not associated with my employer in any way. You can contact me on Mastodon <a href="https://fosstodon.org/@kn100">@kn100@fosstodon.org</a></p>
</blockquote>
<h2 id="what-even-is-a-breaking-change-anyway">
  <a class="Heading-link u-clickable" href="/making-breaking-changes-in-go/#what-even-is-a-breaking-change-anyway">What even is a breaking change, anyway</a>
</h2>
<p>A simple definition of a breaking change is any change you make to your code that could break other code which directly or indirectly depends on it.</p>
<p>In all seriousness, almost anything you do within a Go library that others depend on could be considered a breaking change. Did your commits fix a bug that was reported, without changing the API? It&rsquo;s possible there is code out there that was depending on the presence of that bug, and you&rsquo;ve just broken their code. Did you update a dependency you depend on? Did your dependency fix a bug consumers of your library were depending on? You get the point.</p>
<p>For the purposes of this article, I align my definition of a breaking change closely with the one given by Semver.org, which is any <a href="https://semver.org/">&ldquo;incompatible API change&rdquo;</a>. The key word there is API. This (to me at least) indicates that bug fixes or similar would be considered patch or minor version updates. If you anticipate that you&rsquo;ll break someones code through changing your API, that&rsquo;s a major version increment. Otherwise, it&rsquo;s a minor or patch. The clue is in the word &ldquo;semantic&rdquo;. A Semver number is intended to be read by humans, not computers. This does not guarantee that a minor version increment is going to be absolutely fine for everyone. Those edge cases where consumers rely on the presence of a bug are difficult to anticipate and therefore are not considered here. The more significant an increment, the more trouble the user is <em>likely</em> to have when upgrading.</p>
<h2 id="a-story-on-making-a-breaking-change">
  <a class="Heading-link u-clickable" href="/making-breaking-changes-in-go/#a-story-on-making-a-breaking-change">A story on making a breaking change</a>
</h2>
<p>I maintain a library that is written in Golang. It&rsquo;s a pretty standard library, and has a pretty simple API associated with it, maybe 7-8 functions that consumers can call. Nothing specialist. It was a library that had some small design issues as well as old code that was now dead. I&rsquo;d been planning to make a breaking change in the API for a few months, and waiting for an opportune time to do it. In total there were 3 distinct breaking changes I wanted to make, only one of which was really &lsquo;required&rsquo;, the others were stylistic/linter improvements that I wanted to make.</p>
<p>I made three separate pull requests, had them approved by others who maintain said library, and made three separate releases on the same day, bumping the &lsquo;major&rsquo; version number three times on the same day. I respect that these could have all been rolled into one major version bump, but I personally did not see the harm in incrementing the major version. This looked a little ridiculous, but my thinking was that this would allow users to upgrade incrementally, rather than having to deal with 3 conceptually different breaking changes in one go.</p>
<figure><img src="/posts/breaking-go/intro.png"
         alt="A screenshot of the Github Releases page for &#39;pusher-http-go&#39;"/><figcaption>
            <p>The releases page on Github looks a bit ridiculous.</p>
        </figcaption>
</figure>

<p>I thought this was enough to indicate that changes had taken place. I tried to describe exactly the changes that were made - and give clear instructions as to what to do to migrate up to that version.</p>
<p>What I had of course forgotten about is that Go ideally does not expect you to <em>ever</em> make breaking changes. A few of the libraries consumers rightly complained about the breaking changes, which is what inspired this article.</p>
<blockquote>
<p>&ldquo;You changed AppId to AppID and also UserId to UserID and it took me hours to figure out why my deployments weren&rsquo;t working. Come on guys lets not introduce breaking changes like this without some sort of heads up!&rdquo;</p>
</blockquote>
<h2 id="go-get---somewhat-flawed">
  <a class="Heading-link u-clickable" href="/making-breaking-changes-in-go/#go-get---somewhat-flawed">Go get - somewhat flawed</a>
</h2>
<p>In the very early stages of development, when a consumer of a library wishes to consume it, they&rsquo;ll usually use <code>go get</code>, a tool which requires basically no thought to use. It will fetch whatever is on master at that point. Not what you&rsquo;ve released, and not whatever is on a release branch. go get is pretty much <code>git clone</code>, with Go tendencies.</p>
<p>The consumer will then eventually run <code>go build</code> or <code>go run</code> and their software will build and run as normal, even if the library author makes a breaking change. The Built binary is, well, built, so it will be fine. The developers build environment will be fine since it effectively vendors the library code - and won&rsquo;t update it unless asked. If the user dares do a <code>go get -u</code> on the package, or recreates their build environment locally, they&rsquo;ll receive the breaking changes, and therefore will have to deal with them.</p>
<p>The trouble starts when that consumer then either tries to build their code on another machine. The consumer <code>go get</code>s the packages they need again, except now there has been a slew of commits merged into libraries master branch, therefore inexplicably breaking their build.</p>
<p>The user then spends a few minutes trying to figure out why what works on their machine does not work in their CI environment, or on their AWS instance, or in their Docker container. Eventually, they find log lines that indicate a variable has been renamed, or a functions return type has changed, or the function they were using is gone altogether!</p>
<p>Frustrated, they either stop using your library, or they contact you and ask why you made such a change.</p>
<p>This whole process was designed by Google, for Google. It was designed for how they manage their repositories. Of course, other organizations manage their source code differently, and conflicts were bound to happen. This lack of package management meant that breaking changes rendered once fine builds completely broken. In other languages where package management has been a first class citizen for longer - this is not an issue, but in Go, it is. No amount of Semver is going to dig us out of this hole.</p>
<h2 id="the-fundamental-problem-with-go-package-management">
  <a class="Heading-link u-clickable" href="/making-breaking-changes-in-go/#the-fundamental-problem-with-go-package-management">The fundamental problem with Go Package Management</a>
</h2>
<p>I am not going to talk too much about the state of package management in Go. <a href="https://codeengineered.com/blog/2018/golang-godep-to-vgo/">Enough has</a> been <a href="https://hackernoon.com/the-state-of-go-dependency-management-6cc5f82a4bfa">said on</a> that <a href="https://peter.bourgon.org/blog/2018/07/27/a-response-about-dep-and-vgo.html">topic already</a>. What I will say is none of them really address this problem. Not really. It doesn&rsquo;t matter how good your package manager is. It makes no difference whether it vendors your dependencies. It is not the default, and therefore breaking changes are going to break someones code.</p>
<p>Since anybody writing go today isn&rsquo;t likely to want to think about the intricacies of dependency management immediately, and it is certain that there are projects everywhere that do not use a package manager, it is crucial to consider the various factors that weigh into whether a breaking change is worth it or not.</p>
<p>The problem is that you are going to have consumers who are going to consume your code and expect there to never be a breaking change. This is the unchangeable reality of Go.</p>
<p>Are breaking changes possible then? This is a question that only the owners of the library can answer. How many consumers do you have? Does the benefit of making the breaking change outweigh the almost certain annoyance your consumers will face? Are there ways of making the change without breaking the build? Do you have a mechanism to warn users that breaking changes are coming? The factors which weigh in are numerous and each should be considered.</p>
<h2 id="so-how-do-i-make-breaking-changes">
  <a class="Heading-link u-clickable" href="/making-breaking-changes-in-go/#so-how-do-i-make-breaking-changes">So, How DO I make breaking changes</a>
</h2>
<p>There are many approaches to making breaking changes in Go. This is part of the problem. Without unifying our approach as library maintainers, we&rsquo;ll struggle. I have no idea what the correct approach should be, and there are more than are listed below, but I hope this brief survey will be useful when you make your decision as to how to do it.</p>
<h3 id="just-never-making-breaking-changes-ever">
  <a class="Heading-link u-clickable" href="/making-breaking-changes-in-go/#just-never-making-breaking-changes-ever">Just never making breaking changes ever</a>
</h3>
<p>This is the only real solution. If not annoying users is your absolute top priority, then never make breaking changes! Design your API upfront to be as close to perfect as possible, and only ever add code that does not change the API.</p>
<p>This is the approach that many crucial components of software take. Only ever add functionality, never take it away or change existing functionality. Never break the build. This approach ensures you&rsquo;ll annoy the fewest people, but of course has the disadvantage that you&rsquo;re unable to fix any mistakes in your original spec.</p>
<h3 id="branch-off-for-breaking-changes">
  <a class="Heading-link u-clickable" href="/making-breaking-changes-in-go/#branch-off-for-breaking-changes">Branch off for breaking changes</a>
</h3>
<p>Another approach that some libraries take is to maintain version branches. For example, see <a href="https://github.com/robfig/cron">github.com/robfig/cron</a>. Here we can see there is a master branch which I assume v0, and then branches for each breaking change. I expect that the idea is that a user who wants to ensure their code will build forever will move to the directory where Go checked out the code, and then move to the branch that their code is written to support.</p>
<p>This ensures that (as long as the library author keeps their promises!) users who remember to check out the correct branch can expect their builds to continue working and their code to continue working.</p>
<p>This has the same downside that users do not have an obvious way to know how to move up major versions, and puts the onus completely on them to determine if there has been a major version update. If they decide to update major versions on one build machine, they must then follow suit everywhere else. This isn&rsquo;t great.</p>
<h3 id="somehow-notifying-consumers-of-breaking-changes">
  <a class="Heading-link u-clickable" href="/making-breaking-changes-in-go/#somehow-notifying-consumers-of-breaking-changes">Somehow notifying consumers of breaking changes</a>
</h3>
<p>I&rsquo;ve heard suggestions that maybe library authors should notify consumers about upcoming breaking changes. One approach is to release a version of your library where you log out that a given thing is changing on a certain date, and on that date release the next version which actually removes or modifies said functionality. This approach relies on the user consulting their logs frequently for these notices - something I&rsquo;m not particularly hopeful happens. I expect most developers including myself only ever really consult logs when things go wrong, and here they go wrong when the thing is changed, meaning the log lines helped nobody. Semver seems to advocate for this approach, suggesting that we &ldquo;issue a new minor release with the deprecation in place&rdquo;. What form this deprecation takes however is left undefined.</p>
<p>Another suggestion I heard from other developers was to somehow contact the consumers of the library to inform them that the breaking change is coming. For instance, users who care about breaking changes could sign up to a mailing list. Maybe you have a method of identifying and contacting consumers already. Then, when you anticipate a breaking change, you contact the customer via email or something to let them know. This approach has a similar problem to the first, which is that I expect people signing up to said mailing list would only do so as a reaction, rather than proactively. Contacting your consumers with emails you&rsquo;ve already got somewhere else somewhat solves this, but will users really read this email? Will they action it? Will they even realize that the change affects them?</p>
<h3 id="declaration-that-breaking-changes-are-to-be-expected">
  <a class="Heading-link u-clickable" href="/making-breaking-changes-in-go/#declaration-that-breaking-changes-are-to-be-expected">Declaration that breaking changes are to be expected</a>
</h3>
<p>Given that almost every library that is not already well defined is likely to want to change its API at some point, I think the most realistic approach we&rsquo;ve got is to explicitly declare that breaking changes are to be expected in the README. Something along the lines of:</p>
<blockquote>
<p>This library, and any others that you are using, may make breaking changes at any time. These changes could unexpectedly break your build. To avoid this, we recommend you use a package manager such as the inbuilt Go Modules, a tutorial for which can be <a href="https://github.com/golang/go/wiki/Modules#quick-start">found here</a>.</p>
</blockquote>
<p>This is clearly not perfect. This requires your consumers to take action and do something in order to prevent this problem in the future - however I think it is fairly reasonable. If we look at other examples of how this problem is handled in other languages, we&rsquo;d see that libraries sometimes even include a snippet to copy into the Gradle file or the language itself will write a package lock file that locks to whatever the latest version was at the time of installation.</p>
<p>This does however point Go consumers in the correct direction for safely using their libraries, and even better, thanks to the way Go modules works, even solves the problem automatically for other libraries that are installed! This doesn&rsquo;t solve the problem at all for consumers that will not or cannot use a package manager, so this, in use with a commitment to make breaking changes as infrequently as possible seems like a solution that will annoy the fewest people over time.</p>
<p>In my humble opinion, use of a package manager should become the norm. Bare <code>go get</code> should be discouraged for everything except experimentation. I have been bitten by others making API breaking changes and I personally have inflicted that pain on others. It&rsquo;s an easy thing to miss - and there are many situations where using a package manager isn&rsquo;t ideal - but in a world where breaking changes are going to be made, pretending that they&rsquo;re not going to be is not a solution.</p>
<p>Go mod has its own <a href="https://github.com/golang/go/issues/27643">idiosyncrasies</a>, but it&rsquo;s as close to a built in package manager Go has gotten so far. I think it&rsquo;s worth it in the long run for libraries to encourage its use and to make it the new normal for developers just starting out in Go. The thousands of unbuildable projects floating around that have depended on libraries that broke backwards compatibility will look on and smile.</p>
]]></description>
    </item>
    
    <item>
      <title>Exploiting UPnP, Literally Childsplay.</title>
      <link>http://kn100.me/exploiting-upnp-literally-childsplay/</link>
      <pubDate>Fri, 05 Jul 2019 14:20:41 +0100</pubDate>
      
      <guid>http://kn100.me/exploiting-upnp-literally-childsplay/</guid>
      <description><![CDATA[<p>As a kid, I used to love playing Minecraft. I was technically precocious from a fairly young age, and naturally gravitated to attempting to host my own Minecraft server for me and my band of geeky pals to play on. The problem was, I had no idea what port forwarding was, nor how to log into a router. The solution I found was about as novel as it was dumb.</p>
<p>I tried a few different things initially. I firstly tried hosting a Minecraft server on a few different &lsquo;free&rsquo; VPS suppliers. Immediately, the lack of RAM was an issue, creating unplayable lag spikes that took the fun out of it. It took me a while to realise that there was no free lunch, and nobody was going to host my ridiculously resource hungry Java based server in a way that would be beneficial to me. Gigabytes of RAM were expensive.</p>
<p>Next, I tried hosting it locally, but kept hitting up against a concept my young brain knew nothing about - which was port forwarding. I knew it required access to the Router, but that was guarded by my father, who was understandably and correctly worried about letting his son mess about in settings that he himself also did not really understand.</p>
<figure><img src="/posts/exploiting-upnp/intro.jpg"
         alt="A photo of a white Netgear DG834GT router"/><figcaption>
            <p>Netgear DG834GT - The router provisioned to my family</p>
        </figcaption>
</figure>

<p>Furious, rushed Googling led me to Hamachi. Hamachi, now owned by LogMeIn is a hosted VPN service. Effectively it puts you and your friends who want to play on a VPN together. This worked a little better than my previous attempts, but still introduced a lot of latency, and as such wasn&rsquo;t ideal. I stuck with this for a little while, but there had to be a better way.</p>
<p>The only other time I&rsquo;d ever come across port forwarding was in my use of Bittorrent. Supposedly, without an open port, Bittorrent couldn&rsquo;t function correctly. How did it function correctly then, I wondered? My client of choice (Transmission) would dutifully tell me whichever port I selected was &lsquo;open&rsquo;. Were all the ports open? Was my Minecraft server lying to me? After all, Transmission seemed to work perfectly downloading and even seeding things. I regularly used Bittorrent for distributing files between my few PCs and friends at the time, and it seemed to work perfectly.</p>
<figure><img src="/posts/exploiting-upnp/upnp-transmission.png"
         alt="A screenshot showing the Transmission Bittorrent &#39;test open port&#39; function"/><figcaption>
            <p>No matter the Listening Port number - Test Port reported open!</p>
        </figcaption>
</figure>

<p>It didn&rsquo;t seem to matter which port I selected, when I pressed that &ldquo;Test Port&rdquo; button, somehow it was open! This wasn&rsquo;t fair! How come the router stazi allowed Transmission to talk to whoever it wanted to, whenever it wished, yet my poor Minecraft server received no such special treatment? Was Big Torrent bribing Netgear? What sort of nepotism was this?!</p>
<p>UPNP meant nothing to me, and I didn&rsquo;t think to Google it at the time, but it was the technology that allowed Transmission to talk to whoever it wanted.</p>
<p>One day, almost by accident, I found a solution, that was far better than Hamachi. It turned out that if I opened the Minecraft Server first, then entered the port that it was configured to use into the Port box within Transmission, and then hit Test Port, and left Transmission open while me and my friends would waste hours building things I&rsquo;d eventually <em>accidentally</em> blow up with TNT, it just seemed to work!</p>
<p>What I didn&rsquo;t realise back then was I was taking advantage of a security flaw within UPNP. Even though I was innocently hijacking the port Transmission opened for use with Minecraft, I was actually exploiting a fairly serious security vulnerability in the protocol. UPNP allows any old software to punch a hole through your routers firewall. It doesn&rsquo;t verify <strong>which</strong> software is making use of said port. Any old code running on your computer is able to do this, including most terrifyingly to me at the time I realised what I was doing the hugely popular <a href="https://www.gnucitizen.org/blog/flash-upnp-attack-faq/">Adobe Flash</a>.</p>
<h2 id="how-upnp-works">
  <a class="Heading-link u-clickable" href="/exploiting-upnp-literally-childsplay/#how-upnp-works">How UPnP works</a>
</h2>
<p>UPnP is a complicated standard whose overarching goal is to allow devices on a network to discover each others presence without require configuration from the user. It seems to want to abstract over various network configurations to reduce the mental burden of the user using a product which supports it. A noble goal. Far too much to talk about here! Instead I will focus on one part of UPnP, which is how it allows devices inside your network to punch holes through your routers firewall (port forward).</p>
<p>UPnP makes use of Internet Gateway Device Protocol, which is a protocol for mapping ports where NAT (Network Address Translation) is in use. Effectively it allows devices on a network to communicate their requests for a router to route data hitting a particular port to a the system making the request. IGDP also allows applications to learn the external IP address without having to use an external system, and even see which port mappings already exist. Crazily, there is even provision for the standard to allow applications on your network to request the router get a new public IP address, however I&rsquo;ve personally never seen this in use.</p>
<p>When an application, for example Transmission, wants to map a port for its use, it makes a request to what the RFC for this refers to as the IGD Control point (in most cases the router).</p>
<p>After this, and if authentication is either not enabled or passes (it is not enabled in most cases!) - the IGD sends a &ldquo;AddAnyPortMapping()&rdquo; request through to the Internet Gateway Device - Port Control Protocol Interworking Function, or the IGD-PCP IWF for short. Oh aren&rsquo;t RFCs fun! The IGD-PCP IWF then makes a Port Control Protocol MAP request to the PCP server.</p>
<p>The PCP Server can then do one of three broad categories of things:</p>
<ul>
<li>It can refuse. It may refuse because the port has already been opened for someone else. It may refuse for other reasons too.</li>
<li>It can accept, and return a PCP MAP Response with the assigned external port the UPnP Control Point requested</li>
<li>It can accept, and return a PCP MAP Response with another external port that the UPnP Control Point did not request but it can use if it wishes.</li>
</ul>
<p>The application that triggered this whole flow may then test that the port mapping worked - usually by using some external api to attempt to GET something running on that port on the machine running the software. Voila, the port is open. The software can then request that the mapping it made be deleted. So can anyone else who can make requests to the IGD. The Software then effectively has a lease over this port, and the port will remain open until its lease expires, or the software deletes it manually. The maximum length of a lease is configurable by the IGD, but the max defined in the spec is 4294967296 seconds - a ridiculously high max.</p>
<p>The only other case where the lease will be terminated is when the IP that generated it is no longer around. If the machine shuts down or its DHCP lease over its own IP expires, the spec assumes the port mappings associated with it are lost too. <a href="http://www.upnp-hacks.org/annoyances.html">This is not always the case though.</a></p>
<p>The RFC states that &ldquo;we assume that the IGD applies the appropriate security policies to determine whether a Control Point has the rights to delete one or a set of mappings&rdquo;. This is not an unreasonable assumption; after all, routers come in all shapes and sizes, and the authentication mechanisms they rely on come in various shapes and sizes too. It has become obvious with time though that due to the nature of the consumer hardware industry, security is an afterthought, if a thought at all. Indeed, a rather hilarious but sad security exploit <a href="https://blogs.akamai.com/sitr/2018/11/upnproxy-eternalsilence.html">reported by Akamai</a> found that around 3.5 million routers scanned responded to UPnP probe packets. This means that external parties can punch holes through the firewall by sending UPnP requests to it. Even worse, around 277,000 of these routers actually don&rsquo;t verify that the software making the request is port forwarding to itself! In fact, the RFC mandates this. If routers were to ensure that port mappings being requested by a source were to point at that source, they&rsquo;d not be in compliant with RFC 6970!</p>
<p>This means that previously inaccessible hosts within a network can now be reached by attackers spamming the routers with UPnP port forward requests - effectively opening the machines behind the firewall to the open internet. Terrifying stuff.</p>
<p>It gets worse though. Some of these routers routers don&rsquo;t do this origin check, and in addition they don&rsquo;t even check that the port mapping points to a <strong>machine that exists on the local network.</strong> Essentially this means that any one of these routers can be hacked to become a sort of proxy. How? By asking it to, of course.</p>
<p>This is why I now disable UPNP everywhere I have the power to. It&rsquo;s one of those convenient yet terrifying technologies I am not sure I can trust. Sometimes free convenience is far more expensive than anybody realizes. <a href="https://arstechnica.com/information-technology/2018/11/mass-router-hack-exposes-millions-of-devices-to-potent-nsa-exploit/">Just ask the NSA.</a></p>
]]></description>
    </item>
    
    <item>
      <title>Those Damn Romanians and Their Damn Data Protection Laws.</title>
      <link>http://kn100.me/those-damn-romanians/</link>
      <pubDate>Wed, 24 Apr 2019 18:00:00 +0000</pubDate>
      
      <guid>http://kn100.me/those-damn-romanians/</guid>
      <description><![CDATA[<p>A long time ago I worked for an ISP as Tier 1 technical support. This is just one of many stories I have from my time here. These were originally published elsewhere on the internet, but are being reworked and republished here.</p>
<p>Sometimes, the degree of insanity in a conversation can reach such a fever pitch that it is hard to recover. Sometimes, people can say things that leave you so flabbergasted that you aren&rsquo;t sure if you&rsquo;re being trolled or not. This was one of those times.</p>
<p>It was another boring grind at AverageISP Tier 1 Technical support. I was getting very bored and was browsing Reddit as usual. Finally, my phone rings towards the end of the night.</p>
<p>I&rsquo;ll just transcribe the call.</p>
<blockquote>
<p>Me: &lsquo;Good evening, you&rsquo;re through to the AverageISP technical support team, my name is Kevin, how can I help?&rsquo;</p>
<p>Caller: <em>&lsquo;Uhhh yes my uhhhh phone isn&rsquo;t working&rsquo;</em></p>
<p>Me: &lsquo;Okay no problem sir, can I start by taking your telephone number?&rsquo;</p>
<p>Caller: <em>&lsquo;uhhh&hellip;er&hellip;&hellip;uhhh&hellip;&hellip;&hellip;.ummmmm&hellip;.0161..I think&hellip;.6549864&rsquo;</em></p>
</blockquote>
<p>This brought up an account on our systems, so far so good</p>
<blockquote>
<p>Me: &lsquo;Okay thank you for that information and could you please provide me with your full name?&rsquo;</p>
<p>Caller: <em>&lsquo;Uhh&hellip;.My name is uhh&hellip;&hellip;..Sarah..no wait&hellip;David Dodgyman&rsquo;</em></p>
</blockquote>
<p>Slightly suspicious me thinks&hellip; Let&rsquo;s check.</p>
<blockquote>
<p>Me: &lsquo;Can you provide me with your full address including postcode&rsquo;</p>
<p>Caller: <em>&lsquo;uhh my address..give me a few minutes&hellip;&hellip;&hellip;&hellip;&hellip;..&rsquo;</em></p>
<p>Caller: <em>&lt;gives address but seems unsure and doesn&rsquo;t know postcode&gt;</em></p>
<p>Me: &lsquo;Okay thank you sir and can I take your date of birth?&rsquo;</p>
<p>Caller: <em>&lsquo;I don&rsquo;t know my date of birth&rsquo;</em></p>
</blockquote>
<p>Alright enough is enough, company policy being what it is, time to terminate the call.</p>
<blockquote>
<p>Me: &lsquo;Then unfortunately sir I am unable to speak to you regarding this account but I will run a line test for you since you have reported a fault and I will book it to be fixed if it fails&rsquo;</p>
<p>Caller: <em>&lsquo;THATS ABSOLUTELY F****** RIDICULOUS&rsquo;</em></p>
<p>Me: &lsquo;all I can do is apologize sir..&rsquo;</p>
<p>Caller: <em>&lsquo;F****** HELL THESE F****** ROMANIANS COMING FROM ROMANIA TAKING OUR BRITISH JOBS&rsquo;</em></p>
</blockquote>
<p>&hellip;Wat? My brain pretty much just BSOD&rsquo;d at this point</p>
<blockquote>
<p>Me: &lsquo;Sir&hellip;I&rsquo;m Welsh&rsquo;</p>
<p>Caller: <em>&lsquo;WHAT&rsquo;S BEING WELSH GOT TO DO WITH ANYTHING?&rsquo;</em></p>
<p>Me: &lsquo;What&rsquo;s being Romanian got to do with anything sir?&rsquo;</p>
<p>Caller: <em>&lsquo;ARE YOU ACCUSING ME OF BEING A RACIST?&rsquo;</em></p>
<p>Me: &lsquo;No sir I am simply trying to establish how Romania is relevant to this discussion&rsquo;</p>
<p>Caller: <em>&lsquo;WELL I BET THE DATA PROTECTION LAW WAS PUT IN PLACE TO PROTECT PEOPLE FROM THOSE F****** ROMANIANS&rsquo;</em></p>
<p>Me: &lsquo;Sir the data protection act was implemented around the turn of the century to protect your information-&rsquo;</p>
</blockquote>
<p>And was mainly written by the British, but that&rsquo;s a different fight altogether kermittea.jpg.</p>
<blockquote>
<p>Caller: <em>&lsquo;THATS F****** RIDICULOUS - I WANT TO SPEAK TO YOUR MANAGER&rsquo;</em>
Me: &lsquo;Sir because you are not the account holder nor a customer I will not honor that request and I am now terminating this call, thanks for calling-&rsquo;</p>
<p>Caller: <em>&lsquo;F*** YO..&rsquo;</em></p>
<p>Automated survey: <em>Thanks for calling AverageISP today, we&rsquo;d like to ask you a few questions about your experience today&hellip;</em></p>
</blockquote>
]]></description>
    </item>
    
    <item>
      <title>Beating Round-Trip Latency With Redis Pipelining</title>
      <link>http://kn100.me/redis-pipelining/</link>
      <pubDate>Tue, 16 Apr 2019 19:59:23 +0100</pubDate>
      
      <guid>http://kn100.me/redis-pipelining/</guid>
      <description><![CDATA[<p>I recently worked on a task which involved checking every single value in a Redis instance, and modifying ones which matched a certain format. This script needed to run in a fairly timely fashion, as it would need to be run frequently. Making requests sequentially didn&rsquo;t cut it, so I learned about pipelining. This simple optimization meant that a script which I originally calculated to take over 50 hours to run ran in under 4 minutes!</p>
<p>The two approaches for doing this in a timely fashion I considered are as follows:</p>
<ol>
<li>Write some script that makes use of pipelining in order to stream requests to the Redis instance without waiting for the response</li>
<li>Writing some Lua script which redis can execute itself, an amazing ability that I should learn to take advantage of more often! This approach would likely have been much more efficient, but there were time constraints on this and my knowledge of Lua is nada, so I decided to go with the script approach.</li>
</ol>
<h2 id="life-without-pipelining">
  <a class="Heading-link u-clickable" href="/redis-pipelining/#life-without-pipelining">Life without pipelining</a>
</h2>
<p>Without pipelining, when making many requests to a Redis, we are at the mercy of the latency between ourselves and the server in question, or are forced to do threading of some sort. Let&rsquo;s look at some code. In this code, we want to get the ttl, or time to live for each key. If you&rsquo;re unsure what a <code>TTL</code> is, the <a href="https://redis.io/commands/ttl">Redis docs</a> do a good job of explaining it, but the long and short of it is that you can set an expiry on a key that Redis stores. The Redis <code>TTL</code> is the number of seconds before the key is deleted.</p>

<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ruby" data-lang="ruby"><span style="display:flex;"><span>keys <span style="color:#f92672">=</span> <span style="color:#f92672">[</span><span style="color:#e6db74">&#39;apple&#39;</span>, <span style="color:#e6db74">&#39;orange&#39;</span>, <span style="color:#e6db74">&#39;banana&#39;</span>, <span style="color:#e6db74">&#39;pineapple&#39;</span><span style="color:#f92672">]</span>
</span></span><span style="display:flex;"><span>values <span style="color:#f92672">=</span> <span style="color:#f92672">[]</span>
</span></span><span style="display:flex;"><span>keys<span style="color:#f92672">.</span>each { <span style="color:#f92672">|</span>key<span style="color:#f92672">|</span>
</span></span><span style="display:flex;"><span>    values <span style="color:#f92672">&lt;&lt;</span> redis<span style="color:#f92672">.</span>ttl(key)
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>In this code, we are getting the <code>TTL</code> for 4 keys. Every time we call <code>redis.ttl()</code>, our code blocks. This is because we need to wait for Redis to respond to our request for the ttl. This is usually fine for small numbers of keys, but when you&rsquo;ve got millions, and the Redis you&rsquo;re talking to is not on your local machine, every single key takes <em>at least</em> the network latency between you and the Redis to retrieve.</p>
<p>In the above example, we are retrieving 4 keys <code>TTLS</code>. Lets assume that I, in London am talking to a Redis in the US somewhere. Let&rsquo;s assume a minimum network latency of 100ms. This means, to retrieve these 4 keys, it will take <em>at least</em> 400ms. 40 keys will take at least 4000ms, and so on. You can see how this doesn&rsquo;t scale.</p>
<h2 id="enter-pipelining">
  <a class="Heading-link u-clickable" href="/redis-pipelining/#enter-pipelining">Enter pipelining</a>
</h2>
<p>Lets look at the code changes required to pipeline our requests.</p>

<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ruby" data-lang="ruby"><span style="display:flex;"><span>keys <span style="color:#f92672">=</span> <span style="color:#f92672">[</span><span style="color:#e6db74">&#39;apple&#39;</span>, <span style="color:#e6db74">&#39;orange&#39;</span>, <span style="color:#e6db74">&#39;banana&#39;</span>, <span style="color:#e6db74">&#39;pineapple&#39;</span><span style="color:#f92672">]</span>
</span></span><span style="display:flex;"><span>values <span style="color:#f92672">=</span> <span style="color:#f92672">[]</span>
</span></span><span style="display:flex;"><span>redis<span style="color:#f92672">.</span>pipelined <span style="color:#66d9ef">do</span>
</span></span><span style="display:flex;"><span>  keys<span style="color:#f92672">.</span>each{ <span style="color:#f92672">|</span>key<span style="color:#f92672">|</span>
</span></span><span style="display:flex;"><span>    values <span style="color:#f92672">&lt;&lt;</span> redis<span style="color:#f92672">.</span>ttl(key)
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">end</span></span></span></code></pre></div>
<p>That&rsquo;s simpler than expected right? What is actually different here? Instead of sequentially doing the requests, we <strong>pipeline</strong> them. This means we send the requests without waiting on the responses. We send as many requests as our machine can, and the responses come in whenever they&rsquo;re ready from the server. This means if we take the same latency figures as above, we can assume the time to retrieve all these <code>TTLS</code> is going to be around 100ms! for 40 keys, the latency will probably be a bit higher, but closer to 100ms than 400ms for sure! 400 keys? Much closer to 100ms than 4000ms!</p>
<p>Here&rsquo;s a diagram I stole from Wikipedia that does a great job of illustrating the difference this makes to how the requests are made.</p>
<figure><img src="/posts/redis-pipelining/intro.png"
         alt="A diagram that illustrates what is described below"/>
</figure>

<p>In this library, the <code>redis.pipelined</code> block will block until all the responses come back, and the <code>values</code> array will be populated with Redis::futures. You can wait on these in a separate thread and do work as soon as results come in, but if you don&rsquo;t care about that and just want to start your work once you&rsquo;ve gotten all your responses, you can just let the <code>redis.pipelined</code> block continue to block until all the responses have come back, and then call <code>value()</code> on each element in the values array. For example:</p>

<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ruby" data-lang="ruby"><span style="display:flex;"><span>puts keys<span style="color:#f92672">[</span><span style="color:#ae81ff">0</span><span style="color:#f92672">].</span>value <span style="color:#75715e"># returns the ttl, ie 3600</span></span></span></code></pre></div>
<h2 id="how-does-this-work-though">
  <a class="Heading-link u-clickable" href="/redis-pipelining/#how-does-this-work-though">How does this work, though?</a>
</h2>
<p>At a fairly high level, what happens is the server buffers these requests we&rsquo;re sending to it in the order that we sent them. This is why we can depend on the order they&rsquo;re returned in. It processes the requests as it normally does and returns them to our client as normal. There are some important caveats which might matter to your use case though:</p>
<ul>
<li>Pipelining does not make your requests atomic. If you&rsquo;re making millions of requests for the <code>TTLS</code> of all your keys, then we can assume it&rsquo;s going to take longer than a few seconds. This means that if keys you request expire in the time it takes for Redis to process all these requests, you&rsquo;re not going to be able to retrieve them again. If you need atomicity, you&rsquo;re going to need to do a <a href="https://redis.io/topics/transactions">transaction</a>.</li>
<li>The buffering of requests on the server takes up memory. If your requests each take a non-trivial amount of time to process by Redis, this buffer can grow very large quickly. If you&rsquo;re making millions of queries, you might want to consider batching your requests. For example, you might pipeline 10000 calls to the <code>TTL</code> method, and once that lot succeeds pipeline another 10000 calls. This means your buffer will never exceed 10000 requests, and you&rsquo;ve ensured you&rsquo;re not going to OOM your Redis. The size of your batches will decide how many times you&rsquo;ll need to pay the RTT penalty.</li>
<li>Redis has lots of lovely methods which somewhat negate the need for pipelining. For example, if you&rsquo;re just getting the values of keys, consider using <code>mget</code> instead of pipelining.</li>
</ul>
<h2 id="conclusion">
  <a class="Heading-link u-clickable" href="/redis-pipelining/#conclusion">Conclusion</a>
</h2>
<p>Redis pipelining allows us to make many requests without waiting for the responses or worrying about the order of the responses. It offers huge performance benefits if your service that talks to Redis is far away from the Redis instance. Regarding my use of it, even with this optimization, the majority of the time my script executed was spent on the machine running the script making the modifications to the keys. I am certain more performance could be gotten, but this was more than fast enough for my purposes - and was a very significant improvement.</p>
]]></description>
    </item>
    
    <item>
      <title>That Time a Sticker Made Me an Accidental Domain Squatter</title>
      <link>http://kn100.me/the-accidental-domain-squatter/</link>
      <pubDate>Wed, 10 Apr 2019 22:56:28 +0100</pubDate>
      
      <guid>http://kn100.me/the-accidental-domain-squatter/</guid>
      <description><![CDATA[<p>It all started when I noticed these particular stickers absolutely everywhere all over London. I&rsquo;d also seen them in other European cities! I was never curious enough to actually search for them, to figure out their meaning, so I just noticed, acknowledged my curiosity and moved on. I later did discover their meaning, but the way I discovered this was something I never expected.</p>
<figure><img src="/posts/whysuspectus/intro.png"
         alt="A sticker with a silhouette of a rabbit and the caption &#39;why suspect us&#39;"/><figcaption>
            <p>The &lsquo;why suspect us.&rsquo; sticker in question</p>
        </figcaption>
</figure>

<p>Every now and then I&rsquo;d come across one and the question would briefly pop back into my head, but it&rsquo;s surprising how high the barrier to giving in to curiosity can be. My curiosity came back more intensely after my wife pointed one out and said that she&rsquo;d pondered the question herself for a long time. She&rsquo;d also seen them around, but once again hadn&rsquo;t been curious enough to search for it.</p>
<p>Some furious searching resulted in my discovery of a Twitter account along with some rather confused discussions online regarding the sticker. I also noticed the domain whysuspectus.com being free to register.</p>
<p>I registered the domain, thinking that it was a shame for these stickers to be everywhere with a related domain being free to register. I rarely buy frivolous domains, but this one just stood out to me for some reason. I bought it on a whim. I didn&rsquo;t really do anything interesting with it. I set up something cute for my wife and also made it point to my CV, because I wasn&rsquo;t sure what else to do with it.</p>
<p>I forgot about it for a few months. In the back of my head, I wondered if the original owner of the domain would notice, or care, or whether they&rsquo;d totally forgotten about these stickers altogether.</p>
<p>A few weeks ago, I got an email from someone who I will refer to as Steve (all details scrubbed at the request of the artist in question).</p>
<blockquote>
<p>Hey Kevin,</p>
<p>My name is Steve, I’m a NaN year old artist from Country.</p>
<p>In 2011 I designed the Why Suspect Us logo</p>
<p>In January 2017 I created a website at <a href="https://www.whysuspectus.com">www.whysuspectus.com</a> to sell t-shirts and art for my brand using my why suspect us logo.</p>
<p>The shirts didn’t sell.</p>
<p>I dropped out of university, worked really, really hard, full time as a barista for a year and a half so that I could travel around Europe working hard, putting up stickers.</p>
<p>While traveling, the credit card attached to the domain name expired and I see that you’ve since registered <a href="https://www.whysuspectus.com">www.whysuspectus.com</a></p>
<p>I’m a struggling young artist in 2019 and its painful, gut wrenching and heartbreaking to see someone else take control of the domain name.</p>
<p>I am appealing to you to pass me back the domain I have spent several years curating - I am happy to compensate you for costs of transferring and hope you can appreciate my anguish at the current situation.</p>
</blockquote>
<p>When I saw this email, I was pretty surprised. I had assumed whoever had placed all the stickers had just forgotten all about it. I also assumed that even if they&rsquo;d noticed, they&rsquo;d do nothing but chuckle about the silly use of their old domain.</p>
<p>After laughing for a bit, I replied:</p>
<blockquote>
<p>Hi Steve,</p>
<p>I had a feeling you might contact me! I&rsquo;m Kevin. It&rsquo;s nice to hear from you.</p>
<p>I bought this domain after I wondered what the hell these stickers were everywhere. It seemed a shame to let a domain with stickers all over Europe go to some domain squatter, so I purchased it and set up some silly stuff with it.</p>
<p>I&rsquo;ll make you a deal! I will transfer the domain to you free of charge, in return for a few of those stickers. I&rsquo;d also love an explanation as to what &ldquo;why suspect us&rdquo; means - if it has a meaning! The domain is good for at least the next 10 months. To do this, You&rsquo;ll need to register a Namecheap account and provide me the email you used.</p>
<p>You should consider yourself very lucky! A domain squatter would have asked for hundreds if not thousands. I highly recommend renewing your domains if you intend to keep them - even if you&rsquo;re not using them. I&rsquo;ve lost domains to squatters and it sucks.</p>
</blockquote>
<p>Almost immediately after I get a reply:</p>
<blockquote>
<p>Hi Kevin,</p>
<p>Thank you so much! It really means alot to me. Very lucky you&rsquo;re a good guy</p>
<p>I have made an account with namecheap using this email address: <a href="mailto:steve@some-email.com">steve@some-email.com</a></p>
<p>And yes! Where would you like the stickers sent!</p>
<p>I also have a few custom WSU T-shirts, what size can i send you?</p>
<p>Thank you Kevin I really appreciate it
Steve
Stickers on their way!</p>
<figure><img src="/posts/whysuspectus/whysuspectus-stickers.jpg"
         alt="A photo of a set of identical stickers, all with a silhouette of a rabbit and the text &#39;why suspect us&#39;"/>
</figure>

</blockquote>
<p>Past this point I verified the persons ownership of the domain via a method I won&rsquo;t talk about here and transferred Steve the domain.</p>
<p>A few weeks later, I received my shirt, as promised.</p>
<figure><img src="/posts/whysuspectus/whysuspectus-tshirt.jpg"
         alt="Kevin, wearing a white tshirt with the why suspect us branding"/>
</figure>

<p>In this story, the original owner of <a href="https://WhySuspectUs.com/">https://WhySuspectUs.com/</a> got incredibly lucky that a domain squatter didn&rsquo;t extort him. I have lost domains to squatters and it absolutely sucks. Domain squatting should be made illegal. In this case, the squatter happened to be a curious engineer who didn&rsquo;t want to let an opportunity pass by.</p>
<p>This makes me wonder whether domains are &rsquo;too&rsquo; easy to purchase. The fact that within 10 minutes I was able to squat a persons previous business could have been incredibly harmful. Imagine if I&rsquo;d used the domain for defamatory purposes!</p>
<p>This story has little point, but was an interesting interaction and answered the question I had all along. It&rsquo;s a T-shirt brand! The artist in question seems to have decided to stop selling them now though, so unfortunately you can&rsquo;t have one - however you can check his website, just check the sticker.</p>
]]></description>
    </item>
    
    <item>
      <title>Initiative Q - Get Rich Quick in 2018?!</title>
      <link>http://kn100.me/initiative-q-get-rich-quick-for-2018/</link>
      <pubDate>Thu, 25 Oct 2018 02:52:15 +0000</pubDate>
      
      <guid>http://kn100.me/initiative-q-get-rich-quick-for-2018/</guid>
      <description><![CDATA[<p>I personally don&rsquo;t pay a massive amount of attention to cryptocurrency. This isn&rsquo;t because I dislike them, more because they&rsquo;re just not that interesting to me at the moment. I&rsquo;ll occasionally hear about Bitcoin rallying or some Etherium app getting hacked, but other than that I avoid it.</p>
<p>Recently, I&rsquo;ve had a bunch of people who wouldn&rsquo;t normally be interested in such things ping me about Initiative Q, asking what I thought of it. When people who aren&rsquo;t in the tech circles I&rsquo;m in start asking me about something like this, alarm bells start ringing, given that usually tech people tend to know about these things before they become popular, so I investigated.</p>
<p>Firstly, and I can&rsquo;t shout this loud enough:</p>
<h2 id="q-is-not-a-cryptocurrency">
  <a class="Heading-link u-clickable" href="/initiative-q-get-rich-quick-for-2018/#q-is-not-a-cryptocurrency">Q is not a cryptocurrency.</a>
</h2>
<p>Q is a centralized coin. It is issued by a central authority, and right now isn&rsquo;t even traded. This makes it presently completely useless; not that being able to trade it would bestow it much more usefulness.</p>
<p>Right now, the website is promising to give users who sign up today over 100,000 dollars. Of course, they mean in <strong>future</strong> money, once Q has gotten up and running and is worth anything at all. How the hell they&rsquo;re estimating the future worth of a currently worthless token is anybodies guess.</p>
<h2 id="q-does-not-solve-the-problem-it-claims-to">
  <a class="Heading-link u-clickable" href="/initiative-q-get-rich-quick-for-2018/#q-does-not-solve-the-problem-it-claims-to">Q does not solve the problem it claims to</a>
</h2>
<p>Q claims that it has found a way to incentivize shop owners to install their payment infrastructure, but as far as I can tell, their strategy is to give a bunch of people worthless tokens, which will somehow encourage shop owners to pay to install hardware so that they can take these pointless coins. I somehow doubt it.</p>
<p>If the number of users was the barrier to a shops adoption of a new currency, then why haven&rsquo;t we seen shops en masse buying Bitcoin or Etherium payment terminals? It wouldn&rsquo;t be that hard really to build a payment terminal that can accept a large array of CryptoCoins - and they&rsquo;ve all got the benefit of low/no fees (lol low fees - 2022) and no central authority! Bitcoin in particular can do practically instant transactions (lol instant transactions - 2022) using the lightning network these days too!</p>
<h2 id="it-smells-a-lot-like-a-pyramid-scheme">
  <a class="Heading-link u-clickable" href="/initiative-q-get-rich-quick-for-2018/#it-smells-a-lot-like-a-pyramid-scheme">It smells a lot like a pyramid scheme</a>
</h2>
<p>Their marketing literally begs you to get others to sign up, and if you get others to sign up then you get more money! It even in a hilariously self aware fashion explains that if not enough people sign up, the project will fail. Unfortunately for the authors of this nonsense, &rsquo;enough&rsquo;, means &lsquo;infinite&rsquo;.</p>
<h2 id="get-rich-quick-schemes-still-work">
  <a class="Heading-link u-clickable" href="/initiative-q-get-rich-quick-for-2018/#get-rich-quick-schemes-still-work">Get rich quick schemes still work</a>
</h2>
<p>What is surprising in all this to me is this:</p>
<ul>
<li>Users are visiting the homepage of this website, and are seeing a dollar amount promised to them in the future, if they just give their email now!</li>
<li>After signing up, they&rsquo;re being told that they&rsquo;ll get 10% of that amount in worthless tokens, but if they want another 40% worthless tokens, they had better sign a few of their friends up!</li>
<li>Users then are spamming referral links all over the internet attempting to get others to sign up with their link.</li>
<li>Users don&rsquo;t see anything wrong with all this!</li>
</ul>
<p>Does this seem like a reputable scheme? A vicious threat to those fat cats at VISA and Mastercard? If only you&rsquo;d share your referral link one more time we could have defeated HSBC. Like my Facebook page to take down AMEX. Share this with 10 of your friends or Western Union will come into your house and mess up your pots and pans. I doubt they&rsquo;re losing any sleep over this nonsense.</p>
<p>How about a ponzi scheme to collect a bunch of emails for people who are interested in futuristic Fintech to potentially exploit later? Seems a bit more likely to me, but hey, what do I know, I missed out on my Bitcoin millions.</p>
]]></description>
    </item>
    
    <item>
      <title>News in the Morning: A Simple Hack</title>
      <link>http://kn100.me/news-in-the-morning-a-simple-hack/</link>
      <pubDate>Sun, 03 Jun 2018 19:43:56 +0100</pubDate>
      
      <guid>http://kn100.me/news-in-the-morning-a-simple-hack/</guid>
      <description><![CDATA[<p>Like many people, I find it difficult to wake up in the morning. I want to automate some of the things I do in the morning in order to continue to hack away at my laziness.</p>
<p>To this effect, I want my television to switch on and over to BBC news in the morning. Unfortunately, Hisense do not provide a way to control the television remotely as far as I can tell, and <code>nmapping</code> it doesn&rsquo;t give me anything too interesting. The only way I can think of to interact with the thing is via Infrared, so that&rsquo;s what I&rsquo;ll do.</p>
<p>So far, I&rsquo;ve acquired the following hardware:</p>
<ul>
<li>China-Arduino Nano</li>
<li>KY-022 Infrared IR Sensor Receiver Module</li>
<li>KY-005 IR LED Module Transmitter</li>
</ul>
<p>I hooked this all together in a sensible fashion.</p>
<figure><img src="/posts/newshack/intro.jpg"
         alt="A photo showing an Arduino Nano, and an IR receiver"/><figcaption>
            <h4>The circuit I built</h4>
        </figcaption>
</figure>

<p>Next up was to figure out how the heck IR transmission worked. It turned out to be a little more complicated than I had imagined. It turns out there&rsquo;s a bunch of different standards for data transmission (surprise surprise), from a bunch of different manufacturers. As usual, nothing is quite that simple and my television was not listed as a supported standard with the <a href="https://github.com/z3t0/Arduino-IRremote">library I was using</a> which is why I purchased the receiver.</p>
<p>Hopefully, I could &rsquo;learn&rsquo; codes from the remote control and rebroadcast them at will using the transmitting IR led.</p>
<p>A preliminary test using the <a href="https://github.com/z3t0/Arduino-IRremote/blob/master/examples/IRrecord/IRrecord.ino">IRecord example</a> showed this theory to mostly work! It turns out that this particular television (the Hisense H43N5300) makes use of a standard called RC6, developed by Philips.</p>
<p>The next steps for me will be to learn the button sequence required to turn the television on, flick to the news, and potentially slowly increase the volume until I turn it off somehow. After that will come the fun process of disassembling a cheap alarm clock and fitting it all inside there, ready to be triggered by my alarm.</p>
]]></description>
    </item>
    
    <item>
      <title>NotSoEducated – A Project From a While Ago</title>
      <link>http://kn100.me/notsoeducated-a-project-from-a-while-ago/</link>
      <pubDate>Sat, 02 Jun 2018 21:22:22 +0000</pubDate>
      
      <guid>http://kn100.me/notsoeducated-a-project-from-a-while-ago/</guid>
      <description><![CDATA[<p>A while ago, I and a friend of mine got rather annoyed at a ridiculous propaganda attempt called &ldquo;Not So Safe&rdquo;, claiming that Electronic Cigarettes were extremely dangerous, so we countered it by parodying the complete lies with a website in the same shape, which contained well cited research. We called it Not So Educated.</p>
<p>They&rsquo;ve long since taken the site down, I suspect in response, but you can still somewhat experience it thanks to the <a href="http://web.archive.org/web/20150819060251/http://notsosafe.org:80/">Wayback Machine</a>.</p>
<p>You can also experience NotSoEducated, thanks to the <a href="https://web.archive.org/web/20150801222634/http://notsoeducated.org/">Wayback Machine</a>.</p>
<figure><img src="/posts/notsoeducated/intro.png"
         alt="A screenshot of the NotSoEducated website, reading E-cigarettes are about as safe as other smoking cessation products approved by the FDA. Compared to cigarettes, they are much less toxic. Quote. Cadmium, lead and nickel have also been detected but in trace levels only, comparable with levels in Nicorette inhalers. Citation. A fresh look at tobacco harm reduction - US National Library of Medicine"/><figcaption>
            <h4>NotSoEducated.org</h4>
        </figcaption>
</figure>

]]></description>
    </item>
    
    <item>
      <title>Wear OS Is Upsetting, and I’m Gonna Rant About It</title>
      <link>http://kn100.me/wear-os-is-upsetting/</link>
      <pubDate>Thu, 05 Apr 2018 07:57:39 +0100</pubDate>
      
      <guid>http://kn100.me/wear-os-is-upsetting/</guid>
      <description><![CDATA[<p>I&rsquo;ve personally been maintaining a policy that generally dumber tech is better. I don&rsquo;t own any smart speakers, I don&rsquo;t own any smart lightbulbs because I&rsquo;m terrified of the prospect of having a web server in a light-socket, and I definitely don&rsquo;t own any smart cameras or the such for similar reasons. I&rsquo;m not opposed to such things existing, I just think they&rsquo;ve got a long way to go before I&rsquo;d trust them.</p>
<p>I did however somewhat break this trend with an impulsive purchase of the Huawei Watch 2, which at the time was one of the best (technically speaking) Wear OS smartwatches on the market. I thought &ldquo;Hey, it&rsquo;s a Google product, how bad can it be?!&rdquo;. As I&rsquo;d learn over the course of owning the damn thing, quite bad.</p>
<p>The first thing I noticed about the watch was just how poor the performance was. Considering the fact that the processor inside of it is roughly equivalent to a Nexus 4, the performance was appalling. Even just swiping through the various menus caused frame drops and stuttering. Still, I powered through, and attempted using the watch for what I thought it would be useful for; music.</p>
<p>One install of Google Play Music later, and I begin to wonder why I&rsquo;d bothered. The app takes around 20-30 seconds to launch fully (I swear I&rsquo;m not making this up!) and you&rsquo;re booted into a very spartan interface that can charitably be described as functional. Then, once I hit play on a particular song, another 10-15 second pause, and&hellip;what&rsquo;s this?! The music is coming out of my watch! I&rsquo;ve got a set of headphones connected to my phone, yet the watch decides the best thing to do is blast said music out of its own speaker! What use case does this serve? I&rsquo;ve never seen a single person listening to music literally through their watch, so why is this the default?!</p>
<p>I later learned the intention is to pair your Bluetooth headset up to the watch, so that it can play independent of the phone. My question is why would I want to do that? I&rsquo;ve got 3000+ MAh in whatever smartphone I&rsquo;m carrying, why not use some of that battery power instead?!</p>
<p>I later learned that in order to control the music on your phone, you first have to take the phone out, start playing music, and then you&rsquo;re presented with some incredibly basic back/forward/play/pause type controls in a notification on the watch. None of the nice navigation through playlists or stations. Wonderful.</p>
<p>Even more hilariously broken is the Google Assistant. One day, I was working out in a gym and I decided that the music coming out of my headphones could do with being a little louder. I hold the Google Assistant button on the watch, and around 5 seconds later it chugs into life. I ask it to turn the volume up. What does it do? Google &ldquo;turn the volume up&rdquo;. Another time, I&rsquo;m going to bed and I want to be woken up in the morning, so I hold the Assistant button, wait the customary 5 seconds for it to finish mining Bitcoin or whatever the watch is doing other than responding to my request for Assistance(tm). After the Assistant finally wakes up, I ask it to set an alarm for whatever-O&rsquo;clock I wanted it set for. Wonderful, it set an alarm! I felt assisted. I then take the watch off, stick it on its charger and go to sleep (a necessary process, seeing as it lasts roughly a day, maybe 2). Unsurprisingly, the assistant played a trick on me again, I slept through the alarm, which was coming through the watch, which was away on its charger, rather than the phone, which I kept beside my bed, fully expecting the alarm to come from there.</p>
<p>Another fun aspect of Wear OS is the app development ecosystem. It&rsquo;s Android! This sounds great if you&rsquo;ve ever messed about with Android development in the past, and in some ways this is nice, but frankly it feels incredibly bloated and wasteful considering the majority of the apps on the watch are garbage anyway.</p>
<p>The common theme with all of these problems is this insistence on the watch being independent from the phone, and I just don&rsquo;t understand it. Surely, it would be better to treat the watch as an alternative method of interacting with the phone, where it makes sense for this to be the case? Why, when I ask the Google Assistant a question, does it deal with the query itself? Why not forward it to the Google Assistant that is running on the phone itself? There&rsquo;s only so many times you can ask the assistant how tall the Eiffel Tower is. Why, when I have headphones plugged into my phone, does the watch assume the best audio output available for music is the watches speaker?</p>
<p>It&rsquo;s not all terrible though. The Citymapper app for Wear OS was a glimmer of hope. In the morning, my phone pops up telling me that it&rsquo;s calculated my route to work for that day. I tap the notification, and immediately my watch vibrates and starts displaying the route information. It sounds ridiculous, but that kind of interaction is extremely rare.</p>
<p>Comparing and contrasting the Citymapper app to the Google Maps app, it&rsquo;s almost comical. Remember that these devices have screens that aren&rsquo;t much larger than a two pence piece. Opening the Citymapper app (which launches pretty much immediately) on the Android Wear device offers you two options, &ldquo;Get me to work&rdquo; and &ldquo;Get me home&rdquo;. Anything more than that, you&rsquo;re going to need to pull your phone out. Open Google Maps however and you&rsquo;ll be waiting approximately 7-8 seconds for it to literally load a street level map onto your wrist. Dragging the map around stutters and skips frames. Can you get directions to your home or work through this App? Of course you can&rsquo;t! You can, however, search for places nearby, and get navigation directions to those places, if they&rsquo;re in the list of Google approved(tm) places. Why, in Gods name, would anyone <strong>want</strong> to look at a two pence piece sized version of the world? If I wanted to see a map, I&rsquo;d have looked at my phone!</p>
<p>I think that Wear OS would be so much better off if instead of working to put a phone on your wrist, it was instead designed to be auxiliary to the ridiculously powerful slab of glass you already almost certainly have in your pocket. While I don&rsquo;t regret the purchase, and to Googles credit things do seem to be very slowly improving, I certainly won&rsquo;t be buying another Wear OS device. Companies like Xiaomi are releasing devices like the Amazfit, which while nowhere near as versatile as Wear OS, give me the main things I care about: Seeing and responding to notifications, tracking fitness/sleep, controlling music, and telling the damn time. It also has a month of battery life, and isn&rsquo;t trying to do literally everything the phone in my pocket does better than it.</p>
<p><strong>Update</strong>: I eventually sold the damn thing and have been much happier since.</p>
]]></description>
    </item>
    
    <item>
      <title>The Cybiko, a Forgotten Gem</title>
      <link>http://kn100.me/the-cybiko-a-forgotten-gem/</link>
      <pubDate>Thu, 29 Mar 2018 11:59:49 +0100</pubDate>
      
      <guid>http://kn100.me/the-cybiko-a-forgotten-gem/</guid>
      <description><![CDATA[<p>Few remember the Cybiko. In the year 2000, a Russian company unveiled a very strange looking device, adorned with a comically small keyboard and a retro antenna. They called it a handheld computer, and marketed it towards teens with some of the most insane advertising I&rsquo;d personally ever seen.</p>

<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
  <iframe src="https://www.youtube.com/embed/cvbQubCXMgo" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" allowfullscreen title="YouTube Video"></iframe>
</div>

<p>The device was an interesting one, and to describe just how far ahead of its time it really was, it will be useful to draw comparisons against smartphones of today. Like a smartphone, it supported instant messaging, had a collection of downloadable apps that received updates, allowed those apps to communicate with one another (for multiplayer), had a SDK to allow others to develop apps in what they called &ldquo;CyBasic&rdquo;, and even received operating system updates! Bearing in mind this was released in 2001, when the most popular phone was likely Nokia 3310 vintage. It even did some interesting stuff around mesh networking that never really worked, due to the relative unpopularity of the Cybiko and the short range of the wireless.</p>
<figure><img src="/posts/the-cybiko-a-forgotten-gem/cybiko-intro.jpg"
         alt="A neon green Cybiko"/><figcaption>
            <h4>A neon green Cybiko</h4>
        </figcaption>
</figure>

<p>Where this device differs from your average phone though, was its lack of GSM support. Hilariously though, if you purchased a wireless adaptor that would plug into your PC, your Cybiko then could browse the internet via WAP. I wonder how many other devices that didn&rsquo;t have GSM supported WAP?</p>
<p>The device was loaded with lots of forward thinking functionality. It had an expansion slot into which one could plug extra devices. One such device was an MP3 player, which took compact flash cards, and then allowed apps on the Cybiko to interact with it.</p>
<p>I think its downfall was its extreme focus on releasing hundreds of games for free, rather than releasing a few good quality games and charging for them. It was competing against the already 2 year old Gameboy Color with its monochrome low resolution (and fairly terrible) display. It also had fairly terrible audio too. Whereas the Gameboy was laser focused on playing games, and as such had the controls to match, the Cybiko came with a stylus. The stylus was not for a touch screen however, it was for pressing the ridiculously small keys. As usual, content is king, and the Cybiko game catalogue just didn&rsquo;t have the content to really &lsquo;sell&rsquo; it as a platform.</p>
<p>They did release a follow up product called the Cybiko Xtreme, which featured a much improved operating system, a higher contrast display, generally faster hardware, a much slimmer profile, and a significantly better keyboard, but it was too little too late. The Xtreme failed to address the main problems Cybiko had - the poor quality game catalogue and the low penetration of the device rendering the interesting wireless functionality useless. The Classic unit could be found in retailers for as little as £10, towards the end of its life, along with whatever remained of the Xtreme stock for £30. A far cry from the original asking price for both.</p>
<p>I owned and loved both as a kid, and fondly remember them. Rest in peace, Cybiko!</p>
]]></description>
    </item>
    
    <item>
      <title>World Explorer - a Unity VR Project</title>
      <link>http://kn100.me/world-explorer/</link>
      <pubDate>Wed, 22 Jun 2016 00:00:00 +0000</pubDate>
      
      <guid>http://kn100.me/world-explorer/</guid>
      <description><![CDATA[<p>This project provides you with two main scripts. The first is a player controller which was designed with the Oculus Rift DK2 in mind, but should work perfectly with more modern hardware. This player controller will attempt to keep the player locked to the surface of whatever three dimensional shape they are walking on, similar to Super Mario Galaxy.
##Video demos
For some video demos of it in action, please see these three videos:

<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
  <iframe src="https://www.youtube.com/embed/SDkdGDm5BXc" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" allowfullscreen title="YouTube Video"></iframe>
</div>
</p>

<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
  <iframe src="https://www.youtube.com/embed/tIEHX5wSVyE" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" allowfullscreen title="YouTube Video"></iframe>
</div>

<h2 id="assets">
  <a class="Heading-link u-clickable" href="/world-explorer/#assets">Assets</a>
</h2>
<h3 id="a-player-controller-that">
  <a class="Heading-link u-clickable" href="/world-explorer/#a-player-controller-that">A player controller that:</a>
</h3>
<ul>
<li>Allows for forwards, backwards, and rotation motion</li>
<li>Keeps the player locked to the surface of any given geometry, provided that it has a mesh collider (Think Super Mario Galaxy)</li>
<li>Is compatible with VR headsets - tested with Oculus Home and Rift DK2.</li>
</ul>
<h3 id="a-virtual-reality-user-interface-controller-script-that">
  <a class="Heading-link u-clickable" href="/world-explorer/#a-virtual-reality-user-interface-controller-script-that">A Virtual Reality User Interface controller script that:</a>
</h3>
<ul>
<li>Implements a gaze pointer type interface, where the user looks at three dimensional objects in order to select them</li>
<li>Follows Googles&rsquo; guidelines on VR interfaces</li>
<li>Describes an interface (<code>IObjectAction</code>) that can be implemented in scripts attached to 3D objects either generated during development or pragmatically, to allow for totally new interfaces</li>
<li>Implements a non-blocking timer that is used to decide if the user has been looking at an element long enough</li>
<li>Displays said timer in three dimensional space to the user wearing the headset.</li>
</ul>
<h2 id="quickstart">
  <a class="Heading-link u-clickable" href="/world-explorer/#quickstart">Quickstart</a>
</h2>
<p>To get started</p>
<ol>
<li>Clone this repository</li>
<li>Open the &lsquo;World Explorer&rsquo; folder within the repo as a project with Unity.</li>
<li>Hit Play in Unity and use your standard method of control! Feel free to build on this scene.</li>
</ol>
<h2 id="implementation-details">
  <a class="Heading-link u-clickable" href="/world-explorer/#implementation-details">Implementation details</a>
</h2>
<h3 id="creating-a-new-scene">
  <a class="Heading-link u-clickable" href="/world-explorer/#creating-a-new-scene">Creating a new scene</a>
</h3>
<ol>
<li>Create a Capsule, and give it a <code>Rigidbody</code>, disabling Gravity. Add a Camera as a child of this Capsule and ensure its&rsquo; position is set sensibly; (0,0,0) is usually a good bet.</li>
<li>Attach the genericWalker script provided to this Capsule.</li>
<li>If user interface is desired, Firstly attach CameraLookAtUI to the player camera.</li>
<li>Tag the game object you wish to do something if the user looks at it with either &ldquo;Major&rdquo; or &ldquo;Minor&rdquo;. This distinction allows you to set two separate activation delays for these two different classes of object.</li>
<li>To define the action you want to take place when the game object is looked at, Create a new C# script called whatever you like, and implement IObjectAction, concretely defining activate() - where your action takes place.</li>
</ol>
<h4 id="example">
  <a class="Heading-link u-clickable" href="/world-explorer/#example">Example</a>
</h4>

<pre tabindex="0"><code>    Example which selects a different scene:

    using UnityEngine;
    using System.Collections;
    using UnityEngine.SceneManagement;

    public class goHome : MonoBehaviour,IObjectAction
    {
	// Use this for initialization
	void Start () {
    }

	// Update is called once per frame
	void Update () {

    }
    public void activate()
    {
        SceneManager.LoadScene(0);
    }</code></pre>
<h2 id="licence">
  <a class="Heading-link u-clickable" href="/world-explorer/#licence">Licence</a>
</h2>

<pre tabindex="0"><code>    Copyright (c) 2016 Kevin Norman.

    Permission is hereby granted, free of charge, to any person obtaining
    a copy of this software and associated documentation files (the &#34;Software&#34;),
    to deal in the Software without restriction, including without limitation
    the rights to use, copy, modify, merge, publish, distribute, sublicense,
    and/or sell copies of the Software, and to permit persons to whom the Software
    is furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in
    all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED &#34;AS IS&#34;, WITHOUT WARRANTY OF ANY KIND,
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
    OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
    OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</code></pre>
<h2 id="github">
  <a class="Heading-link u-clickable" href="/world-explorer/#github">Github</a>
</h2>
<p><a href="https://github.com/kn100/worldExplorer">https://github.com/kn100/worldExplorer</a></p>
]]></description>
    </item>
    
  </channel>
</rss>
