Language Switcher Fallback

Robotex 2018 tuletõrjumise võistluse roboti juhend

Komponendid

Elektroonika komponente saab tellida Aliexpressist, hetkeseisuga Eestist kõiki neid osta ei saa. (Hinnad kõiguvad veidi ajas.)
Minul on olnud probleeme Yanwen Economic Air Mail postiteenusega, võimalusel vali muu.

  1. 2tk DC mootor koos rattaga
    (https://www.aliexpress.com/item/TT-Motor-130motor-with-the-wheel-Smart-C... 1.58€x2 )
  2. Arduino UNO
    (https://www.aliexpress.com/item/Original-DOIT-Free-Shipping-high-quality... 3.27€)
  3. Arduino proto shield mini maketeerimislauaga
    (https://www.aliexpress.com/item/Free-Shipping-UNO-Proto-Shield-prototype... 0.91€)
  4. 3 tk leegisensoreid (peaks kindlasti olema selline, millel on küljes 4 juhtmeühendust)
    (https://www.aliexpress.com/item/LM393-4-Pin-IR-Flame-Detection-Sensor-Mo... 0.30€x3)
  5. 2 tk infrapuna joone-andureid TCRT5000 sensoriga
    (https://www.aliexpress.com/item/2PCS-TCRT5000-Infrared-Reflective-IR-Pho... komplekt 2 tk 0.76€)
  6. 2 tk ultraheli kaugusandureid US-015
    (https://www.aliexpress.com/item/US-015-Ultrasonic-Senor-Module-Distance-... 1.16€x2)
    HC-SR04 - minu tellitud US-015 andurid ei töötanud korralikult, andsid eriti valesid näitusid, vahetasin HC-SR04 vastu välja ja esilagu olukord paranes. Aga tean, et vahel on ka HC-SR04 väga ebatöökindel. Igaks juhuks tasuks varuda mitu komplekti ultraheli kaugusandureid, soovitavalt eri sorti või silmnähtavalt erinevas kohas toodetud, et praagiks osutumisel saaks välja vahetada.
  7. H-bridge L298N moodul
    (https://www.aliexpress.com/item/L298N-Dual-H-Bridge-Stepper-Motor-Driver... 1.26€)
  8. Mosfeti moodul IRF520
    (https://www.aliexpress.com/item/Top-Mosfet-Button-IRF520-Mosfet-Driver-M... 0.54€)
  9. Ventilaator, 5V pinge jaoks
    (https://www.aliexpress.com/item/1-pcs-5015S-5V-0-1-0-3A-Black-Brushless-... 1.16€)
  10. Patareikarp 4-le AA patareile
    (https://www.aliexpress.com/item/1PCS-Plastic-Battery-Storage-Case-Box-Ho... 0.41€)

    Patareikarp 6-le AA patareile (ühes kihis paigutatuna) 1tk või patareikarbid kolmele AA patareile (ühes kihis) 2tk - mootoritele on parem panna 6 alkaline AA patareid, sest 4-ga jääb nõrgaks ja mootorid vahel ei taha liikuma hakata.
  11. 9V patarei klemm, mille juhtme teises otsas Arduinole sobilik toite-ots
    (https://www.aliexpress.com/item/9V-DC-Battery-Power-Cable-Plug-Clip-Barr... 0.22€)
  12. Lüliti moodul
    (https://www.aliexpress.com/store/product/12X12MM-Big-key-module-Big-butt... 0.58€ - vali Yanweni asemel teine postiteenus)
  13. Dupont juhtmete komplekt
    (https://www.aliexpress.com/item/40p-20cm-female-to-male-and-female-to-fe... 2.37€)
  14. Krokodillijuhtmete komplekt
    (https://www.aliexpress.com/store/product/10pcs-50cm-Double-ended-Alligat... 1.97€)
  15. Caster ehk tugi ratas
    (https://www.aliexpress.com/item/Metal-Caster-Universal-Wheel-Eye-Ball/32... 0.80€)
  16. Vajadusel USB 2.0 A-B juhe Arduino programmeerimiseks. Eespool näidatud lingilt Arduinot tellides sain ma kaasa nii lühikese juhtme, mis elu väga ebamugavaks teeb, hea oleks leida pikendus. Või - samasuguseid juhtmeid kasutavad tavaliselt printerid ja skännerid - laena sealt. Kui ei leia, siis telli.
    (https://www.aliexpress.com/store/product/Supply-black-USB2-0-printer-lin... 2.65€)

 

Vanema vanuserühma roboti jaoks, mis erineva kõrgusega küünlaid kustutab:

17. 2 väikest servomootorit (https://www.aliexpress.com/item/2Pcs-EMAX-ES08MA-II-Mini-Metal-Gear-Anal... 7.8€)

Kahjus ei jõua ma sel aastal valmis sellist lihtsustatud robotit, mis eri kõrgustega hakkama saaks. Samuti ei ole mul sellele variandile koodinäidet pakkuda. Ja hilisema tõdemusena leidsin, et tõenäoliselt on vaja ühte väikest ja ühte suuremat servomootorit, kui mõlemad väikesed, siis arvatavasti ei jäksa ventilaatorit tõsta.

 

 

Tööriistad ja materjalid

Videotes näitan, kuidas ehitada robot valmis kõige vähemate tööriistade ja kõige kättesaadavamate materjalidega, mis ma välja mõelda oskasin. Kui Sul on olemas rohkem vahendeid, siis kasuta neid julgelt oma äranägemise järgi.
Minimaalselt vajalik:

  1. Trell ja puuriotsikud (akutrelli asemel saab ka käsitsi opereeritavad puuri kasutada, sest läbida on vaja vaid paksu pappi)
  2. Kuuma liimi püstol ja liimipulgad
  3. Peene otsaga näpitsad
  4. Juhtme lõikamiseks sobilikud tangid
  5. Väiksemat sorti kruvikeeraja (moodulite kruviklemmidele sobib nii lapik kui ristpea)
  6. Terav nuga (või muu tööriist juhtme koorimiseks)
  7. Käärid
  8. Kahepoolne teip - paks, pehme sort
  9. Must isoleerpael
  10. MacGyveri teip ehk duct tape
  11. PVA liim
  12. Joonlaud või paberile printimise võimalus
  13. Pappi suuruses vähemalt 20x20cm, näiteks müsli karbid, aga mitte lainepapp vaid ühekihiline
  14. AA patareisid 6tk (Alkaline tüüpi, 6 tk korraga, aga tegelikult kulub rohkem, sõltuvalt sellest kui palju sa robotit testid, soovitan igal juhul vahetult enne võistlust uued patareid panna)
  15. 9V patarei (kindlasti osta Alkaline märgistusega, teised saavad liiga ruttu tühjaks! Samuti võistluse jaoks uus patarei varuks.)
  16. Välgumihkel - esialgseks testimiseks turvalisem kui küünlad, mis ümber võivad minna
  17. Teeküünal hilisemaks testimiseks

 
 
Kasuks tuleb:
Jootekolb, jootetina traat kampoliga - mootoritele juhtmete külge jootmine tuleks töökindlusele kasuks.
 
 

Teine video: robotiplatvormi ehitus

Lisasin roboti kere papist juppide skeemid - lihtsalt sisseskännitud joonised. Link failile. Prindi välja nii, et ei tehta "fit to page" vaid suurus säilib. Kontrolli pildil oleva joonlaua järgi, kas sai õige suurus.

H-sillale juhtmete ühendamise skeem allpool pildina.

Kolmas video: Arduino sissejuhatus

Arduino IDE allalaadimine

https://www.arduino.cc/en/Main/Software

Mootori driveri juhtmed

Joonis on allpool.

Mootorite testimise kood

Kustuta Arduino IDEs valge ala täiesti tühjaks ja kopeeri järgnev joonte vahel olev tekst asemele, vaata, et algus ja lõpp ei saaks midagi ülearust ega kaotaks ühtki märki.


void setup() { //Siit algab see osa koodi, mis jookseb alguses üks kord.
  //Ütleme oma ühendatud sisend/väljunditele, et nad hakaku väljunditeks.
  pinMode(3, OUTPUT);//parem edasi
  pinMode(5, OUTPUT);//parem tagasi
  pinMode(6, OUTPUT);//vasak edasi
  pinMode(9, OUTPUT);//vasak tagasi

  //Lülitame nad kõik välja
  analogWrite(3, 0);
  analogWrite(5, 0);
  analogWrite(6, 0);
  analogWrite(9, 0);

  delay(5000);//Enne liikuma hakkamist ootame 5 sekundit
  
}//Siin lõppeb see osa koodi, mis jookseb alguses üks kord.


void loop() {//Siin algab see osa koodi, mis jääb kordama.

  //sõidame otse
  analogWrite(3, 150);//sisend/väljund number 3 on parem edasi
  analogWrite(6, 150);//sisend/väljund number 6 vasak edasi
  // 150 eelmistel ridadel on number skaalalt 0-255, 
  // mis määrab, kas mootor lülitub täie võimsusega (255)
  // või aeglasemale kiirusele sisse. Alla 150 võib juhtuda, et ei jäksa liikuda.
  delay(1500);//Kui kaugele sõita jõuab, sõltub sellest ajavahemikust siin.

  //peatume hetkeks
  analogWrite(3, 0);//Number 0 lülitab mootori välja.
  analogWrite(6, 0);
  delay(500);

  //Pöörame vasakule
  analogWrite(3, 150);//sisend/väljund number 3 on parem edasi
  analogWrite(9, 150);//sisend/väljund number 9 on vasak tagasi
  delay(1000);//Kui palju pöörame, sõltub sellest ajavahemikust siin

  //Peatume hetkeks
  analogWrite(3, 0);
  analogWrite(9, 0);
  delay(500);

}//Siin lõppeb see osa koodi, mis jääb kordama.

Neljas video: Sensorid

Joonesensorite ühendused

Vasakpoolne sensor:

Sensori pin Arduino / protoshiledi ühendus
VCC 5V (protoshiledi 5-ne pesa)
GND GND (protoshiledi 5-ne pesa)
DO 7
AO (jääb ühendamata)

Parempoolne sensor:

Sensori pin Arduino / protoshiledi ühendus
VCC 5V (protoshiledi 5-ne pesa)
GND GND (protoshiledi 5-ne pesa)
DO 8
AO (jääb ühendamata)

Joonesensorite testimise kood

/**
 * Joone andurite testimise kood. Saadab lugemi seeriamonitori.
 */
 
 /*Defineerime muutujad millesse salvestame pinide numbrid, kuhu andurid ühendatud on.
 Need aitavad inimesel koodi lugedes sellest palju kergemini aru saada.*/
 int jooneandur_vasak = 7;
 int jooneandur_parem = 8;  
 
void setup() {
  /*Määrame jooneandurite pinid sisenditeks.*/
  pinMode(jooneandur_vasak, INPUT);
  pinMode(jooneandur_parem, INPUT);

  /*Paneme tööle arvutiga suhtluse seeriamonitori kaudu.*/
  Serial.begin(9600);
}

void loop() {
  /*Defineerime muutujad anduri hetkeväärtuse sisselugemiseks.*/
  int joon_vasak;
  int joon_parem;//Esialgu neisse pole väärtust salvestatud.
  
  /*Loeme andurite seisu ja salvestame oma muutujasse.*/
  joon_vasak = digitalRead(jooneandur_vasak);
  joon_parem = digitalRead(jooneandur_parem);
  /* digitalRead() on fuktsioon, mis loeb pini digitaalse väärtuse - annab
  vastuseks kas 0 või 1.*/

  /*Saadame salvestatud andurite lugemi seeriamonitori.*/
  Serial.print("vasak: ");//Jutumärkides on string ehk tekst
  Serial.print(joon_vasak);//ilma jutumärkideta on muutuja nimi, see asendub muutuja väärtusega
  Serial.print("  parem: ");//print kirjutab ilma reavahetuseta
  Serial.println(joon_parem);//println lisab lõppu reavahetuse

}

Kaugussensorite ühendused

Vasakpoolne (paremale vaatav) sensor

Sensori pin   Arduino / protoshiledi ühendus
VCC   5V (protoshiledi 5-ne pesa)
Trig ühenduvad omavahel samale breadboardi reale 11
Echo
GND   GND (protoshiledi 5-ne pesa)

Parempoolne (vasakule vaatav) sensor

Sensori pin   Arduino / protoshiledi ühendus
VCC   5V (protoshiledi 5-ne pesa)
Trig ühenduvad omavahel samale breadboardi reale 12
Echo
GND   GND (protoshiledi 5-ne pesa)

Kaugussensorite testimise kood

/**
 * Kaugusandurite testimise kood. Saadab lugemi seeriamonitori.
 */

/*Kaugusandurid on erinevad näiteks joone anduritest selle poolest, et nad ei
edasta arduinole igal ajaheltkel pinge taset vastavalt sellele, kas näevad midagi või ei.
Vaid nad esiteks võtavad vastu käskluse, et nüüd hakaku mõõtma. Ja peale seda
tagastavad signaali, et nüüd tuli kaja tagasi. Arduino peab ise arvet pidama, kui
pikk aeg möödus sellest, kui ta andis käskluse mõõtma hakata kuni selleni, kuni
andus kaja saamisest märku andis. Selle ajavahemiku järgi ja heli levimise kiiruse
põhjal arvutatakse välja, kui kaugelt kaja tagasi tuli, mis ongi distants lähima objekini.
Aga selle keerulise protsessi on teised inimesed juba valmis programmeerinud ja
pakkinud selliseks ajsaks nagu library ehk teek. Library kasutamiseks tuleb see 
1. installeerida (vt Sketch->Include libraries->Manage libraries. Otsi
NewPing nimeline ja installeeri see.
2. Leida File->Examples->[library nimi] alt näidiskood ja sealt vajalikud osad enda
koodi ümber kopeerida.
Esimene asi, mis tuleb kopeerida on library include rida:*/
#include <NewPing.h>

/*Teine asi, mis kopeerida, on libaray objekti loomise rida. Meil on 2 sensorit,
peame teisele tegema teise rea juurde. */
NewPing sonarParemale(11, 11, 200);//11 on Arduino pin, kuhu anduri ühendasime, 200 on max kaugus
NewPing sonarVasakule(12, 12, 200);//12 on Arduino pin -,,-



void setup() {
  Serial.begin(9600);
}

void loop() {
  Serial.print("Paremal on ruumi ");
  Serial.print(sonarParemale.ping_cm());//sonarParemale.ping_cm() on siin funktsioon, mis tagastab
  //mõõdetud kauguse numbri
  Serial.print(" cm. \t");
  delay(100);
  Serial.print("Vasakul on ruumi ");
  Serial.print(sonarVasakule.ping_cm());
  Serial.println(" cm.");
  delay(900);
}

Leegisensorite ühendused

Vasakpoolne sensor:

Sensori pin Arduino / protoshiledi ühendus
VCC 5V (breadboardile laiendatud riba)
GND GND (breadboardile laiendatud riba)
DO (jääb ühendamata)
AO A2

Keskmine sensor:

Sensori pin Arduino / protoshiledi ühendus
VCC 5V (breadboardile laiendatud riba)
GND GND (breadboardile laiendatud riba)
DO (jääb ühendamata)
AO A1

Parempoolne sensor:

Sensori pin Arduino / protoshiledi ühendus
VCC 5V (breadboardile laiendatud riba)
GND GND (breadboardile laiendatud riba)
DO (jääb ühendamata)
AO A0

Leegisensorite testimise kood

/**
 * Leegi andurite testimise kood. Saadab lugemi seeriamonitori.
 */
 
 /*Defineerime muutujad millesse salvestame pinide numbrid, kuhu andurid ühendatud on.*/
 int leegiandur_vasak = A2;
 int leegiandur_keskmine = A1;
 int leegiandur_parem = A0;  
 
void setup() {
  /*Määrame leegiandurite pinid sisenditeks.*/
  pinMode(leegiandur_vasak, INPUT);
  pinMode(leegiandur_keskmine, INPUT);
  pinMode(leegiandur_parem, INPUT);

  /*Paneme tööle arvutiga suhtluse seeriamonitori kaudu.*/
  Serial.begin(9600);
}

void loop() {
  /*Defineerime muutujad anduri hetkeväärtuse sisselugemiseks.*/
  int infrapuna_vasak;
  int infrapuna_keskmine;
  int infrapuna_parem;//Esialgu neisse pole väärtust salvestatud.
  
  /*Loeme andurite seisu ja salvestame oma muutujasse.*/
  infrapuna_vasak = analogRead(leegiandur_vasak);
  infrapuna_keskmine = analogRead(leegiandur_keskmine);
  infrapuna_parem = analogRead(leegiandur_parem);
  /* analogRead() on fuktsioon, mis loeb analoog pinge väärtuse, ta annab vastuseks
   mingi numbri vahemikus 0-1023. Seda fuktsiooni saab kasutada ainult A[x] pinidel.*/

  /*Saadame salvestatud andurite lugemi seeriamonitori.*/
  Serial.print("vasak: ");
  Serial.print(infrapuna_vasak);
  Serial.print("\t keskmine: "); // \t on "tab", lisab tühikud teatud tulbalaiuse saavutamiseni
  Serial.print(infrapuna_keskmine);
  Serial.print("\t parem: ");
  Serial.print(infrapuna_parem);
  Serial.print(" Kõige lähemal leegile on arvatavasti: ");

  /*If ja else sellised "laused" programmeerimises, kus vastavalt tavalistes sulgudes
  kirjeldatud tingimuse täidetusele tehakse kas üht või teist asja, mis on loogelistes 
  sulgudes if-i (tingimus tõene) või else-i (tingimus polnud tõene) järel.*/
  if(infrapuna_vasak < infrapuna_parem && infrapuna_vasak < infrapuna_keskmine) {
    /*Vasak andur näitab väiksemat numbrit kui parem ja väiksemat kui keskmine.
    Meid huvitab kõige väiksem number, sest leegi läheduses läheb näit allapoole.*/
    Serial.println(" vasak.");
  }
  else {
    /*Vasak andur oli kas paremast või keskmisest siiski suurema numbriga*/
    
    /*If-else lauseid saab ka üksteise sisse panna.*/    
    if(infrapuna_parem < infrapuna_vasak && infrapuna_parem < infrapuna_keskmine) {
      /*Parem andur näitab nii vasakust kui keksmisest väiksemat numbrit.*/
      Serial.println(" parem.");
    }
    else {
      /*Kui eelmised võrdlused tõele ei vastanud, siis on järel vaid variant, et 
      keskmine andur näitab kõige väiksemat numbrit.*/
      Serial.println(" keskmine.");
    }
      
  }

}

 

Viies video: Viimased jupid + Võistlusülesande kood

 

Oluline: Mis on vahepeal selgunud, et peaks olema teisiti

  1. Selgus, et US-015 kaugusanduritest kõigepealt üks ja siis teine hakkasid andma mingit suvalist näitu. Olgu asjad kui kaugel tahes, nemad arvasid, et vastus on 5 sentimeetirt. Vahetasin nad välja HC-SR04 andurite vastu, mis veel ei ole jukerdama hakanud. Aga tõdemus nii varasemast kui ka seekordsest loost on, et ultraheli kaugusandurid on väga ettarvamatult ebatöökindlad. Hea oleks hankida neid rohkem ja hankida erinevaid, või siis HC-SR04, aga vaadata ringi, kas on värvi või logode järgi loota, et erineval ajal ja erinevas kohas toodetud. Et kui üks komplekt ostub praagiks, siis oleks millegi teisega võimalik proovida.
  2. Kuigi selliste kollaste mootoritega robotikeredel, mida mitmelt poolt osta saab, on tavaliselt kaasas patareikarp 4-le AA patareile, siis selgub, et veidigi tühjenenud patareidega hakkavad mootorid tõrkuma. Mitte kogu aeg, aga mõnikord võivad mitte liikuma saada, kui neile vool peale antakse. Ja see on roboti puhul väga halb komme. Seega peaks kasutama 5-6 patareid. 4-se patareikarbi asemel tuleks hankida 6-ne või kaks tükki kolmeseid jadamisi ühendada. Siis on kindel, et mootorid ei kiilu kinni.
  3. Patareikarp (või karbid) põhja all ei tahtnud ainult kahepoolse teibiga seal seista, vajusid lahti, panin sellele serva äärde kuuma liimi, sai korda.
  4. Rataste paigutuse (roboti põhja kõrguse) ja põja all oleva patareikarbi mõõtmed on nii napikad, vahel õnnestus robotil rattad kohapeal pöörlema saada, sest patareikarp oli vastu maad ja kogu raskus ei olnud enam rataste peal. Mina panin katseks ratastele ümber kummirõngaid. Kui nad ära lendama ei kipuks, siis oleks päris head. Kummirõnga libeduse vastane omadus tuleb ka kasuks, lisaks sellele, et ta pool millimeetrit rattale läbimõõtu juurde annab. Püsivama lahendusena soovitaksin proovida jalgratta sisekummist lõigatud laiemat kummiriba. Või kui ma oleks varem teadnud, et keskkoht niipalju madalamale vajub, siis oleksin roboti põhja kleepinud eraldi osadest, kus mootorite kinnitused ühel tasapinnal ja patareikarbi kohal selles kihis auk. Ning too auk teise papikihiga ülevaltpoolt kaetud - see viiks patareikarbi kõrgemale rataste suhtes.

 

Lüliti mooduli ühendamine

Nimekirjas oleva lüliti mooduliga on selline lugu, et kuna minu kood hakkab kasutama interrupti, siis nupp vajab pull-up takistit, aga sellisel nupu moodulil on hoopis pull-down. Pole hullu, tegelik erinevus seisneb vaid selles, mis on nupu mooduli pinide juurde kirjutatud. Kui ühendame VCC ja GND omavahel ära vahetatuna, siis saabki pull-downist pull-up ja moodul töötab minu koodiga koostöös kenasti.

Ehk siis:

Lüliti moodul  
VCC GND breadboardil
OUT Arduino pin 2
GND 5V breadboardil

 

Ventilaator ja mosfet transistori moodul

Mosfeti moodul  
V+ Ventilaatori punane juhe
V- Ventilaatori must juhe
Vin Punane juhe, mis tuleb AA patareide karbist - ühenduse saab teha H-bridge kruviklemmi vahel, st sealt jätkata
GND GND breadboardil
SIG Arduino pin 4
VCC 5V breadboardil
GND GND breadboardil

Ventilaator paiguta niiviisi, et ta sihiks mitte horisontaalselt vaid veidi allapoole, kui sinu robot on sama kõrge, nagu minu oma.

 

Võistlusülesande kood

Kood failina.

Või kopeeri siit:


//Kaugusandurite library
#include <newping.h>


//Andurite Arduino ühendused
int leegiandur_vasak = A2;
int leegiandur_keskmine = A1;
int leegiandur_parem = A0;
int jooneandur_vasak = 7;
int jooneandur_parem = 8; 
int sonarParemalePin = 11;
int sonarVasakulePin = 12; 
//Mootorite Arduino ühendused
int parem_edasi = 3;
int parem_tagasi = 5;
int vasak_edasi = 6;
int vasak_tagasi = 9;
int ventilaator = 4;
//Arduino peal oleva led tulega anname märku, kui nupuvajutus oli edukas
int led = 13;
//Nupu ühendus
int start_stop_nupp = 2;


/*************************************************************************************************************************
   SEADISTUSED - siin sekstioonis olevate muutujate väärtusi võib olla vajalik sul muuta
   kas seetõttu, et sinu robot on natuke erinev minu omast või selleks, et targemini testida.
*************************************************************************************************************************/
/*Kui selle väärtuseks panna "true", siis USB juhet ühendatuna hoides prinditakse seeriamonitori
andurite lugemid ja roboti olek. Võistluse ajal aga pole seda vaja ja robot võiks kiiremini tegutseda ilma
tekste saatmata. Võistluse ajaks muuda see "true" ära "false"-iks.*/
bool debug = false;

/*Delay aeg millisekundites mille jooksul pöörates pwm kiirusega on pöörde tulemuseks 90 kraadine nurk.
Seda infot kasutatab kood robotit otse küünla suunas pööramise manöövri juures.*/
int yheksakymnekraadi_delay = 400;

/*Delay aeg, millega tehakse seintest ja väljaku äärejoonelt tagasipööramist. Kui tahad, et robot teeks 90
kraadise pöörde, siis pane see number sama, mis eelmine. Kui soovid teha laugema või tervama nurgaga tagasipööret,
siis muuda seda numbrit siin.*/
unsigned long poorde_aeg_ms = 400;

/*Mootorite kiirus (PWM väljund). Maksimum võimalik on 255. Kui kasutad 4 patareid, siis kindlasti 255.*/
int pwm = 200;

/*Kui pikaks ajaks ventilaator sisse lülitada kustutamiseks.*/
unsigned long kustutamise_aeg_ms = 4000;

/*Küünla ümber olevast rõngast välja taganemise manöövri ajaline pikkus, millisekundites.*/
unsigned long taganemise_aeg_ms = 600;

/*analogRead lugem infrapunaandurist, millest allapoole võime oletada, et andur on leegi läheduses*/
int oletatav_leek_infrapuna = 200;

/*Kaugus, mille mõõtmisel robot peaks pöörama takistusest (seinast) eemale.*/
int seinast_pooramise_kaugus = 27;

/*Kaugus mida andurid näitabvad siis, kui küünlast ollakse parajal kaugusel kustutamiseks.*/
int kyynlast_peatumise_kaugus = 19;

/*Peale nupu vajutamist roboti käivitamiseks ootame mingi aja enne kui see
tegelikult liikuma asub, et saaks käe rahulikult eemale võtta enne kui robot käest ära jookseb.*/
unsigned long stardi_viivitus_aeg_ms = 500; 

/*************************************************************************************************************************
   Seadistuste lõpp
*************************************************************************************************************************/



/*Kaugusandurite library objektid.*/
NewPing sonarParemale(sonarParemalePin, sonarParemalePin, 200);
NewPing sonarVasakule(sonarVasakulePin, sonarVasakulePin, 200);


/*Define laused et anda olekutele inimkeelsed nimed.*/
#define SEIS 0 //define lause lõpus ei käi semikoolonit
#define STARDI_VIIVITUS 1
#define OTSIMINE_OTSE 2
#define OTSIMINE_VASAKULE 3
#define OTSIMINE_PAREMALE 4
#define OTSEKS_KYYNLALE_KAUGEL 5
#define KYYNLASUUNAS 6
#define OTSEKS_KYYNLALE_LAHEDAL 7
#define KUSTUTAMINE 8
#define TAGANEMINE 9

/*Muutuja, mis hoiab infot millises "olekus" robot parasjagu töötab.*/
int olek = SEIS;

/*Muutujad, kuhu igal mõõtmisel salvestatakse andurite viimane lugem.*/
int infrapuna_vasak;
int infrapuna_keskmine;
int infrapuna_parem;
int joon_vasak;
int joon_parem;
int kaugus_vasakule;
int kaugus_paremale;

/*Muutujad, mis aitavad erinevate aja arvestustega.*/
unsigned long manoovri_algus_millis;
unsigned long nupuVonkeAeg = 0;
unsigned long viimaneNupuVonkeAeg = 0;




/******************************************************************************************************
 * setup on see osa koodi, mida täidetakse üks kord Arduino kävitumisel. 
 * Siin on peamiselt pinide sisenditeks ja väljunditeks määramine.
 *****************************************************************************************************/

void setup() {
  
  pinMode(leegiandur_vasak, INPUT);
  pinMode(leegiandur_keskmine, INPUT);
  pinMode(leegiandur_parem, INPUT);
  pinMode(jooneandur_vasak, INPUT);
  pinMode(jooneandur_parem, INPUT);
  /*Kuna kaugusandurid on seotud libraryga, siis nende pinmode toimub library koodis
  ja meie ei pea siin sellega tegelema. */
  
  pinMode(parem_edasi, OUTPUT);
  pinMode(parem_tagasi, OUTPUT);
  pinMode(vasak_edasi, OUTPUT);
  pinMode(vasak_tagasi, OUTPUT);
  pinMode(ventilaator, OUTPUT);
  

  /*Igaks juhuks anname mootoritele ja ventilaatoritele käsu, et nad oleks välja lülitatud.*/
  analogWrite(parem_edasi, 0);
  analogWrite(parem_tagasi, 0);
  analogWrite(vasak_edasi, 0);
  analogWrite(vasak_tagasi, 0);
  digitalWrite(ventilaator, LOW);

  /*Interrupt on selline asi, mis võimaldab regeerida millelegi, näiteks nupuvajutusele, koheselt
  sõltumata sellest, mida põhiline kood parasjagu teeb. Nupu interruti peale kutsutakse funktsioon
  start_stop(), mis muudab muutuja "olek" väärtust, vaata koodifaili lõpuosa.*/
  attachInterrupt(digitalPinToInterrupt(start_stop_nupp), start_stop, LOW);

  /*Kui debugimine on sisse lülitatud, siis võimaldame seeriamonitori kaudu suhtlemise arvutiga.*/
  if (debug == true) {
    Serial.begin(9600);
  }
  
} /*setup lõppeb. Liigutakse edasi loop-i juurde.*/



/******************************************************************************************************
 * loop on see osa koodi, mis kordub lõputult. 
 * Roboti tegevused on siin sees, täidetakse üht osa koodist, vastavalt sellele, mis on 
 * muutuja "olek" väärtuseks (if ja if else laused)
 *****************************************************************************************************/
 
void loop() {

  /**
    * Kõigepealt võtame kõigi andurite lugemid.
    * Tähelepanu: infrapuna andurid annavad väiksema numbri kui leek on lähemal, st nt 40 leegi vastas ja 800 ilma leegita.
    * Jooneandurid on 0 valgel põradal ja 1 mustal joonel.
    */
  infrapuna_vasak = analogRead(leegiandur_vasak);
  delay(2);/*Lühike paus enne järgmist analogRead käsklust teeb mõõtmistulemused töökindlamaks.*/
  infrapuna_keskmine = analogRead(leegiandur_keskmine);
  delay(2);
  infrapuna_parem = analogRead(leegiandur_parem);
  joon_vasak = digitalRead(jooneandur_vasak);
  joon_parem = digitalRead(jooneandur_parem);
  
  /*Kaugusanduritelt küsime kolm mõõtmist, viskame ära üksikud nullid ja võtame mõõtmiste keskmise. Seetõttu palju koodi siin.*/
  int kaugused_vasakule[] = {0,0,0};
  int kaugused_paremale[] = {0,0,0};
  int kaugused_vasakule_summa = 0;
  int kaugused_paremale_summa = 0;
  int kaugused_vasakule_arv = 0;
  int kaugused_paremale_arv = 0;
  for (int i=0; i<3; i++) {
    kaugused_vasakule[i] = sonarVasakule.ping_cm();
    kaugused_paremale[i] = sonarParemale.ping_cm();
  }
  for (int i=0; i<3; i++) {
    if (kaugused_vasakule[i]>0) {
      kaugused_vasakule_summa += kaugused_vasakule[i];
      kaugused_vasakule_arv++;
    }
    if (kaugused_paremale[i]>0) {
      kaugused_paremale_summa += kaugused_paremale[i];
      kaugused_paremale_arv++;
    }
    if (kaugused_vasakule_arv > 0) {
      kaugus_vasakule = kaugused_vasakule_summa/kaugused_vasakule_arv;
    }
    else {
      kaugus_vasakule = 0;
    }
    if (kaugused_paremale_arv > 0) {
      kaugus_paremale = kaugused_paremale_summa/kaugused_paremale_arv;
    }
    else {
      kaugus_paremale = 0;
    }
  }
  

  /**
   * Edasine tegevus sõltub sellest, millises olekus robot on. Täidetakse vaid üht järgenevatest
   * if/ifelse lausetest, vastavalt sellele, mis väärtus on muutujal "olek".
   */
  if (olek == SEIS) {
    //seisvas olekus ei ole vaja robotil midagi teha, aga kui me just lülitusime seisvasse olekusse, siis
    //mootorid ja ventilaator võisid töötada ja need tuleb välja lülitada, seetõttu need käsud siin
    analogWrite(parem_edasi, 0);
    analogWrite(parem_tagasi, 0);
    analogWrite(vasak_edasi, 0);
    analogWrite(vasak_tagasi, 0);
    digitalWrite(ventilaator, LOW);
    digitalWrite(led, LOW);
  }

  else if (olek == STARDI_VIIVITUS) {
    //Selles olekus ei tehta mitte midagi, ainult LED pannakse põlema, et inimene näeks, et nupuvajutus oli edukas.
    //Led jääb põlema kõigis olekutes peale SEIS oleku.
    digitalWrite(led, HIGH);

    if (millis() >= manoovri_algus_millis + stardi_viivitus_aeg_ms) {
      //Kui stardi viivituse aeg on täis tiksunud, siis minnakse otsimise olekusse.
      olek = OTSIMINE_OTSE; //Läheme otsimise olekusse.
    }
  }

  else if (olek == OTSIMINE_OTSE) {
    analogWrite(parem_edasi, pwm);
    analogWrite(parem_tagasi, 0);
    analogWrite(vasak_edasi, pwm);
    analogWrite(vasak_tagasi, 0);
    
    /*Vaatame, ega me musta joone peale ei ole sõitnud. Otsimise olekus võib küünalde rõngastelt tagasi 
     pöörata sama moodi nagu väljaku servast.
    Kui hakkame, siis pöörame joonelt eemale ca 90 kraadi.*/
    if (joon_vasak == 1) {
      olek = OTSIMINE_VASAKULE;
      manoovri_algus_millis = millis();
    }
    else if (joon_parem == 1) {
      olek = OTSIMINE_PAREMALE;
      manoovri_algus_millis = millis();
    }
    
    /*Kui joont ei leitud, siis järgmiseks vaatame, ega me seinale otsa sõitma ei hakka.
    Kui hakkame siis pöörame seinast eemale ca 90 kraadi.*/
    else if (kaugus_vasakule > 0 && kaugus_vasakule < seinast_pooramise_kaugus) {
      //Kaugus 0 tähendab, et midagi ei nähta, seega pööramiseks peab kaugus olema nulli ja "lähedal" vahel.
      olek = OTSIMINE_PAREMALE;
      manoovri_algus_millis = millis();
    }
    else if (kaugus_paremale > 0 && kaugus_paremale < seinast_pooramise_kaugus) {
      olek = OTSIMINE_VASAKULE;
      manoovri_algus_millis = millis();
    }

    /*Kui kumbagi - joont ega seina ees ei olnud, siis vaatame, 
    kas mõni leegiandur näeb nii palju infrapuna, et võiks oletada küünalt selles suunas.
    Kui näeb, siis proovime pöörata täpselt maksimaalse infrapuna suunas.*/
    else if (infrapuna_vasak < oletatav_leek_infrapuna || infrapuna_parem < oletatav_leek_infrapuna || infrapuna_keskmine < oletatav_leek_infrapuna) {
      olek = OTSEKS_KYYNLALE_KAUGEL;
    }
  }

  else if (olek == OTSIMINE_VASAKULE) {
    /*Mootorid pööravad, kuni aeg pole täis tiksunud.*/
    analogWrite(parem_edasi, pwm);
    analogWrite(parem_tagasi, 0);
    analogWrite(vasak_edasi, 0);
    analogWrite(vasak_tagasi, pwm);

    /*Kontrollime ainult leegisensoreid, sest joonele ega seinale ümber enda pöörates peale ei sõida.*/
    if (infrapuna_vasak < oletatav_leek_infrapuna || infrapuna_parem < oletatav_leek_infrapuna || infrapuna_keskmine < oletatav_leek_infrapuna) {
      olek = OTSEKS_KYYNLALE_KAUGEL;
    }

    if (millis() >= manoovri_algus_millis + poorde_aeg_ms) {
      //Kui aeg on täis tiksunud:
      olek = OTSIMINE_OTSE; //Kui pööre sooritatud, jätkame otsimise otse sõitu.
    }
  }

  else if (olek == OTSIMINE_PAREMALE) {
    analogWrite(parem_edasi, 0);
    analogWrite(parem_tagasi, pwm);
    analogWrite(vasak_edasi, pwm);
    analogWrite(vasak_tagasi, 0);

    /*Kontrollime ainult leegisensoreid, sest joonele ega seinale ümber enda pöörates peale ei sõida.*/
    if (infrapuna_vasak < oletatav_leek_infrapuna || infrapuna_parem < oletatav_leek_infrapuna || infrapuna_keskmine < oletatav_leek_infrapuna) {
      olek = OTSEKS_KYYNLALE_KAUGEL;
    }

    if (millis() >= manoovri_algus_millis + poorde_aeg_ms) {
      //Kui aeg on täis tiksunud:
      olek = OTSIMINE_OTSE; //Kui pööre sooritatud, jätkame otsimise otse sõitu.
    }
  }

  else if (olek == OTSEKS_KYYNLALE_KAUGEL || olek == OTSEKS_KYYNLALE_LAHEDAL) {
    /*Vaatame, millises anduris oli kõige rohkem infrapuna. Pöörame roboti sellest andurist u 30 kraadi vasakule.
    Teeme 10 mõõtmist u 6 kraadiste vahedega, et kaardistada infrapuna selles sektoris.
    Siis leiame maksimaalse näidu nende 10 hulgas ja keerame end täpselt selle suunas.*/
    if (olek == OTSEKS_KYYNLALE_KAUGEL && infrapuna_vasak < infrapuna_keskmine && infrapuna_vasak < infrapuna_parem) {
      //Pöörame 75 kraadi vasakule, et sealt mõõta 60 kraadine sektor, mille keskkoht on 45 kraadi vasakul praegusest seisust.
      analogWrite(vasak_edasi, 0);
      analogWrite(vasak_tagasi, pwm);
      analogWrite(parem_edasi, pwm);
      analogWrite(parem_tagasi, 0);
      delay(75.0/90.0 * yheksakymnekraadi_delay);
    }
    else if (olek == OTSEKS_KYYNLALE_KAUGEL && infrapuna_parem < infrapuna_keskmine && infrapuna_parem < infrapuna_vasak) {
      //Pöörame 15 kraadi paremale, et sealt mõõta 60 kraadine sektor, mille keskkoht on 45 kraadi paremal praegusest seisust.
      analogWrite(vasak_edasi, pwm);
      analogWrite(vasak_tagasi, 0);
      analogWrite(parem_edasi, 0);
      analogWrite(parem_tagasi, pwm);
      delay(15.0/90.0 * yheksakymnekraadi_delay);
    }
    else {//Olek on OTSEKS_KYYNLALE_LAHEDAL või siis olek on -KAUGEL, aga keskmine sensor näeb kõige rohkem infrapuna
      //Pöörame 30 kraadi vasakule, et sealt mõõta 60 kraadine sektor, mille keskkoht on preagune positsioon.
      analogWrite(vasak_edasi, 0);
      analogWrite(vasak_tagasi, pwm);
      analogWrite(parem_edasi, pwm);
      analogWrite(parem_tagasi, 0);
      delay(30.0/90.0 * yheksakymnekraadi_delay);
    }
    //Peatame mootorid
    analogWrite(vasak_edasi, 0);
    analogWrite(vasak_tagasi, 0);
    analogWrite(parem_edasi, 0);
    analogWrite(parem_tagasi, 0);

    //Teeme 11 mõõtmist u 60 kraadises sektoris.
    int infrapuna_sektoris[11];
    //Käivitame mootrid
    analogWrite(vasak_edasi, pwm);
    analogWrite(vasak_tagasi, 0);
    analogWrite(parem_edasi, 0);
    analogWrite(parem_tagasi, pwm);
    //Jagame perioodi osadeks ja teeme iga osa järel mõõtmise keskmise sensoriga
    for (int i=0; i<11; i++) {
      if (i>0) {
        delay(6.0/90.0 * yheksakymnekraadi_delay);
      }
      infrapuna_sektoris[i] = analogRead(leegiandur_keskmine);
    }
    //Peatame mootorid
    analogWrite(vasak_edasi, 0);
    analogWrite(vasak_tagasi, 0);
    analogWrite(parem_edasi, 0);
    analogWrite(parem_tagasi, 0);

    //Leiame kõrgeima infrapunaga sektori indeksi - anduri näit on sel juhul madalaim number.
    int madalaim_nait = 1023;//1023 on maksimum, mida analogRead anda saab, reaalsed näidud on sellest madalamad.
    int madalaima_indeks = 0;
    for (int i=0; i<11; i++) {
      if (infrapuna_sektoris[i] < madalaim_nait) {
        madalaim_nait = infrapuna_sektoris[i];
        madalaima_indeks = i;
      }
    }
    if (debug == true) {//Kui tegeleme testimisega, siis saadame andmed arvutisse
      Serial.println();
      Serial.print("Infrapuna mõõtmised: \t");
      for (int i=0; i<11; i++) {
        Serial.print(infrapuna_sektoris[i]);
        Serial.print("\t");
      }
      Serial.print("\t\tLeek on sektoris: ");
      Serial.println(madalaima_indeks);
      Serial.println();
      delay(5000); //Võtame 5 sek aega, et jõuaks seeriamonitoris lugeda juttu.     
    }

    delay(500);//Ootame eelmise pöördliikumise inertsi möödumist
    //Pöörame tagasi selle sektori suunas.
    analogWrite(vasak_edasi, 0);
    analogWrite(vasak_tagasi, pwm);
    analogWrite(parem_edasi, pwm);
    analogWrite(parem_tagasi, 0);
    delay((11-madalaima_indeks)*6.0/90.0 * yheksakymnekraadi_delay);
    analogWrite(vasak_edasi, 0);
    analogWrite(vasak_tagasi, 0);
    analogWrite(parem_edasi, 0);
    analogWrite(parem_tagasi, 0);
    if (olek == OTSEKS_KYYNLALE_KAUGEL) {
      olek = KYYNLASUUNAS;
    }
    else {
      olek = KUSTUTAMINE;
      manoovri_algus_millis = millis();
    }
  }

  else if (olek == KYYNLASUUNAS) {
    analogWrite(parem_edasi, pwm);
    analogWrite(parem_tagasi, 0);
    analogWrite(vasak_edasi, pwm);
    analogWrite(vasak_tagasi, 0);

    //Kontrollime kas näeme ees leeki, kui ei näe leeki, aga näeme takistust või joont, siis oleme eksiteel arvamusega, et liigume küünla suunas ja
    //peame minema tagasi otsimise olekusse.
    if (
        infrapuna_keskmine > oletatav_leek_infrapuna && 
        (
          kaugus_vasakule <= seinast_pooramise_kaugus || kaugus_paremale <= seinast_pooramise_kaugus ||
          joon_vasak == 1 || joon_parem == 1
        )
      ) {
      olek = OTSIMINE_OTSE;
    }
    //Kui leek on silmapiiril, siis jooni ignoreerime ja kaugusandurist vaatame, millal on paras koht küünla ees peatuda.
    else if ((kaugus_vasakule > 0 && kaugus_vasakule <= kyynlast_peatumise_kaugus) || (kaugus_paremale > 0 && kaugus_paremale <= kyynlast_peatumise_kaugus)) {
      //Kaugus 0 tähendab, et midagi ei nähta, seega otsustamiseks peab kaugus olema nulli ja "lähedal" vahel.
      olek = OTSEKS_KYYNLALE_LAHEDAL;
    }
  }

  else if (olek == KUSTUTAMINE) {
    //Kustustamise olekus lülitatakse ventilaator tööle ja sellest olekust väljutakse siis
    //kui ettenähtud aeg on möödas.

    //Kuni tegevuse aeg pole täis tiksunud kordame siin käsklust mootorid välja ja ventilaator tööle lülitada.
    analogWrite(parem_edasi, 0);
    analogWrite(parem_tagasi, 0);
    analogWrite(vasak_edasi, 0);
    analogWrite(vasak_tagasi, 0);
    digitalWrite(ventilaator, HIGH);

    if (millis() >= manoovri_algus_millis + kustutamise_aeg_ms) {
      //Kui aeg on täis tiksunud:
      olek = TAGANEMINE; //Järgmine tegevus on taganemine.
      manoovri_algus_millis = millis(); //Salvestame ka selle tegevuse algus aja süsteemi kella millisekundites.
      digitalWrite(ventilaator, LOW);
    }
  }

  else if (olek == TAGANEMINE) {
    //Taganemise olekus lülitatakse mootorid tagurpidi käima ja sellest olekust väljutakse siis
    //kui ettenähtud aeg selle manöövri sooritamiseks on möödas.

    //Kuni manöövri aeg pole täis tiksunud kordame siin käsklust mootorid tagurpidikäigule lülitada.
    analogWrite(parem_edasi, 0);
    analogWrite(parem_tagasi, pwm);
    analogWrite(vasak_edasi, 0);
    analogWrite(vasak_tagasi, pwm);
    

    if (millis() >= manoovri_algus_millis + taganemise_aeg_ms) {
      //Kui aeg on täis tiksunud:
      olek = OTSIMINE_PAREMALE; //Järgmine tegevus on otsimine ja loogiline on teha mingi pööre.
    }
  }

  /*Kui debugimine seeriamonitoris on sisse lülitatud, siis trükitakse sinna roboti olek ja andurite lugemid.*/
  if (debug == true) {
    Serial.print("olek:");
    if (olek == SEIS) { Serial.print("SEIS"); }
    else if (olek == STARDI_VIIVITUS) { Serial.print("STARDI_VIIVITUS"); }
    else if (olek == OTSIMINE_OTSE) { Serial.print("OTSIMINE_OTSE"); }
    else if (olek == OTSIMINE_PAREMALE) { Serial.print("OTSIMINE_PAREMALE"); }
    else if (olek == OTSIMINE_VASAKULE) { Serial.print("OTSIMINE_VASAKULE"); }
    else if (olek == OTSEKS_KYYNLALE_KAUGEL) { Serial.print("OTSEKS_KYYNLALE_KAUGEL"); }
    else if (olek == KYYNLASUUNAS) { Serial.print("KYYNLASUUNAS"); }
    else if (olek == OTSEKS_KYYNLALE_LAHEDAL) { Serial.print("OTSEKS_KYYNLALE_LAHEDAL"); }
    else if (olek == KUSTUTAMINE) { Serial.print("KUSTUTAMINE"); }
    else if (olek == TAGANEMINE) { Serial.print("TAGANEMINE"); }
  }
  Serial.print("\tleegi: ");
  Serial.print(infrapuna_vasak);
  Serial.print("\t");
  Serial.print(infrapuna_keskmine);
  Serial.print("\t");
  Serial.print(infrapuna_parem);
  Serial.print( "\tkaugus: ");
  Serial.print(kaugus_vasakule);
  Serial.print("\t");
  Serial.print(kaugus_paremale);
  Serial.print( "\tjoone: ");
  Serial.print(joon_vasak);
  Serial.print("\t");
  Serial.println(joon_parem);
  
} /*loop lõppeb. Minnakse tagasi loopi algusesse.*/



/**************************************************************************************************/

      
/**
 * See funktsioon kutsutakse, kui tuvastati nupu oleku muutus (interrupt).
 * Aga kuna nupp tavaliselt ei ühendu perfektselt 
 * vaid annab võnkumistega signaali, siis siin tehakse veidi ajaarvestust, et mitmest järjestikusest 
 * võnkest lugeda kokku üks tegelik nupuvajutus.
 * 
 * Kui nupuvajutus toimus, siis muudetakse roboti olekut:
 * - seisvast olekust küünla otsimise olekusse (stardi viivitusega),
 * - ükskõik millisest teisest (tegutsemise) olekust seisvasse olekusse.
 */
void start_stop() {
  nupuVonkeAeg = millis();
  if (nupuVonkeAeg - viimaneNupuVonkeAeg > 330) {
    if (olek == SEIS) {
      olek = STARDI_VIIVITUS;
      manoovri_algus_millis = millis();
    }
    else {
      olek = SEIS;
    }
  }
  viimaneNupuVonkeAeg = nupuVonkeAeg;
}