Smart IoT-Based Irrigation System

বাংলাদেশ একটি কৃষি প্রধান দেশ। হাজার বছর ধরে এই দেশের মানুষ কৃষির উপর নির্ভর করেই জীবনযাপন করে আসছে। তবুও বাস্তবতা হলো—এতো দীর্ঘ ইতিহাস থাকা সত্ত্বেও আমাদের Agriculture Technology এখনো খুব বেশি আধুনিক হয়ে উঠতে পারেনি। এখনো অনেক জায়গায় সনাতন পদ্ধতিতেই চাষাবাদ ও সেচ ব্যবস্থাপনা পরিচালিত হচ্ছে, যার ফলে সময়, শ্রম এবং পানির অপচয় হয়।

বর্তমান সময় হলো Internet of Things (IoT) এবং Smart Technology–এর যুগ। এই যুগে দাঁড়িয়ে যদি আমরা কৃষিক্ষেত্রে আধুনিক প্রযুক্তিকে সঠিকভাবে ব্যবহার না করি, তাহলে আন্তর্জাতিক পর্যায়ে উৎপাদন ও দক্ষতার দিক থেকে আমরা ধীরে ধীরে পিছিয়ে পড়বো। শুধু উৎপাদন কমে যাওয়াই নয়, ভবিষ্যতে কৃষিতে স্বয়ংসম্পূর্ণ থাকা আরও কঠিন হয়ে উঠবে।

smart_iot_based_irrigation_projects_intro

আধুনিক IoT-ভিত্তিক Agriculture System আমাদের এই সমস্যাগুলোর একটি কার্যকর সমাধান দিতে পারে। সেন্সরের মাধ্যমে মাটির অবস্থা জানা, স্বয়ংক্রিয়ভাবে সেচ চালু–বন্ধ করা, এবং মোবাইল বা ক্লাউড থেকে পুরো সিস্টেম মনিটর করা—এই সবকিছুই এখন সহজেই সম্ভব। এর মাধ্যমে কৃষকরা কম খরচে, কম পরিশ্রমে এবং আরও পরিকল্পিতভাবে চাষাবাদ করতে পারেন।

এই টিউটোরিয়ালে আমরা এমনই একটি Smart IoT-Based Irrigation System তৈরি করবো, যা আধুনিক প্রযুক্তিকে কৃষির সাথে যুক্ত করার একটি বাস্তব উদাহরণ। ধাপে ধাপে এই প্রোজেক্টটি তৈরি করতে গিয়ে আপনি শিখবেন কীভাবে প্রযুক্তি ব্যবহার করে কৃষিকে আরও দক্ষ, টেকসই এবং লাভজনক করা যায়। শুধু তাই নয়  উদ্দেশ্য একটাই—দেশের কৃষিকে ভবিষ্যতের জন্য প্রস্তুত করা।

প্রয়োজনীয় কম্পোনেন্ট

যেকোনো বড় প্রোজেক্টে কাজ শুরু করার আগে আমাদের প্রথম কাজ হওয়া উচিত প্রয়োজনীয় সব কম্পোনেন্ট এক জায়গায় গুছিয়ে রাখা। কারণ কাজের মাঝপথে যদি হঠাৎ কোনো গুরুত্বপূর্ণ জিনিস খুঁজে না পাওয়া যায়, তাহলে মনোযোগ নষ্ট হয় এবং কাজের গতি কমে যায়। তাই শুরুতেই সব উপকরণ হাতের কাছে সাজিয়ে রাখলে প্রোজেক্টটি অনেক সহজ, দ্রুত এবং সুন্দরভাবে সম্পন্ন করা সম্ভব হয়।

smart_iot_based_irrigation_component_list

এই কারণেই, কাজ শুরু করার সুবিধার জন্য নিচে একটি সুন্দরভাবে সাজানো কম্পোনেন্ট লিস্ট দেওয়া হয়েছে, যেখানে প্রতিটি প্রোডাক্টের নাম, প্রয়োজনীয় পরিমাণ এবং Purchase Link সংযুক্ত করা আছে। চাইলে এখান থেকেই সহজেই সব উপকরণ সংগ্রহ করে নিয়ে আপনি নিশ্চিন্তে প্রোজেক্টের কাজে এগিয়ে যেতে পারবেন।

কম্পোনেন্টের নাম পরিমাণ
Arduino Uno R4 WifiArduino Uno R4 Wifi Original Version
1
Soil Moisture Sensor Module
1
Micro Pump Motor DC 6V
1
I2C LCD Display 16×2
1
Relay Module 1 Channel 5V
1
AA Battery 1.5V / Doublepow NiMH Rechargeable Battery AA 1200mAh
4
Battery Holder 4 x AA with Cover
1
Male to Female Jumper Wire
10

সার্কিট ডায়াগ্রাম 

এই পর্যায়ে নিচে দেওয়া Circuit Diagram অনুসরণ করে Arduino Uno R4 Wifi এবং অন্যান্য সব কম্পোনেন্টের মধ্যে সংযোগগুলো ঠিকভাবে করে নিন। 

smart_iot_based_irrigation_projects_circuit_diagram

প্রিরিকুইজিট

আরডুইনো IDE ইন্সটলঃ 

Arduino Uno R4 WiFi দিয়ে কাজ শুরু করার আগে প্রথমেই আমাদের কম্পিউটারে Arduino IDE সফটওয়্যারটি ইন্সটল করে নিতে হবে। এজন্য নির্দিষ্ট লিংকে ক্লিক করে Arduino–র অফিসিয়াল ওয়েবসাইট থেকে সর্বশেষ ভার্সনটি ডাউনলোড করে ইন্সটল করে নিন। যদি আগে থেকেই Arduino IDE ইন্সটল করা থাকে, তাহলে এই ধাপটি স্কিপ করা যেতে পারে।

installing-arduino-ide

Arduino Uno R4 Wifi বোর্ড ইন্সটলঃ 

এরপর যেহেতু Arduino Uno R4 WiFi বোর্ডটি ডিফল্টভাবে Arduino IDE–তে থাকে না, তাই আলাদাভাবে এটি ইন্সটল করতে হবে। এজন্য Arduino IDE ওপেন করে উপরের মেনু থেকে Tools → Board → Board Manager অপশনে ক্লিক করুন। 

এতে পাশে একটি সাইড প্যানেল ওপেন হবে।

এই প্যানেলের সার্চবারে Arduino Uno R4 লিখে সার্চ করলেই বোর্ডটির নাম লিস্টে দেখা যাবে।

এখন Install বাটনে ক্লিক করে বোর্ড প্যাকেজটি ইন্সটল করে নিন। 

ইন্সটলেশন সম্পন্ন হলে Arduino IDE সম্পূর্ণভাবে Arduino Uno R4 WiFi প্রোগ্রাম করার জন্য প্রস্তুত হয়ে যাবে।

কোডিং 

এই পর্যায়ে নিচে দেওয়া কোডটি কপি করে Arduino IDE–এর কোড এডিটরে পেস্ট করে নিন। এরপর কোডের ভেতরে থাকা SSID এবং Password অংশে নিজের WiFi–এর সঠিক তথ্য বসিয়ে নিন।

#include <Wire.h>

#include <LiquidCrystal_I2C.h>

#include <WiFiS3.h>

// ---------------- LCD ----------------

LiquidCrystal_I2C lcd(0x27, 16, 2);

// ---------------- Pins ----------------

const int SOIL_PIN  = A0;

const int RELAY_PIN = 9;

// ---------------- Calibration (14-bit) ----------------

const int DRY14 = 14000;   // dry / air

const int WET14 = 7000;    // wet soil

// ---------------- Relay logic ----------------

const bool RELAY_ACTIVE_LOW = false;   // false => active HIGH, true => active LOW

const int ON_THRESHOLD  = 35;          // too dry -> relay ON

const int OFF_THRESHOLD = 45;          // wet enough -> relay OFF

// ---------------- WiFi credentials ----------------

const char* WIFI_SSID = "YOUR_WIFI_SSID";

const char* WIFI_PASS = "YOUR_WIFI_PASSWORD";

// ---------------- Web server ----------------

WiFiServer server(80);

// ---------------- State ----------------

volatile bool relayState = false;  // false=OFF true=ON

volatile bool autoMode   = true;   // true=auto hysteresis, false=manual

int lastRaw = 0;

int lastPct = 0;

// ---------------- Helpers ----------------

void writeRelay(bool on) {

  relayState = on;

  if (RELAY_ACTIVE_LOW) {

    digitalWrite(RELAY_PIN, on ? LOW : HIGH);

  } else {

    digitalWrite(RELAY_PIN, on ? HIGH : LOW);

  }

}

int moisturePercent14(int raw) {

  raw = constrain(raw, WET14, DRY14);

  int pct = map(raw, DRY14, WET14, 0, 100);

  return constrain(pct, 0, 100);

}

const char* soilStatusText(int pct) {

  if (pct < 35) return "Dry";

  if (pct < 70) return "Normal";

  return "Wet";

}

// ---------- HTTP helpers ----------

void sendHttp(WiFiClient& client, const char* contentType, const String& body) {

  client.println("HTTP/1.1 200 OK");

  client.print("Content-Type: ");

  client.println(contentType);

  client.println("Connection: close");

  client.println("Cache-Control: no-store");

  client.print("Content-Length: ");

  client.println(body.length());

  client.println();

  client.print(body);

}

void send404(WiFiClient& client) {

  String body = "Not found";

  client.println("HTTP/1.1 404 Not Found");

  client.println("Content-Type: text/plain");

  client.println("Connection: close");

  client.println("Cache-Control: no-store");

  client.print("Content-Length: ");

  client.println(body.length());

  client.println();

  client.print(body);

}

String urlDecode(const String& s) {

  String out;

  out.reserve(s.length());

  for (size_t i = 0; i < s.length(); i++) {

    char c = s[i];

    if (c == '+') out += ' ';

    else if (c == '%' && i + 2 < s.length()) {

      char h1 = s[i + 1], h2 = s[i + 2];

      auto hex = [](char x)->int{

        if (x >= '0' && x <= '9') return x - '0';

        if (x >= 'A' && x <= 'F') return x - 'A' + 10;

        if (x >= 'a' && x <= 'f') return x - 'a' + 10;

        return 0;

      };

      out += char((hex(h1) << 4) | hex(h2));

      i += 2;

    } else out += c;

  }

  return out;

}

String getQueryParam(const String& url, const String& key) {

  int q = url.indexOf('?');

  if (q < 0) return "";

  String qs = url.substring(q + 1);

  int start = 0;

  while (start < (int)qs.length()) {

    int amp = qs.indexOf('&', start);

    if (amp < 0) amp = qs.length();

    String pair = qs.substring(start, amp);

    int eq = pair.indexOf('=');

    if (eq > 0) {

      String k = urlDecode(pair.substring(0, eq));

      String v = urlDecode(pair.substring(eq + 1));

      if (k == key) return v;

    }

    start = amp + 1;

  }

  return "";

}

// ---------- Web pages ----------

String htmlPage() {

  String h;

  h.reserve(9000);

  h += F(

"<!doctype html><html><head>"

"<meta charset='utf-8'>"

"<meta name='viewport' content='width=device-width,initial-scale=1'>"

"<title>IoT Dashboard</title>"

"<style>"

"*{box-sizing:border-box;font-family:system-ui,Segoe UI,Roboto,Arial;margin:0;padding:0}"

":root{--bg0:#070a12;--bg1:#0a1020;--t:rgba(255,255,255,.92);--m:rgba(255,255,255,.62);--card:rgba(255,255,255,.04);--stroke:rgba(255,255,255,.10)}"

"body{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:18px;"

"background:radial-gradient(900px 600px at 30% 20%, rgba(123,92,255,.18), transparent 60%),"

"radial-gradient(800px 520px at 70% 35%, rgba(46,214,255,.13), transparent 55%),"

"linear-gradient(180deg,var(--bg0),var(--bg1));color:var(--t)}"

".wrap{width:min(980px,100%);border:1px solid rgba(255,255,255,.08);border-radius:18px;"

"background:rgba(255,255,255,.03);box-shadow:0 24px 70px rgba(0,0,0,.55);overflow:hidden}"

".top{display:flex;justify-content:space-between;align-items:center;padding:16px 18px;border-bottom:1px solid rgba(255,255,255,.08);background:rgba(0,0,0,.14)}"

"h1{font-size:18px;font-weight:950;letter-spacing:.2px}"

"#ip{color:rgba(255,255,255,.65);font-weight:800;font-size:13px}"

".main{padding:18px;display:grid;gap:14px}"

".card{border-radius:18px;padding:16px;border:1px solid rgba(255,255,255,.08);background:var(--card);"

"box-shadow:0 18px 40px rgba(0,0,0,.35), inset 0 1px 0 rgba(255,255,255,.06)}"

".ct{color:var(--m);font-size:13px;margin-bottom:10px;font-weight:800;letter-spacing:.2px}"

".grid2{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}"

"@media(max-width:860px){.grid2{grid-template-columns:1fr}}"

".gaugeWrap{display:grid;grid-template-columns:1.1fr .9fr;gap:16px;align-items:center}"

"@media(max-width:860px){.gaugeWrap{grid-template-columns:1fr}}"

".gauge{display:grid;place-items:center;min-height:320px;position:relative}"

".center{position:absolute;width:215px;height:215px;border-radius:999px;display:grid;place-items:center;"

"background:radial-gradient(circle at 30% 30%, rgba(255,255,255,.10), rgba(255,255,255,.02) 60%), rgba(0,0,0,.18);"

"border:1px solid rgba(255,255,255,.10);box-shadow:inset 0 10px 30px rgba(0,0,0,.45), 0 20px 60px rgba(0,0,0,.35)}"

".val{font-size:64px;font-weight:950;letter-spacing:-1px;line-height:1}"

".val span{font-size:28px;margin-left:4px;opacity:.9}"

".lab{margin-top:10px;color:rgba(255,255,255,.70);font-weight:900;letter-spacing:.2px}"

"svg.gsvg{transform:rotate(-90deg);filter:drop-shadow(0 14px 36px rgba(0,0,0,.55))}"

".mini{display:grid;gap:10px}"

".chip{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:12px 12px;border-radius:14px;border:1px solid rgba(255,255,255,.08);background:rgba(0,0,0,.18)}"

".chip b{font-weight:950}"

".chip span{color:rgba(255,255,255,.72);font-weight:850}"

".hint{margin-top:10px;color:rgba(255,255,255,.50);font-size:12px;font-weight:750;line-height:1.35}"

".big{font-size:44px;font-weight:950;color:rgba(46,214,255,.90);letter-spacing:-.6px}"

/* BADGES BIG */

".badge{display:inline-flex;gap:10px;align-items:center;padding:14px 18px;border-radius:999px;"

"border:1px solid rgba(255,255,255,.10);background:rgba(0,0,0,.18);font-weight:950;"

"font-size:18px;letter-spacing:.4px}"

".ok{color:rgba(34,197,94,.92);border-color:rgba(34,197,94,.25);background:rgba(34,197,94,.10)}"

".bad{color:rgba(239,68,68,.92);border-color:rgba(239,68,68,.25);background:rgba(239,68,68,.10)}"

".pri{color:rgba(46,214,255,.92);border-color:rgba(46,214,255,.25);background:rgba(46,214,255,.10)}"

".btns{display:flex;gap:10px;flex-wrap:wrap;margin-top:12px}"

"button{cursor:pointer;border:none;border-radius:14px;padding:12px 14px;font-weight:950;background:rgba(255,255,255,.06);color:var(--t);border:1px solid rgba(255,255,255,.10)}"

"button:hover{background:rgba(255,255,255,.08)}"

"button:disabled{opacity:.35;cursor:not-allowed}"

".btnOn{background:rgba(34,197,94,.10);border-color:rgba(34,197,94,.22)}"

".btnOff{background:rgba(239,68,68,.10);border-color:rgba(239,68,68,.22)}"

".seg{display:flex;gap:8px;padding:6px;border-radius:16px;border:1px solid rgba(255,255,255,.10);background:rgba(0,0,0,.18);width:100%}"

".seg button{flex:1;padding:12px 10px;border-radius:12px;border:1px solid transparent;background:transparent;font-weight:950}"

".seg button.active{background:rgba(46,214,255,.14);border-color:rgba(46,214,255,.28)}"

".seg button:not(.active){opacity:.75}"

"</style></head><body>"

"<div class='wrap'>"

"<div class='top'><h1>IoT Dashboard</h1><div id='ip'></div></div>"

"<div class='main'>"

"<div class='card'>"

"<div class='ct'>Moisture Gauge</div>"

"<div class='gaugeWrap'>"

"<div class='gauge'>"

"<div class='center'>"

"<div style='display:grid;place-items:center'>"

"<div class='val'><span id='pct'>0</span><span>%</span></div>"

"<div class='lab'>Soil Moisture</div>"

"</div></div>"

"<svg class='gsvg' width='290' height='290' viewBox='0 0 200 200'>"

"<defs>"

"<linearGradient id='gc' x1='0' y1='0' x2='1' y2='1'>"

"<stop offset='0%' stop-color='#ff4fd8'/>"

"<stop offset='55%' stop-color='#7b5cff'/>"

"<stop offset='100%' stop-color='#2ed6ff'/>"

"</linearGradient>"

"<filter id='glow' x='-50%' y='-50%' width='200%' height='200%'>"

"<feGaussianBlur stdDeviation='3.5' result='b'/>"

"<feMerge><feMergeNode in='b'/><feMergeNode in='SourceGraphic'/></feMerge>"

"</filter>"

"</defs>"

"<circle cx='100' cy='100' r='70' fill='none' stroke='rgba(255,255,255,.10)' stroke-width='18'/>"

"<circle id='ring' cx='100' cy='100' r='70' fill='none' stroke='url(#gc)' stroke-width='18' stroke-linecap='round' filter='url(#glow)'/>"

"</svg>"

"</div>"

"<div class='mini'>"

"<div class='chip'><span>Raw Sensor</span><b id='rawMini'>0</b></div>"

"<div class='chip'><span>Relay</span><b id='relayMini'>OFF</b></div>"

"<div class='chip'><span>Mode</span><b id='modeMini'>AUTO</b></div>"

"<div class='hint'>Auto thresholds: ON <= 35% | OFF >= 45%</div>"

"</div>"

"</div>"

"</div>"

"<div class='grid2'>"

"<div class='card'>"

"<div class='ct'>Sensor Value</div>"

"<div class='big' id='raw'>0</div>"

"<div class='hint'>14-bit ADC raw</div>"

"</div>"

"<div class='card'>"

"<div class='ct'>Soil Status</div>"

"<div style='margin-top:6px'><span class='badge ok' id='soilBadge'>NORMAL</span></div>"

"<div class='hint' id='soilHint'>Moisture is in good range</div>"

"</div>"

"<div class='card'>"

"<div class='ct'>Relay Control</div>"

"<div style='margin-top:6px'><span class='badge ok' id='relayBadge'><span id='relay'>OFF</span></span></div>"

"<div class='btns'>"

"<button class='btnOn' id='btnOn'>Relay ON</button>"

"<button class='btnOff' id='btnOff'>Relay OFF</button>"

"</div>"

"<div class='hint'>Manual buttons disabled in AUTO</div>"

"</div>"

"<div class='card'>"

"<div class='ct'>Mode Control</div>"

"<div style='margin-top:6px'><span class='badge pri' id='modeBadge'><span id='modeText'>AUTO</span></span></div>"

"<div class='seg' style='margin-top:12px'>"

"<button id='btnMan'>MANUAL</button>"

"<button id='btnAuto'>AUTO</button>"

"</div>"

"<div class='hint'>Auto controls relay with hysteresis</div>"

"</div>"

"</div>"  // grid2

"</div>"  // main

"</div>"  // wrap

"<script>"

"const $=id=>document.getElementById(id);"

"function setBadge(el,cls){el.classList.remove('ok','bad','pri');el.classList.add(cls);}"

"const R=70, C=2*Math.PI*R;"

"const ring=$('ring');"

"ring.style.strokeDasharray=C;"

"function setRing(p){const pct=Math.max(0,Math.min(100,p)); ring.style.strokeDashoffset=C*(1-pct/100);}"

"function soilUI(name){"

" if(name==='Dry'){ $('soilBadge').textContent='DRY'; setBadge($('soilBadge'),'bad'); $('soilHint').textContent='Soil is dry — watering needed'; return; }"

" if(name==='Wet'){ $('soilBadge').textContent='WET'; setBadge($('soilBadge'),'pri'); $('soilHint').textContent='Soil is wet — watering not needed'; return; }"

" $('soilBadge').textContent='NORMAL'; setBadge($('soilBadge'),'ok'); $('soilHint').textContent='Moisture is in good range';"

"}"

"function setModeUI(isAuto){"

" $('modeText').textContent = isAuto ? 'AUTO' : 'MANUAL';"

" $('modeMini').textContent = isAuto ? 'AUTO' : 'MANUAL';"

" setBadge($('modeBadge'), isAuto ? 'pri' : 'bad');"

" $('btnAuto').classList.toggle('active', !!isAuto);"

" $('btnMan').classList.toggle('active', !isAuto);"

" $('btnOn').disabled = !!isAuto;"

" $('btnOff').disabled = !!isAuto;"

"}"

"function setRelayUI(isOn){"

" $('relay').textContent = isOn ? 'ON' : 'OFF';"

" $('relayMini').textContent = isOn ? 'ON' : 'OFF';"

" setBadge($('relayBadge'), isOn ? 'ok' : 'bad');"

"}"

"async function poll(){"

" try{"

"  const r=await fetch('/api?t='+Date.now());"

"  const j=await r.json();"

"  $('ip').textContent=j.ip;"

"  $('pct').textContent=j.moisture;"

"  $('raw').textContent=j.raw;"

"  $('rawMini').textContent=j.raw;"

"  setRing(j.moisture);"

"  soilUI(j.soil);"

"  setRelayUI(!!j.relay);"

"  setModeUI(!!j.auto);"

" }catch(e){ $('ip').textContent='API FAIL'; }"

"}"

"async function setRelay(on){ await fetch('/relay?state='+(on?'on':'off')+'&t='+Date.now()); poll(); }"

"async function setAuto(on){ await fetch('/mode?auto='+(on?1:0)+'&t='+Date.now()); poll(); }"

"document.addEventListener('DOMContentLoaded',()=>{"

" $('btnOn').addEventListener('click',()=>setRelay(1));"

" $('btnOff').addEventListener('click',()=>setRelay(0));"

" $('btnAuto').addEventListener('click',()=>setAuto(1));"

" $('btnMan').addEventListener('click',()=>setAuto(0));"

" poll(); setInterval(poll,500);"

"});"

"</script>"

"</body></html>"

  );

  return h;

}

// JSON

String jsonApi() {

  String ip = WiFi.localIP().toString();

  String body = "{";

  body += "\"raw\":" + String(lastRaw) + ",";

  body += "\"moisture\":" + String(lastPct) + ",";

  body += "\"soil\":\"" + String(soilStatusText(lastPct)) + "\",";

  body += "\"relay\":" + String(relayState ? "true" : "false") + ",";

  body += "\"auto\":" + String(autoMode ? "true" : "false") + ",";

  body += "\"ip\":\"" + ip + "\"";

  body += "}";

  return body;

}

// Robust request read: read first line until \r\n

bool readRequestLine(WiFiClient& client, String& outLine) {

  outLine = "";

  unsigned long t0 = millis();

  while (client.connected() && !client.available()) {

    if (millis() - t0 > 400) return false;

    delay(1);

  }

  if (!client.available()) return false;

  // Read line until '\n'

  outLine = client.readStringUntil('\n');

  outLine.trim();

  return outLine.length() > 0;

}

void discardHeaders(WiFiClient& client) {

  // Read until empty line

  unsigned long t0 = millis();

  while (client.connected()) {

    if (millis() - t0 > 400) break;

    if (!client.available()) { delay(1); continue; }

    String line = client.readStringUntil('\n');

    if (line == "\r" || line.length() <= 1) break;

  }

}

void handleClient() {

  WiFiClient client = server.available();

  if (!client) return;

  String reqLine;

  if (!readRequestLine(client, reqLine)) { client.stop(); return; }

  discardHeaders(client);

  // Example: GET /api HTTP/1.1

  int sp1 = reqLine.indexOf(' ');

  int sp2 = reqLine.indexOf(' ', sp1 + 1);

  if (sp1 < 0 || sp2 < 0) { send404(client); client.stop(); return; }

  String method = reqLine.substring(0, sp1);

  String url    = reqLine.substring(sp1 + 1, sp2);

  // DEBUG: see requests

  Serial.print("[HTTP] ");

  Serial.print(method);

  Serial.print(" ");

  Serial.println(url);

  if (method == "GET" && (url == "/" || url.startsWith("/?"))) {

    sendHttp(client, "text/html; charset=utf-8", htmlPage());

  }

  else if (method == "GET" && url.startsWith("/api")) {

    sendHttp(client, "application/json; charset=utf-8", jsonApi());

  }

  else if (method == "GET" && url.startsWith("/relay")) {

    String state = getQueryParam(url, "state");

    state.toLowerCase();

    if (!autoMode) {

      if (state == "on")  writeRelay(true);

      if (state == "off") writeRelay(false);

    }

    sendHttp(client, "application/json; charset=utf-8", jsonApi());

  }

  else if (method == "GET" && url.startsWith("/mode")) {

    String a = getQueryParam(url, "auto");

    if (a == "1") autoMode = true;

    if (a == "0") autoMode = false;

    sendHttp(client, "application/json; charset=utf-8", jsonApi());

  }

  else {

    send404(client);

  }

  delay(1);

  client.stop();

}

// LCD helpers

void lcdPrintLine(uint8_t row, const String &text) {

  lcd.setCursor(0, row);

  String t = text;

  if (t.length() > 16) t = t.substring(0, 16);

  while (t.length() < 16) t += ' ';

  lcd.print(t);

}

void setup() {

  Serial.begin(115200);

  delay(300);

  analogReadResolution(14);

  pinMode(RELAY_PIN, OUTPUT);

  writeRelay(false);

  lcd.init();

  lcd.backlight();

  lcdPrintLine(0, "Soil Monitor");

  lcdPrintLine(1, "Starting...");

  delay(800);

  lcdPrintLine(0, "Connecting WiFi");

  lcdPrintLine(1, "Please wait");

  int status = WL_IDLE_STATUS;

  while (status != WL_CONNECTED) {

    status = WiFi.begin(WIFI_SSID, WIFI_PASS);

    delay(1000);

  }

  IPAddress ip = WiFi.localIP();

  Serial.println();

  Serial.print("Connected IP: ");

  Serial.println(ip);

  lcdPrintLine(0, "WiFi Connected");

  lcdPrintLine(1, ip.toString());

  delay(1200);

  server.begin();

  Serial.println("HTTP server started");

  lcdPrintLine(0, "Server Ready");

  lcdPrintLine(1, ip.toString());

}

void loop() {

  handleClient();

  static unsigned long lastUpdate = 0;

  unsigned long now = millis();

  if (now - lastUpdate >= 500) {

    lastUpdate = now;

    int raw = analogRead(SOIL_PIN);

    int pct = moisturePercent14(raw);

    lastRaw = raw;

    lastPct = pct;

    if (autoMode) {

      if (!relayState && pct <= ON_THRESHOLD) writeRelay(true);

      else if (relayState && pct >= OFF_THRESHOLD) writeRelay(false);

    }

    String modeText = autoMode ? "AUTO" : "MAN";

    lcdPrintLine(0, "S.Moist:" + String(pct) + "%");

   lcdPrintLine(1, "R:" + String(relayState ? "ON" : "OFF") + " M:" + modeText);

  }

}

এখন Tools থেকে Board অপশনে গিয়ে Arduino Uno R4 WiFi বোর্ডটি সিলেক্ট করে নিন। এরপর Port অপশন থেকে আপনার কম্পিউটারে যেটি ডিটেক্ট হয়েছে, সেই সঠিক COM Port নির্বাচন করুন। 

এই দুটি সেটিং ঠিক থাকলে, আপলোড বাটন ক্লিক করে কোডটি আপলোড করুন। 

Output

কোড আপলোড হলে, সিরিয়াল মনিটরে IoT Dashboard-এ প্রবেশের জন্য একটি IP Address দেখতে পাবেন। 

এই ধাপে Soil Sensor–টি গাছের টব বা যেকোনো ফসলের গোড়ার মাটিতে সঠিকভাবে বসাতে হবে। ছবিতে দেখানো পদ্ধতি অনুসরণ করে সেন্সরটি এমনভাবে স্থাপন করুন, যাতে এটি সরাসরি মাটির আর্দ্রতা নির্ভুলভাবে পরিমাপ করতে পারে।

যেকোনো ব্রাউজারে Address Bar এ IP Address টি কপি করে, পেস্ট করুন এবং Enter প্রেস করুন। সাথে সাথে IoT Dashboard টি Open হয়ে যাবে। 

ড্যাশবোর্ডে আপনি প্রয়োজনীয় সকল গুরুত্বপূর্ণ তথ্য যেমন—Sensor Value, Soil Moisture লেভেল এবং Relay বা Motor Status সহজেই মনিটর করতে পারবেন। এর মাধ্যমে সিস্টেমটি কীভাবে কাজ করছে তা রিয়েল-টাইমে বোঝা সম্ভব হবে।

এর পাশাপাশি কিছু গুরুত্বপূর্ণ কন্ট্রোল অপশনও থাকবে, যার মাধ্যমে Motor–কে Manual এবং Auto Mode–এর মধ্যে পরিবর্তন করা যাবে। Auto Mode–এ মাইক্রোকন্ট্রোলার Soil Moisture ভ্যালুর উপর ভিত্তি করে স্বয়ংক্রিয়ভাবে Motor নিয়ন্ত্রণ করবে, আর Manual Mode–এ আপনি নিজেই প্রয়োজন অনুযায়ী Motor অন/অফ করতে পারবেন।

Mahbub Morshed Rifat
Mahbub Morshed Rifat

Engineer
TechShop Bangladesh

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.