<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nhlanhla Lucky Nkosi</title>
    <description>The latest articles on DEV Community by Nhlanhla Lucky Nkosi (@luckynkosi).</description>
    <link>https://dev.to/luckynkosi</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F289243%2F3dc09c02-4913-413e-90b6-30b7b0063589.png</url>
      <title>DEV Community: Nhlanhla Lucky Nkosi</title>
      <link>https://dev.to/luckynkosi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/luckynkosi"/>
    <language>en</language>
    <item>
      <title>The Modern Web Is a Superpower: Why Browser APIs Matter in 2026</title>
      <dc:creator>Nhlanhla Lucky Nkosi</dc:creator>
      <pubDate>Mon, 22 Dec 2025 16:05:27 +0000</pubDate>
      <link>https://dev.to/luckynkosi/the-modern-web-is-a-superpower-why-browser-apis-matter-in-2026-1ioi</link>
      <guid>https://dev.to/luckynkosi/the-modern-web-is-a-superpower-why-browser-apis-matter-in-2026-1ioi</guid>
      <description>&lt;p&gt;The web has changed more in the last six years than in the decade before. Once widely considered insecure and incapable of supporting robust, critical experiences, Significant work on standards and web capability is leading to a much more reliable ecosystem. I personally love this because single-use applications frustrate me. From restaurants that have forced me to download their app just to see a menu, to airlines that have forced me to download their app just to check in, single-use applications that will gather dust, take up space and mine data on my phone. &lt;/p&gt;

&lt;p&gt;This, and my fondness for JavaScript, is why I'm interested in bridging the gap between Native and Web applications.  &lt;/p&gt;

&lt;p&gt;If you're still thinking of the browser as “HTML + CSS + maybe some JS,” you're missing one of the most significant platform shifts in tech.&lt;/p&gt;

&lt;p&gt;Today, your browser can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Access the file system
&lt;/li&gt;
&lt;li&gt;Read sensors like orientation and motion
&lt;/li&gt;
&lt;li&gt;Interact with device hardware
&lt;/li&gt;
&lt;li&gt;Run GPU-powered compute
&lt;/li&gt;
&lt;li&gt;Capture media
&lt;/li&gt;
&lt;li&gt;Share data to native apps
&lt;/li&gt;
&lt;li&gt;Send background notifications
&lt;/li&gt;
&lt;li&gt;Store offline data reliably
&lt;/li&gt;
&lt;li&gt;Authenticate with passkeys
&lt;/li&gt;
&lt;li&gt;And so much more&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This explosion of capability (and some personal AI fatigue) is why I've been delivering conference talks on web capabilities, more than anything else, over the last few years. One of the latest talks I delivered at DevConf 2025 is titled&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“Web APIs You Should Be Using in 2025: Unlocking Next-Level Web Experiences.”&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;Talk video:&lt;/strong&gt; &lt;a href="https://youtu.be/FAFNXf5UyhU" rel="noopener noreferrer"&gt;https://youtu.be/FAFNXf5UyhU&lt;/a&gt; 
&lt;/h2&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why all of this matters
&lt;/h2&gt;

&lt;p&gt;For years, developers believed:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“If you want true native functionality, you need to build a native app.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is no longer true.&lt;/p&gt;

&lt;p&gt;Project Fugu, WebGPU, PWA advancements, and browser standardisation mean that &lt;strong&gt;modern web apps can now match native apps&lt;/strong&gt; in many important use cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Productivity tools
&lt;/li&gt;
&lt;li&gt;Editors (image, video, audio, text)
&lt;/li&gt;
&lt;li&gt;Games
&lt;/li&gt;
&lt;li&gt;Data dashboards
&lt;/li&gt;
&lt;li&gt;Offline apps
&lt;/li&gt;
&lt;li&gt;Utilities and internal tools
&lt;/li&gt;
&lt;li&gt;Mobile-first apps
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Add in PWAs, and you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Installability
&lt;/li&gt;
&lt;li&gt;Offline capability
&lt;/li&gt;
&lt;li&gt;Icon presence
&lt;/li&gt;
&lt;li&gt;App-like UI
&lt;/li&gt;
&lt;li&gt;Deep integration with device APIs
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We're at a point where the web platform is &lt;strong&gt;no longer catching up to native — it’s competing with it.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Why now? What changed?
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cross-browser API adoption is finally happening&lt;/strong&gt;&lt;br&gt;
Chrome pioneered many APIs, but now Firefox + Safari have implemented many of them or announced intent.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Security models matured&lt;/strong&gt;&lt;br&gt;
Permissions, transient access, user activation, and sandboxing have all evolved.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;WebAssembly + WebGPU&lt;/strong&gt;&lt;br&gt;
These two alone changed what’s possible.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Enterprise adoption&lt;/strong&gt;&lt;br&gt;
Companies now prefer “one codebase everywhere.” For some, this is to limit development and support costs, while others realise the power and convenience offered by the web platform. &lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  What’s coming next in this series?
&lt;/h2&gt;

&lt;p&gt;Over the next few weeks, I'll break down several &lt;strong&gt;powerful modern Web APIs&lt;/strong&gt;, how they work, and real demos you can use. These include:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;File System Access API
&lt;/li&gt;
&lt;li&gt;Badging API
&lt;/li&gt;
&lt;li&gt;Web Share API
&lt;/li&gt;
&lt;li&gt;Screen Wake Lock
&lt;/li&gt;
&lt;li&gt;Sensor APIs
&lt;/li&gt;
&lt;li&gt;Media Capabilities
&lt;/li&gt;
&lt;li&gt;Clipboard API (including images)
&lt;/li&gt;
&lt;li&gt;Device Orientation + Motion
&lt;/li&gt;
&lt;li&gt;WebGPU
&lt;/li&gt;
&lt;li&gt;Background Tasks &amp;amp; Periodic Sync
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;If you’re a web developer, this is one of the most exciting times to be working on the platform. The browser is no longer just a document renderer but a fully fledged application platform.&lt;/p&gt;

&lt;p&gt;Follow me here on &lt;strong&gt;DEV.to&lt;/strong&gt; to get the rest of the series.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>browser</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Creating a QR Code with custom icon and text using HTML &amp; JavaScript</title>
      <dc:creator>Nhlanhla Lucky Nkosi</dc:creator>
      <pubDate>Mon, 20 Dec 2021 11:17:47 +0000</pubDate>
      <link>https://dev.to/luckynkosi/creating-a-qr-code-with-custom-icon-and-text-using-html-javascript-16gh</link>
      <guid>https://dev.to/luckynkosi/creating-a-qr-code-with-custom-icon-and-text-using-html-javascript-16gh</guid>
      <description>&lt;h2&gt;
  
  
  Background &amp;amp; Context
&lt;/h2&gt;

&lt;p&gt;Quick Response (QR) codes are a powerful tool. A QR code is a type of barcode that stores information as a series of pixels in a square-shaped grid that can be read easily by a digital device such a smartphone through its camera. QR codes are frequently used to track information about products in a supply chain and often used in marketing and advertising campaigns. The most common use for QR codes is to embed a url - giving you the ability to share a link without the tedious task of having to manually type it in a url bar correctly. &lt;/p&gt;

&lt;p&gt;I recently had to build a token redemption feature onto a web application my team maintains. A direct url with a GUID seemed to be the best solution to go with. GUIDs are very long and to simplify the redemption process, we decided to distribute the tokens using QR codes. We needed to generate hundreds of tokens. To avoid mistakes and the tedious process of creating cards manually using word😂, I decided to automate this. JavaScript is currently the hammer I hit all quick problems with so I looked for a web-based solution. &lt;/p&gt;

&lt;h2&gt;
  
  
  The solution to problem
&lt;/h2&gt;

&lt;p&gt;We need to have a card that has a QR code in the middle and some accompanying text to say "congratulations" and provide instructions on how to redeem the code. We understood that not everyone would have a phone that's capable of scanning QR codes and some people might just not know how to. To address this concern, we build a simple input box that allowed people to enter the guid manually should they choose to do so. This added the requirement of printing the GUID onto the card that we distribute. &lt;/p&gt;

&lt;p&gt;This means that we have to do three things to solve this problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generate the QR code&lt;/li&gt;
&lt;li&gt;Generate text around the QR Code which includes the Guid&lt;/li&gt;
&lt;li&gt;Download the resultant "card" in an acceptable format&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Solution - Part 1: Generate QR Code
&lt;/h2&gt;

&lt;p&gt;The core of the solution is automatically generating the QR code. My favourite thing about web-development is the collaborative spirit of developers in the space. As the joke goes: "just search for what you need and suffix it with 'js' and chances are that you'll find a library that does exactly that." &lt;/p&gt;

&lt;p&gt;I went through a few npm packages before finding the &lt;a href="https://www.npmjs.com/package/qr-code-styling" rel="noopener noreferrer"&gt;QR Code Styling&lt;/a&gt; package which is simple to understand and easy to get started with. &lt;/p&gt;

&lt;p&gt;You can include the package using unpkg which is a fast, global content delivery network (cdn) for everything on npm. You can include the script using the following line in the head of your html page:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 javascript
&amp;lt;script type="text/javascript" src="https://unpkg.com/qr-code-styling@1.5.0/lib/qr-code-styling.js"&amp;gt;&amp;lt;/script&amp;gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Once included, you can create a new instance of a QR code using the new QRCodeStyling constructor which takes in an 'options' object as the parameter. The most basic properties of this 'options' parameter include the dimension of the code expressed as width and height, the data you want the code to represent - a url in our case, a url with the image you want at the center of the QR Code, and options configiuring the look and feel of the code like how rounded you want the edges of the code to be. An example code as adapted from the qr-code-styling docs is shown below:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 javascript
&amp;lt;script type="text/javascript"&amp;gt;

    const qrCode = new QRCodeStyling({
        width: 300,
        height: 300,
        type: "svg",
        data: "https://dev.to/luckynkosi/",
        image: "https://d2fltix0v2e0sb.cloudfront.net/dev-rainbow.svg",
        dotsOptions: {
            color: "#4267b2",
            type: "rounded"
        },
        backgroundOptions: {
            color: "#e9ebee",
        },
        imageOptions: {
            crossOrigin: "anonymous",
            margin: 20
        }
    });
    //place it on the screen
    qrCode.append(document.getElementById("canvas"));
    //download the generate image of the QR code
    qrCode.download({ name: "qr", extension: "svg" });
