<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: BorisKlco</title>
    <description>The latest articles on DEV Community by BorisKlco (@borisklco).</description>
    <link>https://dev.to/borisklco</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1004152%2F2285ea9f-a945-46eb-8ef3-586c9defab37.jpg</url>
      <title>DEV Community: BorisKlco</title>
      <link>https://dev.to/borisklco</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/borisklco"/>
    <language>en</language>
    <item>
      <title>Watering System 💦 with web interface on Raspberry Pi</title>
      <dc:creator>BorisKlco</dc:creator>
      <pubDate>Tue, 24 Oct 2023 09:56:14 +0000</pubDate>
      <link>https://dev.to/borisklco/watering-system-with-web-interface-on-raspberry-pi-1d0l</link>
      <guid>https://dev.to/borisklco/watering-system-with-web-interface-on-raspberry-pi-1d0l</guid>
      <description>&lt;p&gt;Alright, alright I know what you thinking... Oh jeepers 😣 yet another raspberry watering tutorial with 4 lines of while loop... But hA! I will do it differently! I will make it "harder" on purpose! 😅 &lt;/p&gt;

&lt;p&gt;What about 🤔 somehow nice box, cam module for creating time lapse video AND web interface?&lt;/p&gt;

&lt;p&gt;If you rlly want, something like this can be amazing project for learning basic electronics. Running linux on own HW, even learn little bit of soldering!&lt;/p&gt;

&lt;p&gt;In this article I will describe my thought process, links and articles that helped me understand what is and how to use GPIO pins...&lt;/p&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Schematic and thought process&lt;/li&gt;
&lt;li&gt;Code review&lt;/li&gt;
&lt;li&gt;Images, web site, github&lt;/li&gt;
&lt;li&gt;Component breakdown&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Schematic and thought process
&lt;/h2&gt;

&lt;p&gt;My idea was do something "fancy" but not too much, main reason was that I dont have any experiences with custom design like 3D printed casing.&lt;/p&gt;

&lt;p&gt;MacGyver time! I chose an old box from my GPU as a case, and used the internal anti-shock foam to hold the components in place.&lt;/p&gt;

&lt;p&gt;Final product just unwraped itself! Only thing I did before setting it into case was inform myself about GPIO pins and what and how I need to connect tham. &lt;br&gt;
For this I chose to use online graphic tool &lt;a href="https://excalidraw.com/#json=RlH4OjntAQs4o_2UkupOm,U1BlJojmNKWQhYva5iNXTw"&gt;Excalidraw&lt;/a&gt;... &lt;br&gt;
And after few rectangles, lines and dream later &lt;a href="https://excalidraw.com/#json=RlH4OjntAQs4o_2UkupOm,U1BlJojmNKWQhYva5iNXTw"&gt;I have basic schematic for my watering system...&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LANTr3kk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l6nj5mh16cdr8tao9dxz.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LANTr3kk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l6nj5mh16cdr8tao9dxz.jpg" alt="Watering System Schematic " width="800" height="434"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Code review
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Path of least resistance&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;best choice for Raspberry Pi is Python... Quick, simple, and many things like GPIO, Cam driver is build for us. Bonus? do web interface in Flask. Do you need some fetching? Do htmx! You dont even need htmx... Flask will do everything for you... Building this with something like next.js is just... overkill.. But you do you 😅&lt;/p&gt;

&lt;p&gt;Main reason why I split watering code and flask code is that I wanna run frontend on my VPS.&lt;br&gt;
After watering, Pi will send data about it, as time, photo taken to my VPS.&lt;/p&gt;

&lt;p&gt;VPS will resize photo, create new time lapse video, add record into db and serve all of it in frontend...&lt;/p&gt;
&lt;h2&gt;
  
  
  Let's write controller class for somehow manageable GPIO control..
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/BorisKlco/pi.watering-system/blob/main/controller.py"&gt;controller.py&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First we define how we wanna let our code know what GPIO pin we want to control. &lt;br&gt;
We have 2 options, &lt;strong&gt;GPIO.BCM&lt;/strong&gt;, &lt;strong&gt;GPIO.BOARD&lt;/strong&gt;, difference is, BCM refers to literal name of pin, and BOARD is physical location on board.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ALL ABOUT PINS, and info for every pin can be found on &lt;a href="https://pinout.xyz/"&gt;https://pinout.xyz/&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# GPIO/BCM Number
GPIO.setmode(GPIO.BCM)

# Physical/Board pin
# GPIO.setmode(GPIO.BOARD)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;ul&gt;
&lt;li&gt;GPIO(General-purpose input/output) have 2 modes.. Input and Output. &lt;/li&gt;
&lt;li&gt;after setting up how we wanna refer to our pin. We need pick what pin we wanna use and what mod will be used.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;For more info how relay works, pleas read end of article Component breakdown&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Relay class:&lt;/strong&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We will not read data from relay, we want control relay by sending &lt;strong&gt;low&lt;/strong&gt;/&lt;strong&gt;high&lt;/strong&gt; state of pin. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;init&lt;/strong&gt; will setup pin to &lt;strong&gt;GPIO.OUT&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;we will control On/Off state of relay by sending &lt;strong&gt;GPIO.output()&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;to power on relay we need send &lt;strong&gt;GPIO.LOW/0/False&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;to power on relay we need send &lt;strong&gt;GPIO.HIGH/1/True&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Why &lt;strong&gt;Low&lt;/strong&gt; to On and &lt;strong&gt;High&lt;/strong&gt; to Off? Again, pleas refer to Component breakdown&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Relay:
    def __init__(self, pin):
        self.pin = pin

    def init(self, name):
        GPIO.setup(self.pin, GPIO.OUT)
        print(f"{name} - GPIO.OUT - pin {self.pin} - init...")

    def on(self):
        GPIO.output(self.pin, GPIO.LOW)

    def off(self):
        GPIO.output(self.pin, GPIO.HIGH)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;strong&gt;Sensor class:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;After powering on Relay what control voltage to Sensor, we want to read data from it. &lt;/li&gt;
&lt;li&gt;Now we setup &lt;strong&gt;init&lt;/strong&gt; class as &lt;strong&gt;GPIO.IN&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;We will read data from it just by calling &lt;strong&gt;GPIO.input(pin)&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Sensor:
    def __init__(self, pin):
        self.pin = pin

    def init(self, name):
        GPIO.setup(self.pin, GPIO.IN)
        print(f"{name} - GPIO.IN - pin {self.pin} - init...")

    def output(self):
        return GPIO.input(self.pin)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;strong&gt;Setting up &lt;a href="https://github.com/BorisKlco/pi.watering-system/blob/main/app.py"&gt;app.py&lt;/a&gt; to run code&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We import controller&lt;/li&gt;
&lt;li&gt;We define 2 relays and reading for sensor data
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from controller import Relay, Sensor

sensor_data = Sensor(22)
pump = Relay(23)
sensor = Relay(24)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Now create few functions to make while loop clean&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;init()&lt;/strong&gt; to set up modes for devices&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;sensor_output()&lt;/strong&gt; , Turn on relay to power up sensor --&amp;gt; Make reading --&amp;gt; Turn off relay --&amp;gt; return reading(True or False)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;water(sec=5)&lt;/strong&gt;, Turn on relay to power up pump --&amp;gt; sleep for x sec while pump is going --&amp;gt; Turn off relay
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def init():
    sensor_data.init("Sensor output data")
    pump.init("Pump Relay")
    sensor.init("Sensor Relay")


def sensor_output():
    sensor.on()
    time.sleep(2)
    reading = sensor_data.output()
    time.sleep(1)
    sensor.off()
    return reading


def water(sec=5):
    pump.on()
    time.sleep(sec)
    pump.off()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;strong&gt;While loop&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;call &lt;strong&gt;init()&lt;/strong&gt; so we can control relay and sensor&lt;/li&gt;
&lt;li&gt;create infinity &lt;strong&gt;while True:&lt;/strong&gt; loop, we dont need to run code every sec,min or hour... Set up &lt;strong&gt;SLEEP_INTERVAL&lt;/strong&gt; for something like 24h,48h.&lt;/li&gt;
&lt;li&gt;to play it safe we can add &lt;strong&gt;GPIO.cleanup()&lt;/strong&gt; to &lt;strong&gt;finally&lt;/strong&gt; block to clear pins.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;try:
    init()

    while True:
        time.sleep(SLEEP_INTERVAL)
        dry_soil = sensor_output()
        if dry_soil :
            print("WATERING!!!")
            water()

finally:
    print("\n-- END -- \ncleaning GPIO channels...")
    GPIO.cleanup()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Images, web site, github
&lt;/h2&gt;

&lt;p&gt;&lt;u&gt;Web interface have temporary images while I gather more data to put there...&lt;/u&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Website:&lt;/strong&gt; &lt;a href="https://pi.tricksofthe.trade/"&gt;https://pi.tricksofthe.trade/&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Github:&lt;/strong&gt; &lt;a href="https://github.com/BorisKlco/pi.watering-system"&gt;https://github.com/BorisKlco/pi.watering-system&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5U3-AIj1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bpr5ljtyqusf8itt4uja.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5U3-AIj1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bpr5ljtyqusf8itt4uja.jpg" alt="Image of case" width="800" height="600"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--j3xpvIL8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qj25ndq4f82zet0bqhil.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--j3xpvIL8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qj25ndq4f82zet0bqhil.jpg" alt="Image of case" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Component breakdown
&lt;/h2&gt;

&lt;p&gt;Here, I will list all components and aliexpress listing for it.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;a href="https://www.aliexpress.com/w/wholesale-relay-5v.html"&gt;5v Relay 2 channel&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Best video about relay what you can watch is from &lt;a href="https://www.youtube.com/watch?v=FWvEEtrTGRQ"&gt;Brad Henson&lt;/a&gt;! Depends on you, what type of relay you will buy, but in my case I have &lt;strong&gt;Relay One&lt;/strong&gt; in his &lt;a href="https://youtu.be/FWvEEtrTGRQ?si=EWFybAj0NbNwZquG&amp;amp;t=176"&gt;video&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;In very short explanation, we will use voltage from Pi, &lt;strong&gt;5v&lt;/strong&gt; -&amp;gt; &lt;strong&gt;VCC&lt;/strong&gt; to power relay and activating it. This is why we have jumper connecting &lt;strong&gt;VCC&lt;/strong&gt; and &lt;strong&gt;JD-VCC&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NTzB_u4m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/me28n382pyaovywt05lb.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NTzB_u4m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/me28n382pyaovywt05lb.JPG" alt="Relay voltage, activation schematic - Brad Henson" width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is reason why we have define &lt;strong&gt;ON&lt;/strong&gt; state in our code as &lt;strong&gt;Low State&lt;/strong&gt; or &lt;strong&gt;GPIO.LOW&lt;/strong&gt;, when we set &lt;strong&gt;INC pin&lt;/strong&gt; to &lt;strong&gt;Low&lt;/strong&gt;, we have 0 volts potential on that pin. This will create &lt;strong&gt;ground&lt;/strong&gt; option for our &lt;strong&gt;VCC&lt;/strong&gt; and turn on LED and activate relay.&lt;/p&gt;

&lt;p&gt;&lt;u&gt;&lt;strong&gt;WARNING!!!&lt;/strong&gt;&lt;/u&gt; - DON'T SWAP VCC and INC PINS ON PI TO USE HIGH STATE AS ON STATE... CAN DMG YOUR PI IF RELAY WILL NOT USE EXTERNAL POWER SOURCE...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    def on(self):
        GPIO.output(self.pin, GPIO.LOW)

    def off(self):
        GPIO.output(self.pin, GPIO.HIGH)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  &lt;a href="https://www.aliexpress.com/w/wholesale-Soil-Humidity-Development-Board.html"&gt;Humidity Sensor&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;As with everything else you can buy several types of humidity sensor. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=udmJyncDvw0"&gt;Andreas Spiess 10 min video explaining different type of sensors and how they works &lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Quick overview:&lt;/em&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;Two-legged sensor with controller PCB:&lt;/strong&gt;&lt;/em&gt; Output is digital and analog but will be quicky destroyed after uninterrupted connection of power source...&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gFaGkRb5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/byojvpafr82dkx1t2ltc.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gFaGkRb5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/byojvpafr82dkx1t2ltc.jpg" alt="Two-legged sensor with controller PCB - Andreas Spiess" width="800" height="411"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;Capacitive, unexposed copper sensor:&lt;/strong&gt;&lt;/em&gt; Legs can't be dissolved in water, no exposed cooper. &lt;strong&gt;555 timer&lt;/strong&gt; creates a square wave for one leg, another leg of sensor is &lt;strong&gt;Ground&lt;/strong&gt;, after sensor is submerged into water, two legs will create &lt;strong&gt;capacitator&lt;/strong&gt;... fuucking genius!&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZJ6iXxpR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xi2x7em4liychr5idq9k.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZJ6iXxpR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xi2x7em4liychr5idq9k.jpg" alt="Capacitive, unexposed copper sensor - Andreas Spiess" width="800" height="411"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Best possible choice what you can do is buy &lt;strong&gt;Capacitive Soil Sensor&lt;/strong&gt;, but be careful! Quality of sensor in this case matters.&lt;/p&gt;

&lt;p&gt;For this part I can advice you to watch video from &lt;strong&gt;&lt;em&gt;Flaura - Smart Plant Pot&lt;/em&gt;&lt;/strong&gt; , amazing overview of Capacitive Sensor and what can go wrong. &lt;a href="https://www.youtube.com/watch?v=IGP38bz-K48"&gt;Link to video&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Quick overview:&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;Check if 555 timer and 662k voltage regulator is present.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rhr6VMRH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gnr9ivujdob19vqocpm3.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rhr6VMRH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gnr9ivujdob19vqocpm3.jpg" alt="Capacitive Soil Moisture Sensors - Flaura - Smart Plant Pot" width="800" height="402"&gt;&lt;/a&gt;&lt;br&gt;
Check if hole is between resistors and not under.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Sxf6Xm9M--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n81ydxsfhayayxxdpm75.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Sxf6Xm9M--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n81ydxsfhayayxxdpm75.jpg" alt="Capacitive Soil Moisture Sensors - Flaura - Smart Plant Pot" width="800" height="402"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;a href="https://www.aliexpress.com/w/wholesale-DC-3V-5V-Mini-Submersible-Pump-Water-.html"&gt;Pump&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Any 3v,5v pump 💪 will do... &lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;a href="https://www.aliexpress.com/w/wholesale-rasperry-pi-cam.html"&gt;Cam&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Buy Pi supported cam, you can even experiment with new python lib PiCamera2.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;a href="https://www.aliexpress.com/w/wholesale-Silicone-Tube.html?g=y&amp;amp;SearchText=Silicone+Tube"&gt;Tubes&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Check Pump output width, from there you can pick "perfect" tubes for that...&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;a href="https://www.aliexpress.com/w/wholesale-1p-cable.html"&gt;Cables&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;1P-female to female will do...&lt;/p&gt;




&lt;p&gt;Thank you for reading ♥ My next project will be watering system but on STM32 😮&lt;/p&gt;

</description>
      <category>python</category>
      <category>raspberrypi</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Media Downloader in Next.js, tailwind, Flask&amp;sqlAlchemy</title>
      <dc:creator>BorisKlco</dc:creator>
      <pubDate>Tue, 25 Jul 2023 07:52:36 +0000</pubDate>
      <link>https://dev.to/borisklco/media-downloader-in-nextjs-tailwind-flasksqlalchemy-3pk0</link>
      <guid>https://dev.to/borisklco/media-downloader-in-nextjs-tailwind-flasksqlalchemy-3pk0</guid>
      <description>&lt;p&gt;It sounds easy, right? 🤔 Just take the link from the user, do some stuff on the backend, and serve them the file. 🎉 In theory, everything sounds easy. Even 6-hour video tutorials on how to build a Twitter/Instagram or Spotify clone sound easy...🙄&lt;/p&gt;

&lt;p&gt;But then you close the tutorial you were watching and try to do it yourself, only to discover that you have trouble even setting up the configurations... ☝🤓&lt;/p&gt;

&lt;p&gt;What I'm trying to say is that &lt;u&gt;beginnings are hard&lt;/u&gt;, man.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Link:&lt;/strong&gt; &lt;a href="https://scrappy.tricksofthe.trade/"&gt;https://scrappy.tricksofthe.trade/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Github:&lt;/strong&gt; &lt;a href="https://github.com/BorisKlco/nextjs.videoDownloader"&gt;BorisKlco/nextjs.videoDownloader&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm sure a more experienced person could rewrite this &lt;strong&gt;much&lt;/strong&gt; better, but this is my first project, and I'm happy with what I learned. I learned about &lt;strong&gt;Next.js&lt;/strong&gt;'s new App Router options, how to run a &lt;strong&gt;Flask API&lt;/strong&gt;, and how to store data with &lt;strong&gt;SQLAlchemy&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;First, I created a draft template in &lt;strong&gt;Figma&lt;/strong&gt; to visualize how I wanted the website to look. My goal was to create a simple design, as I believe that simplicity is king. Many websites that are designed for downloading media are bloated with unnecessary content, which is often done for the sake of SEO. However, I believe that a simple design is more user-friendly..&lt;/p&gt;

&lt;h2&gt;
  
  
  Backend and Flask API
&lt;/h2&gt;

&lt;p&gt;I will use &lt;strong&gt;yt-dlp&lt;/strong&gt;, a &lt;strong&gt;youtube-dl&lt;/strong&gt; fork with &lt;strong&gt;Python&lt;/strong&gt; support. To obtain information about the posted link and provide users with options to download media from it.&lt;/p&gt;

&lt;p&gt;After user submits the form data to backend endpoint, I will use the &lt;strong&gt;yt-dlp&lt;/strong&gt; class &lt;code&gt;.extract_info&lt;/code&gt; to get a &lt;code&gt;JSON object&lt;/code&gt; with all the information about the video, including the title, date, thumbnail, and formats.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@app.route("/extract_info", methods=["POST"])
def post_info():
    data = request.json
    with YoutubeDL() as ydl:
        try:
           info = ydl.extract_info(data, download=False)

               #Storing visit to db
               save_view = History(
                    title=info["title"],
                    image=info["thumbnail"],
                    link=info["original_url"],
                )
                db.session.add(save_view)
                db.session.commit()

           return jsonify(ydl.sanitize_info(info))
        except:
            return {"error": "Wrong URL"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another &lt;strong&gt;Flask&lt;/strong&gt; endpoint will be &lt;code&gt;/get_me_link&lt;/code&gt;, which will download selected media and provide user with a link to download it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@app.route("/get_me_link", methods=["POST"])
def file_download():
    data = request.json
    file = download.get_file(data["url"], data["type"])
    return {"url": "https://url/serve_file/" + file}


@app.route("/serve_file/&amp;lt;file&amp;gt;")
def serve_file(file):
    path = os.getcwd()
    download_file = path + "/files/" + file
    return send_file(download_file, as_attachment=True)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Last endpoit is for serving data from db&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@app.route("/history", methods=["GET"])
def history():
    db_query = History.query.order_by(History.id.desc()).all()
    result = []
    for row in db_query:
        result.append(
            {"id": row.id, 
             "title": row.title, 
             "image": row.image, 
             "link": row.link})
    return jsonify(result)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Next.js layout:
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7EcgLyW1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mnlqg2kar9quctd8e451.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7EcgLyW1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mnlqg2kar9quctd8e451.png" alt="Next.js layout visualization" width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;&amp;lt;Search /&amp;gt;&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;After submitting the link, perform a basic link check to ensure that it is supported.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const supportedSites = [
    "youtu","tiktok","instagram","twitch","twitter","reddit",
  ];
function checkSupported(userInput: String) {
    return supportedSites.some((item) =&amp;gt; userInput.includes(item));
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If site is supported fetch data with &lt;strong&gt;react-query&lt;/strong&gt; and render &lt;code&gt;&amp;lt;Result /&amp;gt;&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        {result.isFetching ? (
          &amp;lt;div className="flex justify-center items-center"&amp;gt;
            &amp;lt;h1 className="truncate my-4 text-xl sm:text-3xl"&amp;gt;
              LoAd1nG.. SeRvEr go Brrr..
            &amp;lt;/h1&amp;gt;
            &amp;lt;Image
              src="/images/search/brr.svg"
              height={48}
              width={48}
              className="object-fit sm:pl-2"
              alt="Loading"
            /&amp;gt;
          &amp;lt;/div&amp;gt;
        ) : result.isFetched ? (
          &amp;lt;div&amp;gt;
            &amp;lt;Result data={resultData} /&amp;gt;
          &amp;lt;/div&amp;gt;
        ) : null}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;code&gt;&amp;lt;Result /&amp;gt;&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Check if data.error property exists. If it does not, return a nice representation of this data in divs, Link and Image elements.&lt;/p&gt;

&lt;p&gt;At this point, we can also perform additional checks, such as if  video is too long or if video is live streamed.&lt;/p&gt;

&lt;p&gt;If everything is &lt;em&gt;Gucci&lt;/em&gt; show user &lt;code&gt;&amp;lt;Download /&amp;gt;&lt;/code&gt; component.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;Download url={data.original_url} type="audio" format="MP3" /&amp;gt;
&amp;lt;Download url={data.original_url} type="video" format="MP4" /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;code&gt;&amp;lt;Download /&amp;gt;&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The download component is using &lt;code&gt;react-query&lt;/code&gt; to fetch data after user clicks on it. The component will have three additional states: &lt;code&gt;.isFetching&lt;/code&gt;, &lt;code&gt;.isFetched&lt;/code&gt;, and &lt;code&gt;none&lt;/code&gt;. The &lt;code&gt;.isFetching&lt;/code&gt; state will be used to show a spinner wheel while the data is being fetched. The &lt;code&gt;.isFetched&lt;/code&gt; state will be used to show a link to download the file. The &lt;code&gt;none&lt;/code&gt; state will be used to show the download option.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{link.isFetching ? 
      (&amp;lt;button&amp;gt;
          &amp;lt;AiOutlineLoading className="animate-spin" /&amp;gt;
        &amp;lt;/button&amp;gt;) 
: link.isFetched ? 
     (&amp;lt;a href={linkData.url}&amp;gt; Download &amp;lt;Image/&amp;gt; &amp;lt;/a&amp;gt;) 
: (
        &amp;lt;button
          onClick={() =&amp;gt; {link.refetch();
            toast.success("Preparing it for uWu.. 🐱")}&amp;gt;
            {format}
        &amp;lt;/button&amp;gt;
   )}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Next.js App Router &lt;code&gt;error.tsx&lt;/code&gt; and &lt;code&gt;loading.tsx&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;in &lt;strong&gt;app dir&lt;/strong&gt; I created error handler &lt;code&gt;error.tsx&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;must be &lt;code&gt;'use client'&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;you have optional props &lt;code&gt;{error, reset}&lt;/code&gt; ( TS: &lt;code&gt;{ error: Error; reset: () =&amp;gt; void }&lt;/code&gt; )
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const Error = ({ error, reset }: { error: Error; reset: () =&amp;gt; void }) =&amp;gt; {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h2&amp;gt;Something went wrong!&amp;lt;/h2&amp;gt;
      &amp;lt;div&amp;gt;
        &amp;lt;Link href={"/"}&amp;gt;
          Go back home
        &amp;lt;/Link&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the &lt;em&gt;/history&lt;/em&gt; route, I use &lt;code&gt;loader.tsx&lt;/code&gt; to create a skeleton if fetching data takes longer than expected. This skeleton will create a list of made-up items that look like the data after it has been fetched.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export default async function Loading() {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;div className="mt-6 md:mt-8 flex flex-wrap"&amp;gt;
        {Array.from({ length: 12 }, (movie, i) =&amp;gt; (
          &amp;lt;div className="max-w-[16rem]animate-pulse" key={i}&amp;gt;
            &amp;lt;p className="truncate mb-2 invisible"&amp;gt;Loading&amp;lt;/p&amp;gt;
            &amp;lt;div className="animate-pulse"&amp;gt;&amp;lt;/div&amp;gt;
          &amp;lt;/div&amp;gt;
        ))}
      &amp;lt;/div&amp;gt;
    &amp;lt;/&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Thank you for reading ♥
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Link:&lt;/strong&gt; &lt;a href="https://scrappy.tricksofthe.trade/"&gt;https://scrappy.tricksofthe.trade/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Github:&lt;/strong&gt; &lt;a href="https://github.com/BorisKlco/nextjs.videoDownloader"&gt;BorisKlco/nextjs.videoDownloader&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>nextjs</category>
      <category>python</category>
      <category>tailwindcss</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
