Minulla on setuppi, jossa Arduinoon on liitetty SD-kortti ja kortilla on .txt tiedostoja. Yritän tehdä Python käyttöliittymää, jolla voi lukea Arduinon kortin sisällön (tiedostonimet) ja valita niistä PC:lle siirrettävän tiedoston.
Tiedostolistauksen saan jo näkyville ja tiedoston siirto onnistuu, jos kirjoitan suoraan Arduinon koodiin, mikä tiedosto lähetetään pyydettäessä.
Alla olevan koodin pitäisi mahdollistaa Arduinolta luetusta listasta valitun tiedostonimen lähettäminen Arduinoon, jonka jälkeen Arduinon pitäisi lähettää ko. tiedosto PC:lle. Voisiko joku kirjata esim. tapahtumaketjun ranskalaisilla viivoilla mitä dataa PC:n ja Arduinon välillä pitäisi kulkea ja missä muodossa? ASCII, binääri, joku muu..
Tuossa koodissa siirettävän tiedoston nimen muuttuja on filetosend.
Seuraava kysymys liittyy tiedoston siirron hitauteen, mutta ensin voisi yrittää saada kuntoon tuon ensimmäisen ongelman.
Koodista saattoi tippua pois jotain olennaistakin, kun siivoilin siitä pois muihin asioihin liittyvät rivit.
Arduino:
#include <Wire.h> #include "RTClib.h" #include <SPI.h> #include <SD.h> #define startMarkerFiles 253 #define startMarker 254 #define endMarker 255 char *filetosend = 0; byte counterfilename = 0; void setup () { Serial.begin(115200); debugToPC(); } void loop () { serialState(); } //======================================== void serialState(){ if(Serial.available()){ //Serial.println(Serial.read()); byte x = Serial.read(); Serial.write(x); if (x == startMarker){ Serial.write(startMarker); sendfile(); } else if (x == startMarkerFiles){ filesList(); } } } //======================================== void debugToPC(){ Serial.write(startMarkerFiles); Serial.write(startMarker); Serial.write(endMarker); } //======================================== void filesList(){ if (!card.init(SPI_HALF_SPEED, chipSelect)) { //chipSelect Serial.println("initialization failed. Things to check:"); Serial.println("* is a card inserted?"); Serial.println("* is your wiring correct?"); Serial.println("* did you change the chipSelect pin to match your shield or module?"); return; } else { Serial.println("Wiring is correct and a card is present."); } if (!volume.init(card)) { Serial.println("Could not find FAT16/FAT32 partition.\nMake sure you've formatted the card"); return; } Serial.println("\nFiles found on the card (name, date and size in bytes): "); root.openRoot(volume); // list all files in the card with date and size root.ls(LS_R); Serial.write(endMarker); } //======================================== void sendfile(){ *filetosend = Serial.read(); myFile = SD.open(filetosend);//"15051509.TXT" if (myFile) { Serial.println(filetosend);//"15051509.TXT" //Serial.println(filetosend); // read from the file until there's nothing else in it: while (myFile.available()) { Serial.write(myFile.read()); delay (1); } // close the file: myFile.close(); Serial.write(endMarker); } else { // if the file didn't open, print an error: Serial.print("error opening: "); Serial.println(filetosend); Serial.write(endMarker); } }
#================================== #Serial connection to Arduino def openSerial(): global serialStatus try: ser = serial.Serial('COM10', 115200) # Establish the connection on a specific port global ser serialStatus = 1 return except: serialStatus = 2 return def waitForArduino(): # wait until the Arduino sends 'Arduino Ready' - allows time for Arduino reset # it also ensures that any bytes left over from a previous message are discarded global endMarker #then wait until an end marker is received from the Arduino to make sure it is #ready to proceed x = "z" print ('Wait for Arduino') while ord(x) != endMarker: # gets the initial debugMessage x = ser.read() #msg = msg + x #print 'Waiting for endMarker' return #================================== def readfileArduino(): x = 'z' fo = open('datafromarduino.txt', 'a') fo.close() os.remove('datafromarduino.txt') global startMarker, endMarker ser.write(chr(startMarker)) # wait for the start character while ord(x) != startMarker: x = ser.read() print ('Sending filename to Arduino...') selectedArduinoFile ser.write(reguestedFile) #while ord(x) != startMarker: # x = ser.read() print ('Receiving data from Arduino...') while ord(x) != endMarker: x = ser.read() if ord(x) == endMarker: print 'end' break if ord(x) == startMarker: x = ser.read() else: fo = open('datafromarduino.txt', 'a') fo.write(x) fo.close() print ('Data received') ser.close() return #================================== def readfilelistArduino(): global filesInArduino x = 'z' y = 'z' fo = open('fileslistfromarduino.txt', 'w') fo.close() os.remove('fileslistfromarduino.txt') global startMarkerFiles, endMarker ser.write(chr(startMarkerFiles)) # wait for the start character while ord(x) != startMarker: x = ser.read() print ('Receiving data from Arduino...') while ord(x) != endMarker: x = ser.read() if ord(x) == endMarker: print 'end' break if ord(x) == startMarker: x = ser.read() else: fo = open('fileslistfromarduino.txt', 'a') fo.write(x) fo.close() #print filesInArduino print ('Data received') #ser.close() #print open(logfile,'r') with open ('fileslistfromarduino.txt', 'r') as data: for line in data: try: row = [str(x) for x in line.split('.')[:1]] filesInArduino.append(row); #print filesInArduino except ValueError as e: text = e tkMessageBox.showerror('Logfile Error', 'Corrupted Logfile!\n\n' + 'ValueError: ' + str(text)) return return #================================== def closeSerial(): ser.close() top2.destroy() print 'serial closed' #================================== def selectedArduinoFile(): reguestedFile = str(Lb1.get(Lb1.curselection())) global reguestedFile print 'Reguested File: ', reguestedFile reguestedFile = reguestedFile + '.TXT' print reguestedFile #================================== def sendFilename(): selectedArduinoFile() ser.write(reguestedFile) x = ser.read() print x #================================== def arduinomenu(): openSerial() if serialStatus == 1: waitForArduino() top2 = Toplevel() global top2 top2.title('Read Data Logger') readfilelistArduino() filesInArduino.reverse() Lb1 = Listbox(top2) global Lb1 i = 0 for x in filesInArduino: Lb1.insert(i, x[0]) i = i + 1 button1 = Button(top2, text = 'List of Files', command = readfilelistArduino) button2 = Button(top2, text = 'Get File', command = readfileArduino) button3 = Button(top2, text = 'Close serial & exit', command = closeSerial) button4 = Button(top2, text = 'Print current', command = selectedArduinoFile) button5 = Button(top2, text = 'Send filename', command = sendFilename) button1.grid(row=1, column=1) button2.grid(row=1, column=2) button3.grid(row=1, column=3) button4.grid(row=1, column=4) button5.grid(row=1, column=5) Lb1.grid(row=2, column=1) else: tkMessageBox.showerror('No Logger found', 'Couldnt find Data Logger on Port COM10')
Kannattaisi opetella esim. C-ohjelmoinnin perusteet ensin ihan ilman Arduinoa.
Koodissasi on tällainen rivi:
*filetosend = Serial.read();
Tässä luet yhden tavun (yhden merkin) ja sijoitat sen paikkaan, johon filetosend-osoitin osoittaa. Osoittimen arvo on 0, joten on oikeastaan tuurista kiinni, ettei koodisi riko mitään. Kokonaista tiedostonimeä et tuosta saa mitenkään.
Selvästikin sinun pitäisi varata filetosend-tekstille riittävästi tilaa ja lukea koko tiedostonimi. Jostain pitäisi tunnistaa tiedostonimen pituus, joten voisit joko ilmoittaa aluksi pituuden ja sitten nimen tai merkitä nimen loppuun vaikka rivinvaihtomerkin.
Olisi järkevää erottaa toisistaan viestiliikenne ja varsinaiset Arduinoon liittyvät osat, jotta koodia voisi testata ja debugata tietokoneella. Voisit tehdä funktiot send ja recv, joille annettaisiin lähetettävä sisältö, ja debuggausvaiheessa send voisi printata tekstit näytölle ja recv voisi lukea näppäimistöltä. Sama koskee sekä C-koodia että Python-koodia. Kun sitten molemmissa tieto liikkuu oikeassa muodossa, voisi lisätä varsinaiset toiminnot eli todelliset tiedosto-operaatiot valmiin testidatan sijaan.
Yamato kirjoitti:
mitä dataa PC:n ja Arduinon välillä pitäisi kulkea ja missä muodossa? ASCII, binääri, joku muu..
Tekstimuotoista protokollaa on helpompi itse debugata, joten aloittelijana kannattaa varmasti tehdä kaikki tekstinä. Komennot voi hyvin päättää rivinvaihtoon, mitään startMarkerFiles-kikkareita ei kannata tässä käyttää. Yleensä on yksinkertaisinta ilmoittaa ensin, paljonko dataa tulee, ja vastaanottaa sitten sen mukaan, mutta jos tämä on jostain syystä vaikeaa, voi myös käyttää lopetusmerkkejä kuten rivinvaihtoja tai monirivisessä asiassa tuplarivinvaihtoa.
Alla on esimerkki protokollasta, jossa jokainen asia on omalla rivillään (ihmiselle kohtuullisen helppo lukea) ja tekstiä edeltää aina tekstin pituus (tietokoneelle helppo lukea). Eri osapuolet merkitty merkeillä > ja <:
> 8 > FILELIST < 2 < 9 < kalle.txt < 19 < ks-nimen-pituus.txt > 7 > GETFILE > 9 > kalle.txt < 116 < kallen data on 116 tavun mittainen, datassa on rivinvaihto kesken -> < ja toinen rivinvaihto toisen rivin lopussa: -> < > 7 > GETFILE > 5 > a.txt < -1 < 58 < file not found, -1 = virhe, 58 = virheilmoitusrivin pituus
Saman voisi toteuttaa ilman pituustietoja, mutta silloin koodin täytyy sopeutua eri mittaisiin riveihin ja laajentaa puskureita (merkkijonoja ja taulukoita) automaattisesti, mikä ainakin C:ssä tuottaa yleensä lisävaivaa ja lisää riskiä puskurin ylivuotoon (buffer overflow), joka on vaarallinen bugi. Tiedoston koko on käytännössä pakko ilmoittaa silti etukäteen, koska tiedosto voi sisältää minkä tahansa merkin, myös sovitun lopetusmerkin, ja koko tiedonsiirto voi siitä mennä sekaisin. Alla on esimerkki protokollasta tällä menetelmällä:
> FILELIST < kalle.txt < ks-nimen-pituus.txt < > GETFILE > kalle.txt < 116 < kallen data on 116 tavun mittainen, datassa on rivinvaihto kesken -> < ja toinen rivinvaihto toisen rivin lopussa: -> > GETFILE > a.txt < -1 < file not found, -1 = virhe, seuraava rivi on virheilmoitus
Kiitos vinkistä. Pitää alkaa testailemaan.
Varmasti on ihan totta, että perusteet kannattaisi olla ensin hyvin hanskassa. Minulle tämä on lähinnä ajanvietettä ja ainoa motivoiva tekijä on harrastepohjainen tarve sovellukselle, jota olen tekemässä. Ne aloittelijoille tarkoitetut tutoriaalit, joita olen käynyt läpi menevät aika nopeasti tasolle, jossa joka tapauksessa pitäisi olla joku jolta kysyä vinkkiä. Tälläinen itselle kiinnostavan applikaation tekeminen on mielekkäämpää ja sekin koostuu niistä pienistä perusasioista, joihin voi tutustua tekemisen edetessä.
Sain kuin sainkin tämän toimimaan. Arduinoon tein alla olevan koodin lukemaan tiedostonimen muuttujaan filetosend.
void sendfile(){ filetosend = ""; while (fileNameComplete == false){ char inChar = (char)Serial.read(); if (inChar > 0) { if (inChar == ':') { stringComplete = true; } else { filetosend += inChar; } } } fileNameComplete = false;
Hienoa, että toimii! Onko filetosend
edelleen tyyppiä char*
, vai vaihdoitko sen johonkin toiseen?
Aika ihmeellinen koodi. Mahtaako oikeasti olla sama, joka nyt toimii? Tuossahan on ikuinen silmukka, jos fileNameComplete on alussa false, tai ei silmukkaa lainkaan, jos fileNameComplete on alussa true. Lisäksi toki muuttujatyypin on täytynyt vaihtua, kuten jlaire totesi.
filetosend on tyyppiä String
String filetosend = ""; boolean fileNameComplete = false; // whether the filename is complete
Huomasin nyt, että edellisessä postauksessa muuttujan stringComplete kuuluu olla fileNameComplete.
Virhe tuli siitä, että muokkasin nimeä tähän palstalle kopioidessa.
Eli koodi tällä hetkellä on tämän näköinen (tällä kertaa ei muutoksia kopiointivaiheessa...):
void sendfile(){ filetosend = ""; while (fileNameComplete == false){ char inChar = (char)Serial.read(); if (inChar > 0) { if (inChar == ':') { fileNameComplete = true; } else { filetosend += inChar; } } } fileNameComplete = false; // tämä luonnollisesti muuttaa arvon falseksi, jotta koodi toimii oikein myös seuraavalla kutsuntakerralla.
Yamato kirjoitti:
Eli koodi tällä hetkellä on tämän näköinen (tällä kertaa ei muutoksia kopiointivaiheessa...):
...
fileNameComplete on turha muuttuja. Pääset while(true) -silmukasta ulos breakilla kaksoispisteen jälkeen. Voit lisätä selvennykseksi kommentin, että tiedostonimi tuli tällöin valmiiksi.
Tuo break ehdotus on hyvä. Arduinosta onkin jo muisti vähissä.
Olisiko vinkkiä, miten saan tiedostonsiirron nopeaksi ja luotettavaksi? En ole koneella nyt, joten en saa koodia liitettyä. Tällä hetkellä Arduino lukee txt tiedostoa ja tulostaa serial porttiin merkin kerrallaan. Patenttiratkaisussani tiedonlähetys katkeaa 1500 merkin välein 0,2 sekunniksi virheiden välttämiseksi. Jos teen pc:llä jotain muuta samaan aikaan niin testien perusteella tiedosto ei siirry kokonaan tai sisältö menee sekaisin.
Minä käyttäisin ongelmassasi Raspberry Pi:tä. Jos budjetti on tiukka, niin Raspberry Pi Zeroa. Tosin jos kyseessä on vain sulautettujen opiskelua, niin silloin Arduino on ihan perusteltu, tai jos projektissa on niukkuuta virrasta.
Oletan myös, että muistikortille saa kirjoittaa myös järjestelmän, jos ei niin zero on poissa laskuista.
Toisaalta Arduino+Raspberry Pi yhdistelmä on myös harkinnan arvoinen, niin päätietokone vapautuu muuhun käyttöön.
... ja onhan noita minilinuxeja myös muita kuin Raspberry Pi.
Jos nopeutta tiedonsiirtoon tarvitaan, niin mielenkiintoinen vaihtohto lieneen myös I2C-väylä.
Raspberry Pi olisi näköjään valovuoden Arduinoa kehittyneempi. Käyttöliittymä pitäisi heti laittaa mobiiliksi toimimaan tabletilla jne jne. Kamala savotta.
Arduino on valikoitunut tähän projektiin lähinnä siksi, että sellainen oli pöytälaatikossa valmiiksi aiempien kokeilujen vuoksi. Ensimmäisen toimivan sovelluksen tein AVR:llä jo ennen kuin koko Arduinoa oli edes olemassa. Ohjelmointia varten piti tehdä kaapeli ja alusta itse. Ohjelmointi winavr:llä.
Kerkiää vain aina näiden projektien välillä unohtaa vähäisenkin opitun asian.
Yamato kirjoitti:
while (myFile.available()) { Serial.write(myFile.read()); delay (1); }
Jos nopeutta hakee, niin tuosta varmaan kannattaisi ottaa delay pois ja laittaa aikakatkaisu(tai vielä jokin luotettavampi kuten tiedoston pituuden perusteella), jos siihen kerta jonkinlainen viive pitää laittaa kortilta lukua varten.
Mut yksinkertaisen aikakatkaisun tuohon saa esim:
int maxLoadTime = 1000; //millisekunteja unsigned long loadStartMillis = millis(); //palauttaa kuin pitkään arduino on ollut päällä millisekunneissa. while (millis() < loadStartMillis + maxLoadTime) { if (myFile.available()) { Serial.write(myFile.read()); loadStartMillis = millis(); //jos dataa löyty, niin nollaa aikakatkaisu. } }
Aihe on jo aika vanha, joten et voi enää vastata siihen.