<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Simon's Blog]]></title><description><![CDATA[Simon-grade thoughts. Usually about tech, UX or some combination.]]></description><link>https://simonam.dev</link><generator>GatsbyJS</generator><lastBuildDate>Tue, 30 Dec 2025 17:47:19 GMT</lastBuildDate><item><title><![CDATA[Let the vibes flow]]></title><description><![CDATA[I happened to have started reading for a Master’s Degree in AI just before the LLM craze started. AI was cool and there were some very…]]></description><link>https://simonam.dev/let-the-vibes-flow/</link><guid isPermaLink="false">https://simonam.dev/let-the-vibes-flow/</guid><pubDate>Wed, 31 Dec 2025 01:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I happened to have started reading for a Master’s Degree in AI just before the LLM craze started. AI was &lt;em&gt;cool&lt;/em&gt; and there were some very impressive use cases but discourse about it was nowhere near as mainstream as it is now.&lt;/p&gt;
&lt;p&gt;When the hype took off, it was a very crazy time to be in both industry and academia at the same time. In industry there was a mad rush to build something, &lt;em&gt;anything&lt;/em&gt; that used the ChatGPT API. In academia, there was so much research happening that at one point we were being encouraged to &lt;em&gt;not consider any papers older than three months old&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Being one of the few folks who knew how LLMs actually worked, having understanding of their inherent flaws and how they wouldn’t really ever go away, just be managed better (because bias is a part of reality), made me &lt;em&gt;very&lt;/em&gt; fun at parties for a while. I got good at bursting people’s bubbles about how &lt;em&gt;no, this is a very bad use case!&lt;/em&gt; didn’t make me any friends for a while.&lt;/p&gt;
&lt;p&gt;This came to a head when there was a presentation about how a team had integrated the ChatGPT API to run sentiment analysis on one of our products. They were talking about how well it was going (without presenting any sort of objective evaluation against a test set) and how one of their struggles was with inconsistent results.&lt;/p&gt;
&lt;p&gt;This rang very weird to my brain. Inconsistent results with an LLM is (a) a feature, not a bug, and (b) configurable! So as the presentation continued I took a look at their source code and found the following: &lt;code class=&quot;language-text&quot;&gt;temperature=0.5&lt;/code&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Fixing Google Antigravity IDE Agent crashing immediately on Proxmox]]></title><description><![CDATA[While in my day job I use Cursor, I’ve been trying out Google Antigravity for my personal projects, mainly due to the generous Gemini limits…]]></description><link>https://simonam.dev/fixing-antigravity-server-crash/</link><guid isPermaLink="false">https://simonam.dev/fixing-antigravity-server-crash/</guid><pubDate>Tue, 30 Dec 2025 01:00:00 GMT</pubDate><content:encoded>&lt;p&gt;While in my day job I use Cursor, I’ve been trying out &lt;a href=&quot;https://antigravity.google/&quot;&gt;Google Antigravity&lt;/a&gt; for my personal projects, mainly due to the generous Gemini limits they provide via Google One (which I already pay for separately).&lt;/p&gt;
&lt;p&gt;After trying it out on my &lt;a href=&quot;/ssh-wol-proxy/&quot;&gt;beefy physical machine&lt;/a&gt; with secure mode enabled to make sure it doesn’t &lt;a href=&quot;https://www.reddit.com/r/google_antigravity/comments/1p82or6/google_antigravity_just_deleted_the_contents_of/&quot;&gt;wipe my entire hard drive&lt;/a&gt; by mistake, I realised that I could trust it in a more hands-off fashion if I gave it its own playground.&lt;/p&gt;
&lt;p&gt;I quickly setup a VM on my Proxmox node but as soon as I attempted to connect to my VM via the SSH extension I would get this error:&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Antigravity server crashed unexpectedly. Please restart to fully restore AI features.&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;No other info unfortunately. A restart would lead to the same behaivor immediately. I tried looking into the Antigravity logs but I didn’t find anything useful.&lt;/p&gt;
&lt;p&gt;Eventually with some googling I found &lt;a href=&quot;https://www.reddit.com/r/GoogleAntigravityIDE/comments/1p1ixj5/antigravity_server_crashed_unexpectedly_please/&quot;&gt;a Reddit thread&lt;/a&gt;  which, while the issue there was Mac specific, had the answer for buried deep in a comment. I’m recreating it here to hopefully make it easier for the next person to solve.&lt;/p&gt;
&lt;p&gt;Checking a different part of Antigravity error logs revealed the following: &lt;code class=&quot;language-text&quot;&gt;FATAL ERROR: This binary was compiled with pclmul enabled, but this feature is not available on this processor (go/sigill-fail-fast).&lt;/code&gt;. &lt;/p&gt;
&lt;p&gt;The solution on Reddit provided by user ConKernel was: &lt;code class=&quot;language-text&quot;&gt;If using proxmox VM, try changing host processor to &quot;host&quot; and reboot the VM. It worked for me&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This I can confirm also worked for me and now I can use Antigravity SSHed into my Proxmox VM just fine.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Using a physical machine as if it were an on-demand Virtual Machine]]></title><description><![CDATA[Here are the specs for the best machine I have in the house: Intel 13th Gen i3-13100 32GB of DDR4 RAM Nvidia Tesla P40 (24GB of VRAM) This…]]></description><link>https://simonam.dev/ssh-wol-proxy/</link><guid isPermaLink="false">https://simonam.dev/ssh-wol-proxy/</guid><pubDate>Sun, 07 Dec 2025 17:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Here are the specs for the best machine I have in the house:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Intel 13th Gen i3-13100&lt;/li&gt;
&lt;li&gt;32GB of DDR4 RAM&lt;/li&gt;
&lt;li&gt;Nvidia Tesla P40 (24GB of VRAM)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This machine lives in a 4U rack case in my server rack along with the rest of my homelab.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/5b0534e3fbaca4b2688e0762efe451f8/7e4a6/homelab.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 149.32432432432435%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAYAAAAsEj5rAAAACXBIWXMAABYlAAAWJQFJUiTwAAAH+0lEQVRIxyXU+VPTBx7G8a/1oNaDG+QIhPvICQHCfd+iiFXEabGAIAICESLhCBAQkPtKCFdBoCQcAhKwtLhWd9fdXjvddWdnO7PT/V/eO0t/fX54zfPD5/MIv74/4Lefjvn1r0d8+PMRP79aZ6vnCb98/x3//vuP/PeXd7w/tvHdq5e8OXzBt3tWfny9zV+OLPzzj5t8eLvOh7dWfvvpkP/8YEP4x2sL/3q3w+stEzvLo7zaMHO0s4TNOs/h1jIHWyvsri+xa1lie3We7ZU5jm0b7K+b2ZjrwzrXz8pUF3vLQ3x4u43wg22Rn79eZmthgLnRLqaHOlk0DbG6aGJjbYntTQsv915waNvj1f4O+y+s7O1ssrkyy7rRwJrRwNKEnnVTD3873kR4vzXD97uzHFsn6etsQalQkhgXR1JiAmmpaaSlpJCfnsLVrGymjDPYdjc4PHjJS8sCGzM9WEwGnk90nIBvdpcQ3n41wfstI3/aMTI52IlKqSBBJSdWHUlsfBRxidEkpaiJUEViNM/x7lsbhwc2dtdmsZq6+Wq6m+djbVhNBmxrRoQ3KyN8tzbG4cogi5MGlAoFUfIwVLIwIqVhRMikqJQSJDI5MzOzvPl6l4MDG4eWWbbNXVhM3axOdLA61f07eLw0yPHyIIvDOtaMPUQoVURIFSSqVWSnJZGXlU5uejIymQKj0fQ7aNvncN3I9mw3GzPdrE7qWZk0cGQ1IxzO9nK00M/zER3rxh7U6likQWKU4SGoVQoSYiKIj5QhVagwm8384dUOB/t7vFgax2rSYzV1sTzWxpfjndjWphF2pjvZM3WxOqrDYuolNSWVEH8fpCH+qGTBqMKCUUfIkEqlmGfMvD7cZX9vh62FYSxTHaxN6lkY1LI42snO8wmElWEd1rFW1kZasJp6yc3JITjAD0mAL5IAP2QhwWRlpKGOi2XOPMu3thfsvtjEMtN/UmJppAVjrwbzYDvWuSGEyY5aVoa0rA5r2TAZKLxWgL+viHB/X0IDRIT4iQgWXcHbU8SMycQ3tu2T21yd7GKxX8NsbwOj7dXM9OuwmAcQhjsrmO1rYFRXyVcTHRTfvIGnuytBYm+C/L2RB4mJDA/B1z/8BPz65SabljUWh1oxdtUw1lpBZ+0dWm5HM/hFLMLdqjBG9ZW0VhWzMqLj85JbeHp4IhGLCPXxIsrPF4nIk0uuvkxNTXO4t4FlbRlzXzPjrZX0N36GpvQalam+NOT4IRTdDWWktZq2B8XM92mo+LwEX78AgnxEBHq4IQ/wI1TkgYuTOxMTk9h2raw+X2DK0MigtpyO6mIqijIoT/alKsUDITtFwtPHZRSkqumuL6Wm4nPc3T3wcHVG5O5CbGwcktBgXF3cGB8fY3drjaWFGUY76jA0lvK4rIjbKXIKogO5ERuIUHotA03Zp3h7uFF+KxdNdRmnT5/B4eIFPFyckUokBPuLcXV1Z2J8jJ2NVeZNEzzTPURfX8qDwkTiA12JDvIgMVyEkJ+q5t6NLNxdnCjKTqKlrhK7s+dwdXLEzckJmVROeEgQ3iIxhq4ulmanmTOO0ffkAU33b1OWG01hgoScJCU5qgCE/39FQVosV1ydyUxQ0dZQxamPznBKELC/8AlXXJxxc7TH21tMeVk5Az2dzBtH6dU+4FHZbWpuZaItSafh0yQeXo9DiJEFkxkfSZhYRJwsFL2mGrvzF/nY7izODva4OjrgfPkiIpGY69dv0K7TnjRsfVRBSVEedwoyyVTLSIoIIVYehJCXGEF2QiRJkXKSZaG01lfh6OyG3dkzOFy6gIuDPS72F3F1vUJHezvm6XHGhp6ia6imQ/uYzKs38PDyxNnZCW8fb4S0KAmZajmZ8VEkK8PR1lbi4eHFmdMf8YndOS5/cp5L589hb+9El17PrGmSwT4D9VVlTI6OYJyeot/QSW5GMkFid4RERTDpUWGoQsQkS/14XHMfsdgfu9OnTrCL5+24dP4sjo7OtOl0TI0P89SgR1d7n7H+Aarrn/BsYAizaYbe3m6EZGUQWWoJ0SE+ZKtCaHxYSVBQMOdOn8Lx8kWcLl/g0sdncHRwQtfczNhQHzqthom+Fg4t88zPTDHU38+znl6e9Q8ixEgCyIqVk6IIJEPpz6PKMqQSKR+fPY2z4+UT8IqLA+7u7jQ/bmRkoIeGump69c0MdDaybu7n3f5zjra/ZHVuAkHs6Up6jIw8dTi5UcFUl5aQnJSEs5M9Xp5ueF1xQezjibe3D00NDQz3G6iuKudZdxvy6EhE0jAq7t1ierCNQ+scQkSgN9lxCm6mKPkiJ5qMqDAyE2PISI4jUilBJPLAzcX+ZDCa6hsY7O2isuIeve3NlH9RSmxeHl7yEJz9vUi5lo1QnKLgVrKCe9lRaIpTKc2M4LOMCPLi5BRkJnH9ag4xUQrEYjENtTU87W6nsryUzhYNmroaBrr06Nu03Cy5TUB6EkJxqpJ7GRHcz1fTei+Hinw1LXczeHI3neyIQDJUEgpz0rial4uuqZEefSuVZaW0N9dTX1uLprGO7jYtI4YuDE+0CHdSFZTnRFGaE01HWR7V1+OpKYynp6qAmsIEKjIV3IwPIz9RRWlxIU+aGijMz6ap7gG3C69SW11Jo6aWuroqtJo6hKIEKWXZ0XyWpaKz4iqa4jQe5MdgqCygqSSDyrxodHdSTrLs6BDykiK5lhTB/U9zyI+TUZgSTdXdIh49rOBRXRX/A0UOAXZMM05IAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Homelab&quot;
        title=&quot;Homelab&quot;
        src=&quot;/static/5b0534e3fbaca4b2688e0762efe451f8/fcda8/homelab.png&quot;
        srcset=&quot;/static/5b0534e3fbaca4b2688e0762efe451f8/12f09/homelab.png 148w,
/static/5b0534e3fbaca4b2688e0762efe451f8/e4a3f/homelab.png 295w,
/static/5b0534e3fbaca4b2688e0762efe451f8/fcda8/homelab.png 590w,
/static/5b0534e3fbaca4b2688e0762efe451f8/efc66/homelab.png 885w,
/static/5b0534e3fbaca4b2688e0762efe451f8/7e4a6/homelab.png 952w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;While my 2019 Macbook Pro technically has better specs, as soon as it starts to do any work it attempts to melt the surface it is sitting on (this was the last Intel based model made).&lt;/p&gt;
&lt;p&gt;So I figured, using VS Code’s awesome remote development extension, I could use my Macbook as a terminal and push the hard work onto the better machine by SSHing in, especially when I want to do any work involving local LLMs.&lt;/p&gt;
&lt;p&gt;To avoid running the machine in the rack 24/7 (especially since I pay the electricity bill), I wondered whether it would be possible to turn the machine on and off only when I wanted to use it. I had already configured it to use Wake On Lan which meant I didn’t need to phyiscally push the power button, but it still meant that I had to trigger the WOL packet from within the local network (which I eventually discovered was an issue when I was using my VPN from a remote location).&lt;/p&gt;
&lt;p&gt;I also had left it running without shutting it down a few times, which was frustrating to discover after the fact. I wished it would behave more like an on-demand / ephemeral Virtual Machine, where I only paid for it when it was being used.&lt;/p&gt;
&lt;p&gt;To solve this, I wrote &lt;a href=&quot;https://github.com/simonamdev/ssh-wol-proxy&quot;&gt;ssh-wol-proxy&lt;/a&gt;. This is a tiny server written in Go that I run within my homelab. It listens for incoming SSH connections and when a connection is established, it sends a WOL packet to my machine. It then makes multiple attempts to establish a TCP connection on the target SSH port. So as long as your SSH connection timeout is longer than it takes for the machine to start and for sshd to become available, then the proxy will manage to connect and connect you to the machine.&lt;/p&gt;
&lt;p&gt;While this was a great solution, it did not solve the part where I left the machine running. For that I configured &lt;a href=&quot;https://github.com/mnul/debian-autoshutdown&quot;&gt;debian-autoshutdown&lt;/a&gt; to check every five minutes whether there is any network traffic on port 22. If after a few checks there is no traffic, it shuts down the machine.&lt;/p&gt;
&lt;p&gt;These two solutions together solve my issues. I now have a physical machine that behaves in an on-demand way. I only need to open VS Code and connect to the proxy using the remote development extension and within approximately 45 seconds I’m ready to work.&lt;/p&gt;
&lt;p&gt;The code could be greatly improved, it suffers from multiple race conditions, but in a homelab setting it works just fine.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Why I have two LinkedIn accounts]]></title><description><![CDATA[If you search for my name on LinkedIn you’ll find out there are two of me.  Both of these are me. So why do I have two LinkedIn accounts…]]></description><link>https://simonam.dev/linkedin-registration-issue/</link><guid isPermaLink="false">https://simonam.dev/linkedin-registration-issue/</guid><pubDate>Sat, 06 Dec 2025 17:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you search for my name on LinkedIn you’ll find out there are two of me.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/20319db85cf4569d7a5fc2d181fc8892/e6c84/two-of-me.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 68.91891891891892%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAABYlAAAWJQFJUiTwAAACAUlEQVQ4y4WTPY/TQBCG/b9oaSmo6BE1Lf1RIyjokBA1DX8AUERQEgkJKRApNlFyJHFiJ3Ji7zr+2A/7RTO+u8SXOzHSo117x6OdeV87ZZEBqAFUqCuLssihVAGtShitYI2GKgveE7Qn6LyFLvlbh4r1vn3FxYvn+Dv14LoeOp0OhsNf6Ha76PX76Pf7vA4GA7iuCyklDocDbkdlLZz0IPDhzUu8evoYnz6+h7/y4XkelsslJpMJ5vM5M5vNmNVqhbquGYrTvbUGTo0K796+xpOHD9D9/gVpmkIIcZN4SlVVjDGGsdbeFD0WrCvEicDv0YgTpEyx3+8RxzGElFw8SYgEO36fQAjJz7fb5pYbUZogARhr7qS6wp6sWivGaBIvhyNlgvF4DM9z+YBEOrZo21h7/u5qDNSpMRoOWWA6nfLgST2tNfI8R1EUrYHfF3R8ncIzpBazvICxFmEYcrEoiniO5x+3RTq3jWlu6I5+QsQ7BGGI9XqNxWKBIAh4JXGyLOO2zmxS1YhSjVAoXkul4UTRBhfPHmHY+4xYpFxgu902qu52XEwpdeftqKAXZPhxKTHyM+SlhkO/zPbyD+J9xJahGf4v7mubZ2hY2XZio1rNHiTYk0Kw6enm1+fH/JqFscbA0apgO1jbtgclbjYbnin9hr7vIwjWDNmjsUvbPnS5f8DEMZF5Z7j0AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Two of me&quot;
        title=&quot;Two of me&quot;
        src=&quot;/static/20319db85cf4569d7a5fc2d181fc8892/fcda8/two-of-me.png&quot;
        srcset=&quot;/static/20319db85cf4569d7a5fc2d181fc8892/12f09/two-of-me.png 148w,
/static/20319db85cf4569d7a5fc2d181fc8892/e4a3f/two-of-me.png 295w,
/static/20319db85cf4569d7a5fc2d181fc8892/fcda8/two-of-me.png 590w,
/static/20319db85cf4569d7a5fc2d181fc8892/efc66/two-of-me.png 885w,
/static/20319db85cf4569d7a5fc2d181fc8892/e6c84/two-of-me.png 1148w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Both of these are me. So why do I have two LinkedIn accounts?&lt;/p&gt;
&lt;p&gt;This isn’t some elite growth hacking method, its just bad software on LinkedIn’s side.&lt;/p&gt;
&lt;p&gt;I don’t have the LinkedIn app on my phone for reason that aren’t relevant here. I do occasionally receive a link to something on LinkedIn I would like to read.&lt;/p&gt;
&lt;p&gt;Unfortunately, LinkedIn seems to think that because I have a google account that I want to immediately create an account using my gmail address account. It doesn’t ask whether I want to do this, it just happens. Sometimes I manage to back out of the browser before it can go through but I’m already on the fourth extra account created.&lt;/p&gt;
&lt;p&gt;At some point I get annoyed enough to attempt to delete this account, which involes the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Logging in with said gmail account (no need to do anything special, I just go to LinkedIn web)&lt;/li&gt;
&lt;li&gt;Navigate to Settings &amp;#x26; Privacy -&gt; Close &amp;#x26; Delete Account&lt;/li&gt;
&lt;li&gt;Encounter the following issue:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/21d088315b77bdb4d58f045c6c4b9fe0/c211c/li-no-password.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 45.94594594594595%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA/UlEQVQoz6WSzW6EMAyEef/nQ3tA4tQKEX4jICSxA1PZ6bJIvWxbS59sghniSYoYA4TjSMhxKokZKf2GBL87FCF4NE2DcRwxTROstViWBX+JGDwK5zY8Hg9UVYWyLLWu6xrGGHRdh2EYcBzHW4Ih7CgoRqzrCma+Xpznqc8v0o28JiPekZ9mQYogogtpfmb9kAmJIhJHML/WpScE8T9qJuIs6P2uoz2RUdu2Rd/3Wn+aGc2w4MNYNGZQn7dtU8GfI3sUMQTM86yHIY0yvmTnnNbWEaxjzI6xOA/x/C4o9giXhzKG7KZtDf4besqiKvdQtpv9fA9W6Jvs775v+AIVPr68PxcmcAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;I dont have a password on LinkedIn&quot;
        title=&quot;I dont have a password on LinkedIn&quot;
        src=&quot;/static/21d088315b77bdb4d58f045c6c4b9fe0/fcda8/li-no-password.png&quot;
        srcset=&quot;/static/21d088315b77bdb4d58f045c6c4b9fe0/12f09/li-no-password.png 148w,
/static/21d088315b77bdb4d58f045c6c4b9fe0/e4a3f/li-no-password.png 295w,
/static/21d088315b77bdb4d58f045c6c4b9fe0/fcda8/li-no-password.png 590w,
/static/21d088315b77bdb4d58f045c6c4b9fe0/efc66/li-no-password.png 885w,
/static/21d088315b77bdb4d58f045c6c4b9fe0/c83ae/li-no-password.png 1180w,
/static/21d088315b77bdb4d58f045c6c4b9fe0/c211c/li-no-password.png 1502w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;Go through the process of resetting my password&lt;/li&gt;
&lt;li&gt;Go back into LinkedIn, navigate again to Settings &amp;#x26; Privacy -&gt; Close &amp;#x26; Delete Account&lt;/li&gt;
&lt;li&gt;Provide my password and specifically tick the unsubscribe option so I don’t receive marketing emails&lt;/li&gt;
&lt;li&gt;All done, until the next time I mistakenly navigate to LinkedIn on my phone&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;No other service I know of pro-actively signs me up without any confirmation.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[permess.mt Update 1]]></title><description><![CDATA[permess.mt is a web-app that allows you to understand Malta’s urban landscape like never before: a fast and precise way of viewing planning…]]></description><link>https://simonam.dev/permess-update-1/</link><guid isPermaLink="false">https://simonam.dev/permess-update-1/</guid><pubDate>Tue, 22 Apr 2025 19:00:00 GMT</pubDate><content:encoded>&lt;p&gt;permess.mt is a web-app that allows you to understand Malta’s urban landscape like never before: a fast and precise way of viewing planning permits.&lt;/p&gt;
&lt;p&gt;It is now live at &lt;a href=&quot;https://app.permess.mt/&quot;&gt;https://app.permess.mt/&lt;/a&gt; and I’ll be iterating constantly on it to improve the service. The data is currently updated weekly.&lt;/p&gt;
&lt;p&gt;If you’re interested in full dataset access, get in touch!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Breaking the build while my son was being born]]></title><description><![CDATA[The Story Where were you in February 2024? Here’s where I was: I worked at Hotjar. My son is about to be born and I headed out on parental…]]></description><link>https://simonam.dev/leap-year-bug/</link><guid isPermaLink="false">https://simonam.dev/leap-year-bug/</guid><pubDate>Fri, 11 Apr 2025 19:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;The Story&lt;/h1&gt;
&lt;p&gt;Where were you in February 2024? Here’s where I was:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I worked at Hotjar.&lt;/li&gt;
&lt;li&gt;My son is about to be born and I headed out on parental leave on the 26th.&lt;/li&gt;
&lt;li&gt;On the 28th I attend a training course because my son hadn’t arrived yet. This was my last contact with my team on Slack.&lt;/li&gt;
&lt;li&gt;On the 29th my son is born.&lt;/li&gt;
&lt;li&gt;On the 1st of March I logged back on to tell my team.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I saw that nearly no deployments had happened and I found a 60+ message long Slack thread of people attempting to diagnose the broken build.
This was clearly serious because Hotjar was always shipping. Easily 10-20 deployments happened on a normal day and it was commonplace for our team (Developer Experience) to get complaints if for any reason the deployment pipeline was closed for more than an hour.&lt;/p&gt;
&lt;p&gt;I scrolled to the bottom of the chat to see what the resolution was and what caused it and it turns out… it was &lt;strong&gt;my fault&lt;/strong&gt;.&lt;/p&gt;
&lt;h1&gt;My fault&lt;/h1&gt;
&lt;p&gt;I joined Hotjar in May of 2021 as a Backend Engineer, shipping features to our customers, until Jan 2023 where I shifted to the Developer Experience team, where we kept our engineers shipping fast and happy.&lt;/p&gt;
&lt;p&gt;At some point during those three years I wrote a very standard looking test that checked whether a repository method (one that interacted with Postgres) returned the expected rows.&lt;/p&gt;
&lt;p&gt;This method in particular would filter the rows based on a date range you passed as a paramter, something like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;python&quot;&gt;&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;get_foo_between_dates&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;start_date&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; datetime&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; end_date&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; datetime&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Foo&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# Code goes here&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Aligning with best practices and the strong testing culture at the time, I wrote both happy path and non-happy path tests. One of them went something like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Setup the test: create a &lt;code class=&quot;language-text&quot;&gt;Foo&lt;/code&gt; for every date in February&lt;/li&gt;
&lt;li&gt;Assert that 28 &lt;code class=&quot;language-text&quot;&gt;Foo&lt;/code&gt;s exist&lt;/li&gt;
&lt;li&gt;Query for all &lt;code class=&quot;language-text&quot;&gt;Foo&lt;/code&gt;s between 1st and 5th February (inclusive)&lt;/li&gt;
&lt;li&gt;Assert that I receive 5 &lt;code class=&quot;language-text&quot;&gt;Foo&lt;/code&gt;s&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Pretty simple test, it did its job for three years straight… until finally there were 29 &lt;code class=&quot;language-text&quot;&gt;Foo&lt;/code&gt;s instead of 28 &lt;code class=&quot;language-text&quot;&gt;Foo&lt;/code&gt;s, failing the first assert.&lt;/p&gt;
&lt;p&gt;Just to make sure I never forget this, my lovely team at the time wrote this in the card that came with the flowers celebrating my son’s birth:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/48ad552cc1ee94851fd23e91ed3c1730/6c0a0/card.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 67.56756756756756%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAOABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAIDBP/EABUBAQEAAAAAAAAAAAAAAAAAAAEC/9oADAMBAAIQAxAAAAGs2jNZhRP/xAAXEAADAQAAAAAAAAAAAAAAAAAAAQMy/9oACAEBAAEFAmMrpjKa/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPwE//8QAFBABAAAAAAAAAAAAAAAAAAAAIP/aAAgBAQAGPwJf/8QAGRABAAIDAAAAAAAAAAAAAAAAAQBxECEx/9oACAEBAAE/IUREWlZuFT//2gAMAwEAAgADAAAAEIDv/8QAFhEBAQEAAAAAAAAAAAAAAAAAAAEx/9oACAEDAQE/EF1//8QAFREBAQAAAAAAAAAAAAAAAAAAAAH/2gAIAQIBAT8QR//EABwQAAICAgMAAAAAAAAAAAAAAAABETEhsUFxkf/aAAgBAQABPxCRlkom6u2I5dWK1yP4Ns//2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Card&quot;
        title=&quot;Card&quot;
        src=&quot;/static/48ad552cc1ee94851fd23e91ed3c1730/1c72d/card.jpg&quot;
        srcset=&quot;/static/48ad552cc1ee94851fd23e91ed3c1730/a80bd/card.jpg 148w,
/static/48ad552cc1ee94851fd23e91ed3c1730/1c91a/card.jpg 295w,
/static/48ad552cc1ee94851fd23e91ed3c1730/1c72d/card.jpg 590w,
/static/48ad552cc1ee94851fd23e91ed3c1730/a8a14/card.jpg 885w,
/static/48ad552cc1ee94851fd23e91ed3c1730/6c0a0/card.jpg 1062w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Worth it.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Why query the last ten when you can query every row]]></title><description><![CDATA[Here’s another SQL query I wrote recently. Lets say you want to query the minimum stock price of a given symbol out of the last ten values…]]></description><link>https://simonam.dev/insidious-sql-bugs-part-2/</link><guid isPermaLink="false">https://simonam.dev/insidious-sql-bugs-part-2/</guid><pubDate>Wed, 02 Apr 2025 19:30:00 GMT</pubDate><content:encoded>&lt;p&gt;Here’s another SQL query I wrote recently. Lets say you want to query the minimum stock price of a given symbol out of the last ten values. Here was my first take, can you spot the bug?&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;MIN&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;price&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; symbol &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;APPL&apos;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; id &lt;span class=&quot;token keyword&quot;&gt;DESC&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;LIMIT&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Breaking down my thinking:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I want the minimum price: &lt;code class=&quot;language-text&quot;&gt;MIN(price)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;For a given symbol: &lt;code class=&quot;language-text&quot;&gt;WHERE symbol = &apos;APPL&apos;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The latest values: &lt;code class=&quot;language-text&quot;&gt;ORDER BY id DESC&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The last ten values: &lt;code class=&quot;language-text&quot;&gt;LIMIT 10&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I eventually realised that this query was incorrect. It was returning the minimum price across &lt;em&gt;all&lt;/em&gt; rows, not just the last ten.&lt;/p&gt;
&lt;p&gt;The answer lies in how the SQL query is parsed. If I had to visualise it, I would put it this way. The query was being executed like so:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;MIN&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;price&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; symbol &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;APPL&apos;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; id &lt;span class=&quot;token keyword&quot;&gt;DESC&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;LIMIT&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In other words, it was grabbing the minimum value over all rows (which returns… a row) and then limiting that result to ten rows, which produces a single row anyway. The correct way would be to query the last ten values, THEN calculate the minimum value:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;MIN&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;price&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; price
    &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; symbol &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;APPL&apos;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; id &lt;span class=&quot;token keyword&quot;&gt;DESC&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;LIMIT&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; last_k_values&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Tests would also have covered this issue :)&lt;/p&gt;</content:encoded></item><item><title><![CDATA[The JOIN that goes nowhere]]></title><description><![CDATA[Here’s an SQL query I wrote recently.  holds URLs that need to be scraped.  holds data of the scraped URL. If a row is added to  and a…]]></description><link>https://simonam.dev/insidious-sql-bugs-part-1/</link><guid isPermaLink="false">https://simonam.dev/insidious-sql-bugs-part-1/</guid><pubDate>Wed, 02 Apr 2025 19:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Here’s an SQL query I wrote recently.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DISTINCT&lt;/span&gt; pu&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id
&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; permit_urls pu
&lt;span class=&quot;token keyword&quot;&gt;LEFT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;JOIN&lt;/span&gt; permit_details pd &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; pu&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pd&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;permit_url_id
&lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; pd&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;permit_type &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; $&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pd&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;permit_url_id &lt;span class=&quot;token operator&quot;&gt;IS&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;OR&lt;/span&gt; pu&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;decided &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;f&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;LIMIT&lt;/span&gt; $&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;permit_urls&lt;/code&gt; holds URLs that need to be scraped. &lt;code class=&quot;language-text&quot;&gt;permit_details&lt;/code&gt; holds data of the scraped URL. If a row is added to &lt;code class=&quot;language-text&quot;&gt;permit_urls&lt;/code&gt; and a relevant &lt;code class=&quot;language-text&quot;&gt;permit_details&lt;/code&gt; row doesn’t exist, then scraping will occur.&lt;/p&gt;
&lt;p&gt;This query worked fine for years until I updated it a few months ago. After that it didn’t break but was consistently returning zero results until I realised my mistake. Can you see the bug?&lt;/p&gt;
&lt;p&gt;Here’s where it lived: &lt;code class=&quot;language-text&quot;&gt;WHERE pd.permit_type = $1 AND (pd.permit_url_id IS NULL OR pu.decided = &apos;f&apos;)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;While that &lt;code class=&quot;language-text&quot;&gt;WHERE&lt;/code&gt; clause is syntatically valid, the brackets around the latter part was causing the query to short-circuit on the &lt;code class=&quot;language-text&quot;&gt;pd.permit_type = $1&lt;/code&gt; check. This is not incorrect in and of itself, but if we think a bit longer about it, &lt;code class=&quot;language-text&quot;&gt;pd.permit_type = $1&lt;/code&gt; was always going to resolve as &lt;code class=&quot;language-text&quot;&gt;FALSE&lt;/code&gt; because the row in the &lt;code class=&quot;language-text&quot;&gt;permit_details pd&lt;/code&gt; table doesn’t exist yet… so we could never return &lt;code class=&quot;language-text&quot;&gt;TRUE&lt;/code&gt; from that part of the clause.&lt;/p&gt;
&lt;p&gt;The adjustment happened because I wanted to split up the processing by the permit type, and I ended up completely breaking the pipeline in the process.&lt;/p&gt;
&lt;p&gt;The obvious solution would be to write tests, but given its a side-project I didn’t see the value in it… until after this bug :)&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Automatic Speech Recognition for Maltese - Part 2]]></title><description><![CDATA[This is part 2 of my effort to create an open-weights Automatic Speech Recognition model for my native language Maltese. Before the how, let…]]></description><link>https://simonam.dev/asr-for-maltese-02/</link><guid isPermaLink="false">https://simonam.dev/asr-for-maltese-02/</guid><pubDate>Thu, 26 Sep 2024 21:30:00 GMT</pubDate><content:encoded>&lt;p&gt;This is part 2 of my effort to create an open-weights Automatic Speech Recognition model for my native language Maltese.&lt;/p&gt;
&lt;p&gt;Before the how, let’s start with why: Put simply, the Maltese language is dying.&lt;/p&gt;
&lt;p&gt;With a fertility rate of 1.1 and an ageing population, every concievable trend shows that the native Maltese population is slowly but surely trending towards 0.&lt;/p&gt;
&lt;p&gt;As a result, there is a day in the future where the last Maltese speaker dies. On that day, the language will die with them. Anything that isn’t written down will become akin to an alien language&lt;/p&gt;
&lt;p&gt;In recent years the population has actually grown due to (mainly economic) immigation. About 25% of the population is now foreign born. With this increase, the use of English (also an official language of Malta) is becoming the de-facto standard for everyday communication. &lt;/p&gt;
&lt;p&gt;These two factors provide a consistent downward pressure on the use of the Maltese language.&lt;/p&gt;
&lt;p&gt;So for me this is not just a personal project, but also a national one. We might all pass away, but if a computer can mathematically understand Maltese, then the language will live on for generations to come.&lt;/p&gt;
&lt;p&gt;People have asked me whether I’m doing this as part of my Master’s degree in AI. Apart from exploring the area in an assignment, the answer is actually &lt;em&gt;no&lt;/em&gt;. There already are researchers much better than me working on this problem, who have had better results than I have.&lt;/p&gt;
&lt;p&gt;Academia’s aim tends to be to produce knowledge. This is super commendable and should continue! But unfortunately the fact that the &lt;a href=&quot;https://www.um.edu.mt/projects/masri/&quot;&gt;MASRI&lt;/a&gt; datasets are not available for commercial use limits the impact of this research. Commercial entities should definitely contribute back to academia to make it sustainable, but without being able to use the result of this research, interest is very quickly lost and in the end, everyone loses and progress stalls.&lt;/p&gt;
&lt;p&gt;If we could get a decent ASR model into everyone’s hands, we’d experience a cambrian explosion of applications and services centered around Maltese, offering an upward pressure against the decline of its use: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Transcription of old Reddifusion shows to be able to enjoy them in written form&lt;/li&gt;
&lt;li&gt;Accelerate research by speeding up transcription of interviews for theses&lt;/li&gt;
&lt;li&gt;Improve the quality of Maltese subtitles for YouTube videos and other media&lt;/li&gt;
&lt;li&gt;Provide real time subtitles for live events and podcasts&lt;/li&gt;
&lt;li&gt;Build tools around learning Maltese and its proper pronunciation&lt;/li&gt;
&lt;li&gt;and much much more!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I’m not limited by compute, but by data. There are a few readily available transcribed audio corpuses, but copyright issues prevent me from using them. An ideal way forward would be to create a new dataset from scratch, one where every Maltese speaker is encouraged to contribute their voice, so that our machines may actually understand them in Maltese.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Automatic Speech Recognition for Maltese - Part 1]]></title><description><![CDATA[As part of my Master’s Degree in AI, one of my assignments was to fine-tune the OpenAI Whisper model for Maltese. Maltese is a low-resource…]]></description><link>https://simonam.dev/asr-for-maltese-01/</link><guid isPermaLink="false">https://simonam.dev/asr-for-maltese-01/</guid><pubDate>Sun, 22 Sep 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As part of my Master’s Degree in AI, one of my assignments was to fine-tune the OpenAI Whisper model for Maltese.&lt;/p&gt;
&lt;p&gt;Maltese is a low-resource language spoken by approximately half a million people, most of whom reside on the tiny island of Malta.&lt;/p&gt;
&lt;p&gt;I was inspired by &lt;a href=&quot;https://ejournal.nusamandiri.ac.id/index.php/pilar/article/view/4633&quot;&gt;this paper&lt;/a&gt; that did the same process for Javanese, another low-resource language spoken mainly by residents of the island of Java.&lt;/p&gt;
&lt;p&gt;Using my trusty and toasty Nvidia 1080 Ti and &lt;a href=&quot;https://github.com/huggingface/peft/blob/main/examples/int8_training/peft_bnb_whisper_large_v2_training.ipynb&quot;&gt;a jupyter notebook provided by huggingface&lt;/a&gt; I was able to re-train the model in around 20 hours for &lt;code class=&quot;language-text&quot;&gt;whisper-large-v2&lt;/code&gt; using 32.9 hours of Maltese transcripted audio, by combining the Mozilla Common Voice and FLEURS datasets.&lt;/p&gt;
&lt;p&gt;Using LoRA parameters of &lt;code class=&quot;language-text&quot;&gt;r=32&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;alpha=64&lt;/code&gt;, resulting in approximately 1% of original parameters being retrained, the results were very encouraging:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Model&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Maltese WER % on original Whisper&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Maltese WER % on fine-tuned Whisper&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Diff&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Base&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;114.66&lt;/td&gt;
&lt;td&gt;67.88&lt;/td&gt;
&lt;td&gt;-46.78&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Small&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;111.18&lt;/td&gt;
&lt;td&gt;44.67&lt;/td&gt;
&lt;td&gt;-66.51&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Large V2&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;89.67&lt;/td&gt;
&lt;td&gt;32.00&lt;/td&gt;
&lt;td&gt;-57.67&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;After completing my assignment, I decided to take it further and see how far I could push LoRA parameters to improve the model. From my research, the current wisdom is to put the alpha value always double the rank value (at least for LLMs). I could not find any supporting evidence for this for ASR, so I varied on that assumption too.&lt;/p&gt;
&lt;p&gt;Here are the results tuning &lt;code class=&quot;language-text&quot;&gt;whisper-large-v3&lt;/code&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Rank&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Alpha&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Dropout&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;WER %&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;CER %&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;512&lt;/td&gt;
&lt;td&gt;1024&lt;/td&gt;
&lt;td&gt;0.05&lt;/td&gt;
&lt;td&gt;38.98&lt;/td&gt;
&lt;td&gt;11.43&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1024&lt;/td&gt;
&lt;td&gt;1024&lt;/td&gt;
&lt;td&gt;0.05&lt;/td&gt;
&lt;td&gt;285.1&lt;/td&gt;
&lt;td&gt;210.98&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1024&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;0.05&lt;/td&gt;
&lt;td&gt;34.74&lt;/td&gt;
&lt;td&gt;9.70&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1024&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;0.1&lt;/td&gt;
&lt;td&gt;32.67&lt;/td&gt;
&lt;td&gt;8.93&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1024&lt;/td&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;0.1&lt;/td&gt;
&lt;td&gt;31.68&lt;/td&gt;
&lt;td&gt;8.61&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1024&lt;/td&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;0.2&lt;/td&gt;
&lt;td&gt;29.59&lt;/td&gt;
&lt;td&gt;7.86&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1024&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;0.3&lt;/td&gt;
&lt;td&gt;29.51&lt;/td&gt;
&lt;td&gt;7.94&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Increasing the trainable parameters was expected to bring an improvement (rank 1024 results in about 25% of the original model being retrained) but in the end increasing the dropout was what improved both WER and CER the most.&lt;/p&gt;
&lt;p&gt;Testing this on data outside of the test data results in something that looks phonetically like Maltese but is not correct. My current intutition is that a lack of data is what is stopping the model from improving further, which will be addressed in part 2.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[TP-Link Tapo C110 in Frigate]]></title><description><![CDATA[I recently purchased a TP-Link Tapo C110 for use with Frigate. Despite my best efforts and a lot of searching online, I was unable to find a…]]></description><link>https://simonam.dev/tapo-c110-frigate-config/</link><guid isPermaLink="false">https://simonam.dev/tapo-c110-frigate-config/</guid><pubDate>Wed, 10 Jul 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I recently purchased a &lt;a href=&quot;https://www.tp-link.com/us/home-networking/cloud-camera/tapo-c110/&quot;&gt;TP-Link Tapo C110&lt;/a&gt; for use with &lt;a href=&quot;https://frigate.video/&quot;&gt;Frigate&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Despite my best efforts and a lot of searching online, I was unable to find a single contained config that worked for &lt;code class=&quot;language-text&quot;&gt;mse&lt;/code&gt; so that I could also view audio as well as video and not just &lt;code class=&quot;language-text&quot;&gt;msjpeg&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here’s my simplified config to get it working. I’m running Frigate on a single host within a Docker container:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;mqtt:
  enabled: False

go2rtc:
  streams:
    tp_link_tapo_test: &quot;ffmpeg:rtsp://USERNAME:PASSWORd@IPADDRESS/stream1&quot;

cameras:
  tp_link_tapo_test:
    enabled: True
    ffmpeg:
      output_args:
        record: preset-record-generic-audio-copy
      inputs:
        - path: rtsp://127.0.0.1:8554/tp_link_tapo_test
          input_args: preset-rtsp-restream
          roles:
            - record
    live:
      stream_name: tp_link_tapo_test

  TP-Link_tapo_c110_0_sub:
    enabled: True
    ffmpeg:
      output_args:
        record: preset-record-generic-audio-copy
      inputs:
        - path: rtsp://USERNAME:PASSWORd@IPADDRESS/stream2
          input_args: preset-rtsp-restream
          roles:
            - record
    live:
      stream_name: TP-Link_tapo_c110_0_sub
    
    detect:
      enabled: False&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content:encoded></item><item><title><![CDATA[Re-using a gaming GPU for LLaMa 2]]></title><description><![CDATA[Introduction I’ve been experimenting with LLMs for a while now for various reasons. Two of the most useful outcomes have been in text…]]></description><link>https://simonam.dev/reusing-gaming-gpu-for-llama2/</link><guid isPermaLink="false">https://simonam.dev/reusing-gaming-gpu-for-llama2/</guid><pubDate>Thu, 02 Nov 2023 22:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;I’ve been experimenting with LLMs for a while now for various reasons.&lt;/p&gt;
&lt;p&gt;Two of the most useful outcomes have been in text extraction of Maltese road names from unstructured addresses and for code generation of boilerplate scripts (the “write me a script to use GitHub’s API to rename all master branches to main” kind of script).&lt;/p&gt;
&lt;h2&gt;OpenAI&lt;/h2&gt;
&lt;p&gt;GPT3.5 Turbo is a great model and very easy to get started with, however with any sort of volume and with a large prompt, the costs begin to add up.&lt;/p&gt;
&lt;p&gt;As a result, I began looking into self-hosting my own LLM to try and replace these tasks. Even if the response was slower at least it would be cheaper.&lt;/p&gt;
&lt;h2&gt;LLaMa&lt;/h2&gt;
&lt;p&gt;One of OpenAI’s biggest challengers is Meta with their &lt;a href=&quot;https://ai.meta.com/llama/&quot;&gt;LLaMa&lt;/a&gt; model. With the highly permissive licence of LLaMa 2 and the great work done in running these models locally (projects such as &lt;a href=&quot;https://github.com/ggerganov/llama.cpp&quot;&gt;llama.cpp&lt;/a&gt;), it is now very easy to get started with self-hosting.&lt;/p&gt;
&lt;p&gt;If however you have structured your project around OpenAI and their models, it would be much easier to somehow package these models in an API that mimics OpenAI’s API structure and responses. This makes it very easy to swap for a locally hosted model without having to change a lot of code.&lt;/p&gt;
&lt;h2&gt;Performance&lt;/h2&gt;
&lt;p&gt;One of OpenAI’s biggest benefits is the performance of the models. While some queries can take a large time to respond, in my experience most of them resolve within two seconds or less.&lt;/p&gt;
&lt;p&gt;When self-hosting, especially if you are running on a CPU, performance leaves much to be desired. You can make improvements by using quantized models but you risk missing out on performance and cross-testing multiple quantized versions is a time-prohibitive one, especially for side projects.&lt;/p&gt;
&lt;p&gt;Luckily, being a gamer, I have more GPUs than I need in my life and these models run much faster on GPUs.&lt;/p&gt;
&lt;p&gt;This blog post covers the steps I went through to self-hosting LLaMa 2 on a gaming PC that I run headlessly in my home.&lt;/p&gt;
&lt;h1&gt;Steps&lt;/h1&gt;
&lt;h2&gt;1: Put together the correct hardware&lt;/h2&gt;
&lt;p&gt;While this might seem like an obvious step, you should make sure the hardware you have is compatible with your aim. To keep this simple, the easiest way right now is to ensure you have an NVIDIA GPU with at least 6GB of VRAM that is CUDA compatible. &lt;a href=&quot;https://developer.nvidia.com/cuda-gpus&quot;&gt;This list&lt;/a&gt; can help.&lt;/p&gt;
&lt;p&gt;In my case, the RTX 2060 has compute capability 7.5.&lt;/p&gt;
&lt;h2&gt;2: Install the OS&lt;/h2&gt;
&lt;p&gt;I installed Ubuntu 22.04 LTS. In general its a good idea to stick to the latest LTS image, it ends in less surprises and is more likely to have matured in terms of installation guides.&lt;/p&gt;
&lt;h2&gt;3: Install the required NVIDIA drivers/tools&lt;/h2&gt;
&lt;p&gt;I have used &lt;a href=&quot;https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html&quot;&gt;this guide&lt;/a&gt; from NVIDIA multiple times to the letter and have never regretted it.&lt;/p&gt;
&lt;p&gt;You will also need to install the latest versions of &lt;code class=&quot;language-text&quot;&gt;docker&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;docker compose&lt;/code&gt; and the &lt;a href=&quot;https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html&quot;&gt;NVIDIA Container Runtime&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: This guide assumes you want to run things in a dockerised fashion. You can just as easily do it withuot docker and that’s a valid choice for many use cases. In my case, I followed this way as down the line I plan to run it in a Kubernetes cluster.&lt;/p&gt;
&lt;h2&gt;4: Confirm all is running well&lt;/h2&gt;
&lt;p&gt;After finishing installing the drivers, &lt;strong&gt;reboot&lt;/strong&gt;. If the machine boots up normally, congratulations! It isn’t the first time that Ubuntu + NVIDIA has broken for me if I look at it funny.&lt;/p&gt;
&lt;p&gt;You can also run &lt;code class=&quot;language-text&quot;&gt;nvidia-smi&lt;/code&gt; to confirm that the GPU is recognised correctly. This is the output I get from mine:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/26108b6e1740cdef4464e4250b4bec74/0ad97/nvidia-smi.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 57.432432432432435%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB6klEQVQoz2VS2XLiQAyc110ghDNgIE7CYY4Y38Y3YJKw1O5W5f9/plMtB/ZhH1QzI42kbrVUPA0QTX34EwvJLMR+HODXIkehufDuDFg/pv+Z/XN2O2lObX57q/3ER/kYohi6OD6yoC/Fgvs1soGN9MFGPnSQ9i0Umod0YGPX3iAfusgGDtKBg6hrii9sraH2eoCjvsP7NEHUeZUCpb7D6TnCxyyTe/m0Q/pgIe5t8fYc4zD2xXeeZzg9RSj1UPKKkQeVjz15HPVQEnadVxwmodD+mGeCknSImAjJgk0OkwBvL7EgvCz3+L0+SiOVjVwpcFkWUkDmN/LE2J3oP50z/poniZ3ZaJYi7pqIe6aAIGXmErGKBxaS3lZmx2DSr6gRGVHwTvr8XN1jeVMIzsxvLuE2DImfFxmU2zRgfyvk1hdw6ovqrM3h1OeScFWXvus/joBb4N0tb//D9gYq0xyhRjR+c3Uryu5BcyW+4H5VqdjeyF2afTesiq8kRlFVOrSRDR0RJddcSXRqC6F3pU4fV4Rz5bxYzG1UqILWGn82pQjFPyrpb5F0TNnF95dEdq5ajwgS628FPYXg6nALRIzuP0Go/MUoZKWUq5mgBRMLwdiWNaD8pFzNyEDY2kgBn/NqVDMWaxi3k3RZ/AtoKkm0GbUzxAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;nvidia-smi output&quot;
        title=&quot;nvidia-smi output&quot;
        src=&quot;/static/26108b6e1740cdef4464e4250b4bec74/fcda8/nvidia-smi.png&quot;
        srcset=&quot;/static/26108b6e1740cdef4464e4250b4bec74/12f09/nvidia-smi.png 148w,
/static/26108b6e1740cdef4464e4250b4bec74/e4a3f/nvidia-smi.png 295w,
/static/26108b6e1740cdef4464e4250b4bec74/fcda8/nvidia-smi.png 590w,
/static/26108b6e1740cdef4464e4250b4bec74/0ad97/nvidia-smi.png 717w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;5: Download the model from huggingface&lt;/h2&gt;
&lt;p&gt;Go to &lt;a href=&quot;https://huggingface.co&quot;&gt;https://huggingface.co&lt;/a&gt; and find a GGUF version of LLaMa-2-7B-Chat.&lt;/p&gt;
&lt;p&gt;Let’s break that down:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;huggingface is the premier website to find ML models&lt;/li&gt;
&lt;li&gt;GGUF is the format used by llama.cpp which is the library we will use to run the model&lt;/li&gt;
&lt;li&gt;LLaMa 2 and not 1 because the licencing of the first version was for research only&lt;/li&gt;
&lt;li&gt;7B stands for the number of parameters, in this case 7 Billion. We’re opting for the smallest one to get things started&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;-Chat&lt;/code&gt; is the chat dialogue optimised model. You can get the non-chat model later to try it out and find the difference.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://huggingface.co/TheBloke/Llama-2-7b-Chat-GGUF&quot;&gt;Here&lt;/a&gt; is a working link at time of writing.&lt;/p&gt;
&lt;h2&gt;6: Clone the repository for llama-cpp-python&lt;/h2&gt;
&lt;p&gt;Clone the following repository: &lt;a href=&quot;https://github.com/abetlen/llama-cpp-python&quot;&gt;abetlen/llama-cpp-python&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This will provide us with the API server.&lt;/p&gt;
&lt;h2&gt;7: Build the cuda_simple image&lt;/h2&gt;
&lt;p&gt;Go to &lt;code class=&quot;language-text&quot;&gt;docker/cuda_simple&lt;/code&gt; and build the Docker image using &lt;code class=&quot;language-text&quot;&gt;docker build -t cuda_simple .&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: You &lt;em&gt;must&lt;/em&gt; edit the &lt;code class=&quot;language-text&quot;&gt;CUDA_IMAGE&lt;/code&gt; argument at the top of this Dockerfile to match the version of CUDA on your machine.&lt;/p&gt;
&lt;h2&gt;8: Bring up the webserver&lt;/h2&gt;
&lt;p&gt;For my original usecase (an LLM capable of answering questions about Maltese Law), I used a docker compose file to bring up multiple services. Here is the minimal parts I needed to bring up just the LLM API:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;version: &apos;3.5&apos;

services:
  llama-api:
    image: cuda_simple:latest
    ports:
      - 8000:8000
    cap_add:
      - SYS_RESOURCE
    environment:
      USE_MLOCK: 0
      MODEL: /models/7B/llama-2-7b-chat.Q4_0.gguf
      N_GPU_LAYERS: 35
      MAX_TOKENS: 1024
      CONTEXT_WINDOWS: 2048
    volumes:
      - /home/simon/models:/models
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu]
    restart: unless-stopped&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Breaking it down:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;llama-2-7b-chat.Q4_0.gguf&lt;/code&gt;: this is the filename of the 4 bit quantized model I downloaded from huggingface. This needs to match the filename that you downloaded. A less quantized (meaning 5 bit, 6 bit, 8 bit, etc) version will take more RAM to run but may perform better.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;N_GPU_LAYERS&lt;/code&gt;: this specifies the nubmer of layers to run on the GPU. Ideally you run all of them to get the best performance, however your VRAM is another limiting factor here.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;/home/simon/models&lt;/code&gt;: this is the directory where I store models. The full path on the host in my case would be &lt;code class=&quot;language-text&quot;&gt;/home/simon/models/7B/llama-2-7b-chat.Q4_0.gguf&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To bring it up, you can do &lt;code class=&quot;language-text&quot;&gt;docker-compose up&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;If all works as expected, you should see the following logs:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/74886f9ff62b0e9a9baf5011165ab374/1b1d5/llama-api-logs.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 47.2972972972973%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAABx0lEQVQoz01RaXPaUAz0t14QCKQkEGzAgI3xfd9HjEmApjTT6f//K9uROsz0w47ekzSr1UpwnwO4Mx+uFMEdG0gnNsqpi2LqIn9yOJYzD+l3ixHc7ZA8mAiHGryeAq//H3oKBEcM4cwjOKsU9tjAYZXiana4aA3e9RY/9g1+WR06OeN3OfXwusmRP9qwPstwvm4Y9pc1Q7BnPhhSBPNeR7tMcNnVOO9qnJQSnZwy8du2wEmtOPdCvZ9WTHgDkRGxYC0SMDYFjJGO47bAb/+ED+cVH84RV6vDn/DCiq/mgf/v+guO6wztIkY8NpCMDbjftkwseFIMV4zgyTnciY12leKnccBZrXnFi1YzASkk1RQJtPabUuKsVuzxzVchlyI06xy5FOOwLVDPA55ITTQ9m9ionn2Ewz2qmc+WUI08pEjrUz1/dDgK6SJGtSmQyRlSaljEXGzEEO0yxmGZ4ChnHOkwjRiwknik85Do/h/o+pQTLDGCOQ9hr3Powz1OWoN8YqORIvaRVzZaPgz5V7NaDcFgB//uBhV+X0VAKydPLqy+Cmeowe4pPM2j4mDHb5oasRqNraBcNrGQPphcI1vikYFgoKEWA/wF63MUF4KJ0VsAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;API logs&quot;
        title=&quot;API logs&quot;
        src=&quot;/static/74886f9ff62b0e9a9baf5011165ab374/fcda8/llama-api-logs.png&quot;
        srcset=&quot;/static/74886f9ff62b0e9a9baf5011165ab374/12f09/llama-api-logs.png 148w,
/static/74886f9ff62b0e9a9baf5011165ab374/e4a3f/llama-api-logs.png 295w,
/static/74886f9ff62b0e9a9baf5011165ab374/fcda8/llama-api-logs.png 590w,
/static/74886f9ff62b0e9a9baf5011165ab374/1b1d5/llama-api-logs.png 876w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Testing&lt;/h2&gt;
&lt;p&gt;You should be able to access the API docs at &lt;code class=&quot;language-text&quot;&gt;&amp;lt;host_ip_address:8000/docs&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/a473efb50ba6b519deb84963424c75d0/baaa6/api-docs.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 59.45945945945946%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAAB4klEQVQoz52Qy27TQBhG8/4SG1ZIqI/AAiFAIDZI0FK16YXSJsRJHceObxPbY49nHLupD7LTpkHqBkY6+rw6Ov4HbdvSvbauqHRJmkm0NhhTYaoKpUpkXpBlkqIoMcYQi4QgjHG9CF8OCYshj57B40ejJMv5nO/H55ycXjCezDg6HnLx84aJdcvhjyEnp5dYU5uz8yt+WzbOIsRNj/DzY9hqGPTbtmxauOP/XxfWsSu832yQSYZMJU3d/IuKXd7+L2dacTi64swaI2RGbso9NFKX5Lrst6g0ZV2h1qbH3NU7BvcPwpHyeeF85k00xElCrr05llgyDhZYkdfvyHf45dpY8ZJ5FjFbBdwmAYGRhFVOWMkn4bSMeO185X14iZcJptGSWezjSsGySPDyFb5KSTeGpClZ7SFqhVhv2QknKuSl/YW3wRmeFH3FTPgspMDNVyxkjKcSoirfst6n2PFX4cHiGx/DK5xVxMibMwncvrBjkcV9YV/VFxXPshNapc8r9xPvohOcpLuP31eOA4ep8HFk3N/tdhVgZ1Ff1t0s7HaPJ2HScHCh+GBp7DDleh5ix5JZkOKmJaGqCYr1AzVR2TzLYOtrEWXLoQM3UYtUa0Smyc0dUjeo9T26AV3ztHX7LH8A5VKOh8slyQUAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;API docs&quot;
        title=&quot;API docs&quot;
        src=&quot;/static/a473efb50ba6b519deb84963424c75d0/fcda8/api-docs.png&quot;
        srcset=&quot;/static/a473efb50ba6b519deb84963424c75d0/12f09/api-docs.png 148w,
/static/a473efb50ba6b519deb84963424c75d0/e4a3f/api-docs.png 295w,
/static/a473efb50ba6b519deb84963424c75d0/fcda8/api-docs.png 590w,
/static/a473efb50ba6b519deb84963424c75d0/baaa6/api-docs.png 813w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The docs will also let you try out a query using the &lt;code class=&quot;language-text&quot;&gt;Try it out&lt;/code&gt; button. Here you can see the response for &lt;code class=&quot;language-text&quot;&gt;&quot;The capital of France is:&quot;&lt;/code&gt; and the corresponding increase in GPU usage as the LLM processes a response.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/678fce951eed84fcc5be6c5615104969/8b84a/try-it-out.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 66.89189189189189%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAACnUlEQVQ4y4WR20uTcRjHdxMWBNJFJ4TSLefmDu+cTmMptFla6aCjHdzsZNZNdVXYiVHeSv9DEARSYRmRliiozSi1wyJNQiszdYHVDm5z7/uJ932N3I098OH5Ps/Fh+fHTwMQScYJzc4Sic8RnosRjkaULPM7FmVuPklSSpEUUyTElDLLxBf63xybT6CRhXKQl/OSuCSyUGTp0kiSxFzq/7J5UVRkvyJh+l4E6O1/zsuhAQIvXyj0BPp4PzqsXhhNxQmn4kSW4HcyRgroDfSjX6vHmmWkWGvDkmVE2GBFl7mR86fPqcLpaIy7QyFaBkK0DIaU/OCtPM/QMjjDnVczPH43RSgR5llXN6VZRWxelU9xphFHphHnagFLhparZxtVofwpv5Jx5QIZ+WnSIsSFvVyvh95QuFKPZVk2wnItQkYOBSt05GrW4D97WRUGPw7jb27i+s0m/M03uNJ0jYtXLqVx4XIjjf6rNNSfxrHeSsFqE/Y1MlYK19kwZeo5421Qhd19PdicNiwOMxaHBaPFgN6Qi96gx2DKY5NOhzZHizY7G12eEbPbo2Aqr8a87SDW8iPkbq2mxlevCgeCrzlcV0ut10etz4e3zof3aF06dT58x4+yf+8BnPYKttgrcBZVsqVgO6UF2yg0u2k4dV4Vjk9OcKf1Ph293XT199Le00Vb5xPaOtvTeNTZTmvHY24/vM/th/fS+q3Wu7R1P1WFPxNRxmenGRr9wIeJMUYmPzPybVzpHxehzN+/8Gl6YoGv//LUV8Z+TKKRRIk4IgMjQWoOH2T3IQ+eGg879+yi0rMDl8uFy+1OY6vblYar3E1pWRnHTp6QhSIJJALBQaqqqqiodFNSVoLD6cBeZEewWhEEYUlsNhv5xnz27NvLHzN/8UBxBsa2AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Example result&quot;
        title=&quot;Example result&quot;
        src=&quot;/static/678fce951eed84fcc5be6c5615104969/fcda8/try-it-out.png&quot;
        srcset=&quot;/static/678fce951eed84fcc5be6c5615104969/12f09/try-it-out.png 148w,
/static/678fce951eed84fcc5be6c5615104969/e4a3f/try-it-out.png 295w,
/static/678fce951eed84fcc5be6c5615104969/fcda8/try-it-out.png 590w,
/static/678fce951eed84fcc5be6c5615104969/efc66/try-it-out.png 885w,
/static/678fce951eed84fcc5be6c5615104969/c83ae/try-it-out.png 1180w,
/static/678fce951eed84fcc5be6c5615104969/8b84a/try-it-out.png 1531w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;The next steps for me are to try out the &lt;a href=&quot;https://huggingface.co/TheBloke/CodeLlama-7B-Python-GGUF/tree/main&quot;&gt;CodeLlama model&lt;/a&gt;, a model optimised to produce Python code and see if I can connect it easily with VS Code, providing a locally hosted version of something like GitHub Copilot without having to pay for the subscription cost.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Mixing datasets using symlinks]]></title><description><![CDATA[I recently had to fine-tune around 100 Computer Vision AI models for an assignment I’m completing as part of my Master’s Degree (the same…]]></description><link>https://simonam.dev/dataset-mixing-using-symlinks/</link><guid isPermaLink="false">https://simonam.dev/dataset-mixing-using-symlinks/</guid><pubDate>Sat, 01 Jul 2023 10:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I recently had to fine-tune around 100 Computer Vision AI models for an assignment I’m completing as part of my Master’s Degree (the same assignment as &lt;a href=&quot;opencv-vs-yolo-coordinates/&quot;&gt;this blogpost&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;The models were actually the same variants with the difference that they were being trained on different mixes of datasets (specifically, the &lt;a href=&quot;http://mi.eng.cam.ac.uk/research/projects/VideoRec/CamVid/&quot;&gt;CamVid&lt;/a&gt; dataset, &lt;a href=&quot;https://synthia-dataset.net/&quot;&gt;SYNTHIA&lt;/a&gt; dataset and the &lt;a href=&quot;https://download.visinf.tu-darmstadt.de/data/from_games/&quot;&gt;Playing for Data&lt;/a&gt; dataset).&lt;/p&gt;
&lt;p&gt;In the assignment I needed to generate mixes of the CamVid dataset and another dataset at intervals of 10% of the second dataset added, for example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CamVid + 10% SYNTHIA&lt;/li&gt;
&lt;li&gt;CamVid + 10% PFD&lt;/li&gt;
&lt;li&gt;CamVid + 20% SYNTHIA&lt;/li&gt;
&lt;li&gt;…etc&lt;/li&gt;
&lt;li&gt;CamVid + 100% SYNTHIA&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Given that the SYNTHIA and PFD datasets are quite large (in the region of 10,000s of images), creating a copy of each dataset was going to be prohibitively expensive, in terms of both time and storage.&lt;/p&gt;
&lt;p&gt;I considered overriding the way that the computer vision models I was using (specifically the YOLO family of models) loads data, however under the time constriants of the assignment this looked like a risky approach. The default way of feeding AI models, especially ones based on images, does tend heavily towards providing a directory of files and the library figures out how to use it.&lt;/p&gt;
&lt;p&gt;Here is where symlinks became invaluable.&lt;/p&gt;
&lt;p&gt;Symlinks or &lt;a href=&quot;https://en.wikipedia.org/wiki/Symbolic_link&quot;&gt;Symbolic Link&lt;/a&gt;s are files with the sole purpose of pointing towards another file. Deleting a symbolic link does not affect the file that it links to. Creating one on Linux systems is done using &lt;code class=&quot;language-text&quot;&gt;ln&lt;/code&gt; and on Windows using &lt;code class=&quot;language-text&quot;&gt;mklink&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;By leveraging some very simple python, I came up with two scripts which:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Read the file structures of the datasets and created text files for each dataset mixture, such as &lt;code class=&quot;language-text&quot;&gt;100_synthia_20_camvid.txt&lt;/code&gt;, indicating the dataset mix was all of SYNTHIA and 20% of Camvid. Each line in the text file contained a file path.&lt;/li&gt;
&lt;li&gt;Read each text file and for each line, modify the source file path to create a target file path (so if the path was &lt;code class=&quot;language-text&quot;&gt;/foo/bar.png&lt;/code&gt; in &lt;code class=&quot;language-text&quot;&gt;100_synthia_20_camvid.txt&lt;/code&gt; it would set the target as &lt;code class=&quot;language-text&quot;&gt;./datasets/100_synthia_20_camvid/foo/bar.png&lt;/code&gt;) and create a symlink from the source to the target. If the directories did not exist, it would create them.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Using this method I managed to create around 30 different datasets that, despite being made up of only symlinks, occupied around 6GB of disk space. This is a massive improvement over the 82.5GB of the source datasets. Had I copied each file, it would have easily resulted in the single digit terabytes of storage required and been time prohibitive to generate.&lt;/p&gt;
&lt;p&gt;For completeness, you can find the python scripts here: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://gist.github.com/simonamdev/7df8875642d9ab80b237b61b10de3666&quot;&gt;generate_mixed_datasets.py&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://gist.github.com/simonamdev/8ff41692d1939ee08481803b6e94b96e&quot;&gt;setup_dataset_links.py&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[OpenCV vs YOLO Co-ordinates]]></title><description><![CDATA[I recently had to fine-tune around 100 Computer Vision AI models for an assignment I’m completing as part of my Master’s Degree. OpenCV, to…]]></description><link>https://simonam.dev/opencv-vs-yolo-coordinates/</link><guid isPermaLink="false">https://simonam.dev/opencv-vs-yolo-coordinates/</guid><pubDate>Tue, 13 Jun 2023 19:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I recently had to fine-tune around 100 Computer Vision AI models for an assignment I’m completing as part of my Master’s Degree.&lt;/p&gt;
&lt;p&gt;OpenCV, to draw a bounding box on an image, expects the top left co-ordinate and the width and height of the box. My annotations however, were in the form of image colour maps, like so (image taken from the CamVid dataset):&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/01ce1aab472309428839f090025946d6/d9199/camvid-label.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAIAAABr+ngCAAAACXBIWXMAAAABAAAAAQE4IvRAAAAC40lEQVQoz22Q/UtbVxjHv+fG+JJUEm3xrYpLxLarRXQ2iW8xvsZpX0ZBhzKYA39wQ9iQ1dg0997znJt7c5O0yTYqFiZr5y9jjDGQ/dRqmjddV1n/p+1qhQZ2eDjwHL6f5/s9Dwg4Kw5otdWpFhd3N5CqkhDEuXULQW43+XyUSlnvm5u0uEiSRDgnz+B4bXWyrYEDtL5Oum7BmkbRqCXt6iIiMk1rCvD/cLbjIgfEnduWj6KQYdDamiVoa3s3zuOxWsYqYAJEjX2jqz0KJMIh8ShF8im8uioA0dQsPl+hzs4zWyHZBGPvkYDKWMPI8F2XOzMbpscZkmUr5/KyAsinZYWSbMSYciqugBXG7MFgqKnNDH4gsjKXrU/yhYXt7qEffZ887Q3rdRdkK7P0g3fg92WzAibGrkwHZxvbeRD6d5OabMik7NxPHonjcvKkbJ5sXxt90j24v7pTih+/ybytWJhWZdv5uE921NNdh8g0bdEDM33j0PityI/zPJdXcweR/WL8r3LiTUkr8tCzioWR3fZTqOfTVtf1XsdQ3/DYUPqLifk/vt4/ipfzar5AhYIoFUXhtVnaGtz1I3ZuyyQFTLguPF8avddjb2yyB7A5JRkfQY/4d/9OHhWoWKBCOV56+fAw4t8dgTohCaiwEZjBWBLQL7dq65Hu9m+aHd9OS3zSJoKQP7uczSmvXkRfHMQOXqmH2uSeD7EJSYyDkALiwFfwzmCp07bd1/9nde2J37U3wZRRiBHweaeemntkzOiZ2xl9OjHn1ELg44yHoCKMJQ+yEnLAW4bXYKWw9+etGzTnoIVL/F6jesupRvtMHjDVYU0JJMJ2MYb4FMwZpAH8A5wARZvFl/pbfzGGUltXv48MGOnZRDqcigVMbdzQAxnNl1VC8opHWezY6K9a9mIGEnIScozlLbLlV2PEEIF0rPfxeuDLsHd2yjPtb7/Z03z9SsO1LueHl5wX3XX1dTVVjOG/8y8e+RvClT1jGgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Camvid Label&quot;
        title=&quot;Camvid Label&quot;
        src=&quot;/static/01ce1aab472309428839f090025946d6/fcda8/camvid-label.png&quot;
        srcset=&quot;/static/01ce1aab472309428839f090025946d6/12f09/camvid-label.png 148w,
/static/01ce1aab472309428839f090025946d6/e4a3f/camvid-label.png 295w,
/static/01ce1aab472309428839f090025946d6/fcda8/camvid-label.png 590w,
/static/01ce1aab472309428839f090025946d6/efc66/camvid-label.png 885w,
/static/01ce1aab472309428839f090025946d6/d9199/camvid-label.png 960w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Using some OpenCV code, I converted these segment maps to bounding boxes, generating an image like so instead (taking the cars only as an example):&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/1866093531bfbeb30ca1e67be2f01cef/d9199/camvid-result.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAIAAABr+ngCAAAACXBIWXMAAA7EAAAOxAGVKw4bAAADPUlEQVQozy2S22scZRjG56K7c/i++c7fzHyzc9zdmZ3d7a6bbDRJk6ympoQqpdgqVrBSKq0UJTUtXggqxjYqLQRsL6JWjQewtBeeqKKUCCL1QhSx8dALKZRS6bX/QGWj8N7++D0Pz6vZto0xCpI4bRaU0tkdc08uHP3++q0fb9y89PPvX29cv3Lj9thg++JLL/xz587VWze/3bj69iefdSYm40ZDC5MEIcQYV2GICL73/u17Htt3/vL6xu2/v/n1zy9/ufbdtb/qrc7eAwfWf/vj4/UrOx/ZT6TcOj4ZNwqNUCodhxDCGEME39Uf3bV3z/FXls9e/GLlowsffLV+8s01FSUz8zs//eGntUuXp+YeCPOi2R9LipZWKpUwIVGacs4xxtVGMb/rwcePPP3aOx+eWH13aXXtqeeX/DgZ2Tb14sqZidm5Si1rj0/Wu920WWimaVqmBSB0XNdTCiHa7nYPLh4/uryycOL0c6fOPHrkWan8ShwleeFG1bCe1TqdtN1Jmi3NMAwAoV4uAwBcpWgahbX6Q/ufeOblkwtLy8dePf3wwcNBkiZFM+uNeVHVjdN6r593e0nR0QzdsBEa6g3DNAzayvJer9vv7zt86PW3Vt94/71DxxaDaibDuhPWwloja4+2+1NpFEtMtHK5jDA2LWsYAQBgWCoI0kZ+92D63OcXT507Ozo97SU1L8mcKI1rjZYX+ogqYLfCVNN1HUB7KDUMy7KYEJDgiIuGCmanZmYHg1x6WFZtL4dORojrQWhimo2Mjw7mh51Nc4iam+bNwWhCmSs8rqo28zPHl05A/ZT4NSG8kElRifORe5K0PoR1w9B1fdMMMKZEyFg4XAbMqzA/jp0KHb4RJ1JSTAMqEHe46ys30P6vCiDGhAsppedFaeooRiTCDGLqI4IANAEEiDDCFETAxgBiCmzNthGhjEtXuEqqinR94QcRk8C0dAuULOBBpPxI+BFzA8et+JiZiFgIY4g0yjjj8j9SuD5lwmYysLGv67xU4lu2RHqJM8FVwFXIhVuFyDEtx7CUbmiEcsol2zzCBBUOZoJQIYUXJkW+daI9vqM9s7u5bXetdx/mnm1BAm1GqCOdfwHFRKRHjykEeQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Camvid Result&quot;
        title=&quot;Camvid Result&quot;
        src=&quot;/static/1866093531bfbeb30ca1e67be2f01cef/fcda8/camvid-result.png&quot;
        srcset=&quot;/static/1866093531bfbeb30ca1e67be2f01cef/12f09/camvid-result.png 148w,
/static/1866093531bfbeb30ca1e67be2f01cef/e4a3f/camvid-result.png 295w,
/static/1866093531bfbeb30ca1e67be2f01cef/fcda8/camvid-result.png 590w,
/static/1866093531bfbeb30ca1e67be2f01cef/efc66/camvid-result.png 885w,
/static/1866093531bfbeb30ca1e67be2f01cef/d9199/camvid-result.png 960w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;All looked great up to this point.&lt;/p&gt;
&lt;p&gt;Unfortunately I was getting pretty bad results from my AI models: we’re talking 4% mAP50. This is the mean average precision of the top 50% of classes that the model was attempting to draw bounding boxes around. Usually in detection models mAP50 isn’t expected to be very high however this result was abysmally low.&lt;/p&gt;
&lt;p&gt;Only when I removed 10 of the 11 classes to attempt to train a detector on a single class did I realise that all of the labels were offset 50% to the left and 50% upwards.&lt;/p&gt;
&lt;p&gt;This immediately triggered by spidey-senses: it was far too uniform to be a fluke. I eventually realised an important difference between OpenCV and the YOLO text annotation format.&lt;/p&gt;
&lt;p&gt;To validate the bounding boxes were correctly I used OpenCV to visually confirm they were correct… after using OpenCV to generate those same bounding boxes. It turns out that OpenCV both expects and returns the following for bounding boxes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Width and Height of the bounding box&lt;/li&gt;
&lt;li&gt;The top left co-ordinate (x, y)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In constrast to this, the &lt;a href=&quot;https://docs.ultralytics.com/datasets/detect/&quot;&gt;YOLO text annotation format&lt;/a&gt; expects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Width and Height of the bound box&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;centre&lt;/strong&gt; co-ordinate (x, y)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The fact that the bounding boxes were all offset upwards and to the left is because to convert from centre to top left, one needs to perform the following calculation: &lt;code class=&quot;language-text&quot;&gt;top_left_x = centre_x - (width / 2)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Once I solved this issue and regenerated all of my annotations in the correct format, the mAP50 of my models jumped from 4% to 40%.&lt;/p&gt;
&lt;p&gt;Lesson learned :)&lt;/p&gt;</content:encoded></item><item><title><![CDATA[The Petoi Bittle Robot Dog - Part 3]]></title><description><![CDATA[Introduction This is part 3 of 3 of my review of the Petoi Bittle Robot Dog kit. This kit was provided to me for free by Petoi in return for…]]></description><link>https://simonam.dev/petoi-bittle-part-3/</link><guid isPermaLink="false">https://simonam.dev/petoi-bittle-part-3/</guid><pubDate>Thu, 11 May 2023 12:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;This is part 3 of 3 of my review of the &lt;a href=&quot;https://www.petoi.com/products/petoi-bittle-robot-dog?variant=44053953478968&amp;#x26;utm_source=Simon&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=bittle202304&quot;&gt;Petoi Bittle Robot Dog kit&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This kit was provided to me for free by &lt;a href=&quot;https://www.petoi.com/&quot;&gt;Petoi&lt;/a&gt; in return for a review.
I am thankful for the opportunity and had a blast reviewing this product.&lt;/p&gt;
&lt;p&gt;In a first for my blog, read till the end to find a special discount code just for my readers: 7% off your entire order until May 14th 2023!&lt;/p&gt;
&lt;h1&gt;Part 3 of 3&lt;/h1&gt;
&lt;p&gt;I’m structuring these blog posts as follows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;In &lt;a href=&quot;/petoi-bittle-part-1&quot;&gt;part 1&lt;/a&gt;, I focused on the kit, its materials and the build process.&lt;/li&gt;
&lt;li&gt;In &lt;a href=&quot;/petoi-bittle-part-2&quot;&gt;part 2&lt;/a&gt;, I will focus on the Python API and explore seeing how to make the dog do a trick from scratch.&lt;/li&gt;
&lt;li&gt;In part 3 (this post), I will install the &lt;a href=&quot;https://www.petoi.com/collections/all/products/intelligent-camera-module?utm_source=Simon&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=camera202304&quot;&gt;Intelligent Camera Module&lt;/a&gt; and see how far I can take its hardware.&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;Who has eyes to see&lt;/h1&gt;
&lt;p&gt;The kit I received included the &lt;a href=&quot;https://docs.petoi.com/extensible-modules/mu-camera&quot;&gt;MU Vision Sensor&lt;/a&gt;. Given my current study unit for my MSc. in AI is related to computer vision, I thought this would be a great opportunity to stretch my skills.&lt;/p&gt;
&lt;p&gt;Following the docs, I updated the firmware of the Bittle to be able to support the camera. The only issue I had here was that I had to update the following line in the code: &lt;code class=&quot;language-text&quot;&gt;pathBoardVersion = &quot;NyBoard_V1&quot;&lt;/code&gt; for the firmware upload to complete.&lt;/p&gt;
&lt;p&gt;Once the firmware was updated, I plugged the camera in and mounted it on the Bittle itself.&lt;/p&gt;
&lt;p&gt;Previously I had noted that the Bittle is highly durable. This is still the case, however the camera module is mounted outside of the frame of the dog, using a rubber band to attach it to the plastic bone that comes with it and the bone is held in place by the Bittle’s mouth. Without the camera module I was being pretty liberal in how I treat the Bittle, however with the camera module hanging out front, mostly unprotected, I began being much more careful with it. If there was a protective enclosure I could purchase or 3D print to provide some protection I definitely would consider using one.&lt;/p&gt;
&lt;p&gt;Turns out, there is one available, they’re just not listed in the docs. A comment in &lt;code class=&quot;language-text&quot;&gt;camera.h&lt;/code&gt; within OpenCat points to the &lt;a href=&quot;https://github.com/PetoiCamp/NonCodeFiles/blob/master/stl/MuIntelligentCamera_mount.stl&quot;&gt;following link&lt;/a&gt; which is an STL to 3D print a mount for the camera that does not require use of the plastic bone.&lt;/p&gt;
&lt;p&gt;Upon turning the Bittle on, despite expecting it to do what it did, I got freaked out! The Bittle instantly recognised me in front of it and started tracking my movements.&lt;/p&gt;
&lt;p&gt;Its a bit hard to appreciate the fluidity of the movements in the quality of this gif. I recommend checking out the following &lt;a href=&quot;https://www.youtube.com/watch?v=4Qnf1j6eyUA&quot;&gt;YouTube video&lt;/a&gt; demonstrating its movement - it is very accurate to my experience!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/50c9c91bb26b26c436ce1cb069a8e4d8/bittle-with-eyes.gif&quot; alt=&quot;Bittle with Eyes&quot;&gt;&lt;/p&gt;
&lt;h1&gt;Trying to make it work&lt;/h1&gt;
&lt;p&gt;The default behaviour of tracking humans or tennis balls is amusing to see. I wanted to see how easy it would be to try and run an ML model on the image stream coming through the camera.&lt;/p&gt;
&lt;p&gt;Here I realised that there are some limitations if you want to continue to operate at a Python level.&lt;/p&gt;
&lt;p&gt;I was expecting being able to install a pypi package and begin to print at least some output from the camera at the console, that would then allow me to try out approaches such as running a very light ML model on the ESP8266 that adjusted the behaviour of the Bittle on the fly, or streaming the images over HTTP and controlling the Bittle remotely, allowing me to run larger ML models on my machine.&lt;/p&gt;
&lt;p&gt;Unfortunately I was unable to find an easy way forward with this approach. In fact, &lt;a href=&quot;https://www.petoi.camp/forum/software/please-tell-me-how-to-connect-the-image-transmission-of-mu-vision-sensor-3-through-python&quot;&gt;a question on the forums&lt;/a&gt; indicates there is no way of doing it this way at present.&lt;/p&gt;
&lt;p&gt;Having said that, it looks like it is possible with more time and effort than I could dedicate. I read through the Arduino sketch available and it is likely not too hard to change it to send these values over i2c to the micro-controller. There’s also &lt;a href=&quot;https://www.yuque.com/tinkergen-help-en/bittle_course/sensor_pack_lesson_5&quot;&gt;a lesson in the Tinkergen Bittle Course&lt;/a&gt; covering how to use scratch to make the Bittle react to hand gestures.&lt;/p&gt;
&lt;p&gt;Unable to proceed in a practical way, I thought it would be useful to turn this into a system design exercise, theorising the methods I could potentially leverage the camera to adjust the Bittle’s behaviour and the various trade-offs each setup would have.&lt;/p&gt;
&lt;h1&gt;Computing with one eye and four legs&lt;/h1&gt;
&lt;p&gt;Here’s the possible ways I would have approached this problem:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Running everything locally on the micro-controller&lt;/li&gt;
&lt;li&gt;Running everything locally on a Raspberry Pi&lt;/li&gt;
&lt;li&gt;Streaming the image data to another machine through the micro-controller/Raspberry Pi&lt;/li&gt;
&lt;li&gt;Streaming the image data directly from the camera to another machine and controlling the Bittle from that machine&lt;/li&gt;
&lt;li&gt;A mix of the above&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Running everything locally on the micro-controller&lt;/h2&gt;
&lt;p&gt;This is the setup that I tried to implement above. It is the simplest setup possible whereby the camera is simply feeding what it receives to the controller and the controller decides its output based on that.&lt;/p&gt;
&lt;p&gt;There are two main constraints in this kind of setup: CPU clock speed and RAM.&lt;/p&gt;
&lt;p&gt;The micro-controller provided looks like an &lt;a href=&quot;https://www.espressif.com/sites/default/files/documentation/esp-wroom-02u_esp-wroom-02d_datasheet_en.pdf&quot;&gt;ESP wroom o2d&lt;/a&gt; which, according to the dataset, reaches a maximum clock speed of 160MHz (only 80% of which is usable) with &amp;#x3C;50kB of RAM available.&lt;/p&gt;
&lt;p&gt;This is a world away from the kind of resources that are available on SoCs, Laptops and Servers. One might be able to fit a very simple neural network within these constraints, however the behaviour it would be able to exhibit is exceedingly limited.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 500px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/ad39ea86abfd369b0f6abb8f49f23bec/41099/setup-1.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAUABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAMCBP/EABUBAQEAAAAAAAAAAAAAAAAAAAAB/9oADAMBAAIQAxAAAAG/b0zMLDcSLiv/xAAaEAADAQADAAAAAAAAAAAAAAABAgMABBAS/9oACAEBAAEFArcii1rcSyWDggZ5rTBFUZF89f/EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQMBAT8BH//EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQIBAT8BH//EAB0QAAIBBAMAAAAAAAAAAAAAAAABEQIQEjEhUXH/2gAIAQEABj8CaWkKaanPRONS9NCyRCVuJt//xAAbEAEAAwEAAwAAAAAAAAAAAAABABEhQRBRYf/aAAgBAQABPyGx+z1FyyeZW26qEcON+RLTUyAI6MNoqHL8f//aAAwDAQACAAMAAAAQ0wCA/8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAwEBPxAf/8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAgEBPxAf/8QAHRABAQADAAIDAAAAAAAAAAAAAREAITEQQVFhsf/aAAgBAQABPxDgBmjOE9ZYYART9xiRNDF5vv3i2u7NDU5cgROi3Q/GRCH0LhBPEmTrKVR3vj//2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Setup One&quot;
        title=&quot;Setup One&quot;
        src=&quot;/static/ad39ea86abfd369b0f6abb8f49f23bec/41099/setup-1.jpg&quot;
        srcset=&quot;/static/ad39ea86abfd369b0f6abb8f49f23bec/a80bd/setup-1.jpg 148w,
/static/ad39ea86abfd369b0f6abb8f49f23bec/1c91a/setup-1.jpg 295w,
/static/ad39ea86abfd369b0f6abb8f49f23bec/41099/setup-1.jpg 500w&quot;
        sizes=&quot;(max-width: 500px) 100vw, 500px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Running everything locally on a Raspberry Pi&lt;/h2&gt;
&lt;p&gt;The Bittle has the option to be run from a &lt;a href=&quot;https://docs.petoi.com/apis/raspberry-pi-serial-port-as-an-interface&quot;&gt;Raspberry Pi&lt;/a&gt;. This would bring it closer to the Freenove Robot Dog. Once you have an ARM CPU, an SD card, Megabytes worth of memory and a standard Linux OS, then running ML models become more viable.&lt;/p&gt;
&lt;p&gt;There is however more advanced work involved in getting this to work:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You need to solder a socket to the NyBoard&lt;/li&gt;
&lt;li&gt;You will be unable to install the back cover of the Bittle, which makes the RPi susceptible to damage if the Bittle topples itself over&lt;/li&gt;
&lt;li&gt;Petoi provide a file for a 3D printable Pi standoff to help keep it steady&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 500px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/b0bf97212e1639c0792a9b59eb8a7823/41099/setup-2.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAUABQDASIAAhEBAxEB/8QAGAABAQEBAQAAAAAAAAAAAAAAAAMCAQX/xAAVAQEBAAAAAAAAAAAAAAAAAAAAAf/aAAwDAQACEAMQAAABv6VORltWokXFf//EABsQAAICAwEAAAAAAAAAAAAAAAECAAMEEBIR/9oACAEBAAEFArsixbWs5gf0ECEAzkaRedf/xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAEDAQE/AR//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAECAQE/AR//xAAZEAACAwEAAAAAAAAAAAAAAAABEAARMSH/2gAIAQEABj8CIGBXMmPlr//EABwQAAMBAAIDAAAAAAAAAAAAAAABESEQQVFhcf/aAAgBAQABPyGj9PAiarRciweTxvoaq19IdD1MW7TQurx//9oADAMBAAIAAwAAABCLAID/xAAVEQEBAAAAAAAAAAAAAAAAAAABIP/aAAgBAwEBPxBj/8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAgEBPxAf/8QAGxABAAMBAAMAAAAAAAAAAAAAAQARMSEQQaH/2gAIAQEAAT8QwAzhrCvUOtIDx2AupnYtrvTYbTLlfQq2kM30wgnEqV1lVrG74//Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Setup Two&quot;
        title=&quot;Setup Two&quot;
        src=&quot;/static/b0bf97212e1639c0792a9b59eb8a7823/41099/setup-2.jpg&quot;
        srcset=&quot;/static/b0bf97212e1639c0792a9b59eb8a7823/a80bd/setup-2.jpg 148w,
/static/b0bf97212e1639c0792a9b59eb8a7823/1c91a/setup-2.jpg 295w,
/static/b0bf97212e1639c0792a9b59eb8a7823/41099/setup-2.jpg 500w&quot;
        sizes=&quot;(max-width: 500px) 100vw, 500px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Streaming to another machine via the onboard controller&lt;/h2&gt;
&lt;p&gt;In this scenario, we would take the output from the camera and, using the micro-controller, stream the images to another machine. The machine would then provide instructions to the Bittle over a protocol such as WebSockets&lt;/p&gt;
&lt;p&gt;The advantages of this approach are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We are no longer limited by the compute of the Bittle&lt;/li&gt;
&lt;li&gt;We can run any size ML model we want on the other machine&lt;/li&gt;
&lt;li&gt;We can potentially control multiple Bittles at once&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The disadvantages of this approach are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We need to maintain the wirless connection (whether Wi-Fi or Bluetooth) with the other machine at all times to provide the upgraded behaviour&lt;/li&gt;
&lt;li&gt;If we lose the wireless connection then the Bittle will either freeze or continue whatever it was doing before&lt;/li&gt;
&lt;li&gt;The latency may be too high to provide life-like reaction times&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 500px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/94ec478669e2cfc2252ce59559f25645/41099/setup-3.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAUABQDASIAAhEBAxEB/8QAGAABAQEBAQAAAAAAAAAAAAAAAAIDBAX/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIQAxAAAAH0OnTAoFwGgP/EABsQAQEBAAIDAAAAAAAAAAAAAAIDAQASBBEh/9oACAEBAAEFAiK55FcWiOLB7+1uJcnU0PQ9nIvhGHP/xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAEDAQE/AR//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAECAQE/AR//xAAeEAACAQQDAQAAAAAAAAAAAAAAARECEiJBECFhkf/aAAgBAQAGPwK5vEwcMzqufEVT34TTPwu2KdEI/8QAGxAAAwEBAQEBAAAAAAAAAAAAAAERITFhQVH/2gAIAQEAAT8heOm8o5r1yf2OiRtGnBwtSZKOUCc2D5HVo/ZX+I4aAnrP/9oADAMBAAIAAwAAABBgBwD/xAAVEQEBAAAAAAAAAAAAAAAAAAABIP/aAAgBAwEBPxBj/8QAFREBAQAAAAAAAAAAAAAAAAAAARD/2gAIAQIBAT8QIT//xAAdEAACAgMBAQEAAAAAAAAAAAABIQARMUFRYXHw/9oACAEBAAE/EAxiffAdL9iDq3Ah8W4zBM5Icc1fA0QiLULdcG141Nrk3FdDR+x1J2BkqmeeQPAcik3cLyGf/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Setup Three&quot;
        title=&quot;Setup Three&quot;
        src=&quot;/static/94ec478669e2cfc2252ce59559f25645/41099/setup-3.jpg&quot;
        srcset=&quot;/static/94ec478669e2cfc2252ce59559f25645/a80bd/setup-3.jpg 148w,
/static/94ec478669e2cfc2252ce59559f25645/1c91a/setup-3.jpg 295w,
/static/94ec478669e2cfc2252ce59559f25645/41099/setup-3.jpg 500w&quot;
        sizes=&quot;(max-width: 500px) 100vw, 500px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Streaming to another machine directly from the camera&lt;/h2&gt;
&lt;p&gt;It turns out that the &lt;a href=&quot;https://morpx-docs.readthedocs.io/en/latest/MUVS3/introduction.html&quot;&gt;MU Vision Sensor 3&lt;/a&gt; allows for direct Wi-Fi connection since it runs an ESP32 micro-controller. It even allows for MicroPython to be used!&lt;/p&gt;
&lt;p&gt;In this scenario, we would connect the camera and the Bittle both to the same WiFi network and have the machine ingest the images directly from the camera and then send commands to the Bittle.&lt;/p&gt;
&lt;p&gt;The advantages of this approach are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We may achieve better throughput in terms of reaction time, since the Bittle’s micro-controller is not switching between streaming the images and adjusting its behaviour&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The disadvantages of this approach are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Instead of one wireless connection, we are now using two. This means double the opportunity for flaky or slow connections to interrupt the control of the Bittle&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 500px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/73e66a19896000016b80decea5e1edb1/41099/setup-4.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAUABQDASIAAhEBAxEB/8QAGAABAAMBAAAAAAAAAAAAAAAAAAIDBAX/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIQAxAAAAHoaLs5IE4BYD//xAAaEAADAQEBAQAAAAAAAAAAAAABAgMAEhET/9oACAEBAAEFAlnX71DFIhgno1bpLTqtF4Xp5K+VAo//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAEDAQE/AR//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAECAQE/AR//xAAeEAABAwQDAAAAAAAAAAAAAAAAAQIREBIiQSFhkf/aAAgBAQAGPwK5VxMXQpm65aQ6eeiWz4XbEnRCH//EABsQAAMBAQEBAQAAAAAAAAAAAAABESExYUFR/9oACAEBAAE/IUX028o2r2if2OiZY04OFqTJRygTmwfI6tH7K/xHDQE9Z//aAAwDAQACAAMAAAAQYAcA/8QAFREBAQAAAAAAAAAAAAAAAAAAASD/2gAIAQMBAT8QY//EABURAQEAAAAAAAAAAAAAAAAAAAEQ/9oACAECAQE/ECE//8QAHhAAAgICAgMAAAAAAAAAAAAAAREAITFBUWFxgZH/2gAIAQEAAT8QE0Km1C1XsfILe0EKld4lmCZyocXDoHCRAODC3OC101NrkuJoaPmXaTYGTSs8dQPAclSbuDyGf//Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Setup Four&quot;
        title=&quot;Setup Four&quot;
        src=&quot;/static/73e66a19896000016b80decea5e1edb1/41099/setup-4.jpg&quot;
        srcset=&quot;/static/73e66a19896000016b80decea5e1edb1/a80bd/setup-4.jpg 148w,
/static/73e66a19896000016b80decea5e1edb1/1c91a/setup-4.jpg 295w,
/static/73e66a19896000016b80decea5e1edb1/41099/setup-4.jpg 500w&quot;
        sizes=&quot;(max-width: 500px) 100vw, 500px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;A mix of the above&lt;/h2&gt;
&lt;p&gt;We can attempt to find compromises between the advantages and disadvantages of the above setups by mixing and matching.&lt;/p&gt;
&lt;p&gt;One potential way of doing this would be to have different tiers of behaviour - a simple movement behaviour which is solely controlled by the micro-controller, but is also influenced (but not directly controller) by a separate machine performing computer vision tasks. This way, if either one or both WiFi connections are slow or lost, the Bittle’s core loop keeps running uninterrupted on the micro-controller and then re-synchronises once the connection is restored.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;The Petoi Bittle is a great piece of hardware, truly a good product to explore programming, robotics and STEM topics, especially with older children. The price tag (~$300) is higher than other similar products but the quality is very much apparent.&lt;/p&gt;
&lt;p&gt;The setup process might need a bit of polish if you are running from a Linux machine - it is my understanding that I could have avoided these issues if I used a Windows or Mac one.&lt;/p&gt;
&lt;p&gt;Big thanks to Petoi for entrusting me with this review!&lt;/p&gt;
&lt;p&gt;Interested in buying the Petoi Bittle? Go to the &lt;a href=&quot;https://www.petoi.com/products/petoi-bittle-robot-dog?variant=44053953478968&amp;#x26;utm_source=Simon&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=bittle202304&quot;&gt;Bittle Robot Dog&lt;/a&gt; product page and then apply the code Simon7 at checkout to get 7% off your entire order! (Valid till May 14th, 2023).&lt;/p&gt;</content:encoded></item><item><title><![CDATA[The Petoi Bittle Robot Dog - Part 2]]></title><description><![CDATA[Introduction This is part 2 of 3 of my review of the Petoi Bittle Robot Dog kit. This kit was provided to me for free by Petoi in return for…]]></description><link>https://simonam.dev/petoi-bittle-part-2/</link><guid isPermaLink="false">https://simonam.dev/petoi-bittle-part-2/</guid><pubDate>Mon, 01 May 2023 20:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;This is part 2 of 3 of my review of the &lt;a href=&quot;https://www.petoi.com/products/petoi-bittle-robot-dog?variant=44053953478968&amp;#x26;utm_source=Simon&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=bittle202304&quot;&gt;Petoi Bittle Robot Dog kit&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This kit was provided to me for free by &lt;a href=&quot;https://www.petoi.com/&quot;&gt;Petoi&lt;/a&gt; in return for a review.
I am thankful for the opportunity and had a blast reviewing this product.&lt;/p&gt;
&lt;p&gt;In a first for my blog, read till the end to find a special discount code just for my readers: 7% off your entire order until May 14th 2023!&lt;/p&gt;
&lt;h1&gt;Part 2 of 3&lt;/h1&gt;
&lt;p&gt;I’m structuring these blog posts as follows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;In &lt;a href=&quot;/petoi-bittle-part-1&quot;&gt;part 1&lt;/a&gt;, I focused on the kit, its materials and the build process.&lt;/li&gt;
&lt;li&gt;In part 2 (this post), I will focus on the Python API and explore seeing how to make the dog do a trick from scratch.&lt;/li&gt;
&lt;li&gt;In &lt;a href=&quot;/petoi-bittle-part-3&quot;&gt;part 3&lt;/a&gt;, I will install the &lt;a href=&quot;https://www.petoi.com/collections/all/products/intelligent-camera-module?utm_source=Simon&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=camera202304&quot;&gt;Intelligent Camera Module&lt;/a&gt; and see how far I can take its hardware.&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;The dog so far&lt;/h1&gt;
&lt;p&gt;It turns out, having a robot dog around the house raises questions.&lt;/p&gt;
&lt;p&gt;Every person who I showed it to were very interested in seeing how the Bittle handled itself.&lt;/p&gt;
&lt;p&gt;What really wowed observers were the tricks that come pre-programmed into the Bittle. It is highly entertaining to see a robot dog doing push ups or pretending to take a pee. The latter one was especially entertaining to my three year old daughter who would start laughing out loud every time I made the dog do the pee trick.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Clarification from Petoi:&lt;/strong&gt; Uploading the newest firmware will reveal new pre-programmed skills. These were added in anticipation of newer modules which will be released in due time.&lt;/p&gt;
&lt;p&gt;The most interesting of them is the “play dead” action. All the other actions keep the dog on its feet whilst “play dead” causes it to topple over. Not only that, but the action then triggers a built-in self righting mechanism which flips the robot dog back onto its feet, ready to perform the next trick.&lt;/p&gt;
&lt;p&gt;To trigger these tricks, you can use the provided Infra Red Remote which comes with the kit itself:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/2672cfe4227e4ab1927689b883a6ac1f/d2602/remote.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.08108108108109%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAGQAAAgMBAAAAAAAAAAAAAAAAAAQBAgMF/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAH/2gAMAwEAAhADEAAAAXE+pEuJUP/EABsQAAEEAwAAAAAAAAAAAAAAAAEAAhExAwQS/9oACAEBAAEFAjJWN87BsKB0+//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EABoQAQABBQAAAAAAAAAAAAAAAAEQABESMVH/2gAIAQEABj8CE7WLoVlbR//EABoQAAIDAQEAAAAAAAAAAAAAAAABESExUXH/2gAIAQEAAT8h7ohYzino6GTIJa0of//aAAwDAQACAAMAAAAQSy//xAAVEQEBAAAAAAAAAAAAAAAAAAAAEf/aAAgBAwEBPxBH/8QAFhEBAQEAAAAAAAAAAAAAAAAAAAER/9oACAECAQE/EK1//8QAHRABAAICAgMAAAAAAAAAAAAAAREhADFBYRBRgf/aAAgBAQABPxABREp1zmlBCGVHPW4+YRxQ+JOiQYW4qCNes//Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Remote&quot;
        title=&quot;Remote&quot;
        src=&quot;/static/2672cfe4227e4ab1927689b883a6ac1f/1c72d/remote.jpg&quot;
        srcset=&quot;/static/2672cfe4227e4ab1927689b883a6ac1f/a80bd/remote.jpg 148w,
/static/2672cfe4227e4ab1927689b883a6ac1f/1c91a/remote.jpg 295w,
/static/2672cfe4227e4ab1927689b883a6ac1f/1c72d/remote.jpg 590w,
/static/2672cfe4227e4ab1927689b883a6ac1f/a8a14/remote.jpg 885w,
/static/2672cfe4227e4ab1927689b883a6ac1f/fbd2c/remote.jpg 1180w,
/static/2672cfe4227e4ab1927689b883a6ac1f/d2602/remote.jpg 4032w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Alternatively, you can use the mobile app too:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/4e615dd41d9f113934fbd0ae4e5093b6/5aa45/app.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 47.972972972972975%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAKABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAgADBf/EABYBAQEBAAAAAAAAAAAAAAAAAAEAAv/aAAwDAQACEAMQAAAB5JS2Yyo//8QAGRAAAgMBAAAAAAAAAAAAAAAAABIBAhAx/9oACAEBAAEFAlgUUrzP/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPwE//8QAFRABAQAAAAAAAAAAAAAAAAAAIDH/2gAIAQEABj8Ci//EABkQAQADAQEAAAAAAAAAAAAAAAEAETEQUf/aAAgBAQABPyFpMSkQe9HZ/9oADAMBAAIAAwAAABAz3//EABURAQEAAAAAAAAAAAAAAAAAAAAR/9oACAEDAQE/EKr/xAAWEQEBAQAAAAAAAAAAAAAAAAABABH/2gAIAQIBAT8QQsL/xAAcEAEAAgMAAwAAAAAAAAAAAAABABEhMUFRkcH/2gAIAQEAAT8QSwV1tz7i9NuuURqhWPMaq29fY9jXJ5P/2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;App&quot;
        title=&quot;App&quot;
        src=&quot;/static/4e615dd41d9f113934fbd0ae4e5093b6/1c72d/app.jpg&quot;
        srcset=&quot;/static/4e615dd41d9f113934fbd0ae4e5093b6/a80bd/app.jpg 148w,
/static/4e615dd41d9f113934fbd0ae4e5093b6/1c91a/app.jpg 295w,
/static/4e615dd41d9f113934fbd0ae4e5093b6/1c72d/app.jpg 590w,
/static/4e615dd41d9f113934fbd0ae4e5093b6/a8a14/app.jpg 885w,
/static/4e615dd41d9f113934fbd0ae4e5093b6/fbd2c/app.jpg 1180w,
/static/4e615dd41d9f113934fbd0ae4e5093b6/5aa45/app.jpg 2246w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;In general I found the app to be more useful since with the IR remote you need to make sure that you point it at the rear of the dog, which can become obscured relatively easy. It is also easily obscured by the tail that you can attach at the back of the dog, which I ended up removing at some point to make the IR remote more responsive.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Clarification from Petoi:&lt;/strong&gt; The IR remote was originally intended as a debug interface. The app is recommended for more sensitive and one-to-one control of the Bittle.&lt;/p&gt;
&lt;h1&gt;Teaching a new dog new tricks&lt;/h1&gt;
&lt;p&gt;Part of why this product drew my attention was that the company advertised the availability of a Python API. Being a full time Software Engineer who primarily works in Python as well as an ex-organiser of a Python meetup, this was definitely my ideal option to interact with the Bittle and begin to control its behaviour programmatically.&lt;/p&gt;
&lt;p&gt;In this part I will briefly explore two methods:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Using the &lt;a href=&quot;https://docs.petoi.com/api/python-serialmaster-user-guide&quot;&gt;Python SerialMaster&lt;/a&gt; approach&lt;/li&gt;
&lt;li&gt;Using the &lt;a href=&quot;https://docs.petoi.com/communication-modules/wifi-esp8266/esp8266-+-python-scripts-implement-wireless-crowd-control&quot;&gt;ESP8266 + Python Scripts&lt;/a&gt; approach&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Python SerialMaster&lt;/h2&gt;
&lt;p&gt;In case it is unclear, as it is not stated in the docs, these instructions require you to find the scripts contained in the &lt;a href=&quot;https://github.com/PetoiCamp/OpenCat&quot;&gt;OpenCat Repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I decided to use the Bluetooth method to avoid having to stay plugging into the Bittle. The docs only show examples for using Windows, so I had to do some extra steps on my Ubuntu machine.
First I paired with the Bittle via the GUI which gave me its MAC address:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/94876cffbb453ed20a4a874d95549d5d/587b0/bt-pair.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 83.78378378378379%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAYAAADdRIy+AAAACXBIWXMAAAsTAAALEwEAmpwYAAACj0lEQVQ4y52TzW7TQBSF80Ls2HTPkg1StzwLEhJCVCCkUlJESeJ/ezwe23HSRl1UabsJIZH6EixII9qkbUiqtDnoXntKqqYbLB15bN/55twfl1zXQa1axXa5DMs0Qc+macK2bTiOA7sQrel9uVxGrVbLv7kenHev4Gx/QtUwUa1UUBKRQqhiJFkDSaOJpJ4hiiIEQZBDLQu2acKyrBxSHOS6LsuxTDi2jTAMIaVEyf/6Ge6XLcTmNyTGDmKrglgpeJ6Haq0G3zLgG1UGhVLyJtr8T/k7MqGUQsleX4O1vgb7xVPYz5/AefkMkfAhRAiDUnNdREpxsIbQOo5jFq3vAX3XgWUYkCJAPYmRxPnGRpbh+PgY7XYbh4eHrE6ng263i1arhSRJkGV5eagcd8BACFhFOlnWQL1eh+/7ODg4wHw+x2QywWAwYI3HY1xfXzOYDk3TlN1R/B1QhiFcx4FSEcPqacoB5GixWGA2mzF0cnXFMLr6/T7HqKIUOnWClugkagClwMDCIaVKQIKMRiMMh0OG09Xr9TiGQATRyh1KyfPlBwHD0sKhBhJkOp2yy1VA7fKBQwIRkJzeAec3uByNMTo7w/j8HLPJBLi9fRTIDjVQFzmllD0P7aMjdkOeRjdz/Lq8wOn0D26ohicnjwOlDHniyS67pJRFgPZeCxdegkHZwNCOcGqF+LlVwe8dB929fQSRfABjoBCCfzOaKXaYJBBSoqli9N5vofN6A92NTfzY2MT3Nx/Qe/sR+0IijBXUUjPuASld3WGqIQVK+p+bGcRuE0Ejy7XbYIWqgBWNuAckd1RDcqiBOiiOIsQyyu96LfNvUQFdUUPJQJKuowYuz9gqqVUOqVs0hxq27PB/9BfXXRwZZbXM4wAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;BT Pairing&quot;
        title=&quot;BT Pairing&quot;
        src=&quot;/static/94876cffbb453ed20a4a874d95549d5d/fcda8/bt-pair.png&quot;
        srcset=&quot;/static/94876cffbb453ed20a4a874d95549d5d/12f09/bt-pair.png 148w,
/static/94876cffbb453ed20a4a874d95549d5d/e4a3f/bt-pair.png 295w,
/static/94876cffbb453ed20a4a874d95549d5d/fcda8/bt-pair.png 590w,
/static/94876cffbb453ed20a4a874d95549d5d/efc66/bt-pair.png 885w,
/static/94876cffbb453ed20a4a874d95549d5d/587b0/bt-pair.png 970w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;I then tried to setup &lt;code class=&quot;language-text&quot;&gt;rfcomm&lt;/code&gt; following &lt;a href=&quot;https://askubuntu.com/a/252235&quot;&gt;these instructions&lt;/a&gt; so that I could access the bluetooth connection as a serial port. This however, took much longer that expected.&lt;/p&gt;
&lt;p&gt;Using &lt;code class=&quot;language-text&quot;&gt;bluetoothctl&lt;/code&gt; I could see that I was definitely paired with the Bittle. By running &lt;code class=&quot;language-text&quot;&gt;sudo rfcomm bind 0 D8:71:4D:96:C4:3A&lt;/code&gt; I could finally see the &lt;code class=&quot;language-text&quot;&gt;rfcomm0&lt;/code&gt; file, however the script was failing with a Permission Denied error. With some more googling, I figured out I needed to run &lt;code class=&quot;language-text&quot;&gt;sudo chmod a+rw /dev/rfcomm0&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;On attempting to run the script, it seemed to always attempt to first connect to &lt;code class=&quot;language-text&quot;&gt;/dev/ttyS0&lt;/code&gt;, so I changed the script using the following line &lt;code class=&quot;language-text&quot;&gt;allPorts = [p for p in allPorts if p != &apos;/dev/ttyS0&apos;]&lt;/code&gt;. Even then however, connecting to &lt;code class=&quot;language-text&quot;&gt;/dev/rfcomm0&lt;/code&gt; still continued to fail.&lt;/p&gt;
&lt;p&gt;At that point I switched back to the USB method.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/b253585115ec09c4ef32180f46cadd15/d2602/usb.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.08108108108109%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAGAAAAgMAAAAAAAAAAAAAAAAAAAQBAgP/xAAVAQEBAAAAAAAAAAAAAAAAAAACAP/aAAwDAQACEAMQAAABWtssW4SR/8QAGxAAAgEFAAAAAAAAAAAAAAAAARECAAMQISL/2gAIAQEAAQUCDoNWpcrU8f/EABYRAQEBAAAAAAAAAAAAAAAAAAARAf/aAAgBAwEBPwGYj//EABYRAQEBAAAAAAAAAAAAAAAAAAARAf/aAAgBAgEBPwG6r//EABgQAAMBAQAAAAAAAAAAAAAAAAABIREg/9oACAEBAAY/AqzCrj//xAAbEAACAwEBAQAAAAAAAAAAAAAAAREhMUFhgf/aAAgBAQABPyGidvhUjCftnWtp6ukIuCrTFh//2gAMAwEAAgADAAAAEMMf/8QAFhEBAQEAAAAAAAAAAAAAAAAAAREA/9oACAEDAQE/EECTRv/EABYRAQEBAAAAAAAAAAAAAAAAAAARQf/aAAgBAgEBPxDBb//EABwQAQEAAgIDAAAAAAAAAAAAAAERACExUUFhcf/aAAgBAQABPxAuSp0JA4yocxVy7erlCPgqSPOOuKOnGhMR16xMfM//2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;usb&quot;
        title=&quot;usb&quot;
        src=&quot;/static/b253585115ec09c4ef32180f46cadd15/1c72d/usb.jpg&quot;
        srcset=&quot;/static/b253585115ec09c4ef32180f46cadd15/a80bd/usb.jpg 148w,
/static/b253585115ec09c4ef32180f46cadd15/1c91a/usb.jpg 295w,
/static/b253585115ec09c4ef32180f46cadd15/1c72d/usb.jpg 590w,
/static/b253585115ec09c4ef32180f46cadd15/a8a14/usb.jpg 885w,
/static/b253585115ec09c4ef32180f46cadd15/fbd2c/usb.jpg 1180w,
/static/b253585115ec09c4ef32180f46cadd15/d2602/usb.jpg 4032w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;It seemed that the auto-selection of the serial port was never completing, causing me to have to go through manual mode every time:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 573px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/811efa760cc7cdb23a3b98af83fa8ed5/3c024/manual-mode.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 39.86486486486486%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABnElEQVQoz02Q247aMBCG87y96oP0qg/ALdsFdQsXVLAVSoJyIBzsGFCOcIFKOEhgYl+QXYymmpQgRvr0jz2/x2Nr+/3+q5TyXQjxJoRoIVLKVlEUJVmWtTabzV2zslb5Ki/n/I1z/me73X7Rdrvdu5QS8jyH8/kMmJ9OJ0jTFP5uNpAmCURRBLPZDLIsAyHEw4vKOYfj8Vjqer1ua51O55eu62AYxofrukrXdWWapmo2m6pWq6lut6vq9bpqNBplzbZtZRiGQq9lWcrzPDWdTj8mkwl4nveimabZHgwGYJrmdTQaoYLjOGBZFvR6Pej3+0ApLSceDodg23YJenE9Ho/B9/0rIQQbvmqMsfZqtcID1yRJYLlcAire6Pt+qfgcBH1Yw+aoFXEcX3GPUvqqEUJa+EdBEHyGYXgLguCGWhTFDQAeYKRp+qhXRFF0WywWnzghIeSHRin9ndw/Po7jh14uF3gOpVQ54bOvysMwLHNK6U/Ndd3vvu8v6f9gFYfDgUkpWZ7nTAjBOOdsPp8zQgh79t0hjLGV4zjf/gF75yoT5uJGLwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Manual Mode&quot;
        title=&quot;Manual Mode&quot;
        src=&quot;/static/811efa760cc7cdb23a3b98af83fa8ed5/3c024/manual-mode.png&quot;
        srcset=&quot;/static/811efa760cc7cdb23a3b98af83fa8ed5/12f09/manual-mode.png 148w,
/static/811efa760cc7cdb23a3b98af83fa8ed5/e4a3f/manual-mode.png 295w,
/static/811efa760cc7cdb23a3b98af83fa8ed5/3c024/manual-mode.png 573w&quot;
        sizes=&quot;(max-width: 573px) 100vw, 573px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 427px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/c19c8b20d500dfec93f3c7695f1d9690/a7c74/manual-mode-countdown.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 24.324324324324326%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsTAAALEwEAmpwYAAABNUlEQVQY0z2MwYrCMBRF+4HzB/MHszcrd/Mng9Bp0RlFRCgpmLbGplppXbhxFk1RFwUxTdUaXDxJYFxczn333fesqqo+m6ahUkpPSon/1TQNrqoKF0WBD4cD3u/3xgshXp26rvH5fMan08kTQtCyLD8sKWX+eDxAKWWk/f1+NxRCwN9uB5yXwHkBnHO4te2ro9W2LVwuF7her3A8Hn8t13WD0WgEg8Hg5vu+sm1bzWYz5TiO6vV6ijGmCCGKJYlynG81HA7NTmfj8dh4fbvdbiHP8y+LMZalaQpaWZZBHMeGq9UKkiSB5XIJk8kEfN83u/V6bfLNZmO87mnqh2ma/liEkHdKaTeKok4YhmixWCDNKIpQHMfI8zzU7/eRbdsmn8/niFKKgiAwHT2HYdghhHSn0+nbE7cCSInxGMD5AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Manual Mode Countdown&quot;
        title=&quot;Manual Mode Countdown&quot;
        src=&quot;/static/c19c8b20d500dfec93f3c7695f1d9690/a7c74/manual-mode-countdown.png&quot;
        srcset=&quot;/static/c19c8b20d500dfec93f3c7695f1d9690/12f09/manual-mode-countdown.png 148w,
/static/c19c8b20d500dfec93f3c7695f1d9690/e4a3f/manual-mode-countdown.png 295w,
/static/c19c8b20d500dfec93f3c7695f1d9690/a7c74/manual-mode-countdown.png 427w&quot;
        sizes=&quot;(max-width: 427px) 100vw, 427px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Even then, the script was throwing exceptions related to &lt;code class=&quot;language-text&quot;&gt;tkinter&lt;/code&gt;, the GUI framework used for this script:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;    tk.messagebox.showwarning(title=&apos;Warning&apos;, message=txt(&apos;Manual mode&apos;))
AttributeError: module &apos;tkinter&apos; has no attribute &apos;messagebox&apos;
/dev/ttyACM0
ttyACM0
2023-04-20 18:10:12,095 __main__ - INFO - Connected to serial port: /dev/ttyACM0
Exception in Tkinter callback
Traceback (most recent call last):
  File &quot;/home/simon/Projects/OpenCat-1.0.2/serialMaster/ardSerial.py&quot;, line 712, in selectList
    tk.messagebox.showwarning(title=&apos;Warning&apos;, message=txt(&apos;Need to manually select the model type (Nybble/Bittle)&apos;))
AttributeError: module &apos;tkinter&apos; has no attribute &apos;messagebox&apos;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;At this point I was pretty frustrated with the wired approach, so I decided to move onto the next option. I didn’t regret it. 👇&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Clarification from Petoi:&lt;/strong&gt; Due to the many variants of Linux, testing was mainly done on Windows/Mac. Work will be done in due time to improve the auto-connection algorithm when interacating with the Bittle via bluetooth.&lt;/p&gt;
&lt;h2&gt;ESP8266 and MicroPython&lt;/h2&gt;
&lt;p&gt;This option involved the use of the ESP8266 chip which came packaged with the robot, programming it with a version of Python capable of running on micro-controllers: &lt;a href=&quot;https://micropython.org/&quot;&gt;MicroPython&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The first step was to download the latest firmware and update the board as outlined in the &lt;a href=&quot;https://docs.petoi.com/apis/micropython-controller#part-2-software-setup&quot;&gt;docs&lt;/a&gt;. This approach was much smoother as all of the steps worked on the first try.&lt;/p&gt;
&lt;p&gt;I was reluctant to use &lt;a href=&quot;https://thonny.org/&quot;&gt;Thonny&lt;/a&gt; as it would mean having to learn a separate IDE from VS Code, however in the interest of following the instructions comprehensively, I downloaded and installed it. I opted for this approach as I was focusing on using the Python method - there is also the option to use the Arduino environment if one is more familiar with that.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/b31691de1f1772624f90069f49a60846/9f82e/micropython.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 89.86486486486487%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAYAAABb0P4QAAAACXBIWXMAAAsTAAALEwEAmpwYAAACe0lEQVQ4y62Sa0/bMBiF8/+/8DP4jjpN4zKGpq6AaRo3sdNLCoW1Xcn92jS1nTOlBQabhhCbpUfn2LKOX/u15rrefpokN0mcsDhOeBzHvNGsKPhyueSz2ZwvFgs+nd5ufRhGPMsynqYPZBlP0pTFcXzjuu6+putX1KB99KgOy+rDMvswqYHJgGMyceCMR3CcMcZbHeF64uD6evKSh7WBzanW0zsX3zptEL0tmNVVNuspTi+VTdpqwKkacOMlzFD2n4gBp2CmfqFRo0uGA47Q92QcBYhCH1GjcbjzbyKQYeCBWwbRODNJFEUAIPH+Ieu6xmjIiMasPvE8D0opKaXEO5FCiMdAk/i+j7qu3x3YFPNqoFLqzbwp8B8r3L1hXf+nN7S5RcIwfOoUajxQv0IjvzxqyOb628Du1Tm5nowR+K70/Xt43j0834UXePCC5+o+zd1mX+TvvLdsVDZq9XWi2axPHJvhxhnJkW1h7gyxsC3MeleYG90dpoGFRbc088WQ4+7rGeb9Hn44Q8x1Ips10+wRzVvMSEoIEoPKSNdRjsYoKUV2dobStFAyhvziEkW3i7zTQd5uo2Qc6cnJds/KsrAejWXlujAMQrTv0xtSuB7S5VIWnossDJAXObI4Rp7nKNdrrIoC6/Ua5Wq19VVZ7pohBERVQQFyIzboN4Gz6d15GmRwl/fVfeILPwuEnwSiqiqx2WxE00CllJDPUI/UtagbVF0pKTEe8nNtNr3lSRQjSRIUef7wDcT29Eab7/BXNhtU6zWqqkJZrmAzyrXueWePm/TQ5vSAM6PFzN4O65n+jtlrcUZbhn7ROv182PpyenxwfPTx8OjTh72fVbo4SC/sJhUAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;micropython&quot;
        title=&quot;micropython&quot;
        src=&quot;/static/b31691de1f1772624f90069f49a60846/fcda8/micropython.png&quot;
        srcset=&quot;/static/b31691de1f1772624f90069f49a60846/12f09/micropython.png 148w,
/static/b31691de1f1772624f90069f49a60846/e4a3f/micropython.png 295w,
/static/b31691de1f1772624f90069f49a60846/fcda8/micropython.png 590w,
/static/b31691de1f1772624f90069f49a60846/9f82e/micropython.png 820w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Within ten minutes, I had a python script which made the LED on the board blink. Running this code reminded of the very first time I ran code on an Arduino, several years earlier, also making it simply blink an LED.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/08ab68c0ef3c2e4ef8dde9cd1fbaa96c/blink.gif&quot; alt=&quot;blink&quot;&gt;&lt;/p&gt;
&lt;p&gt;Following the high of getting blink to work, I used the &lt;a href=&quot;https://docs.petoi.com/apis/micropython-controller/run-micropython-on-esp8266#4.-write-a-script-to-let-robot-perform-actions-sequentially&quot;&gt;example script&lt;/a&gt; from the docs to see that the board, now running MicroPython, will be recognised by the Bittle.&lt;/p&gt;
&lt;p&gt;Here it is running the example script:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/24b80298eba393f3f76ff7fba53ae5c7/testscript.gif&quot; alt=&quot;testscript&quot;&gt;&lt;/p&gt;
&lt;p&gt;As outlined in the docs, disconnecting and reconnecting the ESP8266 every time you want to change some code and observe the effect is very cumbersome.&lt;/p&gt;
&lt;p&gt;The solution presented is to use the WebREPL. I was very impressed that, following the docs, I was now running my blink script over Wi-Fi!&lt;/p&gt;
&lt;p&gt;Re-installing the ESP8266 into the robot presented an issue - it started crashing after a while and hanging, playing a beeping sound over and over. Fixing this issue is covered in &lt;a href=&quot;https://docs.petoi.com/apis/micropython-controller/setup-webrepl#4.-separate-the-serial-port-from-the-debugger&quot;&gt;this part of the docs&lt;/a&gt;. After applying the fix I thought I had broken something since my terminal was no longer returning any output. Eventually I realised, it was working as intended! (the fix disables the debug output which eventually crashes the board). This allowed me to run the example script file from my desktop onto the robot itself, wirelessly!&lt;/p&gt;
&lt;p&gt;A tip while working with the WebREPL: understanding how to find out whether the board has connected to your WiFi via your router’s web UI is very useful to know whether any issues connecting to the board are because the board is still booting and connecting or in the software.&lt;/p&gt;
&lt;p&gt;Now that I could run the WebREPL, I read through the example script to understand how to control the behaviour directly. Using the following code and this &lt;a href=&quot;https://docs.petoi.com/apis/serial-protocol&quot;&gt;list of skills&lt;/a&gt; led me to success!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/f6d3ed7bb4fd7c829f3f8cbf7ec894b5/webrepl.gif&quot; alt=&quot;webrepl&quot;&gt;&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Now that I could control the Bittle programmatically from a language that I was familiar with, I was already beginning to daydream the kind of applications I could use the Bittle for. Given my current focus on AI and Machine Learning, I can see myself experimenting using ML to drive the Bittle and to begin to give it a life of its own. Interestingly, Petoi provide a &lt;a href=&quot;https://github.com/AIWintermuteAI/Bittle_URDF&quot;&gt;Universal Robot Description Format of the Bittle&lt;/a&gt;, which is a useful resource to run simulations of the Bittle for the purpose of ML training.&lt;/p&gt;
&lt;p&gt;Speaking of AI and ML, the final part of this series of blog posts will explore using the &lt;a href=&quot;https://www.petoi.com/collections/all/products/intelligent-camera-module?utm_source=Simon&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=camera202304&quot;&gt;Intelligent Camera Module&lt;/a&gt;. I intend on seeing how good the eyes of this dog can be.&lt;/p&gt;
&lt;p&gt;Interested in buying the Petoi Bittle? Go to the &lt;a href=&quot;https://www.petoi.com/products/petoi-bittle-robot-dog?variant=44053953478968&amp;#x26;utm_source=Simon&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=bittle202304&quot;&gt;Bittle Robot Dog&lt;/a&gt; product page and then apply the code Simon7 at checkout to get 7% off your entire order! (Valid till May 14th, 2023).&lt;/p&gt;</content:encoded></item><item><title><![CDATA[The Petoi Bittle Robot Dog - Part 1]]></title><description><![CDATA[Introduction This is part 1 of 3 of my review of the Petoi Bittle Robot Dog kit. This kit was provided to me for free by Petoi in return for…]]></description><link>https://simonam.dev/petoi-bittle-part-1/</link><guid isPermaLink="false">https://simonam.dev/petoi-bittle-part-1/</guid><pubDate>Sun, 16 Apr 2023 23:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;This is part 1 of 3 of my review of the &lt;a href=&quot;https://www.petoi.com/products/petoi-bittle-robot-dog?variant=44053953478968&amp;#x26;utm_source=Simon&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=bittle202304&quot;&gt;Petoi Bittle Robot Dog kit&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This kit was provided to me for free by &lt;a href=&quot;https://www.petoi.com/&quot;&gt;Petoi&lt;/a&gt; in return for a review.
I am thankful for the opportunity and had a blast reviewing this product.&lt;/p&gt;
&lt;p&gt;In a first for my blog, read till the end to find a special discount code just for my readers: 7% off your entire order until May 14th 2023!&lt;/p&gt;
&lt;h1&gt;Part 1 of 3&lt;/h1&gt;
&lt;p&gt;I’m structuring these blog posts as follows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;In part 1 (this post), I will focus on the kit, its materials and the build process.&lt;/li&gt;
&lt;li&gt;In &lt;a href=&quot;/petoi-bittle-part-2&quot;&gt;part 2&lt;/a&gt;, I will focus on the Python API and explore seeing how to make the dog do a trick from scratch.&lt;/li&gt;
&lt;li&gt;In &lt;a href=&quot;/petoi-bittle-part-3&quot;&gt;part 3&lt;/a&gt;, I will install the &lt;a href=&quot;https://www.petoi.com/collections/all/products/intelligent-camera-module?utm_source=Simon&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=camera202304&quot;&gt;Intelligent Camera Module&lt;/a&gt; and see how far I can take its hardware.&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;The package&lt;/h1&gt;
&lt;p&gt;I got a real kick out of seeing &lt;strong&gt;Simon’s Blog&lt;/strong&gt; on the box. This blog is little more than a creative and professional outlet and this was a very physical acknowledgement that it has some sort of impact on the world, however small it is.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/71367372fb4bdf5f540239ea540dd7c2/d2602/simons-blog.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.08108108108109%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAQFAgP/xAAVAQEBAAAAAAAAAAAAAAAAAAAAAv/aAAwDAQACEAMQAAAB5soaiqpGD//EABgQAQEBAQEAAAAAAAAAAAAAAAECABAS/9oACAEBAAEFAvbi3FFnGk3/xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/AT//xAAZEAADAAMAAAAAAAAAAAAAAAAAAREQMTL/2gAIAQEABj8COitK52f/xAAaEAEBAQADAQAAAAAAAAAAAAABABEhMVGR/9oACAEBAAE/IT1PkUhRLo6Wu2s1wi//2gAMAwEAAgADAAAAEOzf/8QAFhEBAQEAAAAAAAAAAAAAAAAAAAER/9oACAEDAQE/EMV//8QAFREBAQAAAAAAAAAAAAAAAAAAEBH/2gAIAQIBAT8Qp//EABwQAQADAAIDAAAAAAAAAAAAAAEAESExUZGh8P/aAAgBAQABPxApniQA0FOjIHTGIon3uVIGZcyo3BbTP//Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Simon&amp;#39;s Blog&quot;
        title=&quot;Simon&amp;#39;s Blog&quot;
        src=&quot;/static/71367372fb4bdf5f540239ea540dd7c2/1c72d/simons-blog.jpg&quot;
        srcset=&quot;/static/71367372fb4bdf5f540239ea540dd7c2/a80bd/simons-blog.jpg 148w,
/static/71367372fb4bdf5f540239ea540dd7c2/1c91a/simons-blog.jpg 295w,
/static/71367372fb4bdf5f540239ea540dd7c2/1c72d/simons-blog.jpg 590w,
/static/71367372fb4bdf5f540239ea540dd7c2/a8a14/simons-blog.jpg 885w,
/static/71367372fb4bdf5f540239ea540dd7c2/fbd2c/simons-blog.jpg 1180w,
/static/71367372fb4bdf5f540239ea540dd7c2/d2602/simons-blog.jpg 4032w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/81f399f04eeda7fb8907294a99564791/d2602/box-1.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.08108108108109%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAIDBP/EABYBAQEBAAAAAAAAAAAAAAAAAAEAAv/aAAwDAQACEAMQAAABynosrBr/xAAZEAEBAAMBAAAAAAAAAAAAAAABAgADEhD/2gAIAQEAAQUCqQAnOdd4gvJ5/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPwE//8QAHBAAAgEFAQAAAAAAAAAAAAAAAAEhAhARMaGx/9oACAEBAAY/AqUT6J46Sat//8QAHBAAAwABBQAAAAAAAAAAAAAAAAERITFBUXGR/9oACAEBAAE/IXUrfZVESXInq0mGQhRYR2eCwkf/2gAMAwEAAgADAAAAEO8P/8QAFhEBAQEAAAAAAAAAAAAAAAAAAAER/9oACAEDAQE/EJWv/8QAFREBAQAAAAAAAAAAAAAAAAAAABH/2gAIAQIBAT8QiP/EABoQAQEBAQEBAQAAAAAAAAAAAAERIQAxgZH/2gAIAQEAAT8QPL3F3OftTRfJrFMEEzz5wkNMvQM/NygMAwO//9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Box&quot;
        title=&quot;Box&quot;
        src=&quot;/static/81f399f04eeda7fb8907294a99564791/1c72d/box-1.jpg&quot;
        srcset=&quot;/static/81f399f04eeda7fb8907294a99564791/a80bd/box-1.jpg 148w,
/static/81f399f04eeda7fb8907294a99564791/1c91a/box-1.jpg 295w,
/static/81f399f04eeda7fb8907294a99564791/1c72d/box-1.jpg 590w,
/static/81f399f04eeda7fb8907294a99564791/a8a14/box-1.jpg 885w,
/static/81f399f04eeda7fb8907294a99564791/fbd2c/box-1.jpg 1180w,
/static/81f399f04eeda7fb8907294a99564791/d2602/box-1.jpg 4032w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Inside the box there was a list of parts that it contained. At first I did not understand what some parts are (such as the USB programmer or the “cross shape servo arm”) however the docs on the website provide both pictures and videos that make it easier to follow along.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/ef434bf2d88824adc903f7913e5daf20/d2602/box-2.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.08108108108109%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAGAAAAgMAAAAAAAAAAAAAAAAAAAMCBAX/xAAVAQEBAAAAAAAAAAAAAAAAAAAAAv/aAAwDAQACEAMQAAABGRSapWJr/8QAGhAAAgIDAAAAAAAAAAAAAAAAAAIBEAMSIf/aAAgBAQABBQLJ0RVN6mv/xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/AT//xAAXEAEAAwAAAAAAAAAAAAAAAAARACAh/9oACAEBAAY/AiOV/8QAGRABAAMBAQAAAAAAAAAAAAAAAQARITGB/9oACAEBAAE/IU1wBy4QKWI22+x03Z17DCif/9oADAMBAAIAAwAAABDHD//EABYRAQEBAAAAAAAAAAAAAAAAAAARIf/aAAgBAwEBPxC4r//EABYRAQEBAAAAAAAAAAAAAAAAAAERAP/aAAgBAgEBPxAI3Tf/xAAZEAEBAQEBAQAAAAAAAAAAAAABESEAQXH/2gAIAQEAAT8QkADUoNPvPPARG6enJljXY0CHKWdYQUEYMOSSQCB3/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Box&quot;
        title=&quot;Box&quot;
        src=&quot;/static/ef434bf2d88824adc903f7913e5daf20/1c72d/box-2.jpg&quot;
        srcset=&quot;/static/ef434bf2d88824adc903f7913e5daf20/a80bd/box-2.jpg 148w,
/static/ef434bf2d88824adc903f7913e5daf20/1c91a/box-2.jpg 295w,
/static/ef434bf2d88824adc903f7913e5daf20/1c72d/box-2.jpg 590w,
/static/ef434bf2d88824adc903f7913e5daf20/a8a14/box-2.jpg 885w,
/static/ef434bf2d88824adc903f7913e5daf20/fbd2c/box-2.jpg 1180w,
/static/ef434bf2d88824adc903f7913e5daf20/d2602/box-2.jpg 4032w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;My Bittle arrived partially assembled so I was able to skip some of the build steps. You can see below how the legs and their servos were already attached together.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/87f2ed8a7d4c5a24e1e75fce764f9d15/d2602/box-3.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.08108108108109%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAwABAv/EABUBAQEAAAAAAAAAAAAAAAAAAAEA/9oADAMBAAIQAxAAAAEVwxS7i//EABkQAAMAAwAAAAAAAAAAAAAAAAABAgMRM//aAAgBAQABBQJENS6xw3pFdj//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/AT//xAAcEAABAwUAAAAAAAAAAAAAAAAAAQIxEBESIaH/2gAIAQEABj8C0LlFoI6INp//xAAZEAEAAwEBAAAAAAAAAAAAAAABABEhMRD/2gAIAQEAAT8h1T0k2CqgOstFoW0cfX//2gAMAwEAAgADAAAAEOP/AP/EABURAQEAAAAAAAAAAAAAAAAAAAAR/9oACAEDAQE/EKr/xAAVEQEBAAAAAAAAAAAAAAAAAAAAEf/aAAgBAgEBPxBH/8QAGxABAAICAwAAAAAAAAAAAAAAAQARITFRYYH/2gAIAQEAAT8QdRVSrVexBK7IA7epR4iGlMOWio0QXiLln//Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Box&quot;
        title=&quot;Box&quot;
        src=&quot;/static/87f2ed8a7d4c5a24e1e75fce764f9d15/1c72d/box-3.jpg&quot;
        srcset=&quot;/static/87f2ed8a7d4c5a24e1e75fce764f9d15/a80bd/box-3.jpg 148w,
/static/87f2ed8a7d4c5a24e1e75fce764f9d15/1c91a/box-3.jpg 295w,
/static/87f2ed8a7d4c5a24e1e75fce764f9d15/1c72d/box-3.jpg 590w,
/static/87f2ed8a7d4c5a24e1e75fce764f9d15/a8a14/box-3.jpg 885w,
/static/87f2ed8a7d4c5a24e1e75fce764f9d15/fbd2c/box-3.jpg 1180w,
/static/87f2ed8a7d4c5a24e1e75fce764f9d15/d2602/box-3.jpg 4032w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;I underestimated the force required to put some of the screws in place. I would advise people to be careful not to apply too much pressure and instead patiently tighten the screws.&lt;/p&gt;
&lt;p&gt;The instructions are &lt;em&gt;very&lt;/em&gt; precise, especially around how the wires should be threaded through the body during assembly. Try not to get ahead of yourself the way I did or you will end up having to undo steps to correct your mistakes 😅.&lt;/p&gt;
&lt;p&gt;No corners were cut in the plastic used for this product. It is sleek, sturdy and feels very nice to the touch. At one point I knocked the central body part off the table and it flew across the room. I was already dreading the fact that I would need to ask them to send me another part however the body survived the episode just fine, it didn’t even get scratched.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/10a4bb0dffc4d4557bb71b4a3ad83463/d2602/dog-body-1.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.08108108108109%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAwACBP/EABYBAQEBAAAAAAAAAAAAAAAAAAEAAv/aAAwDAQACEAMQAAABQH5su46f/8QAGxABAAICAwAAAAAAAAAAAAAAAgABAxESE0P/2gAIAQEAAQUCrZrusqshU9hBjPH/xAAWEQEBAQAAAAAAAAAAAAAAAAAAESH/2gAIAQMBAT8BrH//xAAWEQEBAQAAAAAAAAAAAAAAAAAAESH/2gAIAQIBAT8BjX//xAAbEAABBAMAAAAAAAAAAAAAAAAAAQIRITFRgv/aAAgBAQAGPwKGJW1LsmDkeYP/xAAbEAADAAIDAAAAAAAAAAAAAAAAAREhMUFRwf/aAAgBAQABPyFrhhaeDSN4ipfZd4yPRdBU3RxXZ//aAAwDAQACAAMAAAAQPw//xAAXEQADAQAAAAAAAAAAAAAAAAAAARFR/9oACAEDAQE/EIkFo//EABcRAAMBAAAAAAAAAAAAAAAAAAABEVH/2gAIAQIBAT8Qq1MeD//EAB4QAQEAAgICAwAAAAAAAAAAAAERACExQVFhcYGx/9oACAEBAAE/EJIHKhGYCDIs2m9nXz9Ya6KERTq+8SsdIh7Xf5m1TNFXQ8h4uPpZrm5//9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Dog Body Wires&quot;
        title=&quot;Dog Body Wires&quot;
        src=&quot;/static/10a4bb0dffc4d4557bb71b4a3ad83463/1c72d/dog-body-1.jpg&quot;
        srcset=&quot;/static/10a4bb0dffc4d4557bb71b4a3ad83463/a80bd/dog-body-1.jpg 148w,
/static/10a4bb0dffc4d4557bb71b4a3ad83463/1c91a/dog-body-1.jpg 295w,
/static/10a4bb0dffc4d4557bb71b4a3ad83463/1c72d/dog-body-1.jpg 590w,
/static/10a4bb0dffc4d4557bb71b4a3ad83463/a8a14/dog-body-1.jpg 885w,
/static/10a4bb0dffc4d4557bb71b4a3ad83463/fbd2c/dog-body-1.jpg 1180w,
/static/10a4bb0dffc4d4557bb71b4a3ad83463/d2602/dog-body-1.jpg 4032w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;There were a few confusing moments in the instructions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;At the end of part 4, just as you are instructed to connect the battery, the instructions send you to complete part 5 then return to part 4. &lt;strong&gt;Clarification from Petoi: This has been acknowledged and will be rectified&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;The notes for how the legs are meant to be connected were confusing to me. The picture on the inside of the box helped in this regard.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/571839a7d603c8f77ed1f628e00a5b9c/d2602/box-picture.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.08108108108109%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAMBBP/EABUBAQEAAAAAAAAAAAAAAAAAAAAB/9oADAMBAAIQAxAAAAGWzpByq//EABkQAAMAAwAAAAAAAAAAAAAAAAABERASMf/aAAgBAQABBQI2wuld/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPwE//8QAFxAAAwEAAAAAAAAAAAAAAAAAAAEgIf/aAAgBAQAGPwKXp//EABoQAAIDAQEAAAAAAAAAAAAAAAERABAxIVH/2gAIAQEAAT8hBjLyiL9o7DZ//9oADAMBAAIAAwAAABDP3//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8QP//EABYRAQEBAAAAAAAAAAAAAAAAAAABEf/aAAgBAgEBPxCNf//EABgQAAMBAQAAAAAAAAAAAAAAAAABESFB/9oACAEBAAE/EIK9KNU4Xejr3SJqKYcF1YJktP/Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Box Picture&quot;
        title=&quot;Box Picture&quot;
        src=&quot;/static/571839a7d603c8f77ed1f628e00a5b9c/1c72d/box-picture.jpg&quot;
        srcset=&quot;/static/571839a7d603c8f77ed1f628e00a5b9c/a80bd/box-picture.jpg 148w,
/static/571839a7d603c8f77ed1f628e00a5b9c/1c91a/box-picture.jpg 295w,
/static/571839a7d603c8f77ed1f628e00a5b9c/1c72d/box-picture.jpg 590w,
/static/571839a7d603c8f77ed1f628e00a5b9c/a8a14/box-picture.jpg 885w,
/static/571839a7d603c8f77ed1f628e00a5b9c/fbd2c/box-picture.jpg 1180w,
/static/571839a7d603c8f77ed1f628e00a5b9c/d2602/box-picture.jpg 4032w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Calibration instructions were a bit unclear. It seems like you can calibrate it without using the app, however part 6 indicates that the dog should first be calibrated, then the legs need to be installed. The way it works if you are doing it for the first time is to do an initial calibration, then install the legs, then refine that calibration.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Clarification from Petoi: There are two kinds of calibration. First you calibrate the IMU, followed by initially calibrating the joints. Then, you attach the legs and then refine the calibration using the mobile app, Arduino code or desktop app.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Also to be able to perform Calibration I first had to update the firmware by connecting it to a computer.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/22709e416365bda713363b0827f59590/d2602/calibration-1.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.08108108108109%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAGAAAAgMAAAAAAAAAAAAAAAAAAAQBAgP/xAAWAQEBAQAAAAAAAAAAAAAAAAABAgP/2gAMAwEAAhADEAAAAawwZXiNg//EABoQAAIDAQEAAAAAAAAAAAAAAAECAAMSITL/2gAIAQEAAQUCyoIUOFqzH8VdWf/EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABURAQEAAAAAAAAAAAAAAAAAABAR/9oACAECAQE/Aaf/xAAcEAACAQUBAAAAAAAAAAAAAAAAATEQITJBUXL/2gAIAQEABj8C9bZEdM2MvT//xAAbEAEAAwEAAwAAAAAAAAAAAAABABEhMUFRYf/aAAgBAQABPyG76vgWQ5ymsYylQB6hy+RK2WmthP/aAAwDAQACAAMAAAAQXM//xAAXEQEBAQEAAAAAAAAAAAAAAAABABEh/9oACAEDAQE/EE7YX//EABYRAQEBAAAAAAAAAAAAAAAAAAEAEf/aAAgBAgEBPxAWSrf/xAAbEAEBAQEBAAMAAAAAAAAAAAABEQAhMVFhcf/aAAgBAQABPxAdkNYQ7Z+4nVMX7NU7qbg2Cz6x+4PizdYVCqyGBPN//9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Calibration&quot;
        title=&quot;Calibration&quot;
        src=&quot;/static/22709e416365bda713363b0827f59590/1c72d/calibration-1.jpg&quot;
        srcset=&quot;/static/22709e416365bda713363b0827f59590/a80bd/calibration-1.jpg 148w,
/static/22709e416365bda713363b0827f59590/1c91a/calibration-1.jpg 295w,
/static/22709e416365bda713363b0827f59590/1c72d/calibration-1.jpg 590w,
/static/22709e416365bda713363b0827f59590/a8a14/calibration-1.jpg 885w,
/static/22709e416365bda713363b0827f59590/fbd2c/calibration-1.jpg 1180w,
/static/22709e416365bda713363b0827f59590/d2602/calibration-1.jpg 4032w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Here I encountered a few issues due to my setup:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;I initially understood that there is a Linux version of the Desktop App, however I could not find a precompiled version of it on &lt;a href=&quot;https://github.com/PetoiCamp/OpenCat&quot;&gt;their GitHub link&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Buried in the docs was an explanation for the above: you can use the Desktop App on Linux, you just need to download the source code instead of a pre-compiled version and run the script from a terminal after installing a few dependencies.&lt;/li&gt;
&lt;li&gt;There were some minor issues in the code: the code assumes that you are either on a Windows or a Mac machine, &lt;code class=&quot;language-text&quot;&gt;avrdudeconfPath&lt;/code&gt; which you are instructed to modify did not exist and there was no release file for the &lt;code class=&quot;language-text&quot;&gt;1_2&lt;/code&gt; version of the nyboard (the one I was provided). Thankfully knowing a bit of Python I managed to work around these issues and I applied the &lt;code class=&quot;language-text&quot;&gt;1_1&lt;/code&gt; version instead.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Ultimately I was unable to complete the calibration from the desktop app as the program was always picking an incorrect COM port without giving me the option to choose the one I knew was correct (the screen which allows you to update the firmware gives you a choice with a dropdown). Instead I switched to using the provided bluetooth adapter and used their mobile app instead which was a much smoother experience.&lt;/p&gt;
&lt;p&gt;When removing the USB cable from the USB adapter, do so CAREFULLY! It resisted me pulling it off and it felt very fragile to put more force on it. Instead, remove the USB adapter from the robot dog first, then remove the USB cable from the adapter.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/19bc93272dd93f1d90dc815b253708a7/d2602/dog-body-2.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.08108108108109%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAIDAf/EABYBAQEBAAAAAAAAAAAAAAAAAAABAv/aAAwDAQACEAMQAAABjlqZqjh//8QAGxAAAgEFAAAAAAAAAAAAAAAAAQIDABESMkP/2gAIAQEAAQUCcNaEYsyMW6R71//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EABwQAAIBBQEAAAAAAAAAAAAAAAABEQIQMTJBYf/aAAgBAQAGPwKYTRCz3w2TKVf/xAAbEAEAAwADAQAAAAAAAAAAAAABABEhEDFBcf/aAAgBAQABPyG1Rm1N4Bdh7Ro6PWO+JuyZ+r4//9oADAMBAAIAAwAAABB4/wD/xAAWEQEBAQAAAAAAAAAAAAAAAAAAASH/2gAIAQMBAT8QlY//xAAWEQEBAQAAAAAAAAAAAAAAAAABABH/2gAIAQIBAT8QS1v/xAAbEAEBAAIDAQAAAAAAAAAAAAABEQAhMVFhQf/aAAgBAQABPxDh4HUkOvuU0KFCBb4bzaPLVrjVlfKMBMegCVdc5A0AHRn/2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Dog Body&quot;
        title=&quot;Dog Body&quot;
        src=&quot;/static/19bc93272dd93f1d90dc815b253708a7/1c72d/dog-body-2.jpg&quot;
        srcset=&quot;/static/19bc93272dd93f1d90dc815b253708a7/a80bd/dog-body-2.jpg 148w,
/static/19bc93272dd93f1d90dc815b253708a7/1c91a/dog-body-2.jpg 295w,
/static/19bc93272dd93f1d90dc815b253708a7/1c72d/dog-body-2.jpg 590w,
/static/19bc93272dd93f1d90dc815b253708a7/a8a14/dog-body-2.jpg 885w,
/static/19bc93272dd93f1d90dc815b253708a7/fbd2c/dog-body-2.jpg 1180w,
/static/19bc93272dd93f1d90dc815b253708a7/d2602/dog-body-2.jpg 4032w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The dog was really coming together at this point. The last part of the build involves making it look as good as it does in the pictures on the website by covering up the board and by threading the wires of the leg servos through plastic covers that keep them out of the way. By the end, this robot dog looks amazing.&lt;/p&gt;
&lt;p&gt;Here it is in comparison to the Freenove Robot Dog kit:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/e07bce1011c2e36bcfd33b80e08fd504/d2602/dog-compare.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.08108108108109%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAQCA//EABUBAQEAAAAAAAAAAAAAAAAAAAAB/9oADAMBAAIQAxAAAAGffdLMqH//xAAaEAEAAgMBAAAAAAAAAAAAAAABAAIDBBES/9oACAEBAAEFArWyVgvHXFnk7P/EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EAB0QAAAFBQAAAAAAAAAAAAAAAAABEBExAhIhQWH/2gAIAQEABj8C0GqK7olkLEJ//8QAHBABAAEEAwAAAAAAAAAAAAAAAQARMUFRECFh/9oACAEBAAE/IdQG2Uhqx2yt5HCMavCNQoyOP//aAAwDAQACAAMAAAAQqA//xAAVEQEBAAAAAAAAAAAAAAAAAAAAEf/aAAgBAwEBPxCq/8QAFhEBAQEAAAAAAAAAAAAAAAAAAQAR/9oACAECAQE/EEsb/8QAHBABAAMAAgMAAAAAAAAAAAAAAQARITGhQVFx/9oACAEBAAE/ELIL2iHQitBlzEOirbYjXcIO1zw+zmwDhwrrBfc//9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Dog Comparison&quot;
        title=&quot;Dog Comparison&quot;
        src=&quot;/static/e07bce1011c2e36bcfd33b80e08fd504/1c72d/dog-compare.jpg&quot;
        srcset=&quot;/static/e07bce1011c2e36bcfd33b80e08fd504/a80bd/dog-compare.jpg 148w,
/static/e07bce1011c2e36bcfd33b80e08fd504/1c91a/dog-compare.jpg 295w,
/static/e07bce1011c2e36bcfd33b80e08fd504/1c72d/dog-compare.jpg 590w,
/static/e07bce1011c2e36bcfd33b80e08fd504/a8a14/dog-compare.jpg 885w,
/static/e07bce1011c2e36bcfd33b80e08fd504/fbd2c/dog-compare.jpg 1180w,
/static/e07bce1011c2e36bcfd33b80e08fd504/d2602/dog-compare.jpg 4032w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;I was curious whether it would perform as good as it does in the website’s videos. The answer is - yes! After two rounds of calibration, the movement is really impressive.&lt;/p&gt;
&lt;p&gt;The pack comes with an IR remote and pre-programmed actions which show the dog off doing actions such as pretend to pee or to wave. Here is how it handles itself on our hardwood floor:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/959f06261b98d836a76739559f685db4/pee-and-wave.gif&quot; alt=&quot;Pee and Wave&quot;&gt;&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;This is clearly a product which has been heavily polished for a great user experience. I’ve passed on the issues I faced during the build to Petoi and I look forward to Part 2 where I will put this product through its paces by seeing how far I can push it in terms of speed and usability.&lt;/p&gt;
&lt;p&gt;Interested in buying the Petoi Bittle? Go to the &lt;a href=&quot;https://www.petoi.com/products/petoi-bittle-robot-dog?variant=44053953478968&amp;#x26;utm_source=Simon&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=bittle202304&quot;&gt;Bittle Robot Dog&lt;/a&gt; product page and then apply the code Simon7 at checkout to get 7% off your entire order! (Valid till May 14th, 2023).&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Irresponsible Denial]]></title><description><![CDATA[The recent story about a group of University of Malta students being arrested for performing a by the book responsible disclosure over a…]]></description><link>https://simonam.dev/irresponsible-denial/</link><guid isPermaLink="false">https://simonam.dev/irresponsible-denial/</guid><pubDate>Wed, 12 Apr 2023 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://timesofmalta.com/articles/view/we-wanted-help-students-arrested-exposing-freehour-security-flaw.1024757&quot;&gt;The recent story&lt;/a&gt; about a group of University of Malta students being arrested for performing a by the book responsible disclosure over a security vulnerability in a &lt;a href=&quot;https://www.freehour.eu/&quot;&gt;popular mobile app used by students, FreeHour&lt;/a&gt; is shocking. Everyone I spoke to about it today was similarly shocked or speechless.&lt;/p&gt;
&lt;p&gt;The Maltese police force have come across as heavy handed, trigger happy and ignorant, clearly unable to understand how this kind of situation should be addressed.&lt;/p&gt;
&lt;p&gt;FreeHour, particularly their CEO Ciappara has revealed incompetence by running a business completely based around a mobile application that is meant to share data, yet clearly not doing so securely or understanding how there was no malicious intent involved from the students.&lt;/p&gt;
&lt;p&gt;There are a lot of hot takes going around social media right now and many of them are right, however few are delving into the actual situation and seeing where things went wrong.&lt;/p&gt;
&lt;p&gt;It is easy to misunderstand or overlook the nuance of the situation, so let’s start from the beginning.&lt;/p&gt;
&lt;h1&gt;Responsible Disclosure&lt;/h1&gt;
&lt;p&gt;What is responsible disclosure? &lt;/p&gt;
&lt;p&gt;This is a standard method used within the Information Security (InfoSec) industry which lays out how a security researcher should approach an entity about a security vulnerability they discovered. It is meant to protect the interests of both parties, but most importantly the general wellbeing of the consumers involved.&lt;/p&gt;
&lt;p&gt;Let us construct an example situation:&lt;/p&gt;
&lt;p&gt;Jane is a security researcher. She investigates a mobile application she uses frequently and discovers that the application is leaking other people’s personal identifying information, such as their personal address, when it is not supposed to.&lt;/p&gt;
&lt;p&gt;These kind of issues are usually the result of bugs or misconfigurations, whether by incompetence or by negligence.&lt;/p&gt;
&lt;p&gt;Jane decides that, in the interest of the wellbeing of all the users of the mobile application, to contact the company and inform them of what she discovered, so that it can be fixed.&lt;/p&gt;
&lt;p&gt;Working under the rules of responsible disclosure, Jane sends an email to the company informing them:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Who she is&lt;/li&gt;
&lt;li&gt;What she discovered&lt;/li&gt;
&lt;li&gt;How to reproduce it&lt;/li&gt;
&lt;li&gt;The approximate time, date and IP Address that she had at the time of discovery&lt;/li&gt;
&lt;li&gt;A timeline about when she expects this to be resolved before she proceeds with public disclosure (the standard is 90 days)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In an ideal world, the company:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Understands that Jane is not acting maliciously&lt;/li&gt;
&lt;li&gt;Acts as soon as possible to mitigate and resolve the issue&lt;/li&gt;
&lt;li&gt;Informs Jane that the issue has been resolved so that she can confirm that the issue has been resolved&lt;/li&gt;
&lt;li&gt;Prepares a plan for disclosing the potential data breach to their customers (under &lt;a href=&quot;https://gdpr-info.eu/art-33-gdpr/&quot;&gt;Article 33 of GDPR&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Optionally offers compensation to Jane for her services&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The idea of offering compensation is that the payout to a security researcher would always be less than the:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Cost of potential litigation in the case of a severe data breach&lt;/li&gt;
&lt;li&gt;Material cost of loss of business or abuse of systems by malicious actors&lt;/li&gt;
&lt;li&gt;Reputational damage suffered by the business&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I state that it is &lt;em&gt;optional&lt;/em&gt; because there is no legal requirement for a bug bounty to exist. It exists because it is &lt;em&gt;more profitable&lt;/em&gt; to have one than to not have one.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why set a deadline for public disclosure though?&lt;/strong&gt;: This is a measure to avoid companies from passing the buck or outright ignoring the issue. Remember that the aim here is safeguard the wellbeing of consumers, not to pretend that everything is fine when in reality it is not.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Doesn’t a company have security experts to stop this from happening in the first place?&lt;/strong&gt;: In my experience, most do not. Security is sometimes referred to as an “unwritten requirement”. We obviously want our systems to “be secure”, but security is not a single action or characteristic, it is an ongoing mindset and investment.&lt;/p&gt;
&lt;p&gt;Some companies will pay for directed penetration testing (or pentests), either because they want to avoid the potential damage mentioned above or because of complaince reasons. The hard cold truth is that most companies just do not invest time and money into security.&lt;/p&gt;
&lt;p&gt;Imagine a plumber had to come to your house and offer to ensure all your plumbing is working for 500 Euro. After you paid them 500 Euro, they come back and say, its all OK! If you do not appreciate the potential downsides of having shitty (excuse the pun) plumbing, then it is easy to feel ripped off by the plumber.&lt;/p&gt;
&lt;p&gt;This meme sums it up pretty easily:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/99d93c49cb79804086eaa2cd99df7465/471ef/meme.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 72.2972972972973%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAIAAACgpqunAAAACXBIWXMAAAsTAAALEwEAmpwYAAADEElEQVQozzXOyW8bZQAF8Pm/EKeKE0JqJS4c6BGJtgJEWIQCKkQqXQ5tURNoApdsdlLSkNS1PbWNE+92bScTz+IlHnvGs8Szj2ef8dgfalOent7tJz0IAOB5HtXGDUnybce17MD1RIZhyB54G3Oi443aRJRUQVDG44miDAhMk0UAAAQAcB37NA/rPBfanq1NXNMe4mcnhcwlFnkWrxwbsmLrpqnq7sTEa0WeJt9hQ1PHg75vWMDxQ9ube1Ptght1sUvsGEb28O9njxeb+4/J1DKe+L0S3+Gp/7HAsq5pzfzp3A1mbwu80JIVxzAAAEju1WcfvvfP0rX4r1cj313JP7x25/r7K7e/9hwTmmiarU1CL5j6QRhMLzf0Ak83BYaRRenRwqcrCx+X95azf/3w/Lfv0fR69P4XT258MECKkGNZjm7Mg3A+m7/JbPbm63Rmqzrd64zZUSGV6BJo8/XrHo7wDE3g7TFL1+BdCqtDs2k46PZap0i/d05gaAfH2zhOoGifIDiyL3CMxHOiKEUjkWw2WymVtzc3JUXtnp300VPIMgyeYeFkcicagZPJYqGwtvoUhmFJEIbdLkcPz9s4ACCbycRjsej29v7zPQBA9SjTquYh2zQlQTAMo1wqJROJ3NFRo163HUeTZZokxyzdRpqHBweRra1mo55Jp7c2N2A4VUzHKRyBPMcZdjqqKI45ro1hBIpKgqDJ8qDT4Uf0aEASrTNV12MvXpTL5VKxmIi/vBClRv4VkktCvuvZsmormm/aoWn7huWYliEp0oiROGZw3q8Vcoau546Pc9lM7OCgWqm6jl1M7J/mEpAiCBSB6YLku4HvBBNRnfthYJgiTV8Mz7GTRmr9Xv/4Tyqzgj37MbX8lTVuNWNP1pYWqC4G6bKEFNIaS2lDTOnXOeSIbOTRcoFpt1SeGnNsbHUpv/4Tun6DjH6ee/RJG35Q3H1QOtyU+RE0n8/OSv82or+0t27tLn5U++M6unFr/+GXxb1VUxEAAK1SZuPet/Hlb/iXi5W1mzt3b8ae/ky1qp5t/QfFAbf2KLfi4wAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Meme&quot;
        title=&quot;Meme&quot;
        src=&quot;/static/99d93c49cb79804086eaa2cd99df7465/fcda8/meme.png&quot;
        srcset=&quot;/static/99d93c49cb79804086eaa2cd99df7465/12f09/meme.png 148w,
/static/99d93c49cb79804086eaa2cd99df7465/e4a3f/meme.png 295w,
/static/99d93c49cb79804086eaa2cd99df7465/fcda8/meme.png 590w,
/static/99d93c49cb79804086eaa2cd99df7465/efc66/meme.png 885w,
/static/99d93c49cb79804086eaa2cd99df7465/c83ae/meme.png 1180w,
/static/99d93c49cb79804086eaa2cd99df7465/471ef/meme.png 1185w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Ideally companies always set out and make their responsible disclosure policy clear and easy to find on their website. A great example of one is &lt;a href=&quot;https://www.ecb.europa.eu/services/using-our-site/responsible-disclosure/html/index.en.html&quot;&gt;this page by the European Central Bank&lt;/a&gt;. It outlines very clearly:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;What the security researcher is expected to do&lt;/li&gt;
&lt;li&gt;What the security researcher can expect from the ECB&lt;/li&gt;
&lt;li&gt;How to inform the ECB and what to include&lt;/li&gt;
&lt;li&gt;Which vulnerabilities are considered in scope and which are not&lt;/li&gt;
&lt;li&gt;That they do not offer monetary return for any successfully reported vulnerability&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This way expectations are managed very well for everyone involved.&lt;/p&gt;
&lt;p&gt;An alternative example is the &lt;a href=&quot;https://bughunters.google.com/about/rules/6625378258649088/google-and-alphabet-vulnerability-reward-program-vrp-rules&quot;&gt;Vulnerability Reward Program by Google/Alphabet&lt;/a&gt;. It includes a table stating the potential payout of successful disclosures. The more impactful the issue, the more money a security researcher can expect to earn.&lt;/p&gt;
&lt;h1&gt;Irresponsible Denial&lt;/h1&gt;
&lt;p&gt;The above example was the fairy tale ideal scenario. The reality of what happened to these students is unfortunately much worse.&lt;/p&gt;
&lt;p&gt;The details in &lt;a href=&quot;https://timesofmalta.com/articles/view/we-wanted-help-students-arrested-exposing-freehour-security-flaw.1024757&quot;&gt;the article by Times of Malta&lt;/a&gt; are a bit obscured however from what we can glean, the vulnerability was one of those that you just can’t believe someone would allow to exist (I had found a similar kind of issue myself as detailed &lt;a href=&quot;https://www.simonam.dev/accidental-pentest/&quot;&gt;here&lt;/a&gt;). The amount of data one could extract was appalling, the kind that you sincelery hope that no one else has discovered it yet.&lt;/p&gt;
&lt;p&gt;Quoting the article, the CEO of FreeHour claimed that &lt;code class=&quot;language-text&quot;&gt;“We are pleased to report that no user data was compromised and the vulnerability was addressed within 24 hours,”&lt;/code&gt;. As someone who has a little bit of experience in this field (compared to the clearly &lt;em&gt;no&lt;/em&gt; experience that Ciappara has), this statement sounds blatantly false.&lt;/p&gt;
&lt;p&gt;Quoting from the above linked ECB responsible disclosure policy, they request the following: &lt;code class=&quot;language-text&quot;&gt;the IP address(es) from which the security vulnerability was identified, together with the date and time of the discovery;&lt;/code&gt;. The reason they request this is that, without this information, you are categorically unable to tell whether the only request made for the data was made by the security researcher/s.&lt;/p&gt;
&lt;p&gt;The article implies that the only response the students received after sending the email was when &lt;strong&gt;the police arrested them at home&lt;/strong&gt;, meaning that the company did not do proper due diligence in determining whether the vulnerability had been exploited before its discovery by the students.&lt;/p&gt;
&lt;p&gt;My only criticism of the students would have been to avoid being naive about how Malta works. Everyone knows that things get done by knowing people and by establishing personal relationships. Even in &lt;a href=&quot;https://www.simonam.dev/accidental-pentest/&quot;&gt;my own experience&lt;/a&gt;, I wrote how I was paranoid that someone at the company would not understand where I was coming from and instead I went through a trusted acquitance who could vouch for me, establishing a chain of trust first.&lt;/p&gt;
&lt;p&gt;Sending a cold email, using the proper industry standard jargon, to someone who may or may not understand what you are saying or have someone available to them who can explain it, is dangerous. For all we know, the email came across as something along the lines of &lt;strong&gt;“if you don’t pay us a bug bounty, then we will tell everyone in 3 months time”&lt;/strong&gt;. I however repeat: this does not excuse the appalling behaviour that they have been treated with.&lt;/p&gt;
&lt;h1&gt;The Law&lt;/h1&gt;
&lt;p&gt;One has to ask: if Ciappara did not know what responsible disclosure is or looks like (which honestly, I can forgive him for not knowing, because from what I understand, he is not a technical person), why didn’t the police know? How did no one in the Cyber Crime Unit, upon reviewing the email sent by the students, realise what was going on?&lt;/p&gt;
&lt;p&gt;What is scary for these students is that they are being investigated under Article 337, which &lt;em&gt;very ambiguously states&lt;/em&gt; &lt;code class=&quot;language-text&quot;&gt;it is illegal to access an application without being “duly authorised by an entitled person”.&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Unfortunately this law has not aged well. In this case the system was clearly (mis-)configured to authorise &lt;strong&gt;everyone&lt;/strong&gt; who asks it for the data. &lt;/p&gt;
&lt;p&gt;This was not some fancy hack involving zero-days and social engineering whereby the students broke into a super secure system and made off with a treasure trove of data. This was the equivalent of leaving a cabinet of papers out on the street where anyone could open it and look inside.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Do not let the lack of visual element of how computers work and interact with each other make it feel like anything less.&lt;/strong&gt; People who work in Software… we see the world differently than non-technical people. We understand what happens under the hood and we do not take things at face value, which is why these kind of vulnerabilities are able to be discovered.&lt;/p&gt;
&lt;h1&gt;The future&lt;/h1&gt;
&lt;p&gt;This incident has the risk of scaring off anyone who has even the slightest interest in InfoSec or Computing from a very rewarding career and professional life. It will discourage the proper reporting of security vulnerabilities, &lt;strong&gt;leaving everyone in Malta worse off.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Seeing the people doing the good work be severely and unfairly punished, while the commercial entities get away scot free is severly unnerving and damaging.&lt;/p&gt;
&lt;p&gt;As with anything in Malta, we begin the process of learning when these major screw ups happen. Hopefully the calls for Good Samaritan laws for security researchers are heeded.&lt;/p&gt;
&lt;p&gt;I am happy to answer questions (best reach me on LinkedIn) and provide follow up as more details of the case emerge.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Initial review of the Freenove Robot Dog Kit]]></title><description><![CDATA[Introduction This is my initial review of the Freenove Robot Dog Kit for Raspberry Pi. I purchased this kit from my own funds, thanks to…]]></description><link>https://simonam.dev/robot-dog-initial-review/</link><guid isPermaLink="false">https://simonam.dev/robot-dog-initial-review/</guid><pubDate>Fri, 16 Dec 2022 22:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;This is my initial review of the &lt;a href=&quot;https://www.youtube.com/watch?v=7BmIZ8_R9d4&quot;&gt;Freenove Robot Dog Kit for Raspberry Pi&lt;/a&gt;. I purchased this kit from my own funds, thanks to &lt;a href=&quot;https://www.hotjar.com/&quot;&gt;Hotjar&lt;/a&gt;’s Personal Development perk.&lt;/p&gt;
&lt;p&gt;I am not affiliated with Freenove.&lt;/p&gt;
&lt;h1&gt;Computing on four legs&lt;/h1&gt;
&lt;p&gt;This particular kit caught my eye for a few reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I wanted to make use of one of my older Raspberry Pi 3s which had been recently retired from my home compute cluster.&lt;/li&gt;
&lt;li&gt;I wanted to expose my daughter to the idea of robotics within the household. We already have a robot vacuum and the robot dog could help expand her understanding of what a robot could be. We had seen Boston Dynamics’ Spot videos many times together.&lt;/li&gt;
&lt;li&gt;I wanted to have a platform to experiment with software and AI within a physical space with compute and network constraints present (AKA ‘at the edge’) rather than a big desktop PC with an even bigger GPU and unlimited power supply.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Buying the kit&lt;/h1&gt;
&lt;p&gt;I purchased the kit from Amazon for around 150 Euros and it arrived pretty quickly.&lt;/p&gt;
&lt;p&gt;The batteries were proving difficult to source online as this kit in particular needs high current draw batteries (the instructions include guidelines on which batteries are ideal).&lt;/p&gt;
&lt;p&gt;I eventually realised that the batteries ideal for this kit are the same kind of batteries used in Vapes. A local Vape shop had them in stock and delivered them to me in same day for around 25 Euros.&lt;/p&gt;
&lt;p&gt;Best consult the document first to make sure you can acquire the batteries easily.&lt;/p&gt;
&lt;h1&gt;Building the kit&lt;/h1&gt;
&lt;p&gt;The instructions were available on the kit’s &lt;a href=&quot;https://github.com/Freenove/Freenove_Robot_Dog_Kit_for_Raspberry_Pi&quot;&gt;GitHub Repository&lt;/a&gt; as a PDF. The document includes instructions for both the physical build and setting up the code on the server (the Raspberry Pi) and the client (in my case, my personal laptop running Ubuntu).&lt;/p&gt;
&lt;h2&gt;The physical build&lt;/h2&gt;
&lt;p&gt;The instructions for building the kit are &lt;em&gt;very good&lt;/em&gt;. I only experienced two small issues:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I did not realise there was a smaller screw driver in the box and ended up coming close to stripping some of the screw heads (this was my fault)&lt;/li&gt;
&lt;li&gt;The acrylic parts of the kit came covered in a brown paper. At first I proceeded to build the kit with the paper attached and then after realised it was just a cover that you can remove with a little force and a finger nail.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I ended up having to disassemble a few of the legs just to remove the cover followed by rebuilding them.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/2fcf66a46acb7f1dbb55316261219e25/eea4a/build1.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.08108108108109%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAMFAgT/xAAVAQEBAAAAAAAAAAAAAAAAAAAAAf/aAAwDAQACEAMQAAAB1z0liiaR/8QAGhABAQACAwAAAAAAAAAAAAAAAgEAEQMQE//aAAgBAQABBQK3yycqdqfWtJJVf//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EABkQAQADAQEAAAAAAAAAAAAAAAEAESECEP/aAAgBAQAGPwLBtlBM59dn/8QAGRABAAMBAQAAAAAAAAAAAAAAAQARIVEx/9oACAEBAAE/ISqKHXs3oFq4F9DoES6vjEABWy0HjXs//9oADAMBAAIAAwAAABAgH//EABYRAQEBAAAAAAAAAAAAAAAAAAARIf/aAAgBAwEBPxCsf//EABcRAAMBAAAAAAAAAAAAAAAAAAABESH/2gAIAQIBAT8QkRp//8QAHBABAQACAwEBAAAAAAAAAAAAAREAITFBYVFx/9oACAEBAAE/EG3WKtVYawFDGg178xOB1QH3bgDkvJvvCpDaH5lAtBIgOf/Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Building the kit 1&quot;
        title=&quot;Building the kit 1&quot;
        src=&quot;/static/2fcf66a46acb7f1dbb55316261219e25/1c72d/build1.jpg&quot;
        srcset=&quot;/static/2fcf66a46acb7f1dbb55316261219e25/a80bd/build1.jpg 148w,
/static/2fcf66a46acb7f1dbb55316261219e25/1c91a/build1.jpg 295w,
/static/2fcf66a46acb7f1dbb55316261219e25/1c72d/build1.jpg 590w,
/static/2fcf66a46acb7f1dbb55316261219e25/a8a14/build1.jpg 885w,
/static/2fcf66a46acb7f1dbb55316261219e25/fbd2c/build1.jpg 1180w,
/static/2fcf66a46acb7f1dbb55316261219e25/eea4a/build1.jpg 1280w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The process took longer than I thought it would, in part because for some parts of the process you need to take out small screws that are in separate small bags next to the servos and this is somewhat time consuming.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/c06017b9465d9925d470cda53b133338/b17f8/build2.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 45.27027027027027%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAJABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAIDBf/EABUBAQEAAAAAAAAAAAAAAAAAAAEC/9oADAMBAAIQAxAAAAGLWWKQxxP/xAAbEAACAgMBAAAAAAAAAAAAAAAAAgEDERIxMv/aAAgBAQABBQLcWWJwV+bxu//EABYRAQEBAAAAAAAAAAAAAAAAAAARYf/aAAgBAwEBPwHVf//EABURAQEAAAAAAAAAAAAAAAAAAAAR/9oACAECAQE/Aaj/xAAZEAADAAMAAAAAAAAAAAAAAAAAARIgMlH/2gAIAQEABj8CTk1UnMP/xAAbEAEAAgIDAAAAAAAAAAAAAAABABEQMUFxsf/aAAgBAQABPyFVCKOIyk2LJU+Ca+prj//aAAwDAQACAAMAAAAQW9//xAAWEQADAAAAAAAAAAAAAAAAAAAQESH/2gAIAQMBAT8QVB//xAAXEQADAQAAAAAAAAAAAAAAAAABEBEh/9oACAECAQE/EBiL/8QAHxABAAEEAQUAAAAAAAAAAAAAAREAITFBcRBRYbHw/9oACAEBAAE/EFIyJO897Dq1LSSwnAuptqgkTkCe6+rz0c/Ff//Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Building the kit 2&quot;
        title=&quot;Building the kit 2&quot;
        src=&quot;/static/c06017b9465d9925d470cda53b133338/1c72d/build2.jpg&quot;
        srcset=&quot;/static/c06017b9465d9925d470cda53b133338/a80bd/build2.jpg 148w,
/static/c06017b9465d9925d470cda53b133338/1c91a/build2.jpg 295w,
/static/c06017b9465d9925d470cda53b133338/1c72d/build2.jpg 590w,
/static/c06017b9465d9925d470cda53b133338/a8a14/build2.jpg 885w,
/static/c06017b9465d9925d470cda53b133338/fbd2c/build2.jpg 1180w,
/static/c06017b9465d9925d470cda53b133338/b17f8/build2.jpg 1600w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The final build quality comes out pretty sturdy at the end. Each of the legs will have a mess of wires passing underneath the belly of the dog which can be somewhat remedied with the cable organizing tube that comes with the box.&lt;/p&gt;
&lt;p&gt;This organizer is not listed in the instructions. The ribbon cable for the camera hangs a bit awkwardly over the top of the robot.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/4664a128f9feec83ba239e316f1afa38/b17f8/build3.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 72.2972972972973%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAOABQDASIAAhEBAxEB/8QAGQAAAgMBAAAAAAAAAAAAAAAAAAMBAgQF/8QAFgEBAQEAAAAAAAAAAAAAAAAAAAEC/9oADAMBAAIQAxAAAAFaOnObc0B//8QAGhAAAwADAQAAAAAAAAAAAAAAAQIDABESE//aAAgBAQABBQJzVX9KkzXc3TsTjyc//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPwE//8QAHBAAAgICAwAAAAAAAAAAAAAAAAECERIxECEy/9oACAEBAAY/AuvJvETcrZUtFyllx//EABwQAQEAAgIDAAAAAAAAAAAAAAERACEQUTFBof/aAAgBAQABPyHsAWprKwr4xLUG0950Y8GUUUSJo4//2gAMAwEAAgADAAAAEGTf/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPxA//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPxA//8QAGxABAQACAwEAAAAAAAAAAAAAAREAITFBYYH/2gAIAQEAAT8QqiojSBnfTgmztiIJ7jSKCibcnmDWENHH3LyAAELdYAENGf/Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Building the kit 3&quot;
        title=&quot;Building the kit 3&quot;
        src=&quot;/static/4664a128f9feec83ba239e316f1afa38/1c72d/build3.jpg&quot;
        srcset=&quot;/static/4664a128f9feec83ba239e316f1afa38/a80bd/build3.jpg 148w,
/static/4664a128f9feec83ba239e316f1afa38/1c91a/build3.jpg 295w,
/static/4664a128f9feec83ba239e316f1afa38/1c72d/build3.jpg 590w,
/static/4664a128f9feec83ba239e316f1afa38/a8a14/build3.jpg 885w,
/static/4664a128f9feec83ba239e316f1afa38/fbd2c/build3.jpg 1180w,
/static/4664a128f9feec83ba239e316f1afa38/b17f8/build3.jpg 1600w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;I tried to encourage my daughter to do the build with me however the smaller screws and pieces were discouraging for safety reasons, apart from her inability to focus on the build for longer than five minutes at a time since it was taking a while to complete and she just wanted to see it working.&lt;/p&gt;
&lt;h2&gt;The software&lt;/h2&gt;
&lt;p&gt;The software for both the client and the server are written in Python and are also available on GitHub in the same repository as the instructions.&lt;/p&gt;
&lt;p&gt;There were multiple steps in the instructions about how to set up the Raspberry Pi from scratch and even setup VNC which I initially skipped with the intention of interacting only over ssh.&lt;/p&gt;
&lt;p&gt;The entire repository is pretty big (around 544MB) so it took a while to download onto the Pi. This is mainly due to the repository including pre-compiled executables for Mac and Windows and due to &lt;code class=&quot;language-text&quot;&gt;git&lt;/code&gt; itself. Both of these occupy 532MB:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt; du -h Freenove_Robot_Dog_Kit_for_Raspberry_Pi
1.2M    Freenove_Robot_Dog_Kit_for_Raspberry_Pi/Application/windows/Face
680K    Freenove_Robot_Dog_Kit_for_Raspberry_Pi/Application/windows/Picture
72M     Freenove_Robot_Dog_Kit_for_Raspberry_Pi/Application/windows
1.2M    Freenove_Robot_Dog_Kit_for_Raspberry_Pi/Application/mac/Face
680K    Freenove_Robot_Dog_Kit_for_Raspberry_Pi/Application/mac/Picture
98M     Freenove_Robot_Dog_Kit_for_Raspberry_Pi/Application/mac
169M    Freenove_Robot_Dog_Kit_for_Raspberry_Pi/Application  &amp;lt;----
4.0K    Freenove_Robot_Dog_Kit_for_Raspberry_Pi/.git/branches
8.0K    Freenove_Robot_Dog_Kit_for_Raspberry_Pi/.git/logs/refs/remotes/origin
12K     Freenove_Robot_Dog_Kit_for_Raspberry_Pi/.git/logs/refs/remotes
8.0K    Freenove_Robot_Dog_Kit_for_Raspberry_Pi/.git/logs/refs/heads
24K     Freenove_Robot_Dog_Kit_for_Raspberry_Pi/.git/logs/refs
32K     Freenove_Robot_Dog_Kit_for_Raspberry_Pi/.git/logs
4.0K    Freenove_Robot_Dog_Kit_for_Raspberry_Pi/.git/objects/info
363M    Freenove_Robot_Dog_Kit_for_Raspberry_Pi/.git/objects/pack
363M    Freenove_Robot_Dog_Kit_for_Raspberry_Pi/.git/objects
8.0K    Freenove_Robot_Dog_Kit_for_Raspberry_Pi/.git/info
64K     Freenove_Robot_Dog_Kit_for_Raspberry_Pi/.git/hooks
4.0K    Freenove_Robot_Dog_Kit_for_Raspberry_Pi/.git/refs/tags
8.0K    Freenove_Robot_Dog_Kit_for_Raspberry_Pi/.git/refs/remotes/origin
12K     Freenove_Robot_Dog_Kit_for_Raspberry_Pi/.git/refs/remotes
8.0K    Freenove_Robot_Dog_Kit_for_Raspberry_Pi/.git/refs/heads
28K     Freenove_Robot_Dog_Kit_for_Raspberry_Pi/.git/refs
363M    Freenove_Robot_Dog_Kit_for_Raspberry_Pi/.git  &amp;lt;----
432K    Freenove_Robot_Dog_Kit_for_Raspberry_Pi/Picture
656K    Freenove_Robot_Dog_Kit_for_Raspberry_Pi/Datasheet
28K     Freenove_Robot_Dog_Kit_for_Raspberry_Pi/Code/Patch
132K    Freenove_Robot_Dog_Kit_for_Raspberry_Pi/Code/Server
1.2M    Freenove_Robot_Dog_Kit_for_Raspberry_Pi/Code/Client/Face
680K    Freenove_Robot_Dog_Kit_for_Raspberry_Pi/Code/Client/Picture
2.0M    Freenove_Robot_Dog_Kit_for_Raspberry_Pi/Code/Client
2.1M    Freenove_Robot_Dog_Kit_for_Raspberry_Pi/Code
544M    Freenove_Robot_Dog_Kit_for_Raspberry_Pi&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Getting the software to run on the Pi was somewhat straightforward and the instructions were pretty clear. Unfortunately &lt;a href=&quot;https://github.com/raspberrypi/picamera2&quot;&gt;picamera2&lt;/a&gt; had just been released and it caused some of the video functionality to break. I put the kit down for a few days only to discover that the kit’s repository was updated in the meantime to support it which unblocked me.&lt;/p&gt;
&lt;p&gt;Installing the client was not so easy. The provided instructions did not fully work and failed with incomprehensible errors about segfaults. To get it working I ended up creating an entire python environment using &lt;code class=&quot;language-text&quot;&gt;conda&lt;/code&gt; and manually installing every dependency within it. I suspect this is due to my laptop running on Ubuntu, whereas using the pre-provided Windows or Mac binaries would have been easier.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f90b99830a20021c02e336e79fb3cb80/44fd6/user-interface.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 77.02702702702703%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsTAAALEwEAmpwYAAADG0lEQVQ4y01Uu44jRRSdb2HXdgCDNAmsxMhtd3d1dT26qvr9GM8MnrHGIgPy3WSkzYYc8RGwASREfMWOEIZkkQMCIpI1pYOqeg0ER3379r2nzn1Un0wmk59ms9mf0+n099lstp9Opx7Ons/nHosg2J+fn++DINg/+/ij/eTJe/vp5OmIp0/2px+8/+bs7Oyv09PTb06Wy+WvhBA4LBdLJAlFHMceR/8RSZKAZxpclyjaCxTNAKYKECaREIIgCL4/IYS8Zoy54L+3n21t0ZRWqMxKIS2l1FKa2DRNPYwxtu0vrDSVFbp4h9JS073NjQFJku9O4jh+TNPUKbJVZZCpFAFZYpEuwDOJpmmRZRkopV6h1gopTbwimjjVBBHXB8exDMNXJ1EUPRpjMPStDebnqOsakqcQWmBYrfD5F1+irCpP5pKKooCryNkjGGKRe8LQEYZh+Ni1Ndoqt5wLdF2Hoe/QtzXyPEfTNJBSeoUO7nCapv6AIyJuRoXL8NW/JWutLUkSpxR1oX0S59wnOKKjoq5tkecGWilP7sB0dXCqoyj6j3AcgEukvixn/7+043umFKhQUKb0vXU+IcTB5LmLGQlZysBSZt0zpSOBCzwSHgciOIfINBJVI2YZSBRisQjc94NrT+R6SGLyGL54hk9efmjnX51BbwQklXj+4jnu7+/x8PCAy8tLT5pJCSYyhLrHUtZYiAqyaME5O2it3aaMUy6HAhebwZpeoe5q9P2AqqpwdXWF9XoNpRSkEBCCg3KJedZDNp+CC4mYJK6SQzVuwkiYiQylKa1g4945RY7ATder4gLSlOAsRUwZYt0hy2tEUehWxZdsTD4OJYqi1+/UHPIit33f2/V6bfM8txerlb1crSyT2oaytlpJa4rKClNbpZQHY9xmmXpblqVT+K0j/K1pW2w2GzjnMAy4ubnxO3h9fY2b9RpcKncb0DYN7u7uUBY5ur7Hdrv1G+Fybm9vXWt+cD+HHwkhbyilvxBCdkVR7rqu26VpukuSZEcp3Tl/kpCdEGJXVdWOMb6TMvO2i9Pa/Nw2zR+EkK//AX+fKAQAgiqVAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;The Client&quot;
        title=&quot;The Client&quot;
        src=&quot;/static/f90b99830a20021c02e336e79fb3cb80/fcda8/user-interface.png&quot;
        srcset=&quot;/static/f90b99830a20021c02e336e79fb3cb80/12f09/user-interface.png 148w,
/static/f90b99830a20021c02e336e79fb3cb80/e4a3f/user-interface.png 295w,
/static/f90b99830a20021c02e336e79fb3cb80/fcda8/user-interface.png 590w,
/static/f90b99830a20021c02e336e79fb3cb80/44fd6/user-interface.png 789w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Interestingly getting the video functionality to work required me to log into the Pi using VNC instead of ssh and manually starting up the server every time. It does not work if I do the same process via ssh.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/5f9861f570916a7fdd7d5688b31570af/0f98f/server.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.08108108108109%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAIEA//EABYBAQEBAAAAAAAAAAAAAAAAAAIAAf/aAAwDAQACEAMQAAABg1RMVZIR/8QAGxAAAgIDAQAAAAAAAAAAAAAAAQIAEgMEEBH/2gAIAQEAAQUCbYb1cxYBxKLyon//xAAWEQEBAQAAAAAAAAAAAAAAAAAAIRH/2gAIAQMBAT8ByI//xAAWEQEBAQAAAAAAAAAAAAAAAAAAESH/2gAIAQIBAT8BrX//xAAcEAACAgIDAAAAAAAAAAAAAAAAASExAhAygZH/2gAIAQEABj8C44+EpEp9Fao//8QAGxABAAICAwAAAAAAAAAAAAAAAQAREEEhMXH/2gAIAQEAAT8hEgpGCyPyU+5CDwZWtY3/2gAMAwEAAgADAAAAEED/AP/EABgRAAIDAAAAAAAAAAAAAAAAAAABESFx/9oACAEDAQE/EKyZk//EABcRAQADAAAAAAAAAAAAAAAAAAEQESH/2gAIAQIBAT8Q00Q//8QAHBAAAgICAwAAAAAAAAAAAAAAAREAIVGRMUFh/9oACAEBAAE/EGfYRZ16DLMgQwK05lL6UtwSQhBgKwFRnIbn/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Running the Server via VNC&quot;
        title=&quot;Running the Server via VNC&quot;
        src=&quot;/static/5f9861f570916a7fdd7d5688b31570af/1c72d/server.jpg&quot;
        srcset=&quot;/static/5f9861f570916a7fdd7d5688b31570af/a80bd/server.jpg 148w,
/static/5f9861f570916a7fdd7d5688b31570af/1c91a/server.jpg 295w,
/static/5f9861f570916a7fdd7d5688b31570af/1c72d/server.jpg 590w,
/static/5f9861f570916a7fdd7d5688b31570af/a8a14/server.jpg 885w,
/static/5f9861f570916a7fdd7d5688b31570af/fbd2c/server.jpg 1180w,
/static/5f9861f570916a7fdd7d5688b31570af/0f98f/server.jpg 1920w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Another issue is that the dog is setup to connect to a WiFi network. This introduces both latency and the fact that if I had to take the robot with me elsewhere then I would not be able to connect to it without plugging the Pi into a screen and keyboard and setting up a new WiFi connection.&lt;/p&gt;
&lt;p&gt;The above issues were a particular pain whenever I wanted to show the robot to anyone. I would have to get the robot, turn it on, wait for it to finish booting, connect with VNC, start up the server, switch to my laptop, activate the correct conda environment, start the client and finally connect.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d90f90b9575fb80cbc0fdffdd4ce7d26/54af7/in-action.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.08108108108109%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAABAUA/8QAFQEBAQAAAAAAAAAAAAAAAAAAAgH/2gAMAwEAAhADEAAAARLJZCkahjf/xAAaEAACAwEBAAAAAAAAAAAAAAABAgADEiJE/9oACAEBAAEFAgOtsitbYT6CqiZWf//EABURAQEAAAAAAAAAAAAAAAAAAAEQ/9oACAEDAQE/AQn/xAAXEQEAAwAAAAAAAAAAAAAAAAAAAQIR/9oACAECAQE/AbS1/8QAGxAAAgMAAwAAAAAAAAAAAAAAAAECITFBUXH/2gAIAQEABj8CcORRTVVZ0P0ww//EABwQAAICAgMAAAAAAAAAAAAAAAABESExQWGRof/aAAgBAQABPyGkw5RLJcLxJ6azxTE+2JKW1Y5sD//aAAwDAQACAAMAAAAQG9//xAAWEQEBAQAAAAAAAAAAAAAAAAAAETH/2gAIAQMBAT8QhiP/xAAXEQEBAQEAAAAAAAAAAAAAAAABABFR/9oACAECAQE/ENQGzy//xAAeEAEAAgEEAwAAAAAAAAAAAAABABEhMUFxgVFh0f/aAAgBAQABPxAi8Se3xCpmkzS2i+NDqXQaikaPuMAKCGOYLzCUlr2z4lP/2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Dog in action&quot;
        title=&quot;Dog in action&quot;
        src=&quot;/static/d90f90b9575fb80cbc0fdffdd4ce7d26/1c72d/in-action.jpg&quot;
        srcset=&quot;/static/d90f90b9575fb80cbc0fdffdd4ce7d26/a80bd/in-action.jpg 148w,
/static/d90f90b9575fb80cbc0fdffdd4ce7d26/1c91a/in-action.jpg 295w,
/static/d90f90b9575fb80cbc0fdffdd4ce7d26/1c72d/in-action.jpg 590w,
/static/d90f90b9575fb80cbc0fdffdd4ce7d26/a8a14/in-action.jpg 885w,
/static/d90f90b9575fb80cbc0fdffdd4ce7d26/fbd2c/in-action.jpg 1180w,
/static/d90f90b9575fb80cbc0fdffdd4ce7d26/54af7/in-action.jpg 2016w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;All of the functionality worked as expected except for the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The kit advertises that it has ‘red ball following’ functionality and actually comes with a red ball in the box, however regardless of what I tried, the dog never reacted to the red ball infront of it.&lt;/li&gt;
&lt;li&gt;The kit advertises some facial recognition functionality which also did not work out of the box.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Latency was an issue overall in some instance, especially when the robot moved into certain pockets of theh house where I knew the WiFi signal falters.&lt;/p&gt;
&lt;h1&gt;Final Thoughts&lt;/h1&gt;
&lt;p&gt;The kit is great and can get to a working state with only minor tinkering. It is nowhere close to being anything like Boston Dynamic’s Spot in agility and polish but it definitely makes you feel like you have a tiny version of one.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/d3ff9bebea58c8d4468a6f18f9c6b085/dog.gif&quot; alt=&quot;Dog moving&quot;&gt;&lt;/p&gt;
&lt;p&gt;If you are looking for a robot kit that younger children could enjoy and easy to use, this is definitely not the ideal purchase.&lt;/p&gt;
&lt;p&gt;If you want a platform open to experimenting and learning with, this is definitely a great option. I highly recommend being at least somewhat experienced in running python scripts and knowing how to install dependencies before considering it.&lt;/p&gt;
&lt;h1&gt;The future&lt;/h1&gt;
&lt;p&gt;I think this kit has several avenues with which it could be improved upon:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The observability. The client does not store any data historically (such as the video stream, battery level, gyroscope values or state of the servos). It would be great to have this data exposed via something such as Prometheus or MQTT to make it easier to visualise.&lt;/li&gt;
&lt;li&gt;The time to getting started. In my opinion, getting started should be as easy as running something along the lines of &lt;code class=&quot;language-text&quot;&gt;docker pull robotdog&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;docker run robotdog&lt;/code&gt;. Some projects such as flightradar24 offer a Raspberry Pi &lt;a href=&quot;https://www.flightradar24.com/build-your-own&quot;&gt;OS image&lt;/a&gt; which you can install directly and get started with much faster than installing each part yourself separately.&lt;/li&gt;
&lt;li&gt;The ease of use. An alternative to having a QT based client would be to have the Pi host a web based client and the client connects to a WiFi network provided by the Pi. Whilst this approach has its downsides, this would greatly reduce the latency between issuing commands and seeing the result as well as making the robot controllable from any device capable of a WiFi connection and running a conventional web browser.&lt;/li&gt;
&lt;li&gt;The ease of extensibility. Currently extending the functionality means having to edit the code and restarting the server. An alternative model could be one of plugins, such as being able to run an image classifier on the video stream and have the results available via REST or MQTT.&lt;/li&gt;
&lt;li&gt;An autonomous mode. Right now it only operates in a manually controlled mode. An autonomous mode where it walks and looks around and exhibits some basic behaviour would be a great alternative.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I am planning on using this kit as a test bed for experimenting what I am learning as I read for a &lt;a href=&quot;https://www.um.edu.mt/courses/overview/PMSCIARIPET7-2022-3-O&quot;&gt;Masters in AI from University of Malta&lt;/a&gt; so I will likely tackle a number of the above issues as I go along.&lt;/p&gt;
&lt;p&gt;Here’s to hoping we don’t end up with this…&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/e798b7412d5618dc3c5dc03db80803e6/metalhead.gif&quot; alt=&quot;Metalhead - Black Mirror&quot;&gt;&lt;/p&gt;
&lt;p&gt;Thanks for reading!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[So you want to move some data from A to B in AWS]]></title><description><![CDATA[At my day job I’ve been recently thrown head-first into designing, building and improving an AWS setup. One of our requirements was to have…]]></description><link>https://simonam.dev/aws-s3-to-efs/</link><guid isPermaLink="false">https://simonam.dev/aws-s3-to-efs/</guid><pubDate>Wed, 14 Dec 2022 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;At my day job I’ve been recently thrown head-first into designing, building and improving an AWS setup.&lt;/p&gt;
&lt;p&gt;One of our requirements was to have a script in ECS run against some JSON files when these files are either uploaded or updated within S3. Simple enough.&lt;/p&gt;
&lt;p&gt;Given AWS has thousands of ways of doing things, I safely assumed there’s probably an easy way to expose S3 to ECS as some sort of filesystem. Turns out there is, but only if you are running ECS on EC2 and not Fargate. Coming from a heavy Docker and Kubernetes background, I figured there must be some way to expose a “volume” to ECS and indeed I found AWS EFS.&lt;/p&gt;
&lt;p&gt;AWS EFS which is block (not Object) storage, simply put allows you to connect a filesystem to an ECS task. &lt;/p&gt;
&lt;p&gt;Once I figured this out, the next issue was: how do I get my data from S3 to EFS? I thought this would be a good use case for Lambda and in the course of my reserach I discovered AWS DataSync.&lt;/p&gt;
&lt;p&gt;DataSync offers a managed way to sync data within AWS. It offers multiple sources and destinations and can even do syncing data in and out of AWS. Both S3 and EFS are supported so this looked like the perfect solution for our problem.&lt;/p&gt;
&lt;p&gt;After multiple false starts until I configured it properly, DataSync was working. The next issue I had to deal with: how do I trigger DataSync when the S3 upload happens?&lt;/p&gt;
&lt;p&gt;My research led me to S3 Bucket Notifications - you can configure an S3 bucket to notify a Lambda when its contents change. This resulted in the following &lt;del&gt;stack of dominos&lt;/del&gt; setup:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;S3 Bucket&lt;/strong&gt; -&gt; &lt;strong&gt;S3 Bucket Notifiction&lt;/strong&gt; -&gt; &lt;strong&gt;Lambda&lt;/strong&gt; -&gt; &lt;em&gt;Lambda uses AWS API to trigger DataSync and wait for completion&lt;/em&gt; -&gt; &lt;strong&gt;DataSync Task to copy from S3 to EFS&lt;/strong&gt; -&gt; &lt;em&gt;Lambda uses AWS API to trigger ECS Task&lt;/em&gt; -&gt; &lt;strong&gt;ECS Task&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This setup worked… but not too well. It had a number of disadvantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The S3 bucket notification fires for &lt;em&gt;every file changed&lt;/em&gt; and the user was uploading all of them, even if they had not changed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This was triggering several Lambdas. I resolved this by limiting the labmda concurrency to 1 and I did some reserach on how to buffer and consume bucket notifications through something such as SQS.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The Lambda spent a LONG time waiting.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;DataSync tasks, even for tiny amounts of data syncing (we were moving around 50KB), take on the order of several minutes to start. This meant that the lambda had to wait and keep checking via the API whether the DataSync task had completed yet or not, which was all time we were paying for. There was also no easy way to surface this status to our end user, who had no idea whether the process had finished or even started and would likely retry their upload, excacerbating the problem.&lt;/p&gt;
&lt;p&gt;Despite being the perfect solution on paper, it felt like I was increasingly trying to convince myself that there is no better solution than DataSync.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Numeric Scripts]]></title><description><![CDATA[I’m often starting new small projects, especially ones that involve web scraping. This habit of gathering one off odd datasets has come in…]]></description><link>https://simonam.dev/numeric-scripts/</link><guid isPermaLink="false">https://simonam.dev/numeric-scripts/</guid><pubDate>Wed, 14 Dec 2022 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I’m often starting new small projects, especially ones that involve web scraping. This habit of gathering one off odd datasets has come in handy a few times when I want to back an opinion up by fact.&lt;/p&gt;
&lt;p&gt;Since I only check in on these projects rarely, I typically forget the end to end flow of how to run each step (such as scrape -&gt; parse -&gt; scrape -&gt; parse -&gt; clean up data), which I tend to split into separate scripts.&lt;/p&gt;
&lt;p&gt;I’ve started using a “numeric script” approach. &lt;strong&gt;scrape_links.py&lt;/strong&gt;, &lt;strong&gt;parse&lt;em&gt;links&lt;/em&gt;for_urls.py&lt;/strong&gt;, &lt;strong&gt;check_links.py&lt;/strong&gt;, and &lt;strong&gt;extract_links.py&lt;/strong&gt; on their own have an ambiguous flow, while the following makes it much easier for me to remember:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;01&lt;em&gt;scrape&lt;/em&gt;links.py&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;02&lt;em&gt;check&lt;/em&gt;links.py&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;03&lt;em&gt;extract&lt;/em&gt;links.py&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;04&lt;em&gt;parse&lt;/em&gt;links&lt;em&gt;for&lt;/em&gt;urls.py&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I could of course write a quick README stating the order, but this approach is ✨ &lt;em&gt;self-documenting&lt;/em&gt; ✨.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[How to talk to the public internet from AWS ECS]]></title><description><![CDATA[At $WORK I’ve been recently thrown head-first into designing, building and improving an AWS setup and this reality with ECS networking…]]></description><link>https://simonam.dev/talk-to-the-internet-on-ecs/</link><guid isPermaLink="false">https://simonam.dev/talk-to-the-internet-on-ecs/</guid><pubDate>Wed, 14 Dec 2022 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;At $WORK I’ve been recently thrown head-first into designing, building and improving an AWS setup and this reality with ECS networking really tripped me up.&lt;/p&gt;
&lt;p&gt;To get going quickly, we setup Fargate with a Public IP address. That allowed us to verify that our containerised backend was being built, pushed and run correctly in ECS and that we were able to CURL it over the internet - success!&lt;/p&gt;
&lt;p&gt;We knew this wasn’t the end goal, so I got to work setting up an ALB, having &lt;/p&gt;</content:encoded></item><item><title><![CDATA[Lessons learned about Web Scraping]]></title><description><![CDATA[Introduction I got my start in software back in 2015. Right out of Codeacademy’s Python guide, I got interested in web scraping and the rest…]]></description><link>https://simonam.dev/webscraping-lessons/</link><guid isPermaLink="false">https://simonam.dev/webscraping-lessons/</guid><pubDate>Wed, 14 Dec 2022 22:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;I got my start in software back in 2015. Right out of Codeacademy’s Python guide, I got interested in web scraping and the rest is history.&lt;/p&gt;
&lt;p&gt;This blog post is a collection of the lessons learned through the years and multiple projects in this space.&lt;/p&gt;
&lt;h1&gt;Split your scraping and parsing&lt;/h1&gt;
&lt;p&gt;Initially my web scrapers would attempt to parse the data (such as extract valuable information from HTML) as soon as the scraping was completed.
This approach inevitably led to inefficient scraping as well as the potential for bugs unrelated to scraping causing the scraper to break.&lt;/p&gt;
&lt;p&gt;Nowadays I split up the scraping and parsing processes. I decouple them by having the scraper either save the raw data to a database or to files, with the parser being a separate process doing reading of that data and extracting the valuable information.&lt;/p&gt;
&lt;p&gt;To keep things simple, these different modes typically live in the same script, with a CLI argument used to run it in scraping mode or in parsing mode. This also allows for easy code re-use between both modes.&lt;/p&gt;
&lt;h1&gt;Keep your full source content&lt;/h1&gt;
&lt;p&gt;In a number of projects I have experienced a need to go back and get some new information from a source that I was already scraping. In each case, I ended up having to begin scraping again from scratch.&lt;/p&gt;
&lt;p&gt;An alternative to this is to &lt;strong&gt;keep all the source content&lt;/strong&gt;. Whether you do it by storing them as files, or saving to a database, it doesn’t matter as long as it is easy for you to re-inspect it. This approach also allows you to iterate on your parsing without making repeated calls to the same pages over and over.&lt;/p&gt;
&lt;p&gt;If storage space is an issue, either because you are retrieving large files (such as wav files) or you expect to scrape large volumes of data, leverage compression techniques such as brotli to minimize the impact.&lt;/p&gt;
&lt;h1&gt;Respect your scraping targets&lt;/h1&gt;
&lt;p&gt;In the beginning I would scrape rather liberally. &lt;code class=&quot;language-text&quot;&gt;Computers are fast!&lt;/code&gt; was my reasoning. Eventually I had one too many run-ins with my IP being flagged or blocked by my scraping targets (either automatically with a tool such as Cloudflare or even manually in some cases).&lt;/p&gt;
&lt;p&gt;Whilst there are workarounds (such as rotating your IP Address using a proxy), I have found that scraping respectfully is a better solution.&lt;/p&gt;
&lt;p&gt;By implementing large delays between your scrapes, avoiding peak traffic time and lowering my need to have all the data NOW, I’ve largely eliminated these kind of issues.&lt;/p&gt;
&lt;h1&gt;If you can, scrape asynchronously&lt;/h1&gt;
&lt;p&gt;If your scrape target has liberal limits, consider scraping asynchronously. What this means is using a distributed task runner (such as &lt;a href=&quot;https://docs.celeryq.dev/en/stable/getting-started/introduction.html&quot;&gt;Celery&lt;/a&gt; for Python or &lt;a href=&quot;https://github.com/hibiken/asynq&quot;&gt;Asnyq&lt;/a&gt; for Go). Running multiple workers will reduce the impact of the fact that web scraping tends to be an I/O bound problem.&lt;/p&gt;
&lt;h1&gt;Handle exceptions and timeouts&lt;/h1&gt;
&lt;p&gt;Often enough in my first few projects I would leave a scraper running overnight only to discover that at 2am it died of an unhandled exception (such as a TLS handshake failure) or got stuck waiting for a response because &lt;a href=&quot;https://stackoverflow.com/a/17782541&quot;&gt;requests.get() doesn’t have a default timeout&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Handle exceptions liberally and set sane default timeouts to avoid falling in this pit. You can also work around it by having your process automatically restart if it fails, however your script could get into a loop where it is failing on a specific page and will keep failing over and over. In this case I prefer to have the script skip that page and move on.&lt;/p&gt;
&lt;h1&gt;Store all your data in one place&lt;/h1&gt;
&lt;p&gt;This is a more recent decision, inspired by the idea of &lt;a href=&quot;http://paulgraham.com/ds.html&quot;&gt;doing things that don’t scale&lt;/a&gt;. There’s likely an academic answer to the question: “which format/database is ideal to store scraped HTML?” however the more realist answer I now prefer is “postgres” regardless of the domain.&lt;/p&gt;
&lt;p&gt;This for me is a mix of simplicity (all your data, both raw and parsed is in one place) and a lack of time to dedicate more than is absolutely necessary to side projects. Scaling issues are a good issue to have, it likely means your project is successful by some metric.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[That time I discovered a full account takeover vulnerability]]></title><description><![CDATA[Important note Similar to the accidental pentest, this story happened over two years ago and the details have been fudged to protect both…]]></description><link>https://simonam.dev/total-account-takeover/</link><guid isPermaLink="false">https://simonam.dev/total-account-takeover/</guid><pubDate>Fri, 04 Nov 2022 22:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Important note&lt;/h1&gt;
&lt;p&gt;Similar to the &lt;a href=&quot;/accidental-pentest&quot;&gt;accidental pentest&lt;/a&gt;, this story happened over two years ago and the details have been fudged to protect both the innocent and the guilty.&lt;/p&gt;
&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;One of our clients had a successful website and were looking to expand into the mobile space. The task to build this mobile application was assigned to me.&lt;/p&gt;
&lt;p&gt;The supplier which built and operated their website for them was also going to provide the backend for us to wire up the App to.&lt;/p&gt;
&lt;p&gt;One of the first items I requested was any sort of API documentation they had and a staging environment so that I could test the integration without worrying about affecting their production instance.&lt;/p&gt;
&lt;h1&gt;The project&lt;/h1&gt;
&lt;p&gt;The first issue arose when they came back to us stating that they were having difficulty setting up a staging server for us and that we should use the production environment for testing.&lt;/p&gt;
&lt;p&gt;This immediately rubbed me the wrong way. Did they not even have a staging environment for their own testing? Turns out, no, they did not.&lt;/p&gt;
&lt;p&gt;I did my best to not allow this issue to get to me: I could understand that, on occasion, corners are cut, especially when resources are scarce (whether its time, financial or competence). I decided to, with sufficient CYA, proceed to use their production API whilst exercising caution.&lt;/p&gt;
&lt;p&gt;The API documentation (provided in a Word document) was mostly sufficient for us to proceed with a good part of the work, which was a counter-balancing relief to any issues so far.&lt;/p&gt;
&lt;h1&gt;Working in production&lt;/h1&gt;
&lt;p&gt;We started to work our way through creating the screens that our designer had given us. We prioritised the work where the API was already available and using mocks for where it was not.&lt;/p&gt;
&lt;p&gt;Overall the project proceeded quite smoothly from there.&lt;/p&gt;
&lt;h1&gt;Testing in production&lt;/h1&gt;
&lt;p&gt;At one point I wanted to test how the user profile updates itself by changing something on my own profile on the production environment.&lt;/p&gt;
&lt;p&gt;I navigated to the user profile page, which had a URL similar to this:&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;www.example.com/user/profile&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;…and decided to change my name whilst watching the network tab to observe the behaviour.&lt;/p&gt;
&lt;p&gt;Funnily enough, whilst other parts of the website performed asynchronous requests using JavaScript, this one did not. It did not even perform a POST request from a form. This implementation smell triggered the curiosity snowball that eventually formed this entire story.&lt;/p&gt;
&lt;h1&gt;The smell&lt;/h1&gt;
&lt;p&gt;My profile had clearly been updated as I could see the new value, both on the website and on the API. So how did the website manage to update it?&lt;/p&gt;
&lt;p&gt;Then I realised that the URL had changed: it was no longer &lt;code class=&quot;language-text&quot;&gt;www.example.com/user/profile&lt;/code&gt; but instead it had become something similar to the following:&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;www.example.com/user/profile?email=example@example.com&amp;amp;name=Simon&amp;amp;surname=Test%20Surname&amp;amp;gender=M&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This was stinky and smelly. Not only did the website perform an update using a GET request, but as a result of doing so, it had to pass all the values as query parameters. To confirm my suspicion, I changed the &lt;code class=&quot;language-text&quot;&gt;surname&lt;/code&gt; query parameter value and retried the URL, succeeding in changing the saved value.&lt;/p&gt;
&lt;p&gt;Admittedly, while smelly, meaning it is the kind of code that I would have flagged as an issue in a code review, it does the job it is meant to do. This blog post however is not about smelly code, but rather about…&lt;/p&gt;
&lt;h1&gt;The vulnerability&lt;/h1&gt;
&lt;p&gt;Between the lack of staging server, the API docs in a Word document and the GET request performing modifications, it was becoming clear to me that corners had been cut on this backend.&lt;/p&gt;
&lt;p&gt;From experience I also know that when corners are being cut, security tends to be one of the first that are cut.&lt;/p&gt;
&lt;p&gt;So the thought passed through my head: given what I know so far about the supplier, what are the chances that they are ignoring extra keys on this GET request that modifies the User Profile?&lt;/p&gt;
&lt;p&gt;It was super easy to test out. I simply typed out the following URL into my browser:&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;www.example.com/user/profile?email=my-colleague@example.com&amp;amp;password=foobar123&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;I logged out, then attempted to log into &lt;code class=&quot;language-text&quot;&gt;my-colleage@example.com&lt;/code&gt; with password &lt;code class=&quot;language-text&quot;&gt;foobar123&lt;/code&gt; and sure enough… I got in.&lt;/p&gt;
&lt;h1&gt;Raising the issue&lt;/h1&gt;
&lt;p&gt;I had successfully reproduced a full account takeover. I also knew that this was a non-trivally popular website in Malta, with multiple thousands of registered users, each with a certain set of PII and potentially sensitive data in their accounts.&lt;/p&gt;
&lt;p&gt;I could not keep this to myself. I raised it through my then manager who raised it with the client who took it up with the supplier. The issue was quickly patched, faster than I had seen any other issue solved in this project so far.&lt;/p&gt;
&lt;h1&gt;In Conclusion&lt;/h1&gt;
&lt;p&gt;The client shared with us that they had been having issues with the supplier for a while and wished to switch away, however their hands were tied for now.&lt;/p&gt;
&lt;p&gt;I got what amounted to a pat on the back and acquired the knowledge about how to avoid creating this vulnerability in the first place.&lt;/p&gt;
&lt;p&gt;This was yet another simple issue that could have been easily solved long before the code hit production with a simple automated test.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Never go hungry again]]></title><description><![CDATA[I was recently stuck in traffic (which is a rare occurrence given I work remotely) so I took the opportunity to observe my surroundings. The…]]></description><link>https://simonam.dev/never-go-hungry-again/</link><guid isPermaLink="false">https://simonam.dev/never-go-hungry-again/</guid><pubDate>Mon, 31 Oct 2022 12:20:00 GMT</pubDate><content:encoded>&lt;p&gt;I was recently stuck in traffic (which is a rare occurrence given I work remotely) so I took the opportunity to observe my surroundings.&lt;/p&gt;
&lt;p&gt;The bus stuck in front of me had an advert for one of the food delivery companies here in Malta, with the slogan “Never go hungry again”.&lt;/p&gt;
&lt;p&gt;This slogan rubbed me the wrong way and I thought it worthwhile to share why after musing on it for a while.&lt;/p&gt;
&lt;p&gt;Here are the issues that I find with this slogan:&lt;/p&gt;
&lt;h1&gt;It is logically paradoxical&lt;/h1&gt;
&lt;p&gt;A food delivery company is not offering you food security or even food: it is a highly efficient middleman using supply, demand and technology to get goods from point A to point B. Convenient food just happens to be a good in demand right now.&lt;/p&gt;
&lt;p&gt;If all restaurants had to fold, food delivery companies would fold too and the age of convenient food would end.&lt;/p&gt;
&lt;h1&gt;It is economically paradoxical&lt;/h1&gt;
&lt;p&gt;Given how obscenely more expensive restaurant food is compared to home cooking (a fact that restaurant owners would &lt;a href=&quot;https://timesofmalta.com/articles/view/dont-compare-restaurants-home-cooking-catering-association-says.972335&quot;&gt;hope you conventiently ignore&lt;/a&gt;, unsurprising given their vested interests), the idea that a food delivery middleman can assure food security is economically paradoxical.&lt;/p&gt;
&lt;p&gt;You’re paying more for convenience today over retaining more funds to ensure you can purchase food tomorrow.&lt;/p&gt;
&lt;p&gt;This is re-inforced by the fact that &lt;a href=&quot;https://timesofmalta.com/articles/view/cost-convenience-food-delivery-comes-price-hike.978797&quot;&gt;several restaurants were discovered with inflated prices&lt;/a&gt; for listings on delivery apps, supposedly to compensate for the commission charged by the delivery company.&lt;/p&gt;
&lt;h1&gt;It is socially tone deaf&lt;/h1&gt;
&lt;p&gt;In a time of explosive inflation and rising cost of living, having a slogan referring to food security, even indirectly, is tone deaf, even somewhat insensitive. An &lt;a href=&quot;https://timesofmalta.com/articles/view/85-300-risk-poverty-2020.963530&quot;&gt;increasing number&lt;/a&gt; of people in Malta are being classified as at risk of poverty.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;My aim is not to take a moral high ground, I make use these services myself, especially when the alternative is much less efficient.&lt;/p&gt;
&lt;p&gt;My aim instead is to wonder whether a more humanistic approach could have been taken, one which can exist within the current socio-economic context without coming close to mocking it.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Remote done Well Part 1]]></title><description><![CDATA[Introduction After I had spent several months working remotely for my previous employer due to pandemic restrictions I realised that: I…]]></description><link>https://simonam.dev/remote-done-well-1/</link><guid isPermaLink="false">https://simonam.dev/remote-done-well-1/</guid><pubDate>Tue, 27 Sep 2022 20:20:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;After I had spent several months working remotely for my previous employer due to pandemic restrictions I realised that:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;I wanted to work remotely every day for the forseeable future&lt;/li&gt;
&lt;li&gt;I wanted to do so at a company for which remote was a feature not a bug&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I have now worked at &lt;a href=&quot;https://www.hotjar.com&quot;&gt;Hotjar&lt;/a&gt; for 1.5 years now. It has been operating fully remotely since it was founded in 2014 and as a result has very strong processes which allow it to continue to operate efficiently, especially as it grows in terms of headcount.&lt;/p&gt;
&lt;p&gt;I want to document the more hidden and nuanced parts of working at a company where remote is done &lt;em&gt;well&lt;/em&gt;, the kind that you aren’t really exposed to in a job vacancy (we’re &lt;a href=&quot;https://www.hotjar.com/careers/&quot;&gt;hiring&lt;/a&gt; by the way).&lt;/p&gt;
&lt;h1&gt;Meetings are often recorded&lt;/h1&gt;
&lt;p&gt;You need to miss the weekly all hands because you need to pick up your aunt from the airport? That’s fine, you can watch the recording later.&lt;/p&gt;
&lt;p&gt;Not every meeting is recorded. Whilst meetings with large audiences or workshops tend to be recorded, team stand-ups, syncs and refinements typically are not.&lt;/p&gt;
&lt;p&gt;This allows for an incredible amount of flexibility with your schedule, especially useful since employees are spread across multiple timezones.&lt;/p&gt;
&lt;h1&gt;Strong documentation culture&lt;/h1&gt;
&lt;p&gt;Over my tenure I’ve reversed my sequence of acquiring knowledge. It used to be:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Ask a colleague&lt;/li&gt;
&lt;li&gt;Read the code&lt;/li&gt;
&lt;li&gt;Check documentation (if it exists)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now it is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Check documentation&lt;/li&gt;
&lt;li&gt;Read the code&lt;/li&gt;
&lt;li&gt;Ask a colleague&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Given that colleagues are spread around the world and may be still asleep, just waking up, ramping up focus after their first hot beverage or eating lunch at any time, relying on synchronous communication all the time is highly counter-productive.&lt;/p&gt;
&lt;p&gt;As a result, it is more productive to see if it’s been written down somewhere before asking someone about it. Even when I end up asking a colleague, I have a 50/50 chance that their answer will include a link to a document that my confluence-foo was unable to surface.&lt;/p&gt;
&lt;h1&gt;Occasional, intentional social calls&lt;/h1&gt;
&lt;p&gt;One of my colleagues once asked me: “do you really sit on a call watching each other eat food at their desk? Isn’t it just awkward munching?“.&lt;/p&gt;
&lt;p&gt;It is only awkward if you make it awkward.&lt;/p&gt;
&lt;p&gt;Over the past 1.5 years our team has perfected the fortnightly social call:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Order some food (covered by our Work Together perk)&lt;/li&gt;
&lt;li&gt;Talk about anything but work (pets, life, achievements, drugs, etc) for the first half hour or so&lt;/li&gt;
&lt;li&gt;spend the next 45 minutes playing games together. &lt;a href=&quot;https://really.boring.website/&quot;&gt;Scattergories&lt;/a&gt;, &lt;a href=&quot;https://skribbl.io/&quot;&gt;Skribbl&lt;/a&gt;, &lt;a href=&quot;https://garticphone.com/&quot;&gt;Gartic Phone&lt;/a&gt; and &lt;a href=&quot;https://www.geoguessr.com/&quot;&gt;Geoguessr&lt;/a&gt; are all awesome options.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Every 2-3 weeks run an optional, no-agenda social call for the last 30 minutes of the day on Friday. Right before the weekend starts there is not anything you can accomplish that cannot wait until Monday morning.&lt;/p&gt;
&lt;p&gt;A remote environment does not allow easily for natural conversations and social relationship building. It does not mean you cannot have social relationships with your colleagues, it just means that you need to be intentional about it.&lt;/p&gt;
&lt;h1&gt;Core hours&lt;/h1&gt;
&lt;p&gt;There are three hours in the day where there’s an expectation that you can be reached if you are working that day: 14:00 - 17:00 CET.&lt;/p&gt;
&lt;p&gt;Other than that, there are no particular restrictions about when you should be ‘butt in seat’.&lt;/p&gt;
&lt;p&gt;Teams tend to converge and settle on a steady state of meeting &amp;#x26; ceremony times that work for all their members, such as starting a daily sync at 11:00 CET to allow for people who wake up later as well as people in earlier timezones to join comfortably.&lt;/p&gt;
&lt;h1&gt;Salary Equity&lt;/h1&gt;
&lt;p&gt;A common criticism of remote work is that it allows work to be farmed out to countries with lower cost of living, resulting in cost saving due to workers more often than not being temporary contractors and accepting of a lower salary.&lt;/p&gt;
&lt;p&gt;At Hotjar, the gross salaries are the same for the sheer majority of roles. There’s no reason that someone living in Nigeria should be paid less than someone living in London for the same work. Pay and perks are also the exact same regardless of whether you are an employee or a contractor.&lt;/p&gt;
&lt;p&gt;Whether this is a fair approach due to difference in taxation is another debate entirely, however it is definitely more equitable.&lt;/p&gt;
&lt;p&gt;This also leads to…&lt;/p&gt;
&lt;h1&gt;Diversity&lt;/h1&gt;
&lt;p&gt;I never realised how mono-cultural my previous roles were before I worked at Hotjar. Often enough I find myself on a call with people spanning multiple continents. The focus on equity contributes heavily towards a beautifully diverse team.&lt;/p&gt;
&lt;h1&gt;etc&lt;/h1&gt;
&lt;p&gt;I’ll definitely write another post as I continue to muse more about these points.&lt;/p&gt;
&lt;p&gt;Did I mention we’re &lt;a href=&quot;https://www.hotjar.com/careers/&quot;&gt;hiring&lt;/a&gt;? Come work with me!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Remote done Well Part 2]]></title><description><![CDATA[Introduction After a good amount of interest and feedback on my first article about Remote done Well, I’ve determined another set of good…]]></description><link>https://simonam.dev/remote-done-well-2/</link><guid isPermaLink="false">https://simonam.dev/remote-done-well-2/</guid><pubDate>Tue, 27 Sep 2022 20:20:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;After a good amount of interest and feedback on my &lt;a href=&quot;/remote-done-well-1&quot;&gt;first article about Remote done Well&lt;/a&gt;, I’ve determined another set of good practices I have observed at &lt;a href=&quot;www.hotjar.com&quot;&gt;Hotjar&lt;/a&gt; where I have worked for the past 1.5 years that allow it to succeed as a fully remote company.&lt;/p&gt;
&lt;h1&gt;Slack Statuses&lt;/h1&gt;
&lt;p&gt;TODO&lt;/p&gt;
&lt;h1&gt;Standard Headsets&lt;/h1&gt;
&lt;p&gt;TODO&lt;/p&gt;
&lt;h1&gt;Donut Calls&lt;/h1&gt;
&lt;p&gt;TODO&lt;/p&gt;
&lt;h1&gt;Helpdesks&lt;/h1&gt;
&lt;p&gt;TODO&lt;/p&gt;
&lt;h1&gt;Radio Discipline&lt;/h1&gt;
&lt;p&gt;TODO&lt;/p&gt;</content:encoded></item><item><title><![CDATA[That time I accidentally did a pentest]]></title><description><![CDATA[Important note This story happened over two years ago and the details have been fudged. I offered the company involved an opportunity to…]]></description><link>https://simonam.dev/accidental-pentest/</link><guid isPermaLink="false">https://simonam.dev/accidental-pentest/</guid><pubDate>Fri, 26 Aug 2022 17:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Important note&lt;/h1&gt;
&lt;p&gt;This story happened over two years ago and the details have been fudged. I offered the company involved an opportunity to review this blog post before publishing and they approved it without requesting any changes.&lt;/p&gt;
&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;My wife benefits from a partial refund on some services as a perk provided by her workplace. At the time we were renovating a house so every Euro we could claw back and put into the renovation meant getting one step closer to being able to move in.&lt;/p&gt;
&lt;p&gt;To benefit from this refund, we had to provide the receipts of the year &lt;em&gt;before&lt;/em&gt; the previous. Meaning that even though we would apply for the refund in (for example) 2016, I would need the receipts of 2014.&lt;/p&gt;
&lt;h1&gt;The Bills&lt;/h1&gt;
&lt;p&gt;I logged in to a specific company’s website since the account was in my name and I clicked through to the billing page… only to find the last few bills only available. 🤔. I had opened and been paying my account for longer than that for sure and this would have left me with a few months of bills missing.&lt;/p&gt;
&lt;p&gt;I hopped onto their support chat asking for a way to access all of my bills. To authenticate I had to provide my full name, my address and my account number (this is relevant later), only to be told that the system is limited to keeping the last few months of bills only and no more.&lt;/p&gt;
&lt;p&gt;This instantly smelled funny to me. As someone who writes software I couldn’t fathom any system let alone a &lt;em&gt;billing system&lt;/em&gt; retaining only a few months of data. This data definitely would have been retained, if not for internal purposes then most likely for auditing, reporting and legal reasons.&lt;/p&gt;
&lt;p&gt;Frustrated at the prospect of missing out on the full refund, I did what any self respecting programmer does: I pressed F12.&lt;/p&gt;
&lt;h1&gt;The Network Tab&lt;/h1&gt;
&lt;p&gt;I started clicking through the billing section of the website again. It was an SPA that was using REST calls to contact the company’s backend, with the responses being encoded in JSON. Simple, solid stuff.&lt;/p&gt;
&lt;p&gt;When loading the list of bills the response looked something like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;{
    &quot;result&quot;: [
        {&quot;id&quot;: 1000, &quot;month&quot;: &quot;Feb&quot;, &quot;year&quot;: 2022},
        {&quot;id&quot;: 5000, &quot;month&quot;: &quot;Mar&quot;, &quot;year&quot;: 2022},
        {&quot;id&quot;: 9500, &quot;month&quot;: &quot;Apr&quot;, &quot;year&quot;: 2022},
        ... etc
    ]
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Very typical response for a list. The most curious part of it however was the &lt;code class=&quot;language-text&quot;&gt;id&lt;/code&gt;. My bill’s &lt;code class=&quot;language-text&quot;&gt;id&lt;/code&gt;s were very different from each other, in irregular intervals. This immediately indicated to me that each bill has a globally unique identifier. This is expected… and the curious part is how the &lt;code class=&quot;language-text&quot;&gt;id&lt;/code&gt; is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A monotonic increasing integer (meaning 1, 2, 3, etc)&lt;/li&gt;
&lt;li&gt;Globally unique across all users (meaning no two users will ever have the same bill &lt;code class=&quot;language-text&quot;&gt;id&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Exposed to the Frontend&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This meant that I could, in theory, guess all the bill IDs, including the ones that were older than a few months old.&lt;/p&gt;
&lt;h1&gt;The Bill&lt;/h1&gt;
&lt;p&gt;To further reinforce my theory that the bill ID was globally unique, I observed that downloading the bill made an API call to an endpoint similar to this: &lt;code class=&quot;language-text&quot;&gt;/api/bill/12345/download&lt;/code&gt;. The value &lt;code class=&quot;language-text&quot;&gt;12345&lt;/code&gt; always correlated with the &lt;code class=&quot;language-text&quot;&gt;id&lt;/code&gt; values returned in the list API call. Calling this API endpoint would return data that the browser was turning into a PDF file of the bill.&lt;/p&gt;
&lt;h1&gt;The Search&lt;/h1&gt;
&lt;p&gt;Armed with this knowledge, I realised that the best course of action would be:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Determine the average difference between each bill ID&lt;/li&gt;
&lt;li&gt;Use that to generate the potential bill IDs that were mine that are older than a few months old&lt;/li&gt;
&lt;li&gt;To narrow down the search, attempt to download each bill ID +- 100. It is expected that each billing cycle there will be less bills or more bills.&lt;/li&gt;
&lt;li&gt;Use the HTTP Status Code to know whether it is my bill or not&lt;/li&gt;
&lt;li&gt;Download all the bills that return an OK status code&lt;/li&gt;
&lt;li&gt;Success!&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;Penny Drop&lt;/h1&gt;
&lt;p&gt;To find out what status codes would be returned, I attempted to download a bill with a single digit difference (if my bill ID was &lt;code class=&quot;language-text&quot;&gt;123&lt;/code&gt; I attempted to download &lt;code class=&quot;language-text&quot;&gt;124&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;It downloaded just fine.&lt;/p&gt;
&lt;p&gt;Wait what?&lt;/p&gt;
&lt;p&gt;I opened the PDF file quickly and there it was: someone else’s bill.&lt;/p&gt;
&lt;p&gt;I had to confirm if it was a fluke.&lt;/p&gt;
&lt;p&gt;I changed the ID by one digit again and tried again.&lt;/p&gt;
&lt;p&gt;It downloaded just fine.&lt;/p&gt;
&lt;p&gt;Awww shit.&lt;/p&gt;
&lt;h1&gt;The Vulnerability&lt;/h1&gt;
&lt;p&gt;So it turns out that the API to download a bill &lt;em&gt;did not check whether I owned the account being billed&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;You would not think that this is a big deal (especially if you ignore GDPR)… other than the fact that each bill contained the person’s:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Full Name&lt;/li&gt;
&lt;li&gt;Address&lt;/li&gt;
&lt;li&gt;Account Number&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;These are the same three data points I had used to authenticate with their support staff. Retrieving a bill could, in theory, escalate to a total account takeover.&lt;/p&gt;
&lt;h1&gt;The Fix&lt;/h1&gt;
&lt;p&gt;I remember feeling scared at the time. I had had a negative impression about the attitude of Maltese companies to Information Security for a while, especially after &lt;a href=&quot;https://timesofmalta.com/articles/view/it-company-official-thought-report-about-data-leak-was-a-joke.883312&quot;&gt;a huge data leak of voter records where the IT professional in charge it was reported to “thought the report was an April Fool’s Joke”&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I needed to somehow contact someone at the company who would trust I was not malicious and action it as soon as possible. After reaching out to a trusted acquaintance of mine, they linked me up with a Senior Manager at the company. They (with my permission) called me on my phone soon after and told me that both an executive and their security team had been informed and asked me for a high level overview before the security team called me for more details.&lt;/p&gt;
&lt;p&gt;I was kind of embarrassed to reveal that the entire thing boiled down to, in my opinion, missing validation, rather than some fancy zero day that gave me root access to all their traffic.&lt;/p&gt;
&lt;p&gt;After hanging up, the security team called me and we went over reproduction steps together. They thanked me and told me they would be in touch.&lt;/p&gt;
&lt;h1&gt;The Aftermath&lt;/h1&gt;
&lt;p&gt;An hour or so later I got an email from them stating that, due to it being a weekend, they went ahead and disabled the download bill feature and would follow up on Monday. Monday evening they communicated again that they had successfully patched the vulnerability. I tried one more time to download a bill that wasn’t mine and got an HTTP Forbidden status code in return.&lt;/p&gt;
&lt;p&gt;I eventually spoke again to the senior manager a few days later. I suggested how they could display an email address or some other method for security related escalation more prominently on their website for these kind of issues&lt;/p&gt;
&lt;p&gt;I ended up being invited to apply for one of their open positions. The interview went well (and they waived their technical test for me, which was great) however I ultimately did not join the company due to other factors in my life at the time.&lt;/p&gt;
&lt;h1&gt;In Conclusion&lt;/h1&gt;
&lt;p&gt;Sometimes even the simplest of issues could escalate into a potentially disastrous data breach. This kind of issue is trivial to track with an automated test.&lt;/p&gt;
&lt;p&gt;Also, never get between a motivated software engineer and what is rightfully their data, unless you want them to poke holes in your software 😉&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Four day work weeks]]></title><description><![CDATA[For a while in a previous role I worked a 32 work week. I afforded it at the time and preferred to spend a bit more time at home with my…]]></description><link>https://simonam.dev/four-day-work-week/</link><guid isPermaLink="false">https://simonam.dev/four-day-work-week/</guid><pubDate>Tue, 22 Feb 2022 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;For a while in a previous role I worked a 32 work week. I afforded it at the time and preferred to spend a bit more time at home with my newly minted family.&lt;/p&gt;
&lt;p&gt;In this blog post I’ll talk a bit about four day weeks, what I managed to achieve, what the benefits are and how I replicated them despite switching back to a 40 hour work week in my current role.&lt;/p&gt;
&lt;h2&gt;The many four day work weeks&lt;/h2&gt;
&lt;p&gt;First of all, there are a good number of variants of a four day work week and few people clarify which they are referring to, especially in online conversations. They are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;40 hour weeks in four days, AKA 4x10. This means longer work days, but shorter weeks.&lt;/li&gt;
&lt;li&gt;32 hour weeks in four days, AKA 4x8. This means an entire day is no longer dedicated to work. You could start your weekend early, start your week later or have a break somewhere in between.&lt;/li&gt;
&lt;li&gt;30 hour weeks in four days, AKA 4x7.5. This is similar to 4x8, with 30 minutes less each day.&lt;/li&gt;
&lt;li&gt;32 hour weeks in five days, AKA 5x6.4. This is the model I used, where you’re still working every day, however you leave earlier (or arrive later) than your colleagues.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each have their pros and cons and are ultimately up to personal preference. While I still worked five days, my day was from 08:00 to 15:00 with a break of an hour. If I wanted I could have worked four 8 hour days, however I felt that working the entire week kept my momentum going for the whole week rather than having a hard stop half way. I do consider 4x10 to be a non-starter, since 8 hour days are already a stretch on the brain, let alone 10 hour days.&lt;/p&gt;
&lt;h2&gt;The benefits&lt;/h2&gt;
&lt;p&gt;For me, there were four main benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;My wife and I now started and ended at similar times, which meant she could drop me off on her way to work and pick me up on the way back. This reduced our fuel costs drastically and definitely reduced our carbon footprint.&lt;/li&gt;
&lt;li&gt;For the times when I drove to work, driving back at 15:00 meant I would get home in 10-15 minutes. The same commute at 17:00 would easily take 40-50 minutes in Malta’s atrocious traffic. That was at least 30 minutes of time saved simply by shifting the time I left the office.&lt;/li&gt;
&lt;li&gt;With a shorter day, I cut out a lot of the times I would let my mind wander and a good chunk of time wasting. I spent less time on reddit and more time focused on my tasks, because I knew it would simply be a few more hours before I would be home.&lt;/li&gt;
&lt;li&gt;I ended the day with a fresher mind, capable of handling other intellectually stimulating tasks and side projects better.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The downsides&lt;/h2&gt;
&lt;p&gt;While I did mostly experience upsides, there were a few downsides. The one that comes to mind mostly is that people forget and get surprised that I end the day earlier. I think this created pockets of aminosity from my colleagues at times which I regret causing.&lt;/p&gt;
&lt;p&gt;Another one was that I had an agreement with my employer that I could work up to 40 hours in a given week and I would submit a time-sheet before the end of the month in the case that I did. This worked out well for me as I could make up that last 20% of the salary cut I took to work 32 hours if I wanted to, however it essentially turned me into an hourly employee, which meant that no two payslips ever matched fully.&lt;/p&gt;
&lt;p&gt;The amount of income tax I was meant to be paying kept varying and it made conversations with a bank (I was looking into taking a loan) annoying complicated.&lt;/p&gt;
&lt;h2&gt;How I replicated the gains without the 32 hours&lt;/h2&gt;
&lt;p&gt;I did mention that this was for a previous role. In fact in my current role at &lt;a href=&quot;https://www.hotjar.com&quot;&gt;Hotjar&lt;/a&gt;, I work a full 40 hour work week.&lt;/p&gt;
&lt;p&gt;I do miss the slightly extra free time that a 32 hour work week provided however I managed to replicate the gains I had by switching to a completely different paradigm: fully remote work.&lt;/p&gt;
&lt;p&gt;Shaving 30 minutes off a 40 minute commute is awesome. Shaving 39.5 minutes off a 40 minute commute is even better. I now have no commute and am completely immune to traffic or parking issues (two very real issues in Malta!).&lt;/p&gt;
&lt;p&gt;I am also around my wife more often since she was on maternity leave for the past two years. I sometimes take the opportunity to do some mindless chores while thinking about a difficult problem I’m trying to solve, saving more time for after the day is over. I also have the advantage that software is both my work and my hobby, so I get a good fill of enjoyment at work.&lt;/p&gt;
&lt;p&gt;Would I go back to a 32 hour work week while fully remote? I could probably afford it, but in the meantime our combined focus has become more centered around paying off our debts and investing in our future rather than micro-optimising the amount of free time we have now. On the other hand, I could definitely see it working great for people for whom the job is just a job and their passions lie elsewhere.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Scaling Battlesnake testing with Kubernetes]]></title><description><![CDATA[Note: this article assumes familiarity with kubernetes related keywords. A friend and I are using Battlesnake as a platform for learning new…]]></description><link>https://simonam.dev/scaling-battlesnake-with-k8s/</link><guid isPermaLink="false">https://simonam.dev/scaling-battlesnake-with-k8s/</guid><pubDate>Sun, 14 Nov 2021 20:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;Note: this article assumes familiarity with kubernetes related keywords.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;A friend and I are using &lt;a href=&quot;play.battlesnake.com/&quot;&gt;Battlesnake&lt;/a&gt; as a platform for learning new tech skills. In my case, it is to continue to improve my DevOps technical skillset, mainly to finally take the plunge and learn &lt;a href=&quot;https://kubernetes.io/&quot;&gt;Kubernetes&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Battlesnake is a multiplayer version of the classic game snake, where game engine sends HTTP requests to your web server. Your response simply needs to be &lt;code class=&quot;language-text&quot;&gt;up&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;down&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;left&lt;/code&gt; or &lt;code class=&quot;language-text&quot;&gt;right&lt;/code&gt; within a deadline of 500ms. How you achieve it and with which technologies are decisions all up to you.&lt;/p&gt;
&lt;p&gt;As you iterate on your snake it becomes essential to test it frequently, both to avoid regressions such as causing your snake to prefer to headbutt snakes stronger than it, and to test whether your changes have made it a stronger contender or not.&lt;/p&gt;
&lt;p&gt;Initially we achieved this by pushing the snake to our production EC2 instance hosted in AWS us-west-2 (as close as possible to the Battlesnake servers, since every millisecond of latency counts towards the 500ms deadline), however this process was slow, and made even slower by &lt;a href=&quot;/docker-is-leaky/&quot;&gt;our move to graviton instances&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The Battlesnake team provides a &lt;a href=&quot;https://github.com/BattlesnakeOfficial/rules&quot;&gt;command line version of their rules engine&lt;/a&gt; which allows for running of local games. This means that we could run our snake testing locally, ensuring our snake performs the basics (do not hit walls, do not hit yourself, do not run out of health, etc), along with seeing how it performs in a game against another snake (or potentially even against itself).&lt;/p&gt;
&lt;p&gt;This is still limiting when it comes to testing two snake variants. You can only easily run the CLI sequentially. If your snakes are also running on the same computer, then they will compete for the same resources (CPU, RAM) that the CLI is using.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/768ccd2a900c75b573d6e3a369b02a65/60a48/diagram-1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 66.89189189189189%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB0klEQVQoz22SS4+qQBCF+fVu5n+4c28iUSAujFGvJLxssHkJNDTSIgIij5474DUzzv2WlTqpqnOKof+jbVvHcTDGtm0DANI0pZR2XffWxrxV27allF4ul+VyuV6vZVkGAHieN7QN/BAPvKplWSKEIIQAAMuyHMeJ47iqqrexXdcxTdNYluV5nm3bh57j8Ygxns1mq9UKQqjrehRFcRy3bZskCSHk8XgMk5iqqjiOEwThcDiUZTkajSaTSVEUoigahiFJEoQwz3PXdRVFsW07z/MoisIwzLKMqeta0zRRFHe7HQBAURTHcQghLMtuNhvYgzFGCH18fIzHY0mSVFXVdf1L/HYGpTTLMsuyBqWu6xBCQgjGOAgC13WPPaZpft08eNs0zXc9Qojn+f1+r+t6GIb3+/13Tk+3fd8Pw5AQcj6f4zhGCAVBYJqmpmmmaWKMi6IYUmzbtuu6vOcpbpom+UcURbfbzTCM6XQqCIJhGK7rYow9zxt8QgglSVLX9Y+cv/8Jx3EAgPl8zrLsdrullMqyrKqq7/uDF6fTCWPMvE4dfut6vSqKYpomy7J/ehaLRRAEmqatenieT5LkGdXb2KIo0jQlhGRZ9juIrute1v5d+xPIGszyYzJS6QAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;My Laptop&quot;
        title=&quot;My Laptop&quot;
        src=&quot;/static/768ccd2a900c75b573d6e3a369b02a65/fcda8/diagram-1.png&quot;
        srcset=&quot;/static/768ccd2a900c75b573d6e3a369b02a65/12f09/diagram-1.png 148w,
/static/768ccd2a900c75b573d6e3a369b02a65/e4a3f/diagram-1.png 295w,
/static/768ccd2a900c75b573d6e3a369b02a65/fcda8/diagram-1.png 590w,
/static/768ccd2a900c75b573d6e3a369b02a65/efc66/diagram-1.png 885w,
/static/768ccd2a900c75b573d6e3a369b02a65/c83ae/diagram-1.png 1180w,
/static/768ccd2a900c75b573d6e3a369b02a65/60a48/diagram-1.png 1325w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Ideally we should be able to run multiple instances of the CLI in parallel, along with multiple, load balanced instances of each snake variant. Here is where kubernetes comes in as a solution. Whilst this can be achived without kubernetes (there are options such as Hashicorp Nomad), k8s offers multiple features we need in a single package whilst also providing a great opportunity for learning.&lt;/p&gt;
&lt;h3&gt;A kubernetes based approach&lt;/h3&gt;
&lt;p&gt;Kubernetes provides an opportunity to orchestrate our software (packaged as containers) across a single or multiple nodes. It has existing concepts which aid in achieving outcomes such as service discovery, load balancing and parallelisation of jobs.&lt;/p&gt;
&lt;p&gt;Here is what our setup looks like. It runs &lt;a href=&quot;https://k3s.io/&quot;&gt;k3s&lt;/a&gt; on a Raspberry Pi based cluster:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/c98b23c73c3e6f084d1d3241c0996f53/5e4a4/diagram-2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 119.59459459459461%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAYCAIAAAB1KUohAAAACXBIWXMAAAsTAAALEwEAmpwYAAAD+ElEQVQ4y22UW5OaShSF/f9/IXlMZaqmUpWXXMrJRNQZYTSOCoogYnPpbu7Y9AiIXM9xOpWcnMp66Bfq23v3WrvpNU3jvgoh5DgOACCO46Iodq86HA4AAMMwyrJEr4IQ+r4fBAGltFdV1Wq1ghCar1IUZbFYAABWq9V+v1cUxTCM4XAYhuF6vQYAqKrqOM5+v7dtu9e27Wq1Wi6Xg8FAURQI4Wg04jhOFMXn52ee533f//r163a77ff7d3d3o9FI13U25hWWZfnm5maz2bRtezqdKKWSJL1580aWZXYdWZYppTzPS5LUtm1ZloQQjHGv6zrDMBBChJDj8WhZliiK9/f3CCFRFDHGrusOBoMfP34ghCillmWx07KsKwwh5Diu3+9jjFVVRQjNZrP3798LggAA2O12uq67rstx3JcvX75//66qKiv6E3737t3Dw0OSJBDC2Ww2Go3CMBQEwbKsxWLB8/xyuRQEYb/fN01T13Wapp7nXd12XTeKIt/3CSFhGFqWNZlMPnz4MB6PEULb7fZwOGia5jjO5XLJ87xpmjzPgyDoNU2z3W45jvv06RMLjOd5QRBkWf748SOEcD6fD4fDp6cnSZKGw+FisXAcxzRNAMDV7efn57dv3242G9/3TdNECHEcd3t7Ox6PRVEcj8eGYQwGg+FwqKqqZVlFUZimeTUMADAYDB4eHu7v73e7XRRFVVWt12tBEHRd1zSt67q2bZfL5Wg0AgBgjKMoUlU1y7Ke53mEEEqp67q2bVNK0zQlhERR5DgOxjhJkiAICCGmaYZhqOu6aZpxHHdd1yuK4ng8EkLyPMcYK4rC8zxC6HA46Loex7HjOGwlkiQ5Ho+U0peXlyAIyrK8RsUG67oujmNVVafTqSzLiqKwNXx8fBQEQZKkLMuqqsrzPE3TsiyvndtXNU1zOBy22y2E0PO8IAjyPI+iyPO8uq6rqiKEJElyuVxYG6afnauqUhTlfD6naRoEgSiKlmWx16dp2m63C4Igy7KXl5ckSZqm+Q2zYoZheJ7XdV1d14ZhYIwvl0vXdXmey7Ls+z6lNMsy13XZzH/AAIDPnz/P53MWtaIogiBACKMokiSJUlrXdZIkjuP8MTaDXddN07QoijiOZVm2bdt/VRiGmqbN5/P1ej2dTn3f/4X8hhFCk8kEIWTbNosqjuPj8ajrum3bzKrz+RxF0V9gjLEoioQQ5ofned++fYMQXi4X3/fv7u5YlTRN/wJnWRbHcRAETdOwXwrGmBDSdV1RFOv1GmNcVVX3H/2GT6fTfr9XVTVJEmaBpmkYY/bJMAzbtuu6/j/M1DRNWZZ5nrMkzuczpZTtcJ7nlmWpqkop/dXsX/gfvo4JvA2Jj3YAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Cluster&quot;
        title=&quot;Cluster&quot;
        src=&quot;/static/c98b23c73c3e6f084d1d3241c0996f53/fcda8/diagram-2.png&quot;
        srcset=&quot;/static/c98b23c73c3e6f084d1d3241c0996f53/12f09/diagram-2.png 148w,
/static/c98b23c73c3e6f084d1d3241c0996f53/e4a3f/diagram-2.png 295w,
/static/c98b23c73c3e6f084d1d3241c0996f53/fcda8/diagram-2.png 590w,
/static/c98b23c73c3e6f084d1d3241c0996f53/efc66/diagram-2.png 885w,
/static/c98b23c73c3e6f084d1d3241c0996f53/c83ae/diagram-2.png 1180w,
/static/c98b23c73c3e6f084d1d3241c0996f53/5e4a4/diagram-2.png 1403w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Each snake is containerised and deployed to a Docker registry hosted alongside the cluster.&lt;/p&gt;
&lt;p&gt;For each snake, we define a &lt;strong&gt;deployment&lt;/strong&gt;. This maintains a &lt;strong&gt;replicaset&lt;/strong&gt; underneath which creates a set of &lt;strong&gt;pods&lt;/strong&gt; running the snake’s webserver. We also define a &lt;strong&gt;service&lt;/strong&gt; which allows each web server’s pod to be reachable within the cluster. To allow reaching it from outside the cluster, we define it as type &lt;code class=&quot;language-text&quot;&gt;NodePort&lt;/code&gt; and hardcode a port such as &lt;code class=&quot;language-text&quot;&gt;30000&lt;/code&gt;, in case we wish to &lt;code class=&quot;language-text&quot;&gt;curl&lt;/code&gt; it manually.&lt;/p&gt;
&lt;p&gt;Here’s a sample:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: foobar1
  namespace: snakepit
  labels:
    app: foobar1
spec:
  replicas: 3
  selector:
    matchLabels:
      app: foobar1
  template:
    metadata:
      labels:
        app: foobar1
    spec:
      containers:
        - name: foobar1
          image: &quot;our-registry-url:5000/snake-foobar1:testing-arm&quot;
          ports:
            - containerPort: 8111
          imagePullPolicy: Always
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: foobar1
  name: foobar1-service
  namespace: snakepit
spec:
  selector:
    app: foobar1
  type: NodePort
  ports:
    - protocol: TCP
      port: 8111
      targetPort: 8111
      nodePort: 30000&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The CLI is also containerised. We define a k8s &lt;code class=&quot;language-text&quot;&gt;Job&lt;/code&gt; where the &lt;code class=&quot;language-text&quot;&gt;command&lt;/code&gt; is specified to include all CLI args required to include the snakes also deployed within the cluster. We can even define these using the name of the service, since k8s will resolve DNS for us within the cluster (for example: &lt;code class=&quot;language-text&quot;&gt;http://foobar1-service:8111&lt;/code&gt;). Here’s a sample:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;apiVersion: batch/v1
kind: Job
metadata:
  name: bscli
  namespace: snakepit
spec:
  completions: 4
  completionMode: Indexed
  parallelism: 2
  template:
    spec:
      containers:
        - name: bscli
          image: &quot;our-registry-url:5000/bscli:testing-arm&quot;
          imagePullPolicy: IfNotPresent
          command:
            [
              &quot;./battlesnake&quot;,
              &quot;play&quot;,
              &quot;-W&quot;,
              &quot;11&quot;,
              &quot;-H&quot;,
              &quot;11&quot;,
              &quot;--name&quot;,
              &quot;foobar1&quot;,
              &quot;--url&quot;,
              &quot;http://foobar1-service:8111&quot;,
              &quot;--name&quot;,
              &quot;pastaz2&quot;,
              &quot;--url&quot;,
              &quot;http://pastaz2-service:8111&quot;,
              &quot;-v&quot;,
              &quot;-t&quot;,
              &quot;500&quot;,
            ]
      restartPolicy: Never
  backoffLimit: 1&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;By adjusting &lt;code class=&quot;language-text&quot;&gt;completions&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;parallelism&lt;/code&gt; we can decide how large our testing round should be, along with how many games should run at the same time. In an ideal world: &lt;code class=&quot;language-text&quot;&gt;parallelism == completions&lt;/code&gt;, however my cluster has limited resources and by running too many pods the end result will slow down the response time of the snake webservers, ending up in a situation where it takes &lt;em&gt;longer&lt;/em&gt; to complete a round of testing. By constraining &lt;code class=&quot;language-text&quot;&gt;parallelism&lt;/code&gt;, we can ensure that we do not saturate the cluster’s resources.&lt;/p&gt;
&lt;h3&gt;Future work&lt;/h3&gt;
&lt;p&gt;Our work is not finished yet. Right now, this system does not track the number of wins per snake in a testing round, which is the entire point of testing variants against each other. Here’s what we would need to achieve:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/532892c507b38603199d2beead1666c3/f793b/diagram-3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 133.1081081081081%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAbCAIAAADzvTiPAAAACXBIWXMAACMQAAAjEAFnL93WAAADgUlEQVQ4y3WUy26rOhRA8/+/0lHbQaVOqiqV+hKkgUAwECA1YMBgm4fBQBKuDo7Snt6eNQLBYm/vB4vp31RVZVnWfr/HGIcz4zjKR6eZhbwhhCRJ0rZtNzOOoxAiyzLXdQ3DsCwLAGAYxjAM0zQdj0dpLU6n0zRNEEIhBGOMUooxjuM4z/MsywAAbKbv+/1+7/s+ACCKos/Pz7Ztz7KmaYqibDYb13U550mSxHHsed5yudR1PYqisiytmdvb25ubG8uyhBBnGQDg+/5ut/v8/KyqCgBQFAXnvG3baoYQQimdpqnrupeXF8bYn8gy+7qui6Ko6zqO4zAM0zQlhGRZxhjL85zOdF0nXx6GAWMMAFhcqicvjsfjJcL7+7thGJdqB0EQhqHjOGEYMsYwxj9lIURZlkIISqlt26ZppmmKENJ1PUmS5+fn+/v7q6sr27bTNP0jHw4HWVKEkG3bAACMcdM0Mn5ZllVV5Xku+zSO42azads2DMOFjKaqqu/7QRAMw5AkSdd1hJA8z4uikBdlWV5yjKLINE1d18/y6+vrarUyTZNSGgQBY8x1XU3TEEIYYwhhkiSMMc65EOJwOFRVZZrmOW3OeV3XhJA0TWWGaZoCAOI4bpomSRIIoeM419fXd3d3j4+PAADP8xbfh1lOrDwqQkj2SRYiiqJpmhhjTdM4jhNFke/7ix/apfJ93w/D0Pe9EIJz/r3JCKG6rpMk+Uv+/olL29u2FUL0fS/9YRgghJTSuq4XP9bwdDodj8fD4TBNU9M0EEJCCABAVVXTNLMso5RqmiYX6SxzzuM4TpIEIRTPUEoVRTEMg1JKCNF13bKsoijats3znHP+tZKe5202m+12u16vTdN8e3t7eHjQNM22bc/zTNOEEF6OfeEsy97KwjDGfN+XMwwACIIgyzJFURBC36vzl7xer5+enuI4Xi6Xq9UqDENFUWzbbpoGYxxFUVEU37fgS7Ysq+97+T+qqipNU2dmt9shhHzfV1X1EvmnbNu23G85w8MwlGU5jmPf94wxQgia+WfkruuEEFVVcc4ZYxBCPFPObLfbLMt+l03TrGfKshyGgRASRRHGWKYg90Qm9bs8zHDO5SbIv4fv+2VZNk2z3W5/mF8yAODj48O27c3MdiYIAgjhbrczDENV1V/ky7i7rgsh3O/3wYzjOL7vx3Fs27au63Vd/1/+DyVL4IGapdhaAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Results DB&quot;
        title=&quot;Results DB&quot;
        src=&quot;/static/532892c507b38603199d2beead1666c3/fcda8/diagram-3.png&quot;
        srcset=&quot;/static/532892c507b38603199d2beead1666c3/12f09/diagram-3.png 148w,
/static/532892c507b38603199d2beead1666c3/e4a3f/diagram-3.png 295w,
/static/532892c507b38603199d2beead1666c3/fcda8/diagram-3.png 590w,
/static/532892c507b38603199d2beead1666c3/efc66/diagram-3.png 885w,
/static/532892c507b38603199d2beead1666c3/c83ae/diagram-3.png 1180w,
/static/532892c507b38603199d2beead1666c3/f793b/diagram-3.png 1404w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;To achieve this, there are number of options:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Tail and grep the logs of the CLI pods (the last line printed by the CLI states the winner).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This option is quick and dirty, however has some constraints, mainly that we would be doing it after the testing round is complete. If we had to interleave the logs from multiple pods from different testing rounds we would not be able to tell which result belongs to which testing round.&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Patch the CLI to write the results to a file and upload it to a webserver by including a script of some sort which will run after the CLI exits.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For this, I forked and patched the CLI to export the details of the game to a line delimited JSON file. I also &lt;a href=&quot;https://github.com/BattlesnakeOfficial/rules/pull/56&quot;&gt;opened a PR&lt;/a&gt; which has &lt;a href=&quot;https://github.com/BattlesnakeOfficial/feedback/discussions/136&quot;&gt;started the RFC process&lt;/a&gt; around this functionality.&lt;/p&gt;
&lt;p&gt;The main issue here is that I would have to maintain a fork of the CLI and would I need to run the script after the game ends, potentially complicating how the container is built and how the current system of overriding the container’s command is done.&lt;/p&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Divert the CLI Pods’s logs to another Pod by &lt;a href=&quot;https://kubernetes.io/docs/concepts/cluster-administration/logging/#streaming-sidecar-container&quot;&gt;streaming them to a sidecar&lt;/a&gt;, followed by uploading the result.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This would be the most “k8s native” way of reaching the goal. This means that there is no need to patch the CLI (for our current needs at least, which is simply to answer the question “did the snake win?”) as that is easily parseable from the current CLI’s output, whilst also keeping the script which will upload the result in a separate container within the Pod, keeping the CLI’s container clean and simple.&lt;/p&gt;
&lt;p&gt;Once a solution is picked and implemented I’ll be back with another blog post. In the meantime, feel free to checkout our progress on the &lt;a href=&quot;https://play.battlesnake.com/t/bantersnake/&quot;&gt;bantersnake team page&lt;/a&gt;!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Sidenote&lt;/em&gt;: The diagrams have been illustrated by myself on a &lt;a href=&quot;https://remarkable.com/&quot;&gt;ReMarkable2&lt;/a&gt;. Its a great piece of hardware but my lack of decent handwriting is pretty clear. I am hoping this will improve over time.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Docker is a leaky abstraction]]></title><description><![CDATA[This blog post details how I finally understood that Docker is not the perfect abstraction I thought it was and how the hardware your docker…]]></description><link>https://simonam.dev/docker-is-leaky/</link><guid isPermaLink="false">https://simonam.dev/docker-is-leaky/</guid><pubDate>Sat, 18 Sep 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This blog post details how I finally understood that Docker is not the perfect abstraction I thought it was and how the hardware your docker image will run on still matters.&lt;/p&gt;
&lt;p&gt;In my efforts to improve my Ops skills, I have taken to using Docker wherever possible. By exposing myself continually to it I can understand better how containerisation works, where it is useful and more importantly, where it is &lt;em&gt;not&lt;/em&gt; useful along with the pitfalls involved.&lt;/p&gt;
&lt;p&gt;In my mind, Docker was this great layer of abstraction that provided value in the form of vague keywords such as &lt;em&gt;reproducible builds&lt;/em&gt; and &lt;em&gt;isolated dependencies&lt;/em&gt;. Whilst I have definitely reaped those benefits, a recent experience of trying to move some software from a machine with a particular CPU architecture to another has made me realise that Docker (or any flavour of containerisation) does not insulate a developer’s code completely from the machine it runs on. Like any abstraction, it &lt;em&gt;leaks&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;For a particular project I am using AWS. Typically it feels over-engineered for my needs which are usually met by Hetzner, Digital Ocean or any smaller VPS provider. In this case, latency was important and the service we were interacting with also runs on AWS, so I wanted to deploy into the same AWS region (more on this project in another blogpost 😉).&lt;/p&gt;
&lt;p&gt;I try to run my sideprojects on shoe string budgets. I believe that it helps encourage learning by doing and stimulates creativity over simply throwing money at the problem.&lt;/p&gt;
&lt;p&gt;During the course of this project we deployed some code written in Python (rather than Go), and began experiencing CPU saturation on our AWS Lightsail VPS. This was a pressing issue as it was causing us to respond slowly, sometimes exceeding the short timeout provided to us (we have to respond within 500ms).&lt;/p&gt;
&lt;p&gt;There were several solutions to solving CPU usage in this case, each with their own tradeoffs:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Moving to a cheaper cloud provider&lt;/li&gt;
&lt;li&gt;Optimise the code to make it faster&lt;/li&gt;
&lt;li&gt;Switch to a different offering within AWS&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Moving to cheaper cloud provider&lt;/h3&gt;
&lt;p&gt;There are two main issues to this solution:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The amount of vCPUs available to a VPS tend to scale slowly compared to other elements such as RAM or Disk space. For instance, &lt;a href=&quot;https://www.digitalocean.com/pricing&quot;&gt;Digital Ocean&lt;/a&gt; will only offer more CPU when going from $5 a month to $15 a month.&lt;/li&gt;
&lt;li&gt;Moving further away from AWS will introduce network latency, which counted towards our 500ms deadline. I could get 3 AMD vCPUs for around €8 a month from &lt;a href=&quot;https://www.hetzner.com/cloud&quot;&gt;Hetzner&lt;/a&gt;, however they only offer datacentres in Europe. From my testing, the latency difference could be up to 100ms from us-west-2 to Hetzner’s Nuremberg Data Centre.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Optimise the code to make it faster&lt;/h3&gt;
&lt;p&gt;There was one main issue to this solution: we are still heavily in the experimenting and prototyping phase of the project. By spending time on optimising the code (whether the existing code or by rewriting it in a compiled language), we would be burning precious time which would be better spent on more prototyping.&lt;/p&gt;
&lt;h3&gt;Switch to a different offering within AWS&lt;/h3&gt;
&lt;p&gt;So far we had used AWS Lightsail to provision a VPS, since it was the offering most familiar to us. Lightsail however offers very little control of the kind of hardware you run on (kind of the point, you shouldn’t need to think about it!).&lt;/p&gt;
&lt;p&gt;I had heard about &lt;a href=&quot;https://aws.amazon.com/ec2/graviton/&quot;&gt;AWS Graviton&lt;/a&gt;, which are AWS’ custom ARM processors. When I checked the price, it turned out I could get 2vCPUs on a &lt;code class=&quot;language-text&quot;&gt;t4g.nano&lt;/code&gt; EC2 instance for the same price as what I was paying for 0.5vCPU on Lightsail.&lt;/p&gt;
&lt;h2&gt;Moving to Graviton&lt;/h2&gt;
&lt;p&gt;Moving from Lightsail to EC2 was not particularly diffiicult, as I mostly worked through Terraform and had some prior experience playing around with the EC2 interface. Moving to a &lt;em&gt;graviton&lt;/em&gt; instance however is where the learnings came from.&lt;/p&gt;
&lt;p&gt;If you ever try to run something compiled for x86 (the CPU architecture of both my laptop, my CI server and our Lightsail instance) on an ARM machine, you’ll get cryptic error messages such as: &lt;code class=&quot;language-text&quot;&gt;exec user process caused: exec format error&lt;/code&gt;. This counts for Docker too.&lt;/p&gt;
&lt;p&gt;The above error had already begun challenging my incorrect understanding of Docker - it was a Docker Image! I shouldn’t need to think about the machine! Well it turns out I did. I did not allow myself to be dissuaded by this and powered on, through several hours of trial and error.&lt;/p&gt;
&lt;p&gt;It turns out that Docker has &lt;a href=&quot;https://docs.docker.com/desktop/multi-arch/&quot;&gt;support for multi-CPU architectures&lt;/a&gt; on an image level using a command called &lt;code class=&quot;language-text&quot;&gt;buildx&lt;/code&gt; where one can setup a builder. Customising this builder allows users to compile across CPU architectures. In my case, on Ubuntu, I followed some guides which mentioned qemu and binfmt_misc to enable hardware emulation for buildx to operate. I admit I was mostly flying blindly at this stage. After multiple attempts I was able to manually build an arm64 image on my x86 laptop and verify this by moving the image to our graviton instance and run it as a container.&lt;/p&gt;
&lt;p&gt;Great! But &lt;em&gt;holy cow&lt;/em&gt; does it come at a cost. A none-too-special multi-stage Docker image packaging a single Go binary which typically took 60 seconds to build (which I personally considered quite slow), around 300s to build for arm64. These kind of long lead times really hampered our development cycle, slowing down our ability to prototype quickly.&lt;/p&gt;
&lt;h2&gt;The Solution&lt;/h2&gt;
&lt;p&gt;Not willing to take the slow deployment cycle due to the cost of emulation every time, I began looking into alternatives. One approach suggested to me by more than one person was to build the Docker image on the graviton instance itself.&lt;/p&gt;
&lt;p&gt;Whilst a very simple and easy approach, it encourages treating the server as a pet over cattle, along with burning the same compute I wanted to improve. It was plausible that the act of building the Docker image would interfere the running of the code itself.&lt;/p&gt;
&lt;p&gt;Another approach would be to either configure my CI/CD server (in my case I use Jenkins) to perform the hardware emulation (I’d still pay the emulation cost, but at least it is not tied to my laptop) or to source some ARM hardware and build the Docker Image on it. After a few more failed attempts, it resulted that the &lt;a href=&quot;https://www.raspberrypi.org/&quot;&gt;Raspberry Pi 3 or 4&lt;/a&gt;, running the 64 bit version of Raspbian (the default one provided on the imager is 32 bit) mirrors Graviton’s arm64 architecture.&lt;/p&gt;
&lt;p&gt;Our final setup looks like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We write and test the code locally on our x86 laptops&lt;/li&gt;
&lt;li&gt;When finished, we merge the code into the main branch&lt;/li&gt;
&lt;li&gt;Jenkins (running on an x86 machine), polls the repository and picks up the new code&lt;/li&gt;
&lt;li&gt;Jenkins Master hands off the job to a Raspberry Pi running the Jenkins Agent (by restricting the job to a specific label)&lt;/li&gt;
&lt;li&gt;The Docker image is built for the arm64 architecture and deployed to the Graviton instance using Ansible&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Caveats&lt;/h2&gt;
&lt;p&gt;Whilst this solution may feel rube-goldberg worthy, I want to reiterate that the aim is to &lt;em&gt;learn&lt;/em&gt; over to provide the most cost-effective and bullet-proof solution.&lt;/p&gt;
&lt;p&gt;This process also brings into question one of the main benefits of Docker: reproducible builds. A major advantage of Docker is being able to use the image built on the dev’s laptop into a test environment, then onto a staging environment and eventually production. If any of those steps involve a different CPU architecture however and you end up having to &lt;em&gt;rebuild&lt;/em&gt; your image, are you still testing/running the same image? How can you be certain that building the image for another CPU architecture will not cause different behaviour?&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Dev vs Ops - Go embed]]></title><description><![CDATA[Introduction In my latest article on my game Cabin Fever. I document how I use Docker to compile and bundle all of the code into a single…]]></description><link>https://simonam.dev/dev-vs-ops-using-go-embed/</link><guid isPermaLink="false">https://simonam.dev/dev-vs-ops-using-go-embed/</guid><pubDate>Sun, 21 Mar 2021 17:50:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;In my &lt;a href=&quot;/game-from-scratch-6&quot;&gt;latest article on my game Cabin Fever&lt;/a&gt;. I document how I use Docker to compile and bundle all of the code into a single, deployable package.&lt;/p&gt;
&lt;p&gt;With &lt;a href=&quot;https://blog.golang.org/go1.16&quot;&gt;Go 1.16&lt;/a&gt;, embedding files has become a &lt;a href=&quot;https://golang.org/doc/go1.16#library-embed&quot;&gt;first class citizen&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This potentially means that, rather than relying on Docker to keep my binary and the generated CSS and JS files together, I can embed these files directly in the Go binary and ship it out as it is, without the Docker wrapping around it.&lt;/p&gt;
&lt;p&gt;This is made possible due to Go’s efficient cross-compilation and the ability to put everything into a single binary, so your mileage may vary with other programming languages.&lt;/p&gt;
&lt;h2&gt;Using Go embed&lt;/h2&gt;
&lt;p&gt;To be able to use Go embed, I need to ensure that both my development machine and my CI machine are operating with go version &lt;code class=&quot;language-text&quot;&gt;1.16&lt;/code&gt;.
At the time of writing, my development machine is on &lt;code class=&quot;language-text&quot;&gt;Ubuntu 20.04.2 LTS&lt;/code&gt; and the latest Go version available for me is &lt;code class=&quot;language-text&quot;&gt;go 1.15.8&lt;/code&gt;. Using the instructions available &lt;a href=&quot;https://github.com/golang/go/wiki/Ubuntu&quot;&gt;here&lt;/a&gt; I was able to install a backport without any hassle.&lt;/p&gt;
&lt;h2&gt;Switching to Go embed&lt;/h2&gt;
&lt;p&gt;Working off of the following &lt;a href=&quot;https://blog.carlmjohnson.net/post/2021/how-to-use-go-embed/&quot;&gt;blogpost&lt;/a&gt;, it was very simple to switch to Go embed. I did not include the author’s solution to switch between the normal filesystem and the embedded one depending on the environment at this time, since the hot reload provided by modd already recompiles the binary from scratch.&lt;/p&gt;
&lt;p&gt;My next issue was that my &lt;code class=&quot;language-text&quot;&gt;go.mod&lt;/code&gt; file was still using Go &lt;code class=&quot;language-text&quot;&gt;1.13&lt;/code&gt; and was unable to understand the embed directive. This was quickly solved by updating the version to &lt;code class=&quot;language-text&quot;&gt;1.16&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;After resolving a minor issue where the latest &lt;code class=&quot;language-text&quot;&gt;golang&lt;/code&gt; Docker image was not the 1.16 image (quickly resolved by performing &lt;code class=&quot;language-text&quot;&gt;docker pull golang&lt;/code&gt;), it looked like the embedding was not working. If I changed &lt;code class=&quot;language-text&quot;&gt;index.html&lt;/code&gt; after compilation, it was still returning the latest version, meaning that the binary was still retrieving it from my machine’s filesystem rather than the embedded one.&lt;/p&gt;
&lt;p&gt;This was resolved by re-reviewing how I served the &lt;code class=&quot;language-text&quot;&gt;index.html&lt;/code&gt;. It turns out I had written code to read a file from the OS filesystem and had not taken the new embedded file system into account.&lt;/p&gt;
&lt;p&gt;Whilst I could have resolved this by directing it to the new embedded filesystem, I decided to use Go embed again, but instead of embedding the file as a filesystem, I used the option to embed the contents of the file as a &lt;code class=&quot;language-text&quot;&gt;string&lt;/code&gt;, which achieved the same result.&lt;/p&gt;
&lt;p&gt;Switching to Go embed actually made the resulting Docker image smaller, reducing it from 10.5MB to 9.27MB. Not a staggeringly large difference, but a difference nonetheless.&lt;/p&gt;
&lt;p&gt;Technically I meant to do away with the Docker wrapping completely - however I can still use the Dockerfile as a compilation mechanism that is isolated from the machine I am running it on. According to the &lt;a href=&quot;https://unix.stackexchange.com/a/370221&quot;&gt;following StackOverflow link&lt;/a&gt; I can run &lt;code class=&quot;language-text&quot;&gt;docker create&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;docker cp&lt;/code&gt; to extract the binary from the Docker image that is created.&lt;/p&gt;
&lt;h2&gt;Takeaway&lt;/h2&gt;
&lt;p&gt;This article shows how, despite the latest and greatest Ops tools (such as Docker) technically mean that DevOps practitioners can opt to &lt;em&gt;not&lt;/em&gt; care about whatever the code is written in, if we combine Dev knowledge of the underlying code being packaged and deployed we can simplify and streamline the DevOps process even further.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[A real-time game from scratch - Continuous Deployment Part 1 [6]]]></title><description><![CDATA[Previous Entries 3 - PixiJS 4 - Physics, TDD and Core Game Loops 5 - CI/CD Introduction Continuous Deployment is the DevOps discipline of…]]></description><link>https://simonam.dev/game-from-scratch-6/</link><guid isPermaLink="false">https://simonam.dev/game-from-scratch-6/</guid><pubDate>Sat, 20 Mar 2021 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;Previous Entries&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/game-from-scratch-3&quot;&gt;3 - PixiJS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/game-from-scratch-4&quot;&gt;4 - Physics, TDD and Core Game Loops&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/game-from-scratch-5&quot;&gt;5 - CI/CD&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Continuous Deployment is the DevOps discipline of having automated deployments that move our continuously integrated code out to development, staging or even production environments.&lt;/p&gt;
&lt;p&gt;The reasoning is that by deploying quickly, often and in small chunks we can avoid the risks associated with large deployments, get feedback quickly and if we encounter issues we can rollback much faster.&lt;/p&gt;
&lt;h2&gt;Continuous Deployment Steps&lt;/h2&gt;
&lt;p&gt;Cabin Fever is a game that is played using a browser, so this continuous deployment would involve steps such as the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Provision a VPS or some sort of cloud server&lt;/li&gt;
&lt;li&gt;Install all dependencies on that server to run the game server&lt;/li&gt;
&lt;li&gt;Transfer the game server binary onto the server&lt;/li&gt;
&lt;li&gt;Setup or update DNS to point to our new server&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The above assumes that I am provisioning a new server every time. An alternative to this would be to provision the server ahead of time, followed by transferring the latest game server binary to the existing server and switching traffic from the old one to the new one.&lt;/p&gt;
&lt;h2&gt;One thing to deploy&lt;/h2&gt;
&lt;p&gt;Before I implement deployment, I need to ensure that I have something to deploy. In the &lt;a href=&quot;/game-from-scratch-5&quot;&gt;previous article&lt;/a&gt; I explored the possibility of using Jenkins to build both the server and client, ending with archiving the client bundle and server binary for later use.&lt;/p&gt;
&lt;p&gt;An alternative to this approach would be to build a single package which contains everything I need to place on the server to get started. Having worked extensively with Docker, it felt like the easiest way to proceed. It would also make the process simpler, as I could build the image on my CI machine and ship out the image only to the production machine.&lt;/p&gt;
&lt;h3&gt;Multiple Dockerfiles vs one Mutli-stage Dockerfile&lt;/h3&gt;
&lt;p&gt;In a project spanning different parts of a stack (in this case, backend and frontend), one will often find Dockerfiles for each part of the project, both for development and deployment purposes. This process helps keep the code both portable across machines (so as long as the docker daemon is available), more reproducible (since all the toolchain required is available in the docker image) and easier to get started with.&lt;/p&gt;
&lt;p&gt;Since I am using a mono-repo for Cabin Fever (meaning all the code, regardless of the part of the stack, is in the same git repository), I chose to leverage a single multi-stage Dockerfile over having to wrangle separate ones.&lt;/p&gt;
&lt;p&gt;Multi-stage Dockerfiles are considered a good practice even at a single part of the stack - it is a useful tool to avoid shipping larger docker images than are necessary. A smaller image ends up being both cheaper and faster to move around, cutting down on turnaround times.&lt;/p&gt;
&lt;p&gt;A psuedo-code example of a multi-stage dockerfile could be the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Stage 1: Start from the latest available golang image&lt;/li&gt;
&lt;li&gt;Stage 1: Compile the binary using the golang toolchain&lt;/li&gt;
&lt;li&gt;Stage 2: Start from the &lt;a href=&quot;https://hub.docker.com/_/scratch&quot;&gt;scratch&lt;/a&gt; image&lt;/li&gt;
&lt;li&gt;Stage 2: Copy the compiled binary from &lt;strong&gt;Stage 1&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Stage 2: Setup the image to run the binary&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As a result, the final image will not be shipped with the golang toolchain (which is not required to be present in the image for our usecase), making it smaller, more secure and more efficient.&lt;/p&gt;
&lt;h3&gt;Mutli-stage for Cabin Fever&lt;/h3&gt;
&lt;p&gt;In the &lt;a href=&quot;https://github.com/simonamdev/cabin-fever/blob/6cad1a6a5fc98699c73b1dbd6ff8abea4cb82e40/Dockerfile&quot;&gt;final resulting&lt;/a&gt; Dockerfile, I used the following pattern:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Stage 1: Using the latest available golang image&lt;/li&gt;
&lt;li&gt;Stage 1: Compile the binary using the golang toolchain&lt;/li&gt;
&lt;li&gt;Stage 2: Using the latest nodejs image&lt;/li&gt;
&lt;li&gt;Stage 2: Download all required dependencies&lt;/li&gt;
&lt;li&gt;Stage 2: Build the production bundle&lt;/li&gt;
&lt;li&gt;Stage 3: Using the scratch image&lt;/li&gt;
&lt;li&gt;Stage 3: Copy the compiled binary from &lt;strong&gt;Stage 1&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Stage 3: Copy the production bundle from &lt;strong&gt;Stage 2&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Stage 3: Setup the image to run the server binary&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This results in a 10.5MB image, consisting of both the server binary and the frontend bundle. The only two changes I had to do to the code to enable this process were:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;serve the &lt;code class=&quot;language-text&quot;&gt;index.html&lt;/code&gt; file at the root endpoint &lt;code class=&quot;language-text&quot;&gt;/&lt;/code&gt; and also to serve the static folder through the server so that JS and CSS files can be served to the end user.&lt;/li&gt;
&lt;li&gt;set the production bundler (in this case, parcel) to include &lt;code class=&quot;language-text&quot;&gt;/static/&lt;/code&gt; as a path under the CSS &amp;#x26; JS assets in the generated &lt;code class=&quot;language-text&quot;&gt;index.html&lt;/code&gt; using the following command line argument: &lt;code class=&quot;language-text&quot;&gt;--public-url ./static/&lt;/code&gt;. I included this addition in the &lt;code class=&quot;language-text&quot;&gt;package.json&lt;/code&gt; under the &lt;code class=&quot;language-text&quot;&gt;build&lt;/code&gt; script to avoid having to include it in the Dockerfile.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Whilst none of the above is technically &lt;em&gt;deployment&lt;/em&gt;, it lays the groundwork for the packaging of all the code involved in delivering the full game experience to our end users. The next article will involve more of what I initially wrote about: the actual provision of a new server or usage of an existing one to serve the game experience.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Next Entry: Continuous Deployment Part 2 (Coming soon)&lt;/strong&gt;&lt;/p&gt;
&lt;!-- **Next Entry** --&gt;
&lt;!-- * [7-  Continuous Deployment Part 2](/game-from-scratch-7) --&gt;</content:encoded></item><item><title><![CDATA[A real-time game from scratch - CI/CD [5]]]></title><description><![CDATA[Previous Entries 2 - WebSockets 3 - PixiJS 4 - Physics, TDD and Core Game Loops Introduction Up until now, the development of Cabin Fever…]]></description><link>https://simonam.dev/game-from-scratch-5/</link><guid isPermaLink="false">https://simonam.dev/game-from-scratch-5/</guid><pubDate>Mon, 15 Mar 2021 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;Previous Entries&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/game-from-scratch-2&quot;&gt;2 - WebSockets&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/game-from-scratch-3&quot;&gt;3 - PixiJS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/game-from-scratch-4&quot;&gt;4 - Physics, TDD and Core Game Loops&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Up until now, the development of Cabin Fever has all happened on my personal machine, running on localhost. Showing a demo to someone either involves physical proximity to demo it (which is not a good idea given the ongoing pandemic) or recording a video and uploading it to YouTube.&lt;/p&gt;
&lt;p&gt;Alternatively, I could push both the back-end and front-end to a server or VPS and allow potentially anyone on the internet to try it out.&lt;/p&gt;
&lt;p&gt;By the end of this article, my aim is to have automated the full process of building the software and pushing it out to the server, rather than having to perform those steps manually. I will be applying the DevOos discipline of CI/CD to achieve this.&lt;/p&gt;
&lt;h2&gt;CI &amp;#x26; CD&lt;/h2&gt;
&lt;p&gt;CI and CD here refer to the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Continuous Integration&lt;/li&gt;
&lt;li&gt;Continuous Deployment&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Continuous Integration&lt;/h3&gt;
&lt;p&gt;Whenever anyone wants to work on a piece of code, it is a good idea to keep track of the changes using a Version Control System (VCS) such as git. This is software which allows us to keep different versions of the same code, switch between these versions and even bring different versions together.&lt;/p&gt;
&lt;p&gt;The idea behind Continuous Integration is to automate checks and balances that the differences between our different version (or &lt;em&gt;branch&lt;/em&gt; as git calls it) are still compatible with the original version we &lt;em&gt;branched&lt;/em&gt; off from and that the logic of our application still behaves as expected.&lt;/p&gt;
&lt;p&gt;In our case, it would involve the following steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Check that the code is still valid and can be compiled.&lt;/li&gt;
&lt;li&gt;Run automated tests to ensure our code still implements our logic as we expect it to.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The result of this process will either be a compiled binary, ready to be used/deployed, which means we have successfully &lt;em&gt;integrated&lt;/em&gt; our code changes. Doing this continuously avoids large changes from piling up which increases the risk of the process failing. By automating it, we take the tedium and human element out of it.&lt;/p&gt;
&lt;p&gt;If any of the above steps fail then we should expect some sort of report stating why. This could be that our integration failed, or our new code changes cannot be compiled, or even that some of the tests failed. It is more ideal for us to catch these issues at integration stage rather than when we have already pushed the code out to our production environment.&lt;/p&gt;
&lt;p&gt;I plan on tackling the Continuous Deployment part in a future article&lt;/p&gt;
&lt;h2&gt;DevOps before it was cool&lt;/h2&gt;
&lt;p&gt;I like to say I did DevOps before DevOps was cool. I’m a DevOps Hipster if you will.&lt;/p&gt;
&lt;p&gt;I started my first internship a whole month before the other students. This is because my exams finished sooner and rather than sit and stare at home, the company too me in anyway.&lt;/p&gt;
&lt;p&gt;On one of my first days, the Technical Architect outlined one of my first tasks: “Take this thing called Jenkins and link it to this thing called GitLab”. Being a &lt;em&gt;very&lt;/em&gt; green programmer, this was my kind of challenge: no idea what I’m doing and no idea where to start from! Nowadays this task takes maybe ten minutes to complete, but circa 5-6 years ago it was not as trivial.&lt;/p&gt;
&lt;h3&gt;Jenkins&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://www.jenkins.io/&quot;&gt;Jenkins&lt;/a&gt; is the automation software that the company was using for its Continuous Integration. I decided to use Jenkins again for Cabin Fever as I had already revisited it as I was improving my DevOps skills. I was pleasently happy to find Jenkins had become more versatile over these past several years along with still being as usable as the day I first used it.&lt;/p&gt;
&lt;h3&gt;Jenkinsfile&lt;/h3&gt;
&lt;p&gt;The Jenkinsfile is a file describing the pipeline of automated steps that we want to perform.
In this case, we would want to perform the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Build and package the server binary&lt;/li&gt;
&lt;li&gt;Build a production bundle of the frontend&lt;/li&gt;
&lt;li&gt;(In the next article): deploy the server and the frontend to a server accessible by our players&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Focusing on the first two steps were not particularly difficult as I had setup similar pipelines in the past. It involved:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Starting up a docker container containing the build tools I require (for the server: GoLang and for the frontend: NodeJS)&lt;/li&gt;
&lt;li&gt;Running the tests against the built code (if there are any)&lt;/li&gt;
&lt;li&gt;Running a command to build the code&lt;/li&gt;
&lt;li&gt;Archiving the resulting binary/bundle for later use&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I involved docker to keep my builds isolated and repeatable. This means that they do not rely on having toolchains present or configured on the machine that Jenkins is installed on, making it easier to reproduce even if I had to run my Jenkins on another machine.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;It took some back and forth because I tried running the server compilation within an alpine image, but after eight attempts I finally completed the Jenkinsfile, meaning I can now build all of Cabin Fever on my personal Jenkins server after every push to the git repository.&lt;/p&gt;
&lt;p&gt;Right now the assets are just sitting there, archived within Jenkins, so they aren’t particularly useful. My next article will focus on the Continuous Deployment part - the aim being deploying the latest version of Cabin Fever to my personal server using Jenkins to enable quick playtesting without always running it on my development machine.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Next Entry&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/game-from-scratch-6&quot;&gt;6 - Continuous Deployment Part 1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Dev vs Ops - Hide and Seek]]></title><description><![CDATA[Introduction I received a merge request for review. The changes involved renaming a folder, with no other discernable changes. This MR had…]]></description><link>https://simonam.dev/dev-vs-ops-hide-and-seek/</link><guid isPermaLink="false">https://simonam.dev/dev-vs-ops-hide-and-seek/</guid><pubDate>Thu, 11 Mar 2021 21:10:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;I received a merge request for review. The changes involved renaming a folder, with no other discernable changes. This MR had the following paraphrased description:&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;The code works on my laptop, but this is the only way I could get it to build on Jenkins, otherwise the build fails.&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;🤔&lt;/p&gt;
&lt;h2&gt;Blurred Line&lt;/h2&gt;
&lt;p&gt;Similar to my &lt;a href=&quot;/dev-vs-ops-case-sensitivity/&quot;&gt;last blog post&lt;/a&gt; in the Dev vs Ops series, my mind wandered onto the difference between this dev’s machine (a Macbook similar to mine) and Jenkins. I knew it had something to do with docker.&lt;/p&gt;
&lt;p&gt;One of the selling points of containerisation is that the same container that builds on a dev’s machine gets built on your CI and gets shipped out to production, reducing the impact of different environments.&lt;/p&gt;
&lt;p&gt;To rule out the environments being the issue, I attempted to build the docker image locally and in fact the same error occurred. Something was failing between our machines and the docker daemon.&lt;/p&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;p&gt;Most dockerfiles will have the following directive or something similar within them:&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;COPY . .&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This line basically says copy everything from the host in this directory into the docker container’s root folder. One might want to only copy a &lt;code class=&quot;language-text&quot;&gt;src&lt;/code&gt; folder and nest in inside another directory in the container, which would look something like this:&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;COPY src/ /app/src/&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;When you run &lt;code class=&quot;language-text&quot;&gt;docker build&lt;/code&gt;, the docker daemon will actually show you the progress of copying the host’s context into the docker daemon and the amount of data it moves across this boundary.&lt;/p&gt;
&lt;p&gt;We do not want to move everything from the host into a container, typically we would only want to move the minimum required to be able to build and run our code.&lt;/p&gt;
&lt;p&gt;Rather than specifying exactly what we want to copy into the container (using &lt;code class=&quot;language-text&quot;&gt;COPY&lt;/code&gt;) in a whitelist fashion, instead we can do this in a blacklist fashion by creating a file called &lt;code class=&quot;language-text&quot;&gt;.dockerignore&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Similar to &lt;code class=&quot;language-text&quot;&gt;.gitignore&lt;/code&gt;, this file instructs the docker daemon to ignore specific files, folders or combination thereof.&lt;/p&gt;
&lt;p&gt;It turns out that one of the files was importing a file held inside a specific folder… this same folder was included in the &lt;code class=&quot;language-text&quot;&gt;.dockerignore&lt;/code&gt; file as it was not required at runtime in production. This meant that the file existed at build time on the dev’s machine, but not within the docker daemon when the container was being built.&lt;/p&gt;
&lt;h2&gt;Takeaway&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Works on my machine&lt;/em&gt; needs to be taken with a pinch of salt, or you’ll end up playing hide and seek with missing files.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Dev vs Ops - Case Sensitive Operating Systems]]></title><description><![CDATA[Series Introduction I am starting a series of articles exploring issues at the line between Development and Operations. In the past few…]]></description><link>https://simonam.dev/dev-vs-ops-case-sensitivity/</link><guid isPermaLink="false">https://simonam.dev/dev-vs-ops-case-sensitivity/</guid><pubDate>Sun, 07 Mar 2021 12:40:00 GMT</pubDate><content:encoded>&lt;h2&gt;Series Introduction&lt;/h2&gt;
&lt;p&gt;I am starting a series of articles exploring issues at the line between Development and Operations.&lt;/p&gt;
&lt;p&gt;In the past few years this middle ground has started being filled with DevOps Engineers. I believe that whilst DevOps is the logical next step in how we package, run and monitor our software, ever since it has no longer remained part of a software developer’s workflow it has caused Operational and Deployment issues to become “someone else’s problem”.&lt;/p&gt;
&lt;p&gt;This encourages developers to care less about how or where their code is run, so as long that it runs on their machine.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dev vs Ops&lt;/strong&gt; aims at re-blurring this line, documenting issues I resolve as I work on my DevOps skills. I believe that all software engineers should have at least a cursory understanding of how and where their code will run, without needing to become Kubernetes experts.&lt;/p&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;I was working on a NodeJS project written in TypeScript.&lt;/p&gt;
&lt;p&gt;One of the files had an import written as follows:&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;import shortid from &apos;shortId&apos;;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Nothing out of the ordinary, and the code builds! Works on my machine!
I submitted the code for review, it got approved and merged into our develop branch.&lt;/p&gt;
&lt;p&gt;Our CI tool (in this case, Jenkins), dutifully detected a new merge on develop and started building the code for deployment onto our development environment.&lt;/p&gt;
&lt;p&gt;The build failed and amidst the logs I found the following error (paraphrasing):&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;No module called shortId.&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;🤔&lt;/p&gt;
&lt;p&gt;I double checked both package file and lock file to be sure I was not being bitten by something at runtime rather than at buildtime, however &lt;code class=&quot;language-text&quot;&gt;shortid&lt;/code&gt; was clearly present in both.&lt;/p&gt;
&lt;h2&gt;Blurred Line&lt;/h2&gt;
&lt;p&gt;It did not feel like a code problem and I did have a sneaking suspicion scratching at the back of my brain that this issue resided at the boundary between Dev and Ops, so I put on my metaphorical debugging hat.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;What is the main difference between my machine and Jenkins?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The first that comes to mind is that my machine is a 2019 MacBook running MacOS whilst Jenkins is on a Linux machine. Is there potential for difference in behaviour between these two systems?&lt;/p&gt;
&lt;p&gt;I felt close to a solution but also a bit hesitant. I did know that we package all our software as Docker containers and that Jenkins did basically that: build the Docker container and push it out to AWS.&lt;/p&gt;
&lt;p&gt;To confirm whether it was the host system or the process of building the container, I decided to build the docker container locally. This was trivial to do as the Dockerfile is in the same repository.&lt;/p&gt;
&lt;p&gt;Build failed! Does not work in my container!&lt;/p&gt;
&lt;p&gt;From the Dockerfile I could see that we were using the &lt;code class=&quot;language-text&quot;&gt;node-alpine&lt;/code&gt; Docker image as a basis for our image. This convinced me that the issue was an incompatibility between running it on MacOS and running it in Linux, just not because of the machine the CI was running on as I originally thought was the issue.&lt;/p&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;p&gt;The issue turned out to be case-sensitivity. Whilst MacOS tolerated shortid being written as &lt;code class=&quot;language-text&quot;&gt;shortId&lt;/code&gt;, Alpine only began accepting the import once I renamed the import to &lt;code class=&quot;language-text&quot;&gt;shortid&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Had I kept the mindset that anything that happens that is not on my machine is DevOp’s concern (or fault), I could have easily wasted everyone’s time and caused unnecessary friction between our teams.&lt;/p&gt;
&lt;h2&gt;Takeaway&lt;/h2&gt;
&lt;p&gt;Be aware of how your code is packaged and will be run - it is still your responsibility even if its not on your machine anymore!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Kudos to Daniel D’Agostino for the article review.&lt;/em&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Visualising Covid-19 in Malta]]></title><description><![CDATA[Introduction When the pandemic the world became obsessed with numbers. Back then it was new infections, eventually deaths and now…]]></description><link>https://simonam.dev/c19-malta/</link><guid isPermaLink="false">https://simonam.dev/c19-malta/</guid><pubDate>Thu, 04 Mar 2021 20:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;When the pandemic the world became obsessed with numbers. Back then it was new infections, eventually deaths and now vaccinations. A flurry of visualisations popped up, most famously the one from &lt;a href=&quot;https://coronavirus.jhu.edu/map.html&quot;&gt;John Hopkins University&lt;/a&gt;. To help myself understand the progress of the pandemic in my home country of Malta and as an excuse to learn more about &lt;a href=&quot;https://www.gatsbyjs.com/&quot;&gt;GatsbyJS&lt;/a&gt;, I created my own visual tracker.&lt;/p&gt;
&lt;p&gt;The main difference between my Covid-19 tracker and the rest is that I wanted to localise it specifically to Malta. Whilst many use WHO, CDC or ECDC data, I based mine on two sources:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;a href=&quot;https://github.com/COVID19-Malta/COVID19-Cases&quot;&gt;Open Dataset&lt;/a&gt; published by the Public Health team in Malta&lt;/li&gt;
&lt;li&gt;A collection of &lt;a href=&quot;https://github.com/simonamdev/covid19-malta/blob/master/data/measures.json&quot;&gt;events&lt;/a&gt; that I maintain myself&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By encoding events on the chart alongside official data, I avoid speculation and unreliable reporting whilst also helping to show the effects of events and measures introduced on the situation.&lt;/p&gt;
&lt;h2&gt;How its made&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The data retrieval, parsing and calculation is a set of Python 3 scripts which retrieve from the Public Health open dataset.&lt;/li&gt;
&lt;li&gt;The visualisation is generated using GatsbyJS and react-vis, using TypeScript&lt;/li&gt;
&lt;li&gt;The automation of updating data depended on GitHub Actions until it was moved to Jenkins instead to avoid having to update the repository with the data generated every day.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can find the latest version hosted here: &lt;a href=&quot;https://www.c19.mt/&quot;&gt;https://www.c19.mt/&lt;/a&gt; and more information about the project on the &lt;a href=&quot;https://github.com/simonamdev/covid19-malta&quot;&gt;GitHub Repo&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[A real-time game from scratch - Physics, TDD and Core Game Loops [4]]]></title><description><![CDATA[Previous Entries 1 - Initial Setup 2 - WebSockets 3 - PixiJS Introduction Moving a spinning yellow box is not particularly interesting or…]]></description><link>https://simonam.dev/game-from-scratch-4/</link><guid isPermaLink="false">https://simonam.dev/game-from-scratch-4/</guid><pubDate>Fri, 27 Nov 2020 17:15:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;Previous Entries&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/game-from-scratch-1&quot;&gt;1 - Initial Setup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/game-from-scratch-2&quot;&gt;2 - WebSockets&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/game-from-scratch-3&quot;&gt;3 - PixiJS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Moving a spinning yellow box is not particularly interesting or engaging. I am trying to write a space game after all.&lt;/p&gt;
&lt;p&gt;To continue to head in that direction, I will begin implementing some basic physics. The challenge here is two-fold:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Realistic physics as if the player were in space&lt;/li&gt;
&lt;li&gt;Having the physics calculated on the server side and propogated to the client&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Space Physics&lt;/h2&gt;
&lt;p&gt;Physics in space is a bit weird if you have never been exposed to the math behind it.
Since there is no friction to speak of in the vacuum of space, Newton’s laws of motion display themselves beautifully when compared to how they are exhibited under Earth conditions.&lt;/p&gt;
&lt;p&gt;The laws are as follows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;If a body is at rest it will remain at rest. If a body is moving, it will keep moving at constant speed in a straight line. These hold unless the object is acted upon by a force.&lt;/li&gt;
&lt;li&gt;The acceleration of a body is proportional to the force applied to it and its mass. Conventionally known as &lt;em&gt;F = m * a&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;For every action, there is an equal and opposite reaction.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Newton’s First Law… in space&lt;/h3&gt;
&lt;p&gt;Imagine a ball sitting in space. It is not moving (it actually is if you look at it on a solar system or galactic scale, but let us keep things simple). Given nothing ever coming into contact with it, it will remain as it is: motionless.&lt;/p&gt;
&lt;p&gt;Now imagine an alien passes by and kicks the ball towards our sun. The ball now travels freely and smoothly in a straight line. With no one or nothing else interacting with it, it will happily keep moving towards the sun at the same speed (I am leaving out gravitational pulls for simplicity). There is no friction to take away from the kinetic energy that has been given to the ball.&lt;/p&gt;
&lt;h3&gt;Newton’s First Law… in code&lt;/h3&gt;
&lt;p&gt;Currently in our case, the inputs from the keyboard which affect the spinning square are the equivalent of the alien kick mentioned above - we are applying a force to a motionless object which is sitting in a no-friction vaccuum (AKA outer space).&lt;/p&gt;
&lt;p&gt;If we leave the ideas of force, mass and acceleration out of the picture for now (we will cover that later when we get to Newton’s Second Law), then the expectation of the movement of our spinning square is very simple: If we move it in a direction, we expect it to keep moving in that direction, until we move it in a different direction.&lt;/p&gt;
&lt;p&gt;To write the code implementing the above statement, we can use a software development methodology called &lt;em&gt;Test Driven Development&lt;/em&gt;. The idea is that we will write a program which will verify the behaviour of our physics program for us. If the tests pass, then the logic is correct, otherwise we should investigate as we may have introduced a bug, or our expectation of the behaviour of the physics program is incorrect.&lt;/p&gt;
&lt;p&gt;For our physics (which only considers the first law), I wrote a function which takes a position, a direction (which may be &lt;code class=&quot;language-text&quot;&gt;nil&lt;/code&gt;, meaning no direction was passed. &lt;code class=&quot;language-text&quot;&gt;nil&lt;/code&gt; ).&lt;/p&gt;
&lt;p&gt;Accompanying this, I wrote a function which takes a position, the expected position and a direction. This second function simply calls the first one and compares the result with the expected result. If there is a difference, then an error is thrown, otherwise the test passes.&lt;/p&gt;
&lt;p&gt;This form of automated testing helps ensure that the logic of my code is correct and remains correct throughout the development of the project. If the test begins failing, then either the code was changed in a way that violates the expected logic or the logic has changed and now the code needs to be changed to match it.&lt;/p&gt;
&lt;h2&gt;Core Game Loops&lt;/h2&gt;
&lt;p&gt;To apply the code written for our initial physics engine (which for now covers Newton’s First Law only), we need to reimagine the paradigm of how our server will work.&lt;/p&gt;
&lt;p&gt;In its current form, the game state is only ever updated when the client sends data to the server. Now that we are introducing phyics, the game state update should be calculated &lt;em&gt;regardless&lt;/em&gt; of whether the client send data or not. The data the client sends should count for the next physics update being calculated.&lt;/p&gt;
&lt;p&gt;The above means that we need to implement a &lt;em&gt;core game loop&lt;/em&gt;. Different from a core &lt;em&gt;gameplay&lt;/em&gt; loop, this is a continuous process which will receive inputs within a specific timeframe, then update the game state and push it out to all clients, even if no inputs are received.&lt;/p&gt;
&lt;p&gt;This raises the question: how often should we update? This update time, also called a &lt;em&gt;tick rate&lt;/em&gt; determines how responsive a game will be to inputs but also how much data is passed back and forth. The higher the tick rate, the more data will be received, regardless of whether inputs are sent to the server or not.&lt;/p&gt;
&lt;p&gt;A useful part of software is that these kind of decisions can be configurable. I do not need to take the decision now and deal with the consequences, but rather I can make the tick rate a parameter and then test out different values to try and find the sweet spot in the ratio of responsiveness to data flow.&lt;/p&gt;
&lt;p&gt;Here begins the usage of the concurrency primitives that Go provides. The core game loop will run in its own go routine and on a timer (if our tick rate is 30 ticks per second, then we it would be 1000ms / 30 = every 33.3ms) it will send out a game update. Until that time elapses, the game will accept inputs that the server receives and reccalculate the game state.&lt;/p&gt;
&lt;h2&gt;Demo&lt;/h2&gt;
&lt;p&gt;You can find the first video demo of the above logic implemented here:&lt;/p&gt;
&lt;iframe width=&quot;100%&quot; height=&quot;315&quot; src=&quot;https://www.youtube-nocookie.com/embed/AGWvJ7LWFko?rel=0&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;This would be trivial to implement in a client-side only fashion, however in the above demo all the game state is being calculated server-side, pushed out to the client at the end of every tick.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Whilst the server-side code looks like a bowl of delicious spaghetti and someone’s first concurrent project ever, it works! Whilst it is indeed a small step, it is beginning to feel like proper progress.&lt;/p&gt;
&lt;p&gt;Given how long this article is, I have chosen to implement the rest of Newton’s Laws in another article.
The next article however, will aim at improving my DevOps skills by implementing Continuous Integration and Continuous Deployment (CI/CD) for the latest version of Cabin Fever on every push to the &lt;code class=&quot;language-text&quot;&gt;main&lt;/code&gt; branch. This will include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Building the Frontend&lt;/li&gt;
&lt;li&gt;Building the Backend&lt;/li&gt;
&lt;li&gt;Ensuring that tests pass&lt;/li&gt;
&lt;li&gt;Deploying to a server&lt;/li&gt;
&lt;li&gt;Switching over from the old ones to the new&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can find the code for this article at the &lt;a href=&quot;https://github.com/simonamdev/cabin-fever/releases/tag/article-004&quot;&gt;article-004&lt;/a&gt; tag.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Next Entry&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/game-from-scratch-5&quot;&gt;5 - CI/CD&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[A real-time game from scratch - PixiJS [3]]]></title><description><![CDATA[Previous Entries 0 - Motivation 1 - Initial Setup 2 - WebSockets Introduction At some point I need to be able to show something on the…]]></description><link>https://simonam.dev/game-from-scratch-3/</link><guid isPermaLink="false">https://simonam.dev/game-from-scratch-3/</guid><pubDate>Thu, 12 Nov 2020 21:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;Previous Entries&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/game-from-scratch-0&quot;&gt;0 - Motivation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/game-from-scratch-1&quot;&gt;1 - Initial Setup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/game-from-scratch-2&quot;&gt;2 - WebSockets&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;At some point I need to be able to show something on the client side that is not simply text retrieved from the server.&lt;/p&gt;
&lt;p&gt;Short of making some super complex ASCII art based rendering engine I will need to use some library or framework to avoid messing with the canvas manually.&lt;/p&gt;
&lt;h2&gt;Research&lt;/h2&gt;
&lt;p&gt;I did not do much research before making my choice.&lt;/p&gt;
&lt;p&gt;Admittedly this may be flawed logic if one is considering a long term project, however in my case I would not mind starting with one and switching to the other as it means I would have gained in experience in two tools rather than one.&lt;/p&gt;
&lt;p&gt;As a result, my criteria for selection were very simple:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I had heard about it before&lt;/li&gt;
&lt;li&gt;It looks vaguely popular (GitHub stars, albeit a noisy indicator of popularity, was a useful consideration here)&lt;/li&gt;
&lt;li&gt;Updated semi-recently (This is easy to do with open-source projects since I can take a look at how recent the last commits are)&lt;/li&gt;
&lt;li&gt;If open-source, a permissive licence&lt;/li&gt;
&lt;li&gt;TypeScript support&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Fitting all of the above criteria very well, I chose PixiJS. I did not choose PhaserJS as it appeared to be more featureful but also more restrictive. I do not want to spend my time learning “the Phaser way” of writing a game.&lt;/p&gt;
&lt;p&gt;Admittedly in a few article’s time I may bemoan my lack of open mindedness on this matter.&lt;/p&gt;
&lt;h2&gt;PixiJS&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.pixijs.com/&quot;&gt;PixiJS&lt;/a&gt; is currently sitting at version 5. Whilst getting started was easy, the example on the GitHub readme was not completely “copy-pasteable”. It helps me greatly when my first impression of any library that I want to try out is an example I can simply copy paste onto my machine and get running as fast as possible so that I can verify my setup is correct.&lt;/p&gt;
&lt;p&gt;After a bit of digging, I found an &lt;a href=&quot;https://pixijs.io/examples&quot;&gt;examples section&lt;/a&gt; on the website which contains both the example running and editable source code I could tinker with to see the effect of my changes.&lt;/p&gt;
&lt;p&gt;Soon enough, I had a spinning yellow square on my screen. Initially it was rotating around the origin (top left corner) in and out of the screen. This would have left me very confused had my Bachelor’s Degree in Creative Computing not taught me about global and local co-ordinates and how rotations work in 2D and 3D spaces.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d879caa42a37aaf8f765d4bf9e802ea0/ea7fb/yellowsquare.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsTAAALEwEAmpwYAAABRUlEQVQ4y52UsUsDMRTGvzQVBBE6FUEprt2K4CI4C06CdvUPEOuom07uIiiCk5ubg4PQUf+ATurm7KCLk5pePknfu4bK3fXa4fGSy+WX70teAgDMCwPQWtCY/H8yInvAVv7BzZTAigGNlfYswOMdsFmPY6WBA3vVONBeBV+7IAnenirQTqFwZQHsXgkoxG/I3+BGM3srcoFL8+D5Aeg/BNQP4SRCv3cnLjAOaNXGSTuCnAO9V5UedIm0O5uqssi60UbNgG9PMjHpg54C9doP3997YL0a9zzXcrri7rpMdD7CgsJg+0dVXu6PVZl6l/x4o9adKHOqjgl4fwa2FofWioHp6a01QH6BjvGknx/ArdbIPpUrm7QOr48E9PkCHm6DM4h1WKK4R+9uyMs18GIPbMxFRSXqr/guD1XbiR6Ggsdh8ldmEH95/p5//FR/EwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Yellow Spinny Square&quot;
        title=&quot;Yellow Spinny Square&quot;
        src=&quot;/static/d879caa42a37aaf8f765d4bf9e802ea0/fcda8/yellowsquare.png&quot;
        srcset=&quot;/static/d879caa42a37aaf8f765d4bf9e802ea0/12f09/yellowsquare.png 148w,
/static/d879caa42a37aaf8f765d4bf9e802ea0/e4a3f/yellowsquare.png 295w,
/static/d879caa42a37aaf8f765d4bf9e802ea0/fcda8/yellowsquare.png 590w,
/static/d879caa42a37aaf8f765d4bf9e802ea0/ea7fb/yellowsquare.png 788w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;From here it was not difficult to wire up the exchange of data between client and server to the new visual frontend. On every tick, I would update the Yellow Spnning Square’s X and Y position values to whatever was received in the last message from the server.&lt;/p&gt;
&lt;p&gt;Whilst a promising start, the box moved too slowly! To get it move faster, I began multiplying the retrieved position by 10 so the box appeared to move faster across the screen.&lt;/p&gt;
&lt;p&gt;This however got me thinking: the front-end visualisation is tightly coupled to whatever state is sent back by the server, mapping it 1 to 1 from game state to pixel position on the browser.&lt;/p&gt;
&lt;p&gt;In reality, rather than simply using whatever the server sends back, it would make sense for the front-end to map it to the current browser, otherwise a player on desktop with a 4K screen would be able to see more of their surroundings than a player on a laptop with a 1080p screen.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;A yellow spinning square is not much of a game, but it beats simply showing text and provides a good foundation to be able to properly visualise changes in game state being sent by the server.&lt;/p&gt;
&lt;p&gt;So far any changes are caused by my input from the browser, so in my next article I will attempt to implement a basic physics system, simulating what one could expect if they were in space.&lt;/p&gt;
&lt;p&gt;You can find the code for this article at the &lt;a href=&quot;https://github.com/simonamdev/cabin-fever/releases/tag/article-003&quot;&gt;article-003&lt;/a&gt; tag.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Next Entry&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/game-from-scratch-4&quot;&gt;4 - Physics, TDD and Core Game Loops&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[A real-time game from scratch - WebSockets [2]]]></title><description><![CDATA[Previous Entries 0 - Motivation 1 - Initial Setup Introduction Cabin Fever is one of those side-projects which occupies a disproportionate…]]></description><link>https://simonam.dev/game-from-scratch-2/</link><guid isPermaLink="false">https://simonam.dev/game-from-scratch-2/</guid><pubDate>Wed, 28 Oct 2020 21:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;Previous Entries&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/game-from-scratch-0&quot;&gt;0 - Motivation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/game-from-scratch-1&quot;&gt;1 - Initial Setup&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Cabin Fever is one of those side-projects which occupies a disproportionate amount of mind share compared to &lt;a href=&quot;/introducing-tempus/&quot;&gt;Tempus&lt;/a&gt; or &lt;a href=&quot;https://www.c19.mt/&quot;&gt;C19&lt;/a&gt;. Whilst I am making an effort to work on side-projects which provide more end-to-end value over a deep technical dive form of value, the challenge of tackling a realtime entertainment requirement is far too enticing to not devote thinking time to.&lt;/p&gt;
&lt;h2&gt;Passing data back and forth&lt;/h2&gt;
&lt;p&gt;We need to be able to send instructions from the browser to the server and data back about the game state. In the first iteration I used the typical HTTP request-response so as to ensure that the setup was working.&lt;/p&gt;
&lt;p&gt;This method however is far too inefficient. There is an alternative called WebSockets.&lt;/p&gt;
&lt;h3&gt;WebSockets&lt;/h3&gt;
&lt;p&gt;Instead of setting up a connection, sending a request, receing a response, then closing the connection, we can setup the connection, send and receive as much data as we want, then close the connection once we’re done.&lt;/p&gt;
&lt;h3&gt;Server-side&lt;/h3&gt;
&lt;p&gt;On the server-side, I considered using the built in support for websockets, however I got started much faster with &lt;code class=&quot;language-text&quot;&gt;github.com/gorilla/websocket&lt;/code&gt;. Creating the initial websocket handler took only around 20 lines of code that hooked into the same http server I used for serving HTTP requests. My only issue was that I needed to provide a &lt;code class=&quot;language-text&quot;&gt;checkOrigin&lt;/code&gt; function which is basically the websocket equivalent of CORS.&lt;/p&gt;
&lt;h3&gt;Client-side&lt;/h3&gt;
&lt;p&gt;On the client-side, I did not need any custom libraries so far. I simply started using the built-in WebSockets implementation to connect to the new endpoint (in this case, &lt;code class=&quot;language-text&quot;&gt;/ws&lt;/code&gt;, since &lt;code class=&quot;language-text&quot;&gt;/&lt;/code&gt; was taken by a standard HTTP handler).&lt;/p&gt;
&lt;p&gt;Since we explicitly need to wait for the connection before starting, our code begins to look event-driven. To setup, I hook into &lt;code class=&quot;language-text&quot;&gt;onopen&lt;/code&gt; on the WebSocket object to ensure that the connection has been setup before we begin to send messages back to the server and the &lt;code class=&quot;language-text&quot;&gt;onmessage&lt;/code&gt; event to react to messages received from the server.&lt;/p&gt;
&lt;h2&gt;Towards a game&lt;/h2&gt;
&lt;p&gt;So far it has been sending datetimes back and forth, which does not amount to much of a game.&lt;/p&gt;
&lt;p&gt;The endgoal of this project is to create a realtime browser game, so the next step is to begin introducing features one would find in a game. An easy place to start is capturing keyboard events and sending them to the server, having it update some state on the sever which can be sent back to the client.&lt;/p&gt;
&lt;p&gt;This process has an important aim - by performing calculations on the server rather than the client we are aiming for a &lt;strong&gt;server-authorative&lt;/strong&gt; model, which can make cheating more difficult.&lt;/p&gt;
&lt;h3&gt;Keyboard events&lt;/h3&gt;
&lt;p&gt;Capturing keyboard input was not difficult at all - it simply involved attaching a listener to the &lt;code class=&quot;language-text&quot;&gt;onkeydown&lt;/code&gt; event on the &lt;code class=&quot;language-text&quot;&gt;&amp;lt;body&gt;&lt;/code&gt; tag of the HTML document. This gave me access to &lt;code class=&quot;language-text&quot;&gt;e.key&lt;/code&gt; which value is a string referring to the key pressed. In my case, I stuck to the classic &lt;code class=&quot;language-text&quot;&gt;WASD&lt;/code&gt; combination (which is a left hand equivalent of the arrow keys) which are used most of the time in games to control lateral movement.&lt;/p&gt;
&lt;p&gt;I only begin listening to keyboard inputs after &lt;code class=&quot;language-text&quot;&gt;onopen&lt;/code&gt; on the websocket and depending on whether the key was &lt;code class=&quot;language-text&quot;&gt;w&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;a&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;s&lt;/code&gt; or &lt;code class=&quot;language-text&quot;&gt;d&lt;/code&gt; I translate it to an enum with the values &lt;code class=&quot;language-text&quot;&gt;UP&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;DOWN&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;LEFT&lt;/code&gt; or &lt;code class=&quot;language-text&quot;&gt;RIGHT&lt;/code&gt;. Notably I do not update the internal state of the client side at this point in time. I do happen to know that some games do both at the same time and then update the state when it receives a response from the backend with the aim of making the game feel smoother and snappier. I plan on applying this optimisation later on.&lt;/p&gt;
&lt;h3&gt;Game State&lt;/h3&gt;
&lt;p&gt;On the server-side I initialise a struct called &lt;code class=&quot;language-text&quot;&gt;GameState&lt;/code&gt; which for now simply tracks the X and Y position of the one player. On receiving of a message, I update the X and Y value according to whether the received string is &lt;code class=&quot;language-text&quot;&gt;UP&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;DOWN&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;LEFT&lt;/code&gt; or &lt;code class=&quot;language-text&quot;&gt;RIGHT&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Two points of note here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The co-ordinate system in the browser is partially inverted from what we learn at school in Maths&lt;/li&gt;
&lt;li&gt;The game state has no wrap around validation and I ended up causing an integer underflow&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Browser Co-ordinate System vs School Maths Co-ordinates&lt;/h4&gt;
&lt;p&gt;In school, I learnt that a cartesian co-ordinate system works like this:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/dc51435a11b49078a73dad019bd1fefd/4e286/cartesian.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 101.35135135135135%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAUABQDASIAAhEBAxEB/8QAGAABAAMBAAAAAAAAAAAAAAAAAAECAwT/xAAWAQEBAQAAAAAAAAAAAAAAAAAAAQP/2gAMAwEAAhADEAAAAemc5z30UJULYJ//xAAVEAEBAAAAAAAAAAAAAAAAAAARMP/aAAgBAQABBQJj/8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAwEBPwEf/8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAgEBPwEf/8QAFBABAAAAAAAAAAAAAAAAAAAAMP/aAAgBAQAGPwIf/8QAFhABAQEAAAAAAAAAAAAAAAAAABEg/9oACAEBAAE/ISrv/9oADAMBAAIAAwAAABCjF4P/xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAEDAQE/EB//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAECAQE/EB//xAAbEAEBAQACAwAAAAAAAAAAAAABABEhkTFBUf/aAAgBAQABPxAUPWFkZu+4eXl7gPr3BZzseL//2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Cartesian Coordinate System&quot;
        title=&quot;Cartesian Coordinate System&quot;
        src=&quot;/static/dc51435a11b49078a73dad019bd1fefd/1c72d/cartesian.jpg&quot;
        srcset=&quot;/static/dc51435a11b49078a73dad019bd1fefd/a80bd/cartesian.jpg 148w,
/static/dc51435a11b49078a73dad019bd1fefd/1c91a/cartesian.jpg 295w,
/static/dc51435a11b49078a73dad019bd1fefd/1c72d/cartesian.jpg 590w,
/static/dc51435a11b49078a73dad019bd1fefd/a8a14/cartesian.jpg 885w,
/static/dc51435a11b49078a73dad019bd1fefd/fbd2c/cartesian.jpg 1180w,
/static/dc51435a11b49078a73dad019bd1fefd/4e286/cartesian.jpg 1582w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;When it comes to the browser (or graphics in general), the Y Axis is actually &lt;em&gt;inverted&lt;/em&gt;. Increasing the Y axis value means moving &lt;em&gt;downwards&lt;/em&gt; rather than upwards.&lt;/p&gt;
&lt;p&gt;Put simply:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Moving up: &lt;code class=&quot;language-text&quot;&gt;y--&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Moving down: &lt;code class=&quot;language-text&quot;&gt;y++&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Moving left: &lt;code class=&quot;language-text&quot;&gt;x--&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Moving right: &lt;code class=&quot;language-text&quot;&gt;x++&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;Wrap Around and Underflows&lt;/h4&gt;
&lt;p&gt;In a game like pong, there are boundaries to the game. The ball will never exit the boundaries (hopefully.)
This means that the x and y positions should never go lower than zero, if the origin point is the top left corner.&lt;/p&gt;
&lt;p&gt;In my case, whether the final game will have wrap around or not is a design decision for a later stage. An interesting occurrence is that, out of habit, I used the &lt;code class=&quot;language-text&quot;&gt;uint&lt;/code&gt; type to represent the &lt;code class=&quot;language-text&quot;&gt;X&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;Y&lt;/code&gt; values in the game state. &lt;code class=&quot;language-text&quot;&gt;uint&lt;/code&gt; stands for &lt;code class=&quot;language-text&quot;&gt;unsigned integer&lt;/code&gt;, meaning a zero or positive whole number, the value cannot be negative.&lt;/p&gt;
&lt;p&gt;As I tested the client (which was writing the retrieved game state to the DOM), the Y value suddenly jumped from &lt;code class=&quot;language-text&quot;&gt;0&lt;/code&gt; to &lt;code class=&quot;language-text&quot;&gt;18446744073709552000&lt;/code&gt;. In this case, since the value is unable to be negative, it wrapped all the way around the largest possible value that it can store. This is the same reason that Gandhi in the original Civilization game would become aggressive, birthing the meme of &lt;a href=&quot;https://knowyourmeme.com/memes/nuclear-gandhi&quot;&gt;Gandhi using Nuclear Weapons&lt;/a&gt;: the low aggression number would be pushed into a negative value, however since it was unsigned it would wrap around to the largest possible value.&lt;/p&gt;
&lt;p&gt;The solution to this is to provide validation of the values - if it reaches below zero, then switch it back to zero.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;So far I have not felt particularly challenged as I have not written any code which I was not already at least familar with.
Here are the following avenues I look forward to exploring:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A proper frontend for rendering graphics, using a framework such as PhaserJS or PixiJS&lt;/li&gt;
&lt;li&gt;Multiple Clients - how to handle receiving of data from multiple sources, updating a single game state and propogating those changes to all clients&lt;/li&gt;
&lt;li&gt;Physics - having the game state update on a core loop and push out updates to all clients regardless of whether they provide input or not&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You can find the code for this article at the &lt;a href=&quot;https://github.com/simonamdev/cabin-fever/releases/tag/article-002&quot;&gt;article-002&lt;/a&gt; tag.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Next Entry&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/game-from-scratch-3&quot;&gt;3 - PixiJS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Riċerka rigward Covid Alert Malta]]></title><description><![CDATA[This article is also available in English. Introduzzjoni Qiegħed nikteb dan l-artiku ftit iktar minn seba’ xhur wara li l-pandemija tan…]]></description><link>https://simonam.dev/covid-alert-malta-mt/</link><guid isPermaLink="false">https://simonam.dev/covid-alert-malta-mt/</guid><pubDate>Sun, 25 Oct 2020 10:31:00 GMT</pubDate><content:encoded>&lt;p&gt;This article is also &lt;a href=&quot;/covid-alert-malta-en&quot;&gt;available in English&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Introduzzjoni&lt;/h2&gt;
&lt;p&gt;Qiegħed nikteb dan l-artiku ftit iktar minn seba’ xhur wara li l-pandemija tan-Novel Coronavirus waslet f’Malta. Hemm ħafna aspetti tal-pandemija li nixtieq naqsam ħsiebi fuqhom, speċjalment rigward il-veritajiet li kixfet dwarna bħala umani u anke bħala nazzjon, f’sens soċjo-ekonomiku u anke politiku, iżda għal dan l-artiklu ser nżomm ma dak li naf u nifhem l-iktar fih: it-teknoloġija.&lt;/p&gt;
&lt;h2&gt;Contact tracing f’dawn iż-żminijiet&lt;/h2&gt;
&lt;p&gt;Il-Gvern Malti ħareg &lt;a href=&quot;https://covidalert.gov.mt/&quot;&gt;app tal-mowbajls għal-Contact tracing&lt;/a&gt;. Din hija application li tikkuntattja mowbajls oħrajn fil-viċinanzi sabiex tgħin fil-mekanżimi tal-contact tracing.&lt;/p&gt;
&lt;p&gt;L-ideja hija li, jekk inti kellek kuntatt ma xi ħadd li ġie pożittiv għall-Covid-19, il-proċess sabiex inti tiġi infurmat ssir ħafna ehfef u iktar malajr, ħabba li l-mowbajl tiegħek żamm kont tal-fatt li inti kont fil-viċinanzi ta’ każ pożittiv.&lt;/p&gt;
&lt;p&gt;L-ikbar problema għal sistema ta’ dan it-tip dejjem ħa tkun:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Kif tista tippreżerva l-privatezza ta’ individwu filwaqt li żżomm kont tagħhom u tal-kuntatti kollha tagħhom?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;L-awtorita’ teknoloġika tal-Gvern il-&lt;a href=&quot;https://mita.gov.mt/&quot;&gt;MITA&lt;/a&gt; tisħaq li l-app giet żviluppata mal-linji gwida tal-GDPR u li saret verifika tagħha mill-&lt;a href=&quot;https://mdia.gov.mt/&quot;&gt;Awtorita’ tal-Innovazzjoni Diġitali ta’ Malta, l-MDIA&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Sfornutatament nuqqas ta’ trasparenza hija komuni f’xogħol Malti - ġieli huwa diffiċli li tirċievi risposta fuq b’liema mod u sa liema livell sar dan ix-xogħol.&lt;/p&gt;
&lt;p&gt;F’azzjoni rari, il-MITA għazlu li l-app ssir &lt;a href=&quot;https://github.com/GOVMT-MITA&quot;&gt;open-source&lt;/a&gt;. Din hija azzjoni kuraġġjuża u hija l-ahjar metodu li biha l-MITA jistgħu jżidu fiduċja fis-sistema.&lt;/p&gt;
&lt;p&gt;Litteralment qegħdin juru li mgħandhom xejn x’jaħbu rigward kif taħdem l-app. Din tippermetti sabiex anke l-iktar nies xettiċi jistgħu jagħmlu riċerka tagħhom u jivverifikaw li s-sistema filfatt tippreserva il-privatezza u li hija għodda tajba sabiex tgħin il-pajjiż ġġib il-pandemija taht kontroll iktar malajr.&lt;/p&gt;
&lt;h3&gt;Iż-żwieg bejn il-privatezza u Contact tracing&lt;/h3&gt;
&lt;p&gt;Il-Contact tracing jinħass donnu jaħdem kontra l-privatezza nnifisha. Jikkuntatjak xi ħadd li ma tafx, dwar xiħaga li ma tistax tara b’għajnejk u trid taqsam magħhom fejn kont dan l-aħħar u ma min kont f’kemm tiflaħ dettall.&lt;/p&gt;
&lt;p&gt;Il-Contact tracing huwa &lt;em&gt;importanti ħafna&lt;/em&gt; sabiex titwaqqaf pandemija, allura akkost ta’ kemm forsi li tmur kontra il-kunċett tal-privatezza, nagħmluha xorta ħalli forsi nħallu din il-pandemija warajna ħarira iktar malajr,&lt;/p&gt;
&lt;p&gt;Meta tahseb ftit iktar fuqha, l-ħin u l-post fejn kellek kuntatt ma xi ħadd mhumiex importanti daqs &lt;em&gt;ma min&lt;/em&gt; kont f’kuntatt (skont parametri bħad-distanza bejnietkom, kemm domtu ħdejn xulxin u jekk kontux libsin maskra). Ma jkunx ahjar kieku stajna naqsmu din l-informazzjoni mingħajr ma nidħlu f’dettallji dwar &lt;em&gt;fejn&lt;/em&gt; u &lt;em&gt;meta&lt;/em&gt;?. Bonus jekk tista tinkludi nies li inti qas tafhom personalment.&lt;/p&gt;
&lt;p&gt;Problema oħra hija li l-memorja tagħna l-umani ma tantx hija robusta jew b’saħħitha. Ninsew dettalji, nħawdu l-ordni ta’ kif ġraw l-affarijiet jew mingħalina li xihaġa damet inqas jew iktar milli verament damet. Dik il-parlata li qbadt mal-ġirien għand il-grocer? Mingħalik li domt ħames minuti titkellem imma fil-verita’ domt għoxrin minuta. Ma jkunx aħjar kieku stajna niftakru d-dettalji ta’ dawn il-ġrajjiet b’ preċiżjoni assoluta?&lt;/p&gt;
&lt;p&gt;Grazzi għal-kollaborazzjoni bejn imħuħ intelliġenti li jaħdmu ma Apple u Google, ammont kbir mill-mowbajls moderni (dawk li għandhom sapport għal &lt;a href=&quot;https://en.wikipedia.org/wiki/Bluetooth_Low_Energy&quot;&gt;Bluetooth Low Energy (BLE)&lt;/a&gt; u li għandhom Android 6 “Marhsmallow” jew ogħla jew iOS 13.5 jew ogħla) issa għandhom sistema ta’ Privacy Preserving Contact tracing mibnijja diretta fl-Operating System tagħhom.&lt;/p&gt;
&lt;h3&gt;Dan l-Artiklu&lt;/h3&gt;
&lt;p&gt;Dan l-artiklu jiffoka fuq l-implementazzjoni speċifika tal-app li żviluppaw l-MITA li taqbad mas-sitema tal-&lt;a href=&quot;https://Covid19.apple.com/Contacttracing&quot;&gt;Apple-Google Privacy-Preserving Contact tracing&lt;/a&gt;, minflok is-sistema tal-PPCT innifisha. L-għan huwa li nesplora u nivverifika d-dikjarazzjoni li din l-app ma tiġbor l-ebda tip ta’ data dwar il-location tal-individwu li qiegħed jużahha.&lt;/p&gt;
&lt;p&gt;Ser nimxi b’metodu sempliċi ta &lt;strong&gt;&lt;em&gt;Mistoqsija&lt;/em&gt;&lt;/strong&gt; u &lt;strong&gt;&lt;em&gt;Risposta&lt;/em&gt;&lt;/strong&gt; minflok nirrispondi sett mistoqsijiet fl-aħħar.&lt;/p&gt;
&lt;h3&gt;Covid Alert Malta&lt;/h3&gt;
&lt;p&gt;Aħjar nibda b’spjegazzjoni ta’ xinhu open-source.&lt;/p&gt;
&lt;p&gt;Software li huwa &lt;em&gt;open-source&lt;/em&gt; huwa software fejn il-code tal-programm jista jinqara u jiġi kkompilat, filwaqt li &lt;em&gt;closed-source&lt;/em&gt; ma jħallikx taqra l-code tal-programm u min qiegħed jużaħ jista biss juża s-software fil-forma diġa kkompilata (bhal Microsoft Word) jew bħala servizz (bħal Google Maps).&lt;/p&gt;
&lt;p&gt;B’open-source software (ġieli mqassar bħala OSS), nistgħu nivverifikaw l-imġieba tal-programm jew billi nħaddmu l-programm jew billi naqraw s-source code, minħabba li jista jkun li jkun hemm imġieba li qiegħda ssir wara l-kwinti imma li mhiex tidher. F’closed-source, nistgħu biss nosservaw l-imġieba tal-programm billi nħaddmuh, u rridu noqogħdu fuq x’jingħad lilna sabiex nkunu nafu x’jagħmel (jew ma jagħmilx) meta nħaddmuħ.&lt;/p&gt;
&lt;p&gt;Kieku kelli noffri analoġija, kejk taċ-ċikkulata li huwa &lt;em&gt;closed-source&lt;/em&gt; huwa mit-tip li tixtri lest u li tista tġawdi mill-ewwel. Jidher sabiħ u għandu togħma tajba, imma mgħandek l-ebda ideja fuq kif, fejn, meta u b’liema ingredjenti ssajjar. Il-furnar jista jassigurak li l-kejk mgħandux gluten, huwa “vegan” jew ssajjar f’ambjent li huwa “nut free”, imma l-uniku assigurazzjoni li għandek tal-verita hija fil-kelma tal-furnar.&lt;/p&gt;
&lt;p&gt;Kejk li huwa &lt;em&gt;open-source&lt;/em&gt; jigi bir-riċetta inkluża, b’lista speċifika ħafna tal-ingredjenti, l-ammonti tagħhom, is-sors tagħhom u l-proċess tat-tisjir. Nistgħu nosservaw jew nieklu l-kejk filwaqt li ma nagħtux każ tar-riċetta, imma wkoll nistgħu nuzaw ir-riċetta sabiex nsajru verzjoni tagħna tal-kejk, jew sabiex nbiddlu parti minnu għal xihaġa li nippreferu iktar (per ezepmju, nagħmlu ġamm flok frosting taċ-ċikkulata) jew sabiex naraw jekk nispiċċawx bl-istess kejk ezatt li kien diġa misjur u mogħti lilna jekk insegwu l-istruzzjonijiet.&lt;/p&gt;
&lt;p&gt;F’dan il-każ, l-app ta’ Covid Alert Malta, kemm tal-Andriod u anke tal-iOS, flimkien mas-server li tieħu hsieb tinnotifika l-kuntatti pożittivi, huma &lt;strong&gt;forks&lt;/strong&gt; (l-ekwivalenti ta’ remix fis-software) ta’ apps oħrajn tal-Contact tracing li huma open-source, speċifikament is-&lt;a href=&quot;https://github.com/DP-3T/dp3t-app-android-ch&quot;&gt;SwissCovid App&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Mistoqsija&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Iġifieri l-MITA serqet l-app SwissCovid u għamlitha daqs li kieku tagħha?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Risposta&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Lanqas xejn. Il-liċenzja tal-open-source li taħtha saret l-app SwissCovid tippermetti modifikazzjonijiet u anke tqassim (nikteb f’iktar dettall fuq dan iktar tard fl-artiklu).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Mistoqsija&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Għalfejn l-MITA ma kitbux app mill-ġdid?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Risposta&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Jiena ma nitkellimx għal-MITA, allura ma nistax nirrispondi f’isimhom, imma nista nispekula.&lt;/p&gt;
&lt;p&gt;Qabel ma tikteb kwalunkwe software, wieħed għandu jagħmel evalwazzjoni fuq il-fiżabilita’ tagħha, kemm f’termini finanzjarji u anke ta’ ħin. Il-kitba ta’ app tal-Contact Tracing mill-bidu nett hija possibbli, imma l-ammont ta’ investiment ta’ ħin u għarfien li hemm bzonn huwa għoli ħafna. Fil-każ ta’ pandemija l-ħin huwa ta’ importanza kbira allura kwalunkwe soluzzjoni li tinvolvi investiment għoli ta’ ħin ma tibqax soluzzjoni tajba.&lt;/p&gt;
&lt;p&gt;Jkun iktar vijabbli kieku ssib implementazzjoni eżistenti u taddattahha għall-bżonnijiet tiegħek speċifiċi. Wahda mill-idejat prinċipali wara open-source hija li wieħed jevita xogħol doppju. Code jista jigi mqassam f’spirtu ta’ kollaborazzjoni fejn kulħadd jibbenefika u nevitaw li nerggħu nivvintaw ir-rota mill-bidu kull darba.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Mistoqsija&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Il-MITA obbligati li jqassmu s-source code?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Risposta&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Iva. (Kważi) kull open-source code tiġi b’liċenzja li tispeċifika id-dmirijiet ta’ individwu jew organiżazzjoni meta jagħmlu użu mill-code. F’dan il-każ, l-app SwissCovid app hija liċenzjata taħt il-&lt;a href=&quot;https://www.mozilla.org/en-US/MPL/2.0/FAQ/&quot;&gt;Mozilla Public Licence 2.0&lt;/a&gt;. Nikkwota dirett mill-FAQ tagħhom (bl-ingliż):&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Q9: I want to distribute (outside my organization) MPL-licensed source code that I have modified. What do I have to do?

To see the complete set of requirements, read the license. However, generally:

You must inform the recipients that the source code is made available to them under the terms of the MPL (Section 3.1), inċluding any Modifiċations (as defined in Section 1.10) that you have created.

You must make the grants described in Section 2 of the license.

You must respect the restriċtions on removing or altering notiċes in the source code (Section 3.4).&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Mistoqsija&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;App li hija open-source hija inqas sigura?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Risposta&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Is-sigurta’ ta’ kull tip ta’ software mhiex marbuta ma jekk hijex open-source jew le, imma hija marbuta mal-kwalita’ tas-software innifsu.&lt;/p&gt;
&lt;p&gt;Meta xi ħadd jikteb software li muhiex sigur u jagħzel li ma jiżvelax is-source code, hija l-ekwivalenti ta’ li ssajjar kejk imma tagħżel li ma tiżvelax jekk fihx gluten jew le. Irrispettivament ta’ jekk tiżvelax jew le, l-ammont ta’ gluten ma jinbidilx u jista jwassal xorta għal-ħsara fis-saħħa ta’ min jiekol il-kejk jekk min qiegħed jieklu huwa intolleranti għal-gluten, jew huwa coeliac.&lt;/p&gt;
&lt;p&gt;Li tagħzel li ma tizvelax source code fuq bazi ta’ sigurta’ huwa metodu magħruf bħala &lt;em&gt;sigurta’ permezz tal-oskurita’&lt;/em&gt;, li huwa metodu skoraġġat minn kwalunke persuna professjonali li taħdem fis-setturi ta’ Software, Sigurta’, jew l-Informatika.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Mistoqsija&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Din l-app mhux ġiet ċertifikata mill-MDIA?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Risposta&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Wieħed jispera li software bħal Covid Alert Malta, fejn f’dinja idejali hija installata fuq kull mowbajl li hawn fil-pajjiz, tkun għaddiet minn proċess rigoruż ta’ inspezzjoni u verifikazzjoni.&lt;/p&gt;
&lt;p&gt;S’issa l-MDIA assigurat li l-app għaddiet mill-proċess ta’ ċertifikażzjoni tagħhom, iżda dan il-proċess muhiex publikku jew trasparenti. Fidi għamja f’instituzzjoni għandha tkun evitata’ minħabba li hija magħmula mill-umani. Jiena ninkoraġġi lill-MDIA sabiex jaqsmu kif u sa liema livell sar dan iċ-ċertifikażzjoni għax dan jżid iktar il-fidi fis-sistema, li għandha twassal għall-aktar nies jinstallaw l-app.&lt;/p&gt;
&lt;p&gt;Irrispettivament ta’ dan, il-fatt li l-app hija open-source tħalli lil &lt;em&gt;kulħadd&lt;/em&gt; jagħmel verifika tagħha. Irrispettivament ta’ kemm għandek fidi f’kwalunkwe instituzzjoni, kemm jekk inti inqas jew iktar pedantiku mill-instituzzjoni, il-fatt li s-source code huwa pubbliku u tista ssir riċerka fuqu ttik l-opportunita’ sabiex tagħti l-approvazzjoni tiegħek personali.&lt;/p&gt;
&lt;h2&gt;Is-source code&lt;/h2&gt;
&lt;p&gt;Il-kompetenza u l-esperjenza tiegħi tinsab l-iktar fl-apps li huma cross platform. Jiena ma niktibx apps f’lingwi speċifiċi ta’ pjattaformi (bhal Java, Kotlin, Objective C jew Swift), iżda għandi espejenza nikteb Java u esperjenza fl-apps in ġenerali u nikkunsidra din l-esperjenza biżżejjed sabiex nagħmel din ir-riċerka.&lt;/p&gt;
&lt;p&gt;Is-source code tal-app Covid Alert Malta tinstab f’dan il-link: &lt;a href=&quot;https://github.com/GOVMT-MITA/dp3t-app-android-ch&quot;&gt;https://github.com/GOVMT-MITA/dp3t-app-android-ch&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Minħabba li hija fork, nistgħu &lt;a href=&quot;https://github.com/DP-3T/dp3t-app-android-ch/compare/master...GOVMT-MITA:covidalert-mt?w=1&quot;&gt;nikkomparaw&lt;/a&gt; id-differenzi bejn ir-repositorju oriġinali u dak tal-MITA. Din id-&lt;strong&gt;diff&lt;/strong&gt;, qasir għal &lt;strong&gt;diff&lt;/strong&gt;erenza, hija lista ġġenerata awtomatikament.&lt;/p&gt;
&lt;p&gt;Minn ħarsa ta’ malajr, jidher li l-uniku żieda fl-app mill-MITA kienet l-għażla tal-lingwa meta tużaħħa l-ewwel darba u l-lokaliżżazzjoni minhabba l-lingwa Maltijja. Ma jidher li ma hemm l-ebda differenza fl-imġieba tal-app minn dak provdut minn SwissCovid.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Mistoqsija&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;L-App tibqa tinsisti li nixgħel is-setting tal-location. Kulħadd jinsisti li l-app ma żżommx kont tal-location tiegħi. Għala hemm din id-diskrepanza? Xi ħadd qiegħed jigdeb?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Risposta&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Din id-diskrepanza hija problema sfortunata tal-Esperjenza Umana (jew User Experience / UX bl-ingliż) tas-sistema tal-Android. Tidher biċ-ċar fl-iscreenshot ta’ hawn taħt:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/6f77125ea8a073c0094fd0ae1867689d/47311/location-perms.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 81.08108108108108%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAQABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAMFAf/EABUBAQEAAAAAAAAAAAAAAAAAAAEC/9oADAMBAAIQAxAAAAG4IQXQMGP/xAAaEAACAgMAAAAAAAAAAAAAAAAQEQADEiMx/9oACAEBAAEFAuRi3N7x/8QAFxEAAwEAAAAAAAAAAAAAAAAAAQIQEf/aAAgBAwEBPwEJs//EABURAQEAAAAAAAAAAAAAAAAAABAR/9oACAECAQE/Aaf/xAAZEAEAAgMAAAAAAAAAAAAAAAABABACEjL/2gAIAQEABj8Cs0xGcFf/xAAcEAACAgIDAAAAAAAAAAAAAAAAARExECFB0fD/2gAIAQEAAT8h00ixpI5kn37FSP/aAAwDAQACAAMAAAAQxM//xAAXEQEAAwAAAAAAAAAAAAAAAAARARCR/9oACAEDAQE/EALG1//EABYRAAMAAAAAAAAAAAAAAAAAAAEQEf/aAAgBAgEBPxAwv//EAB0QAQEAAQQDAAAAAAAAAAAAAAERACExQXFRYbH/2gAIAQEAAT8QVCW+K/MFYk7Mt1xGghtbPHJ7zSd3vFlgGahn/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Permessi tal-location&quot;
        title=&quot;Permessi tal-location&quot;
        src=&quot;/static/6f77125ea8a073c0094fd0ae1867689d/1c72d/location-perms.jpg&quot;
        srcset=&quot;/static/6f77125ea8a073c0094fd0ae1867689d/a80bd/location-perms.jpg 148w,
/static/6f77125ea8a073c0094fd0ae1867689d/1c91a/location-perms.jpg 295w,
/static/6f77125ea8a073c0094fd0ae1867689d/1c72d/location-perms.jpg 590w,
/static/6f77125ea8a073c0094fd0ae1867689d/a8a14/location-perms.jpg 885w,
/static/6f77125ea8a073c0094fd0ae1867689d/47311/location-perms.jpg 1080w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;L-ewwelnett, nistgħu nikkonfermaw jekk l-app għandhiex aċċess għad-data tal-location, kemm jekk hux bil-Wi-Fi (magħruf bħala Coarse Location) jew bil-GPS (magħruf bħala Fine Location).&lt;/p&gt;
&lt;p&gt;Developer jrid jiddikjara minn qabel xinhuma l-permessi li l-app għandha bżonn sabiex taħdem. F’Android, dawn jinasbu f’file jismu &lt;code class=&quot;language-text&quot;&gt;AndroidManifest.xml&lt;/code&gt;. F’dan il-każ, nistgħu nsibuħ &lt;a href=&quot;https://github.com/GOVMT-MITA/dp3t-app-android-ch/blob/covidalert-mt/app/src/main/AndroidManifest.xml&quot;&gt;hawnhekk&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Il-permessi li nsibu huma:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;lt;uses-permission android:name=&quot;android.permission.BLUETOOTH&quot; /&gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;lt;uses-permission android:name=&quot;android.permission.INTERNET&quot; /&gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Kieku l-app riedet tkun taf l-location tiegħek, konna naraw dawn il-permessi minflok, kif nsibu miktub fid-dokumentazzjoni ta’ &lt;a href=&quot;https://developer.android.com/training/location/permissions#foreground&quot;&gt;Android Developers&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;lt;uses-permission android:name=&quot;android.permission.ACCESS_COARSE_location&quot; /&gt;
&amp;lt;uses-permission android:name=&quot;android.permission.ACCESS_FINE_location&quot; /&gt;
&amp;lt;uses-permission android:name=&quot;android.permission.ACCESS_BAċKGROUND_location&quot; /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Ċar daqs il-kristall: Covid Alert Malta &lt;strong&gt;mihiex kapaċi tkun taf fejn inti&lt;/strong&gt;. Mhux direttament għallinqas.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Mistoqsija&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Mela għaliex l-app għandha bzonn is-setting tal-location mixgħula biex taħdem?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Risposta&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Insa Covid Alert Malta għal mument. Informazzjoni ta’ location &lt;em&gt;tista tiġi esposta&lt;/em&gt; jekk Bluetooth huwa mixgħul.&lt;/p&gt;
&lt;p&gt;Jekk Bluetooth huwa mixgħul, mela l-mowbajl ħa jkun kapaċi kemm jitrasmetti u anke jirċievi data fuq BT. Apparat differenti huwa kapaċi jsib lil xulxin jekk wieħed minnhom huwa ssetjat biex jkun &lt;em&gt;discoverable&lt;/em&gt;. Dan huwa l-proċess li jintuża meta tissetja apparat b’kapaċita tal-BT (bħal wireless headset) f’pairable mode. Il-headset jkun qiegħed jgħajjat lil-apparat kollu fil-viċinanza li huwa kapaċi jigi paired u inti tuża l-mowbajl tiegħek sabiex tagħmel pair miegħu, u b’hekk jistgħu jqassmu data ma xulxin, li f’dan il-każ jkun sabiex tisma l-mużika.&lt;/p&gt;
&lt;p&gt;Wieħed mill-modi li apparat jista jagħraf apparat ieħor huwa mill-MAC Address tiegħu. Dan qisu l-indirizz tad-dar. Jiena nagħraf id-dar ta’ siehbi Ċikku għax naf li Ċikku jgħix f’numru 24 fi Triq il-Kbira. Bl-istess mod, il-mowbajl tiegħek jagħraf apparat ieħor li jkollu indirizz fil-forma ta’ kelma hekk: &lt;code class=&quot;language-text&quot;&gt;D9:79:D4:9C:C9:1C&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Jekk l-mowbajl tiegħek qiegħed jaqsam dan l-indirizz ma kull apparat ieħor fil-viċinanza, mela l-apparat jista juża dan sabiex jagħmel korrelazzjoni bejn l-MAC Address u l-persuna preżenti. Tipikament, dan jsir b’apparat msejjaħ &lt;a href=&quot;https://en.wikipedia.org/wiki/Bluetooth_low_energy_beacon&quot;&gt;BLE Beacon&lt;/a&gt; f’proċess bħal dan:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Sid ta’ ristorant jixtieq jżomm kont ta’ kemm hemm nies fir-ristorant&lt;/li&gt;
&lt;li&gt;Is-sid jixtri BLE Beacon u jwaħħalha fuq s-suffitt&lt;/li&gt;
&lt;li&gt;Inti tmur regolari fir-ristorant - kull nhar ta’ Ġimgħa wara x-xogħol għal-happy hour.&lt;/li&gt;
&lt;li&gt;Il-mowbajl tiegħek qed jgħajjat il-MAC Address tiegħu lil kull apparat ieħor fil-viċinanza&lt;/li&gt;
&lt;li&gt;Il-BLE Beacon tieħu nota tal-MAC Address tal-mowbajl tiegħek&lt;/li&gt;
&lt;li&gt;Is-sid issa jaf li hemm persuna tiġi kull nhar ta’gimgħa, ħabba li dejjem jara l-istess MAC Address darba fil-ġimgħa fl-istess ħin.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Dan ifisser li s-sid jaf min inti, l-indirizz tad-dar tiegħek, in-numru tal-karta tal-identita’, eċċ? Le, qas xejn.&lt;/p&gt;
&lt;p&gt;Dan ifisser li s-sid jaf ta’ min hu l-mowbajl b’dak il-MAC Address? Iva, forsi. Forsi qegħdin jaraw s-CCTV biex jaraw min jidħol mill-bieb meta dik il-MAC Address jiġi rreġistrat mill-BLE Beacon. Din magħrufa bħala &lt;em&gt;attakk tal-korrelazzjoni&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Dan li ridt infisser meta semmejt li l-location &lt;em&gt;tista&lt;/em&gt; tiġi esposta. Ma jfissirx illi li jkollok l-BT mixgħul ħa jqassam l-indirizz tad-dar tiegħek, imma tipprovdi biċċa informazzjoni zgħira, li permezz tagħha, jekk xi ħadd lest li jagħmel biżżejjed xogħol biex jiġbor iżjed biċċiet informazzjoni bħal din, jaf jkunu kapaċi jsiru jafu min hu jew hi s-sid tal-mowbajl.&lt;/p&gt;
&lt;p&gt;Ifhem din: l-istess jsir bil-Wi-Fi. Il-mowbajl tiegħek qiegħed jagħmel riklam tiegħu nnfisu lil routers fil-viċinanzi (proċess msemmi probing), li fil-proċess jizvela il-MAC Address tiegħu, li jekk xi ħadd jzomm kont tiegħu, jaf jikkawza espożizzjoni tad-data tal-location.&lt;/p&gt;
&lt;p&gt;Covid Alert Malta mhiex il-&lt;em&gt;kawża&lt;/em&gt; ta’ din l-espożizzjoni, imma din hija r-raġuni li location services trid tkun mixgħula sabiex taħdem l-app, anke jekk Covid Alert Malta muhiex l-app li qiegħed jikkawza din l-espożizzjoni.&lt;/p&gt;
&lt;h2&gt;Konklużjoni&lt;/h2&gt;
&lt;p&gt;Jiena tal-parir li wieħed għandu jinstalla Covid Alert Malta għal-dawn ir-raġunijiet:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Is-source code hija pubblika għal kull min jixtieq jagħmel riċerka fuqha&lt;/li&gt;
&lt;li&gt;Ma tagħmel l-ebda użu mill-GPS jew minn data tal-location tiegħek&lt;/li&gt;
&lt;li&gt;Tgħin sabiex tħaffef il-proċess ta’ Contact Tracing li qiegħed jgħin sabiex tiġi kkontrollata l-pandemija tal-Covid-19&lt;/li&gt;
&lt;li&gt;Hemm ħafna apps oħrajn li qegħdin jieħdu d-data tal-location tiegħek f’modijiet iktar ovji, bhal Facebook, TikTok, Google Maps, Revolut, eċċ. Li tkun ikkonċernat fuq Covid Alert Malta imma mbagħad ma tkunx ikkonċernat fuq dawn l-apps l-ohrajn li huma diga insallati turi nuqqas ta’ fehma fuq kemm issir frekwenti dan it-teħid tad-data.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Hemm problema ohra meta tuża l-Bluetooth il-ħin kollu li mgħandhiex x’taqsam ma sigurta’: il-fatt li jista jwaqqa l-batterija iktar malajr. Jiena naħseb li dan huwa prezz zgħir biex inħallsu għal din l-opportunita ghal rapid contact tracing li jżomm il-privatezza tal-individwu, speċjalment meta daż-żminijiet jeżistu battery packs jew quick charging.&lt;/p&gt;
&lt;p&gt;Inti tista tniżżel Covid Alert Malta minn hawnhekk:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=mt.gov.dp3t&quot;&gt;For Android&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://apps.apple.com/us/app/id1513522951&quot;&gt;For iOS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Grazzi kbira lil-mara tiegħi Rachel tal-paċenzja li ħadet filwaqt ċċekjat dan l-artiklu.&lt;/em&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Reviewing Covid Alert Malta]]></title><description><![CDATA[Il-verżjoni bil-Malti ta’ dan l-artiklu jista jinsab hawnekk. Introduction I write this article a bit more than seven months after the Novel…]]></description><link>https://simonam.dev/covid-alert-malta-en/</link><guid isPermaLink="false">https://simonam.dev/covid-alert-malta-en/</guid><pubDate>Sun, 25 Oct 2020 10:30:00 GMT</pubDate><content:encoded>&lt;p&gt;Il-verżjoni bil-Malti ta’ dan l-artiklu jista &lt;a href=&quot;/covid-alert-malta-mt&quot;&gt;jinsab hawnekk&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;I write this article a bit more than seven months after the Novel Coronavirus pandemic arrived onto Maltese shores. There are many aspects of this pandemic that I wish to share thoughts about, especially on the truths that it has revealed about us as a species and as a nation in socio-economic and political realms, however I will, for this article, stick to what I know best, which is the technological.&lt;/p&gt;
&lt;h2&gt;Contact Tracing in the 21st Century&lt;/h2&gt;
&lt;p&gt;The Maltese Government has released a &lt;a href=&quot;https://covidalert.gov.mt/&quot;&gt;contact tracing smartphone application&lt;/a&gt;. This is an application which contacts other smartphones in the person’s immediate vicinity, for the purpose of augmenting a contact tracing mechanism.&lt;/p&gt;
&lt;p&gt;The idea is that, if someone you came into contact with tests positive for Covid-19, then the process of informing you of this contact becomes much easier, since your smartphone kept track of the fact that you were in the vicinity of a positive case.&lt;/p&gt;
&lt;p&gt;The main issue around such a system is and always will be:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How can you preserve the privacy of an individual whilst still keeping track of them and their contacts?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The Maltese Government’s technological authority &lt;a href=&quot;https://mita.gov.mt/&quot;&gt;MITA&lt;/a&gt; has claimed that the app was developed in line with GDPR and also had it audited by the &lt;a href=&quot;https://mdia.gov.mt/&quot;&gt;Malta Digital Innovation Authority&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Unfortunately, lack of transparency is a persistent issue in Maltese affairs - it is often difficult to get an answer as to how something was performed or to what level.&lt;/p&gt;
&lt;p&gt;In a rare move, MITA have opted to &lt;a href=&quot;https://github.com/GOVMT-MITA&quot;&gt;open-source&lt;/a&gt; the entire stack for this application. This is the boldest and best move that MITA could ever make with the aim of fostering trust in the system.&lt;/p&gt;
&lt;p&gt;They are literally stating that they have nothing to hide when it comes to what is going on under the hood. This enables even the most skeptical amongst us to review, audit and verify that the system is indeed preserving privacy and equipping our country to bring the pandemic under control faster.&lt;/p&gt;
&lt;h3&gt;Marrying privacy and contact tracing&lt;/h3&gt;
&lt;p&gt;Contact tracing feels like the evil twin of privacy. You are contacted by someone you do not know, about something you cannot see and you have to share where you were and whom you were with within the past few days, in as much detail as possible.&lt;/p&gt;
&lt;p&gt;Despite this, it turns out that contact tracing is &lt;em&gt;super important&lt;/em&gt; for containing a pandemic, so despite the seemingly inherent privacy nightmare, we do it, so that maybe, just maybe, we can put all this pandemic behind us a little bit sooner.&lt;/p&gt;
&lt;p&gt;On closer inspection however, the exact time, date and place you were within proximity to someone is not as important as exactly &lt;em&gt;who&lt;/em&gt; you were within proximity to (given parameters such as whether you were within two metres of each other, for longer than fifteen minutes and without any sort of face covering).&lt;/p&gt;
&lt;p&gt;Wouldn’t it be great if we could share the information of &lt;em&gt;who&lt;/em&gt; we were within proximity of, without going into the other details of &lt;em&gt;where&lt;/em&gt;, &lt;em&gt;when&lt;/em&gt; and possibly &lt;em&gt;why&lt;/em&gt;? Bonus points if it could include people we do not even personally know.&lt;/p&gt;
&lt;p&gt;Another issue is that our human memory is not particularly robust or reliable. We forget details, we mix up their order, we perceive time to be longer or shorter than it actually is. That chat you had with your neighbour at the grocer? It felt like five minutes but you actually spent twenty minutes. Wouldn’t it be great if we could recall details of our interactions with the highest precision?&lt;/p&gt;
&lt;p&gt;It turns out, thanks to a collaboration between the greatest minds at Apple and Google, most modern smartphones (those supporting &lt;a href=&quot;https://en.wikipedia.org/wiki/Bluetooth_Low_Energy&quot;&gt;Bluetooth Low Energy (BLE)&lt;/a&gt; and running Android 6 Marshmallow and above or iOS 13.5 and above) now have Privacy-Preserving Contact Tracing capability built into their respective Operating Systems.&lt;/p&gt;
&lt;h3&gt;Article Outline&lt;/h3&gt;
&lt;p&gt;This article will focus heavily on the specific implementation that MITA have released that hooks into the &lt;a href=&quot;https://covid19.apple.com/contacttracing&quot;&gt;Apple-Google Privacy-Preserving Contact Tracing&lt;/a&gt; system rather than the PPCT system itself. The aim is to challenge and verify the specific claims that it does not gather any sort of location data of the individual running it.&lt;/p&gt;
&lt;p&gt;I will also follow a simple &lt;strong&gt;&lt;em&gt;Question&lt;/em&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;em&gt;Answer&lt;/em&gt;&lt;/strong&gt; structure throughout rather than providing an FAQ at the end.&lt;/p&gt;
&lt;h3&gt;Covid Alert Malta&lt;/h3&gt;
&lt;p&gt;Let us start with an explanation of what open-source is.&lt;/p&gt;
&lt;p&gt;Software which is &lt;em&gt;open-source&lt;/em&gt; is software for which the program’s code is available for viewing and compilation, whilst &lt;em&gt;closed-source&lt;/em&gt; does not have the program’s code disclosed, and a user can only make use of the software in a pre-compiled manner (like Microsoft Word) or as a service (like Google Maps).&lt;/p&gt;
&lt;p&gt;With open-source software (often abbreviated as OSS), we can verify behaviour either by running the program or by reading the source code, since there may be behaviour which occurs but is not disclosed by the program. In closed-source, we can only observe the behaviour of the program by running it, and we have to rely on what we are told it does (or does not) do.&lt;/p&gt;
&lt;p&gt;If I had to provide an analogy, a &lt;em&gt;closed-source&lt;/em&gt; choclate cake is the kind you buy that is ready to enjoy. It looks delicious and tastes delicious, but you have no idea how, when, where or with what it was baked. The baker may assure you that it is gluten free, vegan or was baked in a nut free environment, but the only assurance of truth in that claim here is to take the baker’s word for it.&lt;/p&gt;
&lt;p&gt;An &lt;em&gt;open-source&lt;/em&gt; cake would be if the cake came with the recipe listing precisely the ingredients, their amounts, their source and the process of baking. We can observe or eat the cake and not care about the recipe, but we can also use the recipe to bake our own version of the cake, either to mix it up with our own preferences (such as replacing the chocolate frosting with jam) or to see if we end up with the exact same chocolate cake that has been pre-baked for us.&lt;/p&gt;
&lt;p&gt;In this case, the Covid Alert Malta Android and iOS apps, along with the server which handles exposure notifications are &lt;strong&gt;forks&lt;/strong&gt; (the equivalent of a software remix) of other open-source Covid-19 contact tracing apps, specifically the &lt;a href=&quot;https://github.com/DP-3T/dp3t-app-android-ch&quot;&gt;SwissCovid App&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Question&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Does this mean that MITA stole the SwissCovid app and claimed it as its own?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Answer&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Not at all. The specific open-source licence under which the SwissCovid app is made available allows for modifications and re-distribution (more about this later on).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Question&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Why didn’t MITA write one from scratch?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Answer&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I do not speak for MITA so I cannot answer this in their name, however I can speculate.&lt;/p&gt;
&lt;p&gt;Before writing any software one should evaluate the feasability of it, both in terms of financial and time investment. Writing a contact tracing app from scratch is an option, however the time and knowledge investment required can be very high. Taking into consideration the fact that the sooner you release a contact tracing app the better in the case of a pandemic, any large time investment becomes cost-ineffective.&lt;/p&gt;
&lt;p&gt;It would be much more feasible to find a pre-existing open-source implementation and adapt it to your specific needs. One of the main ideas behind Open Source is to avoid double-work. Code is shared in the spirit of collaboration and so that everyone benefits from not constantly re-inventing the wheel.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Question&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Is MITA obliged to share the source code?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Answer&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Yes. (Nearly) all open-source code comes with a licence, which specifies the duties an individual or organisation must assume in order to make use of the code. In this case, the SwissCovid app is licenced under the &lt;a href=&quot;https://www.mozilla.org/en-US/MPL/2.0/FAQ/&quot;&gt;Mozilla Public Licence 2.0&lt;/a&gt;. Quoted directly from the FAQ linked:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Q9: I want to distribute (outside my organization) MPL-licensed source code that I have modified. What do I have to do?

To see the complete set of requirements, read the license. However, generally:

You must inform the recipients that the source code is made available to them under the terms of the MPL (Section 3.1), including any Modifications (as defined in Section 1.10) that you have created.

You must make the grants described in Section 2 of the license.

You must respect the restrictions on removing or altering notices in the source code (Section 3.4).&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Question&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Does making the source code available make the app less secure?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Answer&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The security of any software is not contigent on whether it is open-source or not, but rather it is contingent on the quality of the software itself.&lt;/p&gt;
&lt;p&gt;Creating insecure software and choosing not to disclose the source code is the equivalent of baking a cake but not choosing to disclose whether it contains gluten or not. Regardless of disclosure, the amount of gluten does not change and can still cause mild to deadly health issues on consumption of the cake if the consumer is gluten intolerant or a coeliac.&lt;/p&gt;
&lt;p&gt;Choosing not to disclose source-code on the basis of security is a method known as &lt;em&gt;security through obscurity&lt;/em&gt;, which is highly discouraged by any professional working in Software, Security or Information Systems.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Question&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Wasn’t this App reviewed and certified by the MDIA?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Answer&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;One would hope that software such as Covid Alert Malta, which in an ideal world is running on every single smartphone in the country, would have gone through
a rigorous security review.&lt;/p&gt;
&lt;p&gt;To this end, the MDIA has claimed that the app passed their certification process, however this certification process is not public or transparent. Blind trust in any institution should be avoided since at the end of the day it is still made up of humans. I highly encourage the MDIA to share how and to what level certification was ensured, especially as this will continue to build trust in the system, which would drive even more installations.&lt;/p&gt;
&lt;p&gt;Regardless of the above, open-sourcing the app allows &lt;em&gt;anyone&lt;/em&gt; to perform their own audit. Regardless of your level of trust in any institution, whether you are more lax or more pedantic, the fact that the source code is right there for review will enable you to put your own seal of approval on it.&lt;/p&gt;
&lt;h2&gt;The Source Code&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Full Disclosure&lt;/em&gt;: My expertise and experience lies mainly in cross-platform apps. I do not write apps in platform specific languages (such as Java, Kotlin, Objective C or Swift). Regardless, I have prior experience in Java and apps in general which I consider sufficient enough to perform this review.&lt;/p&gt;
&lt;p&gt;The source code can be found at the following URL: &lt;a href=&quot;https://github.com/GOVMT-MITA/dp3t-app-android-ch&quot;&gt;https://github.com/GOVMT-MITA/dp3t-app-android-ch&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Given that it is a proper fork, we can actually &lt;a href=&quot;https://github.com/DP-3T/dp3t-app-android-ch/compare/master...GOVMT-MITA:covidalert-mt?w=1&quot;&gt;compare&lt;/a&gt; the differences between the original repository and the MITA one. This &lt;strong&gt;diff&lt;/strong&gt;, short for difference, is an auto-generated list of differences on the level of each file.&lt;/p&gt;
&lt;p&gt;From a quick skim, it looks like the only major addition to the app was the option to choose language when onboarding and localisation of the app due to the chosen language. There does not seem to have been any change in the behaviour in the app from what SwissCovid provided.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Question&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The App keeps insisting that I turn on location settings. Everyone insists that the app never tracks my location. Why is there this disparity? Are they lying?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Answer&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This disaprity is an unfortunate User Experience (UX) issue within the Android operating system, encompassed within the screenshot below:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/6f77125ea8a073c0094fd0ae1867689d/47311/location-perms.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 81.08108108108108%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAQABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAMFAf/EABUBAQEAAAAAAAAAAAAAAAAAAAEC/9oADAMBAAIQAxAAAAG4IQXQMGP/xAAaEAACAgMAAAAAAAAAAAAAAAAQEQADEiMx/9oACAEBAAEFAuRi3N7x/8QAFxEAAwEAAAAAAAAAAAAAAAAAAQIQEf/aAAgBAwEBPwEJs//EABURAQEAAAAAAAAAAAAAAAAAABAR/9oACAECAQE/Aaf/xAAZEAEAAgMAAAAAAAAAAAAAAAABABACEjL/2gAIAQEABj8Cs0xGcFf/xAAcEAACAgIDAAAAAAAAAAAAAAAAARExECFB0fD/2gAIAQEAAT8h00ixpI5kn37FSP/aAAwDAQACAAMAAAAQxM//xAAXEQEAAwAAAAAAAAAAAAAAAAARARCR/9oACAEDAQE/EALG1//EABYRAAMAAAAAAAAAAAAAAAAAAAEQEf/aAAgBAgEBPxAwv//EAB0QAQEAAQQDAAAAAAAAAAAAAAERACExQXFRYbH/2gAIAQEAAT8QVCW+K/MFYk7Mt1xGghtbPHJ7zSd3vFlgGahn/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Location Permissions&quot;
        title=&quot;Location Permissions&quot;
        src=&quot;/static/6f77125ea8a073c0094fd0ae1867689d/1c72d/location-perms.jpg&quot;
        srcset=&quot;/static/6f77125ea8a073c0094fd0ae1867689d/a80bd/location-perms.jpg 148w,
/static/6f77125ea8a073c0094fd0ae1867689d/1c91a/location-perms.jpg 295w,
/static/6f77125ea8a073c0094fd0ae1867689d/1c72d/location-perms.jpg 590w,
/static/6f77125ea8a073c0094fd0ae1867689d/a8a14/location-perms.jpg 885w,
/static/6f77125ea8a073c0094fd0ae1867689d/47311/location-perms.jpg 1080w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;First of all, we can confirm whether the app has access to location data via Wi-Fi (known as Coarse Location) or via GPS (known as Fine Location).&lt;/p&gt;
&lt;p&gt;When it comes to Android (this is also the case in iOS), a developer must state up-front what permissions the application requires to function. These are stated in the &lt;code class=&quot;language-text&quot;&gt;AndroidManifest.xml&lt;/code&gt; file. In our case, we can find it &lt;a href=&quot;https://github.com/GOVMT-MITA/dp3t-app-android-ch/blob/covidalert-mt/app/src/main/AndroidManifest.xml&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The permissions listed are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;lt;uses-permission android:name=&quot;android.permission.BLUETOOTH&quot; /&gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;lt;uses-permission android:name=&quot;android.permission.INTERNET&quot; /&gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If the App wanted to track your location, we would see the following permissions listed, as outlined in the &lt;a href=&quot;https://developer.android.com/training/location/permissions#foreground&quot;&gt;Android Developers Documentation&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;lt;uses-permission android:name=&quot;android.permission.ACCESS_COARSE_LOCATION&quot; /&gt;
&amp;lt;uses-permission android:name=&quot;android.permission.ACCESS_FINE_LOCATION&quot; /&gt;
&amp;lt;uses-permission android:name=&quot;android.permission.ACCESS_BACKGROUND_LOCATION&quot; /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Plain as day: Covid Alert Malta is &lt;strong&gt;not able to track your location&lt;/strong&gt;. Not directly at least.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Question&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;So then why does the app need location setting turned on?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Answer&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Forget Covid Alert Malta for a second. Location information &lt;em&gt;may leak&lt;/em&gt; if Bluetooth is enabled.&lt;/p&gt;
&lt;p&gt;If Bluetooth is enabled, then your phone is going to be able to both transmit and receive data over BT. Devices are capable of finding each other if one of them is made &lt;em&gt;discoverable&lt;/em&gt;. This is the process used when setting a BT device (such as a headset) in pairable mode. The headset shouts to all other devices in the vicinity that it is available for pairing, you use your phone to pair with it and they can now exchange data, which in this case would be audio.&lt;/p&gt;
&lt;p&gt;One of the ways devices can recognise each other is via their MAC Address. This is similar to a postal address - I recognise it is John’s House because John lives at number 24 and the house infront of me has number 24. Similarily, your BT device will have an address in the format of &lt;code class=&quot;language-text&quot;&gt;D9:79:D4:9C:C9:1C&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If your device is sharing this address with any other device in the vicinity, then devices can use this to correlate your location and have a vague idea that you are the same person. Typically this is done through a device such as a &lt;a href=&quot;https://en.wikipedia.org/wiki/Bluetooth_low_energy_beacon&quot;&gt;BLE Beacon&lt;/a&gt; in a process such as:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Restaurant owner wants to track how many people are in his restaurant&lt;/li&gt;
&lt;li&gt;Owner sticks a BLE Beacon above the soffit&lt;/li&gt;
&lt;li&gt;You, as a regular, go there every Friday evening for happy hour&lt;/li&gt;
&lt;li&gt;Your phone is screaming out its MAC Address to every device in the vicinity&lt;/li&gt;
&lt;li&gt;BLE Beacon takes note of your MAC Address&lt;/li&gt;
&lt;li&gt;The owner can know that they have a consistent regular every friday night, based on the fact that the same MAC Address turns up at the same time every week&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Does this mean that the owner knows your full name, address, ID Card number, etc? No, not at all.
Does this mean that they can know which person has that MAC Address? Maybe. Maybe they are watching their security camera footage to see who walks into the door when the MAC Address is registered by the beacon. This is considered a &lt;em&gt;correlation attack&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This is what I meant by location data &lt;em&gt;may&lt;/em&gt; leak. It does not mean that having BT enabled will share your home address, but that there can be a nugget of information which, if sufficient nuggets are gathered, can begin to indicate who owns that device.&lt;/p&gt;
&lt;p&gt;Get this: the exact same happens with Wi-Fi. Whenever your device is advertising itself to possible routers (in a process calling probing), it reveals its MAC Address, which if you keep track of, can cause location data leakage.&lt;/p&gt;
&lt;p&gt;Covid Alert Malta is not the &lt;em&gt;cause&lt;/em&gt; of possible location leakage, however this is the reason that location services are required to be enabled, even if it is not Covid Alert Malta which is causing the leakage.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I greatly advise towards installing Covid Alert Malta for the following reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The source code is right there if you want to audit it&lt;/li&gt;
&lt;li&gt;It does not actually make use of your GPS or any sort of location data&lt;/li&gt;
&lt;li&gt;It will help speed up contact tracing, which should help bring the current Covid-19 outbreak under control&lt;/li&gt;
&lt;li&gt;There are many other applications which are siphoning off your location data in more egregious and obvious ways, such as Facebook, TikTok, Google Maps, Revolut, etc. To be concerned about Covid Alert Malta but not about all the other apps that are installed which actually request location permissions shows a fundamental misunderstanding of how widespread data siphoning is.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;There are non-security concerns relating to running BT constantly, such as the fact that it can indeed cause higher than expected battery drain. I think that this is a small price to pay for a privacy conscious way of performing rapid contact tracing, especially in the age of battery packs and quick charging.&lt;/p&gt;
&lt;p&gt;You can download Covid Alert Malta from the following links:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=mt.gov.dp3t&quot;&gt;For Android&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://apps.apple.com/us/app/id1513522951&quot;&gt;For iOS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[A real-time game from scratch - Initial Setup [1]]]></title><description><![CDATA[Previous Entries 0 - Motivation Introduction So I finally got started on Cabin Fever. Given that I have chosen to not proceed from an…]]></description><link>https://simonam.dev/game-from-scratch-1/</link><guid isPermaLink="false">https://simonam.dev/game-from-scratch-1/</guid><pubDate>Sun, 04 Oct 2020 19:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;Previous Entries&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/game-from-scratch-0&quot;&gt;0 - Motivation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;So I finally got started on Cabin Fever.&lt;/p&gt;
&lt;p&gt;Given that I have chosen to &lt;em&gt;not&lt;/em&gt; proceed from an academic route where I learn all the theory &lt;em&gt;then&lt;/em&gt; I proceed with the practice, the easiest way to make initial progress is to setup the repository and a Hello World for both the back-end (BE) and front-end (FE) part of the project.&lt;/p&gt;
&lt;p&gt;I will not be detailing progress in a step-by-step reproducible fashion, instead I will gloss over details which I feel are not relevant, instead I will focus on the high level concepts. Despite this, the resultant code will be tagged with the number of the article in the title (in this case, the tag will be &lt;code class=&quot;language-text&quot;&gt;article-001&lt;/code&gt;).&lt;/p&gt;
&lt;h2&gt;The Repository&lt;/h2&gt;
&lt;p&gt;The repository is hosted on &lt;a href=&quot;https://github.com/simonamdev/cabin-fever&quot;&gt;GitHub&lt;/a&gt; under my &lt;a href=&quot;https://github.com/simonamdev&quot;&gt;personal account&lt;/a&gt;. I chose the &lt;a href=&quot;https://www.gnu.org/licenses/gpl-3.0.en.html&quot;&gt;GPLv3&lt;/a&gt; licence since, at the time of writing, the aim of the project is more education focused over commercial.&lt;/p&gt;
&lt;p&gt;The folder structure is a very simple monorepo - cross cutting concerns (readme, licence, eventually CI and relevant config files) are stored in the root, whilst BE and FE have their own folders: &lt;code class=&quot;language-text&quot;&gt;cabinserver&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;cabinclient&lt;/code&gt; respectively.&lt;/p&gt;
&lt;h2&gt;The Server&lt;/h2&gt;
&lt;p&gt;Inside the &lt;code class=&quot;language-text&quot;&gt;cabinserver&lt;/code&gt; folder I placed a &lt;code class=&quot;language-text&quot;&gt;main.go&lt;/code&gt; file to allow me to simply run &lt;code class=&quot;language-text&quot;&gt;go run main.go&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;When it comes to Go &amp;#x26; web servers, there is not a decent built-in option to allow for hot-reload development (not in the way one can come to expect in the Frontend world). To achieve this, I use an open source tool called &lt;a href=&quot;https://github.com/cortesi/modd&quot;&gt;modd&lt;/a&gt; (also written in Go) which according to my provided config will watch the filesystem for changes to &lt;code class=&quot;language-text&quot;&gt;*.go&lt;/code&gt; files and rerun the provided command.&lt;/p&gt;
&lt;h2&gt;The Client&lt;/h2&gt;
&lt;p&gt;On the client-side, I chose &lt;a href=&quot;https://parceljs.org/&quot;&gt;Parcel&lt;/a&gt; as my bundler. I could have used alternaatives such as Webpack, Fuse, or sticking with the plain TS compiler, but I have found parcel to be one of the best bundlers which requires the least/zero config. Within two &lt;code class=&quot;language-text&quot;&gt;yarn add&lt;/code&gt; commands, I had functioning typescript compilation along with hot-reload and source-map generation.&lt;/p&gt;
&lt;p&gt;To ensure that the above works, I simply added an &lt;code class=&quot;language-text&quot;&gt;index.html&lt;/code&gt; file which runs a TS file, which simply logs &lt;code class=&quot;language-text&quot;&gt;Hello, World&lt;/code&gt; to console. The “Hello, World” is stored in a strongly typed string, to ensure that the TS features are working as expected. I have currently even gotten away without having to define a &lt;code class=&quot;language-text&quot;&gt;tsconfig.json&lt;/code&gt; file, however in my experience at some point I will need to definitely provide it.&lt;/p&gt;
&lt;h2&gt;Linking them together&lt;/h2&gt;
&lt;p&gt;The simplest and most standard way to send data back and forth would be to use HTTP in the form of REST API using JSON for serialisation and de-serialisation. Whilst I Would definitely not build a real-time game using this option (a turn-based game would most probably work just fine), I will use it to verify that my client and server can at minimum talk to each other on my machine.&lt;/p&gt;
&lt;p&gt;The basic setup for now is that on &lt;code class=&quot;language-text&quot;&gt;GET&lt;/code&gt; to route &lt;code class=&quot;language-text&quot;&gt;localhost:8080/&lt;/code&gt;, the server returns a string containing the current time. The client is hitting endpoint &lt;code class=&quot;language-text&quot;&gt;/&lt;/code&gt; once a second, then updating the DOM with the string returned by the server, which contains the timestamp. One can also confirm it is working by looking at the network tab and by seeing no errors in the console.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The next step would be to explore a more &lt;em&gt;event-driven&lt;/em&gt; approach through the use of a technology such as Web Sockets. I have experience using &lt;code class=&quot;language-text&quot;&gt;SocketIO&lt;/code&gt; in Node to simplify development, however since I am now using a non-isomorphic JavaScript stack, it will require going back to basic WebSockets.&lt;/p&gt;
&lt;p&gt;You can find the code for this article at the &lt;a href=&quot;https://github.com/simonamdev/cabin-fever/releases/tag/article-001&quot;&gt;article-001&lt;/a&gt; tag.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Next Entry&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/game-from-scratch-2&quot;&gt;2 - WebSockets&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Tempus - Rethinking what to Track]]></title><description><![CDATA[Introduction I am writing an experimental time tracker for software development workflows. It is called Tempus and you can see more…]]></description><link>https://simonam.dev/tempus-rethinking-what-to-track/</link><guid isPermaLink="false">https://simonam.dev/tempus-rethinking-what-to-track/</guid><pubDate>Sat, 03 Oct 2020 17:30:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;I am writing an experimental time tracker for software development workflows. It is called Tempus and you can see more information about the motivation behind it in my &lt;a href=&quot;/introducing-tempus/&quot;&gt;previous article&lt;/a&gt; and the source code on my &lt;a href=&quot;https://github.com/simonamdev/tempus&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I have been using the first iteration of Tempus since the 14th of September (nearly a month at time of writing). I am even using it right now as I write this article, as I use it to keep track of how I spend my time on my various side-projects.&lt;/p&gt;
&lt;p&gt;In this time I have realised that if I am going to write a time tracker specifically for software development workflows, I cannot write a time tracker which is essentially the same as any of the existing options and state that its &lt;em&gt;for devs&lt;/em&gt; simply because I am a software developer myself suffering from &lt;a href=&quot;https://en.wikipedia.org/wiki/Not_invented_here&quot;&gt;Not Invented Here Syndrome&lt;/a&gt;. This article is about my thoughts on why one-size-fits-all time trackers aren’t sufficient and how I plan to remedy it in the next iteration of Tempus.&lt;/p&gt;
&lt;h2&gt;The Issues&lt;/h2&gt;
&lt;p&gt;I split my Tempus tasks in a fashion similar to the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Slack Chats (meant to refer to IM in general, as I tend to use Skype and Teams too)&lt;/li&gt;
&lt;li&gt;On a Call&lt;/li&gt;
&lt;li&gt;Writing Code&lt;/li&gt;
&lt;li&gt;Thinking Time&lt;/li&gt;
&lt;li&gt;Architecture&lt;/li&gt;
&lt;li&gt;Code Review&lt;/li&gt;
&lt;li&gt;Stand up break&lt;/li&gt;
&lt;li&gt;Debugging&lt;/li&gt;
&lt;li&gt;Dealing with external providers&lt;/li&gt;
&lt;li&gt;…etc&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Part of what aids adoption of any system is a well designed User Experience (UX). If the UX is not thoroughly planned for, system’s success tends to be despite of its other characteristics rather than because of them.&lt;/p&gt;
&lt;p&gt;This is the reason why public transport adoption is better in some countries over others - if the UX of public transport in Malta sucks (and it does), and the only alternative is to drive your own personal car (even with its accompanying issues with parking and cost), then the adoption of public transport will be low.&lt;/p&gt;
&lt;p&gt;Any solution to this that does not address the basic UX issues (timeliness, availability, frequency, reliability, etc) is simply a micro-optimisation to a macro problem.&lt;/p&gt;
&lt;p&gt;In using Tempus, I (as a &lt;strong&gt;U&lt;/strong&gt;ser), e&lt;strong&gt;X&lt;/strong&gt;perienced the following main issues:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;I often do not know which task to choose. I have a task for being &lt;strong&gt;On a Call&lt;/strong&gt;. I also have a task for &lt;strong&gt;Debugging&lt;/strong&gt;. So if I am debugging a production issue whilst also on a call with a colleague, which one should I choose?&lt;/li&gt;
&lt;li&gt;Some context is being lost. I could be &lt;strong&gt;Debugging&lt;/strong&gt; in either our development environment (such as: the build was successful on my machine but not in our CI pipeline), or our production environment (such as: diving into incoming logs to diagnose an urgent ongoing issue)&lt;/li&gt;
&lt;li&gt;I would often forget to switch tasks once I have completed them (I would sometimes remain &lt;strong&gt;On a Call&lt;/strong&gt; for over 15 minutes after the call concluded)&lt;/li&gt;
&lt;li&gt;I do not have a clear idea of whether I was hitting my ideal time spent ratios. If my current position or the current week calls for a 50:50 split between writing code and working on our architecture, the only way I could reliably know that I was reaching that ratio was if the only two tasks I used were &lt;strong&gt;Writing Code&lt;/strong&gt; and &lt;strong&gt;Architecture&lt;/strong&gt;. Since there were other tasks interleaved with missing context (such as &lt;strong&gt;On a Call&lt;/strong&gt;, which may have been either writing code or architecture), I could not reliably track my progress.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;The Solutions&lt;/h2&gt;
&lt;h3&gt;Issues 1 &amp;#x26; 2 - Choosing between tasks &amp;#x26; lost context&lt;/h3&gt;
&lt;p&gt;The idea so far to solve these issues is to double down on characteristics unique to software development workflows. After all, Tempus is meant to be a niche tool so I should design and develop it with that aim in mind.&lt;/p&gt;
&lt;p&gt;Instead of tracking &lt;em&gt;tasks&lt;/em&gt; I suppose that tracking the &lt;em&gt;mode&lt;/em&gt; that the task is being performed with. The UI look something like this, where each option is a button. Choosing any option would deselect the other option in the row (behaviour similar to radio buttons).&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Option A&lt;/th&gt;
&lt;th&gt;Option B&lt;/th&gt;
&lt;th&gt;Option C&lt;/th&gt;
&lt;th&gt;etc&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Physical&lt;/td&gt;
&lt;td&gt;Sitting&lt;/td&gt;
&lt;td&gt;Standing&lt;/td&gt;
&lt;td&gt;AFK&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workplace&lt;/td&gt;
&lt;td&gt;Home&lt;/td&gt;
&lt;td&gt;Office&lt;/td&gt;
&lt;td&gt;Co-working Space&lt;/td&gt;
&lt;td&gt;Other&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Task&lt;/td&gt;
&lt;td&gt;Developing&lt;/td&gt;
&lt;td&gt;Debugging&lt;/td&gt;
&lt;td&gt;Documentation&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Environment&lt;/td&gt;
&lt;td&gt;Development&lt;/td&gt;
&lt;td&gt;Staging&lt;/td&gt;
&lt;td&gt;Production&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Communication&lt;/td&gt;
&lt;td&gt;Quiet&lt;/td&gt;
&lt;td&gt;On a Call&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;In the initial idea of Tempus I thought about supporting concurrent tasks, in an effort to support a structure similar to the above, however I decided against it in the interest of simplicity. The difference between free-text concurrent tasks and the above is that one would have to define each mode and the possible values of that mode. It would also make sense to somehow link the values of different modes together via simple rules - for example if I set myself to &lt;strong&gt;AFK&lt;/strong&gt;, then Tempus could remove the selected value for the Task, Communication and Environment modes, whilst retaining the values for Workplace.&lt;/p&gt;
&lt;h3&gt;Issue 3 - Forgetting to switch tasks&lt;/h3&gt;
&lt;p&gt;This issue is not particularly addressed by the redesign and would be better addressed by the introduction of some sort of hook mechanism - one such as “if I stand up from my seat, hit the Tempus API to switch my &lt;strong&gt;Physical&lt;/strong&gt; mode to the &lt;em&gt;standing&lt;/em&gt; value”.&lt;/p&gt;
&lt;p&gt;In the mean time, I would add functionality to change the start and end time that a specific mode’s value was active - for retro-active repair of incorrect data.&lt;/p&gt;
&lt;h3&gt;Issue 4 - Reliably tracking my progress&lt;/h3&gt;
&lt;p&gt;The current version shows me a very basic breakdown of how much time I spend on what task. I setup a very basic chart component which literally consists of a &lt;code class=&quot;language-text&quot;&gt;div&lt;/code&gt; element with a fixed height, max width and then the actual width is set on page render (&lt;code class=&quot;language-text&quot;&gt;width: {{ .Task.Percentage }}%&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Here is a screenshot of the breakdown of all my tasks on my production Tempus instance:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 303px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/3c7ae1358a604dd612f18381ee38ab90/6728c/tempus-chart.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 48.64864864864865%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsTAAALEwEAmpwYAAABz0lEQVQoz5WSSW/aUBSF+f//gV2zSJtBXVRtlHYVCIGUwQM2DZMBz3gmxo6j8FV+imhTZdMjvbe57x2d4TZc1yUIAqI4Jk1TttstWZaSJAlpmlGWJcV+T/I6z7IdRVGw2+2onp+pcTgcjqfx4/s1lxcXnF9+pnVzQ6vd5urbF05PP/L16hp9MmFlLOn3+0wmv+h2uwyHIzq3bdYbSxC+vLz8IeQVB96iqirx4H/ReHiYMpIVNEVCUlUGgyHr9RrPtVksFqiqSqdzhyJJ9AcDNqbFemWgKIqYjccaujam17vHMFY06qziJCXPH0mzjDiO2e/3PD2VQmVRlsRRdMw5z/ciwzzPRc5RFBMEWzzPF7kKy3XwSRLj+z51SbZtEwQhYRji2Dae61IUpSB1HIeVYRBG8fuW6+upqpBGI5rNJrPpFEkaoapj8Xmsqui6ThhGBFufjWlimhth/d+Gj6XUFjVN4/zsk1Bn2Q6yJDHWdQLPYeN4WGuDbq+H6/pHNe+VdlQoyxInJx/YBqGwa5mmUJhlGY/5o8jP9zwsyxL7Wa/K3wrfECZpynI+o9264f7nAE2fsJjPubttcd8fYFsm86XBbPqALMvIikpVPb+r8jelR+rbLtzUSQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Tempus&quot;
        title=&quot;Tempus&quot;
        src=&quot;/static/3c7ae1358a604dd612f18381ee38ab90/6728c/tempus-chart.png&quot;
        srcset=&quot;/static/3c7ae1358a604dd612f18381ee38ab90/12f09/tempus-chart.png 148w,
/static/3c7ae1358a604dd612f18381ee38ab90/e4a3f/tempus-chart.png 295w,
/static/3c7ae1358a604dd612f18381ee38ab90/6728c/tempus-chart.png 303w&quot;
        sizes=&quot;(max-width: 303px) 100vw, 303px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;To my surprise, 30% of my time is spent solely in some sort of active, synchronous communication. Surprise aside, this only tells me how much (percentage of total) time I spent on a task - it does not tell me whether I am spending either too &lt;em&gt;little&lt;/em&gt; or too &lt;em&gt;much&lt;/em&gt; time on a specific task type. It would greatly help if I could set daily/weekly targets of, say &lt;strong&gt;Not more than 40% of my time should be writing code&lt;/strong&gt;, to avoid getting stuck trying to optimise my code too much instead of sticking to &lt;em&gt;good enough&lt;/em&gt;, alongisde &lt;strong&gt;at least 20% of my time should be standing up&lt;/strong&gt;, to help combat the health issues that come with a sedentary style of work and too much sitting down, encouraging me to make use of my standing desk more.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In conclusion, it looks like the Tempus project is already bearing fruit - helping me flex my UX Design muscles alongside my effort to learn more Go, as also outlined in my previous article about &lt;a href=&quot;/game-from-scratch-0/&quot;&gt;learning to write a real-time game from scratch&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[A real-time game from scratch - Motivation [0]]]></title><description><![CDATA[Introduction In my day to day work, the software I write and architect consists of the standard stateless request & response web server kind…]]></description><link>https://simonam.dev/game-from-scratch-0/</link><guid isPermaLink="false">https://simonam.dev/game-from-scratch-0/</guid><pubDate>Thu, 01 Oct 2020 17:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;In my day to day work, the software I write and architect consists of the standard stateless request &amp;#x26; response web server kind. Not particularly exciting or complicated in terms of fundamental structure, although it presents various challenges when running at a certain scale.&lt;/p&gt;
&lt;p&gt;In the interest of continued professional development, I have started on the path to writing a real-time game from scratch.&lt;/p&gt;
&lt;h2&gt;Motivation - Why?&lt;/h2&gt;
&lt;p&gt;I have been writing programs in Go for a few years now. I love the language and its simplicity, however the applications I have used it for are also quite simplistic - typically REST APIs, small CLI tools and a web scraper. There have been little to no opportunities to use the more sophisticated features of Go, such as its first class concurrency: Go routines and channels, particularly because I aim to pick the simplest tools for the job and the majority APIs I have written have never needed any complex concurrency requirements.&lt;/p&gt;
&lt;p&gt;Along with Go, this would also challenge me on my Front-end skills. Most of the FE work that I do is within the context of a framework (typically React) or some minor vanilla JS to add some dynamic interaction to a server side generated web page. A real time game however would require a completely different approach, using tools such as WASM, Canvas or possibly even WebGL to ensure that the refresh rate of the game is as fast as possible.&lt;/p&gt;
&lt;p&gt;Another reason is that the more time I spend at my full time job thinking at an architectural level of abstraction, the more I realise the importance of choosing the correct data structures and how to process them, a concern which is less important when you are knee deep in the code. My main concerns are typically &lt;em&gt;how can we reduce the coupling within these two services to avoid development time slowing down&lt;/em&gt; or &lt;em&gt;what is the ideal way to upgrade this legacy method of service communication to keep up with our load demand?&lt;/em&gt;. These concerns are very different from &lt;em&gt;how do I ensure that, despite unexpected latency from some clients, the game can still run its core loop without blocking all the other clients’ inputs?&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;One more reason is that I want to learn how to model systems that are beyond the standard CRUD plus some validation / business logic. A real-time game needs to take several inputs at the same time, ensure that the input is valid, update the game state, synchronise it to all clients and do so fast enough that the game actually feels realtime, despite varying latency, computational power, network availability, etc, all this within a stateful manner.&lt;/p&gt;
&lt;p&gt;Finally, I have always found games to be a great entertainment and education tool. Ever since I became a father, being able to sit down for a 3 hour stretch of gaming has become a once in a blue moon treat, causing me to prefer games I can quickly jump into and out of whenever I get a 20 minute window whilst the baby is asleep and all the chores have been finished and I have already spent quality time with my family.&lt;/p&gt;
&lt;p&gt;In summary:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Improving my knowledge of Go and its concurrency features&lt;/li&gt;
&lt;li&gt;Challenging myself to learn a method of Front-end Development that takes real-time into consideration&lt;/li&gt;
&lt;li&gt;Improving my understand of low-level performance and optimisation&lt;/li&gt;
&lt;li&gt;Being able to architect a stateful, real-time system which caters for multiple clients&lt;/li&gt;
&lt;li&gt;Creating a game that has a short play time and is easy to pick up and drop out of&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;The Approach - How?&lt;/h2&gt;
&lt;p&gt;I could plausibly hop onto YouTube or Udemy, follow a game development course and simply copy paste my way to a finished game. Whilst that was a very valid way for me to learn when I did not fully understand what was going on at a code level and allowed me to bootstrap my software career, my aim now is to improve my existing skills, not only in writing the code but also understanding what that code is doing under the hood. Often enough I have spent my time learning the theory, only to come to apply it and get stuck.&lt;/p&gt;
&lt;p&gt;Over time I have come to believe that the theory and practice of a topic cannot be divorced from each other, and at least &lt;em&gt;personally&lt;/em&gt;, my learning style prefers to start from the practice, learning bits of theory on the way, getting something working, then investing time to seeing whether knowing the full theoretical story could have produced something more practical. In a way, I am taking the agile approach to learning - knowing just enough to get something working, then iterating on that. Adding onto this - I could use tests to confirm my understanding of the theory. If I write a calculation for acceleration on paper, does my answer return the same when I write it a test for the function?&lt;/p&gt;
&lt;p&gt;In summary:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Use my existing knowledge to create something working&lt;/li&gt;
&lt;li&gt;Document the process, outcomes, areas of improvement&lt;/li&gt;
&lt;li&gt;Investigate whether there exist patterns that can improve on the bottlenecks / issues&lt;/li&gt;
&lt;li&gt;Refactor existing code to adapt to new found understanding&lt;/li&gt;
&lt;li&gt;Repeat until the game can be considered complete&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;What kind of game?&lt;/h2&gt;
&lt;p&gt;I have always been an avid fan of &lt;a href=&quot;https://www.elitedangerous.com/&quot;&gt;Elite Dangerous&lt;/a&gt; and space games in general (such as &lt;a href=&quot;https://www.kerbalspaceprogram.com/&quot;&gt;KSP&lt;/a&gt;). Inspired by Elite Dangerous Arena, I will be creating a 2D space game, where the aim is to compete in some sort of Deathmatch / Team deathmatch / CTF setup.&lt;/p&gt;
&lt;p&gt;This type of game is also interesting as it can involve a simple physics model - namely frictionless movement within a vacuum without gravity, at least initially. It could be interesting to involve elements such as gravitational pulls of objects within the arena, which also affect projectiles. Areas of concern in terms of design would include edges of maps - should they wrap around? Or somehow penalise players who exit the arena bounds?&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I do not expect progress to be fast, especially given my packed schedule, but it should be a great learning experience, both for myself and any of my readers who have never delved into games development before, especially if they are coming from a more standard/corporate software development background rather than a pure game development background.&lt;/p&gt;
&lt;p&gt;Any and all code I write will be made open-source, under the GPLv3 licence at &lt;a href=&quot;https://github.com/simonamdev/cabin-fever&quot;&gt;https://github.com/simonamdev/cabin-fever&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Next Entry&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/game-from-scratch-1&quot;&gt;1 - Initial Setup&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Introducing Tempus]]></title><description><![CDATA[Introduction Time tracking can be a touchy subject, especially when it comes to Software Development. Often enough it is associated with…]]></description><link>https://simonam.dev/introducing-tempus/</link><guid isPermaLink="false">https://simonam.dev/introducing-tempus/</guid><pubDate>Fri, 18 Sep 2020 20:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Time tracking can be a touchy subject, especially when it comes to Software Development. Often enough it is associated with either tracking time against an estimate of a task or time spent for billing purposes.&lt;/p&gt;
&lt;p&gt;The former can cause undue stress and anxiety when a task is still ongoing yet the estimate has been exceeded.
The latter can cause undue issues with clients disputing the amount of time that was actually spent.&lt;/p&gt;
&lt;p&gt;Regardless of having experienced both of the above in the past and not being the biggest fan of time tracking, over the last year I have found myself tracking the number of hours that I worked in a given month in a Google Keep note.&lt;/p&gt;
&lt;h3&gt;The Process&lt;/h3&gt;
&lt;p&gt;Every day for the last 14+ months I have been taking down the time that I:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Started the workday&lt;/li&gt;
&lt;li&gt;Stopped for my break&lt;/li&gt;
&lt;li&gt;Stopped/Started for any reason (such as doing an errand mid-way through the work-day)&lt;/li&gt;
&lt;li&gt;Completed the workday&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At the end of every month I would manually calculate the actual number of hours I have worked so that I can adjust to work less/more hours in the next month.
After having done this process manually for three times, my developer senses started tingling that this could be easily automated.&lt;/p&gt;
&lt;h3&gt;Productivity&lt;/h3&gt;
&lt;p&gt;Recently my role at $WORK has become more vague, causing me to spend a lot of time switching between different areas of concern depending on the priority assigned by the business. I prefer long uninterrupted streches of deep work (up to an hour or two), I find context switches very mentally taxing so this has been a learning experience for me on how to juggle several different contexts.&lt;/p&gt;
&lt;p&gt;To this end, I wanted to start to keep track of context switches. This data will eventually lead to a better understanding of when most switches occur or what kind of tasks cause more switches than other.&lt;/p&gt;
&lt;p&gt;To clarify, for me a context switch refers not to the type of task but rather the topic / feature being addressed. Switching from a chat over slack with a colleague, moving to a call with that same colleague whilst opening my code editor as we discuss and evaluate a new feature does not involve any context switches. If I am focusing on one area of our system and a colleague asks for a call about a different area, then a context switch occurs.&lt;/p&gt;
&lt;h2&gt;The Solution&lt;/h2&gt;
&lt;p&gt;The solution is &lt;strong&gt;Tempus&lt;/strong&gt;. Tempus is my experimental time tracker, mainly aimed at software development and related workflows.&lt;/p&gt;
&lt;p&gt;My requirements were simple:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Be able to create a project&lt;/li&gt;
&lt;li&gt;Be able to create any kind of task for that project&lt;/li&gt;
&lt;li&gt;Be able to start and end a task within a project&lt;/li&gt;
&lt;li&gt;Be able to switch from one task to another, whilst also recording if I am making a context switch&lt;/li&gt;
&lt;li&gt;Aggregate task data within a project to understand my time investment better (how many hours have I spent today? How many times did I switch contexts?)&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;The Implementation&lt;/h2&gt;
&lt;p&gt;To build Tempus, I used the simplest tools I could think of that would allow me access from any device. It is a Web App which is fully server side generated (that is, no JavaScript or AJAX style API calls are involved). Creating Entry types, starting or stopping entries and recording context switches are simple HTML forms which perform POST requests. I used &lt;a href=&quot;https://golang.org/&quot;&gt;Go&lt;/a&gt; as a programming language and &lt;a href=&quot;https://www.sqlite.org/index.html&quot;&gt;SQLite&lt;/a&gt; for data persistence, I upload and host the binary on a &lt;a href=&quot;https://www.digitalocean.com/&quot;&gt;Digital Ocean&lt;/a&gt; VPS behind &lt;a href=&quot;https://www.nginx.com/&quot;&gt;nginx&lt;/a&gt; as a reverse proxy, using &lt;a href=&quot;https://letsencrypt.org/&quot;&gt;letsencrypt&lt;/a&gt; for an SSL certificate.&lt;/p&gt;
&lt;p&gt;To get an idea of the simplicity, here is a screenshot of tempus in its current state:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/c8520f6b23637bcbab04dd054614fe0e/7645c/tempus.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 54.72972972972974%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAMEAgX/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIQAxAAAAHqMbsmKA//xAAYEAADAQEAAAAAAAAAAAAAAAAAAxMBEP/aAAgBAQABBQKaiCiKzM7/AP/EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EABoQAAICAwAAAAAAAAAAAAAAAACRAjMBIDL/2gAIAQEABj8CrwiuKOIrT//EABgQAQADAQAAAAAAAAAAAAAAAAEAEBEx/9oACAEBAAE/ITlSAxhYOUz/2gAMAwEAAgADAAAAEIMP/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPxA//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPxA//8QAHBABAAIBBQAAAAAAAAAAAAAAAQAxERAhUZHx/9oACAEBAAE/EMkTtUnioN260SwZ50tP/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Tempus&quot;
        title=&quot;Tempus&quot;
        src=&quot;/static/c8520f6b23637bcbab04dd054614fe0e/1c72d/tempus.jpg&quot;
        srcset=&quot;/static/c8520f6b23637bcbab04dd054614fe0e/a80bd/tempus.jpg 148w,
/static/c8520f6b23637bcbab04dd054614fe0e/1c91a/tempus.jpg 295w,
/static/c8520f6b23637bcbab04dd054614fe0e/1c72d/tempus.jpg 590w,
/static/c8520f6b23637bcbab04dd054614fe0e/a8a14/tempus.jpg 885w,
/static/c8520f6b23637bcbab04dd054614fe0e/fbd2c/tempus.jpg 1180w,
/static/c8520f6b23637bcbab04dd054614fe0e/7645c/tempus.jpg 1922w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;The Future&lt;/h2&gt;
&lt;p&gt;The current iteration is simply for me to try out features as they begin to make sense or as I spot inefficiencies in my usage. One example I quickly spotted was that the project page lists all of the entries under each other (with no sort of filtering or pagination). After the first day of usage I had already clocked in &lt;strong&gt;twenty-five&lt;/strong&gt; unique entries.&lt;/p&gt;
&lt;p&gt;Since this list kept on growing, the buttons to choose a new entry type were getting pushed below the fold, which meant I needed to scroll down every time I wanted to switch tasks. To remedy this, I moved the buttons and the current task indicator &lt;strong&gt;above&lt;/strong&gt; the list of entries.&lt;/p&gt;
&lt;p&gt;I also see myself changing the front-end to a React-based Single Page Application. This would enable interactions such as a long press to indicate a task switch with a context switch, as well as make charts/analytics super simple to show on screen.&lt;/p&gt;
&lt;p&gt;Given that Tempus is aimed primarily at Software Development workflows, it would also make sense to have an easy to use CLI, given that I spend more time living inside a terminal over the browser. Even if I were in a browser, I would need to find the relevant tab again, whilst a terminal is typically a hotkey away. I envisage it would have an API such as &lt;code class=&quot;language-text&quot;&gt;tempus start &apos;Programming&apos;&lt;/code&gt; or &lt;code class=&quot;language-text&quot;&gt;tempus switch &apos;Documentation&apos; --context-switch&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Worst case scenario, I learn that I do not need a time tracker or any time tracker could work. I also improve my Go and product development skills.
Best case scenario, I create an awesome time tracker. I also improve my Go and product development skills.&lt;/p&gt;
&lt;p&gt;Either way, win-win.&lt;/p&gt;
&lt;p&gt;Tempus is open-source and is licensed under the MIT Licence. You can find the source code &lt;a href=&quot;https://github.com/simonamdev/tempus&quot;&gt;here on GitHub&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Zero to Setup on Ubuntu]]></title><description><![CDATA[Due to a recent purchase of a new laptop (followed by not one but two guarantee covered repairs) as well as refreshing some old hardware, I…]]></description><link>https://simonam.dev/zero-to-setup/</link><guid isPermaLink="false">https://simonam.dev/zero-to-setup/</guid><pubDate>Sun, 13 Sep 2020 18:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Due to a recent purchase of a new laptop (followed by not one but &lt;em&gt;two&lt;/em&gt; guarantee covered repairs) as well as refreshing some old hardware, I have had to (re-)install Ubuntu (my OS of choice if the aim of the machine is anything but gaming) more times than I would care to enjoy.&lt;/p&gt;
&lt;p&gt;Each (re-)install required the installation of numerous tools which I would need in the course of any software development I perform. In the first two iterations it was a case of downloading and installing the tool whenever I realised that I had not done so yet.&lt;/p&gt;
&lt;p&gt;The third time round I realised I should make a list of the tools and install them all upfront.&lt;/p&gt;
&lt;h3&gt;git&lt;/h3&gt;
&lt;p&gt;With all of my code hosted on &lt;a href=&quot;https://www.github.com&quot;&gt;GitHub&lt;/a&gt;, I need the &lt;a href=&quot;https://git-scm.com/&quot;&gt;git&lt;/a&gt; CLI to retrieve and push code.&lt;/p&gt;
&lt;h3&gt;curl&lt;/h3&gt;
&lt;p&gt;Since most of my work is Web Development, &lt;a href=&quot;curl&quot;&gt;https://curl.haxx.se/&lt;/a&gt; is an essential CLI tool to quickly check the response of a webserver running on my machine or the response of a remote website/API that I am interacting with.&lt;/p&gt;
&lt;h3&gt;vim&lt;/h3&gt;
&lt;p&gt;Whilst not my main text editor, I occasionally want to edit a file quickly from the CLI. &lt;a href=&quot;https://www.vim.org/&quot;&gt;vim&lt;/a&gt; is my CLI editor of choice. Learning the basic of vim have also saved a good chunk of time when I needed to quickly edit a configuration file on a headless server.&lt;/p&gt;
&lt;h3&gt;ZSH&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://www.zsh.org/&quot;&gt;ZSH&lt;/a&gt;, which stands for Z Shell is an alternative shell to Bash. ZSH along with the plugin framework &lt;a href=&quot;https://ohmyz.sh/&quot;&gt;oh-my-zsh&lt;/a&gt; give my terminal superpowers. Completion for various tools, colours in normal tools such as &lt;code class=&quot;language-text&quot;&gt;ls&lt;/code&gt;, even showing the current branch of the current folder’s git repository.&lt;/p&gt;
&lt;h3&gt;Visual Studio Code&lt;/h3&gt;
&lt;p&gt;I used to be an Atom Editor fan which was the free (and slow) alternative to Sublime by GitHub. &lt;a href=&quot;https://code.visualstudio.com/&quot;&gt;VS Code&lt;/a&gt; however has taken the web development world by storm. With great performance (even for an electron based application) and an ever growing plugin eco-system which can fit nearly anything you throw at it, it has been my daily driver for years.&lt;/p&gt;
&lt;h3&gt;Bitwarden&lt;/h3&gt;
&lt;p&gt;In an effort to be more sercure when it comes to passwords, I have moved to using &lt;a href=&quot;https://bitwarden.com/&quot;&gt;Bitwarden&lt;/a&gt;. With a very generous free account option along with being open source, it is the password manager of my choosing.&lt;/p&gt;
&lt;h3&gt;Docker&lt;/h3&gt;
&lt;p&gt;I used to be a &lt;a href=&quot;https://www.docker.com/&quot;&gt;docker&lt;/a&gt; sceptic. Admittedly I started learning how to use docker around the time that it was just getting into vogue, before cloud vendors went all in on containers or when orchestration was still a mostly unsolved problem (not that it is completely solved now, but that is a different story).&lt;/p&gt;
&lt;p&gt;But after one too many times of spending more time trying to get a Python script running on a VPS over writing the actual script, the ability to just upload a tarred image and then run a container from it with all dependencies it needed tagging along, it graduated to one of those tools that I wonder how I got any work done before I started using it.&lt;/p&gt;
&lt;p&gt;Along with docker I also install &lt;a href=&quot;https://docs.docker.com/compose/&quot;&gt;Docker Compose&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Programming Languages&lt;/h3&gt;
&lt;p&gt;My go-to stack reflects the fact that I am mainly focused on web development. At this point I am installing:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://golang.org/&quot;&gt;Go&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nodejs.org/en/&quot;&gt;NodeJS&lt;/a&gt;, along with &lt;code class=&quot;language-text&quot;&gt;npm&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;yarn&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.python.org/&quot;&gt;Python3&lt;/a&gt;, although this is typically already installed in the distro that I use&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;After each one I also enable zsh support and VS Code extensions.&lt;/p&gt;
&lt;h3&gt;Ulauncher&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://ulauncher.io/&quot;&gt;Ulauncher&lt;/a&gt; gives a macOS spotlight like experience to Ubuntu. A great addition to enable the transfer of muscle memory from macOS to Ubuntu&lt;/p&gt;
&lt;h3&gt;Postman&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://www.postman.com/&quot;&gt;Postman&lt;/a&gt; is a GUI for API development. It is a great tool to refer to when manually crafting a &lt;code class=&quot;language-text&quot;&gt;curl&lt;/code&gt; request is too daunting.&lt;/p&gt;
&lt;h3&gt;htop&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://htop.dev/&quot;&gt;htop&lt;/a&gt; is a terminal based process viewer. It comes with pretty colours and more features over the built-in &lt;code class=&quot;language-text&quot;&gt;top&lt;/code&gt;. This is useful if coming from a histoy of Windows machines, where Task Manager was always open.&lt;/p&gt;
&lt;h3&gt;sqlite3browser&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://sqlitebrowser.org/&quot;&gt;Sqlite3Browser&lt;/a&gt; is a GUI for interacting with an &lt;a href=&quot;https://www.sqlite.org/index.html&quot;&gt;SQLite&lt;/a&gt; Database. I love SQLite due to its simplicity in the way that it creates a flat file that includes all the data relevant to your task. When getting started with any development idea, I always start with SQLite then move to a bigger solution if the idea graduates to a project.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;At some point I will probably have to repeat this process as I do experience a few issues with my current laptop that I am associating with LVM and full disk encryption. I forsee the next evolution of this process being the use a tool such as &lt;a href=&quot;https://www.ansible.com/&quot;&gt;Ansible&lt;/a&gt; to automate the process, or possibly using docker for the more often used tools to avoid local installations completely.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[O-Level Grading UX Issues]]></title><description><![CDATA[Within Malta’s Education system, as a secondary school student, you are constantly working towards the O-Levels, which are examinations set…]]></description><link>https://simonam.dev/o-level-grading-ux/</link><guid isPermaLink="false">https://simonam.dev/o-level-grading-ux/</guid><pubDate>Thu, 14 May 2020 21:30:00 GMT</pubDate><content:encoded>&lt;p&gt;Within Malta’s Education system, as a secondary school student, you are constantly working towards the O-Levels, which are examinations set by the government’s Ministry of Education, which will award you with the SEC Certificate you need to be able to continue towards higher education.&lt;/p&gt;
&lt;h2&gt;Grades in the Maltese Education System&lt;/h2&gt;
&lt;p&gt;Famously these exams award you with grades that are defined by the following: 1, 2, 3, 4, 5, 6, 7 and U.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1 is the highest grade, 7 is the lowest&lt;/li&gt;
&lt;li&gt;1 through 5 are considered Passes&lt;/li&gt;
&lt;li&gt;6 and 7 are considered Failures&lt;/li&gt;
&lt;li&gt;U indicates that the exam was unable to be marked&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Covid-19&lt;/h2&gt;
&lt;p&gt;Due to the Covid-19 pandemic, the inital sitting of the MATSEC exams usually held in April/May was cancelled. Going forward, students have been given the following options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sit for their SEC exams in the September Sitting (typically used for &lt;em&gt;resits&lt;/em&gt; of the April/May sitting).&lt;/li&gt;
&lt;li&gt;Be assessed by the Board based on their mock exam results (typically run by schools, for the purpose of preparing students for the actual SEC exam).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This poses two main issues:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It is unclear so far (in May) that the September sittings will actually be held, due to the dynamic nature of the pandemic. If they are not, then students may lose their one option to be assessed this year.&lt;/li&gt;
&lt;li&gt;The student misses on their opportunity to take a resit. I personally have benefitted from resits to help me realise that I was over-confident or under-prepared for the subject I was being examined on&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Whilst the latter seems to be a more viable option, its implementation has created a number of UX issues which will continue to plague students, teachers, educators and administrators for a number of years to come, as I will explain throughout this blog post.&lt;/p&gt;
&lt;p&gt;According to the following &lt;a href=&quot;https://timesofmalta.com/articles/view/school-leavers-to-be-awarded-predicted-achievement-levels.782975&quot;&gt;Times of Malta article&lt;/a&gt; on the matter, the students who choose the latter option will be awarded “predicted levels” in the following manner:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Level 3 indicates that the student is at the “expected level” (equivalent to grades 1 through 5)&lt;/li&gt;
&lt;li&gt;Level 2 indicates that the student is “nearly at the expected level” (equivalent to grades 6 and 7)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When I read the above article, it was quickly obvious to me that whoever or whichever comittee came up with this scheme did not apply any human centric design thinking to this problem. A number of issues are apparent:&lt;/p&gt;
&lt;h3&gt;Problem 1: Number re-use&lt;/h3&gt;
&lt;p&gt;By re-using numbers, it will cause confusion in both conversation and administrative tasks. If the response to &lt;em&gt;“What grade did you get?”&lt;/em&gt; is &lt;em&gt;“I got a 2”&lt;/em&gt;, this no longer means that the student passed and scored quite well, now it can now ambiguously mean that the student failed.&lt;/p&gt;
&lt;p&gt;Also consider a simple administrative task: a headmaster requests a report of the distribution of marks attained by the school’s students in a specific subject. If an administrator, using a tool such as Excel, performs the following query: &lt;code class=&quot;language-text&quot;&gt;=COUNTIF(A1:A10, &quot;=3&quot;)&lt;/code&gt;, without taking a separate indicator into consideration that the student opted for a predicted grade, the report will now be factually incorrect.&lt;/p&gt;
&lt;h3&gt;Problem 2: The use of the predicted marks is inconsistent with the standard marks&lt;/h3&gt;
&lt;p&gt;In the brain of every teacher, parent, student or administrator there is the following thought: &lt;em&gt;a smaller mark is a better mark&lt;/em&gt;. Since 1 is the best mark and the 7 is the worst mark, the smaller your grade, the better your score.&lt;/p&gt;
&lt;p&gt;The predicted marks however, follow the opposite system. Marks 1 through 5 are considered &lt;strong&gt;Level 3&lt;/strong&gt;, whilst 6 and 7 are considered &lt;strong&gt;Level 2&lt;/strong&gt;. In the predicted mark system, the smaller mark means that you “failed”, whilst the larger mark means that you “passed”.&lt;/p&gt;
&lt;h3&gt;Problem 3: The predicted marks are coarser than the standard marks&lt;/h3&gt;
&lt;p&gt;O-Level grades are often used to differentiate between one student application and another within certain competitive sixth-form instiutions. By making the marking coarser, one cannot tell between a student who would have scored a 5 or a student who would have scored a 1, only that the student “passed” or “failed”.&lt;/p&gt;
&lt;h2&gt;Proposed solutions&lt;/h2&gt;
&lt;p&gt;Maltese are known for complaining without wanting to help make the issue better, so here is my contribution on how I would resolve the issues mentioned above:&lt;/p&gt;
&lt;h3&gt;Solution 1: Use new, unique marks for the predicted levels&lt;/h3&gt;
&lt;p&gt;By using unique marks (for example: &lt;em&gt;E&lt;/em&gt; for &lt;em&gt;Expected Level&lt;/em&gt; and &lt;em&gt;N&lt;/em&gt; for &lt;em&gt;Nearly Expected Level&lt;/em&gt;). The pandemic is an extenuating circumstance, so why not consider extenuating marks?
This will avoid:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Confusion between Level 2/3 and the actual grade 2/3&lt;/li&gt;
&lt;li&gt;Providing historical context. If a student has an &lt;strong&gt;E&lt;/strong&gt; grade, everyone would know the year when the student had their exam&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Solution 2: Avoid providing coarse marks in the first place&lt;/h3&gt;
&lt;p&gt;From the article, it appears that the MATSEC board is going to be using mark schemes provided by the teachers who would have created the mock exams. If a marking scheme has been provided, a solution would be to mark the exam, then grade the students on a bell curve. If the exam is deliberately easy to allow students to pass or deliberately difficult to attempt to prepare students for the SEC exam, normalising in this way would reduce that bias.&lt;/p&gt;
&lt;p&gt;The coarse “pass” or “fail” marks have to be determined somehow. It may simply be that if a student scores more than 50 on 100, then they receive Level 3, otherwise Level 2. This lack of transparency does not inspire confidence that the marking will be fair.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In conclusion, I hope that this article would help bring awareness that, without applying user centric design thinking, even our non-technical or software systems will end up causing extra work or increase the possibility of mistakes which could easily be avoided. If you are in a position of determining any sort of policy or regulation, consider seeing it from the user’s perspective before going ahead.&lt;/p&gt;
&lt;p&gt;Kudos to &lt;a href=&quot;https://twitter.com/joseph_trapani&quot;&gt;Joseph Trapani&lt;/a&gt; for proofreading this article before publication.&lt;/p&gt;</content:encoded></item></channel></rss>