&amp;lt;/script&amp;gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Setting up the options object can be cumbersome. Luckily enough, Denys Kozak has created a brilliant website that lets you configure the look and feel using a simple interface and then export the config as a json file. I played around with the config and made everything a gradient. The exported json object is below:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 json
{"width":300,"height":300,"data":"https://dev.to/luckynkosi/","margin":0,"qrOptions":{"typeNumber":"0","mode":"Byte","errorCorrectionLevel":"Q"},"imageOptions":{"hideBackgroundDots":true,"imageSize":0.4,"margin":0},"dotsOptions":{"type":"extra-rounded","gradient":{"type":"linear","rotation":0,"colorStops":[{"offset":0,"color":"#7a0617"},{"offset":1,"color":"#beb819"}]}},"backgroundOptions":{"gradient":{"type":"radial","rotation":0,"colorStops":[{"offset":0,"color":"#26922d"},{"offset":1,"color":"#ecc1c1"}]}},"image":"data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjM1IDIzNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBjbGFzcz0icmFpbmJvdy1sb2dvIgogICAgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pbllNaW4gbWVldCI+CiAgPGcgaWQ9IlBhZ2UtMSIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+CiAgICA8ZyBpZD0iODBLIj4KICAgICAgPHBvbHlnb24gaWQ9IlNoYXBlIiBmaWxsPSIjODhBRURDIiBwb2ludHM9IjIzNC4wNCAxNzUuNjcgMTU4LjM1IDIzMy45NSAyMDUuNTMgMjMzLjk1IDIzNC4wNCAyMTIiPjwvcG9seWdvbj4KICAgICAgPHBvbHlnb24gaWQ9IlNoYXBlIiBwb2ludHM9IjIzNC4wNCAxNDAuMDYgMTEyLjExIDIzMy45NSAxMTIuMTMgMjMzLjk1IDIzNC4wNCAxNDAuMDgiPjwvcG9seWdvbj4KICAgICAgPHBvbHlnb24gaWQ9IlNoYXBlIiBwb2ludHM9IjEzMy4yNSAwLjk1IDAuMDQgMTAzLjUxIDAuMDQgMTAzLjUzIDEzMy4yNyAwLjk1Ij48L3BvbHlnb24+CiAgICAgIDxwb2x5Z29uIGlkPSJTaGFwZSIgZmlsbD0iI0Y1OEY4RSIgZmlsbC1ydWxlPSJub256ZXJvIiBwb2ludHM9IjAuMDQgMC45NSAwLjA0IDMxLjExIDM5LjIxIDAuOTUiPjwvcG9seWdvbj4KICAgICAgPHBvbHlnb24gaWQ9IlNoYXBlIiBmaWxsPSIjRkVFMThBIiBmaWxsLXJ1bGU9Im5vbnplcm8iIHBvaW50cz0iMzkuMjEgMC45NSAwLjA0IDMxLjExIDAuMDQgNjcuMDEgODUuODQgMC45NSI+PC9wb2x5Z29uPgogICAgICA8cG9seWdvbiBpZD0iU2hhcGUiIGZpbGw9IiNGM0YwOTUiIGZpbGwtcnVsZT0ibm9uemVybyIgcG9pbnRzPSI4NS44NCAwLjk1IDAuMDQgNjcuMDEgMC4wNCAxMDMuNTEgMTMzLjI1IDAuOTUiPjwvcG9seWdvbj4KICAgICAgPHBvbHlnb24gaWQ9IlNoYXBlIiBmaWxsPSIjNTVDMUFFIiBmaWxsLXJ1bGU9Im5vbnplcm8iIHBvaW50cz0iMTMzLjI3IDAuOTUgMC4wNCAxMDMuNTMgMC4wNCAxMzkuMTIgMTc5LjQ5IDAuOTUiPjwvcG9seWdvbj4KICAgICAgPHBvbHlnb24gaWQ9IlNoYXBlIiBmaWxsPSIjRjdCM0NFIiBmaWxsLXJ1bGU9Im5vbnplcm8iIHBvaW50cz0iMjM0LjA0IDAuOTUgMjI2LjY3IDAuOTUgMC4wNCAxNzUuNDUgMC4wNCAyMTEuMzggMjM0LjA0IDMxLjIiPjwvcG9seWdvbj4KICAgICAgPHBvbHlnb24gaWQ9IlNoYXBlIiBmaWxsPSIjODhBRURDIiBmaWxsLXJ1bGU9Im5vbnplcm8iIHBvaW50cz0iMTc5LjQ5IDAuOTUgMC4wNCAxMzkuMTIgMC4wNCAxNzUuNDUgMjI2LjY3IDAuOTUiPjwvcG9seWdvbj4KICAgICAgPHBvbHlnb24gaWQ9IlNoYXBlIiBmaWxsPSIjRjU4RjhFIiBmaWxsLXJ1bGU9Im5vbnplcm8iIHBvaW50cz0iMjM0LjA0IDMxLjIgMC4wNCAyMTEuMzggMC4wNCAyMzMuOTUgMTguMDcgMjMzLjk1IDIzNC4wNCA2Ny42NSI+PC9wb2x5Z29uPgogICAgICA8cG9seWdvbiBpZD0iU2hhcGUiIGZpbGw9IiNGRUUxOEEiIGZpbGwtcnVsZT0ibm9uemVybyIgcG9pbnRzPSIyMzQuMDQgNjcuNjUgMTguMDcgMjMzLjk1IDY0LjcgMjMzLjk1IDIzNC4wNCAxMDMuNTYiPjwvcG9seWdvbj4KICAgICAgPHBvbHlnb24gaWQ9IlNoYXBlIiBmaWxsPSIjRjNGMDk1IiBmaWxsLXJ1bGU9Im5vbnplcm8iIHBvaW50cz0iMjM0LjA0IDEwMy41NiA2NC43IDIzMy45NSAxMTIuMTEgMjMzLjk1IDIzNC4wNCAxNDAuMDYiPjwvcG9seWdvbj4KICAgICAgPHBvbHlnb24gaWQ9IlNoYXBlIiBmaWxsPSIjNTVDMUFFIiBmaWxsLXJ1bGU9Im5vbnplcm8iIHBvaW50cz0iMjM0LjA0IDE0MC4wOCAxMTIuMTMgMjMzLjk1IDE1OC4zNSAyMzMuOTUgMjM0LjA0IDE3NS42NyI+PC9wb2x5Z29uPgogICAgICA8cG9seWdvbiBpZD0iU2hhcGUiIGZpbGw9IiNGN0IzQ0UiIGZpbGwtcnVsZT0ibm9uemVybyIgcG9pbnRzPSIyMzQuMDQgMjEyIDIwNS41MyAyMzMuOTUgMjM0LjA0IDIzMy45NSI+PC9wb2x5Z29uPgogICAgICA8ZyBpZD0iR3JvdXAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDM3LjAwMDAwMCwgNzcuMDAwMDAwKSIgZmlsbD0iI0ZGRkZGRiI+CiAgICAgICAgPHBhdGggZD0iTTI4LjIzNzE1MTcsMC43NSBDMzIuNzUxMDgzNiwxLjcgMzYuMDExMTQ1NSwzLjU1IDM5LjM3MTUxNyw3LjA1IEM0Mi40MzA5NTk4LDEwLjI1IDQ0LjMzNjg0MjEsMTMuOSA0NS4xMzkzMTg5LDE4IEM0NS43OTEzMzEzLDIxLjQ1IDQ1Ljc5MTMzMTMsNTguNTUgNDUuMTM5MzE4OSw2Mi4wNSBDNDMuNDM0MDU1Nyw3MS4xNSAzNS42NjAwNjE5LDc4LjI1IDI2LjAzMDM0MDYsNzkuNSBDMjQuMDI0MTQ4Niw3OS43NSAxNy4zMDM0MDU2LDgwIDExLjE4NDUyMDEsODAgTC03LjEwNTQyNzM2ZS0xNSw4MCBMLTcuMTA1NDI3MzZlLTE1LDEuNDIxMDg1NDdlLTE0IEwxMi40MzgzOTAxLDEuNDIxMDg1NDdlLTE0IEMyMS4yNjU2MzQ3LDEuNDIxMDg1NDdlLTE0IDI1Ljc3OTU2NjYsMC4yIDI4LjIzNzE1MTcsMC43NSBaIE0xNC41NDQ4OTE2LDQwIEwxNC41NDQ4OTE2LDY1LjYgTDE5LjcxMDgzNTksNjUuNCBDMjQuMTc0NjEzLDY1LjI1IDI1LjEyNzU1NDIsNjUuMDUgMjcuMTMzNzQ2MSw2My45IEMzMS4wNDU4MjA0LDYxLjYgMzEuMDk1OTc1Miw2MS40NSAzMS4wOTU5NzUyLDM5LjcgQzMxLjA5NTk3NTIsMTguNSAzMS4wOTU5NzUyLDE4LjUgMjcuNDM0Njc0OSwxNi4xIEMyNS42MjkxMDIyLDE0LjkgMjQuODc2NzgwMiwxNC43NSAxOS45NjE2MDk5LDE0LjU1IEwxNC41NDQ4OTE2LDE0LjQgTDE0LjU0NDg5MTYsNDAgWiIKICAgICAgICAgICAgICBpZD0iQ29tYmluZWQtU2hhcGUiPjwvcGF0aD4KICAgICAgICA8cGF0aCBkPSJNOTMuNzg5NDczNyw3LjI1IEw5My43ODk0NzM3LDE0LjUgTDY4LjIxMDUyNjMsMTQuNSBMNjguMjEwNTI2MywzMi41IEw4My43NTg1MTM5LDMyLjUgTDgzLjc1ODUxMzksNDcgTDY4LjIxMDUyNjMsNDcgTDY4LjMxMDgzNTksNTYuMSBMNjguNDYxMzAwMyw2NS4yNSBMODEuMTUwNDY0NCw2NS40IEw5My43ODk0NzM3LDY1LjUgTDkzLjc4OTQ3MzcsODAgTDc4Ljk5MzgwOCw4MCBDNjIuNTQzMDM0MSw4MCA1OS45ODUxMzkzLDc5LjcgNTcuMzc3MDg5OCw3Ny40IEM1My43MTU3ODk1LDc0LjIgNTMuOTE2NDA4Nyw3Ni4yNSA1My43NjU5NDQzLDQxLjEgQzUzLjY2NTYzNDcsMTkuMiA1My44MTYwOTkxLDguODUgNTQuMTY3MTgyNyw3LjQ1IEM1NC44NjkzNDk4LDQuODUgNTcuODI4NDgzLDEuNjUgNjAuNDM2NTMyNSwwLjc1IEM2MS45OTEzMzEzLDAuMiA2NS45MDM0MDU2LDAuMDUgNzguMTQxMTc2NSw0LjI2MzI1NjQxZS0xNCBMOTMuNzg5NDczNyw0LjI2MzI1NjQxZS0xNCBMOTMuNzg5NDczNyw3LjI1IFoiCiAgICAgICAgICAgICAgaWQ9IlBhdGgiPjwvcGF0aD4KICAgICAgICA8cGF0aCBkPSJNMTI1LjQzNzE1MiwyOC4xIEMxMjkuMTQ4NjA3LDQyLjM1IDEzMi4yNTgyMDQsNTMuNyAxMzIuMzU4NTE0LDUzLjM1IEMxMzIuNTA4OTc4LDUzIDEzNS42Njg3MzEsNDAuOTUgMTM5LjQzMDM0MSwyNi41IEwxNDYuMzAxNTQ4LDAuMjUgTDE1NC4xMjU2OTcsMC4xIEMxNjAuMDQzOTYzLDcuMTA1NDI3MzZlLTE1IDE2MiwwLjE1IDE2MiwwLjYgQzE2MiwxLjA1IDE0NC42NDY0NCw2Ni44IDE0My42NDMzNDQsNzAuMSBDMTQyLjk0MTE3Niw3Mi40IDEzOS4xNzk1NjcsNzcuMSAxMzcuMDczMDY1LDc4LjM1IEMxMzQuNDE0ODYxLDc5Ljg1IDEzMC41MDI3ODYsODAuMSAxMjguMDk1MzU2LDc4Ljg1IEMxMjUuOTM4Nyw3Ny43NSAxMjMuMDc5ODc2LDc0LjQ1IDEyMS42MjUzODcsNzEuMzUgQzEyMC43MjI2MDEsNjkuNDUgMTA1Ljk3NzA5LDE1LjM1IDEwMi41NjY1NjMsMS4zNSBMMTAyLjIxNTQ4LDAgTDExMC4wMzk2MjgsMCBDMTE3LjcxMzMxMywwIDExNy45MTM5MzIsMCAxMTguMzE1MTcsMS4xIEMxMTguNTE1Nzg5LDEuNzUgMTIxLjcyNTY5NywxMy45IDEyNS40MzcxNTIsMjguMSBaIgogICAgICAgICAgICAgIGlkPSJQYXRoIj48L3BhdGg+CiAgICAgIDwvZz4KICAgIDwvZz4KICA8L2c+Cjwvc3ZnPgo=","dotsOptionsHelper":{"colorType":{"single":true,"gradient":false},"gradient":{"linear":true,"radial":false,"color1":"#6a1a4c","color2":"#6a1a4c","rotation":"0"}},"cornersSquareOptions":{"type":"extra-rounded","gradient":{"type":"radial","rotation":0.017453292519943295,"colorStops":[{"offset":0,"color":"#3e747e"},{"offset":1,"color":"#de720d"}]}},"cornersSquareOptionsHelper":{"colorType":{"single":true,"gradient":false},"gradient":{"linear":true,"radial":false,"color1":"#000000","color2":"#000000","rotation":"0"}},"cornersDotOptions":{"type":"","gradient":{"type":"radial","rotation":0,"colorStops":[{"offset":0,"color":"#ff0000"},{"offset":1,"color":"#1c93ce"}]}},"cornersDotOptionsHelper":{"colorType":{"single":true,"gradient":false},"gradient":{"linear":true,"radial":false,"color1":"#000000","color2":"#000000","rotation":"0"}},"backgroundOptionsHelper":{"colorType":{"single":true,"gradient":false},"gradient":{"linear":true,"radial":false,"color1":"#ffffff","color2":"#ffffff","rotation":"0"}}}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Solution - Part 2: Decorate the space
&lt;/h2&gt;

&lt;p&gt;The qr-code-styling example above appends the generated QR code to an element with a "canvas" id. For our token, we can add text around the code and style it accordingly. I'll leave the styling to you but the HTML for the text components can be added as shown below:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 html
&amp;lt;body&amp;gt;
&amp;lt;div id="output"&amp;gt;
    &amp;lt;p&amp;gt;Congratulations. To redeem your token, scan the below Code&amp;lt;/p&amp;gt;
    &amp;lt;div id="canvas"&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;p&amp;gt;OR&amp;lt;/p&amp;gt;
    &amp;lt;p&amp;gt;
      Enter the below claim code on the redemption site to redeem your token
    &amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;&amp;lt;strong class="guid"&amp;gt;&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The below is an example of what the above code renders.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqrurwwtayfp44uskldqb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqrurwwtayfp44uskldqb.png" alt="screenshot of what the QR code looks like"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution - Part 3:  Downloading the token.
&lt;/h2&gt;

&lt;p&gt;At this point, we have everything we need on the screen in HTML. The next step download everything we see, including the borders, as an image. To achieve this, we need to "screenshot" the part of the web page we want to download as image and download the result. We can use a &lt;a href="https://www.npmjs.com/package/html2canvas" rel="noopener noreferrer"&gt;HTML2Canvas&lt;/a&gt; to achieve this.&lt;/p&gt;

&lt;p&gt;As described in the docs, "The script allows you to take "screenshots" of webpages or parts of it, directly on the users browser. The screenshot is based on the DOM and as such may not be 100% accurate to the real representation as it does not make an actual screenshot, but builds the screenshot based on the information available on the page."&lt;/p&gt;

&lt;p&gt;Similar to qr-code-styling, we can import the script using the following:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 javascript
&amp;lt;script src="https://cdn.jsdelivr.net/npm/html2canvas@1.0.0-rc.5/dist/html2canvas.min.js"&amp;gt;&amp;lt;/script&amp;gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;and then implement the "screenshot" and download using the following code:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 javascript
    html2canvas(document.getElementById('output'))
    .then((canvas) =&amp;gt; {
        let link = document.createElement("a");
        link.download = 'fileName.png';
        link.href = canvas.toDataURL("image/jpg");;
        link.click();
    });


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The html2canvas library takes in the target html element and returns a promise with a new canvas element. We can then create a new download link with the new canvas (converted to data URL) as the content of the link, specify the filename and then "click" it to download the resultant canvas.&lt;/p&gt;

&lt;p&gt;Load the page and watch the screen populate and the "output" div download as a .png file. &lt;/p&gt;

&lt;p&gt;I hope you found this article useful and I'd love to see what you build with these tools. Please feel free to share in the comments.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>htm</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>The final step: How to package a Node.JS application as an exe</title>
      <dc:creator>Nhlanhla Lucky Nkosi</dc:creator>
      <pubDate>Mon, 04 Jan 2021 16:48:29 +0000</pubDate>
      <link>https://dev.to/luckynkosi/the-final-step-how-to-package-a-node-js-application-4hol</link>
      <guid>https://dev.to/luckynkosi/the-final-step-how-to-package-a-node-js-application-4hol</guid>
      <description>&lt;p&gt;“To a man with a hammer, everything looks like a nail.” — Twain/Maslow/Kaplan/Baruch/Buddha/Unknown.&lt;/p&gt;

&lt;p&gt;We all have our go-to tools; that language, framework, and/or IDE that we swear we can do anything with. Personally, I enjoy pushing the limits of whatever tool or programming language I use at the time. Currently, JavaScript is my hammer, and I hit everything with it. &lt;/p&gt;

&lt;p&gt;I was recently asked to build a "simple app" that did some administrative tasks. As with most things lately, I decided to use JavaScript to build it and only found out at the end of my development that I was expected to deliver this app as a .exe file to run on windows. Now that I had written all the wonderful Node.JS code, I had to answer the question: how do we package our application share it as an executable. &lt;/p&gt;

&lt;p&gt;There are numerous online resources aimed at teaching you how to build a Node.JS application. This is great since node, especially when reinforced with the myriad of open-source packages you can add with npm and yarn, is a great way of building applications of varying sizes and complexities. &lt;/p&gt;

&lt;p&gt;JavaScript is most commonly used for web development. Since sharing our creation on the web is such a common part of the web development ecosystem, we have a lot of hosting services that package and run our Node.JS code, leaving us only with the responsibility of giving them our code.&lt;/p&gt;

&lt;p&gt;What happens when we need to build a simple application that runs on a custom server somewhere? Most developers that use languages like Java and the c-family languages will simply go for building an executable programme from their code so that the application is self-contained and they don't need to share their actual code. JavaScript developers, how do we go about doing this; how do we build an executable from our Node.JS code without containerization?&lt;/p&gt;

&lt;h2&gt;
  
  
  PKG
&lt;/h2&gt;

&lt;p&gt;The first option is to use &lt;a href="https://www.npmjs.com/package/pkg" rel="noopener noreferrer"&gt;pkg&lt;/a&gt;. It is a command-line interface that enables the developer to create executables from Node.JS projects; allowing you to run the app even on environments that do not have Node.JS installed on them. Use cases include being able to share your app without sharing your source code thereby equipping you to create cross-platform commercial and demo copies of your application without needing to re-code the entire application or install dependencies on the machine that runs the application.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;To install pkg&lt;br&gt;
&lt;code&gt;npm install -g pkg &lt;br&gt;
&lt;/code&gt;&lt;br&gt;
To check if the pkg is correctly installed, you can run the following command: &lt;code&gt;pkg&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Most people have the entry point of their Node.JS application as an index.js file. If you build a package with no configuration, it will name your executable index.exe which is not a very meaningful name. I would, therefore, advise that you give this file a more meaningful name, like 'lucky-example-node-executable.js' but since I'm lazy to retype this name in the rest of the article, I'll call my example file 'lene.js'. With lene.js as the entry point to my Node app, I can package my app by running the following command:&lt;br&gt;
&lt;code&gt;pkg lene.js&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
Running the above command will generate three programmes; namely an executable for Windows, macOS, and Linux as shown in the image below:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqe8zx6u9gi2tjsr0q5ux.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqe8zx6u9gi2tjsr0q5ux.png" alt="pkg output"&gt;&lt;/a&gt;&lt;br&gt;
As shown in the above image, the command generates files that are aptly-named in accordance with the target platform of each programme.&lt;/p&gt;

&lt;p&gt;Suppose you only need to run your programme on one platform, say a windows server. Then packaging your app for all three targets would not necessarily make sense or be a good thing to do. In this instance, you can specify your target platform using the following command:&lt;br&gt;
&lt;code&gt;pkg -t &amp;lt;targets&amp;gt; &amp;lt;programme-entry&amp;gt;.js&lt;/code&gt;&lt;br&gt;
where &lt;code&gt;&amp;lt;programme-entry&amp;gt;.js&lt;/code&gt; is the file name of the entry point into your Node project. In our example here, the file name is &lt;code&gt;lene.js.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;targets&amp;gt;&lt;/code&gt; is a comma-separated list of target platforms that follow a specific 3-part format. Each part specifies different platform attributes that help tell pkg how exactly to package our app. These attributes are &lt;code&gt;nodeRange-platform-architecture&lt;/code&gt;. The first specifies the version of Node to be used by your application and specified as &lt;code&gt;node${n}&lt;/code&gt; or &lt;code&gt;node latest&lt;/code&gt; to use the latest available LTS version of Node. The second is the platform, specified as one of the following supported strings: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;freebsd, &lt;/li&gt;
&lt;li&gt;linux, &lt;/li&gt;
&lt;li&gt;alpine, &lt;/li&gt;
&lt;li&gt;macos, &lt;/li&gt;
&lt;li&gt;win.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and the last part is the target architecture namely x64, x86, armv6 or armv7.&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;pkg -t node12-linux,node10-win-x86 lene.js&lt;/code&gt; will fetch the necessary dependencies required to package the application and use node version 12 to build a Linux executable and then Node version 10 to build a Windows x86 executable. &lt;/p&gt;

&lt;p&gt;This is a quick start guide to using pkg. This was the most beginner-friendly solution I could find. For a simple application, you need almost no further configuration. The only extra work you might need is if you import dependencies based on variables. In this scenario, you would have to specify the files - scripts and assets - manually in pkg property of your package.json file in order for them to get packaged with your application code. &lt;/p&gt;

&lt;p&gt;Pkg is only one of a number of ways to do this. I also found &lt;a href="https://github.com/nexe/nexe" rel="noopener noreferrer"&gt;Nexe&lt;/a&gt; which is also a command-line utility that compiles your Node.js application into a single executable file.&lt;/p&gt;

&lt;p&gt;Let me know what you think of pkg and if you'd like to make a comparison of pkg and nexe. Do you have any other similar tools that I can look at? Please let me know in the comments section. &lt;/p&gt;

&lt;p&gt;I hope you found this article useful. &lt;/p&gt;

</description>
    </item>
    <item>
      <title>Complete Guide: How to use Grafana with a  custom Node API.</title>
      <dc:creator>Nhlanhla Lucky Nkosi</dc:creator>
      <pubDate>Fri, 21 Aug 2020 12:01:17 +0000</pubDate>
      <link>https://dev.to/luckynkosi/complete-guide-how-to-use-grafana-with-a-custom-node-api-2hd5</link>
      <guid>https://dev.to/luckynkosi/complete-guide-how-to-use-grafana-with-a-custom-node-api-2hd5</guid>
      <description>&lt;p&gt;Data visualization is important for harnessing the value in the data we have at our disposal. Grafana (described as The open observability platform) is used by thousands of companies to monitor everything. It makes data visualization and monitoring simpler. &lt;/p&gt;

&lt;h2&gt;
  
  
  Grafana basics
&lt;/h2&gt;

&lt;p&gt;Grafana can be downloaded in various ways from their &lt;a href="https://grafana.com/get" rel="noopener noreferrer"&gt;site&lt;/a&gt;.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgab00cgllf4ov9leg6o0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgab00cgllf4ov9leg6o0.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Grafana is available for Linux, Windows, Mac, Docker, and ARM. &lt;br&gt;
The &lt;a href="https://grafana.com/grafana/download" rel="noopener noreferrer"&gt;download page&lt;/a&gt; details how it can be downloaded and installed for each one of these options. &lt;/p&gt;

&lt;p&gt;Once Grafana is installed and running, you'll need to create a dashboard and at least one panel. A panel is the part of the dashboard that will have a specific visualization. &lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F5y9vbjb1kxcgzouku2pj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F5y9vbjb1kxcgzouku2pj.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you add a new panel in the latest version of Grafana (7.1.4 at the time of writing this article), a visualization of random walk data over time will be automatically created for you.&lt;/p&gt;
&lt;h2&gt;
  
  
  Data
&lt;/h2&gt;

&lt;p&gt;In the panel edit screen, you can see the visualization and three tabs at the bottom; namely, the Query, Transform, and Alert tabs. The Query tab has a dropdown with options of data sources you've added to your project as shown in the image below. &lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fb5eoqd6o9zryrbbq1hpz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fb5eoqd6o9zryrbbq1hpz.png" alt="Data sources"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You need to create your own data source. To do this, head back to the main screen, hover over the settings cog, then click on 'Data Sources.' &lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fd2d14kwb3k7tecc0ecg9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fd2d14kwb3k7tecc0ecg9.png" alt="head to data source"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The dependency (JSON)
&lt;/h2&gt;

&lt;p&gt;We'll need a plugin to help us fetch and format our data for Grafana. This example makes use of the &lt;a href="https://grafana.com/grafana/plugins/simpod-json-datasource" rel="noopener noreferrer"&gt;JSON Datasource Plugin&lt;/a&gt;. You need to download and install the plugin as it's shown on the site.&lt;/p&gt;
&lt;h3&gt;
  
  
  Adding the data source
&lt;/h3&gt;

&lt;p&gt;Once you've got the plugin installed, click on add data source then search for JSON plugin under Add data source. Once you have it, click select. &lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fh3o2wn98eljp8pk9reje.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fh3o2wn98eljp8pk9reje.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The most important field on this screen is the URL. In here, populate your custom Node.JS endpoint. The name field is simply for you to be able to tell between your different data sources so you can name it anything you like as I did below 😂. &lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F2sm595smwxvce6fjxqbq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F2sm595smwxvce6fjxqbq.png" alt="adding data source endpoint"&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Restful API
&lt;/h3&gt;

&lt;p&gt;The plugin's documentation stipulates that you need to implement at least 4 endpoints. I'll walk you through what these actually do and when they are triggered. &lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fxqi7mrlsjowtg3xrxvoz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fxqi7mrlsjowtg3xrxvoz.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As mentioned in the docs, you need a GET/ endpoint that returns a status code: 200 response. This is used to test whether your API is running. Without the expected response, Grafana won't add your data source and will pop up a "HTTP ERROR Bad Gateway" message. &lt;/p&gt;

&lt;p&gt;Click on the 'Save and Test' button at the bottom of the screen to save your new data source. &lt;/p&gt;

&lt;p&gt;Head back to your panel and click on edit. Now, when you click on the Query dropdown, your aptly named data source should appear as the first alternative option in the list. &lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F0fet8gju62nr7xod6sdv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F0fet8gju62nr7xod6sdv.png" alt="data source is now an option"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Under the 'A' query, there are a few things to discuss. &lt;/p&gt;

&lt;p&gt;The first field labeled 'Format as' controls the kind of data that is sent to your backend with the data request. It has two options, Time and Table. You can decide to perform different actions on your backend based on this. For this example, we won't care about the field. &lt;/p&gt;

&lt;p&gt;The next field, labeled Metric, is important to us. To populate this field, the data source will make a POST request to the endpoint you specified and suffix it with a 'sub endpoint': "/search". so in our example, to populate this dropdown, the JSON plugin will make a POST request to &lt;em&gt;localhost:4400/myEdnpoint/search&lt;/em&gt;. This means that your server should make a 'search' endpoint available. &lt;/p&gt;

&lt;p&gt;In my Node.JS + Express restful API, this is what the example code would look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/myEndpoint&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;handle_errors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;successfully tested&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/myEndpoint/search&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;handle_errors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;      
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;upper_25&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;upper_75&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first endpoint GET/ simply returns a status code 200 response. &lt;br&gt;
The second endpoint with '/search' is a &lt;em&gt;POST&lt;/em&gt; and returns a key-value part of text and value. The value will be used to query for data we want to visualize.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Feyixw12jg31n00fv3u5m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Feyixw12jg31n00fv3u5m.png" alt="Options showing"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you click on the Metric field of your choice as presented in the dropdowns, the plugin will make a &lt;em&gt;POST&lt;/em&gt; request to a 'sub endpoint' with '/query'. So in our example, once a choice is made from the dropdown, the JSON plugin will make a POST request to &lt;em&gt;localhost:4400/myEdnpoint/query&lt;/em&gt; with added information passed to it. &lt;/p&gt;

&lt;p&gt;This means our restful API needs to expose this endpoint. Our example implementation is shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/myEndpoint/search&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;handle_errors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;      
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;upper_25&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;upper_75&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As shown in the image of my debug window belong, the plugin makes a POST request and passes a lot of data in the body. &lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F5eq69ib60cst101ukca9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F5eq69ib60cst101ukca9.png" alt="debug window"&gt;&lt;/a&gt;&lt;br&gt;
The body object has a field called &lt;em&gt;targets&lt;/em&gt; which is an array of added user-input information. The first element of this array provides information from our first query in Grafana. This includes information about whether we want data formatted as &lt;em&gt;Time series&lt;/em&gt; or a &lt;em&gt;Table&lt;/em&gt;, the data source name, any additional user data, and importantly, the 'target' which is the value of the selected metric on the Grafana UI. I clicked on the 'upper_75' option and as dictated by our response in the &lt;em&gt;/search/&lt;/em&gt; endpoint, the value of this field is 2. This is also visible in the debug window shown above.&lt;/p&gt;

&lt;p&gt;On the Grafana query editor window, we also have a field where we can provide additional data in JSON form as shown in this image &lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F4pketgbwk80gvumvo2lj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F4pketgbwk80gvumvo2lj.png" alt="additional JSON data"&gt;&lt;/a&gt;&lt;br&gt;
This data will be sent to the &lt;em&gt;/query&lt;/em&gt; endpoint with the POST request once a metric is chosen. &lt;/p&gt;

&lt;p&gt;This data will also be in the target array's first element under the 'data' object. This can be seen in my debug window below. &lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fp1ahjk2ulp5hqsegrfsd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fp1ahjk2ulp5hqsegrfsd.png" alt="debug view of additional data"&gt;&lt;/a&gt; &lt;/p&gt;
&lt;h3&gt;
  
  
  Data response.
&lt;/h3&gt;

&lt;p&gt;Now that we have the required endpoints to accept requests for status checking (GET/ 200), options (/search), and actual data retrieval (/query), we need to understand how to format our response for Grafana to be able to visualize our data.&lt;/p&gt;

&lt;p&gt;In the screenshots above, data is retrieved from a function called &lt;code&gt;getQueryData()&lt;/code&gt;. All this function does is return an array with data formatted for visualization by Grafana as shown below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getQueryData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;target&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pps in&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;datapoints&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;622&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1450754160000&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;365&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1450754220000&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;target&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pps out&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;datapoints&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;861&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1450754160000&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;767&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1450754220000&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;target&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;errors out&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;datapoints&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;861&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1450754160000&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;767&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1450754220000&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;target&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;errors in&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;datapoints&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;861&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1450754160000&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;767&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1450754220000&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's understand this response. This is an array of 4 objects. These objects are labeled 'target'. This should also tell us that the &lt;code&gt;target&lt;/code&gt; field is likely to be a key of sorts. Since we'll be plotting a simple bar graph, the &lt;code&gt;target&lt;/code&gt; field will be labeling our x-axis values. The next field in the object is &lt;code&gt;datapoints&lt;/code&gt; which has to be a 2-dimensional array.  &lt;/p&gt;

&lt;p&gt;As shown in the Grafana documentation, the &lt;code&gt;datapoints&lt;/code&gt; property is of type &lt;code&gt;TimeSeriesPoints&lt;/code&gt;.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fahnlqzgskf1ub3hnxd6m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fahnlqzgskf1ub3hnxd6m.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;br&gt;
...which is of type &lt;code&gt;[][]&lt;/code&gt; (2D Array) as shown below. &lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F7zfrtgfww1ki1vvj7mn8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F7zfrtgfww1ki1vvj7mn8.png" alt="TimeSeriesPoints definition"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can think of the 2D Array as a Table with values and the time. The first entry's datapoints are represented in the table below. The time is shown as a Unix timestamp in milliseconds. &lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;pps in&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;622&lt;/td&gt;
&lt;td&gt;1450754160000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;365&lt;/td&gt;
&lt;td&gt;1450754220000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The Grafana visualization is taking shape but it isn't the bar graph we want yet. &lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F60jakybwykshd16kvgm0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F60jakybwykshd16kvgm0.png" alt="Time series graph"&gt;&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;At this point, we've done everything necessary on our Node backend. The rest is up to the front-end configuration of Grafana. &lt;/p&gt;

&lt;p&gt;Click on the 'show options' button on the top right corner of the edit screen. This brings up a panel with configurations for your display panel. &lt;/p&gt;

&lt;p&gt;Under "Visualization," you can see the different types of visualizations you can create on Grafana. We'll go with the bar graph. &lt;/p&gt;

&lt;p&gt;We need to change from being a time-based visualization to one where our 'target' is the independent variable. We need to change the x-axis. Change the x-axis mode from Time to 'Series' and voila; we now have a bar graph. Play around with the rest of the configurations to see how they change your visualization. &lt;/p&gt;

&lt;p&gt;Grafana is a powerful tool in the right hands. I hope this was useful to you. Keep learning. &lt;/p&gt;

</description>
      <category>grafana</category>
      <category>node</category>
      <category>javascript</category>
      <category>database</category>
    </item>
    <item>
      <title>To Kill a Working Drone: Flying drones with Twitter, Bananas and WebAPIs with Lucky Nkosi</title>
      <dc:creator>Nhlanhla Lucky Nkosi</dc:creator>
      <pubDate>Thu, 23 Jul 2020 11:59:28 +0000</pubDate>
      <link>https://dev.to/luckynkosi/to-kill-a-working-drone-flying-drones-with-twitter-bananas-and-webapis-with-lucky-nkosi-4l98</link>
      <guid>https://dev.to/luckynkosi/to-kill-a-working-drone-flying-drones-with-twitter-bananas-and-webapis-with-lucky-nkosi-4l98</guid>
      <description>&lt;p&gt;Lucky is a Software Engineer in the R&amp;amp;D division of BBD Software and a game development lecturer at the Digitals Arts division of Wits University in Johannesburg. He has experience in building and maintaining enterprise banking software with varying tech-stacks; all the way from VB6 and .Net to web and cloud technologies. Lucky is interested in IoT and passionate about making technology accessible through the web. &lt;/p&gt;

&lt;p&gt;My talk covers the power of modern browsers and how their features can be used to create accessible web applications that facilitate the delivery of meaningful and interactive experiences. I show this by flying a drone with a variety of control schemes; from pure JS to my phone's accelerometer and even detecting my hand gestures with no direct contact to anything. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://drive.google.com/file/d/1A6N0Uf_NZotakKVNH2tcNsCVT1VhsfT0/view?usp=sharing"&gt;Here is a download link to the talk slides (PDF)&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This talk will be presented as part of &lt;a href="https://codelandconf.com"&gt;CodeLand:Distributed&lt;/a&gt; on &lt;strong&gt;July 23&lt;/strong&gt;.  After the talk is streamed as part of the conference, it will be added to this post as a recorded video.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>codeland</category>
      <category>drones</category>
    </item>
    <item>
      <title>Flying a drone with Web Bluetooth</title>
      <dc:creator>Nhlanhla Lucky Nkosi</dc:creator>
      <pubDate>Thu, 18 Jun 2020 19:11:21 +0000</pubDate>
      <link>https://dev.to/luckynkosi/flying-a-drone-with-web-bluetooth-5aco</link>
      <guid>https://dev.to/luckynkosi/flying-a-drone-with-web-bluetooth-5aco</guid>
      <description>&lt;p&gt;The second article in the "To Kill a working drone" Series. &lt;/p&gt;

&lt;p&gt;All the code for the projects mentioned in the series can be found in my "MamboMiniControllers" Github repo:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vJ70wriM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://practicaldev-herokuapp-com.freetls.fastly.net/assets/github-logo-ba8488d21cd8ee1fee097b8410db9deaa41d0ca30b004c0c63de0a479114156f.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/LuckyNkosi"&gt;
        LuckyNkosi
      &lt;/a&gt; / &lt;a href="https://github.com/LuckyNkosi/MamboMiniControllers"&gt;
        MamboMiniControllers
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Demos and example code showing different control schemes for flying the MamboParrorMini 
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h1&gt;
  
  
  To kill a working drone with Web Bluetooth
&lt;/h1&gt;

&lt;p&gt;In the previous article, we looked at the Mambo Parrot drone and its capabilities. The drone can be controlled using Bluetooth Low Energy (BLE) and Wifi. For this experiment, we'll be focusing on BLE. &lt;/p&gt;

&lt;p&gt;Gery has a post on the fundamentals of Bluetooth and using the Web Bluetooth API. She covers the topic very well in her article titled "BLE and GATT and other TLAs". I suggest you take a look at that if you need a deeper understanding of Web Bluetooth.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag__link"&gt;
  &lt;a href="/gerybbg" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6aq31wP0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/practicaldev/image/fetch/s--g7tdNwUo--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/207813/9b9c6346-0f6b-474b-9aa1-1ff4d608575c.jpg" alt="gerybbg image"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/gerybbg/ble-and-gatt-and-other-tlas-21f5" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;BLE and GATT and other TLAs&lt;/h2&gt;
      &lt;h3&gt;Gergana Young ・ Dec 17 '19 ・ 6 min read&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#bluetooth&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#webbluetooth&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#javascript&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#iot&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;Armed with BLE, WBA and other TLAs, we can deduce that in order to fly the drone safely, we need to be able to do the following: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Connect to the drone&lt;/li&gt;
&lt;li&gt;Takeoff&lt;/li&gt;
&lt;li&gt;Hover&lt;/li&gt;
&lt;li&gt;Land&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are tasks that are likely to be actioned quite frequently so I created a drone connection management script. The script is based on Peter O'Saughnessy's &lt;em&gt;drone.v1.2.js&lt;/em&gt; script which is part of his parrot drone project on GitHub. &lt;/p&gt;

&lt;p&gt;Our modified &lt;code&gt;drone-connection-management.js&lt;/code&gt; script exports a class named &lt;code&gt;ParrotDrone&lt;/code&gt;. With &lt;code&gt;drone&lt;/code&gt; assigned to an instance of &lt;code&gt;ParrotDrone&lt;/code&gt;,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;drone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ParrotDrone&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The class exposes utility function which we can then access as shown below. :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;drone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onDisconnectCallback&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;drone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;takeOff&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;drone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hover&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;drone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;land&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;drone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;disconnect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;drone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;emergencyCutOff&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;//Movement commands&lt;/span&gt;
&lt;span class="nx"&gt;drone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flip&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;//One directional flip&lt;/span&gt;
&lt;span class="nx"&gt;drone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;moveForwards&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; 
&lt;span class="nx"&gt;drone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;moveBackwards&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;drone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;moveRight&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;drone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;moveLeft&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;drone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;twistLeft&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;drone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;twistRight&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  'Moving' a drone:
&lt;/h3&gt;

&lt;p&gt;The above commands translate to specific drone movements. 'moving' in any direction, involves a combination of slightly tilting the drone and thereby moving it in said direction. Forwards and backward involves pitching while left and right involve rolling the drone. &lt;/p&gt;

&lt;p&gt;The 'twist' commands are simply yawing movements; keeping the drone in place and rotating it about the y-axis. The roll, pitch, and yaw movements are shown in the image below sourced from &lt;a href="https://emissarydrones.com/what-is-roll-pitch-and-yaw"&gt;Emissary Drones&lt;/a&gt;. &lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Q9nEyCgk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://emissarydrones.com/wp-content/uploads/2016/09/Pitch-Roll-and-Yaw-1024x678.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Q9nEyCgk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://emissarydrones.com/wp-content/uploads/2016/09/Pitch-Roll-and-Yaw-1024x678.jpg" alt="yaw, roll, and pitch"&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This forms the backbone of our different control schemes. To abstract from the complexity of our drone-connection-management script, we can create a &lt;code&gt;drone-control.js&lt;/code&gt; script which simplifies how we work with the drone. The code for this script is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ParrotDrone&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../lib/drone-connection-management.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;//#region  Initialisation&lt;/span&gt;
&lt;span class="c1"&gt;// to make working with angles easy&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TO_RAD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PI&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TO_DEG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;TO_RAD&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;drone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ParrotDrone&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;drone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;drone&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;onDisconnectCallback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Disconnected called&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;//connect:&lt;/span&gt;
  &lt;span class="nx"&gt;drone&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onDisconnectCallback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;connected&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Connection Error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;connectToDrone&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;takeOff&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;drone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;takeOff&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;land&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;drone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;land&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;disconnectFromDrone&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;drone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;land&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;hover&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;drone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hover&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;//Global functions so we can simply call them &lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;takeOff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;takeOff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;land&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;land&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connectToDrone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;connectToDrone&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hover&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;hover&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;disconnectFromDrone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;disconnectFromDrone&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  Using the code
&lt;/h1&gt;

&lt;p&gt;To use this code, we simply have to import the file in order HTML document.Using a relative path with the script saved in a '&lt;em&gt;lib&lt;/em&gt;' folder,we can import it as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"../lib/drone-control.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The following buttons will then allow you to control the drone:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"takeOff()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Take Off&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"connectToDrone()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Connect To Drone&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"disconnectFromDrone()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Disconnect&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"hover()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Hover&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"land()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Safe Land&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  All done
&lt;/h1&gt;

&lt;p&gt;At this point, you have everything you need to fly the drone with Web-Bluetooth. The following section is added information on how the drone works and if you use the script in my repo, is not necessary to get you started and you can skip to the conclusion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connect to the drone
&lt;/h2&gt;

&lt;p&gt;Connecting to the drone involves a few steps. Firstly, we need to discover (search for) the drone. Then connect to it's GATT services.  &lt;/p&gt;

&lt;h3&gt;
  
  
  Discovering the drone
&lt;/h3&gt;

&lt;p&gt;The drone is a bluetooth periferal. To discover it, we use the Web Bluetooth API's &lt;code&gt;Bluetooth.requestDevice()&lt;/code&gt;.&lt;br&gt;
The below image is the method definition as found on MDN Docs.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0HVnTz8Z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3088c3dw7zuc25ar1zdd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0HVnTz8Z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3088c3dw7zuc25ar1zdd.png" alt="Screenshot of MDN Docs on Bluetooth.requestDevice()"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As shown in the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Bluetooth/requestDevice"&gt;MDN Docs&lt;/a&gt;, the method takes in an optional &lt;code&gt;options&lt;/code&gt; parameter object which has &lt;code&gt;filters&lt;/code&gt; and &lt;code&gt;optionalServices&lt;/code&gt; arrays as well as the &lt;code&gt;acceptAllDevices&lt;/code&gt; boolean which defaults to false. &lt;/p&gt;

&lt;p&gt;With droneDevice declared in this scope, we can create a &lt;code&gt;_discover()&lt;/code&gt; function to do this. The function looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="nx"&gt;_discover&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Searching for drone...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bluetooth&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requestDevice&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;namePrefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Mambo_&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;optionalServices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_getUUID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fa00&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_getUUID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fb00&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_getUUID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fd21&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_getUUID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fd51&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;device&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Discovered drone&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;device&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;droneDevice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;device&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Connect GATT
&lt;/h3&gt;

&lt;p&gt;From Gery's post: "The Bluetooth Generic Attributes (GATT) Profile is the way that Bluetooth devices communicate with each other. It defines a hierarchical data structure for communication between two BLE devices"&lt;/p&gt;

&lt;p&gt;Once we've got the &lt;code&gt;droneDevice&lt;/code&gt; returned and set by our &lt;code&gt;_discover()&lt;/code&gt; function, we can now connect to the drone using it's  GATT profile. We can listen for it's disconnection using an eventListener and the code looks as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="nx"&gt;_connectGATT&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Connect GATT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;droneDevice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gattserverdisconnected&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_handleDisconnect&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;droneDevice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gatt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GATT server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gattServer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Based on the drone's characteristics, we can get the state of the drone and use the drone's services to interact with it. For this, we need to start notifications for the services. For this, we have the &lt;code&gt;_startNotifications()&lt;/code&gt; function which also handles caching. As Peter notes: The services are&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Services:
 *  - fa00 - contains 'write without response' this.characteristics starting with fa...
 *  - fb00 - contains 'notify' this.characteristics starting with fb...
 *  - fc00 - contains 'write' characteristic ffc1, not currently used
 *  - fd21 - contains 'read write notify' this.characteristics fd22, fd23, fd24
 *  - fd51 - contains 'read write notify' this.characteristics fd52, fd53, fd54
 *  - fe00 - contains this.characteristics fe01, fe02, not currently used
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  connect in a nutshell
&lt;/h2&gt;

&lt;p&gt;In a nutshell, we can create a promise chain of the steps required for connection and it looks as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_discover&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_connectGATT&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
          &lt;span class="p"&gt;})&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_startNotifications&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
          &lt;span class="p"&gt;})&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="nx"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_handshake&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
              &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
          &lt;span class="p"&gt;})&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Completed handshake&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
          &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Once we've connected successfully to the drone, we can begin sending control commands to the drone. To control the drone, we need to write commands to it's characteristics through it's GATT profile thrugh the &lt;code&gt;characteristic.writeValue(command);&lt;/code&gt;.&lt;br&gt;
The rest of the code is available in the repo. &lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;The scripts we have created here, namely the drone-connection-management.js and drone-control.js will form the base for the different control scheme we explore in the rest of the series which includes flying the drone with twitter, bananas, and a leap motion controller.&lt;/p&gt;

&lt;p&gt;I hope you enjoyed this article. &lt;/p&gt;

&lt;p&gt;Lucky &lt;/p&gt;

</description>
    </item>
    <item>
      <title>To Kill a working drone - Intro</title>
      <dc:creator>Nhlanhla Lucky Nkosi</dc:creator>
      <pubDate>Wed, 03 Jun 2020 19:58:19 +0000</pubDate>
      <link>https://dev.to/luckynkosi/to-kill-a-working-drone-intro-20jp</link>
      <guid>https://dev.to/luckynkosi/to-kill-a-working-drone-intro-20jp</guid>
      <description>&lt;p&gt;In this series, I'll take you through flying a drone with Node.JS and BLENO, Web Bluetooth, and a wide range of custom controllers which include a variety of fruits, hand gesture detection and twitter. &lt;/p&gt;

&lt;p&gt;I'll share all the libraries I used, the code I wrote, and details of the devices I use, where necessary. &lt;/p&gt;

&lt;h1&gt;
  
  
  Context
&lt;/h1&gt;

&lt;p&gt;Like most good stories, this one starts with me having a few drinks at a company event. On my way home, I went online and 'accidentally' bought a drone and completely forgot that I did. I bought the drone from a an online store that charges unbelievably low prices for almost anything you can imagine and takes months to deliver the items to you. This is where I buy gadgets and electronic components I don't actually need. &lt;/p&gt;

&lt;p&gt;About three months later, the drone, along with other things I had forgotten about, arrived. I now had a drone I had no use for so I did what any sane tech enthusiast would do: I decided to hack the drone. &lt;/p&gt;

&lt;p&gt;This idea was inspired by a talk on Web Bluetooth I saw at DevConf in Johannesburg, South Africa by the incredible Gergana (Gery) Young &lt;/p&gt;
&lt;div class="ltag__user ltag__user__id__207813"&gt;
    &lt;a href="/gerybbg" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F207813%2F9b9c6346-0f6b-474b-9aa1-1ff4d608575c.jpg" alt="gerybbg image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/gerybbg"&gt;Gergana Young&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/gerybbg"&gt;Software developer, adventurer, and Star Wars geek extraordinaire.&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
 

&lt;p&gt;The below image was stuck in my head and it is the reason for months of all the madness in this series of articles&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F5ql6644s5jku5kofcka4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F5ql6644s5jku5kofcka4.png" alt="Gergana Yougn Speaking at tech conference"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I spoke about five ways to control a drone at &lt;a href="https://www.bbd.co.za" rel="noopener noreferrer"&gt;BBD Software Development&lt;/a&gt;’s &lt;a href="https://www.youtube.com/watch?v=So326HH45lM" rel="noopener noreferrer"&gt;Es@cape conference&lt;/a&gt; last year and you can watch that video here: &lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/elWMa9H7cQM"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;From me, Lucky: &lt;em&gt;Sharp👍 Sharp👍&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fay71s5q02kljzp6xvzlf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fay71s5q02kljzp6xvzlf.png" alt="Godd bye brain"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>drone</category>
      <category>browserapi</category>
      <category>iot</category>
    </item>
    <item>
      <title>Social Intelligence is among the most critical of skills. </title>
      <dc:creator>Nhlanhla Lucky Nkosi</dc:creator>
      <pubDate>Tue, 04 Feb 2020 15:30:12 +0000</pubDate>
      <link>https://dev.to/luckynkosi/social-intelligence-is-among-the-most-critical-of-skills-38k8</link>
      <guid>https://dev.to/luckynkosi/social-intelligence-is-among-the-most-critical-of-skills-38k8</guid>
      <description>&lt;p&gt;Technology is changing how the workplace operates and understandably, everyone is anxious about the future of work and related socio-economics elements. While automation, digitization, and the 4IR will scale down and in some instances, even bring a complete end to scores of jobs and industries, it will also create new jobs that previously did not exist. The question then, all students and young professionals should be asking themselves, is: What will set them apart in the future of work?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CBfI0EES--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/whj62mud7p42mrqo307u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CBfI0EES--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/whj62mud7p42mrqo307u.png" alt="Social Approval"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Traditionally, society looked to academia and formal tertiary education for answers. However, with technology, its hyper-rapid development, its accessibility and the greater impact it has on society, these institutions have been found wanting with regards to how fast they can pivot to meet industry needs. This is, of course, not to say that they are not doing anything. There are several higher education institutions in South African and the world that have, over the past ten years, introduced incredible programmes to address skill shortages, emerging industries’ needs and meet the market demand for highly skilled professionals. However, overall, these institutions stay stagnant for far too long and as a result, they are often out of touch with the respective industries. &lt;/p&gt;

&lt;p&gt;While young people have, in the past, been able to easily rely on tertiary education to secure comfortable employment, we’re now seeing a high number of graduates that can’t get employment. Naturally, there are a plethora of factors that contribute to this. These include, but are not limited to, saturation in the different fields, a deteriorating economy, hiring biases and a general mismatch of skills availability and demand. As a result, the youth can no longer just simply rely on their tertiary qualifications and are forced to ask the question: What will set us apart? &lt;/p&gt;

&lt;p&gt;I believe that the answer is something that is we can refer to as “Social Intelligence.” Social Intelligence can be described as “the ability to connect to others in a deep and direct way, to sense and stimulate reactions and desired interactions.”&lt;sup&gt;&lt;a href="https://sastudy.co.za/article/10-skills-for-the-future-workforce-or-how-to-get-yourself-employed/"&gt;1&lt;/a&gt;&lt;/sup&gt;  This belief is founded on solid scientific research. A 2019 study conducted by Cecily Josten and Grace Lordan for IZA - Institute of Labor Economics (IZA DP No. 12520)&lt;sup&gt;&lt;a href="https://www.oecd-ilibrary.org/docserver/2e2f4eea-en.pdf?expires=1580310900&amp;amp;id=id&amp;amp;accname=guest&amp;amp;checksum=EA230FDB22DD7A6CEE6714836333634A"&gt;2&lt;/a&gt;&lt;/sup&gt;  projects that by 2030, 47% of jobs will be automatable; with 35% of all jobs being fully automatable. The study, along with many others, suggests that ‘people’ and ‘thinking’ skills will become more valuable in the fourth industrial revolution. &lt;/p&gt;

&lt;p&gt;Several recent studies into the future of jobs expanded on the revolutionary work of Carl Frey and Michael Osborne on the future of employment. The Frey and Osborne paper entitled “The future of employment: how susceptible are jobs to computerization?”&lt;sup&gt;&lt;a href="https://www.oxfordmartin.ox.ac.uk/downloads/academic/The_Future_of_Employment.pdf"&gt;3&lt;/a&gt;&lt;/sup&gt;  was published in 2013 and provides a list of 70 different kinds of jobs and ranks them by their chances of being automatable. At the top of the list of jobs least likely to be automatable are medical practitioners (especially those dealing with mental health), educators, entertainers, designers, analysists, makeup artists, first responders, scientists, and engineers. Some of the most automatable jobs include telemarketers, librarians, clerks, tellers and barrage of manual-intensive and repetitive jobs. &lt;/p&gt;

&lt;p&gt;We’ve all heard of robots taking over everything, so what makes these specific jobs immune? Josten and Lordan analysed the types of skills required for said jobs – with a focus on their similarities. The most prominent were:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;em&gt;Social perceptiveness&lt;/em&gt; - Being aware of others' reactions and understanding why they react as they do. &lt;/li&gt;
&lt;li&gt; &lt;em&gt;Negotiation&lt;/em&gt; - Bringing others together and trying to reconcile differences. &lt;/li&gt;
&lt;li&gt; &lt;em&gt;Persuasion&lt;/em&gt; - Persuading others to change their minds or behaviour. &lt;/li&gt;
&lt;li&gt; &lt;em&gt;Assisting and caring for others&lt;/em&gt; - Providing personal assistance, medical attention, emotional support, or other personal care to others such as co-workers, customers, or patients. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Collectively, these traits makeup Social Intelligence; or simply: the ability to connect to others in a deep and direct way, to sense and stimulate reactions and desired interactions. Robots are fundamentally logic-based operators reliant on repetition. From the listed jobs and skills, we can abstract that the safest jobs are those that rely on human perception and interaction. &lt;/p&gt;

&lt;p&gt;I keep mentioning the future, but what about the present? Well, social intelligence is key to thriving in the modern-day workplace. There are very few jobs in which you don’t have to interact with other people. For instance, the myth of the Loner Genius Nerd in software development is not actually real when it comes to the working world. Programmers, for instance, must interact with each other, the business analysts, test teams, managers and even their clients. All of these different relationships and inter-person interactions are navigated differently. They require us to tap into our reserve of social skills. &lt;/p&gt;

&lt;p&gt;Social intelligent people will be able to see when a team member is anxious and panicking and they’ll be able to disarm and help calm them down. Social intelligence has a direct impact on an individual’s personal performance. Having good social skills allows one to effectively get critical information from those around them in order to enhance their own performance. Socially intelligent people are more likely to build good social capital – helping them elevate their career far better and rapidly than those who are not sociable. Biases, whether blatant or not, exist in the workplace and we cannot shy away from that. They affect everyone’s decision making, whether they are aware of them or not. Social Intelligence allows one to understand other people’s biases and how to navigate and disarm them. It also allows people to be able to identify and interrogate their own biases and thought processes.&lt;/p&gt;

&lt;p&gt;Having worked in different organisations across different industries, I've had the opportunity to experience different leadership styles. I strongly believe that Social Intelligence is a cornerstone of good leadership. The time of the traditional scary boss shouting orders from a tower has now passed. The most successful companies in the world have realised that human capital is their most valuable asset and have begun treating their employees with empathy and care. If you, as a leader, want to retain the best professionals and get the best performance out of your employees, you must be able to engage them beyond the surface. To be a truly effective leader in the modern-day workplace, you need to have well developed “soft skills.” People around you want to feel that they are led by a competent person who’s human and understand that they are human too. &lt;/p&gt;

&lt;p&gt;According to the latest Council for Medical Schemes (CMS) Report (2013-14)&lt;sup&gt;&lt;a href="https://www.medicalschemes.com/files/Annual%20Reports/CMS_AnnualReport2017-2018.pdf"&gt;4&lt;/a&gt;&lt;/sup&gt; the prevalence of mental illness amongst medical schemes beneficiaries is rising – which is directly linked to professionals. Dr Ali Hamdulay concludes that “Mental illness in the workplace leads to decreased productivity, increased sick-related absenteeism, poor work quality, wasted materials and even compromised workplace safety.”&lt;sup&gt;&lt;a href="http://www.sadag.org/index.php?option=com_content&amp;amp;view=article&amp;amp;id=2279:manage-mental-illness-in-the-workplace&amp;amp;catid=61&amp;amp;Itemid=143"&gt;5&lt;/a&gt;&lt;/sup&gt;   Dr. Humdulay emphasizes that Proactive Management is the best medicine for mental illness in the workplace. Proactive management in this instance requires great social intelligence. &lt;/p&gt;

&lt;p&gt;Ultimately, the work we all do in our jobs is meant to serve human beings. If we do not understand each other, how can we truly deliver the best possible work we can? A builder must be able to understand and know how to manage their client’s emotions during construction. A teacher must do the same with their learners. Engineers must understand the people who will be using the systems they are building in order to build valuable things. We, as humans, are corporative species. As a species, we do our best when we work well with each other. That can’t happen without us being aware of each others' mental state and well-being. &lt;/p&gt;

&lt;p&gt;One of the most critical and underrated skills anyone entering the workplace can have is social intelligence. It is not only about listening to those around you. It’s about being able to understand, empathise and connect with people beyond basic communication. This skill requires great personal effort to sharpen and will ultimately help us build the future of work environments we want and deserve. &lt;/p&gt;

&lt;p&gt;I love working with new technologies and exploring the effects technology has on society. You can see some of my work on &lt;a href="https://luckynkosi.dev"&gt;my personal site&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;From me, Lucky: &lt;em&gt;Sharp👍 Sharp👍&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lT2k2n9o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/ay71s5q02kljzp6xvzlf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lT2k2n9o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/ay71s5q02kljzp6xvzlf.png" alt="Godd bye brain"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>mentalhealth</category>
      <category>socialintelligence</category>
      <category>productivity</category>
      <category>leadership</category>
    </item>
    <item>
      <title>The future beyond writing software</title>
      <dc:creator>Nhlanhla Lucky Nkosi</dc:creator>
      <pubDate>Fri, 27 Dec 2019 16:53:30 +0000</pubDate>
      <link>https://dev.to/luckynkosi/the-future-beyond-writing-software-3ni0</link>
      <guid>https://dev.to/luckynkosi/the-future-beyond-writing-software-3ni0</guid>
      <description>&lt;p&gt;Creating new, immersive and inclusive cyber-physical and digital experiences.&lt;/p&gt;

&lt;p&gt;The date is December 9, 1968, and the phenomenal Douglas Engelbart delivers the mother of all demos; a video of which can be found here.&lt;br&gt;
In this demo, Engelbart essentially introduces the idea of the mouse and keyboard. This is at a time when personal computing is simply inconceivable. 18 years later, in 1986, IBM released the Model M keyboard that resembles what most keyboards look like today.&lt;/p&gt;

&lt;p&gt;Since the computer became personal, we've generally had one main means of interacting with it; the mouse and keyboard. Every single time we've dared to rethink and experiment with how we interact with computers, we've unlocked fulfilling new and immersive experiences that reshape the understanding of our reality and potential through facilitating meaningful engagement with the digital environment and in some cases, as with augmented reality, even blurring the lines between the digital and physical spaces.&lt;/p&gt;

&lt;p&gt;In addition to changing our experiences of technology and the world around us, our experimentation with different means of interacting with computers often disrupts existing ways of doing things and indeed create entirely new genres, sub-genres and entire industries that challenge, grow and sometimes even replace existing industries.&lt;/p&gt;

&lt;p&gt;As software developers, we often confine ourselves to just that; writing code. The reasons for this vary widely. Whatever the reason, the need to investigate new creative means of interacting with computers has never been greater and the only way we can do that is by borrowing from other fields. This is evident with the advent of The Internet of Things.&lt;/p&gt;

&lt;p&gt;Enterprise software is never built in silos; it's built with the collaboration of people from different backgrounds with different skillsets. It makes me happy that we're now realizing the value of expanding this collaboration beyond the confines of software development and that we're collaborating with professionals and hobbyists from all sorts of knowledge backgrounds.&lt;/p&gt;

&lt;p&gt;The technology industry has matured beyond just being about getting things working. It's transcended to being about delivering value to the lives of ordinary people in the world. This has required us to look at the problematic areas of our field, from our exclusionary designs - which lacked consideration of people's different abilities to interact with the systems we create to the toxic and often masculine culture we've allowed to develop and fester in the social fabric of our industry.&lt;/p&gt;

&lt;p&gt;Touch screens, VR, AR, and IoT have started to make us think outside the box with regards to what's possible. My plea is that we don't stop there; that we don't solely rely on ready-made products from big corporations but also think of what we'd like to see exist ourselves and develop it. Our means of interacting with systems can have large effects on what we end up designing and building. When building systems, developers should consider human-computer interaction and how they can break boundaries with revolutionary means of interaction. It is the constant tempering with ideas around computer interaction that will give birth to groundbreaking experiences because play and experimentation are the parents of innovation.&lt;/p&gt;

&lt;p&gt;A key area of focus when researching new ways of human-computer interaction must be finding ways of making said technologies affordable and accessible. We need to be able to teach enthusiasts, hobbyists and professionals alike the skills they need to use the interaction means expediently and with ease. This is because the success of any system built is reliant on its use and the more developers use it, the more experiences it creates and the more we grow the field and its reach into solving societal issues.&lt;/p&gt;

&lt;p&gt;I'm writing a bit about my experience with building my own means of interacting with computer systems. You can see my work on &lt;a href="https://luckynkosi.dev"&gt;my personal site&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;From me, Lucky: &lt;em&gt;Sharp👍 Sharp👍&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lT2k2n9o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/ay71s5q02kljzp6xvzlf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lT2k2n9o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/ay71s5q02kljzp6xvzlf.png" alt="Godd bye brain"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>inclusion</category>
      <category>gaming</category>
      <category>controllers</category>
      <category>technology</category>
    </item>
    <item>
      <title>Banana Controller: Building an affordable Custom Controller with the Arduino Uno</title>
      <dc:creator>Nhlanhla Lucky Nkosi</dc:creator>
      <pubDate>Thu, 19 Dec 2019 13:13:29 +0000</pubDate>
      <link>https://dev.to/luckynkosi/banana-controller-building-an-affordable-custom-controller-with-the-arduino-uno-3d8h</link>
      <guid>https://dev.to/luckynkosi/banana-controller-building-an-affordable-custom-controller-with-the-arduino-uno-3d8h</guid>
      <description>&lt;p&gt;Let’s build a cheap custom controller using bananas and an Arduino Uno.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fo65bqnl2gu7lcwrfom2x.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fo65bqnl2gu7lcwrfom2x.jpeg" alt="banana-controller"&gt;&lt;/a&gt;&lt;br&gt;
Image source: &lt;a href="https://makezine.com/author/nerdyjb" rel="noopener noreferrer"&gt;John Baichtal&lt;/a&gt; on &lt;a href="https://blog.makezine.com/2013/04/05/makey-makey-banana-pong/" rel="noopener noreferrer"&gt;Make: Community&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Aim
&lt;/h2&gt;

&lt;p&gt;In this article, I’ll show you how to build your own custom controller using one of the cheapest, beginner-friendly, and versatile micro-controller boards on the market: the &lt;a href="https://www.arduino.cc/en/Guide/ArduinoUno" rel="noopener noreferrer"&gt;Arduino Uno&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Background &amp;amp; Context
&lt;/h2&gt;

&lt;p&gt;I am fascinated by how we, as humans, interact with computers. I believe that by experimenting with different ways of doing this, we can build very interesting things. The common problem with “new” technologies is that they are often expensive to play around with. We have to find means of making the technology easily and affordably accessible.&lt;/p&gt;

&lt;p&gt;I learn best through practical examples. Since this is supposed to be a serious article - with no banana business, we’ll be building something very useful: a banana controller. Yes, we’ll build a custom controller using bananas. &lt;/p&gt;

&lt;p&gt;I first came across the idea while attending the &lt;a href="http://www.a-maze.net/" rel="noopener noreferrer"&gt;A Maze festival&lt;/a&gt; in Johannesburg a few years ago. I walked up to a stand and I was told that I could play &lt;a href="https://en.wikipedia.org/wiki/Space_Invaders" rel="noopener noreferrer"&gt;Space Invaders&lt;/a&gt; with bananas. The experience blew my mind. They had used the &lt;a href="https://makeymakey.com/" rel="noopener noreferrer"&gt;Makey Makey&lt;/a&gt; to achieve this with ease. The Makey Makey classic board retails for around US$50 which is just over R800 (ZAR). The board is incredible and relatively well priced (i.m.o) for its capabilities. However, the price-tag limits access.&lt;/p&gt;

&lt;p&gt;The Arduino Uno, on the other hand, can be bought for as little as R99 which is less than $10. At a fraction of the price, we can achieve (almost) the same goal: building a custom controller to play a game with bananas.&lt;/p&gt;


&lt;h2&gt;
  
  
  Tools
&lt;/h2&gt;

&lt;p&gt;We’ll need a couple of tools to get this job done.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F5k4qvrdyrcqpjgb8otv8.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F5k4qvrdyrcqpjgb8otv8.jpeg" alt="Tools"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Here’s what you’ll need
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;An Arduino Uno Board&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.arduino.cc/en/Main/Software" rel="noopener noreferrer"&gt;Arduino IDE&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.arduino.cc/en/Main/Software" rel="noopener noreferrer"&gt;Unity 3D&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Bananas&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Solution Overview
&lt;/h2&gt;

&lt;p&gt;The user/player will touch the banana. Using the Arduino, we’ll detect this touch and then notify Unity that the player has touched the banana. Unity will then use this information to make the game react.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fc0cszta4po5peapnova5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fc0cszta4po5peapnova5.png" alt="Circuit"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Electronics — How it works
&lt;/h2&gt;

&lt;p&gt;The Arduino Uno comes with 6 Analog pins. Each pin has a 10-bit Analog to Digital Converter (ADC). With 10 bits, we can represent 1024 distinct values. The Arduino is powered with 5 Volts. Therefore, using the ADC, we can represent the 5V in 10 bits. With this understanding, we can use digital values in our software to represent voltage reading on the input pins. Consider the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F4ea3ro2m5o9ug1bceqkd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F4ea3ro2m5o9ug1bceqkd.png" alt="Equation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This means that each digit difference picked up by our software represents a &lt;code&gt;4.9mV&lt;/code&gt; difference.&lt;/p&gt;


&lt;h2&gt;
  
  
  Arduino Software
&lt;/h2&gt;

&lt;p&gt;We need software that will detect and interpret the touching of the banana. The following is all the code we need to run on our Arduino.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;875&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9600&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;Keyboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;analogRead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;analogRead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use one of the Arduino IDEs to program your Uno. This code makes use of the analogRead() function to get the reading from the Arduino Analogue pins. When the reading drops below our threshold — determined through logging out the behavior during testing, we can assume that the fruit connected to the analog input has been touched. We then have to send a message to Unity.&lt;br&gt;
The Uno is not the best board for activating keyboard events. Therefore, we use the serial port to communicate with the Unity program. Using Serial.write(), we pass on a simple message to be understood by whichever application will read the serial port. In this instance, we send the integer 1 when the first fruit is touched and integer 2 when the second one is touched.&lt;/p&gt;


&lt;h2&gt;
  
  
  Unity3D Software
&lt;/h2&gt;

&lt;p&gt;With the Arduino sending either a 1 or a 2 when one of the fruits is touched, we now need to write some code to read the serial port and make use of the information being sent. Reading the serial in unity is not much of a hassle. First, you need access to ports and related functionality. &lt;code&gt;Using System.IO.Ports&lt;/code&gt; gives us this functionality.&lt;/p&gt;

&lt;p&gt;Next, you need to declare your stream. This configuration must match the serial port configuration used in your Arduino IDE. Set the port (COM6) and the baud rate (9600, is standard on most devices) on the variable declaration section of our like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;    &lt;span class="n"&gt;SerialPort&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SerialPort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="n"&gt;COM6&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;9600&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On &lt;code&gt;Start()&lt;/code&gt;, open the stream to start receiving data as soon as your program starts and set the reading timeout to 1 second.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;    &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Start&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadTimeout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On &lt;code&gt;Update()&lt;/code&gt;, read the information being streamed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;    &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;readValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadByte&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The line above will throw an exception when it does not receive data. I’d recommend wrapping it in a try-catch block and interrogating the exceptions it throws. We put this line in the Update function to ensure that we check it every frame — this ensures that we can detect the banana touch as soon as possible.&lt;br&gt;
The stream has to be flushed to ensure that we read the latest and most up-to-date data possible. Adding &lt;code&gt;stream.BaseStream.Flush();&lt;/code&gt; after checking for data can achieve this.&lt;/p&gt;

&lt;p&gt;I’d recommend setting the stream’s timeout to 1 second in the start function. The full Unity C# script called SerialPortComs will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Collections&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Collections.Generic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;UnityEngine&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.IO.Ports&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SerialPortComs&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;MonoBehaviour&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;SerialPort&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SerialPort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"COM6"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;9600&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// Start is called before the first frame update&lt;/span&gt;
        &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadTimeout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="c1"&gt;// Update is called once per frame&lt;/span&gt;
        &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsOpen&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;try&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;readValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadByte&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                    &lt;span class="nf"&gt;UseBananaInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;readValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="c1"&gt;//handle errors&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BaseStream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Flush&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;//Ensure data refresh&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;UseBananaInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;value_sent_by_arduino&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;//this is where you put code that makes use of the input&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the UserBananaInput function, you can now move an object or perform any other game action that relies on user interactions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;I gave a talk on this topic at &lt;a href="https://www.bbd.co.za" rel="noopener noreferrer"&gt;BBD Software Development&lt;/a&gt;’s &lt;a href="https://www.youtube.com/watch?v=So326HH45lM" rel="noopener noreferrer"&gt;Es@cape conference&lt;/a&gt; in 2018. The talk touches on my motivation for this type of work and includes a live demo in which I do all of the above steps. You can work through both this article and the video. The talk is titled “Play with your food… and everything else.” You can watch it &lt;a href="https://www.youtube.com/watch?v=N_LLymkN7Pg&amp;amp;t=663s" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can find all the code used in this project on my &lt;a href="https://github.com/LuckyNkosi/banana-controller" rel="noopener noreferrer"&gt;GitHub page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I hope you enjoyed reading this and learned something interesting. Let me know if you have any questions and I will gladly answer them. Please feel free to also show me what you build with custom controllers; I’d love to see what everyone else is working on. Check out my other projects which include educational games and teaching conversational &lt;a href="https://en.wikipedia.org/wiki/Zulu_language" rel="noopener noreferrer"&gt;IsiZulu&lt;/a&gt; on my &lt;a href="https://luckynkosi.dev" rel="noopener noreferrer"&gt;site&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;From me, Lucky: &lt;em&gt;Sharp👍 Sharp👍&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fay71s5q02kljzp6xvzlf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fay71s5q02kljzp6xvzlf.png" alt="Godd bye brain"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
