Sisukord:

DuvelBot - ESP32 -CAM õlle serveerimisrobot: 4 sammu (piltidega)
DuvelBot - ESP32 -CAM õlle serveerimisrobot: 4 sammu (piltidega)

Video: DuvelBot - ESP32 -CAM õlle serveerimisrobot: 4 sammu (piltidega)

Video: DuvelBot - ESP32 -CAM õlle serveerimisrobot: 4 sammu (piltidega)
Video: CE190077 (FINAL YEAR PROJECT 1) 2024, Juuni
Anonim
DuvelBot - ESP32 -CAM õlle serveerimisrobot
DuvelBot - ESP32 -CAM õlle serveerimisrobot

Pärast rasket tööpäeva ei jõua diivanil oma lemmikõlut rüübata. Minu puhul on see Belgia blond õlu "Duvel". Pärast kokkuvarisemist seisame aga silmitsi kõige tõsisema probleemiga: külmkapp, mis sisaldab minu suleteki, on diivanilt eemaldatud üle 20 meetri.

Kuigi minu poolne kerge sund võib ajendada teismeliste külmkappide koristajat minu nädala Duveli hüvitise väljavalamiseks, on ülesanne see tegelikult peaaegu ammendatud esivanemale toimetada üks samm liiga kaugele.

Aeg jootekolb ja klaviatuur välja murda …

DuvelBot on imelihtne AI-Thinker ESP32-CAM-põhine juhtimise veebikaamera, mida saate juhtida nutitelefonist, brauserist või tahvelarvutist.

Seda platvormi on lihtne kohandada või laiendada vähem alkoholi tarbimiseks (mõelge näiteks SpouseSpy, NeighbourWatch, KittyCam …).

Ehitasin selle roboti peamiselt selleks, et natuke õppida tundma kogu veebiprogrammeerimist ja asjade Interneti asju, millest ma midagi ei teadnud. Nii et selle juhendi lõpus on üksikasjalik selgitus selle toimimise kohta.

Paljud selle juhendi osad põhinevad Random Nerd Tutorialsi suurepärastel selgitustel, nii et palun külastage neid!

Tarvikud

Mida sa vajad:

Osade loend ei ole kivisse raiutud ja paljusid osi saab hankida tonnides erinevates versioonides ja paljudest erinevatest kohtadest. Enim hankisin Ali-Expressist. Nagu Machete ütles: improviseerige.

Riistvara:

  • AI Thinker ESP32-CAM moodul. See võib ilmselt töötada koos teiste ESP32-CAM moodulitega, kuid seda ma kasutasin
  • L298N mootori juhtplaat,
  • Odav neljarattaline robootikaplatvorm,
  • Suure tasase pinnaga korpus, näiteks Hammond Electronics 1599KGY,
  • USB-3.3V-TTL-muundur programmeerimiseks.
  • Valgustuseks: 3 valget LED -i, BC327 või muu üldotstarbeline transistor NPN (Ic = 500mA), 4k7k takisti, 3 82Ohm takisti, perfboard, kaablid (vt skeemi ja pilte).
  • Sisse/välja lülituslüliti ja tavaliselt avatud nupp programmeerimiseks.

Valikuline:

  • Kalasilma kaamera, mille paindlikkus on pikem kui tavalisel OV2460 kaameral, mis on varustatud ESP32-CAM mooduliga,
  • WiFi -antenn sobiva pikkusega kaabli ja Ultra Miniature Coax Connectoriga. ESP32-CAM-il on sisseehitatud antenn ja korpus on plastikust, nii et antenni pole tegelikult vaja, kuid arvasin, et see näeb lahe välja, nii…
  • Tindiprinteriga kleebispaber ülemise kaane kujundamiseks.

Tavalised riistvaratööriistad: jootekolb, puurid, kruvikeerajad, tangid …

Samm: robotplatvormi loomine

Robotplatvormi ehitamine
Robotplatvormi ehitamine
Robotplatvormi ehitamine
Robotplatvormi ehitamine
Robotplatvormi ehitamine
Robotplatvormi ehitamine

Skeem:

Skeem pole midagi erilist. ESP32-nukk juhib mootoreid L298N mootorijuhtplaadi kaudu, millel on kaks kanalit. Vasaku ja parema külje mootorid on paigutatud paralleelselt ja mõlemad pooled hõivavad ühe kanali. Neli väikest 10..100nF keraamilist kondensaatorit mootori tihvtide lähedal on nagu alati soovitatav raadiosageduslike häirete vastu võitlemiseks. Samuti võib skeemil näidatud suur elektrolüütikork (2200… 4700uF) mootoriplaadi toitel toitepinge pulsatsiooni veidi piirata (kui soovite näha õudusfilmi, siis proovige sondi Vbat ostsilloskoobiga, kui mootorid töötavad).

Pange tähele, et mõlema mootorikanali ENABLE kontakte juhib ESP32 (IO12) sama impulsi laiusega moduleeritud (PWM) tihvt. Selle põhjuseks on asjaolu, et ESP32-CAM-moodulil pole tonni GPIO-sid (lisatud on mooduli skeem). Roboti valgusdioode juhib IO4, mis juhib ka sisseehitatud välklambi LED -i, nii et eemaldage Q1, et välklamp LED suletud korpuses ei süttiks.

Programmeerimisnupp, sisse/välja lüliti, laadimispistik ja programmeerimisliides on ligipääsetavad roboti all. Ma oleksin võinud programmeerimispistiku (3,5 mm pistikupesa?) Jaoks palju paremat tööd teha, kuid õlu ei suutnud enam oodata. Ka õhu kaudu värskendusi (OTA) oleks tore seadistada.

Roboti programmeerimisrežiimi lülitamiseks vajutage programmeerimisnuppu (see tõmbab IO0 madalaks) ja seejärel lülitage see sisse.

Tähtis: roboti NiMH -akude laadimiseks kasutage laboratoorset toiteallikat (laadimata) umbes 14 V pingele ja voolutugevusele kuni 250 mA. Pinge kohandub patareide pingega. Ühendage lahti, kui robot tunneb kuumust või aku pinge jõuab umbes 12,5 V -ni. Ilmselge parandus oleks korraliku akulaadija integreerimine, kuid see ei kuulu käesoleva juhendi reguleerimisalasse.

Riistvara:

Palun vaadake ka piltidel olevaid märkmeid. Korpus kinnitatakse roboti alusele 4 M4 poldi ja isesulguvate mutrite abil. Pange tähele kummist torusid, mida kasutatakse vahekaugustena. Loodetavasti annab see ka Duvelile teatava vedrustuse, kui sõit peaks osutuma konarlikuks. ESP32-CAM moodul ja L298N mootoriplaat on paigaldatud korpusesse, kasutades plastikust kleepuvaid jalgu (pole inglise keeles õige nimi), et vältida täiendavate aukude puurimist. Samuti on ESP32 paigaldatud oma perfboardile ja pistikupesadesse. See muudab ESP32 vahetamise lihtsaks.

Ärge unustage: kui kasutate sisseehitatud antenni asemel välist WiFi-antenni, jootke ka ESP32-CAM-plaadi alumisel küljel olev antennivaliku hüppaja.

Printige faili DuvelBot.svg ülemine logo tindiprinteri kleebispaberile (või kujundage see ise) ja oletegi valmis!

Samm: programmeerige robot

Programmeerige robot
Programmeerige robot

Soovitav on programmeerida robot enne selle sulgemist, veendumaks, et kõik töötab ja võluv suitsu ei teki.

Teil on vaja järgmisi tarkvara tööriistu:

  • Arduino IDE,
  • ESP32 teegid, SPIFFS (järjestikune välisseadmete välkfailisüsteem), ESPAsynci veebiserveri teek.

Viimast saab installida, järgides seda juhuslikku juhendit kuni jaotiseni "failide korraldamine" kaasa arvatud. Ma tõesti ei osanud seda paremini seletada.

Kood:

Minu koodi leiate aadressilt

  • Arduino visand DuvelBot.ino,
  • Andmete alamkaust, mis hoiab failid, mis laaditakse üles ESP -välklambile, kasutades SPIFFS -i. See kaust sisaldab veebilehte, mida ESP hakkab esitama (index.html), logo, mis on veebilehe osa (duvel.png), ja kaskaaditud stiililehte või CSS -faili (style.css).

Roboti programmeerimiseks toimige järgmiselt

  • Ühendage USB-TTL-muundur skemaatiliselt näidatud viisil,
  • Fail -> Ava -> minge kausta, kus DuvelBot.ino asub.
  • Muutke visandis oma võrgumandaate:

