Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: Python & Arduino kommunikaatio ja datan siirto

Sivun loppuun

Yamato [21.05.2016 13:34:32]

#

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')

Metabolix [21.05.2016 14:10:55]

#

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

Yamato [22.05.2016 11:30:28]

#

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ä.

Yamato [22.05.2016 16:34:57]

#

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;

jlaire [22.05.2016 16:39:22]

#

Hienoa, että toimii! Onko filetosend edelleen tyyppiä char*, vai vaihdoitko sen johonkin toiseen?

Metabolix [22.05.2016 16:49:02]

#

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.

Yamato [22.05.2016 17:34:57]

#

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.

Chiman [23.05.2016 08:53:33]

#

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.

Yamato [23.05.2016 19:55:48]

#

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.

peran [23.05.2016 20:49:41]

#

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ä.

Yamato [24.05.2016 19:54:52]

#

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.

Synomi [25.05.2016 11:49:23]

#

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.
  }
}

Sivun alkuun

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta