ESP32 prikuplja podatke za učenje

Modul 4 · Lekcija 2 od 5 · Veštačka inteligencija

Model uči iz podataka – a najbolji podaci su oni iz tvoje učionice. U ovoj lekciji ESP32 meri temperaturu, vlažnost i svetlo, ti mu kažeš kakvo je stanje („prijatno“, „zagušljivo“…), a Python sve to čuva u tabelu. To je postupak označavanja podataka iz lekcije Podaci – hrana za AI, samo sa pravim senzorima.

Povezivanje

DeoPinESP32
DHT11VCC / GND / DATA3V3 / GND / GPIO4
LDR modulVCC / GND / AO3V3 / GND / GPIO34

Isto kao u lekcijama DHT11 i merenje osvetljenja. Ako si napravio pametnu učionicu, DHT11 je već na pravom mestu.

Program za ESP32: merenje i oznaka

// ESP32 – prikupljanje podataka za veštačku inteligenciju
// DHT11 (DATA) -> GPIO4, LDR modul (AO) -> GPIO34
// Svake 3 sekunde šalje red: temperatura,vlaznost,svetlo,stanje
// Stanje (oznaku) zadaješ slovom u Serial Monitoru: h = hladno, p = prijatno,
// t = toplo, z = zagušljivo, x = pauza (bez upisa)
#include <DHT.h>

const int DHT_PIN = 4;
const int LDR_PIN = 34;
DHT dht(DHT_PIN, DHT11);

String stanje = "";              // trenutna oznaka; prazno = ne beležimo
unsigned long poslednje = 0;

void setup() {
  Serial.begin(115200);
  dht.begin();
  Serial.println("temperatura,vlaznost,svetlo,stanje");   // zaglavlje CSV tabele
}

void loop() {
  // 1) da li je korisnik poslao novu oznaku?
  if (Serial.available()) {
    char c = Serial.read();
    if (c == 'h') stanje = "hladno";
    else if (c == 'p') stanje = "prijatno";
    else if (c == 't') stanje = "toplo";
    else if (c == 'z') stanje = "zagusljivo";
    else if (c == 'x') stanje = "";
  }

  // 2) merenje na 3 s, bez delay() – da bi Serial stalno mogao da prima slova
  if (millis() - poslednje >= 3000) {
    poslednje = millis();
    float t = dht.readTemperature();
    float h = dht.readHumidity();
    int svetlo = analogRead(LDR_PIN);
    if (isnan(t) || isnan(h) || stanje == "") {
      return;                    // neuspelo merenje ili pauza – ne upisujemo
    }
    Serial.print(t, 1);
    Serial.print(",");
    Serial.print(h, 0);
    Serial.print(",");
    Serial.print(svetlo);
    Serial.print(",");
    Serial.println(stanje);
  }
}
  • ESP32 svake 3 sekunde šalje jedan red CSV tabele, npr. 24.1,71,1930,zagusljivo.
  • Oznaku zadaješ ti: u Serial Monitoru upiši slovo p (prijatno), h (hladno), t (toplo) ili z (zagušljivo) i pritisni Enter. Slovo x pauzira beleženje.
  • Merenje ide preko millis(), pa ESP32 stalno „sluša“ Serial i odmah prihvata novu oznaku.

Kako da skupiš dobre podatke

  • Raznovrsno: beleži ujutru i posle podne, sa otvorenim i zatvorenim prozorom, pre i posle odmora, kad je u učionici mnogo i malo ljudi.
  • Pošteno označavanje: oznaku menjaj tek kada se stanje zaista promeni; neka o oznaci odluči nekoliko učenika, a ne jedan.
  • Dovoljno primera svake vrste: bar 15–20 redova za svako stanje (vidi zadatak 1).
  • Ako neko stanje ne možeš da dobiješ prirodno, pažljivo ga „napravi“: dah ili dlan na senzoru (toplo), čaša tople vode pored senzora (vlažno).

Python čuva podatke u fajl

Redove iz Serial Monitora možeš da iskopiraš u fajl ručno. Lakše je da to radi Python, pomoću biblioteke pyserial (pip install pyserial). Pre pokretanja zatvori Serial Monitor – port može da koristi samo jedan program.

# Čuvanje redova koje šalje ESP32 u fajl merenja.csv (Python + biblioteka pyserial)
# Instalacija: pip install pyserial   ·   Pre pokretanja zatvori Serial Monitor u Arduino IDE-u!
import serial

PORT = "COM3"          # na Windows-u npr. COM3 (vidi Arduino IDE → Tools → Port); na Linux-u /dev/ttyUSB0
BRZINA = 115200
BROJ_REDOVA = 100      # koliko merenja da sačuvamo

veza = serial.Serial(PORT, BRZINA, timeout=10)
with open("merenja.csv", "w", encoding="utf-8") as f:
    sacuvano = 0
    zaglavlje_upisano = False
    while sacuvano < BROJ_REDOVA:
        red = veza.readline().decode("utf-8", errors="ignore").strip()
        if not red:
            continue                       # ništa nije stiglo (isteklo vreme čekanja)
        print(red)
        if red.startswith("temperatura"):  # zaglavlje upisujemo samo jednom
            if not zaglavlje_upisano:
                f.write(red + "\n")
                zaglavlje_upisano = True
            continue
        f.write(red + "\n")
        sacuvano += 1
veza.close()
print("Sačuvano", sacuvano, "merenja u merenja.csv")

Oznake i dalje šalješ iz Serial Monitora? Ne možeš – port je zauzet. Zato je jednostavnije da merenja radiš u dva dela: prvo sve sa oznakom „prijatno“ (pošalješ p, zatvoriš Serial Monitor, pokreneš Python), pa zatim sledeće stanje. ESP32 pamti poslednju oznaku sve dok ne isključiš napajanje – a pri otvaranju porta se resetuje, pa oznaku pošalji ponovo. U zadatku 2 je bolje rešenje: taster na ploči.

Primer podataka i prvi pogled

Za sledeću lekciju pripremili smo primer tabele ucionica.csv sa 84 merenja (temperatura, vlažnost, stanje). Vrednosti su realne za učionicu, ali su pripremljene za vežbu; tvoja merenja će izgledati drugačije – i to je dobro, jer će tvoj model naučiti baš tvoju učionicu.

Prikaži tabelu ucionica.csv (iskopiraj je u fajl)
temperatura,vlaznost,stanje
20.5,44,hladno
17.0,69,hladno
17.3,72,hladno
16.8,67,hladno
19.0,40,hladno
22.1,39,prijatno
19.4,70,hladno
21.9,71,zagusljivo
17.7,49,hladno
24.8,72,zagusljivo
29.3,71,toplo
24.2,38,prijatno
29.7,37,toplo
23.8,43,prijatno
20.1,44,prijatno
24.0,70,zagusljivo
27.4,46,toplo
17.4,71,hladno
24.9,58,prijatno
17.4,80,hladno
16.9,38,hladno
24.7,66,zagusljivo
25.5,62,prijatno
22.5,64,prijatno
21.1,50,prijatno
27.1,79,toplo
26.9,40,toplo
24.0,68,zagusljivo
22.9,56,prijatno
26.2,53,toplo
17.0,67,hladno
21.9,83,zagusljivo
20.8,66,zagusljivo
21.9,77,zagusljivo
17.1,70,hladno
24.0,55,prijatno
20.8,57,prijatno
24.3,72,zagusljivo
27.2,39,toplo
27.8,52,toplo
22.6,77,zagusljivo
16.9,81,hladno
25.8,76,zagusljivo
25.5,63,prijatno
28.4,57,toplo
16.3,64,hladno
21.0,74,zagusljivo
17.6,38,hladno
19.1,53,hladno
17.8,50,hladno
21.6,66,zagusljivo
17.1,63,hladno
21.6,52,prijatno
28.4,62,toplo
28.1,52,toplo
25.9,57,prijatno
21.3,49,prijatno
18.1,46,hladno
18.1,77,hladno
19.3,66,hladno
27.6,46,toplo
19.7,35,prijatno
24.5,55,prijatno
29.3,79,toplo
28.0,74,toplo
25.2,82,zagusljivo
16.8,84,hladno
29.3,78,toplo
27.2,60,toplo
21.6,60,prijatno
17.4,75,hladno
21.6,47,prijatno
16.9,48,hladno
22.2,42,prijatno
20.8,38,prijatno
17.4,71,hladno
18.1,41,hladno
29.3,74,toplo
16.4,48,hladno
24.6,44,prijatno
24.9,57,prijatno
24.4,65,zagusljivo
17.7,66,hladno
29.9,64,toplo
# Pregled prikupljenih podataka: koliko ima primera i kako izgledaju na grafiku
import csv
import matplotlib.pyplot as plt

podaci = []
with open("ucionica.csv", encoding="utf-8") as f:
    for red in csv.DictReader(f):
        podaci.append((float(red["temperatura"]), float(red["vlaznost"]), red["stanje"]))

boje = {"hladno": "royalblue", "prijatno": "green", "toplo": "red", "zagusljivo": "purple"}
for stanje, boja in boje.items():
    t = [p[0] for p in podaci if p[2] == stanje]
    h = [p[1] for p in podaci if p[2] == stanje]
    print(f"{stanje:11} primera: {len(t):3}   prosečno {sum(t) / len(t):.1f} °C, {sum(h) / len(h):.0f} %")
    plt.scatter(t, h, c=boja, label=stanje, edgecolors="black")

plt.xlabel("temperatura (°C)")
plt.ylabel("vlažnost vazduha (%)")
plt.title("Stanje u učionici")
plt.legend()
plt.grid(alpha=0.3)
plt.show()

Ispis programa:

hladno      primera:  28   prosečno 17.7 °C, 61 %
prijatno    primera:  23   prosečno 22.9 °C, 51 %
toplo       primera:  18   prosečno 28.2 °C, 59 %
zagusljivo  primera:  15   prosečno 23.3 °C, 72 %
Grafik: merenja u učionici obojena po stanju – hladno, prijatno, toplo, zagušljivo
Stanja se jasno odvajaju po temperaturi; „zagušljivo“ se razlikuje od „prijatnog“ po vlažnosti
Za čas (nastavnicima): trajanje 2 × 45 minuta (ili tokom nedelju dana); pribor: ESP32, DHT11, LDR, računar.
Odredite „dežurne za podatke“: na svakom odmoru pokrenu beleženje i zajedno odluče o oznaci. Posle nedelju dana odeljenje ima svoju tabelu za sledeću lekciju.

Česte zablude

  • „Što više redova, to bolje – bez obzira kakvi su“ – 1000 redova snimljenih u istih 10 minuta manje vredi od 100 redova iz različitih dana i situacija.
  • „Senzor uvek meri tačno“ – DHT11 ima grešku oko ±2 °C i ponekad ne uspe da izmeri; zato program proverava isnan(), a podaci se posle čiste.

Zadaci za vežbu

Pokušaj prvo sam, pa tek onda otvori rešenje.

  1. Napiši program koji proverava da li su podaci uravnoteženi: ispiši broj primera po stanju i upozori ako neko stanje ima manje od 10 primera.
  2. Umesto slova iz Serial Monitora, oznaku menjaj tasterom na GPIO15 (svaki pritisak: sledeće stanje). Šta treba promeniti u programu za ESP32?
  3. Napiši program koji iz podataka izbacuje nemoguća merenja (temperatura van −10…50 °C, vlažnost van 0…100 %, prazna polja) i čuva ostala u novi fajl.
Rešenje 1: uravnoteženost
# Zadatak: da li su podaci uravnoteženi? Upozori ako neka klasa ima manje od 10 primera
import csv

broj = {}
with open("ucionica.csv", encoding="utf-8") as f:
    for red in csv.DictReader(f):
        broj[red["stanje"]] = broj.get(red["stanje"], 0) + 1

for stanje, n in sorted(broj.items(), key=lambda p: -p[1]):
    upozorenje = "  <- premalo primera, snimi još!" if n < 10 else ""
    print(f"{stanje:11} {n:3}{upozorenje}")

Ispis programa:

hladno       28
prijatno     23
toplo        18
zagusljivo   15
Rešenje 2: taster umesto Serial Monitora

Dodaj const int TASTER = 15;, u setup() pinMode(TASTER, INPUT_PULLUP);, i niz const char* OZNAKE[] = {"hladno", "prijatno", "toplo", "zagusljivo"}; sa promenljivom int k = 1;. U loop() umesto čitanja Seriala proveravaj prelaz tastera HIGH → LOW (kao u lekciji LED dioda, taster i PWM) i radi k = (k + 1) % 4; stanje = OZNAKE[k];. Tako Python može da drži port otvoren, a ti menjaš oznaku tasterom. Trenutnu oznaku možeš da pokažeš i bojom svetlećeg prstena.

Rešenje 3: čišćenje podataka
# Zadatak: čišćenje podataka – izbaci nemoguća merenja i sačuvaj ostala
import csv

ulaz = [  # deo merenja sa greškama (npr. labav kontakt na senzoru)
    {"temperatura": "22.5", "vlaznost": "48", "stanje": "prijatno"},
    {"temperatura": "-127.0", "vlaznost": "48", "stanje": "prijatno"},   # greška senzora
    {"temperatura": "27.1", "vlaznost": "155", "stanje": "toplo"},       # vlažnost preko 100 %
    {"temperatura": "", "vlaznost": "60", "stanje": "hladno"},           # nedostaje vrednost
    {"temperatura": "18.2", "vlaznost": "62", "stanje": "hladno"},
]


def ispravno(red):
    try:
        t = float(red["temperatura"])
        h = float(red["vlaznost"])
    except ValueError:              # prazno polje ili tekst umesto broja
        return False
    return -10 <= t <= 50 and 0 <= h <= 100


ocisceno = [red for red in ulaz if ispravno(red)]
print("Ostalo", len(ocisceno), "od", len(ulaz), "merenja")
with open("merenja_ociscena.csv", "w", encoding="utf-8", newline="") as f:
    pisac = csv.DictWriter(f, fieldnames=["temperatura", "vlaznost", "stanje"])
    pisac.writeheader()
    pisac.writerows(ocisceno)

Ispis programa:

Ostalo 2 od 5 merenja

Proveri znanje

Otvori kviz

Kviz: prikupljanje podataka

1. Ko u ovoj lekciji određuje oznaku (stanje)?




2. Zašto ESP32 meri preko millis(), a ne delay(3000)?




3. Zašto treba zatvoriti Serial Monitor pre pokretanja Python programa?




4. Koji su podaci bolji za učenje?