<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Raspberry Pi - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Raspberry Pi - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sat, 30 May 2026 22:25:44 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/raspberry-pi/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Use Your Raspberry Pi Headlessly with VS Code and SSH (No Monitor Needed) ]]>
                </title>
                <description>
                    <![CDATA[ The Raspberry Pi is a portable computer with an onboard processor that fits comfortably in the palm of your hand. Compared with general purpose computers, it’s an affordable option developed by the Raspberry Pi Foundation. The Raspberry Pi Model B wa... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-use-your-raspberry-pi-headlessly-with-vs-code-and-ssh/</link>
                <guid isPermaLink="false">6835cf3101fd0876c3c66de0</guid>
                
                    <category>
                        <![CDATA[ Raspberry Pi ]]>
                    </category>
                
                    <category>
                        <![CDATA[ ssh ]]>
                    </category>
                
                    <category>
                        <![CDATA[ vscode extensions ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Josiah Adesola ]]>
                </dc:creator>
                <pubDate>Tue, 27 May 2025 14:41:53 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1748452192906/594a76a0-be8f-478b-a9ae-e3ba55850c65.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>The Raspberry Pi is a portable computer with an onboard processor that fits comfortably in the palm of your hand. Compared with general purpose computers, it’s an affordable option developed by the <a target="_blank" href="https://www.raspberrypi.org/">Raspberry Pi Foundation</a>.</p>
<p>The Raspberry Pi Model B was introduced in 2012 as the first sellable unit, and the company has since released many more models. There are even low-cost models like the Raspberry Pi Zero Series, which is quite small and tailored to embedded systems applications. All the models operate on an operating system called the Raspberry Pi OS, a Linux flavor niched for Raspberry PI Computers.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747912686077/498ff16a-c6a0-4774-b6e4-a0d573afd4f8.jpeg" alt="A Raspberry Pi Model 4B single-board computer with visible ports, components." class="image--center mx-auto" width="768" height="1020" loading="lazy"></p>
<p>In this tutorial, we’ll be using the Raspberry Pi 4 Model for a headless setup through a SSH connection using Visual Studio Code (VS Code). The Raspberry Pi 4 Model has a Quad-core ARM Cortex-A72 (64-bit) SoC at 1.5GHz, up to 8GB RAM options, video inputs, Ethernet shield, USB ports, MicroSD card slot for storage, USB-C power input and 40 General Purpose Inputs and Outputs Pins (GPIO). Impressive, right?</p>
<p>You’ll be able to use the Raspberry Pi as a personal computer, for home automation and IoT projects, robotics projects, network applications, educational tools, and Artificial Intelligence projects.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-understanding-the-headless-setup">Understanding the Headless Setup</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-preparing-the-microsd-card">Preparing the MicroSD Card</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-boot-the-raspberry-pi">How to Boot the Raspberry Pi</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-understanding-the-led-of-the-raspberry-pi-during-setup">Understanding the LED of the Raspberry Pi During Setup</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-establish-an-ssh-connection">How to Establish an SSH Connection</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-visual-studio-code-for-remote-development">How to Set Up Visual Studio Code for Remote Development</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-write-and-run-the-code-remotely">How to Write and Run the Code Remotely</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-understanding-the-headless-setup">Understanding the Headless Setup</h2>
<p>Many Raspberry Pi computers are sold with additional peripherals, including a keyboard, mouse, and monitor, that are essential for the Raspberry Pi's setup. A headless setup is the process of configuring the Raspberry Pi or preparing it for use without needing these peripherals. This entails operating the Raspberry Pi through a network protocol like SSH (Secure Shell) or VNC (Virtual Network Computing).</p>
<p>This is really helpful when you don’t need peripherals, as it lets you use your personal computer to connect to the Raspberry Pi without needing to purchase specialized peripherals. It’s also excellent for remote access. This headless setup is also essential for remote monitoring systems, such as surveillance systems with remote camera access, and IoT systems.</p>
<p><a target="_blank" href="https://www.raspberrypi.com/products/raspberry-pi-400/"><img src="https://assets.raspberrypi.com/static/neat-lg@2x-38697d13d9952791ca96da4891de9a12.jpg" alt="A Raspberry Pi 400 in use on a desk, with a mouse and monitor connected" width="2600" height="1800" loading="lazy"></a></p>
<p>Remote development lets you write code and modify your Raspberry Pi and other devices connected to the GPIO pins through a headless configuration via SSH.</p>
<p>SSH guarantees a secure connection for transferring and modifying files, as well as transferring and debugging commands from one computer (your personal computer) to another computer (the Raspberry Pi). It restricts unauthorized access from any other system that aims to intercept the communication channel.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Here’s what you’ll need to follow along with this tutorial:</p>
<h3 id="heading-hardware-requirements">Hardware Requirements</h3>
<ol>
<li><p>Raspberry Pi 4 or 5</p>
</li>
<li><p>MicroSD Card (8GB or higher recommended)</p>
</li>
<li><p>Flash Drive with SD Card Slot or a MicroSD Card Adapter</p>
</li>
<li><p>Power Supply (5V 2A/3A)</p>
</li>
<li><p>Network Connection (Wi-Fi, Pi, and laptop must be on the same network)</p>
</li>
<li><p>Personal Computer (Windows, macOS, Linux)</p>
</li>
</ol>
<h3 id="heading-software-requirements">Software Requirements</h3>
<ol>
<li><p>Raspberry Pi Operating System (Raspberry Pi OS)</p>
</li>
<li><p>Visual Studio Code</p>
</li>
<li><p>Remote SSH Extension in VS Code</p>
</li>
</ol>
<h2 id="heading-preparing-the-microsd-card">Preparing the MicroSD Card</h2>
<p>The Raspberry Pi requires a MicroSD Card that serves as the storage of your the Raspberry Pi OS using Raspberry Pi Imager. The operating system of the Raspberry Pi provides a graphical interface to interact with the Raspberry Pi, store files and datasets, and write commands to get your Raspberry Pi working.</p>
<p>But the Raspberry Pi needs an empty MicroSD Card to install the Raspberry Pi OS in the MicroSD Card. Here are some step by step instructions that’ll show you how to get your MicroSD Card setup before inserting it back into the Raspberry Pi for SSH Connection.</p>
<h3 id="heading-downloading-and-flashing-raspberry-pi-os">Downloading and flashing Raspberry Pi OS</h3>
<h4 id="heading-insert-your-microsd-card-into-a-flash-drive-with-a-sd-card-slot">Insert your MicroSD Card into a flash drive with a SD Card slot</h4>
<p>Aside from using a flash drive with an SD Card slot (so as to get the memory card connected to the computer), you can also use a SD Card adapter. Make sure it’s inserted into your computer where you have the Raspberry PI Imager downloaded to flash – that is, transfer the OS into the SD Card.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747921369222/7deebdff-d0bc-4f08-9aab-07c562a712bd.jpeg" alt="My flash drive with an SD card slot" class="image--center mx-auto" width="963" height="1280" loading="lazy"></p>
<h4 id="heading-download-the-raspberry-pi-imagerhttpswwwraspberrypicomsoftware-based-on-your-pcs-operating-system">Download the <a target="_blank" href="https://www.raspberrypi.com/software/">Raspberry Pi Imager</a> based on your PC’s operating system</h4>
<p>This involves clicking the link and selecting your operating system (either MacOS, Windows or Linux operating system). The Raspberry Pi OS comes in these variants for different OSes</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747922079380/d1aa21cb-3166-4924-8f98-e2f16816fec6.png" alt="Screenshot of a webpage from raspberrypi.com showing instructions for installing Raspberry Pi OS using Raspberry Pi Imager. It includes download links for Windows, macOS, and Ubuntu. There is a command for installing on Raspberry Pi OS and an image of the Raspberry Pi Imager interface." class="image--center mx-auto" width="872" height="947" loading="lazy"></p>
<h4 id="heading-next-install-and-open-the-raspberry-pi-imager">Next, install and open the Raspberry Pi imager</h4>
<p>Click the Raspberry Pi Imager download, follow all the instructions during the installation process. Once this screen pops up, you’re good to go!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747922173921/02e58463-0634-41d6-bc85-0a1a3a199996.png" alt="The image shows the Raspberry Pi Imager v1.8.5 interface. It has options to &quot;Choose Device,&quot; &quot;Choose OS,&quot; and &quot;Choose Storage.&quot; There is also a faded &quot;Next&quot; button at the bottom. The background is a shade of raspberry red." class="image--center mx-auto" width="1348" height="930" loading="lazy"></p>
<h4 id="heading-choose-your-raspberry-pi-device-and-operating-system-and-select-storage">Choose your Raspberry Pi Device and operating system and select Storage</h4>
<p>For each of the three configurations, you must select one sequentially. Select a device according to the type of Raspberry Pi you have, and various options will appear. I selected the Raspberry Pi 4, as it is my preferred device. You may choose between the Raspberry Pi 5 and the Raspberry Pi Zero 2 W, depending on your device requirements.</p>
<p>Next, proceed to the operating system – I would recommend choosing the 64-bit version. While many people opt for the legacy version (32-bit), I think the 64-bit version is best. Once you're finished, you can choose a storage option, and your MicroSD should appear. My storage is around 128GB, which is why you can see 125.1GB displayed there in the screenshot below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747922610658/d5f4c750-ab40-47ed-8c0c-fe5951f68660.png" alt="Screenshot of the Raspberry Pi Imager v1.8.5 interface. It shows options for selecting a Raspberry Pi device, operating system, and storage. Available devices include Raspberry Pi 5, 4, and Zero 2 W. Operating systems listed are Raspberry Pi OS in 64-bit and 32-bit versions, and there is a USB device listed for storage." class="image--center mx-auto" width="1080" height="1080" loading="lazy"></p>
<h4 id="heading-click-on-next-and-edit-the-settings">Click on “Next” and edit the settings</h4>
<p>It is a customary practice to keep your username as "pi", but it’s not required. The goal is to have something simple and easy to remember when setting up your SSH connection. It’s also helpful to make your password simple. I used 'roboticsai'.</p>
<p>Try to avoid using numbers simply to make things easier, because you may not be able to see what you are entering in the terminal. Then, make sure that your wireless LAN and SSID (WIFI or Hotspot name if you're using a phone, as well as the password for your WIFI or Hotspot) is the same network as the one linked to your computer.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747922797428/c1a1ae55-e109-4a35-878c-820d1ef3f406.png" alt="Setting up the username and password of the Raspberry Pi" class="image--center mx-auto" width="1077" height="1060" loading="lazy"></p>
<h4 id="heading-click-on-services-and-enable-ssh-then-use-password-authentication-for-security-and-click-on-save">Click on “SERVICES” and enable SSH. Then use password authentication for security and Click on “SAVE”.</h4>
<p>After you've completed the changes in the General Section, go to the Services section and click the checkbox button “<em>Enable SSH</em>”. Once highlighted, make sure you pick “<em>Use password authentication</em>”, avoid the “<em>RUN SSH-KEYGEN</em>” button at the moment, and then click Save.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747923037374/b6c63a5a-e3b6-4c9f-9612-53df7e566e41.png" alt="Screenshot of an OS customization window under the &quot;Services&quot; tab. &quot;Enable SSH&quot; is checked, with &quot;Use password authentication&quot; selected. An option for &quot;Allow public-key authentication only&quot; is available. A disabled &quot;RUN SSH-KEYGEN&quot; button and a &quot;SAVE&quot; button are visible." class="image--center mx-auto" width="1071" height="926" loading="lazy"></p>
<h4 id="heading-click-yes-to-apply-the-customizations-and-the-raspberry-pi-os-should-get-flashed-into-your-sd-card">Click “YES” to apply the customizations, and the Raspberry Pi OS should get flashed into your SD Card.</h4>
<p>Following the previous stage, you will be shown various buttons to apply the adjustments you have made. Pick yes, and the Raspberry Pi OS will be flashed or transferred to your Memory Card. This could take between 10 and 20 minutes to go from transferring to writing or customizing. Hold on and enjoy the process.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747923100214/d86fca18-e540-4844-9f26-5253ef5b04b8.png" alt="Raspberry Pi Imager dialog box offering to apply OS customisation settings with options to edit, clear, accept, or decline." class="image--center mx-auto" width="1345" height="897" loading="lazy"></p>
<h4 id="heading-after-a-successful-installation-into-the-disk-remove-your-sd-card">After a successful installation into the disk, remove your SD Card.</h4>
<p>You will receive a successful popup like the one shown below. This demonstrates that all processes were completed successfully, and the Raspberry Pi OS is now installed.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747926095493/ed017dc6-e3a5-4cda-8b3c-6f6a2d74c1ac.png" alt="A notification on Raspberry Pi Imager v1.8.5 shows that Raspberry Pi OS (64-bit) has been successfully written to a mass storage device USB. It instructs to remove the SD card and has a &quot;Continue&quot; button." class="image--center mx-auto" width="1352" height="998" loading="lazy"></p>
<h2 id="heading-how-to-boot-the-raspberry-pi">How to Boot the Raspberry Pi</h2>
<h3 id="heading-eject-the-microsd-safely-from-your-computer"><strong>Eject the MicroSD safely from your computer</strong></h3>
<p>Once the installation is successful, eject the MicroSD safely from the computer.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747926401040/a37dafbb-68ae-4884-9ffe-3c374e6b62b9.jpeg" alt="The Raspberry Pi Board with a micro SD card slot is placed on a wooden surface. A SanDisk 128 GB micro SD card lies next to it." class="image--center mx-auto" width="768" height="1020" loading="lazy"></p>
<h3 id="heading-insert-it-upside-down-into-the-microsd-card-slot-of-your-raspberry-pi">Insert it “upside down” into the MicroSD card slot of your Raspberry Pi</h3>
<p>To properly insert the MicroSD card, place it gently into the slot with the back or gold side facing upward. It will protrude slightly once it is inserted. You are good to go!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747926384277/f7a0c5e9-6a13-4cd2-b94a-65b329a08a5c.jpeg" alt="A Raspberry Pi single-board computer placed on a gray surface, displaying various ports and components including USB ports and an HDMI connector." class="image--center mx-auto" width="768" height="1020" loading="lazy"></p>
<h3 id="heading-connect-the-usb-c-port-of-your-raspberry-pi-to-your-computer-give-the-raspberry-pi-some-time-to-load">Connect the USB-C port of your Raspberry Pi to your computer. Give the Raspberry Pi some time to load</h3>
<p>Get a USB-C cable and connect one end to your Raspberry Pi's USB-C port and the other to a laptop port. It should light up red, indicating that there is an adequate power source. You may also power your Raspberry Pi directly by plugging into a wall socket.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747926452530/b023abe6-5555-4d61-ab99-0d8e36a828a4.jpeg" alt="A Raspberry Pi connected to a laptop via a USB cable on a wooden surface." class="image--center mx-auto" width="768" height="1020" loading="lazy"></p>
<p>After a while, the memory card should begin to boot into the Raspberry Pi, and the green LED will blink for a while. In the next section, we’ll talk about the different states of the two LEDs during and after a successful boot.</p>
<h2 id="heading-understanding-the-led-status-of-the-raspberry-pi-during-setup">Understanding the LED Status of the Raspberry Pi During Setup</h2>
<p>The below table describes the LED statuses you might see when you power on your Raspberry Pi and the SD Card is in the slot.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>LED Color</strong></td><td><strong>State/Pattern</strong></td><td><strong>Meaning/Recommendation</strong></td></tr>
</thead>
<tbody>
<tr>
<td>🔴Red</td><td>Solid (ON)</td><td>Stable and sufficient power supply</td></tr>
<tr>
<td>🔴Red</td><td>Off or Blinking</td><td>Undervoltage detected (Use a direct phone Charger connected to a Socket)</td></tr>
<tr>
<td>🟢Green</td><td>Blinking (Irregular Pattern)</td><td>SD Card is being read/written (normal booting activity)</td></tr>
<tr>
<td>🟢Green</td><td>Solid (ON)</td><td>Raspberry Pi is stuck or trying to boot.</td></tr>
<tr>
<td>🟢Green</td><td>Off</td><td>No SD Card detected or boot completed</td></tr>
<tr>
<td>🟢Green</td><td>Repeated blink patterns (for example 4 long, 4 short)</td><td>Error code indicating firmware issues.</td></tr>
<tr>
<td>🟢Green</td><td>Constant Blinking</td><td>Normal activity (Raspbian OS is loading and running smoothly)</td></tr>
</tbody>
</table>
</div><h2 id="heading-how-to-establish-an-ssh-connection">How to Establish an SSH Connection</h2>
<p>The SSH (Secure Shell) connection is a network protocol that allows two computers to safely communicate without leaking any information. It’s also used for remote command line execution and for file transfers between two computers.</p>
<p>To establish an SSH connection, you’ll have to complete a few steps. Then I’ll explain how to enable SSH using a Visual Studio Code extension</p>
<h3 id="heading-create-a-wpasupplicantconftxt-in-the-same-folder-of-your-raspberry-pi-sd-card"><strong>Create a</strong> <code>wpa_supplicant.conf.txt</code> <strong>in the same folder of your Raspberry Pi SD Card</strong></h3>
<p>Insert your MicroSD card back into the computer. Then the files that comprise the Raspberry Pi OS will appear on your computer. Create a new text (.txt) document on your computer, similar to the image below, under the SD Card storage section.</p>
<p>Add the code below, making sure that "ssid" is the name of your Wi-Fi network and "psk" is your network's password.</p>
<pre><code class="lang-plaintext">country=NG # Your 2-digit country code
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
network={
    ssid="Josiah"
    psk="roboticsai"
    key_mgmt=WPA-PSK
}
</code></pre>
<h3 id="heading-save-the-file-on-the-same-sd-card"><strong>Save the file on the same SD Card</strong></h3>
<p>Once you've finished producing the text file, save it to the SD Card storage, as shown in the image below.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747928812199/fb766a84-7750-468d-ae78-1ff3c688a52b.jpeg" alt="Screenshot of a file explorer window showing contents of the &quot;bootfs (D:)&quot; directory. Various system and configuration files are listed, including kernel images and .elf files. The selected file is &quot;wpa_supplicant.conf.txt&quot;." class="image--center mx-auto" width="540" height="960" loading="lazy"></p>
<h3 id="heading-create-a-ssh-folder"><strong>Create a .ssh folder</strong></h3>
<p>In your personal computer, create a <code>.ssh</code> folder if it doesn’t exist on your personal computer.</p>
<p>If it exists, the <code>.ssh</code> folder should contain files like <code>id_rsa</code>, <code>known_hosts</code>, and <code>config</code> files. It shouldn’t be empty.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747928560247/d07f8cb9-ec22-47ff-a2a5-d18b614e9985.png" alt="A computer file explorer window showing a list of folders in the &quot;JOSIAH&quot; directory. The folder names include &quot;.matplotlib,&quot; &quot;.mchp_cm,&quot; &quot;.ssh,&quot; and others, with details like date modified and type. The &quot;.ssh&quot; folder is highlighted." class="image--center mx-auto" width="1919" height="977" loading="lazy"></p>
<p>After a successful boot, open your terminal or command line application on your personal computer.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747928958812/2c198297-08a5-4781-9a6a-45fd6c7e85d3.png" alt="Command Prompt window showing &quot;Microsoft Windows [Version 10.0.26100.3915]&quot; and the prompt at &quot;C:sersOSIAH>&quot;." class="image--center mx-auto" width="1731" height="923" loading="lazy"></p>
<p>Make sure that the Raspberry Pi is connected to the same network before moving ahead. Once your wifi or mobile hotspot is switched on, make sure it’s the same password as the <code>wpa_supplicant.conf.txt</code> and the settings page while installing the Raspberry Pi.</p>
<p>As long as the SD card is in the Raspberry Pi and there is adequate power supply for at least 2-5 minutes, the Raspberry Pi will get connected to the wifi or your mobile hotspot.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747931364223/13947f34-c1a9-4b1c-b020-c236b4d377af.jpeg" alt="Screenshot of connected devices interface showing a limit of 3 devices. Two devices are listed: &quot;raspberrypi&quot; with MAC address d8:3a:dd:43:27:71, and &quot;Josiah&quot; with MAC address dc:71:96:d0:d5:4a. A blocklist option is available for viewing devices not allowed to connect." class="image--center mx-auto" width="720" height="825" loading="lazy"></p>
<h3 id="heading-how-to-resolve-connection-problems">How to Resolve Connection Problems</h3>
<p>If there is no connection, reinstall the Raspberry Pi OS Imager on the SD Card again. Then you can also change the network AP Band from 5GHz to 2.5GHz or vice-versa. This can be very tricky.</p>
<p>It should get connected after trying this. Just make sure that the passwords are consistent and that you don’t accidentally have the caps lock key switched on while typing, for example.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748028008935/192fb282-93c7-4b65-86a2-c26cdfac9d53.jpeg" alt="Screenshot of a portable hotspot setup screen showing fields for network name, password, security setting (WPA2-Personal), AP band selection (5 GHz), and an option to hide the SSID, which is off." class="image--center mx-auto" width="720" height="914" loading="lazy"></p>
<p>To confirm if the Raspberry Pi is connected using the command line interface, use the <code>ping</code> command – it shows the devices connected to the device.</p>
<pre><code class="lang-bash">ping raspberrypi.local
</code></pre>
<p>After running the above command, you should see an image showing the connection once it’s successful like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747931544791/77217325-4f3e-4abb-a136-d4634b773f2d.png" alt="A command prompt window showing a ping test to &quot;raspberrypi.local&quot; with an IPv6 address. Four packets are sent and received with no loss. Round trip times range from 6ms to 125ms, with an average of 36ms." class="image--center mx-auto" width="925" height="382" loading="lazy"></p>
<p>For establishing an SSH connection using the terminal, run the code below:</p>
<pre><code class="lang-bash">ssh pi@raspberrypi.local
</code></pre>
<p>This will result in a request for a password. If it shows an error like the image below, it means you have to delete the <code>known_hosts.old</code> and <code>known_hosts</code> if either or both exist in the <code>.ssh</code> folder in your PC. This is because the keys are conflicting with each other. Then re-run the above code <code>ssh pi@raspberrypi.local</code> in your terminal.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747931933273/d8f111cc-3455-4de3-af0c-cf0a9814a877.png" alt="SSH warning message indicating a change in the remote host identification for a Raspberry Pi, suggesting possible eavesdropping or a host key update. Offers instructions for resolving the issue by updating the known_hosts file." class="image--center mx-auto" width="912" height="489" loading="lazy"></p>
<p>After successful entry, type “<code>yes</code>” in the terminal.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747932392301/a09ad065-8e1e-464f-baef-5529eee26ce4.png" alt="Command-line interface showing an SSH connection attempt to a Raspberry Pi. It prompts the user to confirm the authenticity of the host with a given key fingerprint, asking if they want to continue connecting." class="image--center mx-auto" width="1392" height="281" loading="lazy"></p>
<p><code>Connection Closed</code> should show when the connection is successful.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747932449402/bcd16bc6-18a1-4a84-82cf-d5c7431345e3.png" alt="Screenshot of a terminal window showing an SSH connection attempt from a user to a Raspberry Pi. The authenticity of the host is questioned, asking for confirmation to continue. The fingerprint is displayed, indicating it's not previously known. The connection is then added to known hosts, before closing." class="image--center mx-auto" width="1501" height="345" loading="lazy"></p>
<h2 id="heading-how-to-set-up-visual-studio-code-for-remote-development">How to Set Up Visual Studio Code for Remote Development</h2>
<p>Download and install <a target="_blank" href="https://code.visualstudio.com/">Visual Studio Code</a> if you don’t have it already.</p>
<p>Then, click on the VS Code extension and search for <code>Remote - SSH</code> by Microsoft and install it to your machine.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747933199343/2c181e3a-80bd-44d1-8b40-5e3cf6191f2b.png" alt="Screenshot of the Visual Studio Code extension marketplace displaying the &quot;Remote - SSH&quot; extension by Microsoft. It shows installation details, ratings, and features like using a remote machine with SSH for development. The left sidebar lists related extensions." class="image--center mx-auto" width="1919" height="888" loading="lazy"></p>
<p>Next, click on the “Remote Explorer” icon that looks like a monitor. Select the SSH config in your <code>C:\Users\{name}\.ssh\config</code> folder.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747933364169/7eaa4a7b-294c-41ef-8ecb-fe80151e6399.png" alt="Screenshot of Visual Studio Code showing the Remote - SSH extension interface. The SSH configuration file selection is open, displaying file paths. On the right, there is a description and installation details for the extension, including version and update information. The left sidebar displays a connection to a remote SSH machine named &quot;raspberrypi&quot;." class="image--center mx-auto" width="1918" height="781" loading="lazy"></p>
<p>Make sure the config has this command:</p>
<pre><code class="lang-bash">Host raspberrypi.local
    HostName raspberrypi.local
    User pi
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747933533204/611b6a3d-cd4c-4756-982b-76efb0aa25c9.png" alt="611b6a3d-cd4c-4756-982b-76efb0aa25c9" class="image--center mx-auto" width="916" height="500" loading="lazy"></p>
<p>Enter your username as <code>raspberrypi.local</code> and input your password – the same as the password during loading Raspbian OS.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747933839359/7f43b231-5177-4b11-9d10-7234961db3f7.png" alt="Visual Studio Code interface showing a prompt to enter a password for &quot;pi@raspberrypi.local&quot; to set up an SSH host. The background features a shortcut guide and a loading bar." class="image--center mx-auto" width="1530" height="1002" loading="lazy"></p>
<p>After inputting the correct password, it should start downloading the server.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747933859344/0c87fd5c-071b-4016-b113-4fc88c166032.png" alt="Visual Studio Code interface showing keyboard shortcuts for various commands. A download progress bar at the bottom indicates &quot;Downloading VS Code Server…&quot;" class="image--center mx-auto" width="1526" height="988" loading="lazy"></p>
<p>Congratulations! The image below has a blue rectangle button showing <code>SSH:raspberrypi.local</code> which shows a successful SSH Connection through Visual Studio Code. This also means you can start remote development as we discussed earlier in this tutorial.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747933925369/a2aaf412-e465-47e9-8e97-129734f87534.png" alt="A screenshot of Visual Studio Code's welcome screen. The interface lists options to open a folder or clone a repository. The &quot;Start&quot; section has options like &quot;New File&quot; and &quot;Open Folder.&quot; The &quot;Recent&quot; section displays a list of recently accessed projects. The &quot;Walkthroughs&quot; area suggests getting started guides. The sidebar on the left shows file explorer and other icons. The bottom status bar indicates an SSH connection." class="image--center mx-auto" width="1919" height="1006" loading="lazy"></p>
<h2 id="heading-how-to-write-and-run-the-code-remotely">How to Write and Run the Code Remotely</h2>
<p>Create a new file on your VS Code. This way, you’re creating files and writing to them directly. Go to the terminal and type the commands to create a folder and a file:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747934210225/a6e7f454-6f65-4a87-8692-cadaa642b007.png" alt="Screenshot of Visual Studio Code showing a terminal session and a text editor. The terminal is open at the bottom, with commands for creating a directory, navigating to it, and opening it in the editor. The main editor area prompts to select a language or open a different editor. The Explorer sidebar is visible on the left." class="image--center mx-auto" width="1916" height="880" loading="lazy"></p>
<h3 id="heading-create-a-new-file-and-write-in-your-code"><strong>Create a new file and write in your code</strong></h3>
<p>Create a new file and name it <code>led.py</code> on your Visual Studio Code. It should be in the same folder as <code>test-raspberry</code> on the Raspberry Pi remote network through the SSH connection on VSCode.</p>
<p>Once you have your file created, you can write in your code such as blinking LED to a Raspberry Pi, as you can see in the code below:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> gpiozero <span class="hljs-keyword">import</span> LED
<span class="hljs-keyword">from</span> time <span class="hljs-keyword">import</span> sleep

<span class="hljs-comment"># Set the GPIO pin where the LED is connected</span>
led = LED(<span class="hljs-number">17</span>)  <span class="hljs-comment"># Replace 17 with your GPIO pin number</span>

<span class="hljs-comment"># Blink the LED in a loop</span>
<span class="hljs-keyword">while</span> <span class="hljs-literal">True</span>:
    led.on()        <span class="hljs-comment"># Turn LED on</span>
    sleep(<span class="hljs-number">1</span>)        <span class="hljs-comment"># Wait for 1 second</span>
    led.off()       <span class="hljs-comment"># Turn LED off</span>
    sleep(<span class="hljs-number">1</span>)        <span class="hljs-comment"># Wait for 1 second</span>
</code></pre>
<p>After writing this code in the new file you’ve created, run the code by typing the command below in your terminal:</p>
<pre><code class="lang-bash">python led.py
</code></pre>
<p>As soon as this command is sent, the LED positive terminal is connected to the GPIO 17 according to the code and the negative terminal is connected to the GND GPIO pin of the Raspberry Pi. The image from <a target="_blank" href="https://randomnerdtutorials.com/raspberry-pi-pinout-gpios/">Random Nerd Tutorials</a> below shows the GPIO pins and their number to understand the connection. Just note that the connection of the LED is beyond the scope of this tutorial.</p>
<p><img src="https://i0.wp.com/randomnerdtutorials.com/wp-content/uploads/2023/03/Raspberry-Pi-Pinout-Random-Nerd-Tutorials.png?quality=100&amp;strip=all&amp;ssl=1" alt="Raspberry Pi Pinout Guide: How to use the Raspberry Pi GPIOs? | Random Nerd  Tutorials" width="1280" height="720" loading="lazy"></p>
<p>The LED should start blinking each second according to the code. With this, you can now control your Raspberry Pi (a tiny computer) with another computer (your personal computer) through an SSH connection on Visual Studio Code.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this tutorial, you went through the whole process of setting up a headless Raspberry Pi for remote development using VS Code.</p>
<p>This offers a wide range of benefits: there’s no need for external peripherals, it provides remote access from anywhere within your network, and it leverages efficient coding and debugging with VS Code integration.</p>
<p>You can use this to deploy web servers and IoT dashboards, and you can explore with automating processes using Python scripts and GPIO control.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Set Up a Home VPN Using Tailscale on a Raspberry Pi ]]>
                </title>
                <description>
                    <![CDATA[ In this article, you’ll learn how to set up a VPN which you can host on a Raspberry Pi. I am a fan of Raspberry Pis because these small form factor computers are a favourite tool for tinkerers, like me. This VPN will allow you to access your home net... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/set-up-a-home-vpn-using-tailscale-on-a-raspberry-pi/</link>
                <guid isPermaLink="false">67e6c11d423cd4f90a6350ab</guid>
                
                    <category>
                        <![CDATA[ iot ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Raspberry Pi ]]>
                    </category>
                
                    <category>
                        <![CDATA[ vpn ]]>
                    </category>
                
                    <category>
                        <![CDATA[ networking ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Homelab ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Daniel Anomfueme ]]>
                </dc:creator>
                <pubDate>Fri, 28 Mar 2025 15:32:45 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1743175949441/1a8c4705-556c-4a1f-899a-9ac8e968fdc3.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this article, you’ll learn how to set up a VPN which you can host on a Raspberry Pi. I am a fan of Raspberry Pis because these small form factor computers are a favourite tool for tinkerers, like me.</p>
<p>This VPN will allow you to access your home network from anywhere as if you’re still at home. So why is this useful, you might ask? Well, it allows you to use your home network IP, no matter where you are, which is a good for privacy.</p>
<p>In this article, we’ll use <a target="_blank" href="https://github.com/tailscale/tailscale">Tailscale</a>, an open-source mesh VPN (Virtual Private Network) service that streamlines connecting devices and services securely across different networks. It enables encrypted point-to-point connections using the open-source <a target="_blank" href="https://www.wireguard.com/">WireGuard</a> protocol. This means that only devices on your private network can communicate with each other.</p>
<h3 id="heading-table-of-contents">Table of Contents</h3>
<ul>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-install-raspberry-pi-os-lite-32-bit">Install Raspberry Pi OS Lite (32-bit)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-boot-the-raspberry-pi">Boot The Raspberry Pi</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-ssh-into-the-raspberry-pi-and-login">SSH Into The Raspberry Pi and Login</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-install-tailscale-on-raspberry-pi">Install Tailscale on Raspberry Pi</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-key-expiry">Key Expiry</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-configuring-the-raspberry-pi-as-an-exit-node">Configuring the Raspberry Pi as an Exit Node</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h3 id="heading-prerequisites">Prerequisites</h3>
<ul>
<li><p>Raspberry Pi (I am working with a Raspberry Pi 5)</p>
</li>
<li><p><a target="_blank" href="https://www.raspberrypi.com/software/">Raspberry Pi Imager</a></p>
</li>
<li><p>A Micro SD Card (8GB is enough)</p>
</li>
<li><p>A Micro SD Card reader for your computer.</p>
</li>
<li><p>Home Router</p>
</li>
<li><p>A <a target="_blank" href="https://tailscale.com/">Tailscale</a> account</p>
</li>
</ul>
<h2 id="heading-install-raspberry-pi-os-lite-32-bit">Install Raspberry Pi OS Lite (32-bit)</h2>
<p>We’ll start this process by installing the Raspberry Pi OS Lite (32-bit) on the micro SD card we have. We will be making use of the Raspberry Pi Imager software which is available for free <a target="_blank" href="https://www.raspberrypi.com/software/">here</a>.</p>
<p>When you run the imager software, pick the Raspberry Pi Device, which for me is a Raspberry Pi 5.</p>
<p>Then in Operating System, click on Raspberry Pi OS (other), then scroll down to Raspberry Pi OS Lite (32-bit)</p>
<p>Next, select your SD card which you have inserted into the card reader, and the card reader into the computer. Your screen should look similar to what you see below. Click on next.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742929198415/b3cd3476-ed82-4db3-9472-f13df2207ca9.png" alt="A Screenshot of the Raspberry Pi Imager software start menu." class="image--center mx-auto" width="751" height="538" loading="lazy"></p>
<p>After next, you should see a pop-up asking if you would like to apply OS customisation settings.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742929274780/4482dd16-8f42-41ec-b1cd-af288180adcb.png" alt="A screenshot of the Raspberry Pi Imager software customisation prompt menu" class="image--center mx-auto" width="703" height="497" loading="lazy"></p>
<p>Next, click on edit settings. Enable set hostname and write the name you want to give the Pi. For this tutorial, I will be using <code>dapivpn</code><em>.</em> Then enable set username and password. Pick a username and a strong and secure password</p>
<p>You can enable configure wireless LAN if you plan to use Wifi, but if you are team Ethernet cable, you can skip this. I will be using WiFi in this tutorial though.</p>
<p>Now you’ll need to enable set local settings and pick your correct time zone and keyboard layout.</p>
<p>After that, go to the Services tab, then enable SSH and click on “Use password authentication”. Then click save, then yes on the apply customisation screen, and yes again. Remember this will erase all the data on the SD card, so make sure you’re using one without any important files on it.</p>
<p>This is how your Raspberry Pi Imager should look now:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742929363470/0c7663d4-a908-4be1-9865-caa665a2ee95.png" alt="A screenshot of the Raspberry Pi Imager software performing the write operation." class="image--center mx-auto" width="721" height="513" loading="lazy"></p>
<h3 id="heading-boot-the-raspberry-pi">Boot the Raspberry Pi</h3>
<p>After this is done, take the SD card and insert it into your Raspberry Pi. Then plug the power cable into the Raspberry Pi and wait some minutes for it to boot properly. You will know it is ready when the green LED light stays on.</p>
<p>Now you should go to your router and set a static IP to the Raspberry Pi. For mine, I set it to <code>192.168.8.21</code><em>.</em></p>
<h3 id="heading-ssh-into-the-raspberry-pi-and-login">SSH into the Raspberry Pi and Login</h3>
<p>Open up your command line terminal. Type “<code>ssh &lt;pi username&gt;@&lt;raspberry_pi_ip_address&gt;</code>”. For me, this would be:</p>
<pre><code class="lang-bash">ssh danpi@192.168.8.21
</code></pre>
<p>Then type in the password you used. You should see your username and the Pi hostname and this confirms you have logged in successfully to it.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743088985613/480325b2-496c-4161-96c6-f150f4020922.png" alt="Command line interface showing a successful SSH process" class="image--center mx-auto" width="747" height="382" loading="lazy"></p>
<p>Type in:</p>
<pre><code class="lang-bash">sudo apt update &amp;&amp; sudo apt upgrade -y
</code></pre>
<p>You run this command to make sure everything is up to date locally.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742929744252/6200841f-98bb-4bfa-8c30-38159a963e2b.png" alt="Command line interface showing the update command running" class="image--center mx-auto" width="748" height="226" loading="lazy"></p>
<p>Now reboot your Pi after this by typing:</p>
<pre><code class="lang-bash">sudo reboot
</code></pre>
<h2 id="heading-install-tailscale-on-raspberry-pi">Install Tailscale on Raspberry Pi</h2>
<p>Now you’re going to add Tailscale’s package signing key and repository.</p>
<pre><code class="lang-bash">curl -fsSL https://pkgs.tailscale.com/stable/debian/bookworm.noarmor.gpg | sudo tee /usr/share/keyrings/tailscale-archive-keyring.gpg &gt;/dev/null 
curl -fsSL https://pkgs.tailscale.com/stable/debian/bookworm.tailscale-keyring.list | sudo tee /etc/apt/sources.list.d/tailscale.list
</code></pre>
<p>Install Tailscale using these commands:</p>
<pre><code class="lang-bash">sudo apt-get update
sudo apt-get install tailscale
</code></pre>
<p>Next, you need to connect your Pi to your Tailscale network and authenticate. You can do that with the following command:</p>
<pre><code class="lang-bash">sudo tailscale up
</code></pre>
<p>Your browser should look like this.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742929786462/4d17cfae-0e87-449f-ac13-413a65f3f338.png" alt="Screenshot of the browser showing the authentication screen" class="image--center mx-auto" width="695" height="262" loading="lazy"></p>
<p>To locate the Tailscale IPv4 address for the Raspberry Pi, run this command:</p>
<pre><code class="lang-bash">tailscale ip -4
</code></pre>
<p>You can also see it on the Tailscale dashboard in your browser.</p>
<p>At this point, you’re done installing Tailsacle and you just need to do some finishing touches.</p>
<h2 id="heading-key-expiry">Key Expiry</h2>
<p>There is something you need to know when it comes to adding a device to Tailsacle. By default, and as a security feature, Tailscale requires devices to re-authenticate after a certain period of time has elapsed, usually 180 days.</p>
<p>If the re-authentication does not occur, keys expire and the connection stops working. It’s up to you to choose what you prefer, as this is a security feature that comes with some inconvenience.</p>
<p>I will be disabling the key expiry on the Raspberry Pi, as I fully trust it. To do this, you need to:</p>
<ul>
<li><p>Open the <a target="_blank" href="https://login.tailscale.com/admin/machines">Machines</a> page of the Tailscale admin console.</p>
</li>
<li><p>Find the Raspberry Pi on the row and select the option menu there.</p>
</li>
<li><p>Click on the Disable Key Expiry option. You should see an Expiry Disable label below the machine name.</p>
</li>
</ul>
<h2 id="heading-how-to-configure-the-raspberry-pi-as-an-exit-node">How to Configure the Raspberry Pi as an Exit Node</h2>
<p>Another thing you’ll need to know about when it comes to Tailscale is what an exit node is. A Tailscale exit node is a designated device in your Tailscale network that routes all of your internet traffic through it. No matter where you are, once you have this device activated as an exit node, when you turn on Tailscale, it routes your internet traffic through the device.</p>
<p>Ideally, you want a device that is powered on 24/7 to serve as your exit node. That’s why we are picking the Raspberry Pi, as it is a low-powered computer.</p>
<p>We are already 90% of the way, as we have Tailscale running on our Pi. Remember to also have Tailscale installed on as many devices on your local network as possible. What’s left is to allow your Pi to act as an exit node, so all your internet traffic or LAN traffic routes through it, giving you access to:</p>
<ul>
<li><p>Local network devices at home</p>
</li>
<li><p>Your home public IP</p>
</li>
<li><p>Internal services like NAS, printers, cameras, and so on</p>
</li>
</ul>
<p>To do this, SSH into your Raspberry Pi and follow these steps:</p>
<ul>
<li><p>Enable IP Forwarding. IP forwarding allows your Raspberry Pi to pass traffic between its network interfaces. Run the commands below line by line:</p>
<pre><code class="lang-bash">  <span class="hljs-built_in">echo</span> <span class="hljs-string">"net.ipv4.ip_forward=1"</span> | sudo tee -a /etc/sysctl.conf

  <span class="hljs-built_in">echo</span> <span class="hljs-string">"net.ipv6.conf.all.forwarding=1"</span> | sudo tee -a /etc/sysctl.conf

  sudo sysctl -p /etc/sysctl.conf
</code></pre>
</li>
<li><p>Advertise the Raspberry Pi as an exit node:</p>
<pre><code class="lang-bash">  sudo tailscale up --advertise-exit-node
</code></pre>
</li>
<li><p>Open the <a target="_blank" href="https://login.tailscale.com/admin/machines">Machines</a> page of the Tailscale admin console.</p>
</li>
<li><p>Find the Raspberry Pi on the row. You should see an Exit Node label on its name.</p>
</li>
<li><p>Click on the options menu there and select Edit Route Settings.</p>
</li>
<li><p>Check the box for Use as an exit node, then save.</p>
</li>
</ul>
<p>Now you should see the option of routing the internet through an exit node when you open up your Tailscale app on mobile or PC or anywhere you have it installed. When you see that option, you will also see the Raspberry Pi as an exit node option. You can also add more devices as an exit node if you want more options.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Using the Tailscale app on other devices, you can now route traffic securely through the Raspberry Pi by selecting it as an exit node. Tailscale also provides clear, <a target="_blank" href="https://tailscale.com/kb/1408/quick-guide-exit-nodes#use-an-exit-node">step-by-step guides</a> tailored to each device type for setting up and using an exit node.</p>
<p>You can now be away from your home internet but still connect to the internet as if you were home. See you next time.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Provision a Nexus Sonatype OSS on an Orange PI 5 with Ansible ]]>
                </title>
                <description>
                    <![CDATA[ Nexus 3 OSS is an Open Source artifact repository manager that can handle multiple formats like container images, Python PIP, Java jar, and many others. Why have an on-premise artifact manager? There are many reasons for it: Use your private infrast... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/provision-nexus-sonatype-oss-on-an-orange-pi-5-with-ansible/</link>
                <guid isPermaLink="false">66d85148f20d0925f8515b0d</guid>
                
                    <category>
                        <![CDATA[ hardware ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Linux ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Raspberry Pi ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Jose Vicente Nunez ]]>
                </dc:creator>
                <pubDate>Fri, 05 May 2023 21:34:24 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/05/cropped_museum.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Nexus 3 OSS is an <a target="_blank" href="https://github.com/sonatype/nexus-public">Open Source</a> <a target="_blank" href="https://www.sonatype.com/products/repository-oss">artifact repository manager</a> that can handle multiple formats like container images, Python PIP, Java jar, and many others.</p>
<p>Why have an on-premise artifact manager? There are many reasons for it:</p>
<ul>
<li><p>Use your private infrastructure: You may have proprietary code that needs to be safeguarded.</p>
</li>
<li><p>Faster artifact download speeds: If you constantly download the same artifacts over the Internet, you can cache them on a central location, for the benefit of your multiple users across multiple servers by caching them.</p>
</li>
<li><p>Control what artifacts make it to your build chain: Centralize the location of the artifacts, ensure they are approved for usage, and also confirm than they do not contain malicious code.</p>
</li>
<li><p>Segregate who can have access to your artifacts: You may have more strict requirements on who can access some artifacts within your own organization.</p>
</li>
</ul>
<p>In this article I will show you how you can download, install, and configure the OSS version of Nexus 3 using an Ansible playbook.</p>
<p>Nexus 3 will run on an <a target="_blank" href="http://www.orangepi.org/html/hardWare/computerAndMicrocontrollers/details/Orange-Pi-5.html">Orange PI 5 computer with 8 GB or RAM</a>, but this provisioning can be done on any machine with the <a target="_blank" href="https://help.sonatype.com/repomanager3/product-information/sonatype-nexus-repository-system-requirements">minimum requirements</a>. Part of the setup will consist of setting a proxy for <a target="_blank" href="https://pypi.org/">PyPI.org</a>, for the machines listed on my <a target="_blank" href="https://tutorials.kodegeek.com/Nexus3OnOrangePI/ansible/inventories/home/hosts.yaml">inventory</a> file.</p>
<h2 id="heading-what-you-need-to-run-the-code-from-this-tutorial">What you need to run the code from this tutorial</h2>
<ol>
<li><p>An Internet connection to download the <a target="_blank" href="https://github.com/josevnz/Nexus3OnOrangePI">source code</a> for the Ansible playbook, Nexus, and PIP modules</p>
</li>
<li><p>Two or more Linux machines (I used <a target="_blank" href="https://raspi.debian.net/">Debian</a>, <a target="_blank" href="https://www.armbian.com/orangepi-5/">Armbian</a> and <a target="_blank" href="https://getfedora.org/iot/">Fedora IOT</a>), with at least 8 GB of RAM. My cluster has a mix of Raspberry PI 4 and an OrangePI 5.</p>
</li>
<li><p>Ansible controller will run on the Fedora machine, but any server can be the controller. <a target="_blank" href="https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html">Installation instructions for Ansible</a> are easy to follow.</p>
</li>
</ol>
<h2 id="heading-playbook-organization">Playbook Organization</h2>
<p>I divided the tasks in groups and the resulting playbook looks like this:</p>
<pre><code class="lang-shell">[josevnz@dmaf5 Nexus3OnOrangePI]$ tree -N ansible/
ansible/
├── inventories
│   └── home
│       └── hosts.yaml
├── roles
│   ├── clients
│   │   ├── tasks
│   │   │   └── main.yaml
│   │   └── templates
│   │       └── pip.conf.j2
│   └── nexus
│       ├── files
│       │   └── swagger.json
│       ├── tasks
│       │   ├── download.yaml
│       │   ├── install.yaml
│       │   ├── main.yaml
│       │   ├── post_install.yaml
│       │   ├── pre_install.yaml
│       │   ├── repositories.yaml
│       │   ├── third_party.yaml
│       │   └── user.yaml
│       └── templates
│           ├── logrotate.nexus3.j2
│           ├── nexus3.service.j2
│           ├── nexus.rc.j2
│           └── nexus.vmoptions.j2
├── site.yaml
├── vars
│   ├── clients.yaml
│   └── nexus.yaml
└── vault
    ├── nexus_password.enc
    └── README.md

13 directories, 21 files
</code></pre>
<p>Now a little bit of explaining:</p>
<ul>
<li><p>There are two roles: ‘nexus’ and ‘clients’. The nexus role is used to setup the artifact management software, while the client role sets up the <a target="_blank" href="https://docs.python.org/3/installing/index.html">pip</a> settings on every machine.</p>
</li>
<li><p>Vars contains variables used on each role, separated by files to make their usage more clear</p>
</li>
<li><p>We have passwords, and we managed them using <a target="_blank" href="https://docs.ansible.com/ansible/latest/cli/ansible-vault.html">Ansible vault</a> feature.</p>
</li>
<li><p>The file ‘<a target="_blank" href="https://tutorials.kodegeek.com/Nexus3OnOrangePI/ansible/site.yaml">site.yaml</a>’ Orchestrates the role execution:</p>
</li>
</ul>
<pre><code class="lang-yaml"><span class="hljs-bullet">-</span> <span class="hljs-attr">hosts:</span> <span class="hljs-string">all</span>
  <span class="hljs-attr">tags:</span> <span class="hljs-string">clients</span>
  <span class="hljs-attr">vars_files:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">vars/clients.yaml</span>
  <span class="hljs-attr">roles:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">clients</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">hosts:</span> <span class="hljs-string">nexus_server</span>
  <span class="hljs-attr">tags:</span> <span class="hljs-string">nexus</span>
  <span class="hljs-attr">become_user:</span> <span class="hljs-string">root</span>
  <span class="hljs-attr">become:</span> <span class="hljs-literal">true</span>
  <span class="hljs-attr">vars_files:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">vars/nexus.yaml</span>
  <span class="hljs-attr">roles:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">nexus</span>
</code></pre>
<p>Now let’s move on to see the universe where the playbook will be executed.</p>
<h2 id="heading-the-host-inventory">The Host Inventory</h2>
<p>In my case it is quite simple – I have two main groups: ‘clients’ and the machine where the Nexus 3 server itself will run:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">all:</span>
  <span class="hljs-attr">children:</span>
    <span class="hljs-attr">nexus_server:</span>
      <span class="hljs-attr">hosts:</span>
        <span class="hljs-attr">orangepi5.home:</span>
    <span class="hljs-attr">home_lab:</span>
      <span class="hljs-attr">hosts:</span>
        <span class="hljs-attr">dmaf5.home:</span>
        <span class="hljs-attr">raspberrypi.home:</span>
        <span class="hljs-attr">orangepi5.home:</span>
</code></pre>
<p>The next important task is to download and configure Nexus 3.</p>
<h2 id="heading-how-to-install-nexus-3">How to Install Nexus 3</h2>
<p>The file <a target="_blank" href="https://tutorials.kodegeek.com/Nexus3OnOrangePI/ansible/roles/nexus/tasks/main.yaml">main.yaml</a> describes the order and purpose of each installation task for the Nexus role:</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># Tasks listed here are related to the remote Nexus 3 server</span>
<span class="hljs-comment"># Included tasks are called in order</span>
<span class="hljs-meta">---</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">include_tasks:</span> <span class="hljs-string">third_party.yaml</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">include_tasks:</span> <span class="hljs-string">pre_install.yaml</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">include_tasks:</span> <span class="hljs-string">download.yaml</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">include_tasks:</span> <span class="hljs-string">install.yaml</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">include_tasks:</span> <span class="hljs-string">post_install.yaml</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">include_tasks:</span> <span class="hljs-string">user.yaml</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">include_tasks:</span> <span class="hljs-string">repositories.yaml</span>
</code></pre>
<p>Let’s see first what I like to call the “core tasks”:</p>
<ol>
<li><p><a target="_blank" href="https://tutorials.kodegeek.com/Nexus3OnOrangePI/ansible/roles/nexus/tasks/third_party.yaml">third_party.yaml</a>: In here we install the OpenJDK8 (Nexus 3 is written in Java) and logrotate to take care of the stale logs.</p>
</li>
<li><p><a target="_blank" href="https://tutorials.kodegeek.com/Nexus3OnOrangePI/ansible/roles/nexus/tasks/pre_install.yaml">pre_install.yaml</a>: A lot happens here, like creating required directories for nexus, dedicated non-privileged user that will run the process.</p>
</li>
<li><p><a target="_blank" href="https://tutorials.kodegeek.com/Nexus3OnOrangePI/ansible/roles/nexus/tasks/download.yaml">download.yaml</a>: As the name says, we get a fresh version of the Nexus 3 OSS software and make sure it has the right checksum. We don’t want to install malware from the Internet.</p>
</li>
</ol>
<p>Then come the tasks that fall into the “customized installation group”:</p>
<ol>
<li><p><a target="_blank" href="https://tutorials.kodegeek.com/Nexus3OnOrangePI/ansible/roles/nexus/tasks/install.yaml">install.yaml</a>: Unpack the software, prepare the systemd unit to start it automatically, setup JVM settings for Nexus, and deploy the logrotate configuration.</p>
</li>
<li><p><a target="_blank" href="https://tutorials.kodegeek.com/Nexus3OnOrangePI/ansible/roles/nexus/tasks/post_install.yaml">post_install.yaml</a>: Exciting stuff happens here – the software is installed, and we run it for the first time. We also change the default password <a target="_blank" href="https://help.sonatype.com/repomanager3/integrations/rest-and-integration-api">using the REST API</a>, so we can move to the customization stage.</p>
</li>
<li><p><a target="_blank" href="https://tutorials.kodegeek.com/Nexus3OnOrangePI/ansible/roles/nexus/tasks/user.yaml">user.yaml</a>: Here we prepare to provide our end users with proper access to the services offered by Nexus. We do this using a combination of the REST-API and Ansible client code:</p>
</li>
</ol>
<pre><code class="lang-yaml"><span class="hljs-comment"># https://help.sonatype.com/repomanager3/installation-and-upgrades/post-install-checklist</span>
<span class="hljs-comment"># https://help.sonatype.com/repomanager3/integrations/rest-and-integration-api</span>
<span class="hljs-meta">---</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Enable</span> <span class="hljs-string">anonymous</span> <span class="hljs-string">user</span>
  <span class="hljs-attr">tags:</span> <span class="hljs-string">anonymous</span>
  <span class="hljs-attr">ansible.builtin.uri:</span>
    <span class="hljs-attr">user:</span> <span class="hljs-string">""</span>
    <span class="hljs-attr">password:</span> <span class="hljs-string">""</span>
    <span class="hljs-attr">url:</span> <span class="hljs-string">"/v1/security/anonymous"</span>
    <span class="hljs-attr">method:</span> <span class="hljs-string">PUT</span>
    <span class="hljs-attr">body_format:</span> <span class="hljs-string">raw</span>
    <span class="hljs-attr">status_code:</span> [ <span class="hljs-number">200</span>, <span class="hljs-number">202</span>, <span class="hljs-number">204</span> ]
    <span class="hljs-attr">headers:</span>
      <span class="hljs-attr">Content-Type:</span> <span class="hljs-string">application/json</span>
    <span class="hljs-attr">body:</span> <span class="hljs-string">|-
      { "enabled" : true, "userId" : "anonymous", "realmName" : "NexusAuthorizingRealm" }
</span>    <span class="hljs-attr">force_basic_auth:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">return_content:</span> <span class="hljs-literal">true</span>
  <span class="hljs-attr">any_errors_fatal:</span> <span class="hljs-literal">true</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Enable</span> <span class="hljs-string">Docker</span> <span class="hljs-string">security</span> <span class="hljs-string">realm</span>
  <span class="hljs-attr">tags:</span> <span class="hljs-string">docker_realm</span>
  <span class="hljs-attr">ansible.builtin.uri:</span>
    <span class="hljs-attr">user:</span> <span class="hljs-string">""</span>
    <span class="hljs-attr">password:</span> <span class="hljs-string">""</span>
    <span class="hljs-attr">url:</span> <span class="hljs-string">"/v1/security/realms/active"</span>
    <span class="hljs-attr">method:</span> <span class="hljs-string">PUT</span>
    <span class="hljs-attr">body_format:</span> <span class="hljs-string">raw</span>
    <span class="hljs-attr">status_code:</span> [ <span class="hljs-number">200</span>, <span class="hljs-number">202</span>, <span class="hljs-number">204</span> ]
    <span class="hljs-attr">headers:</span>
      <span class="hljs-attr">Content-Type:</span> <span class="hljs-string">application/json</span>
    <span class="hljs-attr">body:</span> <span class="hljs-string">|-
      [ "NexusAuthenticatingRealm", "NexusAuthorizingRealm", "DockerToken" ]
</span>    <span class="hljs-attr">force_basic_auth:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">return_content:</span> <span class="hljs-literal">true</span>
  <span class="hljs-attr">any_errors_fatal:</span> <span class="hljs-literal">true</span>
</code></pre>
<p>The logic is easy to follow, by using the ‘PUT’ http method you can tell is a modification operation (meaning existing roles and users already exist). Error detection is done by getting the HTTP codes returned by Nexus.</p>
<p>Next step is to prepare our local PyPi proxy. This is a multistep task and will be described in detail next.</p>
<h2 id="heading-how-to-set-up-pypi-proxy-on-nexus-3">How to Set Up PyPI Proxy on Nexus 3</h2>
<p>The last file on the Nexus 3 role is ‘<a target="_blank" href="https://tutorials.kodegeek.com/Nexus3OnOrangePI/ansible/roles/nexus/tasks/repositories.yaml">repositories.yaml</a>’. In here we go through the following steps:</p>
<ol>
<li><p>Check if the proxy was already setup (GET or read only operation)</p>
</li>
<li><p>If it doesn’t exist, create a new one (POST method with JSON payload with details to create whole new repository)</p>
</li>
</ol>
<p>Notice than this playbook doesn’t offer the option to update repository settings. It is possible to do with the REST API, but I will leave that as an exercise to the reader.</p>
<p>The tasks to prepare the PyPi proxy are shown below:</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># Create proxy for repositories</span>
<span class="hljs-comment"># https://help.sonatype.com/repomanager3/integrations/rest-and-integration-api</span>
<span class="hljs-comment"># PyPi: https://pip.pypa.io/en/stable/user_guide/</span>
<span class="hljs-meta">---</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Check</span> <span class="hljs-string">if</span> <span class="hljs-string">the</span> <span class="hljs-string">PyPi</span> <span class="hljs-string">proxy</span> <span class="hljs-string">exists</span>
  <span class="hljs-attr">tags:</span> <span class="hljs-string">pypi_proxy_exists</span>
  <span class="hljs-attr">ansible.builtin.uri:</span>
    <span class="hljs-attr">user:</span> <span class="hljs-string">""</span>
    <span class="hljs-attr">password:</span> <span class="hljs-string">""</span>
    <span class="hljs-attr">url:</span> <span class="hljs-string">"/v1/repositories/pypi/proxy/python_proxy"</span>
    <span class="hljs-attr">method:</span> <span class="hljs-string">GET</span>
    <span class="hljs-attr">body_format:</span> <span class="hljs-string">raw</span>
    <span class="hljs-attr">status_code:</span> [ <span class="hljs-number">200</span>, <span class="hljs-number">202</span>, <span class="hljs-number">204</span>, <span class="hljs-number">404</span> ]
    <span class="hljs-attr">headers:</span>
      <span class="hljs-attr">Content-Type:</span> <span class="hljs-string">application/json</span>
    <span class="hljs-attr">force_basic_auth:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">return_content:</span> <span class="hljs-literal">true</span>
  <span class="hljs-attr">any_errors_fatal:</span> <span class="hljs-literal">true</span>
  <span class="hljs-attr">register:</span> <span class="hljs-string">python_local</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Create</span> <span class="hljs-string">PyPI</span> <span class="hljs-string">proxy</span>
  <span class="hljs-attr">tags:</span> <span class="hljs-string">pypi_proxy_create</span>
  <span class="hljs-attr">ansible.builtin.uri:</span>
    <span class="hljs-attr">user:</span> <span class="hljs-string">""</span>
    <span class="hljs-attr">password:</span> <span class="hljs-string">""</span>
    <span class="hljs-attr">url:</span> <span class="hljs-string">"/v1/repositories/pypi/proxy"</span>
    <span class="hljs-attr">method:</span> <span class="hljs-string">POST</span>
    <span class="hljs-attr">body_format:</span> <span class="hljs-string">raw</span>
    <span class="hljs-attr">status_code:</span> [ <span class="hljs-number">201</span> ]
    <span class="hljs-attr">headers:</span>
      <span class="hljs-attr">Content-Type:</span> <span class="hljs-string">application/json</span>
    <span class="hljs-attr">body:</span> <span class="hljs-string">|-
      {
        "name": "python_proxy",
        "online": true,
        "storage": {
          "blobStoreName": "default",
          "strictContentTypeValidation": true
        },
        "proxy": {
          "remoteUrl": "https://pypi.org/",
          "contentMaxAge": -1,
          "metadataMaxAge": 1440
        },
        "negativeCache": {
          "enabled": true,
          "timeToLive": 1440
        },
        "httpClient": {
          "blocked": false,
          "autoBlock": true,
          "connection": {
            "retries": 0,
            "timeout": 60,
            "enableCircularRedirects": false,
            "enableCookies": true,
            "useTrustStore": false
          }
        }
      }
</span>    <span class="hljs-attr">force_basic_auth:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">return_content:</span> <span class="hljs-literal">true</span>
  <span class="hljs-attr">any_errors_fatal:</span> <span class="hljs-literal">true</span>
  <span class="hljs-attr">when:</span> <span class="hljs-string">python_local.status</span> <span class="hljs-string">==</span> <span class="hljs-number">404</span>
</code></pre>
<p>We are almost there. Now we need to tell our PyPi clients than we should use our local Nexus and not the direct PyPi site to get our Python libraries.</p>
<h2 id="heading-how-to-set-the-clients">How to Set the Clients</h2>
<p>The clients role is much simpler and only requires deploying a <a target="_blank" href="https://tutorials.kodegeek.com/Nexus3OnOrangePI/ansible/roles/clients/templates/pip.conf.j2">template for pip.conf</a> with enough information to force the search on our new repository:</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># Tasks here are meant to be used on our clients user</span>
<span class="hljs-meta">---</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Create</span> <span class="hljs-string">installation</span> <span class="hljs-string">directory</span> <span class="hljs-string">for</span> <span class="hljs-string">pip.conf</span>
  <span class="hljs-attr">tags:</span> <span class="hljs-string">pip_basedir</span>
  <span class="hljs-attr">ansible.builtin.file:</span>
    <span class="hljs-attr">state:</span> <span class="hljs-string">directory</span>
    <span class="hljs-attr">path:</span> <span class="hljs-string">""</span>
    <span class="hljs-attr">owner:</span> <span class="hljs-string">""</span>
    <span class="hljs-attr">group:</span> <span class="hljs-string">""</span>
    <span class="hljs-attr">mode:</span> <span class="hljs-string">"u+rwx,go-rwx"</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Copy</span> <span class="hljs-string">pip.conf</span> <span class="hljs-string">file</span>
  <span class="hljs-attr">tags:</span> <span class="hljs-string">pip_copy</span>
  <span class="hljs-attr">ansible.builtin.template:</span>
    <span class="hljs-attr">src:</span> <span class="hljs-string">pip.conf.j2</span>
    <span class="hljs-attr">dest:</span> <span class="hljs-string">"/pip.conf"</span>
    <span class="hljs-attr">owner:</span> <span class="hljs-string">""</span>
    <span class="hljs-attr">group:</span> <span class="hljs-string">""</span>
    <span class="hljs-attr">mode:</span> <span class="hljs-string">u=rxw,g=r,o=r</span>
</code></pre>
<p>The resulting file gets deployed on ‘<em>~/.config/pip/pip.conf</em>’ of every machine:</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># https://pip.pypa.io/en/stable/topics/configuration/</span>
[<span class="hljs-string">global</span>]
<span class="hljs-string">timeout</span> <span class="hljs-string">=</span> <span class="hljs-number">60</span>
[<span class="hljs-string">install</span>]
<span class="hljs-string">index</span> <span class="hljs-string">=</span> <span class="hljs-string">http://orangepi5.home:8081/repository/python_proxy/pypi</span>
<span class="hljs-string">index-url</span> <span class="hljs-string">=</span> <span class="hljs-string">http://orangepi5.home:8081/repository/python_proxy/simple/</span>
<span class="hljs-string">trusted-host</span> <span class="hljs-string">=</span> <span class="hljs-string">orangepi5.home</span>
</code></pre>
<p>The file above shows an example of how the final version of the file will look once deployed on my cluster (yours will be different with the resolved URL).</p>
<p>It is time now to run the whole playbook and see what it looks like.</p>
<h2 id="heading-how-to-run-the-playbook">How to Run the Playbook</h2>
<p>To run the playbook, we pass a few arguments:</p>
<ol>
<li><p>The location of our host inventory</p>
</li>
<li><p>The location of the encrypted password file and a master file containing the master password to unlock the contents of the protected file</p>
</li>
<li><p>And finally the location of our main playbook file</p>
</li>
</ol>
<pre><code class="lang-shell">cd ansible
ansible-playbook --inventory  inventories --extra-vars @vault/nexus_password.enc --vault-password-file $HOME/vault/ansible_vault_pass site.yaml
</code></pre>
<p><a target="_blank" href="https://asciinema.org/a/579355"><img src="https://asciinema.org/a/579355.svg" alt="asciicast" width="600" height="400" loading="lazy"></a></p>
<h3 id="heading-how-to-test-the-new-pypi-proxy">How to test the new PyPI proxy</h3>
<p>To test our new proxy, we will install <a target="_blank" href="https://github.com/Textualize/rich">Python Rich</a> using pip and a virtual environment.</p>
<pre><code class="lang-shell">josevnz@orangepi5:~$ python3 -m venv ~/virtualenv/rich
(rich) josevnz@orangepi5:~$ . ~/virtualenv/rich/bin/activate
(rich) josevnz@orangepi5:~$ pip install rich
Looking in indexes: http://orangepi5.home:8081/repository/python_proxy/simple/
Collecting rich
  Downloading http://orangepi5.home:8081/repository/python_proxy/packages/rich/13.3.4/rich-13.3.4-py3-none-any.whl (238 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 238.7/238.7 KB 14.8 MB/s eta 0:00:00
Collecting pygments&lt;3.0.0,&gt;=2.13.0
  Downloading http://orangepi5.home:8081/repository/python_proxy/packages/pygments/2.15.0/Pygments-2.15.0-py3-none-any.whl (1.1 MB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.1/1.1 MB 23.8 MB/s eta 0:00:00
Collecting markdown-it-py&lt;3.0.0,&gt;=2.2.0
  Downloading http://orangepi5.home:8081/repository/python_proxy/packages/markdown-it-py/2.2.0/markdown_it_py-2.2.0-py3-none-any.whl (84 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 84.5/84.5 KB 6.9 MB/s eta 0:00:00
Collecting mdurl~=0.1
  Downloading http://orangepi5.home:8081/repository/python_proxy/packages/mdurl/0.1.2/mdurl-0.1.2-py3-none-any.whl (10.0 kB)
Installing collected packages: pygments, mdurl, markdown-it-py, rich
Successfully installed markdown-it-py-2.2.0 mdurl-0.1.2 pygments-2.15.0 rich-13.3.4
</code></pre>
<p>And then we can confirm than the cache was indeed used by seeing the new artifacts on the new repository:</p>
<p><img src="https://tutorials.kodegeek.com/Nexus3OnOrangePI/sonatype_browse_python_proxy.png" alt="New artifacts on the Python_proxy PyPI repository" width="841" height="315" loading="lazy"></p>
<p><em>See the PyPi artifacts</em></p>
<p>Let’s see a demo of the client in action, installing something else:</p>
<p><a target="_blank" href="https://asciinema.org/a/579357"><img src="https://asciinema.org/a/579357.svg" alt="asciicast" width="600" height="400" loading="lazy"></a></p>
<h2 id="heading-further-customization-using-the-rest-api">Further Customization Using the REST-API</h2>
<p>Every Nexus installation allows you to download a JSON file that describes the API supported by the server. For example, in my server you can get a copy like this from my orangepi5.home server:</p>
<pre><code class="lang-shell">curl --fail --remote-name http://orangepi5.home:8081/service/rest/swagger.json
</code></pre>
<p>Also, the UI allows you to try the other REST API endpoints to customize your installation.</p>
<p><img src="https://tutorials.kodegeek.com/Nexus3OnOrangePI/api-swagger.png" alt="API Swagger documentation on Nexus 3" width="1498" height="782" loading="lazy"></p>
<p><em>REST API testing</em></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>I recommend spending some time and reading the <a target="_blank" href="https://help.sonatype.com/repomanager3">Nexus 3 book</a> to get yourself familiar with the features this tool can offer.</p>
<p>The community prepared <a target="_blank" href="https://github.com/sonatype-nexus-community/nexus-repository-installer">Debian and RPM installers</a>, if you need this kind of setup as opposed to using Ansible.</p>
<p>Nexus 3 <em>has lots</em> of configurable settings. We covered only the surface here. While preparing this article I found '<a target="_blank" href="https://github.com/ansible-ThoTeam/nexus3-oss">ThoTeam Nexus3-oss repository</a>' with a very complete and up-to-date playbook, but it was way more complex than anything I required for my home lab.</p>
<p><a target="_blank" href="https://archiva.apache.org/">Archiva</a> is another Open Source artifact manager, it is more limited in functionality but also simpler to setup.</p>
<p>There is a <a target="_blank" href="https://help.sonatype.com/repomanager3/installation-and-upgrades/post-install-checklist">post-installation checklist</a> with some tasks I did not need to complete for my home lab. Please check it out to make sure your setup is complete.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Install a Wifi Adapter Driver on AML-S905X-CC (Le Potato) ]]>
                </title>
                <description>
                    <![CDATA[ If you're a developer, you might be familiar with Raspberry Pi. But you might not know about the Libre Computer AML-S905X-CC – also called Le Potato.  There was a chip shortage during the pandemic that has resulted in increased Raspberry Pi prices. O... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-install-wifi-adapter-driver-on-aml-s905x-cc-le-potato/</link>
                <guid isPermaLink="false">66ba10c4052fa53219e0a378</guid>
                
                    <category>
                        <![CDATA[ Raspberry Pi ]]>
                    </category>
                
                    <category>
                        <![CDATA[ wifi ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Arunachalam B ]]>
                </dc:creator>
                <pubDate>Wed, 30 Nov 2022 17:27:33 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/11/How-to-install-driver-for-external-wifi-adapter-in-Le-Potato-3.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>If you're a developer, you might be familiar with Raspberry Pi. But you might not know about the Libre Computer AML-S905X-CC – also called Le Potato. </p>
<p>There was a chip shortage during the pandemic that has resulted in increased Raspberry Pi prices. Other world events have also skyrocketed the price of Raspberry Pis, and the manufacturing of a few models has even been stopped. You can read more about it <a target="_blank" href="https://www.raspberrypi.com/news/supply-chain-shortages-and-our-first-ever-price-increase/">here</a>. </p>
<p>Because of this, I felt that switching to a Raspberry Pi alternative would be a good option for a project I wanted to work on.</p>
<p>Le Potato is similar to Raspberry Pi in terms of appearance, configuration, and so on. It also has the ability to run numerous operating systems such as Ubuntu, Debian, Raspbian, Android, and others. </p>
<p>But unfortunately, it does not come with a pre-installed wifi module, whereas Raspberry Pi has the wifi module pre-installed. </p>
<p>In this article, I'll give you clear step-by-step instructions to install an external wifi adapter driver in Le Potato running <strong>Ubuntu OS</strong>. For those who are running other operating systems, you can try the following steps, but I cannot assure you that it'll definitely work. </p>
<p>Let's have a quick look at my accessories. </p>
<h5 id="heading-heres-my-le-potato-device">Here's my Le Potato device:</h5>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/11/Le-Potato.jpeg" alt="Image" width="600" height="400" loading="lazy">
<em>Le Potato Device</em></p>
<h5 id="heading-and-heres-my-zebronics-external-wifi-adapter">And here's my Zebronics External Wifi Adapter:</h5>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/11/Wifi-Device-1.jpeg" alt="Image" width="600" height="400" loading="lazy">
<em>Zebronics External Wifi Adapter</em></p>
<h2 id="heading-trial-and-error-what-didnt-work">Trial and Error – What Didn't Work</h2>
<p>Before I found my final solution and ended up installing the wifi driver and being able to access the internet with my wifi adapter, I tried many approaches. But none of them worked out. </p>
<p>Here's what I tried along the way:</p>
<ol>
<li>I tried to install the driver given on the CD that was delivered along with the wifi adapter. But, I couldn't understand the steps they asked me to follow and finally ended up with a lot of errors.</li>
<li>I downloaded the exact driver for this device from the Zebronics official site. Again that did not pay off well.</li>
<li>I tried to install some open source drivers from GitHub forked by many people from the Realtek source. This also did not work out as expected.</li>
</ol>
<p>Finally, I found an answer from the <a target="_blank" href="https://askubuntu.com/questions/1415466/wireless-usb-apapter-not-show-any-available-networks">Ubuntu Q&amp;A forum</a> and I was able to install it on the first try. Though the steps were not so clear initially, I managed to figure them out. So I'll explain how to do it here. </p>
<h2 id="heading-how-to-install-the-wifi-adapter-driver-for-le-potato-in-ubuntu">How to Install the Wifi Adapter Driver for Le Potato in Ubuntu</h2>
<p>Follow the below steps to install the driver on your device:</p>
<h3 id="heading-install-the-dependencies">Install the dependencies</h3>
<p>The first step is to install the required software. </p>
<p>You need to install <code>git</code>, <code>dkms</code>, <code>build-essential</code>, and <code>linux-headers</code> for your system architecture. </p>
<p>You can install them all together in a single command:</p>
<pre><code class="lang-bash">sudo apt-get install -y build-essential git dkms linux-headers-$(uname -r)
</code></pre>
<p>If you have been prompted (yes/no) while running the above command, just hit <code>y</code> (which is basically agreeing to install the software in your system). </p>
<h3 id="heading-download-the-driver-source">Download the driver source</h3>
<p>Drivers for some devices will not be available in any installable/executable formats. In such cases, you should download, compile, and install the source code on the machine directly. Unfortunately, this driver also falls under this category. </p>
<p>We can download the source of this driver from GitHub. Run the following command in your terminal to download the source code:</p>
<pre><code class="lang-bash">git <span class="hljs-built_in">clone</span> https://github.com/kelebek333/rtl8188fu
</code></pre>
<h3 id="heading-build-and-install-the-driver">Build and install the driver</h3>
<p>Before building and installing the driver, you need to know about the <code>dkms</code> command in Linux. If you know about <code>dkms</code>, you can skip this paragraph and move on to the next paragraph. </p>
<p>DKMS stands for Dynamic Kernel Module Support. It's a program/framework that lets you install the supplementary versions of kernel modules. A package can be compiled and installed into the kernel tree. DKMS is called automatically upon installation of new Ubuntu kernel-image packages, and so modules added to DKMS will be automatically carried across updates. </p>
<p>This is the source package that we downloaded in the previous step. We need to add, compile, and install the source package into our kernel tree. </p>
<p>Run the following commands sequentially to add, compile and install the driver package:</p>
<h4 id="heading-add-source-to-kernel">Add Source to Kernel</h4>
<pre><code class="lang-bash">sudo dkms add ./rtl8188fu
</code></pre>
<h4 id="heading-compile-source-package">Compile source package</h4>
<pre><code class="lang-bash">sudo dkms build rtl8188fu/1.0
</code></pre>
<h4 id="heading-install-the-package-into-the-kernel-tree">Install the package into the kernel tree</h4>
<pre><code class="lang-bash">sudo dkms install rtl8188fu/1.0
</code></pre>
<h4 id="heading-copy-the-firmware">Copy the firmware</h4>
<p>The compiled binary firmware file should then be copied to the default firmware location in Linux, that is <code>/lib/firmware</code> . </p>
<p><strong>Firmware</strong> is software that enables the communication between hardware and software. It gives the machine instructions that make the hardware function. </p>
<p>Run the following command to copy the compiled firmware:</p>
<pre><code class="lang-bash">sudo cp ./rtl8188fu/firmware/rtl8188fufw.bin /lib/firmware/rtlwifi/
</code></pre>
<h3 id="heading-disable-power-saving-and-auto-suspend-modes-on-kernel">Disable power saving and auto suspend modes on kernel</h3>
<p>It is always a good idea to disable power saving and auto suspend modes for wifi drivers. So, you'll need to add this option by default on updating the kernel, too. You can add this configuration in the <code>.conf</code> file in <code>/etc/modprobe.d/</code> directory. </p>
<p>We are creating this conf file in <code>/etc/modprobe.d</code> directory, because we need to load this customized module with the persistent changes. </p>
<p>You use the <code>rtw_power_mgnt</code> flag to control power saving mode:</p>
<ul>
<li>0 - Disables power saving</li>
<li>1 - Power saving on with minPS</li>
<li>2 - Power saving on with maxPS</li>
</ul>
<p>You use the <code>rtw_enusbss</code> flag to control auto-suspend mode:</p>
<ul>
<li>0 - Disables auto suspend</li>
<li>1 - Enables auto suspend</li>
</ul>
<p>Run the following commands to create a <code>.conf</code> file and store the options:</p>
<pre><code class="lang-bash">sudo mkdir -p /etc/modprobe.d/
sudo touch /etc/modprobe.d/rtl8188fu.conf
<span class="hljs-built_in">echo</span> <span class="hljs-string">"options rtl8188fu rtw_power_mgnt=0 rtw_enusbss=0"</span> | sudo tee /etc/modprobe.d/rtl8188fu.conf
</code></pre>
<h3 id="heading-blacklist-the-existing-module">Blacklist the existing module</h3>
<p>You have to blacklist the module which you tried to install before. </p>
<p><strong>Note:</strong> Blacklisting a module will not allow it to be loaded automatically, but the module may be loaded if another non-blacklisted module depends on it or if it is loaded manually. </p>
<p>Let's assume you have added a module named <code>rtl8188au</code>. Then, you need to blacklist it by adding the following line at the end of the <code>/etc/modprobe.d/blacklist.conf</code> file. </p>
<pre><code class="lang-bash">blacklist rtl8188au
</code></pre>
<p>If you haven't added any such module, you can ignore the blacklisting part. </p>
<h3 id="heading-reload-the-module">Reload the module</h3>
<p>You need to reload the module to make it start working. </p>
<p>Here's the command to reload the module we added now:</p>
<pre><code class="lang-bash">sudo modprobe -rv rtl8188fu &amp;&amp; sudo modprobe -v rtl8188fu
</code></pre>
<p>And you're done! You should be able to see the wifi enabled on your Le Potato running Ubuntu OS. If you cannot see it, reboot your system and everything should be alright.  </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/11/image-98.png" alt="Image" width="600" height="400" loading="lazy">
<em>Trying to connect to a network after installing the driver</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/11/image-97.png" alt="Image" width="600" height="400" loading="lazy">
<em>Connected to my wifi network</em></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this article, we have gone through the steps to install the driver for our external wifi adapter. </p>
<p>These are the exact (basic) steps you need to follow to add any external module into your kernel. </p>
<p>Subscribe to my <a target="_blank" href="https://5minslearn.gogosoon.com/">newsletter</a> to receive more such insightful articles that get delivered straight to your inbox. </p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Embedded Rust Programming on Raspberry Pi Zero W ]]>
                </title>
                <description>
                    <![CDATA[ Embedded programming in Rust requires a whole new knowledge base. Using a Raspberry Pi Zero W, you can quickly get up and running with embedded Rust.  Starting with an embedded "Hello World" equivalent, and advancing to a text-to-morse-code translato... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/embedded-rust-programming-on-raspberry-pi-zero-w/</link>
                <guid isPermaLink="false">66ace42a106bc8d5a4eb36f6</guid>
                
                    <category>
                        <![CDATA[ embedded systems ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Raspberry Pi ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Rust ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Shaun Hamilton ]]>
                </dc:creator>
                <pubDate>Thu, 09 Jun 2022 15:37:48 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/06/rpi-rust.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Embedded programming in Rust requires a whole new knowledge base. Using a Raspberry Pi Zero W, you can quickly get up and running with embedded Rust. </p>
<p>Starting with an embedded <em>"Hello World"</em> equivalent, and advancing to a text-to-morse-code translator, this article will walk you through the process.</p>
<ul>
<li><a class="post-section-overview" href="#heading-how-to-set-up-the-pi">How to Set Up the Pi</a><ul>
<li><a class="post-section-overview" href="#heading-format-the-sd-card">Format the SD Card</a></li>
<li><a class="post-section-overview" href="#heading-flash-the-distribution">Flash the Distribution</a><ul>
<li><a class="post-section-overview" href="#heading-configure-wifi-and-ssh">Configure Wifi and SSH</a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-complete-the-circuit">Complete the Circuit</a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-how-to-set-up-cross-compilation">How to Set Up Cross Compilation</a><ul>
<li><a class="post-section-overview" href="#heading-install-the-target">Install the Target</a></li>
<li><a class="post-section-overview" href="#heading-specify-the-linker">Specify the Linker</a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-how-to-program-an-embedded-hello-world">How to Program an Embedded "Hello World"</a><ul>
<li><a class="post-section-overview" href="#heading-successfully-exit-the-program">Successfully Exit the Program</a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-how-to-cross-compile-the-program">How to Cross Compile the Program</a></li>
<li><a class="post-section-overview" href="#heading-how-to-transfer-the-binary-to-the-pi">How to Transfer the Binary to the Pi</a></li>
<li><a class="post-section-overview" href="#heading-how-to-ssh-into-the-pi">How to SSH into the Pi</a><ul>
<li><a class="post-section-overview" href="#heading-run-the-program">Run the Program</a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-how-to-code-a-text-to-morse-code-translator">How to Code a Text-to-Morse-Code Translator</a></li>
<li><a class="post-section-overview" href="#heading-appendix">Appendix</a><ul>
<li><a class="post-section-overview" href="#heading-targets-1">Targets</a></li>
</ul>
</li>
</ul>
<h2 id="heading-how-to-set-up-the-pi">How to Set Up the Pi</h2>
<h3 id="heading-format-the-sd-card">Format the SD Card</h3>
<p>Use the Raspberry Pi Imager which can be downloaded from the <a target="_blank" href="https://www.raspberrypi.com/software/">Raspberry Pi Software Webpage</a>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/06/rpi-imager.png" alt="rpi-imager" width="600" height="400" loading="lazy"></p>
<h3 id="heading-flash-the-distribution">Flash the Distribution</h3>
<p>A distribution I'd suggest is <a target="_blank" href="https://www.raspberrypi.com/software/operating-systems/">Raspberry Pi OS Lite</a>. This is a <em>headless</em> distribution, which means it does not come with a GUI.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/06/rpi-imager-os.png" alt="rpi-imager-os" width="600" height="400" loading="lazy"></p>
<h4 id="heading-configure-wifi-and-ssh">Configure Wifi and SSH</h4>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/06/rpi-imager-ssh.png" alt="rpi-imager-ssh" width="600" height="400" loading="lazy"></p>
<p>Once that is done, you can insert the SD card into the Raspberry Pi, and power it up.</p>
<h3 id="heading-complete-the-circuit">Complete the Circuit</h3>
<p><strong>Circuit Diagram</strong></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/06/rpi-circuit.png" alt="rpi-circuit" width="600" height="400" loading="lazy"></p>
<p><strong>Pi Pinout</strong></p>
<p>Connect negative to ground, and positive to BCM pin 17 as shown below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/06/rpi-pinout.png" alt="rpi-pinout" width="600" height="400" loading="lazy"></p>
<p>The pinout can be seen here: https://pinout.xyz/</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/06/IMG_3418-1-.JPG" alt="IMG_3418-1-" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-set-up-cross-compilation">How to Set Up Cross Compilation</h2>
<h3 id="heading-install-the-target">Install the Target</h3>
<p>Use <code>rustup</code> to install the necessary target for your Raspberry Pi:</p>
<pre><code class="lang-bash">my-pc$ rustup add target arm-unknown-linux-gnueabihf
</code></pre>
<p><a class="post-section-overview" href="#heading-targets-1">Appendix</a> for more information about targets in Rust.</p>
<h3 id="heading-specify-the-linker">Specify the Linker</h3>
<p>Download the <code>raspberrypi/tools</code> repository into a directory named <code>rpi_tools</code>:</p>
<pre><code class="lang-bash">my-pc:~ $ git <span class="hljs-built_in">clone</span> https://github.com/raspberrypi/tools <span class="hljs-variable">$HOME</span>/rpi_tools
</code></pre>
<p>Edit the <code>~/.cargo/config</code> file using your favourite text editor:</p>
<pre><code class="lang-bash">my_pc:~ $ sudo nano ~/.cargo/config
</code></pre>
<p>Tell Cargo to use a specific linker version for your target:</p>
<pre><code class="lang-conf">[target.arm-unknown-linux-gnueabihf]
linker = "/rpi_tools/arm-bcm2708/arm-rpi-4.9.3-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc"
</code></pre>
<h2 id="heading-how-to-program-an-embedded-hello-world">How to Program an Embedded "Hello World"</h2>
<p>Start by creating a new Rust project, and opening the <code>main.rs</code> file in your favourite text editor:</p>
<pre><code class="lang-bash">my-pc:~ $ cargo new blink
my-pc:~ $ <span class="hljs-built_in">cd</span> blink
my-pc:~/blink $ nano src/main.rs
</code></pre>
<p>Import the <code>rust_gpiozero</code> crate, and program an LED to alternate between on and off every second:</p>
<pre><code class="lang-rust"><span class="hljs-keyword">use</span> rust_gpiozero::*;

<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
    <span class="hljs-comment">// Create a new LED attached to Pin 17</span>
    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> led = LED::new(<span class="hljs-number">17</span>);

    led.blink(<span class="hljs-number">1.0</span>, <span class="hljs-number">1.0</span>);

    led.wait();
}
</code></pre>
<p>Be sure to add the dependency to the <code>Cargo.toml</code> file:</p>
<pre><code class="lang-toml"><span class="hljs-section">[dependencies]</span>
<span class="hljs-attr">rust-gpiozero</span> = <span class="hljs-string">"0.2.1"</span>
</code></pre>
<h3 id="heading-successfully-exit-the-program">Successfully Exit the Program</h3>
<p>Since <code>rustc 1.61.0</code> <sup>[<a target="_blank" href="https://doc.rust-lang.org/stable/std/process/struct.ExitCode.html">1</a>]</sup>, you can use the <code>std::process::ExitCode</code> struct to specify the status code returned to the process' parent:</p>
<pre><code class="lang-rust"><span class="hljs-keyword">use</span> std::process::ExitCode;
<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() -&gt; ExitCode {
    <span class="hljs-comment">// ...</span>
    <span class="hljs-keyword">if</span> error {
      <span class="hljs-keyword">return</span> ExitCode::from(<span class="hljs-number">1</span>);
    }
    ExitCode::SUCCESS
}
</code></pre>
<p>Otherwise, you can simply return a <code>Result</code>:</p>
<pre><code class="lang-rust"><span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() -&gt; <span class="hljs-built_in">Result</span>&lt;(), std::io::Error&gt; {
  <span class="hljs-comment">// ...</span>
  <span class="hljs-literal">Ok</span>(())
}
</code></pre>
<h2 id="heading-how-to-cross-compile-the-program">How to Cross Compile the Program</h2>
<p>Build a release of your program, targeting the required architecture:</p>
<pre><code class="lang-bash">my-pc:~/blink $ cargo build --release --target=arm-unknown-linux-gnueabihf
</code></pre>
<h2 id="heading-how-to-transfer-the-binary-to-the-pi">How to Transfer the Binary to the Pi</h2>
<p>Use <code>scp</code> to transfer the compiled binary from your host computer to the Raspberry Pi over SSH:</p>
<pre><code class="lang-bash">my-pc:~/blink $ scp target/arm-unknown-linux-gnueabihf/release/blink pi@192.168.1.199:~/blink
</code></pre>
<p><strong>Note:</strong> The local IP of your Pi will likely be different.</p>
<h2 id="heading-how-to-ssh-into-the-pi">How to SSH into the Pi</h2>
<p>SSH and log in to the Raspberry Pi via its local IP address:</p>
<pre><code class="lang-bash">my-pc:~ $ ssh pi@192.168.1.199
</code></pre>
<h3 id="heading-run-the-program">Run the Program</h3>
<p>From the Raspberry Pi, run the compiled binary:</p>
<pre><code class="lang-bash">pi:~ $ ./blink
</code></pre>
<h2 id="heading-how-to-code-a-text-to-morse-code-translator">How to Code a Text-to-Morse-Code Translator</h2>
<p>Here is an example of an application that reads the stdin line by line, translates the input into Morse Code, and toggles the LED on and off based on the Morse Code for the characters.</p>
<pre><code class="lang-rust"><span class="hljs-keyword">use</span> rust_gpiozero::*;
<span class="hljs-keyword">use</span> std::io::{BufRead, <span class="hljs-keyword">self</span>};
<span class="hljs-keyword">use</span> std::collections::HashMap;
<span class="hljs-keyword">use</span> std::thread::sleep;
<span class="hljs-keyword">use</span> std::time::Duration;

<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() -&gt; <span class="hljs-built_in">Result</span>&lt;(), std::io::Error&gt; {
    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"Starting...\n- Type in text to turn into Morse Code\n- Type `quit()` to quit\n"</span>);
    <span class="hljs-comment">// Create a new LED attached to Pin 17</span>
    <span class="hljs-keyword">let</span> led = LED::new(<span class="hljs-number">17</span>);

    <span class="hljs-comment">/// Length of a dot in milliseconds</span>
    <span class="hljs-keyword">const</span> DOT_DELAY: <span class="hljs-built_in">u64</span> = <span class="hljs-number">80</span>;
    <span class="hljs-comment">/// Length of a dash in milliseconds</span>
    <span class="hljs-keyword">const</span> DASH_DELAY: <span class="hljs-built_in">u64</span> = DOT_DELAY * <span class="hljs-number">3</span>;
    <span class="hljs-comment">/// Delay between inputs in milliseconds</span>
    <span class="hljs-keyword">const</span> PUSH_DELAY: <span class="hljs-built_in">u64</span> = DOT_DELAY;
    <span class="hljs-comment">/// Delay between letters in milliseconds</span>
    <span class="hljs-keyword">const</span> LETTER_DELAY: <span class="hljs-built_in">u64</span> = DOT_DELAY * <span class="hljs-number">3</span>;
    <span class="hljs-comment">/// Delay between words in milliseconds</span>
    <span class="hljs-keyword">const</span> WORD_DELAY: <span class="hljs-built_in">u64</span> = DOT_DELAY * <span class="hljs-number">7</span>;

    <span class="hljs-keyword">let</span> morse_code_alphabet: HashMap&lt;<span class="hljs-built_in">char</span>, &amp;<span class="hljs-symbol">'static</span> <span class="hljs-built_in">str</span>&gt; =
    [
        (<span class="hljs-string">'a'</span>, <span class="hljs-string">".-"</span>),
        (<span class="hljs-string">'b'</span>, <span class="hljs-string">"-..."</span>),
        (<span class="hljs-string">'c'</span>, <span class="hljs-string">"-.-."</span>),
        (<span class="hljs-string">'d'</span>, <span class="hljs-string">"-.."</span>),
        (<span class="hljs-string">'e'</span>, <span class="hljs-string">"."</span>),
        (<span class="hljs-string">'f'</span>, <span class="hljs-string">"..-."</span>),
        (<span class="hljs-string">'g'</span>, <span class="hljs-string">"--."</span>),
        (<span class="hljs-string">'h'</span>, <span class="hljs-string">"...."</span>),
        (<span class="hljs-string">'i'</span>, <span class="hljs-string">".."</span>),
        (<span class="hljs-string">'j'</span>, <span class="hljs-string">".---"</span>),
        (<span class="hljs-string">'k'</span>, <span class="hljs-string">"-.-"</span>),
        (<span class="hljs-string">'l'</span>, <span class="hljs-string">".-.."</span>),
        (<span class="hljs-string">'m'</span>, <span class="hljs-string">"--"</span>),
        (<span class="hljs-string">'n'</span>, <span class="hljs-string">"-."</span>),
        (<span class="hljs-string">'o'</span>, <span class="hljs-string">"---"</span>),
        (<span class="hljs-string">'p'</span>, <span class="hljs-string">".--."</span>),
        (<span class="hljs-string">'q'</span>, <span class="hljs-string">"--.-"</span>),
        (<span class="hljs-string">'r'</span>, <span class="hljs-string">".-."</span>),
        (<span class="hljs-string">'s'</span>, <span class="hljs-string">"..."</span>),
        (<span class="hljs-string">'t'</span>, <span class="hljs-string">"-"</span>),
        (<span class="hljs-string">'u'</span>, <span class="hljs-string">"..-"</span>),
        (<span class="hljs-string">'v'</span>, <span class="hljs-string">"...-"</span>),
        (<span class="hljs-string">'w'</span>, <span class="hljs-string">".--"</span>),
        (<span class="hljs-string">'x'</span>, <span class="hljs-string">"-..-"</span>),
        (<span class="hljs-string">'y'</span>, <span class="hljs-string">"-.--"</span>),
        (<span class="hljs-string">'z'</span>, <span class="hljs-string">"--.."</span>),
        (<span class="hljs-string">'1'</span>, <span class="hljs-string">".----"</span>),
        (<span class="hljs-string">'2'</span>, <span class="hljs-string">"..---"</span>),
        (<span class="hljs-string">'3'</span>, <span class="hljs-string">"...--"</span>),
        (<span class="hljs-string">'4'</span>, <span class="hljs-string">"....-"</span>),
        (<span class="hljs-string">'5'</span>, <span class="hljs-string">"....."</span>),
        (<span class="hljs-string">'6'</span>, <span class="hljs-string">"-...."</span>),
        (<span class="hljs-string">'7'</span>, <span class="hljs-string">"--..."</span>),
        (<span class="hljs-string">'8'</span>, <span class="hljs-string">"---.."</span>),
        (<span class="hljs-string">'9'</span>, <span class="hljs-string">"----."</span>),
        (<span class="hljs-string">'0'</span>, <span class="hljs-string">"-----"</span>),
        (<span class="hljs-string">'.'</span>, <span class="hljs-string">".-.-.-"</span>),
        (<span class="hljs-string">','</span>, <span class="hljs-string">"--..--"</span>),
        (<span class="hljs-string">'?'</span>, <span class="hljs-string">"..--.."</span>),
        (<span class="hljs-string">'\''</span>, <span class="hljs-string">".----."</span>),
        (<span class="hljs-string">'!'</span>, <span class="hljs-string">"-.-.--"</span>),
        (<span class="hljs-string">'/'</span>, <span class="hljs-string">"-..-."</span>),
        (<span class="hljs-string">'('</span>, <span class="hljs-string">"-.--."</span>),
        (<span class="hljs-string">')'</span>, <span class="hljs-string">"-.--.-"</span>),
        (<span class="hljs-string">'&amp;'</span>, <span class="hljs-string">".-..."</span>),
        (<span class="hljs-string">':'</span>, <span class="hljs-string">"---..."</span>),
        (<span class="hljs-string">';'</span>, <span class="hljs-string">"-.-.-."</span>),
        (<span class="hljs-string">'='</span>, <span class="hljs-string">"-...-"</span>),
        (<span class="hljs-string">'+'</span>, <span class="hljs-string">".-.-."</span>),
        (<span class="hljs-string">'-'</span>, <span class="hljs-string">"-....-"</span>),
        (<span class="hljs-string">'_'</span>, <span class="hljs-string">"..--.-"</span>),
        (<span class="hljs-string">'"'</span>, <span class="hljs-string">".-..-."</span>),
        (<span class="hljs-string">'$'</span>, <span class="hljs-string">"...-..-"</span>),
        (<span class="hljs-string">'@'</span>, <span class="hljs-string">".--.-."</span>),
        (<span class="hljs-string">' '</span>, <span class="hljs-string">" "</span>),
    ].iter().cloned().collect();

    <span class="hljs-comment">// Read standard input per line</span>
    <span class="hljs-keyword">for</span> line_res <span class="hljs-keyword">in</span> io::stdin().lock().lines() {
        <span class="hljs-keyword">let</span> line = line_res?;
        <span class="hljs-keyword">if</span> line == <span class="hljs-string">"quit()"</span> {
            <span class="hljs-keyword">break</span>;
        }
        <span class="hljs-comment">// Turn line into morse code</span>
        <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> morse = <span class="hljs-built_in">String</span>::new();
        <span class="hljs-keyword">for</span> c <span class="hljs-keyword">in</span> line.chars() {
            <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> <span class="hljs-literal">Some</span>(morse_code_char) = morse_code_alphabet.get(&amp;c) {
                morse.push_str(morse_code_char);
                <span class="hljs-comment">// Separate characters with a comma</span>
                morse.push_str(<span class="hljs-string">","</span>);
            }
        }
        <span class="hljs-comment">// Blink LED based on characters</span>
        <span class="hljs-keyword">for</span> c <span class="hljs-keyword">in</span> morse.chars() {
            <span class="hljs-keyword">match</span> c {
                <span class="hljs-string">'.'</span> =&gt; {
                    led.on();
                    sleep(Duration::from_millis(DOT_DELAY));
                    led.off();
                    sleep(Duration::from_millis(PUSH_DELAY));
                },
                <span class="hljs-string">'-'</span> =&gt; {
                    led.on();
                    sleep(Duration::from_millis(DASH_DELAY));
                    led.off();
                    sleep(Duration::from_millis(PUSH_DELAY));
                },
                <span class="hljs-string">','</span> =&gt; {
                    sleep(Duration::from_millis(LETTER_DELAY));
                },
                <span class="hljs-string">' '</span> =&gt; {
                    sleep(Duration::from_millis(WORD_DELAY));
                },
                _ =&gt; {
                    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"Unknown character: {}"</span>, c);
                    <span class="hljs-keyword">break</span>;
                }
            }
        }
        sleep(Duration::from_millis(WORD_DELAY));
    }

    <span class="hljs-comment">// Free the variable and associated resources</span>
    led.close();

    <span class="hljs-literal">Ok</span>(())
}
</code></pre>
<h2 id="heading-appendix">Appendix</h2>
<h3 id="heading-targets">Targets</h3>
<p>In Rust, the <em>target</em> is the platform (architecture) the program is compiled for. Cargo automatically detects the target, based on the file system layout <sup>[<a target="_blank" href="https://doc.rust-lang.org/cargo/reference/cargo-targets.html#target-auto-discovery">2</a>]</sup>.</p>
<p>You can see the list of built-in targets, by running:</p>
<pre><code class="lang-bash">rustc --<span class="hljs-built_in">print</span> target-list
<span class="hljs-comment"># OR</span>
rustup target list
</code></pre>
<p>From here you can add a new target to your project, by running:</p>
<pre><code class="lang-bash">rustup target add &lt;target&gt;
</code></pre>
<p>The given target is often in the form of a <em>triple</em> <sup>[<a target="_blank" href="https://rust-lang.github.io/rfcs/0131-target-specification.html">3</a>]</sup>:</p>
<ul>
<li>The architecture</li>
<li>The vendor</li>
<li>The operating system type</li>
<li>The environment type</li>
</ul>
<p><em>This is refered to as a 'target triple', because the fourth part is optional.</em></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Home Network Security – How to Use Suricata, RaspberryPI4, and Python to Make Your Network Safe ]]>
                </title>
                <description>
                    <![CDATA[ In a previous article, I showed you how to secure your wireless home network using Kismet. Kismet is perfect for detecting anomalies and certain types of attack – but what if I want to analyze the traffic and look for abnormal patterns or patterns th... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/home-network-security-with-suricata-raspberrypi4-python/</link>
                <guid isPermaLink="false">66d8513fbfb3c4f0b376afe8</guid>
                
                    <category>
                        <![CDATA[ cybersecurity ]]>
                    </category>
                
                    <category>
                        <![CDATA[ information security ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Raspberry Pi ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Jose Vicente Nunez ]]>
                </dc:creator>
                <pubDate>Tue, 19 Apr 2022 00:23:10 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/04/pexels-george-becker-333837.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In a <a target="_blank" href="https://www.freecodecamp.org/news/wireless-security-using-raspberry-pi-4-kismet-and-python/">previous article</a>, I showed you how to secure your wireless home network using <a target="_blank" href="https://www.kismetwireless.net/">Kismet</a>.</p>
<p>Kismet is perfect for detecting anomalies and certain types of attack – but what if I want to analyze the traffic and look for abnormal patterns or patterns that could indicate an attack?</p>
<p>And <a target="_blank" href="https://en.wikipedia.org/wiki/Intrusion_detection_system">Intrusion Detection System</a> (<strong>IDS</strong>) is:</p>
<blockquote>
<p>...a device or software application that monitors a network or systems for malicious activity or policy violations.</p>
</blockquote>
<p>I used a good IDS in the past called <a target="_blank" href="https://snort.org/">Snort V2</a>, I'm aware than Snort 3 is out. But there is a <a target="_blank" href="https://snort.org/documents/snort-supported-oss">pretty clear warning</a> about running it on a machine without much memory:</p>
<blockquote>
<p>While Snort can compile on almost all *nix based machines, it is not recommended that you compile Snort on a low power or low RAM machine. Snort requires memory to run and to properly analyze as much traffic as possible.</p>
</blockquote>
<p>And</p>
<blockquote>
<p>Snort does not officially support any particular OS.</p>
</blockquote>
<p><em>Not exactly a reason to dislike it</em>, but I feel more confident when a vendor tells me than my OS is in their supported platform list. I do also have more recent experience setting up with the open source tool <a target="_blank" href="https://suricata.io/download/">Suricata,</a> so I decided to give it a more serious try to keep tabs on my local network and alert me if any suspicious activity was detected.</p>
<p>Poking around I found than for my local network, 8 GB of RAM will be sufficient along with my Linux distribution:</p>
<pre><code class="lang-python">josevnz@raspberrypi:~$ lsb_release --release
Release:    <span class="hljs-number">20.04</span>
</code></pre>
<p>My version of Ubuntu <em>is supported out of the box</em>.</p>
<p>The choice is yours. In my case it felt better to use Suricata than Snort. As usual, you need to plan around your hardware, your use cases, and the features offered by the tools (including commercial support).</p>
<h1 id="heading-table-of-contents"><strong>Table of Contents</strong></h1>
<ul>
<li><p><a class="post-section-overview" href="#heading-quick-installation">Quick Installation</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-where-you-should-connect-your-raspberrypi-4-with-suricata">Where you should connect your Raspberry Pi 4 with Suricata</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-suricata">How to Set Up Suricata</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-tune-up-suricata">How to Tune Up Suricata</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-making-sense-of-all-the-alerts">Making Sense of All the Alerts</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-did-we-learn-and-what-is-next">What Did We Learn and What is Next?</a></p>
</li>
</ul>
<h1 id="heading-quick-installation"><strong>Quick Installation</strong></h1>
<p>Installation is explained in detail <a target="_blank" href="https://redmine.openinfosecfoundation.org/projects/suricata/wiki/Ubuntu_Installation_-_Personal_Package_Archives_%28PPA%29">here</a>, so I will only put here the <a target="_blank" href="https://www.youtube.com/playlist?list=PLFqw30a25lWRIhAnQNb7ZaPpexPYgxhVv">quick installation steps</a> I used on my machine:</p>
<pre><code class="lang-python">sudo apt-get install software-properties-common
sudo add-apt-repository ppa:oisf/suricata-stable
sudo apt-get update
sudo apt-get install suricata
</code></pre>
<h2 id="heading-suricata-is-a-complex-beast"><strong>Suricata is a Complex Beast</strong></h2>
<p>You can use Suricata to detect and alert you about anomalies in your network traffic (IDS) or you can proactively drop suspicious connections when working in Intrusion Prevention System (<strong>IPS</strong>).</p>
<p>It can also capture network traffic and store it in PCAP format for later analysis (be careful as you can eat your disk space pretty fast).</p>
<p>We will keep things simple, and for now will take a more passive approach and get alerts when an intrusion is detected (sticking to IDS mode) in this tutorial.</p>
<h1 id="heading-where-you-should-connect-your-raspberrypi-4-with-suricata"><strong>Where you should connect your RaspBerryPI 4 with Suricata?</strong></h1>
<p>Ideally you want to put your Suricata sensor close to your home router. One way to do it is to connect all the devices (including your home router) to a common switch, and then mirror the traffic that goes into/out from the home router into a port on the switch. Suricata will be connected to that port, listening to all the traffic.</p>
<p>If you wanted to run Suricata as an IPS then the connectivity would have to be different, but this is not the intended use in this tutorial.</p>
<h1 id="heading-how-to-set-up-suricata"><strong>How to Set Up Suricata</strong></h1>
<p>Ideally the best place to put Suricata is between a firewall and the rest of the servers in your home network.</p>
<p>In this scenario let's assume than it is not possible because there is no firewall (OK, that will be your ISP router, but you cannot run Suricata there). So the next best thing is the wired network interface connected to it (in my case eth0).</p>
<p>The /etc/suricata/suricata.yaml file contains the defaults. I'll show here what I overrode:</p>
<pre><code class="lang-shell">root@raspberrypi:~# grep -in1 af-p /etc/suricata/suricata.yaml 
580-# Linux high speed capture support
581:af-packet:
582-  - interface: eth0
root@raspberrypi:~# grep -in 'HOME_NET: "' /etc/suricata/suricata.yaml |grep -v '#'
15:    HOME_NET: "[192.168.1.0/24]"
</code></pre>
<p>Start Suricata:</p>
<pre><code class="lang-shell">root@raspberrypi:~# systemctl start suricata.service
root@raspberrypi:~# systemctl status suricata.service
● suricata.service - LSB: Next Generation IDS/IPS
     Loaded: loaded (/etc/init.d/suricata; generated)
     Active: active (running) since Sun 2022-04-10 23:49:00 UTC; 24h ago
       Docs: man:systemd-sysv-generator(8)
      Tasks: 10 (limit: 9257)
     CGroup: /system.slice/suricata.service
             └─1834983 /usr/bin/suricata -c /etc/suricata/suricata.yaml --pidfile /var/run/suricata.pid --af-packet -D -vvv

Apr 10 23:49:00 raspberrypi systemd[1]: Starting LSB: Next Generation IDS/IPS...
Apr 10 23:49:00 raspberrypi suricata[1834973]: Starting suricata in IDS (af-packet) mode... done.
Apr 10 23:49:00 raspberrypi systemd[1]: Started LSB: Next Generation IDS/IPS.
</code></pre>
<p>The important details go into the file '/var/log/suricata/eve.json'. Mine started to grow surprisingly fast after starting Suricata:</p>
<pre><code class="lang-python">{<span class="hljs-string">"timestamp"</span>:<span class="hljs-string">"2022-04-10T23:49:32.527488+0000"</span>,<span class="hljs-string">"event_type"</span>:<span class="hljs-string">"stats"</span>,<span class="hljs-string">"stats"</span>:{<span class="hljs-string">"uptime"</span>:<span class="hljs-number">32</span>,<span class="hljs-string">"capture"</span>:{<span class="hljs-string">"kernel_packets"</span>:<span class="hljs-number">113</span>,<span class="hljs-string">"kernel_drops"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"errors"</span>:<span class="hljs-number">0</span>},<span class="hljs-string">"decoder"</span>:{<span class="hljs-string">"pkts"</span>:<span class="hljs-number">126</span>,<span class="hljs-string">"bytes"</span>:<span class="hljs-number">17986</span>,<span class="hljs-string">"invalid"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"ipv4"</span>:<span class="hljs-number">30</span>,<span class="hljs-string">"ipv6"</span>:<span class="hljs-number">74</span>,<span class="hljs-string">"ethernet"</span>:<span class="hljs-number">126</span>,<span class="hljs-string">"chdlc"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"raw"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"null"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"sll"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"tcp"</span>:<span class="hljs-number">4</span>,<span class="hljs-string">"udp"</span>:<span class="hljs-number">30</span>,<span class="hljs-string">"sctp"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"icmpv4"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"icmpv6"</span>:<span class="hljs-number">70</span>,<span class="hljs-string">"ppp"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"pppoe"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"geneve"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"gre"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"vlan"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"vlan_qinq"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"vxlan"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"vntag"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"ieee8021ah"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"teredo"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"ipv4_in_ipv6"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"ipv6_in_ipv6"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"mpls"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"avg_pkt_size"</span>:<span class="hljs-number">142</span>,<span class="hljs-string">"max_pkt_size"</span>:<span class="hljs-number">392</span>,<span class="hljs-string">"max_mac_addrs_src"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"max_mac_addrs_dst"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"erspan"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"event"</span>:{<span class="hljs-string">"ipv4"</span>:{<span class="hljs-string">"pkt_too_small"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"hlen_too_small"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"iplen_smaller_than_hlen"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"trunc_pkt"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"opt_invalid"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"opt_invalid_len"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"opt_malformed"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"opt_pad_required"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"opt_eol_required"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"opt_duplicate"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"opt_unknown"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"wrong_ip_version"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"icmpv6"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"frag_pkt_too_large"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"frag_overlap"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"frag_ignored"</span>:<span class="hljs-number">0</span>},<span class="hljs-string">"icmpv4"</span>:{<span class="hljs-string">"pkt_too_small"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"unknown_type"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"unknown_code"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"ipv4_trunc_pkt"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"ipv4_unknown_ver"</span>:<span class="hljs-number">0</span>},<span class="hljs-string">"icmpv6"</span>:{<span class="hljs-string">"unknown_type"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"unknown_code"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"pkt_too_small"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"ipv6_unknown_version"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"ipv6_trunc_pkt"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"mld_message_with_invalid_hl"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"unassigned_type"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"experimentation_type"</span>:<span class="hljs-number">0</span>},<span class="hljs-string">"ipv6"</span>:{<span class="hljs-string">"pkt_too_small"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"trunc_pkt"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"trunc_exthdr"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"exthdr_dupl_fh"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"exthdr_useless_fh"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"exthdr_dupl_rh"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"exthdr_dupl_hh"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"exthdr_dupl_dh"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"exthdr_dupl_ah"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"exthdr_dupl_eh"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"exthdr_invalid_optlen"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"wrong_ip_version"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"exthdr_ah_res_not_null"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"hopopts_unknown_opt"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"hopopts_only_padding"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"dstopts_unknown_opt"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"dstopts_only_padding"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"rh_type_0"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"zero_len_padn"</span>:<span class="hljs-number">21</span>,<span class="hljs-string">"fh_non_zero_reserved_field"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"data_after_none_header"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"unknown_next_header"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"icmpv4"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"frag_pkt_too_large"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"frag_overlap"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"frag_invalid_length"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"frag_ignored"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"ipv4_in_ipv6_too_small"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"ipv4_in_ipv6_wrong_version"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"ipv6_in_ipv6_too_small"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"ipv6_in_ipv6_wrong_version"</span>:<span class="hljs-number">0</span>},<span class="hljs-string">"tcp"</span>:{<span class="hljs-string">"pkt_too_small"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"hlen_too_small"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"invalid_optlen"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"opt_invalid_len"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"opt_duplicate"</span>:<span class="hljs-number">0</span>},<span class="hljs-string">"udp"</span>:{<span class="hljs-string">"pkt_too_small"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"hlen_too_small"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"hlen_invalid"</span>:<span class="hljs-number">0</span>},<span class="hljs-string">"sll"</span>:{<span class="hljs-string">"pkt_too_small"</span>:<span class="hljs-number">0</span>},<span class="hljs-string">"ethernet"</span>:{<span class="hljs-string">"pkt_too_small"</span>:<span class="hljs-number">0</span>},<span class="hljs-string">"ppp"</span>:{<span class="hljs-string">"pkt_too_small"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"vju_pkt_too_small"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"ip4_pkt_too_small"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"ip6_pkt_too_small"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"wrong_type"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"unsup_proto"</span>:<span class="hljs-number">0</span>},<span class="hljs-string">"pppoe"</span>:{<span class="hljs-string">"pkt_too_small"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"wrong_code"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"malformed_tags"</span>:<span class="hljs-number">0</span>},<span class="hljs-string">"gre"</span>:{<span class="hljs-string">"pkt_too_small"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"wrong_version"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"version0_recur"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"version0_flags"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"version0_hdr_too_big"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"version0_malformed_sre_hdr"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"version1_chksum"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"version1_route"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"version1_ssr"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"version1_recur"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"version1_flags"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"version1_no_key"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"version1_wrong_protocol"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"version1_malformed_sre_hdr"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"version1_hdr_too_big"</span>:<span class="hljs-number">0</span>},<span class="hljs-string">"vlan"</span>:{<span class="hljs-string">"header_too_small"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"unknown_type"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"too_many_layers"</span>:<span class="hljs-number">0</span>},<span class="hljs-string">"ieee8021ah"</span>:{<span class="hljs-string">"header_too_small"</span>:<span class="hljs-number">0</span>},<span class="hljs-string">"vntag"</span>:{<span class="hljs-string">"header_too_small"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"unknown_type"</span>:<span class="hljs-number">0</span>},<span class="hljs-string">"ipraw"</span>:{<span class="hljs-string">"invalid_ip_version"</span>:<span class="hljs-number">0</span>},<span class="hljs-string">"ltnull"</span>:{<span class="hljs-string">"pkt_too_small"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"unsupported_type"</span>:<span class="hljs-number">0</span>},<span class="hljs-string">"sctp"</span>:{<span class="hljs-string">"pkt_too_small"</span>:<span class="hljs-number">0</span>},<span class="hljs-string">"mpls"</span>:{<span class="hljs-string">"header_too_small"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"pkt_too_small"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"bad_label_router_alert"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"bad_label_implicit_null"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"bad_label_reserved"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"unknown_payload_type"</span>:<span class="hljs-number">0</span>},<span class="hljs-string">"vxlan"</span>:{<span class="hljs-string">"unknown_payload_type"</span>:<span class="hljs-number">0</span>},<span class="hljs-string">"geneve"</span>:{<span class="hljs-string">"unknown_payload_type"</span>:<span class="hljs-number">0</span>},<span class="hljs-string">"erspan"</span>:{<span class="hljs-string">"header_too_small"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"unsupported_version"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"too_many_vlan_layers"</span>:<span class="hljs-number">0</span>},<span class="hljs-string">"dce"</span>:{<span class="hljs-string">"pkt_too_small"</span>:<span class="hljs-number">0</span>},<span class="hljs-string">"chdlc"</span>:{<span class="hljs-string">"pkt_too_small"</span>:<span class="hljs-number">0</span>}},<span class="hljs-string">"too_many_layers"</span>:<span class="hljs-number">0</span>},<span class="hljs-string">"flow"</span>:{<span class="hljs-string">"memcap"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"tcp"</span>:<span class="hljs-number">1</span>,<span class="hljs-string">"udp"</span>:<span class="hljs-number">20</span>,<span class="hljs-string">"icmpv4"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"icmpv6"</span>:<span class="hljs-number">15</span>,<span class="hljs-string">"tcp_reuse"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"get_used"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"get_used_eval"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"get_used_eval_reject"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"get_used_eval_busy"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"get_used_failed"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"wrk"</span>:{<span class="hljs-string">"spare_sync_avg"</span>:<span class="hljs-number">100</span>,<span class="hljs-string">"spare_sync"</span>:<span class="hljs-number">4</span>,<span class="hljs-string">"spare_sync_incomplete"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"spare_sync_empty"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"flows_evicted_needs_work"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"flows_evicted_pkt_inject"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"flows_evicted"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"flows_injected"</span>:<span class="hljs-number">0</span>},<span class="hljs-string">"mgr"</span>:{<span class="hljs-string">"full_hash_pass"</span>:<span class="hljs-number">1</span>,<span class="hljs-string">"closed_pruned"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"new_pruned"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"est_pruned"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"bypassed_pruned"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"rows_maxlen"</span>:<span class="hljs-number">1</span>,<span class="hljs-string">"flows_checked"</span>:<span class="hljs-number">4</span>,<span class="hljs-string">"flows_notimeout"</span>:<span class="hljs-number">4</span>,<span class="hljs-string">"flows_timeout"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"flows_timeout_inuse"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"flows_evicted"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"flows_evicted_needs_work"</span>:<span class="hljs-number">0</span>},<span class="hljs-string">"spare"</span>:<span class="hljs-number">9600</span>,<span class="hljs-string">"emerg_mode_entered"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"emerg_mode_over"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"memuse"</span>:<span class="hljs-number">11668608</span>},<span class="hljs-string">"defrag"</span>:{<span class="hljs-string">"ipv4"</span>:{<span class="hljs-string">"fragments"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"reassembled"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"timeouts"</span>:<span class="hljs-number">0</span>},<span class="hljs-string">"ipv6"</span>:{<span class="hljs-string">"fragments"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"reassembled"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"timeouts"</span>:<span class="hljs-number">0</span>},<span class="hljs-string">"max_frag_hits"</span>:<span class="hljs-number">0</span>},<span class="hljs-string">"flow_bypassed"</span>:{<span class="hljs-string">"local_pkts"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"local_bytes"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"local_capture_pkts"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"local_capture_bytes"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"closed"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"pkts"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"bytes"</span>:<span class="hljs-number">0</span>},<span class="hljs-string">"tcp"</span>:{<span class="hljs-string">"sessions"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"ssn_memcap_drop"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"pseudo"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"pseudo_failed"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"invalid_checksum"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"no_flow"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"syn"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"synack"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"rst"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"midstream_pickups"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"pkt_on_wrong_thread"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"segment_memcap_drop"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"stream_depth_reached"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"reassembly_gap"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"overlap"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"overlap_diff_data"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"insert_data_normal_fail"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"insert_data_overlap_fail"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"insert_list_fail"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"memuse"</span>:<span class="hljs-number">2424832</span>,<span class="hljs-string">"reassembly_memuse"</span>:<span class="hljs-number">393216</span>},<span class="hljs-string">"detect"</span>:{<span class="hljs-string">"engines"</span>:[{<span class="hljs-string">"id"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"last_reload"</span>:<span class="hljs-string">"2022-04-10T23:49:00.377030+0000"</span>,<span class="hljs-string">"rules_loaded"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"rules_failed"</span>:<span class="hljs-number">0</span>}],<span class="hljs-string">"alert"</span>:<span class="hljs-number">0</span>},<span class="hljs-string">"app_layer"</span>:{<span class="hljs-string">"flow"</span>:{<span class="hljs-string">"http"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"ftp"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"smtp"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"tls"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"ssh"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"imap"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"smb"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"dcerpc_tcp"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"dns_tcp"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"nfs_tcp"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"ntp"</span>:<span class="hljs-number">1</span>,<span class="hljs-string">"ftp-data"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"tftp"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"ikev2"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"krb5_tcp"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"dhcp"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"snmp"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"sip"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"rfb"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"mqtt"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"rdp"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"failed_tcp"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"dcerpc_udp"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"dns_udp"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"nfs_udp"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"krb5_udp"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"failed_udp"</span>:<span class="hljs-number">19</span>},<span class="hljs-string">"tx"</span>:{<span class="hljs-string">"http"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"ftp"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"smtp"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"tls"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"ssh"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"imap"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"smb"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"dcerpc_tcp"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"dns_tcp"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"nfs_tcp"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"ntp"</span>:<span class="hljs-number">1</span>,<span class="hljs-string">"ftp-data"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"tftp"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"ikev2"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"krb5_tcp"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"dhcp"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"snmp"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"sip"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"rfb"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"mqtt"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"rdp"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"dcerpc_udp"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"dns_udp"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"nfs_udp"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"krb5_udp"</span>:<span class="hljs-number">0</span>},<span class="hljs-string">"expectations"</span>:<span class="hljs-number">0</span>},<span class="hljs-string">"http"</span>:{<span class="hljs-string">"memuse"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"memcap"</span>:<span class="hljs-number">0</span>},<span class="hljs-string">"ftp"</span>:{<span class="hljs-string">"memuse"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"memcap"</span>:<span class="hljs-number">0</span>},<span class="hljs-string">"file_store"</span>:{<span class="hljs-string">"open_files"</span>:<span class="hljs-number">0</span>}}}
</code></pre>
<p><em>Holy Priceless Collection of Etruscan Snoods!, Batman</em>. How do we tune Suricata to avoid this overwhelming amount of information?</p>
<p>For now let's stop it while we figure it out.</p>
<h1 id="heading-how-to-tune-up-suricata"><strong>How to Tune Up Suricata</strong></h1>
<p>Make sure the settings of suricata.yaml make sense for a home network:</p>
<pre><code class="lang-shell">sudo -i
# And a YAML linter so we can make sure our Suricata configuration files are good
apt-get install yamllint
cp -v -p  /etc/suricata/suricata.yaml /etc/suricata/suricata.yaml.orig
</code></pre>
<p>Note that I provide here a linted and clean version of my [suricata.yaml](file:///home/josevnz/SuricataLog/etc/suricata/suricata.yaml) file.</p>
<h2 id="heading-how-to-tame-the-varlogsuricataevejson-file"><strong>How to tame the /var/log/suricata/eve.json file</strong></h2>
<p>This is the file were we can learn in detail what triggered an alert. But it can grow VERY fast, depending on your traffic and event rules configuration.</p>
<p>So using logrotate (comes installed as part of Ubuntu), do this:</p>
<pre><code class="lang-python"><span class="hljs-comment"># Keep a week of logs, 1 GB of size.</span>
<span class="hljs-comment"># Always test your config: logrotate -vdf /etc/logrotate.d/suricata</span>
/var/log/suricata/*.log /var/log/suricata/*.json {
    daily
    maxsize <span class="hljs-number">1</span>G
    rotate <span class="hljs-number">7</span>
    missingok
    nocompress
    create
    sharedscripts
    postrotate
        systemctl restart suricata.service
    endscript
}
</code></pre>
<h2 id="heading-how-to-help-suricata-to-do-its-job-using-emerging-threats-rules"><strong>How to help Suricata to do its job using emerging threats rules</strong></h2>
<p>We can tune Suricata using the <a target="_blank" href="https://rules.emergingthreats.net/OPEN_download_instructions.html">ET OPEN Ruleset</a>. Because threats change all the time, you need to automate <a target="_blank" href="https://github.com/OISF/suricata-update#suricata-update">their download and updating</a>.</p>
<p>So install it first:</p>
<pre><code class="lang-shell">sudo -i
python3 -m venv ~/virtualenv/suricata
. ~/virtualenv/suricata/bin/activate
pip install --upgrade pip
pip install --upgrade suricata-update
suricata-update
# Also, install jq so we can see the contents of the eve.json file nicely formatted
apt-get install jq
</code></pre>
<p>Let's run it by hand and see how the rules are updated by the tool:</p>
<p><a target="_blank" href="https://asciinema.org/a/487861"><img src="https://asciinema.org/a/487861.svg" alt="asciicast" width="1625.70333269" height="821.3331280000001" loading="lazy"></a></p>
<p>For our home network, we will download these rules once a day. A <a target="_blank" href="https://en.wikipedia.org/wiki/Cron">simple Cron job</a> will do the trick:</p>
<pre><code class="lang-shell">crontab -e
# Run Suricata update once a day, 
# per https://rules.emergingthreats.net/OPEN_download_instructions.html
# Also will update at a different time than the log rotation, to avoid a race condition
# while rotating the logs. Note than we do not need to restart suricata
0 30 * * * . ~/virtualenv/suricata/bin/activate &amp;&amp; suricata-update &amp;&amp; suricatasc -c reload-rules
</code></pre>
<p>Let's start Suricata again, so we can test some rules:</p>
<p><a target="_blank" href="https://asciinema.org/a/487868"><img src="https://asciinema.org/a/487868.svg" alt="asciicast" width="1625.70333269" height="802.666466" loading="lazy"></a></p>
<h2 id="heading-what-is-inside-the-varlogsuricataevejson-file"><strong>What is inside the /var/log/suricata/eve.json file?</strong></h2>
<p>The file packs quite a bit of information, which is <a target="_blank" href="https://suricata.readthedocs.io/en/suricata-6.0.0/output/eve/eve-json-format.html">described in detail</a> here:</p>
<pre><code class="lang-python">{<span class="hljs-string">"timestamp"</span>:<span class="hljs-string">"2022-04-15T20:52:05.026189+0000"</span>,<span class="hljs-string">"flow_id"</span>:<span class="hljs-number">1378250082748552</span>,<span class="hljs-string">"in_iface"</span>:<span class="hljs-string">"eth0"</span>,<span class="hljs-string">"event_type"</span>:<span class="hljs-string">"flow"</span>,<span class="hljs-string">"src_ip"</span>:<span class="hljs-string">"192.168.1.1"</span>,<span class="hljs-string">"src_port"</span>:<span class="hljs-number">59317</span>,<span class="hljs-string">"dest_ip"</span>:<span class="hljs-string">"239.255.255.250"</span>,<span class="hljs-string">"dest_port"</span>:<span class="hljs-number">1900</span>,<span class="hljs-string">"proto"</span>:<span class="hljs-string">"UDP"</span>,<span class="hljs-string">"app_proto"</span>:<span class="hljs-string">"failed"</span>,<span class="hljs-string">"flow"</span>:{<span class="hljs-string">"pkts_toserver"</span>:<span class="hljs-number">1</span>,<span class="hljs-string">"pkts_toclient"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"bytes_toserver"</span>:<span class="hljs-number">378</span>,<span class="hljs-string">"bytes_toclient"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"start"</span>:<span class="hljs-string">"2022-04-15T20:50:32.264328+0000"</span>,<span class="hljs-string">"end"</span>:<span class="hljs-string">"2022-04-15T20:50:32.264328+0000"</span>,<span class="hljs-string">"age"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"state"</span>:<span class="hljs-string">"new"</span>,<span class="hljs-string">"reason"</span>:<span class="hljs-string">"timeout"</span>,<span class="hljs-string">"alerted"</span>:false}}
{<span class="hljs-string">"timestamp"</span>:<span class="hljs-string">"2022-04-15T20:52:05.026418+0000"</span>,<span class="hljs-string">"flow_id"</span>:<span class="hljs-number">2222739437411106</span>,<span class="hljs-string">"in_iface"</span>:<span class="hljs-string">"eth0"</span>,<span class="hljs-string">"event_type"</span>:<span class="hljs-string">"flow"</span>,<span class="hljs-string">"src_ip"</span>:<span class="hljs-string">"192.168.1.1"</span>,<span class="hljs-string">"src_port"</span>:<span class="hljs-number">60890</span>,<span class="hljs-string">"dest_ip"</span>:<span class="hljs-string">"239.255.255.250"</span>,<span class="hljs-string">"dest_port"</span>:<span class="hljs-number">1900</span>,<span class="hljs-string">"proto"</span>:<span class="hljs-string">"UDP"</span>,<span class="hljs-string">"app_proto"</span>:<span class="hljs-string">"failed"</span>,<span class="hljs-string">"flow"</span>:{<span class="hljs-string">"pkts_toserver"</span>:<span class="hljs-number">1</span>,<span class="hljs-string">"pkts_toclient"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"bytes_toserver"</span>:<span class="hljs-number">376</span>,<span class="hljs-string">"bytes_toclient"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"start"</span>:<span class="hljs-string">"2022-04-15T20:50:32.482082+0000"</span>,<span class="hljs-string">"end"</span>:<span class="hljs-string">"2022-04-15T20:50:32.482082+0000"</span>,<span class="hljs-string">"age"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"state"</span>:<span class="hljs-string">"new"</span>,<span class="hljs-string">"reason"</span>:<span class="hljs-string">"timeout"</span>,<span class="hljs-string">"alerted"</span>:false}}
</code></pre>
<p>If you are casually inspecting the contents of the file in real time, I suggest you use <a target="_blank" href="https://stedolan.github.io/jq/">jq</a> (test your filters on <a target="_blank" href="https://jqplay.org/">jqplay.org</a>) and show a few fields of interest:</p>
<p><a target="_blank" href="https://asciinema.org/a/487979"><img src="https://asciinema.org/a/487979.svg" alt="asciicast" width="1844.70999927" height="709.333156" loading="lazy"></a></p>
<p>Going forward we will focus on the alerts, so we can just filter out by that type of event:</p>
<pre><code class="lang-shell">jq 'select(.event_type=="alert")' /var/log/suricata/eve.json
</code></pre>
<p>The Suricata folks have <a target="_blank" href="https://suricata.readthedocs.io/en/suricata-6.0.0/output/eve/eve-json-examplesjq.html">put together a nice page with examples</a> that you should check out.</p>
<h2 id="heading-how-to-test-suricata-installation"><strong>How to test Suricata installation</strong></h2>
<h3 id="heading-tools-of-the-trade-wireshark-tcpreplay-and-pcap-files"><strong>Tools of the trade: Wireshark, tcpreplay, and PCAP files</strong></h3>
<p>We will use some traffic capture files, in <a target="_blank" href="https://tools.ietf.org/id/draft-gharris-opsawg-pcap-00.html">PCAP</a> format. So what is a PCAP file?</p>
<blockquote>
<p>In the late 1980's, Van Jacobson, Steve McCanne, and others at the Network Research Group at Lawrence Berkeley National Laboratory developed the <a target="_blank" href="https://www.tcpdump.org/">tcpdump</a> program to capture and dissect network traces.</p>
<p>The code to capture traffic, using low-level mechanisms in various operating systems, and to read and write network traces to a file was later put into a library named libpcap.</p>
</blockquote>
<p>And we will use a tool to inspect the contents of the PCAP file. <a target="_blank" href="https://www.wireshark.org/">Wireshark</a> is a powerful traffic analysis tool, and we will use <a target="_blank" href="https://tcpreplay.appneta.com/">tcpreplay</a> to trigger the Suricata alerts by playing a PCAP file with suspicious activity:</p>
<pre><code class="lang-shell"># On Ubuntu, Debian: sudo apt-get install wireshark tcpreplay
sudo dnf install -y wireshark tcpreplay
</code></pre>
<p>The best way to learn how the bad actors operate is to see their footprints. You should definitely head to <a target="_blank" href="https://www.malware-traffic-analysis.net/">https://www.malware-traffic-analysis.net/</a> and download some samples, an even <a target="_blank" href="https://www.malware-traffic-analysis.net/training-exercises.html">better practice</a> with their PCAP analysis exercises.</p>
<h3 id="heading-warning-you-will-be-downloading-files-that-are-dangerous"><strong>WARNING</strong>: You will be downloading files that are dangerous:</h3>
<blockquote>
<p>Use this website at your own risk! If you download or use of any information from this website, you assume complete responsibility for any resulting loss or damage.</p>
</blockquote>
<p>So be careful and responsible when using this network traffic capturer.</p>
<h4 id="heading-no-rules-are-enabled-by-default"><strong>No rules are enabled by default?</strong></h4>
<p>How we can check if that is the case? I'll show you next:</p>
<p><a target="_blank" href="https://asciinema.org/a/488000"><img src="https://asciinema.org/a/488000.svg" alt="asciicast" width="1844.70999927" height="933.3331000000001" loading="lazy"></a></p>
<p>Once you enable the rules <code>(suricata-update list-sources --free; uricata-update enable-source source; suricata-update list-enabled-sources)</code> you can tell Suricata to reload the rules without a reboot:</p>
<pre><code class="lang-shell">root@raspberrypi:~# suricatasc -c reload-rules
{"message": "done", "return": "OK"}
</code></pre>
<h3 id="heading-2022-02-23-traffic-analysis-exercise-sunnystation"><strong>2022-02-23 - TRAFFIC ANALYSIS EXERCISE - SUNNYSTATION</strong></h3>
<p>Let's see if we can trigger Suricata using this specific threat (it is relative new).</p>
<p>Start by downloading <a target="_blank" href="https://www.malware-traffic-analysis.net/2022/02/23/2022-02-23-traffic-analysis-exercise.pcap.zip">2022-02-23-traffic-analysis-exercise.pcap.zip</a> (the password is on the [about page](file:///home/josevnz/SuricataLog/)).</p>
<pre><code class="lang-shell">insta_dir="$HOME/Downloads/malware/"
mkdir --parent --verbose "$insta_dir"
url="https://www.malware-traffic-analysis.net/2022/02/23/2022-02-23-traffic-analysis-exercise.pcap.zip"
exercise=$(basename $url)
curl --fail --location --output "$insta_dir/$exercise" $url
# Be ready to put the password :-)
cd $insta_dir &amp;&amp; unzip $exercise
</code></pre>
<p>What is inside? We can check with <code>capinfos</code> to get some insight on the file we just downloaded:</p>
<pre><code class="lang-shell">[josevnz@dmaf5 malware]$ capinfos 2022-02-23-traffic-analysis-exercise.pcap
File name:           2022-02-23-traffic-analysis-exercise.pcap
File type:           Wireshark/tcpdump/... - pcap
File encapsulation:  Ethernet
File timestamp precision:  microseconds (6)
Packet size limit:   file hdr: 65535 bytes
Number of packets:   30k
File size:           19MB
Data size:           19MB
Capture duration:    2680.736661 seconds
First packet time:   2022-02-23 13:22:24.405139
Last packet time:    2022-02-23 14:07:05.141800
Data byte rate:      7,191 bytes/s
Data bit rate:       57kbps
Average packet size: 642.09 bytes
Average packet rate: 11 packets/s
SHA256:              eefc7e61b50e7846f5a3282d7645539d7b2b4b85aa08a09d0b823896c9449d1f
RIPEMD160:           a8d84d262e37563c179e9ca52cdc6aae271efd9c
SHA1:                fdfa0d0edfe0cbcc0c1400fbe6ac61ff40942755
Strict time order:   True
Number of interfaces in file: 1
Interface #0 info:
                     Encapsulation = Ethernet (1 - ether)
                     Capture length = 65535
                     Time precision = microseconds (6)
                     Time ticks per second = 1000000
                     Number of stat entries = 0
                     Number of packets = 30023
</code></pre>
<p>Will use a [small wrapper](file:///home/josevnz/SuricataLog/scripts/replay_pcap_file.sh) around <code>tcpreplay</code> to replay our PCAP file:</p>
<pre><code class="lang-shell">#!/bin/bash
:&lt;&lt;DOC
Script to replay a PCAP file at accelerated pace on the default network interface
Author: Jose Vicente Nunez (kodegeek.com@protonmail.com)
DOC
default_dev=$(ip route show| grep default| sort -n -k 9| head -n 1| cut -f5 -d' ')|| exit 100
if [ "$(id --name --user)" != "root" ]; then
  echo "ERROR: I need to be root to inject the PCAP contents into '$default_dev'"
  echo "Maybe 'sudo $0 $*'?"
  exit 100
fi
for util in tcpreplay ip; do
  if ! type -p $util &gt; /dev/null 2&gt;&amp;1; then
    echo "Please put $util on the PATH and try again!"
    exit 100
  fi
done
:&lt;&lt;DOC
We may have more than one 'default' route, so we sort by priority and pick the one with the
preferred metric:
default via 192.168.1.1 dev eno1 proto dhcp metric 100 &lt;----- PICK ME!!!
default via 192.168.1.1 dev wlp4s0 proto dhcp metric 600
DOC
for pcap in "$@"; do
  if [ -f "$pcap" ]; then
    if ! tcpreplay --stats 5 --intf1 "$default_dev" --multiplier 24 "$pcap"; then
      echo "ERROR: Will not try to replay any pending PCAP files due previous errors"
      exit 100
    fi
  fi
done
</code></pre>
<p>Let it replay until it reaches the end of the file:</p>
<pre><code class="lang-shell">root@raspberrypi:~# tcpreplay --stats 5 --intf1 eth0 --multiplier 24 ~josevnz/Downloads/malware/2022-02-23-traffic-analysis-exercise.pcap 
Test start: 2022-04-16 17:51:40.673394 ...
Actual: 3783 packets (1075843 bytes) sent in 5.03 seconds
Rated: 213624.5 Bps, 1.70 Mbps, 751.17 pps
Actual: 6959 packets (3325918 bytes) sent in 10.04 seconds
Rated: 331191.4 Bps, 2.64 Mbps, 692.96 pps
Actual: 8627 packets (4464002 bytes) sent in 15.14 seconds
Rated: 294744.2 Bps, 2.35 Mbps, 569.61 pps
Actual: 10975 packets (6331901 bytes) sent in 20.21 seconds
Rated: 313180.5 Bps, 2.50 Mbps, 542.83 pps
Actual: 13148 packets (7870783 bytes) sent in 25.26 seconds
Rated: 311561.9 Bps, 2.49 Mbps, 520.45 pps
Actual: 14500 packets (8612630 bytes) sent in 30.43 seconds
...
Actual: 24467 packets (14960314 bytes) sent in 110.83 seconds
Rated: 134978.5 Bps, 1.07 Mbps, 220.75 pps
Test complete: 2022-04-16 17:53:33.735188
Actual: 30023 packets (19277433 bytes) sent in 113.06 seconds
Rated: 170503.5 Bps, 1.36 Mbps, 265.54 pps
Statistics for network device: eth0
    Successful packets:        30023
    Failed packets:            0
    Truncated packets:         0
    Retried packets (ENOBUFS): 0
    Retried packets (EAGAIN):  0
</code></pre>
<p>And eventually we get a few alerts:</p>
<pre><code class="lang-shell">"2022-04-16T17:52:20.134763+0000,dns,1296231906414153,172.16.0.170:53806,172.16.0.52:53"
"2022-04-16T17:52:20.286785+0000,dns,293726410006593,172.16.0.170:50935,172.16.0.52:53"
"2022-04-16T17:52:20.290084+0000,dns,293726410006593,172.16.0.170:50935,172.16.0.52:53"
"2022-04-16T17:52:20.520858+0000,alert,1626224981242326,172.16.0.149:49795,172.16.0.52:139"
"2022-04-16T17:52:21.784804+0000,alert,1992149752477936,172.16.0.149:49796,172.16.0.52:139"
"2022-04-16T17:52:22.142041+0000,flow,1739064507071469,172.16.0.149:5353,224.0.0.251:5353"
"2022-04-16T17:52:22.351091+0000,dns,2078727703255923,172.16.0.149:51367,172.16.0.52:53"
"2022-04-16T17:52:22.351260+0000,dns,181632058678300,172.16.0.149:64943,172.16.0.52:53"
"2022-04-16T17:52:22.351129+0000,dns,2078727703255923,172.16.0.149:51367,172.16.0.52:53"
"2022-04-16T17:52:23.037637+0000,alert,282956779721256,172.16.0.149:49798,172.16.0.52:139"
"2022-04-16T17:52:23.901721+0000,dns,556717995180633,172.16.0.170:51164,172.16.0.52:53"
"2022-04-16T17:52:23.904764+0000,dns,556717995180633,172.16.0.170:51164,172.16.0.52:53"
"2022-04-16T17:52:24.293356+0000,alert,2006941620009246,172.16.0.149:49799,172.16.0.52:139"
"2022-04-16T17:52:25.322102+0000,dns,1671081620007478,172.16.0.170:51909,172.16.0.52:53"
</code></pre>
<p>For sake of example, zoom in alert id '282956779721256':</p>
<pre><code class="lang-python">// root@raspberrypi:~<span class="hljs-comment"># grep 282956779721256 /var/log/suricata/eve.json|jq</span>
{
  <span class="hljs-string">"timestamp"</span>: <span class="hljs-string">"2022-04-16T17:52:23.037637+0000"</span>,
  <span class="hljs-string">"flow_id"</span>: <span class="hljs-number">282956779721256</span>,
  <span class="hljs-string">"in_iface"</span>: <span class="hljs-string">"eth0"</span>,
  <span class="hljs-string">"event_type"</span>: <span class="hljs-string">"alert"</span>,
  <span class="hljs-string">"src_ip"</span>: <span class="hljs-string">"172.16.0.149"</span>,
  <span class="hljs-string">"src_port"</span>: <span class="hljs-number">49798</span>,
  <span class="hljs-string">"dest_ip"</span>: <span class="hljs-string">"172.16.0.52"</span>,
  <span class="hljs-string">"dest_port"</span>: <span class="hljs-number">139</span>,
  <span class="hljs-string">"proto"</span>: <span class="hljs-string">"TCP"</span>,
  <span class="hljs-string">"metadata"</span>: {
    <span class="hljs-string">"flowints"</span>: {
      <span class="hljs-string">"applayer.anomaly.count"</span>: <span class="hljs-number">1</span>
    }
  },
  <span class="hljs-string">"alert"</span>: {
    <span class="hljs-string">"action"</span>: <span class="hljs-string">"allowed"</span>,
    <span class="hljs-string">"gid"</span>: <span class="hljs-number">1</span>,
    <span class="hljs-string">"signature_id"</span>: <span class="hljs-number">2260002</span>,
    <span class="hljs-string">"rev"</span>: <span class="hljs-number">1</span>,
    <span class="hljs-string">"signature"</span>: <span class="hljs-string">"SURICATA Applayer Detect protocol only one direction"</span>,
    <span class="hljs-string">"category"</span>: <span class="hljs-string">"Generic Protocol Command Decode"</span>,
    <span class="hljs-string">"severity"</span>: <span class="hljs-number">3</span>
  },
  <span class="hljs-string">"smb"</span>: {
    <span class="hljs-string">"id"</span>: <span class="hljs-number">1</span>,
    <span class="hljs-string">"dialect"</span>: <span class="hljs-string">"NT LM 0.12"</span>,
    <span class="hljs-string">"command"</span>: <span class="hljs-string">"SMB1_COMMAND_NEGOTIATE_PROTOCOL"</span>,
    <span class="hljs-string">"status"</span>: <span class="hljs-string">"STATUS_SUCCESS"</span>,
    <span class="hljs-string">"status_code"</span>: <span class="hljs-string">"0x0"</span>,
    <span class="hljs-string">"session_id"</span>: <span class="hljs-number">0</span>,
    <span class="hljs-string">"tree_id"</span>: <span class="hljs-number">0</span>,
    <span class="hljs-string">"client_dialects"</span>: [
      <span class="hljs-string">"PC NETWORK PROGRAM 1.0"</span>,
      <span class="hljs-string">"LANMAN1.0"</span>,
      <span class="hljs-string">"Windows for Workgroups 3.1a"</span>,
      <span class="hljs-string">"LM1.2X002"</span>,
      <span class="hljs-string">"LANMAN2.1"</span>,
      <span class="hljs-string">"NT LM 0.12"</span>
    ],
    <span class="hljs-string">"server_guid"</span>: <span class="hljs-string">"a21b9552-a4a0-48cd-8abb-ea111498253d"</span>
  },
  <span class="hljs-string">"app_proto"</span>: <span class="hljs-string">"smb"</span>,
  <span class="hljs-string">"app_proto_ts"</span>: <span class="hljs-string">"failed"</span>,
  <span class="hljs-string">"flow"</span>: {
    <span class="hljs-string">"pkts_toserver"</span>: <span class="hljs-number">4</span>,
    <span class="hljs-string">"pkts_toclient"</span>: <span class="hljs-number">3</span>,
    <span class="hljs-string">"bytes_toserver"</span>: <span class="hljs-number">579</span>,
    <span class="hljs-string">"bytes_toclient"</span>: <span class="hljs-number">387</span>,
    <span class="hljs-string">"start"</span>: <span class="hljs-string">"2022-04-16T17:52:23.037416+0000"</span>
  },
  <span class="hljs-string">"payload"</span>: <span class="hljs-string">"AAAAiv9TTUJzAAAAABgHyAAAQlNSU1BZTCAAAP////4AAEAADP8AAAAEQTIAAAAAAAAASgAAAAAA1AAAoE8AYEgGBisGAQUFAqA+MDygDjAMBgorBgEEAYI3AgIKoioEKE5UTE1TU1AAAQAAAJeCCOIAAAAAAAAAAAAAAAAAAAAACgBhSgAAAA8AAAAAAA=="</span>,
  <span class="hljs-string">"payload_printable"</span>: <span class="hljs-string">".....SMBs.........BSRSPYL ........@.......A2.......J.........O.`H..+......&gt;0&lt;..0..\n+.....7..\n.*.(NTLMSSP.........................\n.aJ........."</span>,
  <span class="hljs-string">"stream"</span>: <span class="hljs-number">0</span>,
  <span class="hljs-string">"packet"</span>: <span class="hljs-string">"AB5PDqh0ABv8e9HACABFAAC2t+tAAIAG6WysEACVrBAANMKGAIthfGQf7GIEdVAYIBP6YwAAAAAAiv9TTUJzAAAAABgHyAAAQlNSU1BZTCAAAP////4AAEAADP8AAAAEQTIAAAAAAAAASgAAAAAA1AAAoE8AYEgGBisGAQUFAqA+MDygDjAMBgorBgEEAYI3AgIKoioEKE5UTE1TU1AAAQAAAJeCCOIAAAAAAAAAAAAAAAAAAAAACgBhSgAAAA8AAAAAAA=="</span>,
  <span class="hljs-string">"packet_info"</span>: {
    <span class="hljs-string">"linktype"</span>: <span class="hljs-number">1</span>
  },
  <span class="hljs-string">"host"</span>: <span class="hljs-string">"ras[berripi"</span>
}
{
  <span class="hljs-string">"timestamp"</span>: <span class="hljs-string">"2022-04-16T17:55:42.050329+0000"</span>,
  <span class="hljs-string">"flow_id"</span>: <span class="hljs-number">282956779721256</span>,
  <span class="hljs-string">"in_iface"</span>: <span class="hljs-string">"eth0"</span>,
  <span class="hljs-string">"event_type"</span>: <span class="hljs-string">"flow"</span>,
  <span class="hljs-string">"src_ip"</span>: <span class="hljs-string">"172.16.0.149"</span>,
  <span class="hljs-string">"src_port"</span>: <span class="hljs-number">49798</span>,
  <span class="hljs-string">"dest_ip"</span>: <span class="hljs-string">"172.16.0.52"</span>,
  <span class="hljs-string">"dest_port"</span>: <span class="hljs-number">139</span>,
  <span class="hljs-string">"proto"</span>: <span class="hljs-string">"TCP"</span>,
  <span class="hljs-string">"app_proto"</span>: <span class="hljs-string">"smb"</span>,
  <span class="hljs-string">"app_proto_ts"</span>: <span class="hljs-string">"failed"</span>,
  <span class="hljs-string">"flow"</span>: {
    <span class="hljs-string">"pkts_toserver"</span>: <span class="hljs-number">13</span>,
    <span class="hljs-string">"pkts_toclient"</span>: <span class="hljs-number">12</span>,
    <span class="hljs-string">"bytes_toserver"</span>: <span class="hljs-number">1743</span>,
    <span class="hljs-string">"bytes_toclient"</span>: <span class="hljs-number">1963</span>,
    <span class="hljs-string">"start"</span>: <span class="hljs-string">"2022-04-16T17:52:23.037416+0000"</span>,
    <span class="hljs-string">"end"</span>: <span class="hljs-string">"2022-04-16T17:52:23.488633+0000"</span>,
    <span class="hljs-string">"age"</span>: <span class="hljs-number">0</span>,
    <span class="hljs-string">"state"</span>: <span class="hljs-string">"closed"</span>,
    <span class="hljs-string">"reason"</span>: <span class="hljs-string">"timeout"</span>,
    <span class="hljs-string">"alerted"</span>: true
  },
  <span class="hljs-string">"metadata"</span>: {
    <span class="hljs-string">"flowbits"</span>: [
      <span class="hljs-string">"smb.tree.connect.ipc"</span>
    ],
    <span class="hljs-string">"flowints"</span>: {
      <span class="hljs-string">"applayer.anomaly.count"</span>: <span class="hljs-number">1</span>
    }
  },
  <span class="hljs-string">"tcp"</span>: {
    <span class="hljs-string">"tcp_flags"</span>: <span class="hljs-string">"1b"</span>,
    <span class="hljs-string">"tcp_flags_ts"</span>: <span class="hljs-string">"1b"</span>,
    <span class="hljs-string">"tcp_flags_tc"</span>: <span class="hljs-string">"1b"</span>,
    <span class="hljs-string">"syn"</span>: true,
    <span class="hljs-string">"fin"</span>: true,
    <span class="hljs-string">"psh"</span>: true,
    <span class="hljs-string">"ack"</span>: true,
    <span class="hljs-string">"state"</span>: <span class="hljs-string">"closed"</span>
  },
  <span class="hljs-string">"host"</span>: <span class="hljs-string">"raspberrypi"</span>
}
</code></pre>
<p>That's quite a bit to process. Keep in mind that while we are tuning Suricata, we can also ask it to replay one or more PCAP file directly.</p>
<h4 id="heading-ask-suricata-to-run-in-offline-mode-using-pcap-file-for-sunnystation"><strong>Ask Suricata to run in offline mode using PCAP file for SUNNYSTATION</strong></h4>
<p>It is a very convenient way to test Suricata, as we do not inject any traffic in our network and instead let Suricata 'ingest' the contents of the PCAP file directly, to test the rules.</p>
<p>Also, we redirect the logs to a separate location (by default the directory where you are running the 'offline' mode), so we don't pollute a live installation.</p>
<p><a target="_blank" href="https://asciinema.org/a/SH8bo3pjpvRt4H617GoHbPdoK"><img src="https://asciinema.org/a/SH8bo3pjpvRt4H617GoHbPdoK.svg" alt="asciicast" width="1844.70999927" height="933.3331000000001" loading="lazy"></a></p>
<h3 id="heading-another-example-emotet-with-cobalt-strike"><strong>Another example: EMOTET WITH COBALT STRIKE</strong></h3>
<p>Let's try another malware capture, in this case 2022-02-08 (TUESDAY) - <a target="_blank" href="https://www.malware-traffic-analysis.net/2022/02/08/index.html">FILES FOR AN ISC DIARY (EMOTET WITH COBALT STRIKE)</a>:</p>
<pre><code class="lang-shell">cd ~/Downloads/malware/ &amp;&amp; \
curl --remote-name https://www.malware-traffic-analysis.net/2022/02/08/2022-02-08-Emotet-epoch4-infection-start-and-spambot-traffic.pcap.zip &amp;&amp; \
unzip 2022-02-08-Emotet-epoch4-infection-start-and-spambot-traffic.pcap.zip &amp;&amp; \
sudo suricata -r ~josevnz/Downloads/malware/2022-02-08-Emotet-epoch4-infection-start-and-spambot-traffic.pcap -k none --runmode autofp -c /etc/suricata/suricata.yaml -l ~josevnz/Downloads/malware/
</code></pre>
<p>Here is a sample session:</p>
<p><a target="_blank" href="https://asciinema.org/a/488035"><img src="https://asciinema.org/a/488035.svg" alt="asciicast" width="1844.70999927" height="933.3331000000001" loading="lazy"></a></p>
<h1 id="heading-making-sense-of-all-the-alerts"><strong>Making Sense of All the Alerts</strong></h1>
<p>Suricata will save lots of details when it detects an anomaly. You can tell that using jq to go through the alerts may not be desirable.</p>
<p>For a bigger setup, you may want to use an <a target="_blank" href="https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-module-suricata.html">Elastic Stack</a> (Filebeat, Logstash, Elastic Search, Kibana):</p>
<ul>
<li><p>Get the logs</p>
</li>
<li><p>Store historically and normalize the logs</p>
</li>
<li><p>Visualize their contents</p>
</li>
</ul>
<p>But that feels overkill for a home setup, so I will roll out a few scripts to help me with what I need.</p>
<h2 id="heading-show-me-what-happened-in-the-last-10-minutes"><strong>Show me what happened in the last 10 minutes</strong></h2>
<p>This is a script that assumes most of the defaults, so I don't have to type a jq expression. If there are any alerts then I dive deeper into the eve.json file.</p>
<p>A simple Python 3 script will do the trick for us:</p>
<pre><code class="lang-python"><span class="hljs-comment">#!/usr/bin/env python</span>
<span class="hljs-string">"""
Show Suricata alerts
Author: Jose Vicente Nunez (kodegeek.com@protonmail.com)
"""</span>
<span class="hljs-keyword">import</span> argparse
<span class="hljs-keyword">import</span> json
<span class="hljs-keyword">from</span> datetime <span class="hljs-keyword">import</span> datetime, timedelta
<span class="hljs-keyword">from</span> json <span class="hljs-keyword">import</span> JSONDecodeError
<span class="hljs-keyword">from</span> pathlib <span class="hljs-keyword">import</span> Path
<span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> Callable, Any, Dict

DEFAULT_EVE = [Path(<span class="hljs-string">"/var/log/suricata/eve.json"</span>)]
DEFAULT_TIMESTAMP_10M_AGO = datetime = datetime.now() - timedelta(minutes=<span class="hljs-number">10</span>)


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_parse_timestamp</span>(<span class="hljs-params">candidate: str</span>) -&gt; datetime:</span>
    <span class="hljs-string">"""
    Expected something like 2022-02-08T16:32:14.900292+0000
    :param candidate:
    :return:
    """</span>
    <span class="hljs-keyword">if</span> isinstance(candidate, str):
        <span class="hljs-keyword">try</span>:
            iso_candidate = candidate.split(<span class="hljs-string">'+'</span>, <span class="hljs-number">1</span>)[<span class="hljs-number">0</span>]
            <span class="hljs-keyword">return</span> datetime.fromisoformat(iso_candidate)
        <span class="hljs-keyword">except</span> ValueError:
            <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">f"Invalid date passed: <span class="hljs-subst">{candidate}</span>"</span>)
    <span class="hljs-keyword">elif</span> isinstance(candidate, datetime):
        <span class="hljs-keyword">return</span> candidate


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">alert_filter</span>(<span class="hljs-params">
        *,
        timestamp: datetime = DEFAULT_TIMESTAMP_10M_AGO,
        data: Dict[str, Any]
</span>) -&gt; bool:</span>
    <span class="hljs-keyword">if</span> <span class="hljs-string">'event_type'</span> <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> data:
        <span class="hljs-keyword">return</span> <span class="hljs-literal">False</span>
    <span class="hljs-keyword">if</span> data[<span class="hljs-string">'event_type'</span>] != <span class="hljs-string">'alert'</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-literal">False</span>
    <span class="hljs-keyword">try</span>:
        event_timestamp = _parse_timestamp(data[<span class="hljs-string">'timestamp'</span>])
        <span class="hljs-keyword">if</span> event_timestamp &gt; timestamp:
            <span class="hljs-keyword">return</span> <span class="hljs-literal">False</span>
    <span class="hljs-keyword">except</span> ValueError:
        <span class="hljs-keyword">return</span> <span class="hljs-literal">False</span>
    <span class="hljs-keyword">return</span> <span class="hljs-literal">True</span>


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_alerts</span>(<span class="hljs-params">
        *,
        eve_files=None,
        row_filter: Callable = alert_filter,
        timestamp: datetime = DEFAULT_TIMESTAMP_10M_AGO
</span>) -&gt; str:</span>
    <span class="hljs-keyword">if</span> eve_files <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:
        eve_files = DEFAULT_EVE
    <span class="hljs-keyword">for</span> eve_file <span class="hljs-keyword">in</span> eve_files:
        <span class="hljs-keyword">with</span> open(eve_file, <span class="hljs-string">'rt'</span>) <span class="hljs-keyword">as</span> eve:
            <span class="hljs-keyword">for</span> line <span class="hljs-keyword">in</span> eve:
                <span class="hljs-keyword">try</span>:
                    data = json.loads(line)
                    <span class="hljs-keyword">if</span> row_filter(data=data, timestamp=timestamp):
                        <span class="hljs-keyword">yield</span> data
                <span class="hljs-keyword">except</span> JSONDecodeError:
                    <span class="hljs-keyword">continue</span>  <span class="hljs-comment"># Try to read the next record</span>


<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    PARSER = argparse.ArgumentParser(description=__doc__)
    PARSER.add_argument(
        <span class="hljs-string">"--timestamp"</span>,
        type=_parse_timestamp,
        default=DEFAULT_TIMESTAMP_10M_AGO,
        help=<span class="hljs-string">f"Minimum timestamp in the past to use when filtering events (<span class="hljs-subst">{DEFAULT_TIMESTAMP_10M_AGO}</span>)"</span>
    )
    PARSER.add_argument(
        <span class="hljs-string">'eve'</span>,
        type=Path,
        nargs=<span class="hljs-string">"+"</span>,
        help=<span class="hljs-string">f"Path to one or more <span class="hljs-subst">{DEFAULT_EVE[<span class="hljs-number">0</span>]}</span> file to parse."</span>
    )
    OPTIONS = PARSER.parse_args()
    <span class="hljs-keyword">try</span>:
        <span class="hljs-keyword">for</span> alert <span class="hljs-keyword">in</span> get_alerts(eve_files=OPTIONS.eve, timestamp=OPTIONS.timestamp):
            print(json.dumps(alert, indent=<span class="hljs-number">6</span>, sort_keys=<span class="hljs-literal">True</span>))
    <span class="hljs-keyword">except</span> KeyboardInterrupt:
        <span class="hljs-keyword">pass</span>
</code></pre>
<p>It is a big improvement over jq as at least we can filter by timestamp, but it would be nice if our script could do the following:</p>
<ol>
<li><p>Support pagination</p>
</li>
<li><p>Colorize output</p>
</li>
<li><p>Let you show between a table format or raw JSON output</p>
</li>
</ol>
<p><a target="_blank" href="https://asciinema.org/a/488166"><img src="https://asciinema.org/a/488166.svg" alt="asciicast" width="1844.70999927" height="933.3331000000001" loading="lazy"></a></p>
<h1 id="heading-what-did-we-learn-and-what-is-next"><strong>What Did We Learn and What is Next?</strong></h1>
<p>Suricata is a complex piece of software. It takes time to tame it and more time to make sense of the information it presents. But it is very rewarding to see how you can tackle a tool that will allow you to secure your network from threats.</p>
<ul>
<li><p><a target="_blank" href="https://www.youtube.com/c/OISFSuricata">The OISF Suricata YouTube channel</a> has many interesting resources about this tool and a thriving community.</p>
</li>
<li><p>Want to learn how to analyze PCAP files for bad traffic? <a target="_blank" href="https://www.malware-traffic-analysis.net/training-exercises.html">malware-traffic-analysis</a> has perfect material for you.</p>
</li>
<li><p><strong>Writing complex software is hard</strong>. For example, older versions of Snort are vulnerable to an <a target="_blank" href="https://claroty.com/2022/04/14/blog-research-blinding-snort-breaking-the-modbus-ot-preprocessor/">attack that can disable it, CVE-2022-20685</a>. Suricata also had <a target="_blank" href="https://nvd.nist.gov/vuln/detail/CVE-2019-1010279">CVE-2019-1010279</a> .These issues were fixed but illustrates the need to keep your software current, specially the one you use to protect your network.</p>
</li>
<li><p>I did not touch the IPS mode, or even hybrid modes for Suricata. Please read the official documentation to get up to speed.</p>
</li>
<li><p>Finally, do yourself a favor and read this <a target="_blank" href="https://resources.sei.cmu.edu/asset_files/Presentation/2016_017_001_449890.pdf">Suricata Tutorial from FloCon 2016</a>. It is very complete and will have you looking for more.</p>
</li>
</ul>
<p>You can leave your comments on the Git repository and report any bugs. But more important get Suricata, <a target="_blank" href="https://github.com/josevnz/SuricataLog">get the code of this tutorial</a>, and start securing your home wireless infrastructure in no time.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Secure Your Home Wireless Infrastructure with Kismet and Python ]]>
                </title>
                <description>
                    <![CDATA[ Everything is connected to wireless these days. In my case I found that I have LOTS of devices after running a simple nmap command on my home network: [josevnz@dmaf5 ~]$ sudo nmap -v -n -p- -sT -sV -O --osscan-limit --max-os-tries 1 -oX $HOME/home_sc... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/wireless-security-using-raspberry-pi-4-kismet-and-python/</link>
                <guid isPermaLink="false">66d8514f4540581f6454412b</guid>
                
                    <category>
                        <![CDATA[ information security ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Raspberry Pi ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Security ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Jose Vicente Nunez ]]>
                </dc:creator>
                <pubDate>Wed, 02 Mar 2022 16:22:20 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/03/wireless_security_with_kismet_and_python.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Everything is connected to wireless these days. In my case I found that I have LOTS of devices after running a simple <a target="_blank" href="https://www.freecodecamp.org/news/enhance-nmap-with-python/#nmap-101-identify-all-the-public-services-in-our-network">nmap command on my home network</a>:</p>
<pre><code class="lang-shell">[josevnz@dmaf5 ~]$ sudo nmap -v -n -p- -sT -sV -O --osscan-limit --max-os-tries 1 -oX $HOME/home_scan.xml 192.168.1.0/24
</code></pre>
<p>So I started to wonder:</p>
<ul>
<li><p>Is my wireless network secure?</p>
</li>
<li><p>How long would it take to an attacker to get in?</p>
</li>
</ul>
<p>I have a <em>Raspberry 4</em> with Ubuntu (focal) installed and decided to use the well-known <a target="_blank" href="https://www.kismetwireless.net/">Kismet</a> to find out.</p>
<p>In this article you will learn:</p>
<ul>
<li><p>How to get a whole picture of the networks nearby you with Kismet</p>
</li>
<li><p>How to customize Kismet using Python and the REST-API</p>
</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/03/raspberrypi-wireless-setup-1.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>If you are curious, this is my home Raspberry PI 4, tiny monitor and all</em></p>
<h1 id="heading-table-of-contents">Table of contents</h1>
<ul>
<li><p><a class="post-section-overview" href="#heading-the-saying-ask-for-forgiveness-not-permission-doesnt-apply-here">The saying 'Ask for forgiveness, not permission' doesn't apply here</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-getting-to-know-your-hardware">Getting to know your hardware</a></p>
</li>
<li><p><a class="post-section-overview" href="#kismet">kismet</a></p>
</li>
<li><p><a class="post-section-overview" href="#restapi">REST-API</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-did-we-learn">What did we learn?</a></p>
</li>
</ul>
<h1 id="heading-the-saying-ask-for-forgiveness-not-permission-doesnt-apply-here">The saying 'Ask for forgiveness, not permission' doesn't apply here</h1>
<p>And by that I mean that <em>you should not be trying to eavesdrop or infiltrate a wireless network that is not yours</em>. It is relatively easy to detect if a new unknown client joined your wireless network, and it is also illegal.</p>
<p>So do the right thing – use this tutorial to learn and not to break into someone else's network, OK?</p>
<h1 id="heading-getting-to-know-your-hardware">Getting to know your hardware</h1>
<p>I will jump a little ahead to show you a small issue with the Raspberry 4 integrated Wireless interface.</p>
<p><strong>The Raspberry PI 4 onboard wireless card will not work out of the box</strong> as the firmware doesn't support monitor mode.</p>
<p>There are works to <a target="_blank" href="https://github.com/seemoo-lab/bcm-rpi3">support this</a>. Instead, I took the easy way out and ordered an external Wi-Fi dongle from <a target="_blank" href="https://www.canakit.com/raspberry-pi-wifi.html">CanaKit</a>.</p>
<p>The CanaKit wireless card worked out of the box, and we'll see it shortly. But first let's install and play around with Kismet.</p>
<h2 id="heading-make-sure-the-interface-is-running-in-monitor-mode">Make sure the interface is running in monitor mode</h2>
<p>By default, the network interface will have monitor mode off:</p>
<pre><code class="lang-shell">root@raspberrypi:~# iwconfig wlan1
wlan1     IEEE 802.11  ESSID:off/any  
          Mode:Managed  Access Point: Not-Associated   Tx-Power=0 dBm   
          Retry short  long limit:2   RTS thr:off   Fragment thr:off
          Encryption key:off
          Power Management:off
</code></pre>
<p>I know I will always set up my Ralink Technology, Corp. RT5370 Wireless Adapter in monitor mode, but I need to be careful as Ubuntu can swap wlan0 and wlan1 (The Broadcom adapter I want to skip is a PCI device).</p>
<p>The Ralink adapter is a USB adapter, so we can find out where it is:</p>
<pre><code class="lang-shell">josevnz@raspberrypi:/etc/netplan$ /bin/lsusb|grep Ralink
Bus 001 Device 004: ID 148f:5370 Ralink Technology, Corp. RT5370 Wireless Adapter
</code></pre>
<p>Now we need to find out what device was mapped to the Ralink adapter. With a little bit of help of the Ubuntu community I found than the Ralink adapter uses the rt2800usb driver <a target="_blank" href="https://help.ubuntu.com/community/WifiDocs/Device/Ralink_RT5370">5370 Ralink Technology</a></p>
<p>The answer I seek is here:</p>
<pre><code class="lang-shell">josevnz@raspberrypi:~$ ls /sys/bus/usb/drivers/rt2800usb/*:1.0/net/
wlan1
</code></pre>
<p>So the code that does the wireless card detection looks like this:</p>
<pre><code class="lang-shell">root@raspberrypi:~#/bin/cat&lt;&lt;RC_LOCAL&gt;/etc/rc.local
#!/bin/bash
usb_driver=rt2800usb
wlan=\$(/bin/ls /sys/bus/usb/drivers/\$usb_driver/*/net/)
if [ $? -eq 0 ]; then
        set -ex
        /usr/sbin/ifconfig "\$wlan" down
        /usr/sbin/iwconfig "\$wlan" mode monitor
        /usr/sbin/ifconfig "\$wlan" up
        set +ex
fi
RC_LOCAL
root@raspberrypi:~# chmod u+x /etc/rc.local &amp;&amp; shutdown -r now "Enabling monitor mode"
</code></pre>
<p>Make sure the card is on monitor mode:</p>
<pre><code class="lang-shell">root@raspberrypi:~# iwconfig wlan1
iw        iwconfig  iwevent   iwgetid   iwlist    iwpriv    iwspy     
root@raspberrypi:~# iwconfig wlan1
wlan1     IEEE 802.11  Mode:Monitor  Frequency:2.412 GHz  Tx-Power=20 dBm   
          Retry short  long limit:2   RTS thr:off   Fragment thr:off
          Power Management:off
</code></pre>
<p>Good, let's move on with the tool setup</p>
<h1 id="heading-what-is-kismet">What is Kismet?</h1>
<p><a target="_blank" href="https://www.kismetwireless.net/">Kismet</a> is:</p>
<blockquote>
<p>a wireless network and device detector, sniffer, wardriving tool, and WIDS (wireless intrusion detection) framework.</p>
</blockquote>
<h2 id="heading-kismet-installation-and-setup">Kismet installation and setup</h2>
<p>The version that comes with the Ubuntu RaspberryPI by default is from 2016, <em>way too old</em>.</p>
<p>Instead, get an updated binary as <a target="_blank" href="https://www.kismetwireless.net/docs/readme/packages/">explained here</a> (I have Ubuntu focal, check with <code>lsb_release --all</code>).</p>
<pre><code class="lang-shell">wget -O - https://www.kismetwireless.net/repos/kismet-release.gpg.key | sudo apt-key add -
echo 'deb https://www.kismetwireless.net/repos/apt/release/focal focal main' | sudo tee /etc/apt/sources.list.d/kismet.list
sudo apt update
sudo apt install kismet
</code></pre>
<h3 id="heading-do-not-run-as-root-use-a-suid-binaryhttpsenwikipediaorgwikisetuid-and-a-unix-group-access">Do not run as root, use a <a target="_blank" href="https://en.wikipedia.org/wiki/Setuid">SUID binary</a> and a unix group access</h3>
<p>Kismet needs elevated privileges to run. And deals with possibly hostile data. So running with minimized permissions is the safest approach.</p>
<p>The right way to set it up is by using a Unix group and set user id (<em>SUID</em>) binary. My user is 'josevnz' so I did this:</p>
<pre><code class="lang-python">sudo apt-get install kismet
sudo usermod --append --groups kismet josevnz
</code></pre>
<h3 id="heading-encrypt-your-access-to-kismet-with-a-self-signed-certificate">Encrypt your access to Kismet with a self-signed certificate</h3>
<p>I will enable SSL for my Kismet <a target="_blank" href="https://github.com/josevnz/home_nmap/tree/main/tutorial">installation by using a self-signed certificate</a>. I will use for that the Cloudflare CFSSL tools:</p>
<pre><code class="lang-python">sudo apt-get update -y
sudo apt-get install -y golang-cfssl
</code></pre>
<p>Next step is to create the self-signed certificates. There is a lot of boilerplate steps here, so I will show you how you can jump through them (but please read the man pages to see what each command does):</p>
<h4 id="heading-initial-certificate">Initial certificate</h4>
<pre><code class="lang-shell">sudo /bin/mkdir --parents /etc/pki/raspberrypi
sudo /bin/cat&lt;&lt;CA&gt;/etc/pki/raspberrypi/ca.json
{
   "CN": "Nunez Barrios family Root CA",
   "key": {
     "algo": "rsa",
     "size": 2048
   },
   "names": [
   {
     "C": "US",
     "L": "CT",
     "O": "Nunez Barrios",
     "OU": "Nunez Barrios Root CA",
     "ST": "United States"
   }
  ]
}
CA
cfssl gencert -initca ca.json | cfssljson -bare ca
</code></pre>
<h4 id="heading-ssl-profile-config">SSL profile config</h4>
<pre><code class="lang-shell">root@raspberrypi:/etc/pki/raspberrypi# /bin/cat&lt;&lt;PROFILE&gt;/etc/pki/raspberrypi/cfssl.json
{
   "signing": {
     "default": {
       "expiry": "17532h"
     },
     "profiles": {
       "intermediate_ca": {
         "usages": [
             "signing",
             "digital signature",
             "key encipherment",
             "cert sign",
             "crl sign",
             "server auth",
             "client auth"
         ],
         "expiry": "17532h",
         "ca_constraint": {
             "is_ca": true,
             "max_path_len": 0, 
             "max_path_len_zero": true
         }
       },
       "peer": {
         "usages": [
             "signing",
             "digital signature",
             "key encipherment", 
             "client auth",
             "server auth"
         ],
         "expiry": "17532h"
       },
       "server": {
         "usages": [
           "signing",
           "digital signing",
           "key encipherment",
           "server auth"
         ],
         "expiry": "17532h"
       },
       "client": {
         "usages": [
           "signing",
           "digital signature",
           "key encipherment", 
           "client auth"
         ],
         "expiry": "17532h"
       }
     }
   }
}
PROFILE
</code></pre>
<h4 id="heading-intermediate-certificate">Intermediate certificate</h4>
<pre><code class="lang-shell">root@raspberrypi:/etc/pki/raspberrypi# /bin/cat&lt;&lt;INTERMEDIATE&gt;/etc/pki/raspberrypi/intermediate-ca.json
{
  "CN": "Barrios Nunez Intermediate CA",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C":  "US",
      "L":  "CT",
      "O":  "Barrios Nunez",
      "OU": "Barrios Nunez Intermediate CA",
      "ST": "USA"
    }
  ],
  "ca": {
    "expiry": "43830h"
  }
}
INTERMEDIATE
cfssl gencert -initca intermediate-ca.json | cfssljson -bare intermediate_ca
cfssl sign -ca ca.pem -ca-key ca-key.pem -config cfssl.json -profile intermediate_ca intermediate_ca.csr | cfssljson -bare intermediate_ca
</code></pre>
<h4 id="heading-configuration-for-the-ssl-certificate-on-the-raspberry-pi-4-machine">Configuration for the SSL certificate on the Raspberry PI 4 machine</h4>
<p>Here we put the name and IP address of the machine that will run our Kismet web application:</p>
<pre><code class="lang-shell">/bin/cat&lt;&lt;RASPBERRYPI&gt;/etc/pki/raspberrypi/raspberrypi.home.json
{
  "CN": "raspberrypi.home",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
  {
    "C": "US",
    "L": "CT",
    "O": "Barrios Nunez",
    "OU": "Barrios Nunez Hosts",
    "ST": "USA"
  }
  ],
  "hosts": [
    "raspberrypi.home",
    "localhost",
    "raspberrypi",
    "192.168.1.11"
  ]               
}
RASPBERRYPI
cd /etc/pki/raspberrypi
cfssl gencert -ca intermediate_ca.pem -ca-key intermediate_ca-key.pem -config cfssl.json -profile=peer raspberrypi.home.json| cfssljson -bare raspberry-peer
cfssl gencert -ca intermediate_ca.pem -ca-key intermediate_ca-key.pem -config cfssl.json -profile=server raspberrypi.home.json| cfssljson -bare raspberry-server
cfssl gencert -ca intermediate_ca.pem -ca-key intermediate_ca-key.pem -config cfssl.json -profile=client raspberrypi.home.json| cfssljson -bare raspberry-client
</code></pre>
<p>Adding SSL support is then as easy as adding the following overrides:</p>
<pre><code class="lang-shell">/bin/cat&lt;&lt;SSL&gt;&gt;/etc/kismet/kismet_site.conf
httpd_ssl=true
httpd_ssl_cert=/etc/pki/raspberrypi/raspberry-server.csr
httpd_ssl_key=/etc/pki/raspberrypi/raspberry-server-key.pem
SSL
</code></pre>
<h3 id="heading-putting-everything-together-with-a-kismet-site-overrides-file">Putting everything together, with a Kismet 'site' overrides file</h3>
<p>Kismet has a really nice feature: it can use a file that overrides some defaults, without the need to edit multiple files. In this case my installation will override the SSL settings, Wifi interface, and log location. So time to update our /etc/rc.local file:</p>
<pre><code class="lang-shell">#!/bin/bash
# Kismet setup
usb_driver=rt2800usb
wlan=$(ls /sys/bus/usb/drivers/$usb_driver/*/net/)
if [ $? -eq 0 ]; then
    set -ex
    /usr/sbin/ifconfig "$wlan" down
    /usr/sbin/iwconfig "$wlan" mode monitor
    /usr/sbin/ifconfig "$wlan" up
    set +ex
    /bin/cat&lt;&lt;KISMETOVERR&gt;/etc/kismet/kismet_site.conf
server_name=Nunez Barrios Kismet server
logprefix=/data/kismet
source=$wlan
httpd_ssl=true
httpd_ssl_cert=/etc/pki/raspberrypi/raspberry-server.csr
httpd_ssl_key=/etc/pki/raspberrypi/raspberry-server-key.pem
KISMETOVERR
fi
</code></pre>
<p>Finally, it is time to start Kismet (in my case as the non-root user josevnz):</p>
<pre><code class="lang-python"><span class="hljs-comment"># If you know which interface is the one in monitoring mode, then </span>
josevnz@raspberrypi:~$ kismet
</code></pre>
<p>Now let's log on for the first time to the web interface (In my case <a target="_blank" href="http://raspberripi.home:2501">http://raspberripi.home:2501</a>)</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/03/kismet-set-login.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>You will get a prompt the first time you try to log in your Kismet installation</em></p>
<p>In here you set up your admin user and password.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/03/kismet-main-screen.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Example of the wireless networks detected</em></p>
<p>After a little time, Kismet will populate the main Dashboard with the list of wireless networks and devices it can detect. You will be surprised not just how many neighboring devices are out there but how many you have in your own house.</p>
<p>In my example, the wireless devices around me look pretty normal, except one that doesn't have a name:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/03/suspect-device-details-kismet.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>A device with suspicious characteristics</em></p>
<p>The web interface provides all sorts of useful information, but is there an easy way to filter all the mac addresses on my networks?</p>
<p>Kismet has a REST API, so it is time to see what we can automate from there.</p>
<h1 id="heading-rest-api-in-python">REST-API in Python</h1>
<p>The <a target="_blank" href="https://www.kismetwireless.net/docs/devel_group.html">developer documentation</a> contains examples of how to extend Kismet, specifically the one related to the <a target="_blank" href="https://github.com/kismetwireless/python-kismet-rest">official Kismet REST-API in Python</a>.</p>
<p>But it seems to be missing a feature to use API keys, instead of user/password. And the interaction with the end points doesn't seem to be complicated, so I will write my (less rich feature) wrapper.</p>
<p>You can download and install the code for a small application I wrote (<a target="_blank" href="https://github.com/josevnz/kismet_home">kismet_home</a> to illustrate how to work with Kismet (also has a copy of this tutorial) like this:</p>
<pre><code class="lang-shell">python3 -m venv ~/virtualenv/kismet_home
. ~/virtualenv/kismet_home/bin/activate
python -m pip install --upgrade pip
git clone git@github.com:josevnz/kismet_home.git
python setup.py bdist_wheel
pip install kismet_home-0.0.1-py3-none-any.whl
</code></pre>
<p>And then run the unit tests/ integration tests and even the third party vulnerability scanner:</p>
<pre><code class="lang-shell">. ~/virtualenv/kismet_home/bin/activate
# Unit/ integration tests
python -m unittest test/unit_test_config.py
python -m unittest /home/josevnz/kismet_home/test/test_integration_kismet.py
# Third party vulnerability scanner
pip-audit  --requirement requirements.txt
</code></pre>
<p>You will find more details on the <a target="_blank" href="https://github.com/josevnz/kismet_home/blob/main/README.md">README.md</a> and <a target="_blank" href="https://github.com/josevnz/kismet_home/blob/main/DEVELOPER.md">DEVELOPER.md</a> files.</p>
<p>Let's move on with the code.</p>
<h3 id="heading-how-to-interact-with-kismet-using-python">How to Interact with Kismet using Python</h3>
<p>First I'll write a generic HTTP client I can use to query or send commands to Kismet, that is the <em>KismetWorker</em> class:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> json
<span class="hljs-keyword">from</span> datetime <span class="hljs-keyword">import</span> datetime
<span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> Any, Dict, Set, List, Union
<span class="hljs-keyword">import</span> requests


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">KismetBase</span>:</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, *, api_key: str, url: str</span>):</span>
        <span class="hljs-string">"""
        Parametric constructor
        :param api_key: The Kismet generated API key
        :param url: URL where the Kismet server is running
        """</span>
        self.api_key = api_key
        <span class="hljs-keyword">if</span> url[<span class="hljs-number">-1</span>] != <span class="hljs-string">'/'</span>:
            self.url = <span class="hljs-string">f"<span class="hljs-subst">{url}</span>/"</span>
        <span class="hljs-keyword">else</span>:
            self.url = url
        self.cookies = {<span class="hljs-string">'KISMET'</span>: self.api_key}

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__str__</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-keyword">return</span> <span class="hljs-string">f"url=<span class="hljs-subst">{self.url}</span>, api_key=XXX"</span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">KismetWorker</span>(<span class="hljs-params">KismetBase</span>):</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">check_session</span>(<span class="hljs-params">self</span>) -&gt; <span class="hljs-keyword">None</span>:</span>
        <span class="hljs-string">"""
        Confirm if the session is valid for a given API key
        :return: None, throws an exception if the session is invalid
        """</span>
        endpoint = <span class="hljs-string">f"<span class="hljs-subst">{self.url}</span>session/check_session"</span>
        r = requests.get(endpoint, cookies=self.cookies)
        r.raise_for_status()

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">check_system_status</span>(<span class="hljs-params">self</span>) -&gt; Dict[str, Any]:</span>
        <span class="hljs-string">"""
        Overall status of the Kismet server
        :return: Nested dictionary describing different aspect of the Kismet system
        """</span>
        endpoint = <span class="hljs-string">f"<span class="hljs-subst">{self.url}</span>system/status.json"</span>
        r = requests.get(endpoint, cookies=self.cookies)
        r.raise_for_status()
        <span class="hljs-keyword">return</span> json.loads(r.text)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_all_alerts</span>(<span class="hljs-params">self</span>) -&gt; Any:</span>
        <span class="hljs-string">"""
        You can get a description how the alert system is set up as shown here: /alerts/definitions.prettyjson
        This method returns the last N alerts registered by the system. Severity and meaning of the alert is explained
        here: https://www.kismetwireless.net/docs/devel/webui_rest/alerts/
        :return:
        """</span>
        endpoint = <span class="hljs-string">f"<span class="hljs-subst">{self.url}</span>alerts/all_alerts.json"</span>
        r = requests.get(endpoint, cookies=self.cookies)
        r.raise_for_status()
        <span class="hljs-keyword">return</span> json.loads(r.text)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_alert_by_hash</span>(<span class="hljs-params">self, identifier: str</span>) -&gt; Dict[str, Any]:</span>
        <span class="hljs-string">"""
        Get details of a single alert by its identifier (hash)
        :return:
        """</span>
        parsed = int(identifier)
        <span class="hljs-keyword">if</span> parsed &lt; <span class="hljs-number">0</span>:
            <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">f"Invalid ID provided: <span class="hljs-subst">{identifier}</span>"</span>)
        endpoint = <span class="hljs-string">f"<span class="hljs-subst">{self.url}</span>alerts/by-id/<span class="hljs-subst">{identifier}</span>/alert.json"</span>
        r = requests.get(endpoint, cookies=self.cookies)
        r.raise_for_status()
        <span class="hljs-keyword">return</span> json.loads(r.text)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_alert_definitions</span>(<span class="hljs-params">self</span>) -&gt; Dict[Union[str, int], Any]:</span>
        <span class="hljs-string">"""
        Get the defined alert types
        :return:
        """</span>
        endpoint = <span class="hljs-string">f"<span class="hljs-subst">{self.url}</span>alerts/definitions.json"</span>
        r = requests.get(endpoint, cookies=self.cookies)
        r.raise_for_status()
        <span class="hljs-keyword">return</span> json.loads(r.text)
</code></pre>
<p>The way Kismet API works is that you make the API KEY part of the query, or you define it in the KISMET cookie. I choose to populate the cookie.</p>
<p>KismetWorker implements the following methods:</p>
<ul>
<li><p><strong>check_session</strong>: It checks if your API KEY is valid. If not it will throw an exception.</p>
</li>
<li><p><strong>check_system_status</strong>: Validates if the administrator (you most likely) defined an administrator for the Kismet server. If not, then all the API queries will fail.</p>
</li>
<li><p><strong>get_all_alerts</strong>: Gets all the available alerts (if any) from your Kismet server.</p>
</li>
<li><p><strong>get_alert_by_hash</strong>: If you know the identifier (hash) of an alert, you can retrieve the details of that event only.</p>
</li>
<li><p><strong>get_alert_definitions</strong>: Get all the alert definitions. Kismet supports a wide range of alerts and a user will definitely be interested to find out what type of alerts they are.</p>
</li>
</ul>
<p>You can see <a target="_blank" href="https://github.com/josevnz/kismet_home/blob/main/test/test_integration_kismet.py">all the integration code</a> here to see how the methods work in action.</p>
<p>I also wrote a class that requires admin privileges. I use it to define a custom alert type and to send alerts using that type to Kismet, as part of the integration tests. Right now I don't have much use of sending custom alerts to Kismet in real life, but that may change in the future, so here is the code:</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">KismetAdmin</span>(<span class="hljs-params">KismetBase</span>):</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">define_alert</span>(<span class="hljs-params">
            self,
            *,
            name: str,
            description: str,
            throttle: str = <span class="hljs-string">'10/min'</span>,
            burst: str = <span class="hljs-string">"1/sec"</span>,
            severity: int = <span class="hljs-number">5</span>,
            aclass: str = <span class="hljs-string">'SYSTEM'</span>

    </span>):</span>
        <span class="hljs-string">"""
        Define a new type of alert for Kismet
        :param aclass: Alert class
        :param severity: Alert severity
        :param throttle: Optional throttle
        :param name: Name of the new alert
        :param description: What does this mean
        :param burst: Optional burst
        :return:
        """</span>
        endpoint = <span class="hljs-string">f"<span class="hljs-subst">{self.url}</span>alerts/definitions/define_alert.cmd"</span>
        command = {
            <span class="hljs-string">'name'</span>: name,
            <span class="hljs-string">'description'</span>: description,
            <span class="hljs-string">'throttle'</span>: throttle,
            <span class="hljs-string">'burst'</span>: burst,
            <span class="hljs-string">'severity'</span>: severity,
            <span class="hljs-string">'class'</span>: aclass
        }
        r = requests.post(endpoint, json=command, cookies=self.cookies)
        r.raise_for_status()

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">raise_alert</span>(<span class="hljs-params">
            self,
            *,
            name: str,
            message: str
    </span>) -&gt; <span class="hljs-keyword">None</span>:</span>
        <span class="hljs-string">"""
        Send an alert to Kismet
        :param name: A well-defined name or id for the alert. MUST exist
        :param message: Message to send
        :return: None. Will raise an error if the alert could not be sent
        """</span>
        endpoint = <span class="hljs-string">f"<span class="hljs-subst">{self.url}</span>alerts/raise_alerts.cmd"</span>
        command = {
            <span class="hljs-string">'name'</span>: name,
            <span class="hljs-string">'text'</span>: message
        }
        r = requests.post(endpoint, json=command, cookies=self.cookies)
        r.raise_for_status()
</code></pre>
<p>Getting the data is just part of the story. We need to normalize it, so it can be used by the final scripts.</p>
<h3 id="heading-how-to-normalize-the-kismet-raw-data">How to Normalize the Kismet raw data</h3>
<p>Kismet contains a lot of details about the alerts, but we do not require to show the user those details (think about the nice view you get with the web application). Instead we do a few transformations using the following class with static methods:</p>
<ul>
<li><p><strong>parse_alert_definitions</strong>: Returns a simplified report of all the alert definitions</p>
</li>
<li><p><strong>process_alerts</strong>: Changes numeric alerts for more descriptive types and also returns dictionaries for the types and severity meaning of those alerts.</p>
</li>
<li><p><strong>pretty_timestamp</strong>: Converts the numeric timestamp into something we can use for comparisons and display</p>
</li>
</ul>
<p>The code for the <em>KismetResultsParser</em> helper class:</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">KismetResultsParser</span>:</span>
    SEVERITY = {
        <span class="hljs-number">0</span>: {
            <span class="hljs-string">'name'</span>: <span class="hljs-string">'INFO'</span>,
            <span class="hljs-string">'description'</span>: <span class="hljs-string">'Informational alerts, such as datasource  errors, Kismet state changes, etc'</span>
        },
        <span class="hljs-number">5</span>: {
            <span class="hljs-string">'name'</span>: <span class="hljs-string">'LOW'</span>,
            <span class="hljs-string">'description'</span>: <span class="hljs-string">'Low - risk events such as probe fingerprints'</span>
        },
        <span class="hljs-number">10</span>: {
            <span class="hljs-string">'name'</span>: <span class="hljs-string">'MEDIUM'</span>,
            <span class="hljs-string">'description'</span>: <span class="hljs-string">'Medium - risk events such as denial of service attempts'</span>
        },
        <span class="hljs-number">15</span>: {
            <span class="hljs-string">'name'</span>: <span class="hljs-string">'HIGH'</span>,
            <span class="hljs-string">'description'</span>: <span class="hljs-string">'High - risk events such as fingerprinted watched devices, denial of service attacks, '</span>
                           <span class="hljs-string">'and similar '</span>
        },
        <span class="hljs-number">20</span>: {
            <span class="hljs-string">'name'</span>: <span class="hljs-string">'CRITICAL'</span>,
            <span class="hljs-string">'description'</span>: <span class="hljs-string">'Critical errors such as fingerprinted known exploits'</span>
        }
    }

    TYPES = {
        <span class="hljs-string">'DENIAL'</span>: <span class="hljs-string">'Possible denial of service attack'</span>,
        <span class="hljs-string">'EXPLOIT'</span>: <span class="hljs-string">'Known fingerprinted exploit attempt against a vulnerability'</span>,
        <span class="hljs-string">'OTHER'</span>: <span class="hljs-string">'General category for alerts which don’t fit in any existing bucket'</span>,
        <span class="hljs-string">'PROBE'</span>: <span class="hljs-string">'Probe by known tools'</span>,
        <span class="hljs-string">'SPOOF'</span>: <span class="hljs-string">'Attempt to spoof an existing device'</span>,
        <span class="hljs-string">'SYSTEM'</span>: <span class="hljs-string">'System events, such as log changes, datasource errors, etc.'</span>
    }

<span class="hljs-meta">    @staticmethod</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">parse_alert_definitions</span>(<span class="hljs-params">
            *,
            alert_definitions: List[Dict[str, str]],
            keys_of_interest: Set[str] = None
    </span>) -&gt; List[Dict[str, str]]:</span>
        <span class="hljs-string">"""
        Remove unwanted keys from full alert definition dump, to make it easier to read onscreen
        :param alert_definitions: Original Kismet alert definitions
        :param keys_of_interest: Kismet keys of interest
        :return: List of dictionaries with trimmed keys, description, severity and header for easy reading
        """</span>
        <span class="hljs-keyword">if</span> keys_of_interest <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:
            keys_of_interest = {
                <span class="hljs-string">'kismet.alert.definition.class'</span>,
                <span class="hljs-string">'kismet.alert.definition.description'</span>,
                <span class="hljs-string">'kismet.alert.definition.severity'</span>,
                <span class="hljs-string">'kismet.alert.definition.header'</span>
            }
        parsed_alerts: List[Dict[str, str]] = []
        <span class="hljs-keyword">for</span> definition <span class="hljs-keyword">in</span> alert_definitions:
            new_definition = {}
            <span class="hljs-keyword">for</span> def_key <span class="hljs-keyword">in</span> definition:
                <span class="hljs-keyword">if</span> def_key <span class="hljs-keyword">in</span> keys_of_interest:
                    new_key = def_key.split(<span class="hljs-string">'.'</span>)[<span class="hljs-number">-1</span>]
                    new_definition[new_key] = definition[def_key]
            parsed_alerts.append(new_definition)
        <span class="hljs-keyword">return</span> parsed_alerts

<span class="hljs-meta">    @staticmethod</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">process_alerts</span>(<span class="hljs-params">
            *,
            alerts: List[Dict[str, Union[str, int]]],

    </span>) -&gt; Any:</span>
        <span class="hljs-string">"""
        Removed unwanted fields from alert details, also return extra data for severity and types of alerts
        :param alerts:
        :return:
        """</span>
        processed_alerts = []
        found_types = {}
        found_severities = {}
        <span class="hljs-keyword">for</span> alert <span class="hljs-keyword">in</span> alerts:
            severity = alert[<span class="hljs-string">'kismet.alert.severity'</span>]
            severity_name = KismetResultsParser.SEVERITY[severity][<span class="hljs-string">'name'</span>]
            severity_desc = KismetResultsParser.SEVERITY[severity][<span class="hljs-string">'description'</span>]
            found_severities[severity_name] = severity_desc
            text = alert[<span class="hljs-string">'kismet.alert.text'</span>]
            aclass = alert[<span class="hljs-string">'kismet.alert.class'</span>]
            found_types[aclass] = KismetResultsParser.TYPES[aclass]
            processed_alert = {
                <span class="hljs-string">'text'</span>: text,
                <span class="hljs-string">'class'</span>: aclass,
                <span class="hljs-string">'severity'</span>: severity_name,
                <span class="hljs-string">'hash'</span>: alert[<span class="hljs-string">'kismet.alert.hash'</span>],
                <span class="hljs-string">'dest_mac'</span>: alert[<span class="hljs-string">'kismet.alert.dest_mac'</span>],
                <span class="hljs-string">'source_mac'</span>: alert[<span class="hljs-string">'kismet.alert.source_mac'</span>],
                <span class="hljs-string">'timestamp'</span>: alert[<span class="hljs-string">'kismet.alert.timestamp'</span>]
            }
            processed_alerts.append(processed_alert)
        <span class="hljs-keyword">return</span> processed_alerts, found_severities, found_types

<span class="hljs-meta">    @staticmethod</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">pretty_timestamp</span>(<span class="hljs-params">timestamp: float</span>) -&gt; datetime:</span>
        <span class="hljs-string">"""
        Convert a Kismet timestamp (TIMESTAMP.UTIMESTAMP) into a pretty timestamp string
        :param timestamp:
        :return:
        """</span>
        <span class="hljs-keyword">return</span> datetime.fromtimestamp(timestamp)
</code></pre>
<p>If you run the integration tests with the admin role enabled, you will see than one or more (depending how many times you ran the test) alerts were added to the Web UI:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/03/kismet_generated_alerts.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>These alerts where generated using the Python client and the REST API</em></p>
<p>As a reminder, you can see how this is used by looking at the code <a target="_blank" href="https://github.com/josevnz/kismet_home/blob/main/test/test_integration_kismet.py">here</a>. Showing a sample run of all the integration tests against my installation (this one without publishing alerts, so some tests are skipped):</p>
<pre><code class="lang-shell">(kismet_home) [josevnz@dmaf5 kismet_home]$ python -m unittest /home/josevnz/kismet_home/test/test_integration_kismet.py 
[09:13:05] DEBUG    Starting new HTTP connection (1): raspberrypi.home:2501                                                                                                                                                        connectionpool.py:228
           DEBUG    http://raspberrypi.home:2501 "GET /session/check_session HTTP/1.1" 200 None                                                                                                                                    connectionpool.py:456
.           DEBUG    Starting new HTTP connection (1): raspberrypi.home:2501                                                                                                                                                        connectionpool.py:228
           DEBUG    http://raspberrypi.home:2501 "GET /system/status.json HTTP/1.1" 200 None                                                                                                                                       connectionpool.py:456
.           DEBUG    Starting new HTTP connection (1): raspberrypi.home:2501                                                                                                                                                        connectionpool.py:228
           DEBUG    http://raspberrypi.home:2501 "GET /alerts/definitions.json HTTP/1.1" 200 None                                                                                                                                  connectionpool.py:456
.[09:13:05] 'ADMIN_SESSION_API' environment variable not defined. Skipping this test                                                                                                                                       test_integration_kismet.py:105
....
----------------------------------------------------------------------
Ran 7 tests in 0.053s

OK
</code></pre>
<h3 id="heading-where-do-we-store-our-api-key-and-other-configuration-details">Where do we store our API key and other configuration details?</h3>
<p>Details like this won't be hardcoded inside the scripts, but instead they will reside on an external configuration file:</p>
<pre><code class="lang-shell">(kismet_home) [josevnz@dmaf5 kismet_home]$ cat ~/.config/kodegeek/kismet_home/config.ini 
[server]
url = http://raspberrypi.home:2501
api_key = E41CAD466552810392D538FF8D43E2C5
</code></pre>
<p>The following classes handle all the access details (using a Reader and a Writer class for each type of operation):</p>
<pre><code class="lang-python"><span class="hljs-string">"""
Simple configuration management for kismet_home settings
"""</span>
<span class="hljs-keyword">import</span> os.path
<span class="hljs-keyword">from</span> configparser <span class="hljs-keyword">import</span> ConfigParser
<span class="hljs-keyword">from</span> pathlib <span class="hljs-keyword">import</span> Path
<span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> Dict

<span class="hljs-keyword">from</span> kismet_home <span class="hljs-keyword">import</span> CONSOLE

DEFAULT_INI = os.path.expanduser(<span class="hljs-string">'~/.config/kodegeek/kismet_home/config.ini'</span>)
VALID_KEYS = {<span class="hljs-string">'api_key'</span>, <span class="hljs-string">'url'</span>}


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Reader</span>:</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, config_file: str = DEFAULT_INI</span>):</span>
        <span class="hljs-string">"""
        Constructor
        :param config_file: Optional override of the ini configuration file
        """</span>
        self.config = ConfigParser()
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> self.config.read(config_file):
            <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">f"Could not read <span class="hljs-subst">{config_file}</span>"</span>)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_api_key</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-string">"""
        Get back the API key used to connect to Kismet
        :return:
        """</span>
        <span class="hljs-keyword">return</span> self.config.get(<span class="hljs-string">'server'</span>, <span class="hljs-string">'api_key'</span>)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_url</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-string">"""
        Get back URL of Kismet server
        :return:
        """</span>
        <span class="hljs-keyword">return</span> self.config.get(<span class="hljs-string">'server'</span>, <span class="hljs-string">'url'</span>)


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Writer</span>:</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">
            self,
            *,
            server_keys: Dict[str, str]
    </span>):</span>
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> server_keys:
            <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">"Configuration is incomplete!, aborting!"</span>)
        self.config = ConfigParser()
        self.config.add_section(<span class="hljs-string">'server'</span>)
        valid_keys_cnt = <span class="hljs-number">0</span>
        <span class="hljs-keyword">for</span> key <span class="hljs-keyword">in</span> server_keys:
            value = server_keys[key]
            <span class="hljs-keyword">if</span> key <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> VALID_KEYS:
                CONSOLE.log(<span class="hljs-string">f"Ignoring invalid key: <span class="hljs-subst">{key}</span> = <span class="hljs-subst">{value}</span>"</span>)
                <span class="hljs-keyword">continue</span>
            self.config.set(<span class="hljs-string">'server'</span>, key, value)
            CONSOLE.log(<span class="hljs-string">f"Added: server: <span class="hljs-subst">{key}</span> = <span class="hljs-subst">{value}</span>"</span>)
        <span class="hljs-keyword">for</span> valid_key <span class="hljs-keyword">in</span> VALID_KEYS:
            <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> self.config.get(<span class="hljs-string">'server'</span>, valid_key):
                <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">f"Missing required key: <span class="hljs-subst">{valid_key}</span>"</span>)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">save</span>(<span class="hljs-params">
            self,
            *,
            config_file: str = DEFAULT_INI
    </span>):</span>
        basedir = Path(config_file).parent
        basedir.mkdir(exist_ok=<span class="hljs-literal">True</span>, parents=<span class="hljs-literal">True</span>)
        <span class="hljs-keyword">with</span> open(config_file, <span class="hljs-string">'w'</span>) <span class="hljs-keyword">as</span> config:
            self.config.write(config, space_around_delimiters=<span class="hljs-literal">True</span>)
        CONSOLE.log(<span class="hljs-string">f"Configuration file <span class="hljs-subst">{config_file}</span> written"</span>)
</code></pre>
<p>The first time you set up your kismet_home installation, you can create the configuration files like this:</p>
<pre><code class="lang-shell">[josevnz@dmaf5 kismet_home]$ python3 -m venv ~/virtualenv/kismet_home
[josevnz@dmaf5 kismet_home]$ . ~/virtualenv/kismet_home/bin/activate
(kismet_home) [josevnz@dmaf5 kismet_home]$ python -m pip install --upgrade pip
(kismet_home) [josevnz@dmaf5 kismet_home]$ git clone git@github.com:josevnz/kismet_home.git
(kismet_home) [josevnz@dmaf5 kismet_home]$ python setup.py bdist_wheel
(kismet_home) [josevnz@dmaf5 kismet_home]$ pip install kismet_home-0.0.1-py3-none-any.whl

(kismet_home) [josevnz@dmaf5 kismet_home]$ kismet_home_config.py 
Please enter the URL of your Kismet server: http://raspberrypi.home:2501/
Please enter your API key: E41CAD466552810392D538FF8D43E2C5
[13:02:35] Added: server: url = http://raspberrypi.home:2501/                                                                                 config.py:44
           Added: server: api_key = E41CAD466552810392D538FF8D43E2C5                                                                          config.py:44
           Configuration file /home/josevnz/.config/kodegeek/kismet_home/config.ini written
</code></pre>
<p>Please note the use of the virtual environment here. This will allow us to keep the application's libraries self-contained.</p>
<h2 id="heading-putting-everything-together-how-to-write-our-cli-for-kismethome">Putting everything together: How to Write our CLI for kismet_home</h2>
<p>The <em>kismet_home_alerts.py</em> script will support two modes:</p>
<ul>
<li><p>Show the alert definitions</p>
</li>
<li><p>Show all the alerts</p>
</li>
</ul>
<p>Also, it will allow filtering alerts based on the level (INFO, MEDIUM, HIGH, ...).</p>
<p>Showing all the definitions, filtered by CRITICAL:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/03/alert_definitions_filtered_by_level.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>You can see here the alert definitions filtered by level</em></p>
<p>Or showing all the alerts received so far, with anonymous MAC address (great for screenshots like this):</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/03/kismet_home_alerts.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Alerts for my local network, with anonymous MAC addresses and filtered</em></p>
<p>How you can generate these tables with ease? There is a dedicated class for the text user interface (TUI):</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> List, Dict, Any

<span class="hljs-keyword">from</span> rich.layout <span class="hljs-keyword">import</span> Layout
<span class="hljs-keyword">from</span> rich.table <span class="hljs-keyword">import</span> Table

<span class="hljs-keyword">from</span> kismet_home.kismet <span class="hljs-keyword">import</span> KismetResultsParser


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create_alert_definition_table</span>(<span class="hljs-params">
        *,
        alert_definitions: List[Dict[str, Any]],
        level_filter: str = <span class="hljs-number">0</span>
</span>) -&gt; Table:</span>
    <span class="hljs-string">"""
    Create a table showing the alert definitions
    :param alert_definitions: Alert definitions from Kismet
    :param level_filter: User can override the level of the alerts shown. But default is 0 (INFO)
    :return: A Table with the alert definitions
    """</span>
    definition_table = Table(title=<span class="hljs-string">"Alert definitions"</span>)
    definition_table.add_column(<span class="hljs-string">"Severity"</span>, justify=<span class="hljs-string">"right"</span>, style=<span class="hljs-string">"cyan"</span>, no_wrap=<span class="hljs-literal">True</span>)
    definition_table.add_column(<span class="hljs-string">"Description"</span>, style=<span class="hljs-string">"magenta"</span>)
    definition_table.add_column(<span class="hljs-string">"Header"</span>, justify=<span class="hljs-string">"right"</span>, style=<span class="hljs-string">"yellow"</span>)
    definition_table.add_column(<span class="hljs-string">"Class"</span>, justify=<span class="hljs-string">"right"</span>, style=<span class="hljs-string">"green"</span>)
    filter_level = KismetResultsParser.get_level_for_security(level_filter)
    filtered_definitions = <span class="hljs-number">0</span>
    <span class="hljs-keyword">for</span> definition <span class="hljs-keyword">in</span> alert_definitions:
        int_severity: int = definition[<span class="hljs-string">'severity'</span>]
        <span class="hljs-keyword">if</span> int_severity &lt; filter_level:
            <span class="hljs-keyword">continue</span>
        severity = KismetResultsParser.SEVERITY[int_severity][<span class="hljs-string">'name'</span>]
        <span class="hljs-keyword">if</span> <span class="hljs-number">0</span> &lt;= int_severity &lt; <span class="hljs-number">5</span>:
            severity = <span class="hljs-string">f"[bold blue]<span class="hljs-subst">{severity}</span>[/ bold blue]"</span>
        <span class="hljs-keyword">if</span> <span class="hljs-number">5</span> &lt;= int_severity &lt; <span class="hljs-number">10</span>:
            severity = <span class="hljs-string">f"[bold yellow]<span class="hljs-subst">{severity}</span>[/ bold yellow]"</span>
        <span class="hljs-keyword">if</span> <span class="hljs-number">10</span> &lt;= int_severity &lt; <span class="hljs-number">15</span>:
            severity = <span class="hljs-string">f"[bold orange]<span class="hljs-subst">{severity}</span>[/ bold orange]"</span>
        <span class="hljs-keyword">else</span>:
            severity = <span class="hljs-string">f"[bold red]<span class="hljs-subst">{severity}</span>[/ bold red]"</span>
        filtered_definitions += <span class="hljs-number">1</span>
        definition_table.add_row(
            severity,
            definition[<span class="hljs-string">'description'</span>],
            definition[<span class="hljs-string">'header'</span>],
            definition[<span class="hljs-string">'class'</span>]
        )
    definition_table.caption = <span class="hljs-string">f"Total definitions: <span class="hljs-subst">{filtered_definitions}</span>"</span>
    <span class="hljs-keyword">return</span> definition_table


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create_alert_layout</span>(<span class="hljs-params">
        *,
        alerts: List[Dict[str, Any]],
        level_filter: str = <span class="hljs-number">0</span>,
        anonymize: bool = False,
        severities: Dict[str, str]
</span>):</span>
    <span class="hljs-string">"""
    :param severities:
    :param alerts:
    :param level_filter:
    :param anonymize:
    :return:
    """</span>
    alerts_table = Table(title=<span class="hljs-string">"Alert definitions"</span>)
    alerts_table.add_column(<span class="hljs-string">"Timestamp"</span>, no_wrap=<span class="hljs-literal">True</span>)
    alerts_table.add_column(<span class="hljs-string">"Severity"</span>, justify=<span class="hljs-string">"right"</span>, style=<span class="hljs-string">"cyan"</span>, no_wrap=<span class="hljs-literal">True</span>)
    alerts_table.add_column(<span class="hljs-string">"Text"</span>, style=<span class="hljs-string">"magenta"</span>)
    alerts_table.add_column(<span class="hljs-string">"Source MAC"</span>, justify=<span class="hljs-string">"right"</span>, style=<span class="hljs-string">"yellow"</span>, no_wrap=<span class="hljs-literal">True</span>)
    alerts_table.add_column(<span class="hljs-string">"Destination MAC"</span>, justify=<span class="hljs-string">"right"</span>, style=<span class="hljs-string">"yellow"</span>, no_wrap=<span class="hljs-literal">True</span>)
    alerts_table.add_column(<span class="hljs-string">"Class"</span>, justify=<span class="hljs-string">"right"</span>, style=<span class="hljs-string">"green"</span>, no_wrap=<span class="hljs-literal">True</span>)
    filter_level = KismetResultsParser.get_level_for_security(level_filter)

    filtered_definitions = <span class="hljs-number">0</span>
    <span class="hljs-keyword">for</span> alert <span class="hljs-keyword">in</span> alerts:
        int_severity: int = KismetResultsParser.get_level_for_security(alert[<span class="hljs-string">'severity'</span>])
        <span class="hljs-keyword">if</span> int_severity &lt; filter_level:
            <span class="hljs-keyword">continue</span>
        severity = KismetResultsParser.SEVERITY[int_severity][<span class="hljs-string">'name'</span>]
        <span class="hljs-keyword">if</span> <span class="hljs-number">0</span> &lt;= int_severity &lt; <span class="hljs-number">5</span>:
            severity = <span class="hljs-string">f"[bold blue]<span class="hljs-subst">{severity}</span>[/ bold blue]"</span>
        <span class="hljs-keyword">if</span> <span class="hljs-number">5</span> &lt;= int_severity &lt; <span class="hljs-number">10</span>:
            severity = <span class="hljs-string">f"[bold yellow]<span class="hljs-subst">{severity}</span>[/ bold yellow]"</span>
        <span class="hljs-keyword">if</span> <span class="hljs-number">10</span> &lt;= int_severity &lt; <span class="hljs-number">15</span>:
            severity = <span class="hljs-string">f"[bold orange]<span class="hljs-subst">{severity}</span>[/ bold orange]"</span>
        <span class="hljs-keyword">else</span>:
            severity = <span class="hljs-string">f"[bold red]<span class="hljs-subst">{severity}</span>[/ bold red]"</span>
        filtered_definitions += <span class="hljs-number">1</span>
        <span class="hljs-keyword">if</span> anonymize:
            s_mac = KismetResultsParser.anonymize_mac(alert[<span class="hljs-string">'source_mac'</span>])
            d_mac = KismetResultsParser.anonymize_mac(alert[<span class="hljs-string">'dest_mac'</span>])
        <span class="hljs-keyword">else</span>:
            s_mac = alert[<span class="hljs-string">'source_mac'</span>]
            d_mac = alert[<span class="hljs-string">'dest_mac'</span>]
        alerts_table.add_row(
            str(KismetResultsParser.pretty_timestamp(alert[<span class="hljs-string">'timestamp'</span>])),
            severity,
            alert[<span class="hljs-string">'text'</span>],
            s_mac,
            d_mac,
            alert[<span class="hljs-string">'class'</span>]
        )
    alerts_table.caption = <span class="hljs-string">f"Total alerts: <span class="hljs-subst">{filtered_definitions}</span>"</span>

    severities_table = Table(title=<span class="hljs-string">"Severity legend"</span>)
    severities_table.add_column(<span class="hljs-string">"Severity"</span>)
    severities_table.add_column(<span class="hljs-string">"Explanation"</span>)
    <span class="hljs-keyword">for</span> severity <span class="hljs-keyword">in</span> severities:
        explanation = <span class="hljs-string">f"[green]<span class="hljs-subst">{severities[severity]}</span>[/green]"</span>
        severities_table.add_row(<span class="hljs-string">f"[yellow]<span class="hljs-subst">{severity}</span>[/yellow]"</span>, explanation)

    layout = Layout()
    layout.split(
        Layout(ratio=<span class="hljs-number">2</span>, name=<span class="hljs-string">"alerts"</span>),
        Layout(name=<span class="hljs-string">"severities"</span>),
    )
    layout[<span class="hljs-string">"alerts"</span>].update(alerts_table)
    layout[<span class="hljs-string">"severities"</span>].update(severities_table)
    <span class="hljs-keyword">return</span> layout, filtered_definitions
</code></pre>
<p>And now with all the ingredients ready, we can see how the final script looks:</p>
<pre><code class="lang-python"><span class="hljs-comment">#!/usr/bin/env python</span>
<span class="hljs-string">"""
# kismet_home_alerts.py
# Author
Jose Vicente Nunez Zuleta (kodegeek.com@protonmail.com)
"""</span>
<span class="hljs-keyword">import</span> logging
<span class="hljs-keyword">import</span> sys

<span class="hljs-keyword">from</span> requests <span class="hljs-keyword">import</span> HTTPError
<span class="hljs-keyword">import</span> argparse

<span class="hljs-keyword">from</span> kismet_home <span class="hljs-keyword">import</span> CONSOLE
<span class="hljs-keyword">from</span> kismet_home.config <span class="hljs-keyword">import</span> Reader
<span class="hljs-keyword">from</span> kismet_home.kismet <span class="hljs-keyword">import</span> KismetWorker, KismetResultsParser
<span class="hljs-keyword">from</span> kismet_home.tui <span class="hljs-keyword">import</span> create_alert_definition_table, create_alert_layout

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">'__main__'</span>:

    arg_parser = argparse.ArgumentParser(
        description=<span class="hljs-string">"Display alerts generated by your local Kismet installation"</span>,
        prog=__file__
    )
    arg_parser.add_argument(
        <span class="hljs-string">'--debug'</span>,
        action=<span class="hljs-string">'store_true'</span>,
        default=<span class="hljs-literal">False</span>,
        help=<span class="hljs-string">"Enable debug mode"</span>
    )
    arg_parser.add_argument(
        <span class="hljs-string">'--anonymize'</span>,
        action=<span class="hljs-string">'store_true'</span>,
        default=<span class="hljs-literal">False</span>,
        help=<span class="hljs-string">"Anonymize MAC addresses"</span>
    )
    arg_parser.add_argument(
        <span class="hljs-string">'--level'</span>,
        action=<span class="hljs-string">'store'</span>,
        default=<span class="hljs-string">'INFO'</span>,
        help=<span class="hljs-string">"Enable debug mode"</span>
    )
    arg_parser.add_argument(
        <span class="hljs-string">'mode'</span>,
        action=<span class="hljs-string">'store'</span>,
        choices=[<span class="hljs-string">'alert_type'</span>, <span class="hljs-string">'alerts'</span>],
        help=<span class="hljs-string">"Operation mode"</span>
    )

    <span class="hljs-keyword">try</span>:
        args = arg_parser.parse_args()
        conf_reader = Reader()
        kw = KismetWorker(
            api_key=conf_reader.get_api_key(),
            url=conf_reader.get_url()
        )
        <span class="hljs-keyword">if</span> args.mode == <span class="hljs-string">'alert_type'</span>:
            alert_definitions = KismetResultsParser.parse_alert_definitions(
                alert_definitions=kw.get_alert_definitions()
            )
            table = create_alert_definition_table(alert_definitions=alert_definitions, level_filter=args.level)
            <span class="hljs-keyword">if</span> table.columns:
                CONSOLE.print(table)
            <span class="hljs-keyword">else</span>:
                CONSOLE.print(<span class="hljs-string">f"[b]Could not get alert definitions![/b]"</span>)
        <span class="hljs-keyword">elif</span> args.mode == <span class="hljs-string">'alerts'</span>:
            alerts, severities, types = KismetResultsParser.process_alerts(
                alerts=kw.get_all_alerts()
            )
            layout, found = create_alert_layout(
                alerts=alerts,
                level_filter=args.level,
                anonymize=args.anonymize,
                severities=severities
            )
            <span class="hljs-keyword">if</span> found:
                CONSOLE.print(layout)
            <span class="hljs-keyword">else</span>:
                CONSOLE.print(<span class="hljs-string">f"[b]No alerts to show for level=<span class="hljs-subst">{args.level}</span>[/b]"</span>)
    <span class="hljs-keyword">except</span> (ValueError, HTTPError):
        logging.exception(<span class="hljs-string">"There was an error"</span>)
        sys.exit(<span class="hljs-number">100</span>)
    <span class="hljs-keyword">except</span> KeyboardInterrupt:
        CONSOLE.log(<span class="hljs-string">"Scan interrupted, exiting..."</span>)
    sys.exit(<span class="hljs-number">0</span>)
</code></pre>
<p>A few things to note:</p>
<ul>
<li><p>This is not a long-running application. Instead, is a snapshot of all the alerts. If you wanted, for example, to forward these alerts by email or to a framework like <a target="_blank" href="https://grafana.com/">grafana</a>, you are better off using <a target="_blank" href="https://pypi.org/project/websockets/">Websockets</a> and one of the methods that retrieves only the last changes.</p>
</li>
<li><p>The layout is crude, and there is plenty of room for improvement. But our little tui is displaying relevant information without too many distractions</p>
</li>
<li><p>And if was fun to code!</p>
</li>
</ul>
<h1 id="heading-what-did-we-learn">What did we learn?</h1>
<ul>
<li><p>How to install Kismet and secure it with a self-signed SSL certificate</p>
</li>
<li><p>How to write a simple Bash script to set up the correct Wireless interface in monitor mode, after the RaspBerryPI reboots</p>
</li>
<li><p>How to add an API KEY with read-only access to use it instead of the legacy user/ password schema for authentication and authorization</p>
</li>
<li><p>How to write classes in Python that can communicate with Kismet using its REST-API</p>
</li>
<li><p>How to add unit and integration tests to the code to make sure everything works and new code changes do not break existing functionality</p>
</li>
</ul>
<p>Please leave your comments on the <a target="_blank" href="https://github.com/josevnz/kismet_home">git repository</a> and report any bugs. But more important get Kismet, get the code of this tutorial, and start securing your home wireless infrastructure in no time.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ What is Raspberry Pi? Specs and Models (2021 Guide) ]]>
                </title>
                <description>
                    <![CDATA[ By Veronica Stork Introduction Are you curious about the IoT (Internet of Things?) Have you always wanted to try to make your own robot, smart mirror, or bird feeder camera? What about building a computer for a fraction of the cost of a commercially ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/what-is-raspberry-pi-specs-and-models-2021-guide/</link>
                <guid isPermaLink="false">66d46181787a2a3b05af4410</guid>
                
                    <category>
                        <![CDATA[ Internet of Things ]]>
                    </category>
                
                    <category>
                        <![CDATA[ iot ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Raspberry Pi ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Wed, 25 Aug 2021 16:17:20 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/08/Raspberry-Pi-2-Bare-FL-bigger.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Veronica Stork</p>
<h2 id="heading-introduction">Introduction</h2>
<p>Are you curious about the IoT (Internet of Things?) Have you always wanted to try to make your own robot, smart mirror, or bird feeder camera? What about building a computer for a fraction of the cost of a commercially available machine?</p>
<p>If you said yes to any of these questions, you might enjoy playing around with a Raspberry Pi. </p>
<p>In this article, I'll explain what a Raspberry Pi is (and what it’s not.) Then I'll show you some things you can use it for, and finally, I'll list all of the current models along with their specs.</p>
<h2 id="heading-what-is-raspberry-pi">What is Raspberry Pi?</h2>
<p>A Raspberry Pi is a single board computer (SBC) created in the United Kingdom by the <a target="_blank" href="https://www.raspberrypi.org/about/">Raspberry Pi Foundation</a>. It's a charity that "works to put the power of computing and digital making into the hands of people all over the world."</p>
<p>The first model of the Raspberry Pi was released in 2012, and as of 2021 there have been five generations of the boards. A microcontroller (more about that later), called the Pico was released in early 2021.</p>
<h3 id="heading-gpio-pins">GPIO Pins</h3>
<p>Something that sets the Pi apart from your average computer is the set of 40 GPIO (General Purpose Input Output) pins. </p>
<p>GPIO pins are pretty much exactly what they sound like. They are designed to input and output single bits. This means that you can use them to add all sorts of functionality to your Raspberry Pi using switches, buzzers, lights, sensors, and so on.</p>
<h3 id="heading-raspberry-pi-hats">Raspberry Pi HATs</h3>
<p>There are a number of add-on boards that you can attach to the Raspberry Pi using the GPIO pins. </p>
<p>Raspberry Pi HATs (Hardware Attached on Top) are add-on boards designed according to certain specifications. The Raspberry Pi can automatically detect and configure the HATs, making set-up easy.</p>
<p>There is a <em>huge</em> variety of HATs and other add-on boards that you can buy, but here are some notable ones:</p>
<ul>
<li><strong><a target="_blank" href="https://www.raspberrypi.org/products/poe-plus-hat/">PoE+ HAT</a></strong> – Power over Ethernet HAT</li>
<li><strong><a target="_blank" href="https://www.raspberrypi.org/products/sense-hat/">Sense HAT</a></strong> – Designed for the <a target="_blank" href="https://astro-pi.org/">Astro Pi mission</a>, the Sense HAT includes a gyroscope, an accelerometer, a magnetometer, and sensors for temperature, humidity, and Barometric pressure.  </li>
<li><strong><a target="_blank" href="https://shop.pimoroni.com/products/explorer-hat">Pimoroni Explorer HAT Pro</a></strong> – All-purpose board featuring touch pads, crocodile clip pads, and analog inputs</li>
<li><strong><a target="_blank" href="https://www.adafruit.com/product/2340">Adafruit Capacitive Touch HAT</a></strong> – Similar to a <a target="_blank" href="https://makeymakey.com/">Makey Makey</a>, this HAT allows you to use any conductive object to trigger events using Python.</li>
</ul>
<h3 id="heading-raspberry-pi-operating-systems">Raspberry Pi Operating Systems</h3>
<p>The Raspberry Pi often runs some form of Linux, but there are a ton of operating systems that you can use. </p>
<p>On the official website, you will find a list of <a target="_blank" href="https://www.raspberrypi.org/software/operating-systems/">operating system images</a> available for download. These include the official Raspberry Pi OS, Debian Buster, and Ubuntu (desktop, core, and server.) </p>
<p>You will also find RetroPie, a specialized gaming platform operating system, and LibreELEC, a lightweight Linux distribution specifically created for use with the open source media player <a target="_blank" href="https://kodi.tv/">Kodi</a>.</p>
<h3 id="heading-raspberry-pi-vs-arduino">Raspberry Pi VS Arduino</h3>
<p>You may have heard of Arduino boards and wondered what the difference is between them and the Raspberry Pi. </p>
<p>The main difference is that Pis (with the exception of the Pico and the RP2040) are full computers with operating systems. You can connect your Pi to a keyboard, mouse, and monitor and use it like you would use a Mac or PC. </p>
<p>The Arduino, on the other hand, is a microcontroller. It cannot function independently as a computer, but is programmed using a computer and then used to control things like cameras, lights, robots, and so on. </p>
<p>As the official Arduino website puts it: “Arduino boards are able to read inputs… and turn it into an output.”</p>
<h2 id="heading-what-is-the-raspberry-pi-used-for">What is the Raspberry Pi Used For?</h2>
<p>Search the internet and you will find a huge array of projects created using Raspberry Pis. </p>
<p>Common use cases include home automation, gaming consoles, servers, WiFi extenders, streaming devices, weather stations, and home computers. (Fun fact: much of this article was written on a Raspberry Pi.)</p>
<p>During the Covid-19 pandemic, Raspberry Pi's were even used to control <a target="_blank" href="https://www.engadget.com/raspberry-pi-ventilators-covid-19-163729140.html">ventilators</a> in some hard-hit areas.</p>
<h2 id="heading-raspberry-pi-models">Raspberry Pi Models</h2>
<p>All models of Raspberry Pi that are currently in production have 40 GPIO pins. </p>
<p>This list does not include the Raspberry Pi microcontrollers, the Pico and the RP2040. </p>
<p>You can find out where to purchase any of these boards on the <a target="_blank" href="https://www.raspberrypi.org/products/">Raspberry Pi website</a>.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Model</td><td>Processor</td><td>RAM</td><td>Connectivity</td><td>USB</td><td>HDMI</td><td>Power</td><td>Price</td></tr>
</thead>
<tbody>
<tr>
<td>Zero</td><td>BCM2835</td><td>512MB</td><td>None</td><td>Micro USB OTG</td><td>Mini HDMI</td><td>Micro USB</td><td>$5</td></tr>
<tr>
<td>Zero W</td><td>BCM2835</td><td>512MB</td><td>802.11 b/g/n wireless LAN</td><td>Micro USB OTG</td><td>Mini HDMI</td><td>Micro USB</td><td>$10</td></tr>
<tr>
<td>Raspberry Pi 1 Model A+</td><td>BCM2835</td><td>512MB</td><td>None</td><td>1x USB 2.0</td><td>Full-size HDMI</td><td>Micro USB</td><td>$25</td></tr>
<tr>
<td>Raspberry Pi 1 Model B+</td><td>BCM2835</td><td>512MB</td><td>100 base ethernet</td><td>4x USB 2.0</td><td>Full-size HDMI</td><td>Micro USB</td><td>$30</td></tr>
<tr>
<td>Raspberry Pi 3 Model A+</td><td>BCM2837B0</td><td>512MB</td><td>dual-band wireless, Bluetooth 4.2</td><td>1x USB 2.0</td><td>Full-size HDMI</td><td>5V DC via Micro USB</td><td>$25</td></tr>
<tr>
<td>Raspberry Pi 3 Model B</td><td>BCM2837</td><td>1GB</td><td>ethernet, wireless, BLE</td><td>4x USB 2.0</td><td>Full-size HDMI</td><td>2.1A via Micro USB</td><td>$35</td></tr>
<tr>
<td>Raspberry Pi 3 Model B+</td><td>BCM2837B0</td><td>1GB</td><td>dual-band wireless, Bluetooth 4.2, BLE</td><td>4x USB 2.0</td><td>Full-size HDMI</td><td>5V DC via Micro USB &amp; Power-over-Ethernet (PoE)</td><td>$35</td></tr>
<tr>
<td>Raspberry Pi 4 Model B</td><td>BCM2711</td><td>2GB, 4GB, or 8GB</td><td>Gigabit ethernet, dual-band wireless, Bluetooth</td><td>2x USB 3.0 &amp; 2x USB 2.0</td><td>2x micro HDMI</td><td>5V DC via USB-C</td><td>$35, $55, $75</td></tr>
<tr>
<td>Raspberry Pi 400</td><td>BCM2711</td><td>4GB</td><td>Gigabit ethernet, dual-band wireless, Bluetooth</td><td>2x USB 3.0 &amp; 1x USB 2.0</td><td>2x micro HDMI</td><td>5V DC via USB</td><td>$70</td></tr>
</tbody>
</table>
</div><h2 id="heading-conclusion">Conclusion</h2>
<p>The Raspberry Pi is an affordable way to explore electronics, hardware, and computer programming. It can be used for a huge variety of projects, from the very silly (like a <a target="_blank" href="https://www.raspberrypi.org/blog/hamsters-all-the-way-down/">hamster powered hamster drawing machine</a>) to the very important (like <a target="_blank" href="https://www.raspberrypi.org/blog/building-computer-labs-in-western-africa/">computer labs</a> in developing nations.)</p>
<p>Now that you know the basics, why don't you go out and make something? Whether you hook a Capacitive Touch HAT up to your Raspberry Pi and turn it into a banana piano or install Linux on it and use it to do your homework, I hope you have a great time creating something cool and useful.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Personal Dev Server on a $5 Raspberry Pi ]]>
                </title>
                <description>
                    <![CDATA[ In this article, you'll learn how to build a personal dev server by installing Git, Node.js, Rust, and Docker on a Raspberry Pi. The cheapest option costs just $5. You can get a starter kit ($25) for free here. The Raspberry Pi is a very powerful com... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-a-personal-dev-server-on-a-5-dollar-raspberry-pi/</link>
                <guid isPermaLink="false">66d46042230dff0166905827</guid>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Git ]]>
                    </category>
                
                    <category>
                        <![CDATA[ node js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Raspberry Pi ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Rust ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Michael Yuan ]]>
                </dc:creator>
                <pubDate>Fri, 17 Jul 2020 20:46:37 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/07/IMG_8632.JPG" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this article, you'll learn how to build a personal dev server by installing Git, Node.js, Rust, and Docker on a Raspberry Pi. The cheapest option costs just $5. <a target="_blank" href="https://www.secondstate.io/articles/raspberry-pi-for-free-20200709/">You can get a starter kit ($25) for free here</a>.</p>
<p>The Raspberry Pi is a very powerful computer in a tiny package. The cheapest option, the <a target="_blank" href="https://www.raspberrypi.org/products/raspberry-pi-zero/">Raspberry Pi Zero</a>, is capable of running a fully featured Linux distribution and driving a high definition display. It is the size of two coins (US Quarters) and costs $5.</p>
<p>At $10, the <a target="_blank" href="https://www.raspberrypi.org/products/raspberry-pi-zero-w/">Raspberry Pi Zero W</a> comes with integrated WiFi and Bluetooth.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/07/img_8603.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>The $10 Raspberry Pi Zero W has a powerful CPU, WiFi, Bluetooth, and all kinds of connectors</em></p>
<p>At the “high end”, you can purchase a <a target="_blank" href="https://www.raspberrypi.org/products/raspberry-pi-4-desktop-kit/">Raspberry Pi 4 desktop kit</a> for less than $100. It has a 4-core ARM CPU running at 1.5GHz, GPU, 2GB (up to 8 GB) of RAM, 16GB (up to 2TB) of storage on MicroSD cards, wifi and Ethernet connectors, USB ports, HDMI ports that can drive 4K displays, as well as a keyboard and mouse.</p>
<p>The Raspberry Pi is also more than a standard computer. It is fun and hackable. The Raspberry Pi exposes a row of GPIO (General Purpose Input Output) pins. You can attach simple sensors (eg. temperature, humidity, light) to those pins, and capture their data from your applications running on the Pi.</p>
<p>You could also attach LED lights and motors to those pins, and use your Pi application to drive those peripheral devices.</p>
<p>For more complex sensors or devices, such as camera modules, you can also connect to the Pi via USB or Wifi, and access them in software. The Pi is a great device for learning and hardware hacking. Because of this, it is widely used in educational settings.</p>
<p>However, fun and learning are not just for kids. With so much computing power and easy networking, the Raspberry Pi can easily become a personal application server for you.</p>
<p>For example, you can put a web application (for example, a collaborative note taking app, or just some documents / videos to share) on a Pi, bring it to a meeting, and make it accessible to everyone in the room. You do not even need the Internet. It is completely decentralized and censorship-resistant.</p>
<p>The personal server is especially useful for developers. You can have a separate environment to deploy and test your server-side applications without having to mess with your laptop. A personal dev server is like Docker on steroids. In this article, I will teach you how to set one up.</p>
<h2 id="heading-first-get-a-raspberry-pi">First, get a Raspberry Pi</h2>
<p>If this is your first Raspberry Pi, the easiest (and most expensive) way to set up is just to buy a <a target="_blank" href="https://www.raspberrypi.org/products/raspberry-pi-4-desktop-kit/">desktop kit for around $100</a>. It comes with everything you need for a computer except for the display.</p>
<p>If you are using the Pi as a personal dev server, you would NOT need a display after the initial setup. You can just SSH into it from your laptop once it is turned on!</p>
<p><a target="_blank" href="https://www.secondstate.io/articles/raspberry-pi-for-free-20200709/">Learn how</a> to get your Raspberry Pi starter kit for free when you participate in this <a target="_blank" href="https://www.secondstate.io/articles/getting-started-with-rust-function/">high performance web application learning exercise</a>.</p>
<p>Of course, if you have spare computer parts, such as MicroSD cards, USB power supply, a keyboard, and a mouse laying around, you could save money by purchasing only the boards. You could get a Raspberry Pi Zero board for $5 and a Raspberry Pi 4 board for $35.</p>
<p>But the thing missing from the board is a MicroSD card that acts as the “hard drive” for storing the operating system and data. You can purchase a 16GB MicroSD card for $10 online, a MicroSD card reader, and use the <a target="_blank" href="https://www.raspberrypi.org/downloads/">Raspberry Pi Imager</a> to load an operating system onto the MicroSD card from your laptop.</p>
<p>The two popular choices are Raspberry Pi OS and Ubuntu Linux. Both are Debian-based Linux distributions. Most starter kits pre-install the Raspberry Pi OS on their MicroSD cards (it is called NOOBS).</p>
<p>In the next two sections, I will talk you through both operating systems.</p>
<h2 id="heading-how-to-set-up-raspberry-pi-os">How to set up Raspberry Pi OS</h2>
<p>Once you put in the MicroSD card with NOOBS, and connect a display, a keyboard, and a mouse, you can turn on the power!</p>
<p>From there, just follow on screen instructions to install Raspberry Pi OS (previously known as the Raspbian OS). Then setup a password for the user pi, and setup the wifi connection.</p>
<p>After you are logged in, go to the Preferences → Raspberry Pi Configuration menu and enable SSH. That will allow you to log into the Pi from another computer.</p>
<p><strong>Note</strong>: in order to use the Pi as a “headless” server, you could request a static IP address from your router. In the future, you can just power on the Pi, and connect to it via SSH from your other computers or phones.</p>
<p>The Raspberry Pi OS is derived from the Debian Linux distribution. It comes with a full desktop UI environment with a modern web browser, a command line terminal, and learning programs such as IDEs for Python, Java, and Scratch.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/07/IMG_8672.JPG" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>My Raspberry Pi 4 with Raspberry Pi OS setup. Notice how small the actual computer is.</em></p>
<p>For our purposes, we are mostly interested in installing dev and server software through the command line terminal.</p>
<p>At this point, you could also find out the IP address of the Pi on your local network by running the following command. Then you can SSH into the Pi using that local IP address, username pi, and the password you gave pi during setup.</p>
<pre><code class="lang-javascript">$ hostname -I
<span class="hljs-number">192.168</span><span class="hljs-number">.2</span><span class="hljs-number">.108</span> <span class="hljs-number">172.17</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>
</code></pre>
<p>You can find a <a target="_blank" href="https://n8henrie.com/2019/08/list-of-default-packages-on-raspbian-buster-lite/">complete list of software packages installed on the Raspberry Pi OS here</a>. It is always a good idea to update and upgrade to the latest packages. Run the command below and be patient. It could take an hour.</p>
<pre><code class="lang-javascript">$ sudo apt update &amp;&amp; sudo apt upgrade
</code></pre>
<h2 id="heading-how-to-set-up-ubuntu-server-2004">How to set up Ubuntu Server 20.04</h2>
<p>The Raspberry Pi OS is primarily geared toward a desktop experience. For developers who just want to use the device as a server or IoT device, the Ubuntu Linux is a much better choice. It has the latest software packages and libraries, and could be far more efficient without the desktop windows, web browser, Java, games, and learning tools.</p>
<p>You can download <a target="_blank" href="https://ubuntu.com/download/raspberry-pi">Ubuntu Server images for Raspberry Pi</a> from the web, and load it on a MicroSD card. But perhaps a much easier way is just to use the <a target="_blank" href="https://www.raspberrypi.org/downloads/">Raspberry Pi Imager</a>, select Ubuntu Server 20.04 TLS from the menu, and write into an empty MicroSD card.</p>
<p>Once the MicroSD card is prepared, you should <a target="_blank" href="https://ubuntu.com/tutorials/how-to-install-ubuntu-on-your-raspberry-pi#3-wifi-or-ethernet">follow these instructions</a> to put in your WiFi network name and password. This allows the Raspberry Pi device to connect to the network as soon as it boots.</p>
<p>Basically, you can just put the MicroSD card into the Raspberry Pi, connect USB power, then wait for it to come online. You can find the <code>raspberrypi</code> device IP from your WiFi router, and then SSH into from any computer on your network.</p>
<p>The initial username and password are <code>ubuntu / ubuntu</code>. There is no need to even connect a monitor or keyboard. That’s it for a completely headless setup!</p>
<p><strong>Note</strong>: if, for some reason, your Raspberry Pi cannot connect to WiFi at startup, you can hook up an HDMI display and a USB keyboard to it. Then <a target="_blank" href="https://linuxconfig.org/ubuntu-20-04-connect-to-wifi-from-command-line">follow these instructions</a> to debug and set up WiFi on the running system.</p>
<p>Next, let's install the developer tool stack on the Pi.</p>
<h2 id="heading-install-git">Install Git</h2>
<p>I always install Git on all my development environments because a lot of software can be directly retrieved from Git repositories. It saves me the trouble of downloading and copying.</p>
<p>Git also allows me to save and backup my own work in private repositories. For a small computer like Raspberry Pi, I would recommend that you save work in Git in case you lose the device or MicroSD card.</p>
<p>The following command installs Git:</p>
<pre><code class="lang-javascript">$ sudo apt install git
</code></pre>
<h2 id="heading-install-nodejs">Install Node.js</h2>
<p>To turn the Raspberry Pi into a personal dev server for web applications, you need to install a modern web application runtime.</p>
<p>For most developers today, the best starting point is Node.js, which allows you to write server-side applications in JavaScript. The following two commands install Node.js on your Pi.</p>
<pre><code class="lang-javascript">$ curl -sL https:<span class="hljs-comment">//deb.nodesource.com/setup_10.x | sudo bash -</span>
$ sudo apt install nodejs
</code></pre>
<p>You can verify the installation is done correctly by running the following two commands. Both node and npm are now available.</p>
<pre><code class="lang-javascript">$ node -v
v10<span class="hljs-number">.19</span><span class="hljs-number">.0</span>
$ npm -v
<span class="hljs-number">5.8</span><span class="hljs-number">.0</span>
</code></pre>
<p>From here, you can use npm to install modules. For example, a commonly used npm module is the express framework for web applications.</p>
<pre><code class="lang-javascript">$ npm install express
</code></pre>
<p>Now, you can go through the <a target="_blank" href="https://expressjs.com/en/starter/hello-world.html">ExpressJS hello world example</a> to create a web server on your Pi, and use web browsers from any computer on your network to access the application!</p>
<h2 id="heading-install-rust">Install Rust</h2>
<p>Rust is a fast growing programming language for writing both systems and web applications. It is close to the hardware, high performance, and memory safe. That makes Rust a great language for writing applications on resource constrained devices like the Raspberry Pi.</p>
<p>Also, Rust is the most beloved programming language by StackOverflow users for the past 5 years in a row. It is well worth your time to learn it!</p>
<p>An important use case of Rust is to compile <a target="_blank" href="https://www.secondstate.io/articles/getting-started-with-rust-function/">Rust functions into WebAssembly and run them inside Node.js</a> applications to achieve <a target="_blank" href="https://www.secondstate.io/articles/why-webassembly-server/">performance, safety, and code portability</a>. It is a great choice for running computationally intensive web applications on a small <a target="_blank" href="https://www.secondstate.io/articles/get-started-with-raspberry-pi-20200708/">Raspberry Pi device</a>. In fact, you could <a target="_blank" href="https://www.secondstate.io/articles/raspberry-pi-for-free-20200709/">get a free Raspberry Pi starter kit</a> if you learn how to do that.</p>
<p>Note: strictly speaking, you do not need to install Rust tools on the Pi. You typically only need to run Rust programs in the Pi. You can compile your Rust program on any computer and then copy the compiled binaries to the Pi.</p>
<p>But still, with the powerful CPU, you can compile Rust programs on the Raspberry Pi. So why not?</p>
<p>The following command installs the Rust compiler toolchain on the Pi.</p>
<pre><code class="lang-javascript">$ curl --proto <span class="hljs-string">'=https'</span> --tlsv1<span class="hljs-number">.2</span> -sSf https:<span class="hljs-comment">//sh.rustup.rs | sh</span>
</code></pre>
<p>Run the following command to set up the correct path without logging out and back in again.</p>
<pre><code class="lang-javascript">$ source $HOME/.cargo/env
</code></pre>
<p>The above command also installs the Rust package manager called cargo. Most Rust developers use cargo to build and share their work.</p>
<pre><code class="lang-javascript">$ cargo -V
cargo <span class="hljs-number">1.44</span><span class="hljs-number">.1</span> (<span class="hljs-number">88</span>ba85757 <span class="hljs-number">2020</span><span class="hljs-number">-06</span><span class="hljs-number">-11</span>)
</code></pre>
<p>Next, you can clone our <a target="_blank" href="https://github.com/second-state/wasm-learning/">Rust learning repository</a>, and learn from examples.</p>
<pre><code class="lang-javascript">$ git clone https:<span class="hljs-comment">//github.com/second-state/wasm-learning.git</span>
</code></pre>
<p>Here is the <a target="_blank" href="https://www.secondstate.io/articles/a-rusty-hello-world/">hello world example</a>. Have fun!</p>
<pre><code class="lang-javascript">$ cd wasm-learning/rust/hello
$ cargo build
   Compiling hello v0<span class="hljs-number">.1</span><span class="hljs-number">.0</span> (<span class="hljs-regexp">/home/</span>pi/Dev/wasm-learning/rust/hello)
    Finished dev [unoptimized + debuginfo] target(s) <span class="hljs-keyword">in</span> <span class="hljs-number">4.35</span>s
$ target/debug/hello
Hello, world!
</code></pre>
<p>Check out the <a target="_blank" href="https://www.rust-lang.org/learn">official Rust web site</a> and the <a target="_blank" href="https://rust-by-example-ext.com/">Rust by Example</a> books for more learning resources.</p>
<h2 id="heading-learn-docker">Learn Docker</h2>
<p>We have seen that the Raspberry Pi OS and Ubuntu Server are both very capable Linux distributions with lots of software packages.</p>
<p>But what if I want to test applications on other OSes? Do I need to wipe clean and reinstall a different OS on the MicroSD card? The answer is no. You can just use Docker! The following two commands install docker on the Raspberry Pi:</p>
<pre><code class="lang-javascript">$ curl -fsSL https:<span class="hljs-comment">//get.docker.com -o get-docker.sh</span>
$ sudo sh get-docker.sh
</code></pre>
<p>Run the following command so that you can use Docker as the pi user:</p>
<pre><code class="lang-javascript">$ sudo usermod -aG docker pi
</code></pre>
<p>The Docker info command shows that Docker is now installed on an ARM system with Raspberry Pi OS.</p>
<pre><code class="lang-javascript">$ docker info
... ...
 Kernel Version: <span class="hljs-number">4.19</span><span class="hljs-number">.118</span>-v7l+
 Operating System: Raspbian GNU/Linux <span class="hljs-number">10</span> (buster)
 <span class="hljs-attr">OSType</span>: linux
 <span class="hljs-attr">Architecture</span>: armv7l
 <span class="hljs-attr">CPUs</span>: <span class="hljs-number">4</span>
 Total Memory: <span class="hljs-number">3.814</span>GiB
 <span class="hljs-attr">Name</span>: raspberrypi
 <span class="hljs-attr">ID</span>: XERI:ZVVZ:XQVA:HXSH:KRPI:<span class="hljs-number">6</span>GL2:<span class="hljs-number">5</span>QRE:E7GZ:Z72Q:<span class="hljs-number">6</span>SGF:CEI6:GKTC
 Docker Root Dir: <span class="hljs-regexp">/var/</span>lib/docker
... ...
</code></pre>
<p>Next, you can pull a Docker image for the latest Ubuntu distribution, run it, and log into Ubuntu as a command line user.</p>
<pre><code class="lang-javascript">$ docker pull ubuntu
... ...
$ docker run -it ubuntu bash
root# ... enter commands ...
</code></pre>
<h2 id="heading-whats-next">What’s next?</h2>
<p>In this article, we have touched on the basics and learned how to turn your Raspberry Pi 4 device into a personal dev server for software developers.</p>
<p>There is much to learn about Git, Node.js, Rust, WebAssembly and Docker. There are also many other developer stacks you can install on the Raspberry Pi.</p>
<p><a target="_blank" href="https://www.secondstate.io/articles/raspberry-pi-for-free-20200709/">Grab your free Raspberry Pi kit</a> and let us know what you did with it!</p>
<p><a target="_blank" href="https://webassemblytoday.substack.com/">Subscribe to our newsletter</a> and stay in touch.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use Node, a Raspberry Pi, and an LCD Screen to Monitor the Weather ]]>
                </title>
                <description>
                    <![CDATA[ By Stan Georgian Over the last few years, smart home devices have gone from less than 300,000 back in 2015 up to almost 1.2 billion in 2020. And they’re expected to grow to 1.5 billion by 2021.  So it's likely you have at least some smart devices in ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/monitor-the-weather-with-node-and-raspberry-pi/</link>
                <guid isPermaLink="false">66d45ee0230dff0166905801</guid>
                
                    <category>
                        <![CDATA[ hardware ]]>
                    </category>
                
                    <category>
                        <![CDATA[ projects ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Raspberry Pi ]]>
                    </category>
                
                    <category>
                        <![CDATA[ smart home ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Tue, 30 Jun 2020 18:34:14 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/06/vinicius-amnx-amano-ALpEkP29Eys-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Stan Georgian</p>
<p>Over the last few years, smart home devices have gone from less than 300,000 back in 2015 up to almost 1.2 billion in 2020. And they’re expected to grow to <a target="_blank" href="https://www.omdia.com/resources/product-content/how-the-smart-home-will-develop-by-2021">1.5 billion by 2021</a>. </p>
<p>So it's likely you have at least some smart devices in your home, given that the average will reach 8.7 smart devices per home by 2021.</p>
<p>As developers, we have some advantage in this domain, since we can build our own smart home devices.</p>
<p>It’s not only the devices that have experienced rapid development. The development boards used for them have started to become more and more commercial and accessible. </p>
<p>In this article, we will see how we can use a Raspberry Pi, an LCD screen, and a few lines of code to monitor the weather outside or for a specific location.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/ezgif-6-8af115ff0d25.gif" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Because this is a Do It Yourself (DIY) project, there are some prerequisites that we need for this device.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<ul>
<li>Raspberry Pi 3 (or higher)</li>
<li>LCD Screen</li>
<li>Connection Wires</li>
<li>Potentiometer (Optional)</li>
<li>Breadboard (Optional)</li>
</ul>
<h1 id="heading-how-to-build-it">How to Build It</h1>
<p>As soon as we have everything we need we can start. Let's take it step by step.</p>
<h2 id="heading-step-i-base-configuration">Step I - Base Configuration</h2>
<p>The first step consists of the basic setup and a verification of all the components.</p>
<p>For this demo, we will use the ClimaCell Weather API as a weather data provider, as they have a large number of indicators, including air quality indicators, for us to use.</p>
<p>To use their API we must open an account on their platform and get an API key, which we’ll use to sign our requests.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/bQHbP2FU.png" alt="Image" width="600" height="400" loading="lazy">
<em>ClimaCell API Limit</em></p>
<p>The account is free to open and it comes with a 100-hour limit of API calls, which is more than enough for our project.</p>
<p>As soon as we have this API key, we can move to the hardware configuration and connect the LCD screen to our Raspberry Pi. You should turn the Raspberry Pi off while you make the wire connection.</p>
<p>The pin layout for Raspberry Pi 3 can be seen in the next image.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/emiVLiHU.png" alt="Image" width="600" height="400" loading="lazy">
<em>Raspberry Pi 3 Pins</em></p>
<p>The wire connection between the LCD and the development board is the following:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/WWjB6lbg.png" alt="Image" width="600" height="400" loading="lazy">
<em>Connection between Raspberry PI and LCD</em></p>
<p>This hardware connection will make the LCD screen be on full brightness and full contrast. The brightness level is not a problem, but contrast is because we won’t be able to see the characters on the screen. </p>
<p>That’s why we need to introduce at least one potentiometer with which to set the contrast level.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/DFu8N63z.png" alt="Image" width="600" height="400" loading="lazy">
<em>Schematic</em></p>
<p>At this point, we can turn on our Raspberry Pi and we should see the LCD screen alive. With the help of variable resistance we should be able to control the contrast.</p>
<h2 id="heading-step-ii-project-configuration">Step II - Project Configuration</h2>
<p>As a programming language, we’ll use <a target="_blank" href="https://nodejs.org/en/">NodeJS</a> to write the code. If you don’t already have NodeJS installed on your Raspberry then you can follow these <a target="_blank" href="https://www.instructables.com/id/Install-Nodejs-and-Npm-on-Raspberry-Pi/">simple instructions</a>.</p>
<p>In a new folder, run the command <code>npm init -y</code> to set up a new npm package, followed by the command <code>npm install lcd node-fetch</code> to install these 2 necessary dependencies.  </p>
<ul>
<li><code>lcd</code> will be used to communicate with the LCD Screen</li>
<li><code>node-fetch</code>  will be used to make <a target="_blank" href="https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol">HTTP</a> requests to ClimaCell API.</li>
</ul>
<p>We said that we need an API key to communicate with the weather data provider. You place your secret API key directly in the main code, or you can create a <code>config.json</code> file in which you can place this key and any other code-related configuration you may have.</p>
<p><code>config.json</code></p>
<pre><code class="lang-javascript">{  <span class="hljs-string">"cc_key"</span>: <span class="hljs-string">"&lt;your_ClimaCell_API_key&gt;"</span>}
</code></pre>
<p>Lastly, let’s create the main file of our project and include all these things we talked about.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// * Dependencies</span>
<span class="hljs-keyword">const</span> Lcd = <span class="hljs-built_in">require</span>(<span class="hljs-string">"lcd"</span>);
<span class="hljs-keyword">const</span> fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">"fs"</span>);
<span class="hljs-keyword">const</span> fetch = <span class="hljs-built_in">require</span>(<span class="hljs-string">"node-fetch"</span>);

<span class="hljs-comment">// * Globals</span>
<span class="hljs-keyword">const</span> { cc_key } = <span class="hljs-built_in">JSON</span>.parse(fs.readFileSync(<span class="hljs-string">"./config.json"</span>));
</code></pre>
<h2 id="heading-step-iii-the-lcd">Step III - The LCD</h2>
<p>Writing on the screen is a piece of cake using the lcd module. This library acts as a layer of abstraction over how we communicate with the device. In this way we don’t need to micro-manage each command individually.</p>
<p>The entire code for our LCD screen is the following:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/MidH14Tk.png" alt="Image" width="600" height="400" loading="lazy">
<em>[RAW](https://carbon.now.sh/?bg=rgba(171%2C%20184%2C%20195%2C%201)&amp;t=seti&amp;wt=none&amp;l=javascript&amp;ds=true&amp;dsyoff=20px&amp;dsblur=68px&amp;wc=true&amp;wa=true&amp;pv=56px&amp;ph=56px&amp;ln=false&amp;fl=1&amp;fm=Hack&amp;fs=14px&amp;lh=133%25&amp;si=false&amp;es=2x&amp;wm=false&amp;code=const%2520lcd%2520%253D%2520new%2520Lcd(%257B%2520rs%253A%252026%252C%2520e%253A%252019%252C%2520data%253A%2520%255B13%252C%25206%252C%25205%252C%252011%255D%252C%2520cols%253A%252016%252C%2520rows%253A%25202%2520%257D)%253B%250A%250A%250Afunction%2520writeToLcd(col%252C%2520row%252C%2520data)%2520%257B%250A%2520%2520return%2520new%2520Promise((resolve%252C%2520reject)%2520%253D%253E%2520%257B%250A%2520%2520%2520%2520lcd.setCursor(col%252C%2520row)%253B%250A%2520%2520%2520%2520lcd.print(data%252C%2520(err)%2520%253D%253E%2520%257B%250A%2520%2520%2520%2520%2520%2520if%2520(err)%2520%257B%250A%2520%2520%2520%2520%2520%2520%2520%2520reject()%253B%250A%2520%2520%2520%2520%2520%2520%257D%250A%2520%2520%2520%2520%2520%2520resolve()%253B%250A%2520%2520%2520%2520%257D)%253B%250A%2520%2520%257D)%253B%250A%257D)</em></p>
<p>The first step was to create a new <code>lcd</code> object and to pass as argument the pins we’ve used.</p>
<p>The keys <code>cols</code> and <code>rows</code> represent the number of columns and rows of our LCD display. 16x2 is the one I used in this example. If your LCD has just 8 columns and 1 row, then replace 16 and 2 with your values.</p>
<p>To write something on the display we need to use these two methods successively:</p>
<ul>
<li>lcd.setCursor() - selecting the position from which to start writing</li>
<li>lcd.print()</li>
</ul>
<p>At the same time, we wrapped these two functions in a promise to make use of <code>async/away</code> keywords.</p>
<p>At this point, you can use this function and print something on your display. <code>writeToLcd(0,0,'Hello World')</code> should print the message <code>Hello World</code> on the first row starting from the first column.</p>
<h2 id="heading-step-iv-the-weather-data">Step IV - The Weather Data</h2>
<p>The next step is to get the weather data and print it on the display.</p>
<p>ClimaCell provides a lot of weather data information, but also air quality and pollen, fire and other information. The data is vast, but keep in mind that your LCD screen only has 16 columns and 2 rows – that’s just 32 characters.</p>
<p>If you want to display more types of data and this limit is too small for you, then you can use a scroll effect.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/81w9nkUg.gif" alt="Image" width="600" height="400" loading="lazy"></p>
<p>For this demo we’ll keep it simple and we’ll print on the LCD screen the following data:</p>
<ul>
<li>current date (hour, minutes, seconds)</li>
<li>temperature</li>
<li>precipitation intensity</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/zj8FQisB.png" alt="Image" width="600" height="400" loading="lazy">
_[RAW](https://carbon.now.sh/?bg=rgba(171%2C%20184%2C%20195%2C%201)&amp;t=seti&amp;wt=none&amp;l=javascript&amp;ds=true&amp;dsyoff=20px&amp;dsblur=68px&amp;wc=true&amp;wa=true&amp;pv=56px&amp;ph=56px&amp;ln=false&amp;fl=1&amp;fm=Hack&amp;fs=14px&amp;lh=133%25&amp;si=false&amp;es=2x&amp;wm=false&amp;code=async%2520function%2520getWeatherData(apiKey%252C%2520lat%252C%2520lon)%2520%257B%250A%2520%2520const%2520url%2520%253D%2520%2560https%253A%252F%252Fapi.climacell.co%252Fv3%252Fweather%252Frealtime%253Flat%253D%2524%257Blat%257D%2526lon%253D%2524%257Blon%257D%2526unit_system%253Dsi%2526fields%253Dtemp%2526fields%253Dprecipitation%2526apikey%253D%2524%257BapiKey%257D%2560%253B%250A%250A%2520%2520const%2520res%2520%253D%2520await%2520fetch(url)%253B%250A%2520%2520const%2520data%2520%253D%2520await%2520res.json()%253B%250A%2520%2520return%2520data%253B%250A%257D%250A%250Aasync%2520function%2520printWeatherData()%2520%257B%250A%2520%2520const%2520%257B%2520temp%252C%2520precipitation%2520%257D%2520%253D%2520await%2520getWeatherData(cc<em>key%252C%252045.658%252C%252025.6012)%253B%250A%250A%2520%2520%252F%252F%2520<em>%2520first%2520row%250A%2520%2520await%2520writeToLcd(0%252C%25200%252C%2520Math.round(temp.value)%2520%252B%2520temp.units)%253B%250A%250A%2520%2520%252F%252F%2520</em>%2520second%2520row%250A%2520%2520const%2520precipitationMessage%2520%253D%250A%2520%2520%2520%2520%2522Precip.%253A%2520%2522%2520%252B%2520precipitation.value%2520%252B%2520precipitation.units%253B%250A%2520%2520await%2520writeToLcd(0%252C%25201%252C%2520precipitationMessage)%253B%250A%257D)</em></p>
<p>To get data from ClimaCell for a specific location, then you need to send its geographical coordinates, latitude and longitude.</p>
<p>To find your city’s coordinates, you can use a free tool like <a target="_blank" href="https://www.latlong.net/place/new-york-city-ny-usa-1848.html">latlong.net</a> and then you can save them in <code>config.json</code> file along with your API key, or you can write them directly in the code.</p>
<p>At this point the data format returned by the API call is the following:</p>
<pre><code class="lang-javascript">{
  <span class="hljs-attr">lat</span>: <span class="hljs-number">45.658</span>,
  <span class="hljs-attr">lon</span>: <span class="hljs-number">25.6012</span>,
  <span class="hljs-attr">temp</span>: { <span class="hljs-attr">value</span>: <span class="hljs-number">17.56</span>, <span class="hljs-attr">units</span>: <span class="hljs-string">'C'</span> },
  <span class="hljs-attr">precipitation</span>: { <span class="hljs-attr">value</span>: <span class="hljs-number">0.3478</span>, <span class="hljs-attr">units</span>: <span class="hljs-string">'mm/hr'</span> },
  <span class="hljs-attr">observation_time</span>: { <span class="hljs-attr">value</span>: <span class="hljs-string">'2020-06-22T16:30:22.941Z'</span> }
}
</code></pre>
<p>We can deconstruct this object and get the temp and the precipitation values and print them on the first and second row.</p>
<h2 id="heading-step-v-wrap-it-up">Step V - Wrap it Up</h2>
<p>All we need to do now is to write the logic for our script, and update the LCD screen when new data arrives.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/oeM4lSfQ.png" alt="Image" width="600" height="400" loading="lazy">
<em>[RAW](https://carbon.now.sh/?bg=rgba(171%2C%20184%2C%20195%2C%201)&amp;t=seti&amp;wt=none&amp;l=javascript&amp;ds=true&amp;dsyoff=20px&amp;dsblur=68px&amp;wc=true&amp;wa=true&amp;pv=56px&amp;ph=56px&amp;ln=false&amp;fl=1&amp;fm=Hack&amp;fs=14px&amp;lh=133%25&amp;si=false&amp;es=2x&amp;wm=false&amp;code=async%2520function%2520main()%2520%257B%250A%2520%2520await%2520printWeatherData()%253B%250A%250A%2520%2520setInterval(()%2520%253D%253E%2520%257B%250A%2520%2520%2520%2520printWeatherData()%253B%250A%2520%2520%257D%252C%25205%2520<em>%252060%2520</em>%25201000)%253B%250A%250A%2520%2520setInterval(async%2520()%2520%253D%253E%2520%257B%250A%2520%2520%2520%2520await%2520writeToLcd(8%252C%25200%252C%2520new%2520Date().toISOString().substring(11%252C%252019))%253B%250A%2520%2520%257D%252C%25201000)%253B%250A%257D%250A%250Alcd.on(%2522ready%2522%252C%2520main)%253B%250A%250A%252F%252F%2520*%2520If%2520ctrl%252Bc%2520is%2520hit%252C%2520free%2520resources%2520and%2520exit.%250Aprocess.on(%2522SIGINT%2522%252C%2520(</em>)%2520%253D%253E%2520%257B%250A%2520%2520lcd.close()%253B%250A%2520%2520process.exit()%253B%250A%257D)%253B)_</p>
<p>The weather data is updated every 5 minutes. But because we have a limit of 100 API Calls / Hour imposed by ClimaCell, we can go even further and update the weather data each minute.</p>
<p>For the current date, we have two options: </p>
<ul>
<li>we can use the property <code>observation_time</code> and display the date at which the data was received, or </li>
<li>we can make a real clock and display the current time.</li>
</ul>
<p>I chose the second option, but feel free to do it as you please.</p>
<p>To print the time in the upper right corner, we must first calculate the starting column so that the text fits snugly. For this we can use the next formula <code>total columns number</code> minus <code>text to display length</code></p>
<p>The date has 8 characters and because he has 16 columns, we must start from column number 8.</p>
<p>The LCD setting is asynchronous, so we must use the method <code>lcd.on()</code> provided by the related library, so we know when the LCD has been initialized and is ready to be used.</p>
<p>Another best practice in embedded systems is to close and free the resources that you use. That’s why we use the <code>SIGNINT</code> event to close the LCD screen when the program is stopped. Other events like this one include:  </p>
<ul>
<li><code>SIGUSR1</code> and <code>SIGUSR2</code> - to catch "kill pid” like nodemon restart</li>
<li><code>uncaughtException</code> - to catch uncaught exceptions</li>
</ul>
<h2 id="heading-step-vi-run-it-forever">Step VI - Run it Forever</h2>
<p>The script is complete and at this point we can run our program. We just have one more thing we must do before we can finish. </p>
<p>At this point you’re probably connected to your Raspberry Pi using SSH or directly with an HDMI cable and a monitor. No matter what, when you close your terminal the program will stop. </p>
<p>At the same time if you power off your device and after some time or immediately power it on again, the script will not start and you’ll have to do it manually.</p>
<p>To solve this problem, we can use a process manager like <a target="_blank" href="https://www.npmjs.com/package/pm2">pm2</a>.</p>
<p>Here are the steps:  </p>
<ol>
<li><code>sudo npm install pm2 -g</code> - install pm2</li>
<li><code>sudo pm2 startup</code> - create a startup script for pm2 manager</li>
<li><code>pm2 start index.js</code> - start an application</li>
<li><code>pm2 save</code> - save your process list across server restart</li>
</ol>
<p>Now you can reboot your board and the script will start automatically when the device is ready.</p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>From this point you can customize your new device however you want. If you find this weather data important for you (or any other data from ClimaCell, like air pollution, pollen, fire index or road risk), you can create a custom case to put the Raspberry Pi and the LCD display in it. Then after you added a battery you can place the device in your house.</p>
<p><a target="_blank" href="https://www.raspberrypi.org/">Raspberry Pi</a> is like a personal computer, so you can do much more on it than you would normally do on a microcontroller like <a target="_blank" href="https://www.arduino.cc/">Arduino</a>. Because of this, it's easy to combine it with other devices you have in your house.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Set Up Ubuntu MATE on a Raspberry PI ]]>
                </title>
                <description>
                    <![CDATA[ By Goran Aviani A few days ago a Raspberry Pi I use for CI on my personal projects stopped working. The error was easily fixable, but since running anything on that PI was slow from day one, I decided not to proceed in that direction.  Also, because ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-set-up-ubuntu-mate-on-raspberry-pi/</link>
                <guid isPermaLink="false">66d45ee6680e33282da25e6d</guid>
                
                    <category>
                        <![CDATA[ Computers ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Linux ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Raspberry Pi ]]>
                    </category>
                
                    <category>
                        <![CDATA[ technology ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Ubuntu ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Tue, 30 Jun 2020 15:46:18 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/06/1593285574373_plus-1.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Goran Aviani</p>
<p>A few days ago a Raspberry Pi I use for CI on my personal projects stopped working. The error was easily fixable, but since running anything on that PI was slow from day one, I decided not to proceed in that direction. </p>
<p>Also, because of the same issue I always wanted to switch to another OS – but I never had the proper motivation since everything worked on that Raspberry PI, right? Well the motivation came along with this article :)</p>
<p><strong>Note</strong>: The original setup on the Raspberry PI was quite simple: Raspibian OS, VNC Server, and Jenkins.</p>
<p>For you that are new to Raspberries here is a little background at what they are, and what they can be used for. </p>
<p>Raspberry Pi's are basically small computers used most often used to learn programming skills, build hardware projects, do home automation, and some have even found usage in industrial applications. </p>
<p>There are a few different types of PI's on the market today. To learn more about them please visit the their Wikipedia page: <a target="_blank" href="https://en.wikipedia.org/wiki/Raspberry_Pi#Generations">https://en.wikipedia.org/wiki/Raspberry_Pi#Generations</a></p>
<p>Tools you'll needed to complete this tutorial:</p>
<ul>
<li>Raspberry PI</li>
<li>Micro SD card</li>
<li>Micro SD card adapter for your laptop</li>
<li>Windows</li>
<li>Mouse for Raspberry PI</li>
<li>HDMI cable to connect  your Raspberry PI to at TV or any other kind of screen</li>
<li>Keyboard for Raspberry PI is good to have but not necessary as Ubuntu MATE offers on screen keyboard</li>
</ul>
<p>Once we complete all the steps, this article will show you how to:</p>
<ul>
<li>Set up Ubuntu MATE on an SD card using Windows</li>
<li>Install Ubuntu MATE to Raspberry PI</li>
<li>Things to do after installing Ubuntu MATE</li>
</ul>
<p>There are several OS options for Raspberry PI available, and Ubuntu Mate is just one option. For more info on other options you can check out this article: </p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.ubuntupit.com/best-raspberry-pi-os-available/">https://www.ubuntupit.com/best-raspberry-pi-os-available/</a></div>
<h2 id="heading-flashing-ubuntu-mate-to-micro-sd-card">Flashing Ubuntu MATE to Micro SD card</h2>
<p>The SD card and USB sticks are called a Flash drives because they have a flash type memory in them. So flashing means creating a bootable drive with a specific operating system (OS) on it. </p>
<p>Navigate to the download page of Ubuntu MATE and download the recommended architecture image for Raspberry PI: </p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://ubuntu-mate.org/">https://ubuntu-mate.org/</a></div>
<p><img src="https://lh6.googleusercontent.com/HJ1b4UY1H8tqnWldXMWh1inaYVTuMQEvFyY6ia-MvNw-pKmx0B42YQ96i2x4UmB3gBfAmtkrOeeoAHr4H4DNU_I025ionZRQY0ZKXDaDLrBBWCP3R_vZrzUR3SC2f0O7-TNYLzne" alt="Image" width="1397" height="731" loading="lazy">
<em>Download the Raspberry PI 32 bit version (recommended)</em></p>
<h3 id="heading-setting-up-the-sd-card">Setting Up the SD Card</h3>
<p>After downloading the Ubuntu MATE image we need to write it to the SD card. To do that we will use the Balena Etcher tool. Etcher is a tool we will download from here:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.balena.io/etcher/">https://www.balena.io/etcher/</a></div>
<p><img src="https://lh6.googleusercontent.com/1vYIoox0YKBcjUKbaZeGySgpiBgGl5VvcibE_vHRG15r1lq8geFO44TBdszTA-qRU2eUYqX2rkUoTiregfWz78BZ5IjQKIOwMAfoZc5ltVtgsVNxwrJ0EswWNXrM-DNOLVdDwsfC" alt="Image" width="1512" height="815" loading="lazy">
<em>Balena Ether download page</em></p>
<p>Install Etcher and launch it. Then select the Ubuntu MATE image file you previously downloaded, along with your SD card, and start the flashing process.</p>
<p><strong>Note</strong>: Flashing Ubuntu MATE to the SD card takes some time, so feel free to get yourself a cup of coffee.</p>
<p><img src="https://lh3.googleusercontent.com/BAvIy-shTq645HjARX0MI0DqE5eZvfNqd2A8srKGtB8sVsDfxPOfhz-v6B7qSUl6JddF7O8dP7C9U_hJmgmhrGB02gZ2Ub0tgsOJfQOaocBdoHgUDQ1tYODhSARSLd2STl_G43Id" alt="Image" width="988" height="596" loading="lazy">
<em>Balena Etcher decompressing Ubuntu MATE image</em></p>
<p><img src="https://lh4.googleusercontent.com/iL3a8osNs-fgqcoqbiWljsfpQItxMB2lsY6ibHOAValUdYoNetN0DoqwV4K2a0enatDXAvulVmuuOckdtcQn3QcyXr-OfZRBgg02_2jSG2Ms7bTzYL5LGVXu5irD6-cL6T03NLo7" alt="Image" width="982" height="582" loading="lazy">
<em>Balena Etcher validating Ubuntu MATE image</em></p>
<p>Once you are done with flashing, take out the SD card from the adapter and put it in the Raspberry PI. Also, this is the step where you will need to connect the PI with the mouse and a screen.</p>
<h2 id="heading-installing-ubuntu-mate-on-raspberry-pi">Installing Ubuntu MATE on Raspberry PI</h2>
<p>Once you are done connecting all peripherals of the Raspberry PI, connect it to the power source and let it startup.</p>
<p>The Ubuntu MATE installation process is exactly the same as for ordinary Ubuntu. During this process you will be asked to to select your keyboard layout, timezone, username and password. Here is the step by step instalation guide for Ubuntu 18.04:  <a target="_blank" href="https://phoenixnap.com/kb/how-to-install-ubuntu-18-04-bionic-beaver">Ubuntu installation</a>. </p>
<p>After the installation is done you will be greeted by the desktop screen.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/ubuntu-mate-home.png" alt="Image" width="600" height="400" loading="lazy">
<em>Ubuntu Mate desktop greeting screen</em></p>
<h2 id="heading-things-to-do-after-installing-ubuntu-mate">Things to do after installing Ubuntu MATE</h2>
<h3 id="heading-update-the-local-database">Update the local database</h3>
<p>This command updates/maps the local database of your local packages with updates, later allowing your system to fetch new versions of the packages.</p>
<p>sudo apt update</p>
<h3 id="heading-upgrading-the-installed-packages">Upgrading the installed packages</h3>
<p>This command fetches new versions of packages existing on the machine you previously mapped with <em>sudo apt update</em> command.</p>
<p>sudo apt upgrade</p>
<p>Note: Updating Ubuntu MATE takes a lot of time so feel free to get yourself another cup of coffee, and a pastry to go along with it. If you don't have any pastry around feel free to go to the nearest bakery, because by the time you get back you system will probably be 50% upgraded.</p>
<h3 id="heading-installing-the-ubuntu-software-application">Installing the Ubuntu Software application</h3>
<p>Software Boutique is the default software center on Ubuntu MATE. However, it has a very limited selection of applications, and one of the more interesting apps needed for my project is missing. </p>
<p>Luckily, since this is an Ubuntu distribution, there is a way to install the standard Ubuntu Software center which has more app choices.  </p>
<p>Apparently there is a way of installing Ubuntu Software center via Software Bundle. But when selecting the option to doing that I was stuck and nothing seemed to happen for me so I just used the terminal command:</p>
<p>sudo apt install ubuntu-software</p>
<p>Bra gjort! You have just finished setting up the Ubuntu Mate OS on your Raspberry PI device!</p>
<p>Check out more articles like this on my <a target="_blank" href="https://www.freecodecamp.org/news/author/goran/">freeCodeCamp profile</a>, <a target="_blank" href="https://medium.com/@goranaviani">Medium profile</a>, and other fun stuff I build on my <a target="_blank" href="https://github.com/GoranAviani">GitHub page</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Perf Machine Learning on Rasp Pi ]]>
                </title>
                <description>
                    <![CDATA[ By Gant Laborde 3 Frameworks for Machine Learning on the Raspberry Pi The revolution of AI is reaching new heights through new mediums. We’re all enjoying new tools on the edge, but what are they? What products frameworks will fuel the inventions of ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/perf-machine-learning-on-rasp-pi-51101d03dba2/</link>
                <guid isPermaLink="false">66d45eedbc9760a197a103bf</guid>
                
                    <category>
                        <![CDATA[ iot ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Machine Learning ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Raspberry Pi ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tech  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ technology ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Tue, 09 Apr 2019 15:39:06 +0000</pubDate>
                <media:content url="https://cdn-media-2.freecodecamp.org/w1280/5f9ca36a740569d1a4ca5b6c.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Gant Laborde</p>
<h4 id="heading-3-frameworks-for-machine-learning-on-the-raspberry-pi">3 Frameworks for Machine Learning on the Raspberry Pi</h4>
<p>The revolution of AI is reaching new heights through new mediums. We’re all enjoying new tools on the edge, but what are they? What products frameworks will fuel the inventions of tomorrow?</p>
<p>If you’re unfamiliar with why Machine Learning is changing our lives, have a read <a target="_blank" href="https://medium.freecodecamp.org/machine-learning-how-to-go-from-zero-to-hero-40e26f8aa6da">here</a>.</p>
<p>If you’re already excited about Machine Learning and you’re interested in utilizing it on devices like the Raspberry Pi, enjoy!</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*Ie5y0cU3BqZkIRRKPNQtmg.png" alt="Image" width="800" height="225" loading="lazy"></p>
<h3 id="heading-simple-object-detection-on-the-raspberry-pi">Simple object detection on the Raspberry Pi</h3>
<p>I’ve implemented three different tools for detection on the Pi camera. While it’s a modern miracle that all three work, it’s important for creators to know “how well” because of #perfmatters.</p>
<p>Our three contenders are as follows:</p>
<ol>
<li><a target="_blank" href="https://www.raspberrypi.org/blog/raspberry-pi-3-model-bplus-sale-now-35/"><strong>Vanilla Raspberry Pi 3 B+</strong></a>— No optimizations, but just using a TensorFlow framework on the device for simple recognition.</li>
<li><a target="_blank" href="https://software.intel.com/en-us/neural-compute-stick"><strong>Intel’s Neural Compute Stick 2</strong></a> — Intel’s latest USB interface device for Neural Networks, boasting 8x perf over the first stick! Around $80 USD.</li>
<li><a target="_blank" href="https://www.xnor.ai"><strong>Xnor.ai</strong></a> — A proprietary framework that reconfigures your model to run efficiently on smaller hardware. Xnor’s binary logic shrinks 32-bit floats to 1-bit operations, allowing you to optimize deep learning models for simple devices.</li>
</ol>
<p>Let’s evaluate all three with simple object detection on a camera!</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*XCtxmYeXSc9hiResMDbXVA.png" alt="Image" width="722" height="716" loading="lazy"></p>
<h3 id="heading-vanilla-raspberry-pi-3-b">Vanilla Raspberry Pi 3 B+</h3>
<p>A Raspberry Pi is like a small, wimpy, Linux machine for $40. It allows you to run high-level applications and code on devices like IoT made easy. Though it sounds like I can basically use laptop machine learning on the device, there’s one big gotcha. The RPi has an <a target="_blank" href="https://whatis.techtarget.com/definition/ARM-processor">ARM processor</a>, and that means we’ll need to recompile our framework, i.e. TensorFlow, to get everything running.</p>
<blockquote>
<p>⚠️ While this is not hard, this is SLOW. Expect this to take a very… very… long time. This is pretty much the fate of anything compiled on the Raspberry Pi.</p>
</blockquote>
<h4 id="heading-setup">Setup</h4>
<p>Here are all the steps I did, including setting up the Pi camera for object detection. I'm simply including this for posterity. Feel free to skip reading it.</p>
<p>Install pi, then camera, then edit the <code>/boot/config.txt</code>
Add <code>disable_camera_led=1</code> to the bottom of the file and rebooting.</p>
<h3 id="heading-best-to-disable-screensaver-mode-as-some-follow-up-commands-may-take-hours">Best to disable screensaver mode, as some follow-up commands may take hours</h3>
<pre><code>sudo apt-get install xscreensaver
xscreensaver
</code></pre><p>Then disable screen saver in the “Display Mode” tab.</p>
<h3 id="heading-now-get-tensorflow-installed">Now get Tensorflow Installed</h3>
<pre><code>sudo apt-get update
sudo apt-get dist-upgrade
sudo apt-get update
sudo apt-get install libatlas-base-dev
sudo apt-get install libjasper-dev libqtgui4 python3-pyqt5
pip3 install tensorflow
sudo apt-get install libjpeg-dev zlib1g-dev libxml2-dev libxslt1-dev
pip3 install pillow jupyter matplotlib cython
pip3 install lxml # <span class="hljs-built_in">this</span> one takes a long time
pip3 install python-tk
</code></pre><h3 id="heading-opencv">OpenCV</h3>
<pre><code>sudo apt-get install libtiff5-dev libjasper-dev libpng12-dev
Sudo apt-get install libavcodec-dev libavformat-dev libswscale-dev libv4l-dev
sudo apt-get install libxvidcore-dev libx264-dev
sudo apt-get install qt4-dev-tools
pip3 install opencv-python
</code></pre><h3 id="heading-install-protobuff">Install Protobuff</h3>
<pre><code>sudo apt-get install autoconf automake libtool curl
</code></pre><p>Then pull down protobuff and untar it.<br>https://github.com/protocolbuffers/protobuf/releases</p>
<p>Then cd in and then run the following command which might cause the computer to become unusable for the next 2+ hours.  Use ctrl + alt + F1, to move to terminal only and release all UI RAM.  Close x process with control + c if needed.   You can then run the long-running command.  Base username “pi” and password “raspberry”</p>
<pre><code>make &amp;&amp; make check
</code></pre><p>You can then install simply with</p>
<pre><code>sudo make install
cd python
<span class="hljs-keyword">export</span> LD_LIBRARY_PATH=../src/.libs
python3 setup.py build --cpp_implementation
python3 setup.py test --cpp_implementation
sudo python3 setup.py install --cpp_implementation
<span class="hljs-keyword">export</span> PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=cpp
<span class="hljs-keyword">export</span> PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION_VERSION=<span class="hljs-number">3</span>
sudo ldconfig
</code></pre><p>Once this is done, you can clean up some install crud with sudo apt-get autoremove, delete the tar.gz download and then finally reboot with sudo reboot now which will return you to a windowed interface</p>
<h3 id="heading-setup-tensorflow">Setup Tensorflow</h3>
<pre><code>mkdir tensorflow1 &amp;&amp; cd tesorflow1
git clone --recurse-submodules \ https:<span class="hljs-comment">//github.com/tensorflow/models.git</span>
modify ~/.bashrc to contain <span class="hljs-keyword">new</span> env <span class="hljs-keyword">var</span> named PYTHONPATH <span class="hljs-keyword">as</span> such
<span class="hljs-keyword">export</span> PYTHONPATH=$PYTHONPATH:<span class="hljs-regexp">/home/</span>pi/tensorflow1/models/research:<span class="hljs-regexp">/home/</span>pi/tensorflow1/models/research/slim
</code></pre><p>Now go to the zoo:  https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/detection_model_zoo.md
We’ll take the ssdlite_mobilenet, which is the fastest!  Wget the file and then tar -xzvf the tar.gz result and delete the archive once untarred.  Do this in the <code>object_detection</code> folder in your local <code>tensorflow1</code> folder.  Now cd up to the research dir.  Then run:</p>
<pre><code>protoc object_detection/protos<span class="hljs-comment">/*.proto --python_out=.</span>
</code></pre><p>This converted the object detection protos files to python in the proto folder</p>
<h1 id="heading-done-installing">Done Installing!!</h1>
<p>Special thanks to <a target="_blank" href="https://www.youtube.com/channel/UCLuS8eZl3_nKKq85gPS62lQ">Edje Electronics</a> for sharing their wisdom on setup, an indispensable resource for my own setup and code.</p>
<p>Once I got Tensorflow running, I was able to run object recognition (with the provided sample code) on Mobilenet for 1 to 3 frames per second.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/0*6AAvPd52hXAY42QV" alt="Image" width="800" height="538" loading="lazy"></p>
<h4 id="heading-vanilla-pi-results">Vanilla Pi Results</h4>
<p>For basic detection, 1 to 3 frames per second aren’t bad. Removing the GUI or lowering camera input quality speeds up detection. This means the tool could be an excellent detector for just simple detection. What a great baseline! Let’s see if we can make it better with the tools available.</p>
<h3 id="heading-intels-neural-compute-stick-2">Intel’s Neural Compute Stick 2</h3>
<p>This concept excites me. For those of us without GPUs readily available, training on the edge instead of the cloud, and moving that intense speed to the Raspberry Pi is just exciting. I missed the original stick, the “Movidius”, but from this graph, it looks like I chose a great time to buy!</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/0*BvMaSJQsL52PIyK_.jpg" alt="Image" width="780" height="665" loading="lazy"></p>
<h4 id="heading-setup-1">Setup</h4>
<p>My Intel NCS2 arrived quickly and I enjoyed unboxing actual hardware for accelerating my training. That was probably the last moment I was excited.</p>
<p>Firstly, the USB takes a lot of space. You’ll want to get a cable to keep it away from the base.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*jsB8SEZrmnY5V8gXne3pFA.jpeg" alt="Image" width="800" height="420" loading="lazy"></p>
<p>That’s a little annoying but fine. The really annoying part was trying to get my NCS 2 working.</p>
<p>There are lots of tutorials for the NCS by third parties, and following them got me to a point where I thought the USB stick might be broken!</p>
<p>Everything I found on the NCS didn’t work (telling me the stick wasn’t plugged in!), and everything I found on NCS2 was pretty confusing. <a target="_blank" href="https://ncsforum.movidius.com/discussion/comment/4202">For a while, NCS2 didn’t even work on ARM processors!</a></p>
<blockquote>
<p>?????????????????</p>
</blockquote>
<p>After a lot of false-trails, I finally found and began compiling C++ examples (sorry Python) that only understood USB cameras (sorry PiCam). Compiling the examples was painful. Often the entire Raspberry Pi would become unusable, and I’d have to reboot.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*WYgHc8_hie3jwVEAU_Qe0Q.jpeg" alt="Image" width="800" height="600" loading="lazy">
<em>locked up at 81% for 24 hours</em></p>
<p>The whole onboarding experience was more painful than recompiling Tensorflow on the raw Pi. Fortunately, I got everything working!</p>
<p><strong>The result!? ??????????????????????</strong></p>
<h4 id="heading-nc2-stick-results"><strong>NC2 Stick Results</strong></h4>
<p><strong>6 to 8 frames per second… ARE YOU SERIOUS!? After all that?</strong></p>
<p><strong>It must be a mistake, let me run the <code>perfcheck</code> project.</strong></p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*WbhdU_Ut0K9BDN2U1F9NVw.jpeg" alt="Image" width="800" height="600" loading="lazy"></p>
<p><strong>10 frames per second…</strong></p>
<p><strong>From videos on the original NCS on python I saw around 10fps.. where’s the 8x boost? Where’s the reason for $80 hardware attached to a $40 device? To say I was let down by Intel’s NCS2 is an understatement. The user experience and final results were frustrating, to put it lightly.</strong></p>
<h3 id="heading-xnorai"><strong>Xnor.ai</strong></h3>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*relSMp3LUrMmQSpzzupksw.jpeg" alt="Image" width="800" height="266" loading="lazy"></p>
<p><strong>Xnor.ai is a self-contained software solution for deploying fast and accurate deep learning models to low-cost devices. As many discrete logic enthusiasts might have noticed, Xnor is the logical complement of the bitwise XOR operator. If that doesn’t mean anything to you, that’s fine. Just know that the people who created the YOLO algorithm are alluding to the use of the logical operator to compress complex 32-bit computations down to 1-bit by utilizing this inexpensive operation and keeping track of the CPU stack.</strong></p>
<p><strong>In theory, avoiding such complex calculations required by GPUs should speed up execution on edge devices. Let’s see if it works!</strong></p>
<h4 id="heading-setup-2"><strong>Setup</strong></h4>
<p><strong>Setup was insanely easy. I had an object detection demo up and running in 5 minutes. </strong><em>5 MINUTES!</em><em>**</em></p>
<p><strong>The trick with Xnor.ai is that, much like the NCS2 Stick, the model is modified and optimized for the underlying hardware fabric. Unlike Intel’s haphazard setup, everything is wrapped in friendly Python (or C) code.</strong></p>
<p><strong><code>model = xnornet.Model.load_built_in()</code></strong></p>
<p><strong>That’s nice and simple.</strong></p>
<p><strong>But it means nothing if the performance isn’t there. Let’s load their object detection model.</strong></p>
<p><strong>Again, no complexity, they have one with no overlay, and one with. Since the others (except for perfcheck on NCS2) were with overlays, let’s use that.</strong></p>
<h4 id="heading-xnorai-results"><strong>Xnor.ai Results</strong></h4>
<p><strong>JAW… DROPPING… PERFORMANCE. I not only get a stat on how fast inference could work, but I also get an overall FPS with my overlay that blew everything else out of the water.</strong></p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*bJM6E_o4omDdQo4KLFwEGQ.jpeg" alt="Image" width="800" height="600" loading="lazy"></p>
<p><strong>OVER 12FPS and an inference speed over 34FPS!?</strong></p>
<p><strong>This amazing throughput is achieved with no extra hardware purchase!? I’d call Xnor the winner at this point, but it seems a little too obvious.</strong></p>
<p><strong>I was able to heat up my device and open a browser in the background to get it down to 8+ FPS, but even then, it’s a clear winner!</strong></p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*dvybusIlJOxVNemjLytUsA.gif" alt="Image" width="600" height="369" loading="lazy">
<em>Xnor hype is real</em></p>
<p><strong>The only negative I can give you on Xnor.ai is that I have no idea how much it costs. The Evaluation model has a limit of 13,500 inferences per startup.</strong></p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*x_6iL1fZ6gKqYBK9M05lpg.png" alt="Image" width="800" height="217" loading="lazy"></p>
<p><strong>While emailing them to get pricing, they are just breaking into non-commercial use, so they haven’t created a pricing system yet. Fortunately, the evaluation model would be fine for most hobbyists and prototypes.</strong></p>
<h3 id="heading-in-summary"><strong>In Summary:</strong></h3>
<p><strong>If you need to take a variety of models into account, you might be just fine getting your Raspberry Pi setup from scratch. This would make it a great resource for testing new models and really customize your experience.</strong></p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*iI4IXTmT-wm8tu6sik_oNw.png" alt="Image" width="618" height="153" loading="lazy"></p>
<p><strong>When you’re ready to ship, it’s no doubt that both the NCS2 and the Xnor.ai frameworks speed things up. It’s also no doubt that Xnor.ai outperformed the NCS2 in both onboarding and performance. I’m not sure what Xnor.ai’s pricing model is, but that would be the final factor in what is clearly a superior framework.</strong></p>
<h4 id="heading-post-publish-updates"><strong>Post Publish Updates:</strong></h4>
<p><strong>This is an excellent blog post on setting up the NCS2</strong></p>
<p><strong><a target="_blank" href="https://medium.com/@aallan/getting-started-with-the-intel-neural-compute-stick-2-and-the-raspberry-pi-6904ccfe963">Getting Started with the Intel Neural Compute Stick 2 and the Raspberry Pi</a></strong><br><strong><a target="_blank" href="https://medium.com/@aallan/getting-started-with-the-intel-neural-compute-stick-2-and-the-raspberry-pi-6904ccfe963">_Getting started with Intel’s Movidius hardware_medium.com</a></strong></p>
<p><strong>Additionally, if you’re looking to play around with Xnor.ai, the link is <a target="_blank" href="http://www.xnor.ai/ai2go">www.xnor.ai/ai2go</a></strong></p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*kePT6qGxTucg__Uz9IC_mQ.png" alt="Image" width="800" height="71" loading="lazy"></p>
<p><strong><a target="_blank" href="https://www.freecodecamp.org/news/perf-machine-learning-on-rasp-pi-51101d03dba2/undefined">Gant Laborde</a> is Chief Technology Strategist at <a target="_blank" href="http://infinite.red">Infinite Red</a>, a published author, adjunct professor, worldwide public speaker, and mad scientist in training. Clap/follow/<a target="_blank" href="https://twitter.com/GantLaborde">tweet</a> or visit him <a target="_blank" href="http://gantlaborde.com/">at a conference</a>.</strong></p>
<p><strong>Expect more awesome edge blog posts coming soon!</strong></p>
<h4 id="heading-have-a-moment-read-more-by-gant"><strong>Have a moment? Read more by Gant</strong></h4>
<p><strong><a target="_blank" href="https://shift.infinite.red/avoid-nightmares-nsfw-js-ab7b176978b1">Avoid Nightmares — NSFW JS</a></strong><br><strong><a target="_blank" href="https://shift.infinite.red/avoid-nightmares-nsfw-js-ab7b176978b1">_Client-side indecent content checking for the soul_shift.infinite.red</a></strong></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to monitor your air quality with this DIY setup ]]>
                </title>
                <description>
                    <![CDATA[ By Bert Carremans With a Raspberry Pi, low-cost gas sensors, and a remote-controlled switch, you can control the air quality in your house. The air we breathe indoors is not always healthier than the air outside. According to a study of the E.U. Join... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-monitor-your-air-quality-with-this-diy-setup-3399793137c3/</link>
                <guid isPermaLink="false">66d45ddb51f567b42d9f8441</guid>
                
                    <category>
                        <![CDATA[ Electronics ]]>
                    </category>
                
                    <category>
                        <![CDATA[ iot ]]>
                    </category>
                
                    <category>
                        <![CDATA[ General Programming ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Raspberry Pi ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Tue, 19 Feb 2019 16:02:04 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/1*d43dkbPP_oxPGy6o0WWvnw.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Bert Carremans</p>
<h4 id="heading-with-a-raspberry-pi-low-cost-gas-sensors-and-a-remote-controlled-switch-you-can-control-the-air-quality-in-your-house">With a Raspberry Pi, low-cost gas sensors, and a remote-controlled switch, you can control the air quality in your house.</h4>
<p>The air we breathe indoors is not always healthier than the air outside.</p>
<p>According to a study of the <a target="_blank" href="http://europa.eu/rapid/press-release_IP-03-1278_en.htm">E.U. Joint Research Centre</a>, you can find a wide range of air pollutants indoors. Some of them are toxic, or can cause genetic mutations or cancer. Factors that influence indoor air quality are:</p>
<ul>
<li>ambient air, or the air outdoors</li>
<li>air tightness and ventilation of the building</li>
<li>indoor sources like tobacco smoke, heating gases, consumer products, etc.</li>
</ul>
<p>Do you know how much time you spend indoors? According to the Environmental Protection Agency, Americans spend <a target="_blank" href="https://www.nature.com/articles/7500165">87% of their time indoors</a>. In Europe, this average percentage is 90%, according to the JRC. And the more time we spent indoors, the more pollutants we inhale.</p>
<p>So it would be interesting if we track the indoor air quality. In this article, I will explain how I did this with a <a target="_blank" href="https://www.raspberrypi.org/">Raspberry Pi</a>, a <a target="_blank" href="https://www.dexterindustries.com/grovepi/">GrovePi</a> and some sensors. We will upload the sensor data to a <a target="_blank" href="https://firebase.google.com/">Firebase</a> database and visualize trends with <a target="_blank" href="https://dash.plot.ly/">Dash</a>.</p>
<p>When pollutant levels reach an unhealthy level, we can send an alert notification to warn us.</p>
<p>Moreover, it would be great if we could automatically start up the ventilation to purify the air. This can be done with a <a target="_blank" href="https://www.raspberrypi.org/blog/controlling-electrical-sockets-with-energenie-pi-mote/">remote-controlled switch from Energenie</a>.</p>
<h4 id="heading-raspberry-pi-and-grovepi">Raspberry Pi and GrovePi</h4>
<p><img src="https://cdn-media-1.freecodecamp.org/images/A50dJjzt1x9eSTy0qoMm8trrsQ0M3GiAGM1t" alt="Image" width="350" height="278" loading="lazy">
<em>Raspberry Pi model 2B</em></p>
<p>In this project, I will use a Raspberry Pi model 2B. The Raspberry Pi is a low-cost small computer. It enables programmers and makers to build anything they can imagine.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/M4xPtxNXeO6K2NCIA6WXpHLMQvGdG77xAd10" alt="Image" width="350" height="263" loading="lazy">
<em>GrovePi (blue board) attached to a Raspberry Pi</em></p>
<p>The GrovePi is an electronics board that we attach to the Raspberry Pi. It makes connecting a wide range of sensors easy. As such, you don’t need to bother about a breadboard, resistors, soldering or jumper wires. You can plug in a connector and start working with it.</p>
<h4 id="heading-data-flow">Data flow</h4>
<p>In the illustration below you see how the sensor data will flow from the sensors to the charts on a web page. We’ll be using Python to do all that.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/fNyezC61-WH5nzRtVLodEZriNHkihVwLQz9c" alt="Image" width="800" height="211" loading="lazy">
<em>Data flow from a GrovePi and a Raspberry Pi to Firebase to a web page hosted on PythonAnywhere.</em></p>
<p>The first step is to pull in the data from the Grove sensors. Then we process the data on the Raspberry Pi and send them to a Firebase database. This data is also used to switch on or off a ventilation unit via a remote controlled switch.</p>
<p>We can get the stored data with a script running on PythonAnywhere.com. With the Dash package, we can build a dashboard to follow up the indoor air quality.</p>
<h4 id="heading-gas-sensors">Gas sensors</h4>
<p>We will use a set of three different gas sensors for this project. I use Grove gas sensors and connect them to the GrovePi.</p>
<p>It is not necessary to use all three sensors in your own project. Feel free to choose sensors that fit your needs. You can do that in the <code>config.py</code> file by keeping the sensors you need in the Python dictionary <code>MQ_SENSORS.</code></p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/ZYw4C3gJXMR1SOZ5cdz81uJkdfQejUdxiy3y" alt="Image" width="760" height="244" loading="lazy">
<em>Grove gas sensors</em></p>
<p>Each sensor detects a specific set of gases. There is quite some overlap between the sensors with regard to the gases they detect. But, the range in which the sensors detect a gas can differ. You can find the ranges on the <a target="_blank" href="http://wiki.seeedstudio.com/How_to_Chose_A_Gas_Sensor/">website of Seeedstudio</a>.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/aXpnoZufynmqRnxESFGV9LiKPJGQ0fm2OAmV" alt="Image" width="800" height="498" loading="lazy">
<em>Gases detected per sensor type</em></p>
<h4 id="heading-temperature-and-humidity-sensor">Temperature and Humidity sensor</h4>
<p>Temperature and humidity influence the readings from the gas sensors. So, it is interesting to measure temperature and humidity as well. We use the Grove BME680 sensor for that.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/8PZoW1BkZtV2xNa3nHXAxT5-xfV1p2ltalSv" alt="Image" width="259" height="194" loading="lazy">
<em>Grove BME680 to measure temperature and humidity</em></p>
<h3 id="heading-working-with-sensor-data">Working with sensor data</h3>
<p>In short, a gas sensor will output a higher voltage when the concentration of a gas is higher. This is because the built-in resistor varies its resistance (Rs) according to the concentration of the gas.</p>
<p>The sensor value only reflects an approximation of a trend in the gas concentration. This means that you can use it to show whether the gas concentration increases or decreases. It does not give the exact gas concentration. If you want to measure the actual concentration, you would need a more expensive sensor.</p>
<p>For learning purposes, we will use the sensor value to approximate the gas concentration. You should keep that in mind. <strong>You should not use these scripts in real-life situations to prevent intoxication by these gases!</strong></p>
<p>On the datasheets listed below, you find a graph with the relation between the gas concentration and sensor resistor values. The gas concentration is expressed in parts per million (ppm). The resistor values as the ratio Rs/R0.</p>
<ul>
<li><a target="_blank" href="https://raw.githubusercontent.com/SeeedDocument/Grove-Gas_Sensor-MQ2/master/res/MQ-2.pdf">MQ2 datasheet</a></li>
<li><a target="_blank" href="https://raw.githubusercontent.com/SeeedDocument/Grove-Gas_Sensor-MQ5/master/res/MQ-5.pdf">MQ5 datasheet</a></li>
<li><a target="_blank" href="https://raw.githubusercontent.com/SeeedDocument/Grove-Gas_Sensor-MQ9/master/res/MQ-9.pdf">MQ9 datasheet</a></li>
</ul>
<p>The curves below correspond to the standard conditions of:</p>
<ul>
<li>a temperature of 20°C</li>
<li>a humidity of 65%</li>
<li>an oxygen concentration of 21%</li>
<li>a load resistance of 5 kilo-Ohm. The load resistance is the total resistance of an electronic circuit.</li>
</ul>
<p><img src="https://cdn-media-1.freecodecamp.org/images/ISA1CBYm5WM3hWuxAU5wsSXEhZ87aQv54Kr8" alt="Image" width="800" height="702" loading="lazy">
<em>Curves displaying the relation between gas concentrations and sensor resistance values. Both the x-axis and y-axis are on a log scale.</em></p>
<p>We can assume that the load resistance and oxygen concentration are stable over time. Yet, the temperature and humidity indoors can vary. Both factors have an influence on the sensor readings, as you can see in the graph below.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/QgM73v0r-BeZXMFVFb9PW9rttlbEKOlbPnCh" alt="Image" width="800" height="406" loading="lazy">
<em>Influence of different temperatures and humidities (RH) on sensor resistance values in the MQ2 sensor for a hydrogen (H2) concentration of 1.000 ppm.</em></p>
<p>To get accurate readings, you should have a graph or look-up table for various temperature-humidity combinations. Unfortunately, these graphs are not provided by the manufacturer.</p>
<p>Another approach is to correct the sensor readings for the temperature-humidity influence.</p>
<p>We can do this with artificial neural networks, as proposed in the <a target="_blank" href="https://www.uni-obuda.hu/journal/Nenova_Dimchev_41.pdf">paper by Nenova &amp; Dimchev</a>. That approach requires ground-truth measurements of the gas concentrations. This is out of scope for this article.</p>
<h4 id="heading-defining-the-r0-value">Defining the R0 value</h4>
<p>First, we need to compute the R0 value. R0 stands for the sensor resistance value of 1.000 ppm of hydrogen (H2)in clean air. The ratio for clean air (black line with blue crosses) is constant. It remains at 9.8 regardless of the concentration of clean air. So, we can compute R0 by reading the sensor value (Rs) in clean air and dividing it by 9.8.</p>
<p>The value we get from the sensor is a value between 0 and 1.023 and has no measurement unit. So, to get the output voltage we divide the sensor value by 1.023. We multiply this value with the circuit voltage, which is usually 5V.</p>
<p>From the sensor voltage, we can derive the sensor resistance by applying <a target="_blank" href="https://en.wikipedia.org/wiki/Ohm%27s_law">Ohm’s law</a>. The sensor resistance is equal to</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/HipHVSJozj6CkZ3TKlEHQlAJTdb5D-dsmsLZ" alt="Image" width="191" height="32" loading="lazy">
<em>Vc is the circuit voltage and Vs is the sensor voltage</em></p>
<p>Let’s see how we do that with Python. The complete code and more documentation can be found on <a target="_blank" href="https://github.com/bertcarremans/air_quality_monitoring">Github</a> in the script <code>get_R0_values.py</code>.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> config <span class="hljs-keyword">as</span> cfg
<span class="hljs-keyword">import</span> grovepi
<span class="hljs-keyword">import</span> time
</code></pre>
<p>First, we import some packages. The <code>config</code> package is a Python script I made to store all configuration parameters.</p>
<p>Throughout the code, you’ll notice that I sometimes refer to <code>cfg.PARAMETER_NAME</code>. We set the<code>PARAMETER_NAME</code> value in that <code>config.py</code> file. It also contains some passwords and API tokens.</p>
<p>For security reasons, I will not save that file on <a target="_blank" href="https://github.com/bertcarremans/air_quality_monitoring">Github</a>. Instead, I’ll provide a clean template <code>config_template.py</code> that you can use for your own project.</p>
<p>Next, we import the <code>grovepi</code> package. You can install it from the <a target="_blank" href="https://github.com/DexterInd/GrovePi/tree/master/Software/Python">Github page of Dexter Industries</a> on your Raspberry Pi. It allows us to work with the GrovePi and the connected sensors.</p>
<p>Finally, we use the <code>time</code> package to pause the program during sensor readings.</p>
<pre><code>mq_values = {}

<span class="hljs-keyword">for</span> sensor, data <span class="hljs-keyword">in</span> cfg.MQ_SENSORS.items():
    grovepi.pinMode(data[<span class="hljs-string">'pin'</span>],<span class="hljs-string">"INPUT"</span>)
    mq_values[sensor] = <span class="hljs-number">0</span>
</code></pre><p>We will store the sensor values for the different sensors in the dictionary <code>mq_values</code>. But first, we initialize the values to zero in a loop over the sensors defined in <code>MQ_SENSORS</code>.</p>
<p>With the <code>pinMode</code> method, you can define the pin as <code>INPUT</code> or <code>OUTPUT.</code> In our case, we’ll use it as <code>INPUT</code>.</p>
<p>The <code>pin</code> tells us to which pin on the GrovePi the sensor is connected. We use the analog pins (or ports) which are labeled as A0, A1 and A2 on the GrovePi. Make sure you connect the right sensor to the port described in the <code>config.py</code> file.</p>
<pre><code><span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(cfg.NB_R0_READ):
    <span class="hljs-keyword">for</span> sensor, value <span class="hljs-keyword">in</span> mq_values.items():
        mq_values[sensor] += grovepi.analogRead(cfg.MQ_SENSORS[sensor][<span class="hljs-string">'pin'</span>])
    time.sleep(cfg.R0_INTERVAL)
</code></pre><p>We then read the sensor value in a loop for <code>cfg.NB_R0_READ</code> times and sum it up in <code>mq_value[sensor]</code>. We read the sensor value with the <code>analogRead</code> method of the <code>grovepi</code> package.</p>
<p>As described in the <a target="_blank" href="https://www.dexterindustries.com/GrovePi/programming/python-library-documentation/">documentation</a>, this will return a value between 0 and 1.023. In fact, it converts an analog sensor value to a digital value.</p>
<p>After one reading for all sensors, we pause the program at <code>cfg.R0_INTERVAL</code> seconds. To get the average value, we divide the cumulated value by <code>cfg.NB_RO_READ.</code></p>
<pre><code><span class="hljs-keyword">for</span> sensor, value <span class="hljs-keyword">in</span> mq_values.items():
    mq_values[sensor] = mq_values[sensor]/cfg.NB_R0_READ
    mq_values[sensor] = mq_values[sensor]/cfg.AR_MAX * cfg.VC
    mq_values[sensor] = (cfg.VC - mq_values[sensor])/mq_values[sensor]
    mq_values[sensor] = mq_values[sensor]/cfg.MQ_SENSORS[sensor][<span class="hljs-string">'r0_rs_air'</span>]
</code></pre><p>We compute the sensor voltage by dividing the averaged sensor value by <code>cfg.AR_MAX</code>. Then we multiply it by the circuit voltage <code>cfg.VC</code>.</p>
<p>From that voltage, we can apply Ohm’s law and compute the sensor resistance value. Dividing that by the ratio for clean air <code>cfg.MQ_SENSORS[sensor]['r0_rs_air']</code> gives us R0.</p>
<p>It is better to have the sensor working at least for 24 hours before you measure the R0 value. This will give more stable sensor readings.</p>
<h4 id="heading-linear-interpolation-of-gas-concentration">Linear interpolation of gas concentration</h4>
<p>Now that we know the R0 value, we can compute the Rs/R0 ratio with the sensor value. With that ratio, we can derive the gas concentration with <a target="_blank" href="https://en.wikipedia.org/wiki/Linear_interpolation">linear interpolation</a>.</p>
<p>For that, we assume that we are working in the standard conditions described for the first graph. In that case, the curves are nearly linear.</p>
<p>For linear interpolation, we need two known points of each curve to calculate its slope. Suppose we have two points with the coordinates (x1, y1) and (x2, y2). The y-values stand for the Rs/R0 values and the x-values for the gas concentrations. For a linear curve, the slope is then calculated as:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/JadXCd-aISp-O16p2TcXsGD8kMaCGptYIwIs" alt="Image" width="369" height="32" loading="lazy"></p>
<p>When we know the slope, we can find the gas concentration(x) for any given Rs/R0 value (y). The formula for this is:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/nSsOuEVNkeRnpvWPOPNKm0z-iuK8Z4QTyNpK" alt="Image" width="334" height="33" loading="lazy"></p>
<p>The code snippets below come from the script <code>get_sensor_values.py</code> which you can find on <a target="_blank" href="https://github.com/bertcarremans/air_quality_monitoring">Github</a>.</p>
<p>We put the formula in a function <code>get_ppm.</code> <code>curve['y']</code> and <code>curve['x']</code> are the known points on the curve for a gas. <code>curve['slope']</code> is what we calculated with the previous formula.</p>
<p>You can find these values in the <code>config_template.py</code> file. I derived these values with <a target="_blank" href="https://automeris.io/WebPlotDigitizer/">Webplotdigitizer</a> from the graphs on the data sheets. As a result, these values are not completely accurate. Use them with caution.</p>
<p>Note the use of <code>np.log10</code> and <code>np.power.</code> This reason for this is that the axes on the graph are in a log-scale.</p>
<pre><code>def get_ppm(Rs_R0_ratio, curve):
    x_val = (np.log10(Rs_R0_ratio) - curve[<span class="hljs-string">'y'</span>])/curve[<span class="hljs-string">'slope'</span>] + curve[<span class="hljs-string">'x'</span>]
    ppm_val = np.power(x_val, <span class="hljs-number">10</span>)
    <span class="hljs-keyword">return</span> ppm_val
</code></pre><p>We calculate the <code>Rs_R0_ratio</code> in the same manner as when calculating the R0 value. So I will not repeat this. To calculate this ratio, we loop over all gases and sensors and store this in <code>ppm_values[mq_sensor][gas].</code></p>
<pre><code class="lang-python"><span class="hljs-keyword">for</span> gas, curve <span class="hljs-keyword">in</span> cfg.CURVES[mq_sensor].items():
                ppm_values[mq_sensor][gas] = get_ppm(mq_values[mq_sensor], curve)
</code></pre>
<h4 id="heading-temperature-humidity-and-pressure">Temperature, humidity and pressure</h4>
<p>Besides the gas sensors, we read the temperature, humidity and pressure with the BME680 sensor. The BME680 sensor is connected to the GrovePi via an I2C port. To use the sensor, we import the package which can be installed from the <a target="_blank" href="https://github.com/pimoroni/bme680-python">Pimoroni repo</a> on Github.</p>
<pre><code><span class="hljs-keyword">import</span> bme680
</code></pre><p>The <code>set_..._oversample</code> methods specify how many samples we take to calculate the average value. We also did that for the gases. With <code>get_sensor_data</code> we read the sensor values.</p>
<pre><code class="lang-python">bme680_sensor = bme680.BME680(bme680.I2C_ADDR_PRIMARY)
bme680_sensor.set_humidity_oversample(bme680.OS_2X)
bme680_sensor.set_pressure_oversample(bme680.OS_4X)
bme680_sensor.set_temperature_oversample(bme680.OS_8X)
bme680_sensor.get_sensor_data()
</code></pre>
<pre><code>bme680_sensor.get_sensor_data()
</code></pre><h3 id="heading-storing-and-retrieving-sensor-data-in-cloud-firestore">Storing and retrieving sensor data in Cloud Firestore</h3>
<p>Some sensors provide new readings very fast. Other (less expensive) sensors will take more time. For this project, we will read the data every minute. This is set in the config file with <code>FIREBASE_INTERVAL = 60</code>.</p>
<p>In the free Spark plan of Firebase, the <a target="_blank" href="https://firebase.google.com/docs/firestore/quotas">Cloud Firestore quota</a> allow for 20K writes per day. With a one-minute interval, we will be well below that quota. The limit for reading documents in the Firestore is 50K per day.</p>
<p>You’ll need to create a Firebase project and <a target="_blank" href="https://firebase.google.com/docs/firestore/quickstart">create a Cloud Firestore</a>. After that, make sure to <a target="_blank" href="https://firebase.google.com/docs/admin/setup">generate the credentials</a> to authenticate your application. Save the credentials file in a secure location.</p>
<p>To work with Firebase via Python, we need to import the <code>firebase_admin</code> package. This package needs to be installed on your Raspberry Pi first, if needed.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> firebase_admin
<span class="hljs-keyword">from</span> firebase_admin <span class="hljs-keyword">import</span> credentials
<span class="hljs-keyword">from</span> firebase_admin <span class="hljs-keyword">import</span> firestore
</code></pre>
<p>After that, we can initialize the Firebase app with credentials. I store the location to the credentials file in <code>cfg.FIREBASE_CREDS_JSON</code>. When the app is initialized, we create a Firestore object <code>db</code>.</p>
<pre><code class="lang-python">firebase_path = Path.cwd() / cfg.FIREBASE_CREDS_JSON
cred = credentials.Certificate(str(firebase_path))
firebase_admin.initialize_app(cred)
</code></pre>
<p>After processing the gas values, it is time to store them in the Cloud Firestore. We will create a dictionary <code>firebase_values</code> to gather all the data. With a dict comprehension, we add the values for all gases for all MQ sensors. The BME680 values and timestamp are also added.</p>
<p>With the <code>add</code> method of the Firestore object <code>db</code>, it is easy to store the data in the Firestore.</p>
<p>The name of the collection in the Firestore is <code>cfg.FIREBASE_DB_NAME</code>. Learn more about the <a target="_blank" href="https://firebase.google.com/docs/firestore/data-model">data model of Firestore</a> on the Firebase website.</p>
<pre><code class="lang-python">firebase_values = {mq_sensor + <span class="hljs-string">'_'</span> + gas + <span class="hljs-string">'_ppm'</span>: ppm
                            <span class="hljs-keyword">for</span> mq_sensor, gases <span class="hljs-keyword">in</span> ppm_values.items()
                            <span class="hljs-keyword">for</span> gas, ppm <span class="hljs-keyword">in</span> gases.items()
                          }
firebase_values[<span class="hljs-string">'temperature'</span>] = bme680_sensor.data.temperature
firebase_values[<span class="hljs-string">'pressure'</span>] = bme680_sensor.data.pressure
firebase_values[<span class="hljs-string">'humidity'</span>] = bme680_sensor.data.humidity
firebase_values[<span class="hljs-string">'date'</span>] = datetime.now()
db.collection(cfg.FIREBASE_DB_NAME).add(firebase_values)
</code></pre>
<p>After storing the data, we wait a minute to start over with the following line of code.</p>
<pre><code>time.sleep(cfg.FIREBASE_INTERVAL)
</code></pre><h3 id="heading-improving-the-air-quality">Improving the air quality</h3>
<p>If the air quality indoors is not good we should take measures to improve it. Before we can do that, we need to be notified about critical gas concentrations.</p>
<p>One possibility is to send an alert notification by email, which we’ll discuss below.</p>
<p>Sometimes the source of indoor air pollution comes from outdoor air. For example, when your neighbors have wood-burning stoves or when you live near a factory.</p>
<p>In that case, you could install your measurement station outside and turn off the ventilation unit in your house if outdoor air quality is bad. With a remote-controlled switch, this can be done easily.</p>
<p>All code for this section is in <code>improve_air_quality.py</code> on Github.</p>
<h4 id="heading-sending-notifications-when-air-quality-reaches-a-critical-level">Sending notifications when air quality reaches a critical level</h4>
<p>We do not want to send an email each time the sensor outputs critical values (here, each minute).</p>
<p>Let’s say we want to check each hour whether there were critical values in the last hour. For that, we need to keep track of a <code>reference_time.</code> We initialize this when the program for readings sensor values starts. The interval at which we check again for critical gas concentrations is defined in <code>cfg.ALERT_INTERVAL</code>.</p>
<pre><code>reference_time = datetime.now()
</code></pre><p>When an hour has passed since <code>reference_time</code>, we can start to check if there were critical air pollutant values. We update <code>reference_time</code> with the current time.</p>
<p>With the <code>pytz</code> package, we can take into account our timezone. In my case, that is <code>Europe/Brussels.</code> We compute <code>one_hour_ago</code> by subtracting 60 minutes from the current time.</p>
<pre><code><span class="hljs-keyword">if</span> datetime.now() &gt; reference_time + timedelta(minutes=cfg.ALERT_INTERVAL):
    reference_time = datetime.now()
    brussels_tz = pytz.timezone(<span class="hljs-string">'Europe/Brussels'</span>)
    prev_check_time = brussels_tz.localize(datetime.now()) - timedelta(minutes=cfg.ALERT_INTERVAL)
</code></pre><p>With <code>prev_check_time</code> we extract the sensor readings from Firebase of the last hour. We do that by applying a <code>where</code> clause to the data that we <code>get</code> from the Cloud Firestore.</p>
<p>In this script, we will only use one gas sensor and a limited set of gases. The sensor is selected in <code>cfg.ALERT_SENSOR.</code> The gases are selected in <code>cfg.ALERT_GASES.</code> The data per gas is appended to <code>ppm_vals</code> as well as the <code>timestamps.</code></p>
<pre><code>timestamps = []
ppm_vals = {}
<span class="hljs-keyword">for</span> gas <span class="hljs-keyword">in</span> cfg.ALERT_GASES:
    ppm_vals[gas] = []

docs = db.collection(cfg.FIREBASE_DB_NAME).order_by(u<span class="hljs-string">'date'</span>).where(u<span class="hljs-string">'date'</span>, <span class="hljs-string">'&gt;='</span>, one_hour_ago).get()

<span class="hljs-keyword">for</span> doc <span class="hljs-keyword">in</span> docs:
    data = doc.to_dict()
    <span class="hljs-keyword">for</span> gas <span class="hljs-keyword">in</span> cfg.ALERT_GASES:
        ppm_vals[gas].append(data[cfg.ALERT_SENSOR + gas + <span class="hljs-string">'_ppm'</span>])

    timestamps.append(data[<span class="hljs-string">'date'</span>].strftime(<span class="hljs-string">'%H:%M:%S'</span>))
</code></pre><p>We look for critical values with the function <code>find_crit_val.</code> We will only check if the value surpassed an upper bound <code>ubound</code>. These upper bounds need to be specified in the config file.</p>
<p>The data are in ascending chronological order. Thus, we can use the <code>next</code> method to find the first timestamp for which <code>v &gt; ubound</code>. We return a tuple containing the timestamp of the critical value and the critical value itself.</p>
<p>If there is no critical value, we return the tuple <code>(None, None).</code></p>
<pre><code>def find_crit_val(timestamps, val_list, ubound):
    <span class="hljs-keyword">try</span>:
        (crit_time, crit_value) = next(((i,v) <span class="hljs-keyword">for</span> i, v <span class="hljs-keyword">in</span> zip(timestamps, val_list) <span class="hljs-keyword">if</span> v &gt; ubound))          
    <span class="hljs-attr">except</span>:
        (crit_time, crit_value) = (None,None)
    <span class="hljs-keyword">return</span> (crit_time, crit_value)
</code></pre><p>The critical tuples are stored in a dictionary <code>crit_dict.</code> As the key, we use the gas name. We then check for gas-sensor combinations with a critical timestamp and critical value. In that case, we add an alert message to <code>critical_msg.</code></p>
<pre><code>crit_dict = {}
<span class="hljs-keyword">for</span> gas <span class="hljs-keyword">in</span> cfg.ALERT_GASES:
    (crit_time, crit_value) = find_crit_val(timestamps, ppm_vals[gas], cfg.UPPERBOUNDS[gas])
    crit_dict[gas] = (crit_time, crit_value)
critical_msg = <span class="hljs-string">''</span>
    <span class="hljs-keyword">for</span> k, v <span class="hljs-keyword">in</span> crit_dict.items():
        <span class="hljs-keyword">if</span> v[<span class="hljs-number">0</span>] is not None and v[<span class="hljs-number">1</span>] is not None:
            critical_msg += <span class="hljs-string">'\nCritical value for '</span> + k + <span class="hljs-string">' of '</span> + str(v[<span class="hljs-number">1</span>]) + cfg.UNITS[k] + <span class="hljs-string">' at '</span> + str(v[<span class="hljs-number">0</span>])
</code></pre><p>If <code>critical_msg</code> is not empty, we send an email. Sending an email is done with the <code>smtplib</code> package. How to send an email with Python is explained on <a target="_blank" href="https://automatetheboringstuff.com/chapter16/">AutomateThe BoringStuff.com</a>.</p>
<p>You need to generate an <a target="_blank" href="https://support.google.com/mail/?p=InvalidSecondFactor">application-specific password</a> for your email if you are using Google’s two-factor authentication.</p>
<pre><code><span class="hljs-keyword">if</span> critical_msg != <span class="hljs-string">''</span>:
    <span class="hljs-keyword">try</span>:
        msg = MIMEText(critical_msg, _charset=<span class="hljs-string">'utf-8'</span>)
        msg[<span class="hljs-string">'Subject'</span>] = Header(<span class="hljs-string">'Air Quality Alert'</span>, <span class="hljs-string">'utf-8'</span>)
        smtpObj = smtplib.SMTP(<span class="hljs-string">'smtp.gmail.com'</span>, <span class="hljs-number">587</span>)
        smtpObj.ehlo()
        smtpObj.starttls()
        smtpObj.login(cfg.EMAIL, cfg.EMAIL_PW)
        smtpObj.sendmail(cfg.EMAIL, cfg.EMAIL, msg.as_string())
        smtpObj.quit()  
    except smtplib.SMTPException:
        print(<span class="hljs-string">'Something went wrong while sending the email'</span>)
</code></pre><h4 id="heading-automatically-control-your-ventilation-unit">Automatically control your ventilation unit</h4>
<p>With a remote-controlled switch, we can turn on or off any device that is connected to it. Thus, also a ventilation unit. <a target="_blank" href="https://energenie.com/">Energenie</a> creates the PiMote add-on specifically for the Raspberry Pi. To control the Energenie switch, you need to install the <code>energenie</code> package and import it.</p>
<p>Note that you can not attach the PiMote on top of the GrovePi. So you’ll need a second Raspberry Pi.</p>
<p>When starting the script, the first thing we do is define a boolean variable <code>ventilation_on.</code> We set it to <code>False</code> because this is the first time we run the script.</p>
<pre><code><span class="hljs-keyword">import</span> energenie
ventilation_on = False
</code></pre><p>If <code>critical_msg</code> is not empty, there was a critical gas concentration in the last alert interval. In that case, we turn on the ventilation with the <code>switch_on</code> method of the energenie package.</p>
<p>If there was no critical gas concentration and the ventilation was switched on in the last alert interval, we can switch it off.</p>
<p>You might need to set another interval before switching your ventilation off. That depends on the flow rate of your ventilation unit, the gases measured and whether the pollution source has been disabled.</p>
<pre><code><span class="hljs-keyword">if</span> critical_msg != <span class="hljs-string">''</span>:
    <span class="hljs-keyword">if</span> not ventilation_on:
        energenie.switch_on(<span class="hljs-number">1</span>)
        ventilation_on = True
<span class="hljs-attr">else</span>:
    <span class="hljs-keyword">if</span> ventilation_on:
        energenie.switch_off(<span class="hljs-number">1</span>)
        ventilation_on = False
</code></pre><h3 id="heading-visualization-of-air-quality-with-dash">Visualization of air quality with Dash</h3>
<p>A notification with critical gas concentrations can help to take immediate action. But it is also interesting to track the gas concentrations over time. By visualizing the sensor values in a dashboard, we can look at the trend of the gas concentrations. This can be done with Dash. On the website of Dash, you can find a <a target="_blank" href="https://dash.plot.ly/">great tutorial</a> on how to get started.</p>
<p>In this project, we will build a dashboard and host it on <a target="_blank" href="https://www.pythonanywhere.com/">PythonAnyWhere.com</a>. To use Dash on PythonAnywhere, you need to create a <a target="_blank" href="https://help.pythonanywhere.com/pages/Virtualenvs/">virtual environment</a>. You can follow the steps of <a target="_blank" href="https://github.com/conradho/dashingdemo">this demo</a> on how to set-up a Dash app on PythonAnyWhere.</p>
<p>Below I will show how I built the dashboard for our air quality station. The full script can be found in plot_sensor_values.py on <a target="_blank" href="https://github.com/bertcarremans/air_quality_monitoring">Github</a>.</p>
<p>First of all, you need to import the <code>dash</code> package.</p>
<pre><code><span class="hljs-keyword">import</span> dash
<span class="hljs-keyword">import</span> dash_core_components <span class="hljs-keyword">as</span> dcc
<span class="hljs-keyword">import</span> dash_html_components <span class="hljs-keyword">as</span> html
</code></pre><p>In the demo on the Dash website, they use a link to a Cascading Style Sheet (CSS) to provide a nice page design. If you want to <a target="_blank" href="https://dash.plot.ly/external-resources">use a local CSS</a> on your laptop or web server, you can add an assets folder. In that folder you can add your CSS and Dash will pick it up from there.</p>
<p>Then you’ll need to get the data from Firebase. This can be done similarly as we did for sending the alert notifications. So we will not go over that again.</p>
<p>With the data collected from Firebase, we can fill the graphs in our dashboard. We first create a Dash object <code>app</code> and give it a <code>title.</code></p>
<pre><code>app = dash.Dash(__name__)
app.title = <span class="hljs-string">'Indoor Air Quality Dashboard'</span>
</code></pre><p>Then we create the <code>layout</code> of the dashboard. A <code>H1</code> heading component, a <code>container</code> div and a div containing the <code>graphs.</code></p>
<pre><code>app.layout = html.Div([
    html.H1(style={<span class="hljs-string">'textAlign'</span>:<span class="hljs-string">'center'</span>}, children=<span class="hljs-string">'Indoor Air Quality Dashboard'</span>),
    html.Div(id=<span class="hljs-string">'container'</span>),
    html.Div(graphs)
])
</code></pre><p><code>graphs</code> is a list that contains the info per graph. Below you can see how the graph for temperature is set up. You can add the <code>dcc.Graph</code> for humidity and pressure as well by appending them to <code>graphs.</code></p>
<pre><code>graphs = [
    dcc.Graph(
        id=<span class="hljs-string">'temperature'</span>,
        figure={
            <span class="hljs-string">'data'</span>:[{
                <span class="hljs-string">'x'</span>:timestamps,
                <span class="hljs-string">'y'</span>:temperatures,
                <span class="hljs-string">'type'</span>:<span class="hljs-string">'line'</span>,
                <span class="hljs-string">'name'</span>: <span class="hljs-string">'Temperature'</span>,
                <span class="hljs-string">'line'</span>: {<span class="hljs-string">'width'</span>:<span class="hljs-number">2</span>, <span class="hljs-string">'color'</span>: <span class="hljs-string">'#542788'</span>}
                }],
            <span class="hljs-string">'layout'</span>:{
                <span class="hljs-string">'title'</span>: <span class="hljs-string">'Temperature'</span>,
                <span class="hljs-string">'yaxis'</span>: {<span class="hljs-string">'title'</span>: <span class="hljs-string">'Celsius'</span>},
                <span class="hljs-string">'xaxis'</span>: {<span class="hljs-string">'title'</span>: <span class="hljs-string">'Timestamp'</span>, <span class="hljs-string">'tickvals'</span>:timestamps}
            }
        }
    )
]
</code></pre><p>The graphs for the MQ sensors can be appended in a for loop.</p>
<pre><code><span class="hljs-keyword">for</span> mq_sensor <span class="hljs-keyword">in</span> cfg.MQ_SENSORS.keys():
    <span class="hljs-keyword">for</span> gas <span class="hljs-keyword">in</span> cfg.CURVES[mq_sensor].keys():
        sensor_gas_key = mq_sensor + <span class="hljs-string">'_'</span> + gas + <span class="hljs-string">'_ppm'</span>
        title = gas + <span class="hljs-string">' concentration on '</span>+ mq_sensor + <span class="hljs-string">' sensor'</span>
        data = ppm_values[mq_sensor][gas]
        data.reverse()

        graphs.append(dcc.Graph(
            id=sensor_gas_key,
            figure={
                <span class="hljs-string">'data'</span>: [{
                    <span class="hljs-string">'x'</span>: timestamps,
                    <span class="hljs-string">'y'</span>: data,
                    <span class="hljs-string">'type'</span>:<span class="hljs-string">'line'</span>,
                    <span class="hljs-string">'name'</span>: title,
                    <span class="hljs-string">'line'</span>: {<span class="hljs-string">'width'</span>:<span class="hljs-number">2</span>}
                }],
                <span class="hljs-string">'layout'</span>: {
                    <span class="hljs-string">'title'</span>: title,
                    <span class="hljs-string">'yaxis'</span>: {<span class="hljs-string">'title'</span>: <span class="hljs-string">'ppm'</span>},
                    <span class="hljs-string">'xaxis'</span>: {<span class="hljs-string">'title'</span>: <span class="hljs-string">'Timestamp'</span>, <span class="hljs-string">'tickvals'</span>:timestamps}
                }
            }
        ))
</code></pre><p>As a result, you will have a dashboard with graphs like the one below.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/XhmuZyEJBLsJ1eUuTWb80qkmI99ptztNyiF7" alt="Image" width="800" height="288" loading="lazy">
<em>Graph with MQ2 sensor data for methane (ch4)</em></p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>With a few low-cost gas sensors and a Raspberry Pi (and GrovePi) it is easy to build an air quality measurement station. You can then act on the data by sending alert notifications when air quality is bad or switch on the ventilation. With Dash you can build beautiful visualizations to monitor the air quality over time.</p>
<p>Below I noted some ideas to take this project further.</p>
<ul>
<li>make a mobile app for the visualizations and receiving notifications</li>
<li>add LEDs, a buzzer and an OLED display to the Raspberry Pi to get instant feedback on air quality</li>
<li>install measurement stations at your family and friends' place and <a target="_blank" href="https://towardsdatascience.com/visualizing-air-pollution-with-folium-maps-4ce1a1880677">visualize it on an interactive map</a>.</li>
<li>once you have enough data, <a target="_blank" href="https://towardsdatascience.com/forecasting-air-pollution-with-recurrent-neural-networks-ffb095763a5c">build a time-series model</a> to predict the indoor air quality</li>
</ul>
<p>Hopefully, this story motivates you to start building your own measurement station. If you have questions or suggestions let me know.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to build the most robust and secure home automation system ]]>
                </title>
                <description>
                    <![CDATA[ By Amir Off In this article, I’ll discuss how I built a Smart Home Automation System with Angular and Node.js on a Raspberry Pi without relying on any external cloud services. Intro Over the last few days, I spent some nights designing and developing... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/the-most-robust-and-secure-home-automation-system-6d0ddbb39f29/</link>
                <guid isPermaLink="false">66c361d95c8b3e001e067e67</guid>
                
                    <category>
                        <![CDATA[ Internet of Things ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Raspberry Pi ]]>
                    </category>
                
                    <category>
                        <![CDATA[ technology ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Sat, 29 Sep 2018 11:55:28 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/1*ekrneFlaBAAqotbwcJ0pDA.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Amir Off</p>
<p><em>In this article, I’ll discuss how I built a Smart Home Automation System with Angular and Node.js on a Raspberry Pi without relying on any external cloud services.</em></p>
<h3 id="heading-intro">Intro</h3>
<p>Over the last few days, I spent some nights designing and developing a home automation system based on JavaScript, with the use of Angular and Node.js. And, like with any other project, the planning involved some deep research on the internet.</p>
<p>It turned out that there are plenty of fish in the sea —plenty of solutions on how to implement a home automation system. Some offer paid services in “the cloud” and others explain how to build your own using a technology called MQTT.</p>
<p>None of the solutions made any sense to me. All options were either expensive, or had inconvenient implementations or even security flaws.</p>
<p>But, before we go any further, let’s explain what MQTT is. MQTT stands for <strong>MQ Telemetry Transport</strong>. It is a publish/subscribe, extremely simple and lightweight messaging protocol. MQTT is designed for constrained devices and low-bandwidth, high-latency, or unreliable networks.</p>
<p>The design principles are to minimise network bandwidth and device resource requirements whilst attempting to ensure reliability and some degree of assurance of delivery. These principles also turn out to make the protocol <a target="_blank" href="http://mqtt.org">ideal for</a> the emerging “machine-to-machine” (M2M) or “Internet of Things” world of connected devices, and for mobile applications where bandwidth and battery power are at a premium.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/mNpCeEZHwF1UuwYj01abd-sbVPrKW-4yy8-t" alt="Image" width="800" height="460" loading="lazy">
<em>MQTT Publish/Subscribe Architecture (Image source: <a target="_blank" href="www.hivemq.com">HiveMQ.com</a>)</em></p>
<p>Why wasn’t I convinced about using MQTT, or by any of the solutions I found on the internet? Two reasons:</p>
<ol>
<li>While the MQTT technology seems very convenient for IoT devices, I still thought it was unnecessary. The system which I will be showing in the following tutorial operates in the same medium where the IoT devices live. All benefits that MQTT has for being “fast” and having “low-bandwidth” become irrelevant. Plus there’s all the hassle that is involved in its implementation and all the extra overhead with the additional npm packages that are required for it to work in a JavaScript environment. Instead, I will be using generic JavaScript and Node.js libraries only, nothing more!</li>
<li>What about the security part? Well, I’m not a big fan of “the cloud” or cloud computing in general. In some cases it can be very beneficial, but in most cases it’s just unnecessary. Think about it: why would you have a service that is required for controlling your home appliances to be hosted somewhere else in “the cloud” and not in your own network?</li>
</ol>
<p><img src="https://cdn-media-1.freecodecamp.org/images/6PYFXtvRMHaKIfhgJC68Ux7p5uJMqe5K1wE4" alt="Image" width="442" height="583" loading="lazy">
_Comic by [Geek and Poke](http://geekandpoke.typepad.com/geekandpoke/2009/11/good-consultants.html" rel="noopener" target="<em>blank" title=")</em></p>
<p>One might think that “the cloud” provides the ability to access your home appliances from anywhere in the world via the internet.</p>
<p>But think about this: when your home network doesn’t have internet connection, “the cloud” becomes redundant. More importantly, you can still make your home automation system accessible from the internet using port-forwarding even if it’s hosted on your local network.</p>
<p>This is when it “clicked” for me, and I thought about hosting the whole system on a Raspberry Pi and keeping it in my local network.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/IkTlTjziky8EUtdFX8G4BCVGwMa9aijqLZh7" alt="Image" width="800" height="600" loading="lazy">
<em>A Raspberry Pi 3 Model B</em></p>
<h3 id="heading-the-technology">The technology</h3>
<ol>
<li><strong>Software:</strong> The reason why I chose Angular and Node.js is because they’re based on JavaScript and I’m already familiar with it. After all, I wanted to design and develop a progressive web app that communicates with my IoT devices via HTTP — and JavaScript offered all the functionality that I needed.</li>
<li><strong>Hardware:</strong> The system works with microcontrollers like the Arduino Uno/Mega/Du/MKR1000, Adafruit HUZZAH CC3000, and any other microcontroller with a WiFi connection. I am using the <a target="_blank" href="https://medium.com/p/deb7bd1841c1?source=user_profile---------3------------------"><strong>ESP8266</strong></a> as a base component for my home automation system. It’s a low-cost WiFi microchip with microcontroller capability. It has everything I need and for a cheap price! Lastly, we need to host the system somewhere on our local network — so what’s better than the Raspberry Pi?</li>
</ol>
<p>This won’t be a coding tutorial where I dive deep into coding, since this project is open-source and I will be publishing everything on GitHub. I will only demonstrate how to implement your own home automation system and will be going through each step. If you’re a developer please <a target="_blank" href="https://github.com/ameer157/smarthaus"><strong>fork</strong></a> the repository and get involved in improving it.</p>
<h3 id="heading-the-setup">The setup</h3>
<p>I estimate that it will take about 40 minutes to finish this whole setup plus any time spent online searching for solutions for installation errors.</p>
<h4 id="heading-what-is-needed"><strong>What is needed?</strong></h4>
<p>A Raspberry Pi is required. In my example I’m using a Raspberry Pi 3, but it should work with most versions. The components needed are:</p>
<ol>
<li>Raspberry Pi board</li>
<li>MicroSD card (A class 10 with 16 GB or higher is recommended)</li>
<li>A USB MicroSD card reader or SD card adapter</li>
<li>HDMI monitor and a USB keyboard (only required temporarily for the first boot of the Raspberry Pi)</li>
<li>Ethernet cable (not needed for Raspberry Pi 3 as it has built in WiFi)</li>
</ol>
<h4 id="heading-installing-raspbian-os-on-the-raspberry-pi">Installing Raspbian OS on the Raspberry Pi</h4>
<p>Raspbian is a free operating system based on Debian Linux, and it is optimized for Raspberry Pi.</p>
<p><strong>I recommend</strong> the headless “LITE” version. It has no desktop environment or any graphical user interface, and it’s remotely accessible from a computer or device on the same network via SSH. We’re keeping things simple since this is the only way we are going to access the Raspberry Pi. The LITE version has all the functionality we’re looking for.</p>
<ol>
<li>Download <a target="_blank" href="https://www.raspberrypi.org/downloads/raspbian/"><strong>the latest</strong></a> Raspbian image from the official Raspberry Pi website.</li>
<li>Flash the Raspbian OS image to the SD card with <a target="_blank" href="https://etcher.io/"><strong>Etcher</strong></a> or any other OS image burning software of your choice.</li>
</ol>
<h4 id="heading-setting-up-the-raspberry-pi">Setting up the Raspberry Pi</h4>
<p>To get the Raspberry Pi ready to boot we need to:</p>
<ol>
<li>Insert the MicroSD card into the Raspberry Pi</li>
<li>Connect the USB keyboard and the HDMI cable</li>
<li>Connect the Ethernet cable or if you have a Raspberry Pi 3 and want to use WiFi you should set up the network in the next section</li>
</ol>
<p>When the Raspberry Pi has finished booting up, log in using username <code>pi</code> and password <code>raspberry</code></p>
<h4 id="heading-enabling-wifi-and-connecting-to-the-network">Enabling WiFi and connecting to the network</h4>
<p><strong>Skip this step</strong> if you chose to connect with an Ethernet cable.</p>
<ol>
<li>Open the “wpa-supplicant” configuration file</li>
</ol>
<pre><code>$ sudo nano /etc/wpa_supplicant/wpa_supplicant.conf
</code></pre><ol start="2">
<li>add the following at the bottom of the file while adding your wifi name and password:</li>
</ol>
<pre><code>network={
</code></pre><pre><code>   ssid=<span class="hljs-string">"your_networks_name"</span>   psk=<span class="hljs-string">"your_networks_password"</span>
</code></pre><pre><code>}
</code></pre><p><strong>3.</strong> Press <code>Ctrl+X</code> to save the code. Confirm with <code>Y</code> then <code>Enter</code></p>
<p><strong>4.</strong> Reboot the Raspberry Pi with the following command:</p>
<pre><code>$ sudo reboot
</code></pre><h4 id="heading-enabling-ssh-and-changing-the-username-and-password"><strong>Enabling SSH and changing the username and password</strong></h4>
<p>Now that the Raspberry Pi is connected to the internet, it’s recommended to change the default password.</p>
<ol>
<li>Open the Raspberry Pi configuration tool and click the second option “Change User Password” and follow the instructions</li>
</ol>
<pre><code>$ sudo raspi-config
</code></pre><p><img src="https://cdn-media-1.freecodecamp.org/images/dtaG9lRXQkYqmnIqANMtcZOb0IoxB9sfhDmz" alt="Image" width="800" height="241" loading="lazy"></p>
<p><strong>2.</strong> Select option 5 “Interfacing Options” then activate SSH</p>
<p><strong>3.</strong> Reboot the Raspberry Pi. When it’s up, you have SSH enabled and it’s ready to be accessed remotely from your desktop computer</p>
<pre><code>$ sudo reboot
</code></pre><h4 id="heading-configuring-remote-access-to-the-raspberry-pi">Configuring remote access to the Raspberry Pi</h4>
<p>Now, finally, the part when we install the required software on the Raspberry Pi. This part can be executed directly on the Pi through the terminal using a HDMI monitor and a USB keyboard. For convenience — and since we enable remote SSH connection — we’re going to connect from another desktop environment. This is the best and easiest way to remotely access and control the Pi whenever changes and configurations are needed.</p>
<p>So, basically, this is how you can access the command line interface of a Raspberry Pi remotely from another computer or any device on the same network using SSH. This can be done in two ways:</p>
<ol>
<li>Using the Command Prompt or PowerShell (I’m using Windows on a Desktop computer), replace with your username and IP address</li>
</ol>
<pre><code>$ ssh username@ipaddress
</code></pre><p>If you <strong>do not know</strong> the IP address, type “<code>hostname -I"</code> in the Raspberry Pi command line.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/p9TrkcLo-EaANTz2-5xivbif9p3RXm5wIGYA" alt="Image" width="918" height="547" loading="lazy"></p>
<p><strong>2.</strong> The second method is using a client program like <a target="_blank" href="https://www.putty.org/"><strong>PuTTY</strong></a> or any <a target="_blank" href="https://www.google.co.il/search?q=ssh+client"><strong>other</strong></a> functioning client SSH software. Here’s an easy <a target="_blank" href="https://www.raspberrypi.org/documentation/remote-access/ssh/windows.md"><strong>guide</strong></a> for using PuTTY.</p>
<h4 id="heading-installing-the-required-software-on-the-raspberry-pi">Installing the required software on the Raspberry Pi</h4>
<p>Before installing anything, it’s recommended to update the Raspberry Pi’s operating system and packages. Doing this regularly will keep it up-to-date.</p>
<ol>
<li>Update the system packages list using the following command:</li>
</ol>
<pre><code>$ sudo apt-get update
</code></pre><p><strong>2.</strong> Upgrade all your installed packages to their latest version:</p>
<pre><code>$ sudo apt-get dist-upgrade
</code></pre><p><strong>3.</strong> Download and install the latest version of Node.js:</p>
<pre><code><span class="hljs-comment">// To download$ curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -</span>
</code></pre><pre><code><span class="hljs-comment">// To install$ sudo apt-get install -y nodejs</span>
</code></pre><pre><code><span class="hljs-comment">// Check if the installation was successful:$ node -v</span>
</code></pre><p><strong>4.</strong> Install the Angular CLI globally:</p>
<pre><code>$ npm install -g @angular/cli
</code></pre><p><strong>5.</strong> Install the Git version-control system:</p>
<pre><code>$ sudo apt-get install git
</code></pre><h4 id="heading-installing-the-database-mongodb">Installing the database (MongoDB)</h4>
<p>We need a database for storing the registered users and their credentials. Here are the required steps:</p>
<ol>
<li>Install MongoDB</li>
</ol>
<pre><code>$ sudo apt-get install mongodb
</code></pre><ol start="2">
<li>Start the MongoDB process</li>
</ol>
<pre><code>$ sudo service mongodb start
</code></pre><ol start="3">
<li>Start the mongo Shell</li>
</ol>
<pre><code>$ mongo
</code></pre><ol start="3">
<li>Create a database called “smarthaus”</li>
</ol>
<pre><code>$ use smarthaus
</code></pre><p>In MongoDB, default database is test. If you didn’t create any database, then collections will be stored in test database.</p>
<h4 id="heading-installing-smart-haus">Installing Smart Haus</h4>
<p><strong>1.</strong> Check the current work directory using this command:</p>
<pre><code>$ pwd
</code></pre><pre><code><span class="hljs-comment">/* It will probably print "/home/pi"   where "pi" is the current user directory */</span>
</code></pre><p><strong>It’s recommended</strong> to clone the project’s repository under the pi’s user directory but you can navigate somewhere else if you’re sure.</p>
<p><strong>2.</strong> Clone the repository from:</p>
<pre><code>$ git clone https:<span class="hljs-comment">//github.com/ameer157/smarthaus.git</span>
</code></pre><p>Make sure to navigate inside the directory using:</p>
<pre><code>$ cd smarthaus
</code></pre><p>Before installing any npm packages using “npm install” please refer to the <a target="_blank" href="https://docs.npmjs.com/getting-started/fixing-npm-permissions#option-two-change-npms-default-directory"><strong>npm guide to fix permissions</strong></a> to learn how to fix any “<strong>EACCESS</strong> ”errors you might face during installation. This is <strong>very important</strong> since it will prevent any npm permission errors, and allows you to install packages globally without using sudo. Using sudo with npm is not recommended and <a target="_blank" href="https://medium.com/@ExplosionPills/dont-use-sudo-with-npm-still-66e609f5f92"><strong>should be avoided</strong></a>.</p>
<p><strong>3.</strong> Install all the required packages for the project:</p>
<pre><code>$ npm install
</code></pre><p><img src="https://cdn-media-1.freecodecamp.org/images/iCw2rJLK34PWv0B-UGGeoWcwO8i10-mgY2FI" alt="Image" width="961" height="576" loading="lazy"></p>
<h4 id="heading-starting-the-nodejs-server">Starting the Node.js server</h4>
<p>Before starting the server we need to build the project using the Angular CLI tool. And lastly, we configure the Raspberry Pi so that it runs the server whenever it boots up.</p>
<ol>
<li>Build the project using:</li>
</ol>
<pre><code>$ ng build --prod
</code></pre><p><strong>2.</strong> Edit the <code>rc.local</code> file using <code>nano</code>:</p>
<pre><code>$ sudo nano /etc/rc.local
</code></pre><p><strong>3.</strong> Add the following on the line before <code>exit 0</code> then exit and save the file:</p>
<pre><code>su pi -c <span class="hljs-string">'cd /home/pi/smarthaus/backend &amp;&amp; sudo node server.js &gt; log.txt &amp;'</span>
</code></pre><p><img src="https://cdn-media-1.freecodecamp.org/images/jHBDN5kBPqJ6oiWY269MJ1o0dAe7TlEPrL7X" alt="Image" width="800" height="501" loading="lazy"></p>
<p>The Node.js server is now ready! It will run on every system boot up and save logs in under the same directory in a “log.txt” file.</p>
<p>Let’s run it now and see if it works using this command:</p>
<pre><code>$ sudo node server
</code></pre><p>The system in now accessible from any device on your network via the Raspberry Pi’s IP address.</p>
<p>Please go ahead and <a target="_blank" href="https://github.com/ameer157/smarthaus"><strong>fork</strong></a> this project and get involved in developing the missing parts ?</p>
<h3 id="heading-the-end">The end</h3>
<p>We got ourselves a working home automation system running safely on a Raspberry Pi in our local network without the use of “the cloud” or somebody else’s server.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/3JIKPjo9dQos9Ua-10JPxsJnQPVt4PvH1EJF" alt="Image" width="566" height="306" loading="lazy"></p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/BdA31vbNfgwI0goItUDCZLdNlAv7XF5akvsu" alt="Image" width="566" height="306" loading="lazy">
<em>Real-time device status synchronization</em></p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/TAQatUFW1P3nbEaFN5v3Lm495VfROtRNnkKo" alt="Image" width="693" height="411" loading="lazy"></p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/tULxNb2WIueLALx-z-A5D6AMPMtw8h0hJ2Tw" alt="Image" width="566" height="306" loading="lazy">
<em>Adding a new device synchronising data on-demand</em></p>
<p>My Raspberry Pi sitting next to my <a target="_blank" href="http://bit.ly/2OiO1Pm"><strong>Fingbox</strong></a> and router in the living room ?</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/UbJPvVTIOvQXwBXNclgpBCxqWVFnUqtbocMZ" alt="Image" width="800" height="600" loading="lazy">
<em>Rick and Morty providing tech support ??</em></p>
<p>I hope you enjoyed reading,<br>Please <a target="_blank" href="https://medium.com/@ameer157"><strong>follow</strong></a> and <strong>share</strong> for more tech stuff ??</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/dc-iMjp94vVUVYzJKTYxFuVd8UYPENjRMPTH" alt="Image" width="146" height="174" loading="lazy"></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to make your own Python dev-server with Raspberry Pi ]]>
                </title>
                <description>
                    <![CDATA[ By Karan Asher In simple terms, Raspberry Pi is a super cheap ($40) Linux based computer. That’s it. Seriously. It can do whatever you can imagine a normal Linux computer can do, such as browse the web, write code, edit documents, and connect to I/O ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-make-your-own-python-dev-server-with-raspberry-pi-37651156379f/</link>
                <guid isPermaLink="false">66c3538969cdcb77cd98a598</guid>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Raspberry Pi ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tech  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Sat, 15 Sep 2018 17:10:32 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/1*ylvkldd2jkFaSSpn8MdVTQ.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Karan Asher</p>
<p><em>In simple terms, Raspberry Pi is a super cheap ($40) Linux based computer. That’s it. Seriously.</em></p>
<p>It can do whatever you can imagine a normal Linux computer can do, such as browse the web, write code, edit documents, and connect to I/O devices such a thumb drive, mouse, keyboard, etc. This tutorial will be focused on learning how to make your own Python dev-server with Raspberry Pi.</p>
<h3 id="heading-step-0-define-the-goal">Step 0. Define the goal</h3>
<p>Before we begin, it is important to understand what is it that we are trying to build. By the end of the tutorial, you will be able to run a basic website (using <a target="_blank" href="http://flask.pocoo.org/">Flask</a>) off of a Raspberry Pi on your local home network.</p>
<p>The goal of this tutorial is to demonstrate how a Pi can be used as a dev-server, more specifically, the example will be to host a simple website (using <a target="_blank" href="http://flask.pocoo.org/">Flask</a>).</p>
<h3 id="heading-step-1-state-the-assumptions">Step 1. State the assumptions</h3>
<p>Here are some assumptions that this tutorial will make:</p>
<ol>
<li>You already have a Raspberry Pi set up with Raspbian OS. <a target="_blank" href="https://www.raspberrypi.org/documentation/setup/">Here</a> is a useful setup guide if you need one.</li>
<li>The Pi is connected to your home WiFi (and that you know the Pi’s IP address).</li>
<li>You will not require a screen going forward. assuming points 1 and 2 are complete.</li>
</ol>
<p>We will use <a target="_blank" href="https://code.visualstudio.com/">VS Code</a> with the <a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=rafaelmaiolla.remote-vscode">Remote VSCode</a> extension to remotely create and edit files on the Pi. I definitely recommend that you use these two to follow along. Also, these will make working with remote files a lot easier, so that’s a plus.</p>
<h3 id="heading-step-2-find-the-pis-ip-address">Step 2. Find the Pi’s IP address</h3>
<p>First, connect the Pi to a power supply, and ensure that it is correctly booted up and connected to the WiFi/Ethernet (basically, it needs to have an internet connection).</p>
<p>We will use ssh to connect to and communicate with the Pi. To do that remotely using a laptop, you need to know its IP address. This can be easily obtained using your ISP’s admin portal (usually available at <a target="_blank" href="http://192.168.0.1">http://192.168.0.1</a>. Please note that this could be different for different ISPs.)</p>
<p>Usually, you should have your Pi connected to an address which may look similar to ‘192.168.0.12.’ Again, this will be different for different people. So please use the IP address that you found for your Pi in the admin portal. Going forward, this tutorial will use 192.168.0.12 as the Pi’s IP address.</p>
<h3 id="heading-step-3-connect-to-the-pi-using-ssh">Step 3. Connect to the Pi using ssh</h3>
<p>Open VS Code and its built-in terminal window on your laptop. Connect to the Pi with an IP address of 192.188.0.12 using the following ssh command:</p>
<blockquote>
<p><em>ssh -R 52698:localhost:52698 pi@192.168.0.12</em></p>
</blockquote>
<p>The above command will set up a 2-way communication channel between your laptop and the Pi. If this is the first time you are connecting to the Pi, use raspberry as the password. You may be prompted to change your default password. It is highly recommended that you do so.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/k-iRsowxI6mnJfpiWmQPGkQOiaRQ8OE50gpg" alt="Image" width="800" height="354" loading="lazy">
<em>Terminal window after successfully connecting to the pi</em></p>
<h3 id="heading-step-4-create-a-project-directory">Step 4. Create a project directory</h3>
<p>You should now be in the home directory of the Pi. Let’s create a directory for the website we wish to build. Use the following command to create the directory:</p>
<blockquote>
<p><em>mkdir MyFlaskWebsite</em></p>
</blockquote>
<p>Use the ‘ls’ command to verify that you can indeed see a new folder named MyFlaskWebsite.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/cUhjzjTdVOTdFCANjEYrStpwuNoy4YsKWTZj" alt="Image" width="716" height="299" loading="lazy">
<em>Create and verify the project directory</em></p>
<h3 id="heading-step-5-install-flask">Step 5. Install Flask</h3>
<p>We will use <a target="_blank" href="http://flask.pocoo.org/">Flask</a> to create a simple website. <a target="_blank" href="http://flask.pocoo.org/">Flask</a> is a Python based micro web framework. It uses <a target="_blank" href="http://jinja.pocoo.org/">Jinja</a> (Python based template engine) as its template engine which makes it very usable and powerful. Use the following command to install flask on the Pi:</p>
<blockquote>
<p><em>sudo apt-get install python3-flask</em></p>
</blockquote>
<p><img src="https://cdn-media-1.freecodecamp.org/images/JBEt1A3RTqaIUErc0sevCZjxpZMppU1qTCjA" alt="Image" width="666" height="158" loading="lazy">
<em>Install flask</em></p>
<h3 id="heading-step-6-write-some-basic-code">Step 6. Write some basic code</h3>
<p>Now that Flask is installed, we can start creating files and writing some code. First, navigate to your newly created project directory (from step 4) by using the following command:</p>
<blockquote>
<p><em>cd MyFlaskWebsite</em></p>
</blockquote>
<p>All project files and folders will reside inside this ‘MyFlaskWebsite’ directory. Now, create your first code file (app.py) using the following command:</p>
<blockquote>
<p><em>touch app.py</em></p>
</blockquote>
<p>On checking the directory using the ‘ls’ command, you should be able to see this newly created file.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/Ad9fp3IFU8Q2TtuKgRC3f3-dInXyaEQ-6NZH" alt="Image" width="557" height="211" loading="lazy">
<em>Navigate to project directory and create a new file</em></p>
<p>Now, press F1 and choose ‘Remote Start Server.’ This should allow you to remotely edit files on the Pi using your laptop.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/UB9XT8GkO3T49BOvUd6iAX6yn2iAbHiqKbqi" alt="Image" width="800" height="411" loading="lazy">
<em>Start the remote server</em></p>
<p>Next, use the following command to start editing the newly created app.py file. It might take a few seconds but the empty file should then be visible in the window right above.</p>
<blockquote>
<p><em>rmate app.py</em></p>
</blockquote>
<p><img src="https://cdn-media-1.freecodecamp.org/images/O4Ybu5hVBCof4wGQ8q4z5c4MEA5zvNgBzkXv" alt="Image" width="709" height="679" loading="lazy">
<em>Start editing the file remotely</em></p>
<p>Type out the code shown in the below picture. Here, we have defined a route to the home page of the website which should display ‘This is my flask website and it is so cool.’ Note that setting the host to 0.0.0.0 allows this website to be accessible by all devices connected to the same network.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/oyV0xCTNN59M32nPsaqLs5oCB8IwBNfgqiWC" alt="Image" width="800" height="457" loading="lazy">
<em>Create a basic website</em></p>
<p>Save the file and use the following command to run the website on the Pi server:</p>
<blockquote>
<p><em>python3 app.py</em></p>
</blockquote>
<p><img src="https://cdn-media-1.freecodecamp.org/images/V1gtV9AL1LrQnyZaQhq-5zk7eCuvaNMrmsWw" alt="Image" width="706" height="232" loading="lazy">
<em>Run the website</em></p>
<p>On receiving the above success message, open a new browser window on any device within your network and type out the Pi’s IP address (in this case, it is 192.168.0.12) followed by the port the dev-server is running on (5000.) So the complete address will be <a target="_blank" href="http://192.168.0.12:5000/">http://192.168.0.12:5000/</a></p>
<p>You should see the text ‘This is my flask website and it is so cool.’ on the webpage.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/3OunjfD19vW0jkUprzpCRgxftK3PU1cWf2mT" alt="Image" width="800" height="246" loading="lazy">
<em>Check the webpage in a browser</em></p>
<p>This confirms that your dev-server is active and is running the website you just created.</p>
<h3 id="heading-step-7-add-more-routes">Step 7. Add more routes</h3>
<p>Currently, the code consists of only 1 route which is the home page of the website. Add another route by typing out the following code. Note that you can dynamically make changes while the dev-server is running. It will automatically capture the delta (code change) and run a revised version once you refresh your browser window.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/wFupha2dFoFLYbsxCjKwMHjnTXmD5YKLwuMu" alt="Image" width="784" height="479" loading="lazy">
<em>Add a meow route</em></p>
<p>To check whether or not the new route works as expected, go to <a target="_blank" href="http://192.168.0.12:5000/meow">http://192.168.0.12:5000/meow</a> and the webpage should ‘MEOW’ at you.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/I-YacWJFs3S2wcDN2pVniaEwwkZj8B0XCZUN" alt="Image" width="800" height="269" loading="lazy">
<em>Verify that the new route works as expected</em></p>
<h3 id="heading-step-8-add-structure-to-your-code">Step 8. Add structure to your code</h3>
<p>Now, adding more routes is cool but having the all the code in just 1 app.py file is not how a website should be structured. Usually, we would have a folder with HTML templates, a folder with static CSS files and another one for JS files. Let’s add these folders and move the code in appropriate folders to structure out code better. Use the following commands to create these directories:</p>
<blockquote>
<p><em>mkdir templates</em></p>
<p><em>mkdir static</em></p>
</blockquote>
<p>Use the ‘ls’ command to verify that these folders have been created.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/MfI2Gvi6kC0WFxxxRkS9Jn4HYdu8KWF56teL" alt="Image" width="800" height="201" loading="lazy">
<em>Add structure to your code</em></p>
<p>Now, let’s create an HTML file for the homepage. Use the following commands to navigate to the templates directory. Then, create a new file named index.html and use rmate to edit the same:</p>
<blockquote>
<p><em>cd templates</em></p>
<p><em>touch index.html</em></p>
<p><em>rmate index.html</em></p>
</blockquote>
<p><img src="https://cdn-media-1.freecodecamp.org/images/C7xLB4tDdW3twueajM9Elzh5LxD169l6DAuw" alt="Image" width="800" height="155" loading="lazy"></p>
<p>Write some basic HTML code for the homepage inside index.html.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/nLyBtnwKxFGqhx9d52DxFMj6mBE2u9gq-qXP" alt="Image" width="800" height="227" loading="lazy">
<em>HTML code for the homepage</em></p>
<p>Make the following changes in app.py to use the index.html file. The below code will search for a file named index.html in the templates directory by default.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/lfuYCPtema6YCe4t1y7pCj8yxOZXPYGtzx8I" alt="Image" width="800" height="423" loading="lazy">
<em>Use the new index.html file and render it using app.py</em></p>
<p>Navigate back to the project directory and run the website again.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/xnv7ptoeZLKzqOmUNfLBhZPgubKooaWj-D3-" alt="Image" width="800" height="209" loading="lazy"></p>
<p>Go back to the home page and you should see the content you put inside of index.html.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/Lh1CRnj7mtlxmzyDPFjALEBRYAC0CvzcwyDn" alt="Image" width="773" height="264" loading="lazy"></p>
<p>Now add some styling by creating ‘main.css’ inside the static directory. As always, use the ‘cd’ command to change the directory, ‘touch’ command to create a new file, and ‘rmate’ command to edit the same file.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/SNa8U2sOoIrMN6tuAsBmtbHFUX43Vfgfc05J" alt="Image" width="680" height="128" loading="lazy">
<em>Create the css file</em></p>
<p>Add some styling to the h4 tag. Note that we currently have 1 h4 tag in index.html which the css is supposed to modify.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/MUCDIINplzZwjSTrm-1fBkev15z0q7Juku3n" alt="Image" width="656" height="148" loading="lazy">
<em>Some css code</em></p>
<p>As always, test your changes by using the following command:</p>
<blockquote>
<p><em>python3 app.py</em></p>
</blockquote>
<p><img src="https://cdn-media-1.freecodecamp.org/images/zY2hMd8vwNjCYVsVs-DyfY7rkBOur3IC66DS" alt="Image" width="800" height="209" loading="lazy"></p>
<p>Notice how the text within the h4 tag gets colored according to the CSS.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/VElytx48rZcyMNhmx3xUt6pf4PVjM3RZrmct" alt="Image" width="712" height="254" loading="lazy"></p>
<h3 id="heading-step-9-take-advantage-of-jinja">Step 9. Take advantage of Jinja</h3>
<p><a target="_blank" href="http://jinja.pocoo.org/">Jinja</a> in a Python based template engine which adds a lot of powerful features to webpages. Although this tutorial is not focused on learning Jinja, let’s just look at a simple example of how Jinja can be useful.</p>
<p>Let’s just create a list of fruits in app.py and pass it as a parameter to index.html. We will then have index.html display that list on the webpage. Make the following changes in app.py and index.html.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/Klh2Cgj-O8v8H4fYR8Jv00o-QiemELP-iH9t" alt="Image" width="800" height="388" loading="lazy">
_Pass the my<em>list as a parameter to index.html</em></p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/k3KZzf3MzBJCxvljXa0RoQKYzRPfc5kGl8JR" alt="Image" width="800" height="338" loading="lazy">
_Display my<em>list on the webpage</em></p>
<p>Refresh your webpage and you should see the list of fruits on screen.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/o8hYHuUKS47d9bESOKFOhiUs1FR8JTKuL3MY" alt="Image" width="731" height="395" loading="lazy"></p>
<p>This speaks to how powerful and useful Jinja can be. For more info on Jinja, please refer to <a target="_blank" href="http://jinja.pocoo.org/">this</a>.</p>
<h3 id="heading-step-10-next-steps">Step 10. Next steps</h3>
<p>Now that you have a fully functional Python dev-server, the possibilities going forward are practically infinite. Here are a few useful next steps that you may consider for your project:</p>
<ol>
<li>Currently, the Pi is accessible only through the devices within your personal network. To expose the Pi to the outside world (access it through any device outside your personal network), you need something known as port forwarding. Basically, you need a domain name and a static IP address which is permanently assigned to the Pi. More info <a target="_blank" href="https://www.raspberrypi.org/documentation/remote-access/access-over-Internet/README.md">here</a> and <a target="_blank" href="https://maker.pro/raspberry-pi/projects/raspberry-pi-web-server">here</a>.</li>
<li>Most applications will require a database for basic CRUD operations. Python supports SQlite right out of the box. Learn how to use SQlite with Flask <a target="_blank" href="http://flask.pocoo.org/docs/1.0/patterns/sqlite3/">here</a> and <a target="_blank" href="https://www.tutorialspoint.com/flask/flask_sqlite.htm">here</a>.</li>
<li><a target="_blank" href="https://www.amazon.com/CanaKit-Raspberry-Starter-Premium-Black/dp/B07BCC8PK7/ref=sr_1_1_sspa?ie=UTF8&amp;qid=1536006349&amp;sr=8-1-spons&amp;keywords=raspberry+pi+canakit&amp;psc=1">Here’s a cool Raspberry Pi starter kit on Amazon</a>. The neat thing about this is that it has everything you need to get started and saves you the effort of searching individual items yourself.</li>
<li>Since you are not using a screen, it is important that you use the shutdown command for the Pi using the terminal. This ensures that the Pi and the SD card are not damaged:</li>
</ol>
<blockquote>
<p><em>sudo shutdown -h now</em></p>
</blockquote>
<p>#UntilNextTime.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