const char* ssid = "yourNetworkSSIDHere"; const char* password = "yourPasswordHere";

  • Tööriistad -> Tahvel -> "AI -Thinker ESP -32 CAM" ja valige oma arvutile sobiv jadaport (Tööriistad -> Port -> midagi sellist /dev /ttyUSB0 või COM4),
  • Avage seeriamonitor Arduino IDE -s. Vajutades nuppu PROG (mis tõmbab IO0 madalaks), lülitage robot sisse,
  • Kontrollige jadamonitorilt, kas ESP32 on allalaadimiseks valmis,
  • Sulgege jadamonitor (muidu SPIFFS -i üleslaadimine ebaõnnestub),
  • Tööriistad -> "ESP32 Sketch Data Upload" ja oodake, kuni see lõpeb,
  • Programmeerimisrežiimi naasmiseks lülitage välja ja uuesti sisse, hoides all nuppu PROG,
  • Eskiisi programmeerimiseks vajutage noolt "Laadi üles" ja oodake selle lõppu,
  • Avage jadamonitor ja lülitage ESP32 välja/välja,
  • Kui see on käivitatud, märkige üles ip-aadress (näiteks 192.168.0.121) ja ühendage robot USB-TTL-muundurist lahti,
  • Avage sellel ip -aadressil brauser. Te peaksite nägema liidest nagu pildil.
  • Valikuline: määrake ESP32 mac-aadress ruuteris fikseeritud ip-aadressiks (sõltub sellest, kuidas seda teha).

See on kõik! Lugege edasi, kui soovite teada, kuidas see toimib …

3. toiming: kuidas see toimib

Nüüd jõuame huvitava osa juurde: kuidas see kõik koos toimib?

Püüan seda samm -sammult selgitada, kuid pidage meeles, et Kajnjaps ei ole veebiprogrammeerimise spetsialist. Tegelikult oli natuke veebiprogrammeerimise õppimine kogu DuvelBoti ehitamise eeldus. Kui ma teen ilmselgeid vigu, palun jätke kommentaar!

Ok, pärast ESP32 sisselülitamist, nagu tavaliselt seadistamisel, lähtestab see GPIO -d, seostab need mootori ja LED -juhtimise PWM -taimeritega. Lisateavet mootori juhtimise kohta leiate siit, see on üsna tavaline.

Seejärel on kaamera konfigureeritud. Hoidsin teadlikult eraldusvõimet üsna madalal (VGA või 640x480), et vältida aeglast reageerimist. Pange tähele, et AI-Thinker ESP32-CAM-plaadil on jadamälu kiip (PSRAM), mida see kasutab suurema eraldusvõimega kaameraraamide salvestamiseks:

if (psramFound ()) {Serial.println ("PSRAM leitud."); config.frame_size = FRAMESIZE_VGA; config.jpg_quality = 12; config.fb_count = 2; // kaadripuhverite arvu vt: https://github.com/espressif/esp32-camera} else {Serial.println ("PSRAM-i ei leitud."); config.frame_size = FRAMESIZE_QVGA; config.jpg_quality = 12; config.fb_count = 1; }

Seejärel lähtestatakse jada -välisseadmete välkfailisüsteem (SPIFFS):

// initsialiseeri SPIFFS if (! SPIFFS.begin (true)) {Serial.println ("SPIFFS -i paigaldamisel tekkis viga!"); tagasipöördumine; }

SPIFFS toimib nagu väike failisüsteem ESP32 -l. Siin kasutatakse seda kolme faili salvestamiseks: veebileht ise index.html, kaskaaditud failistiil style.css ja-p.webp

Järgmisena ühendub ESP32 teie ruuteriga (ärge unustage enne üleslaadimist oma mandaati seadistada):

// muuda oma ruuteri mandaati siinkohal char* ssid = "yourNetworkSSIDHere"; const char* password = "yourPasswordHere"; … // ühenda WiFi Serial.print ("Ühendamine WiFi -ga"); WiFi.begin (ssid, parool); while (WiFi.status ()! = WL_CONNECTED) {Serial.print ('.'); viivitus (500); } // nüüd ruuteriga ühendatud: ESP32 -l on nüüd ip -aadress

Et midagi kasulikku teha, käivitame asünkroonse veebiserveri.

// luua AsyncWebServeri objekt pordis 80AsyncWebServer server (80); … Server.begin (); // hakake seoseid kuulama

Kui nüüd sisestate brauseri aadressiribale ruuteri poolt ESP32 -le määratud IP -aadressi, saab ESP32 päringu. See tähendab, et see peaks kliendile (teile või teie brauserile) vastama, pakkudes talle midagi, näiteks veebilehte.

ESP32 teab, kuidas vastata, sest seadistamisel on vastused kõigile võimalikele lubatud päringutele registreeritud server.on () abil. Näiteks töödeldakse peamist veebilehte või indeksit (/) järgmiselt.

server.on ("/", HTTP_GET, (AsyncWebServerRequest *päring) {Serial.println ("/taotlus laekus!"); request-> send (SPIFFS, "/index.html", String (), false, protsessor);});

Nii et kui klient loob ühenduse, vastab ESP32, saates faili index.html SPIFFS -failisüsteemist. Parameetrite protsessor on funktsiooni nimi, mis töötleb html -i eeltööd ja asendab kõik erimärgendid:

// Asendab html -s kohahoidjad nagu %DATA %// muutujatega, mida soovite näidata //

Andmed: %DATA %

Stringiprotsessor (const String & var) {if (var == "DATA") {//Serial.println("in protsessor! "); return string (dutyCycleNow); } return string ();}

Nüüd laseme veebilehel index.html ennast kahjustada. Üldiselt on alati kolm osa:

  1. html -kood: milliseid elemente tuleks kuvada (nupud/tekst/liugurid/pildid jne),
  2. stiilikood, kas eraldi.css -failis või jaotises… kuidas elemendid välja peaksid nägema,
  3. javascript a… jaotis: kuidas veebileht peaks toimima.

Kui indeks.html laaditakse brauserisse (mis teab, et see on rea DOCTYPE tõttu html), jookseb see sellele reale:

See on css -stiililehe taotlus. Selle lehe asukoht on antud href = "…". Mida teie brauser teeb? Õige, see käivitab serverile järjekordse päringu, seekord style.css. Server võtab selle taotluse vastu, kuna see registreeriti:

server.on ("/style.css", HTTP_GET, (AsyncWebServerRequest *päring) {Serial.println ("css-päring on vastu võetud"); request-> send (SPIFFS, "/style.css", "text/css ");});

Korralik jah? Muide, see võis olla kogu teie brauseri jaoks href = "/some/file/on/the/other/side/of/the moon". See tooks selle faili sama rõõmsalt. Ma ei seleta stiililehte, kuna see kontrollib ainult välimust, nii et see pole siin tõesti huvitav, kuid kui soovite rohkem teada saada, vaadake seda õpetust.

Kuidas DuvelBoti logo ilmub? Indeksis.html on meil:

millele ESP32 vastab:

server.on ("/duvel", HTTP_GET, (AsyncWebServerRequest *päring) {Serial.println ("duveli logo taotlus on vastu võetud!"); request-> send (SPIFFS, "/duvel.png", "image-p.webp

..teine SPIFFS -fail, seekord täielik pilt, nagu vastuses märgitud "image/png".

Nüüd jõuame tõeliselt huvitava osa juurde: nuppude kood. Keskendume nupule FORWARD:

ETTE

Nimi class = "…" on ainult nimi, mis seob selle stiililehega, et kohandada suurust, värvi jne. Olulised osad on onmousedown = "toggleCheckbox ('forward')" ja onmouseup = "toggleCheckbox ('stop') ". Need moodustavad nupu toimingud (sama onuchuchtart/ontouchend, kuid see puudutab puuteekraane/telefone). Siin kutsub nuputoiming JavaScripti jaotises funktsiooni toggleCheckbox (x):

function toggleCheckbox (x) {var xhr = new XMLHttpRequest (); xhr.open ("GET", "/" + x, true); xhr.send (); // saaks ka vastusega midagi ette võtta, kui oleme valmis, aga me ei tee seda}

Nii et edasisuunamisnupu vajutamine viib kohe toggleCheckboxi ('edasi') kutsumiseni. See funktsioon käivitab seejärel asukoha "/edasi" XMLHttpRequest "GET", mis toimib täpselt nii, nagu oleksite brauseri aadressiribale kirjutanud 192.168.0.121/forward. Kui see taotlus saabub ESP32 -le, tegeleb sellega:

server.on ("/edasi", HTTP_GET, (AsyncWebServerRequest *päring) {Serial.println ("vastu võetud/edastatud"); actionNow = EDASI; taotlus-> saatmine (200, "tekst/tavaline", "OK edasi. ");});

Nüüd vastab ESP32 lihtsalt tekstiga "OK edasi". Märkus toggleCheckBox () ei tee selle vastusega midagi (ega oota), kuid see võib toimuda, nagu on näidatud hiljem kaamera koodis.

Selle vastuse ajal määrab programm vastuseks nupuvajutusele ainult muutuva actionNow = FORWARD. Nüüd programmi põhisüsteemis jälgitakse seda muutujat eesmärgiga tõsta üles/alla mootorite PWM -i. Loogika on järgmine: kuni meil on toiming, mis ei ole STOP, tõstke mootoreid selles suunas üles, kuni teatud arv (dutyCycleMax) on saavutatud. Seejärel hoidke seda kiirust, kuni actionNow pole muutunud:

void loop () {currentMillis = millis (); if (currentMillis - previousMillis> = dutyCycleStepDelay) {// salvesta viimane kord, kui käivitasid silmuse previousMillis = currentMillis; // mainloop vastutab mootorite üles/alla tõstmise eest, kui (actionNow! = previousAction) {// kaldtee allapoole, siis peatage, seejärel muutke tegevust ja suurendage dutyCycleNow = dutyCycleNow-dutyCycleStep; if (dutyCycleNow <= 0) {// kui pärast dc vähendamist on 0, seadke uus suund, alustage minimaalsest töötsüklist setDir (actionNow); previousAction = actionNow; dutyCycleNow = tollimaksutsükkelMin; }} else // actionNow == previousAction ramp up, välja arvatud juhul, kui suund on STOP {if (actionNow! = STOP) {dutyCycleNow = dutyCycleNow+dutyCycleStep; if (dutyCycleNow> dutyCycleMax) dutyCycleNow = tollimaksutsükkelMax; } else dutyCycleNow = 0; } ledcWrite (pwmChannel, dutyCycleNow); // mootori töötsükli reguleerimine}}

See suurendab aeglaselt mootorite pöörlemiskiirust, selle asemel, et lihtsalt täiskiirusel käivitada ja kallihinnalist Duvelit maha valada. Ilmselge parandus oleks selle koodi teisaldamine taimeriga katkestamise rutiini, kuid see toimib nii, nagu see on.

Nüüd, kui vabastame edasisuunamisnupu, kutsub teie brauser toggleCheckbox ('stop'), mille tulemuseks on taotlus GET /stop. ESP32 seab actionNow olekuks STOP (ja vastab "OK stop".), Mis avab mootori pööramiseks põhiliini.

Aga LED -id? Sama mehhanism, kuid nüüd on meil liugur:

JavaScriptis jälgitakse liuguri seadistust nii, et iga muudatuse korral helistatakse "/LED/xxx", kus xxx on heleduse väärtus, millele LED -id tuleks seada:

var slide = document.getElementById ('slide'), sliderDiv = document.getElementById ("sliderAmount"); slide.onchange = function () {var xhr = new XMLHttpRequest (); xhr.open ("GET", "/LED/" + this.value, true); xhr.send (); sliderDiv.innerHTML = see.väärtus; }

Pange tähele, et kasutasime dokumenti.getElementByID ('slaid'), et hankida liugobjekt ise, mis oli deklareeritud koos ja et väärtus väljastatakse iga muudatuse korral tekstielemendile.

Visandis olev käitleja püüab kõik heledussoovid, kasutades käitleja registreerimisel "/LED/*". Seejärel jagatakse viimane osa (number) ja jagatakse int:

server.on ("/LED/ *", HTTP_GET, (AsyncWebServerRequest *päring) {Serial.println ("LED-päring saadi!"); setLedBrightness ((päring-> url ()). alamstring (5). toInt ()); request-> send (200, "text/plain", "OK Leds.");});

Sarnaselt ülalkirjeldatule juhivad raadionupud muutujaid, mis määravad PWM -i vaikeseaded, nii et DuvelBot saab õllega aeglaselt teie juurde sõita, ettevaatlikult, et seda vedelat kulda maha ei valaks, ja kiiresti tagasi kööki, et midagi juurde tuua.

… Kuidas siis kaamera pilti värskendada, ilma et peaksite lehte värskendama? Selleks kasutame tehnikat nimega AJAX (asünkroonne JavaScript ja XML). Probleem on selles, et tavaliselt järgneb kliendi-serveri ühendus fikseeritud protseduurile: klient (brauser) esitab päringu, server (ESP32) vastab, juhtum on suletud. Valmis. Midagi ei juhtu enam. Kui vaid kuidagi suudaksime brauseri meelitada ESP32 -lt regulaarselt värskendusi taotlema … ja just seda me selle javascriptiga teeme:

setInterval (function () {var xhttp = new XMLHttpRequest (); xhttp.open ("GET", "/CAMERA", true); xhttp.responseType = "blob"; xhttp.timeout = 500; xhttp.ontimeout = function () {}; xhttp.onload = function (e) {if (this.readyState == 4 && this.status == 200) {// vt: https://stackoverflow.com/questions/7650587/using… // https://www.html5rocks.com/en/tutorials/file/xhr2/ var urlCreator = window. URL || window.webkitURL; var imageUrl = urlCreator.createObjectURL (this.response); // looge mullist objekt document.querySelector ("#camimage"). src = imageUrl; urlCreator.revokeObjectURL (imageurl)}}; xhttp.send ();}, 250);

setInterval võtab parameetrina funktsiooni ja täidab seda iga kord (siin üks kord 250 ms kohta, mille tulemuseks on 4 kaadrit sekundis). Funktsioon, mida täidetakse, taotleb aadressil /CAMERA binaarset "plekki". Sellega tegeleb ESP32-CAM visandis järgmiselt (Randomnerdtutorials):

server.on ("/CAMERA", HTTP_GET, (AsyncWebServerRequest * päring) {Serial.println ("kaamera taotlus on vastu võetud!"); camera_fb_t * fb = NULL; // esp_err_t res = ESP_OK; size_t _jpg_buf_len = 0; uint8 * _jpg_buf = NULL; // jäädvusta kaader fb = esp_camera_fb_get (); if (! fb) {Serial.println ("Kaadripuhvrit ei saanud hankida"); return;} if (fb-> formaat! = PIXFORMAT_JPEG)/ /juba selles vormingus konfiguratsioonist {bool jpeg_converted = frame-j.webp

Olulisteks osadeks on raami fb = esp_camera_fb_get () teisendamine jpg-ks (AI-Thinkeri jaoks on see juba selles vormingus) ja jpeg-i väljasaatmine: request-> send_P (200, "image/jpg", _jpg_buf, _jpg_buf_len).

Seejärel ootab JavaScripti funktsioon selle pildi saabumist. Siis võtab lihtsalt natuke tööd, et teisendada vastuvõetud "blob" URL -iks, mida saab kasutada allikana pildi värskendamiseks html -lehel.

oeh, oleme valmis!

4. samm: ideed ja ülejäägid

Ideed ja ülejäägid
Ideed ja ülejäägid

Selle projekti eesmärk oli minu jaoks õppida täpselt piisavalt veebiprogrammeerimist, et riistvara veebiga liidestada. Selle projekti jaoks on mitu laiendust. Siin on mõned ideed:

  • Rakendage "tõeline" kaamera voogesitus, nagu siin ja siin on selgitatud, ja teisaldage see teisele serverile, nagu siin on kirjeldatud samal ESP32 -l, kuid teisel protsessori tuumal, seejärel importige kaamera voog html -sse, mida teenindab 1. server, kasutades…. Selle tulemuseks peaks olema kaamera kiirem värskendamine.
  • Kasutage pääsupunkti (AP) režiimi, et robot oleks iseseisvam, nagu siin selgitatud.
  • Laiendage aku pinge mõõtmise, sügava une võimalustega jne. Praegu on see natuke keeruline, kuna AI-Thinker ESP32-CAM-il pole palju GPIO-sid; vajab laiendamist uarti ja näiteks orja arduino kaudu.
  • Muutke kassi otsivaks robotiks, kes väljastab aeg-ajalt suure nupuvajutusega käpaga kassitoite, voogesitab päeva jooksul palju toredaid kassipilte…

Palun kommenteerige, kui teile meeldis või teil on küsimusi ja aitäh lugemise eest!

Soovitan: