Arduino<->Android Kommunikation via RedFly Wifishield(Problem der Datengroesse)

Hallo,

derzeit bastel ich an einem kleinen Projekt. Mein Ziel ist es z.B: die Temperatur im Raum zu ermitteln und auf dem Smartphone zu verwerten(Statistik). Auch andere Sensoren sollen später ausgewertet werden, allerdings hat das mit meinem derzeitigen Problem noch nichts zu tun und steht auch noch nicht genau fest.

Folgende Bauteile werden verwendet:
Arduino MegaADKhttp://www.watterott.com/de/Arduino-ADK-Rev3
Electronic Brick Starterkithttp://tronixstuff.files.wordpress.com/2010/04/brickbook-word-v1-03.pdf, http://www.watterott.com/de/Electronic-brick-Starter-kit
Redfly WifiShieldhttp://www.watterott.com/de/Arduino-RedFly-Shield
Android Smartphone

Im Bereich der Androidprogrammierung an sich bin ich relativ sicher. Im Bereich C habe ich so meine Probleme(Warscheinlich auch weil ich oft in Javaumgebungen arbeite und selten in C, C++ oder C#).

Um die Kommunikation zwischen Android und Arduino habe ich mich als erstes gekümmert. Hieru habe ich mich an folgendes Beispiel gehalten
http://android-arduino-redfly.npage.de/.
Bisher hat das auch gut geklappt. Nun stehe ich allerdings vor dem Problem das der Temperatursensor Werte zwischen 0 und 1023 zurückgibt. Im von mir verwendeten Beispiel zur Kommunikation werden die Daten als Char vom Arduino zum Android Smartphone gesendet. Der Wertebereich liegt damit leider bei 0 bis 255.

Gut dachte ich mir und habe in die Bibliothek des Redfly Shields geguckt welche anderen Möglichkeiten es zum senden gibt.

    uint8_t socketSend(uint8_t socket, uint8_t *stream, uint16_t size, uint8_t *ip, uint16_t port); //ip+port only for UDP
    uint8_t socketSend(uint8_t socket, char *stream, uint8_t *ip, uint16_t port); //ip+port only for UDP
    uint8_t socketSendPGM(uint8_t socket, PGM_P stream, uint8_t *ip, uint16_t port); //ip+port only for UDP
    uint8_t socketSend(uint8_t socket, uint8_t *stream, uint16_t size); //TCP
    uint8_t socketSend(uint8_t socket, char *stream);                   //TCP
    uint8_t socketSendPGM(uint8_t socket, PGM_P stream);                //TCP
    uint8_t socketSend(uint8_t socket, int value);                      //TCP

Leider hat mir dies nicht soviel gebracht wie erhofft. Ich dachte es gibt evtl auch einfach eine Methode mit größeren Datenmengen, zum Beispiel einem String.

Kann mit jemand helfen größere Datenmengen übertragen zu bekommen?

uint8_t socketSend(uint8_t socket, char *stream);                   //TCP

Das ist doch schon die passende Funktion, um einen C-String zu senden. Ansonsten poste doch mal den Code, der schon ein Byte sendet. der sollte sich einfach anpassen lassen.
Du mußt natürlich dann auch auf der Gegenseite dafür sorgen das die "größeren" Daten entsprechend verarbeitet werden.

Hier mal der Quelltext des Sketches:

#include <RedFly.h>

byte ip[]      = { 192,168,  72, 2 }; //ip from shield 
byte netmask[] = { 255,255,255,  0 }; //netmask
#define UDP_PORT  (1234) //local UDP port on the shield
uint8_t hUDP=0xFF;  //socket handles

//serial format: 9600 Baud, 8N2
void debugout(char *s)  { RedFly.disable(); Serial.print(s);   RedFly.enable(); }
void debugoutln(char *s){ RedFly.disable(); Serial.println(s); RedFly.enable(); }

void setup()
{
  uint8_t ret;
  //init the WiFi module on the shield
  ret = RedFly.init();
  if(ret)
  {
    debugoutln("INIT ERR"); //there are problems with the communication between the Arduino and the RedFly
  }
  else
  {
    //scan for wireless networks (must be run before join command)
    RedFly.scan();

    //join network
    //ret = RedFly.join("WLAN-SSID", "passwort123", INFRASTRUCTURE);
    ret = RedFly.join("Connectify-arduinopc", "12345678", INFRASTRUCTURE);
    if(ret)
    {
      debugoutln("JOIN ERR");
      for(;;); //do nothing forevermore
    }
    else
    {
      //set ip config
      ret = RedFly.begin(ip, 0, 0, netmask);
      if(ret)
      {
        debugoutln("BEGIN ERR");
        RedFly.disconnect();
        for(;;); //do nothing forevermore
      }
    }
  }
  Serial.begin(9600); // open the serial connection at 9600bps
}

void loop()
{
  uint8_t sock;
  uint16_t buf_len, rd, len;
  uint16_t port; //incomming UDP port
  uint8_t ip[4]; //incomming UDP ip
  
  char buffer[3], *ptr;
  int receiveVar1;

  //check if sockets are opened
  if(RedFly.socketClosed(hUDP) || (hUDP == 0xFF))
  {
    hUDP = RedFly.socketListen(PROTO_UDP, UDP_PORT); //open UDP socket
  }

  //get data
  sock    = 0xFF; //0xFF = return data from all open sockets
  ptr     = &buffer[0];
  buf_len = 0;
  
  do
  {
    rd = RedFly.socketRead(&sock, &len, ip, &port, (uint8_t*)ptr, sizeof(buffer)-buf_len);
    if((rd != 0) && (rd != 0xFFFF)) //0xFFFF = connection closed
    {    
      ptr     += rd;
      buf_len += rd;
    }
  }while(len != 0);
  
  //Buffer auswerten: wenn Länge gleich 3 und der Socket nicht geschlossen
  if(buf_len == 3 && (sock != 0xFF) && sock == hUDP)
  {
      //Pointer an Stelle 0 des Buffers
      ptr = &buffer[0];
      
      //erstes Byte des Buffers ist der Modus
      char modus = *(ptr);
      //zweites Byte des Buffers ist der jeweilige Pin
      int pin = (int)*(ptr + 1);
      //drittes Byte des Buffers ist der Zustand
      int state = (int)*(ptr + 2);
      //Wenn der Pin zwischen 4 und 19 liegt
      if(pin>=4 && pin<=19)
      {
        switch(pin)
        {
          case 14: pin = A0; break;
          case 15: pin = A1; break;
          case 16: pin = A2; break;
          case 17: pin = A3; break;
          case 18: pin = A4; break;
          case 19: pin = A5; break;
        }          
        if (*ptr == 'B') //read analog
        { 
          //Pin als Input definieren
          pinMode(pin, INPUT);
          uint8_t val[1];
          //Wert am Pin lesen und in die Variable speichern
          
          //exakte Temperatur
          float temperature;
          temperature = analogRead(pin);
          Serial.println("Exakter Sensorwert: ");
          Serial.println(temperature);
          int B=3975;
          float resistance=(float)(1023-temperature)*10000/temperature; 
          float ctemperature=1/(log(resistance/10000)/B+1/298.15)-273.15;
          Serial.println("Ermittelte Temperatur: ");
          Serial.println(ctemperature);
          
          val[0] = map(analogRead(pin), 0, 1023, 0, 100);
          Serial.println("gemapter Sensorwert: ");
          Serial.println(val[0]);
          delay(100); 
          //Senden des Wertes an die IP, von dem der Eingangs-Buffer empfangen wurde
          RedFly.socketSend(hUDP, val, 1, ip, UDP_PORT);
        }else if (*ptr == 'E') //read digital
        { 
          //Pin als Input definieren
          pinMode(pin, INPUT);
          uint8_t val[1];
          //Wert am Pin lesen und in die Variable speichern
          if(digitalRead(pin)==0){
            val[0] = 0;
          }else{
            val[0] = 1;
          }
          delay(100);
          //Senden des Wertes an die IP und an den Port, von dem der Eingangs-Buffer empfangen wurde
          RedFly.socketSend(hUDP, val, 1, ip, UDP_PORT);
        }else{
          Serial.print("Wrong Command (not available modus): ");
        }
      }else{
        Serial.print("Wrong Command (not available pin): ");
      }
      //Ausgeben des Eingangsbuffers über den Serial Monitor
      Serial.print(modus);
      Serial.print("-");
      Serial.print(pin);
      Serial.print("-");
      Serial.println(state);
    }
}

Und hier noch der Quelltext des Android :

package de.gerding.wert_auslesen_wifi;

import java.lang.reflect.Array;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketTimeoutException;
import java.util.Arrays;

/**
* Klasse, die eine Kommunikation mit einem Arduino + RedFly/WiFly Shield ermöglicht. Sie beinhaltet Methoden zum senden und empfangen
* digitaler und analoger Werte und zusätzlich das Senden eines PWM Signals für die Servo-Steuerung
*
*/

public class ArduinoCommManager 
{
	public ArduinoCommManager() 
	{
		super();
	}
		
	/**
	* Liest den analogen Wert an dem spezifischen Pin des Arduino.
	*
	* @param pin der spezifische Pin, an dem der Zustand gelesen werden soll (14 bis 19 (14-19 = A0-A5))
	* @param address die Adresse des Arduino (bzw. des RedFly/WiFly - Shields)
	* @param port Port des Arduino
	* @return den an dem spezifischen Pin anliegenden Zustand (0 bis 100)
	*/
	public int readAnalogValue(int pin, InetAddress address, int port) throws Exception
	{
		if (pin >= 14 && pin <= 19) 
		{
			String commString = "B" + (char)pin + (char)0;
				
	        DatagramSocket socket = new DatagramSocket(1234); 
	        socket.setSoTimeout(4000);
	        byte[] buf = commString.getBytes("UTF-8");  
	        DatagramPacket packet = new DatagramPacket(buf, buf.length, address, port);  
	        socket.send(packet);
	        
	        byte[] buf_receive = new byte[1];
	        DatagramPacket packet_receive = new DatagramPacket(buf_receive, buf_receive.length);
	        do 
	        {
	        	try
	        	{
	        		socket.receive(packet_receive);
	        	}catch(SocketTimeoutException ex)
	        	{
	        		socket.close();
	        		throw new Exception("Arduino-Board antwortet nicht! Timeout!!");
	        	}
	        } while (!packet_receive.getAddress().equals(address) || packet_receive.getPort() != port);

	        byte[] data = packet_receive.getData();
	        int value = Array.getInt(data, 0);
	        socket.close();
	        return value;
		}else{
			throw new Exception("Ungueltiger Pin!");
		}
	}
	
	/**
	* Liest den digitalen Wert an dem spezifischen Pin des Arduino.
	*
	* @param pin der spezifische Pin, an dem der Zustand gelesen werden soll (4 bis 19 (14-19 = A0-A5))
	* @param address die Adresse des Arduino (bzw. des RedFly/WiFly - Shields)
	* @param port Port des Arduino
	* @return den an dem spezifischen Pin anliegenden Zustand (false=LOW, true=HIGH)
	*/
	public boolean readDigitalValue(int pin, InetAddress address, int port) throws Exception
	{
		if (pin >= 4 && pin <= 19) 
		{
			String commString = "E" + (char)pin + (char)0;
			
	        DatagramSocket socket = new DatagramSocket(1234);
	        socket.setSoTimeout(4000);
	        byte[] buf = commString.getBytes("UTF-8");  
	        DatagramPacket packet = new DatagramPacket(buf, buf.length, address, port);  
	        socket.send(packet);
	        
	        byte[] buf_receive = new byte[1];
	        DatagramPacket packet_receive = new DatagramPacket(buf_receive, buf_receive.length);
	        do 
	        {
	        	try
	        	{
	        		socket.receive(packet_receive);
	        	}catch(SocketTimeoutException ex)
	        	{
	        		socket.close();
	        		throw new Exception("Arduino-Board antwortet nicht! Timeout!!");
	        	}
	        } while (!packet_receive.getAddress().equals(address) || packet_receive.getPort() != port);
	        
	        byte[] data = packet_receive.getData();
	        socket.close();
	        
	        if(Arrays.equals(data,new byte[]{0}))
	        {
	        	return false;
	        }else if(Arrays.equals(data,new byte[]{1}))
	        {
	        	return true;
	        }else{
	        	throw new Exception("Ungueltige Antwort erhalten!");
	        }
		}else{
			throw new Exception("Ungueltiger Pin!");
		}
	}
}

Die folgenden Zeilen Deines Codes zeigen aber, das kein "char" sondern ein uint8_t, also ein Byte gesendet wird.

uint8_t val[1];
...
val[0] = map(analogRead(pin), 0, 1023, 0, 100);
...
RedFly.socketSend(hUDP, val, 1, ip, UDP_PORT);

Am Ende ist es zwar das Gleiche, aber so genau sollte man trotzdem sein.

Da Du UDP verwendest, sollte folgende Funktion interessant sein:

uint8_t socketSend(uint8_t socket, uint8_t *stream, uint16_t size, uint8_t *ip, uint16_t port); //ip+port only for UDP

Damit kannst Du z.B. ein Byte-Array (also eine Menge an Bytes) bei dem die Größe als Parameter übergeben wird, verwenden.
Der Rest ist geschicktes "Casten" der Nutzdaten auf ein "uint8_t*" und das Ermitteln der Größe der Nutzdaten.

uint8_t socketSend(uint8_t socket, uint8_t *stream, uint16_t size, uint8_t *ip, uint16_t port); //ip+port only for UDP

wie müsste ich denn z.B. die zahl 1024 mit dieser methode ans Smartphone senden?

hUDP ist der socket wie vorher auch
die ip und den port auch wie gehabt

die 1024 müsste ja irgendwie in den Stream
danach die groesse des Streams

wie muss so ein Stream aufgebaut sein?

Kann ich auch ganze Strings in diesen Stream packen? wenn ja wie?

Du kannst einen Wert in einen C-String umwandeln und diesen dann senden

char buffer[25];
sprintf(buffer,"%d",1024);
socketSend(socket,buffer,ip,port);

Dabei kommt aber nicht der Wert 1024 auf der Gegenseite an, sondern die Zeichen '1','0','2','4'. Du mußt dann also daraus auf der Gegenseite wieder eine Zahl machen.
Du kannst aber somit mehrere Werte auf einen Schalg senden, wenn Du mit sprintf() mehrere Werte in den Puffer schreibst (der dann ggf. größer sein muss)

Alternativ kannst Du auch das Integer direkt senden:

int value =1024
socketSend(socket,&value,2,ip,port);

&value ist dabei die Adresse an welcher der Wert steht und die 2 steht für die Anzahl an Bytes die gesendet werden müssen (2 bei Integer).

char buffer[25];
sprintf(buffer,"%d",1024);
socketSend(socket,buffer,ip,port);

klappt soweit erstmal, danke.
habs erstmal am serial monitor wieder ausgegeben. aber im speicher an der addresse von buffer[8] z.B: steht noch quatsch der da garnicht hingehoert. Also dachte ich mir ich änder es zu folgendem ab.

int bla = 1024;
char buffer[sizeof(bla)];         
sprintf(buffer,"%d",bla);
Serial.println("");
for(int i = 0; i < sizeof(bla); i++)
{
    Serial.print(buffer[i]);
}
Serial.println("");

die Ausgabe lautet jetzt 10 es fehlen also 24

was kann ich dagegen tun? ich will das der buffer immer nur genauso viele stellen hat wie die eigentliche zahl, so könnte ich ja dann auch beliebige strings in ein chararray packen.

die andere Methode habe ich auch probiert, klappt leider nicht.

int value = 1024;
RedFly.socketSend(hUDP,&value,sizeof(value),ip,UDP_PORT);

ARDUINO_TO_ANDROID_gekuerzt.ino: In function 'void loop()':
ARDUINO_TO_ANDROID_gekuerzt:179: error: no matching function for call to 'REDFLY::socketSend(uint8_t&, int*, unsigned int, uint8_t [4], int)'
C:...\arduino-1.0.2\libraries\RedFly/RedFly.h:120: note: candidates are: uint8_t REDFLY::socketSend(uint8_t, uint8_t*, uint16_t, uint8_t*, uint16_t)
C:...\arduino-1.0.2\libraries\RedFly/RedFly.h:121: note: uint8_t REDFLY::socketSend(uint8_t, char*, uint8_t*, uint16_t)
C:...\arduino-1.0.2\libraries\RedFly/RedFly.h:123: note: uint8_t REDFLY::socketSend(uint8_t, uint8_t*, uint16_t)
C:...\arduino-1.0.2\libraries\RedFly/RedFly.h:124: note: uint8_t REDFLY::socketSend(uint8_t, char*)
C:...\arduino-1.0.2\libraries\RedFly/RedFly.h:126: note: uint8_t REDFLY::socketSend(uint8_t, int)

Micke:

int bla = 1024;

char buffer[sizeof(bla)];        
sprintf(buffer,"%d",bla);
Serial.println("");
for(int i = 0; i < sizeof(bla); i++)
{
   Serial.print(buffer[i]);
}
Serial.println("");

Die Variable "bla" ist vom Typ "int" und somit genau 2 Byte groß.
Mit

char buffer[sizeof(bla)];

definierst Du somit ein Array mit zwei Stellen.

sprintf(buffer,"%d",bla);

benötigt 5 Stellen (wenn in "bla" 1024 steht). Du überschreibst damit drei Bytes im RAM die ev. anderweitig belegt sind.
Du solltest das Array einfach mit der maximalen Größe deklarieren, welche vorkommen kann. Bei einem 16 Bit Integer sind das fünf Stellen plus ev. Vorzeichen. Inklusive abschließendem Nullbyte sind das somit 7 Stellen.

unabhängig von der länge des buffers habe ich noch ein ganz anderes problem.
auf der Smartphone seinte gebe ich die empfangenen bytes wieder aus und erhalte
[B@414fb268

will sagen ich krieg das ganze in java nicht wieder zurück gecastet

Die Bytes die Du bekommst sind nicht die Zahlen-Werte der einzelnen Stellen, sondern die ASCII-Werte der Zeichen.
Also ein (int)1024 wird nicht zu den Byte-Werten 1,0,2,4 sondern zu den Zeichen-Werten 49,48,50,52.

Außerdem darfst Du bei einem C-String nicht das terminierende '\0'-Byter vergessen.

Wenn Du einen Puffer mit 25 Zeichen definierst, wird der einfach aus dem Speicher "rausgebissen", der Inhalt aber nicht auf '0' gesetzt.
Wandelst Du nun den int-Wert 1024 per sprintf() in einen String um, werden da 5! (nicht 4) Zeichen draus. Nämlich { '1','0','2','4','\0'}. Das 5. Zeichen (0-Byte, nicht das Zeichen "0") sagt den entsprechenden C-Funktionen, das an diesem Zeichen der String zuende ist. Das Zeichen wird auch in der Regel nicht mit ausgegeben.
Damit kann hinter dem 5. Zeichen im Puffer immer noch Müll stehen, das interessiert dann nicht.

Das die Funktion

RedFly.socketSend(hUDP,&value,sizeof(value),ip,UDP_PORT);

Fehler wirft, liegt daran, das die Typen nicht passen. Das sollte sich durch geschicktes "Casten" aber regeln lassen. Leider kann ich das zur Zeit hier nicht testen.

Also muss ich gucken das ich die Ascii Codes wieder auslese aus den Byte-Werten. Das werd ivh am Montag direkt recherchieren wenn ich wieder am PC sitze. Wobei die Methodik mit den gecasteten Werten einfacher wirkt oder liege ich da falsch?

Auf der Android-Seite kannst Du aus einem String wieder ein int machen:

Der "Vorteil", die Rückgabewerte als String zu definieren ist, das Du z.B. mehrere Werte in einen String schreiben kannst, die dann z.B. per "," oder ":" getrennt sind.

sprintf(puffer,"%d,%d,%d",1024,255,12345);

Auf der Android-Seite hast Du dann vermutlich recht viele Möglichkeiten das wieder auseinander zu nehmen.
Wenn Du einen Interger-Wert (oder float, oder long oder was auch immer) überträgst, mußt Du aufpassen, das der Datentyp auf der anderen Seite wieder korrekt umgesetzt wird. (Z.B. zwischen Big Endian und Little Endian Systemen -> Byte Reihenfolge vertauscht). Auch haben auf anderen Systemen die Datentypen andere Größen. int z.B. 4 statt 2 bytes usw.
Das macht es dann ggf. sogar schwerer das sinnvoll zu konvertieren.

Das spricht für die zweite methode mit dem &value oder verstehe ich das jetzt falsch?

Micke:
Das spricht für die zweite methode mit dem &value oder verstehe ich das jetzt falsch?

Aehm, ja. Allerdings weil ich mich nicht gut ausgedrückt habe. Der zweite Teil ab

Wenn Du einen Interger-Wert (oder float, oder long oder was auch immer) überträgst, mußt Du aufpasse...

bezog sich nicht auf die String-Methode, sondern auf die &value Methode.
Mario.

Hi,

vielen Dank für deine Hilfe und dein Engagement.

Das Problem ist mit der von dir beschriebenen Lösung

char buffer[25];
sprintf(buffer,"%d",1024);
socketSend(socket,buffer,ip,port);

nun behoben. Auf der Androidseite habe ich das byte array auch wieder in einen String umgewandelt.

Derzeit habe ich trotz dessen noch ein Problem.

char buffer[25];
sprintf(buffer, "%d",temperature);

damit wandle ich ja den integer in diesem Fall in ein char array um. ich benötige aber nach der Zahl ein Endekennzeichen
ohne ende Kennzeichen kommt es wie folgt beim Anroid an:

485????????????????????????????????????????????

ich hätte gerne das das erste Zeichen nach der Zahl ein leerzeichen ist oder so.
geht das irgendwie?

Das wundert mich jetzt aber, denn eigentlich setzt die sprintf() Funktion ein Ende-Zeichen. Nämlich den Wert '\0' als 5. Zeichen. Das bedeutet in C, das hier der String zuende ist.
Was passiert denn, wenn Du den Puffer per Serial.println() ausgiebst?

Serial.println(buffer)

Bekommst Du da auch zusätzliche Zeichen ausgegeben?

ja. da werden auch Zeichen ausgegeben aber eher sinnlose. Habe das Problem jetzt gelöst in dem ich
sprintf(zusenden, "%d",temperature); zu sprintf(zusenden, "%d$",temperature); verändert habe. Jetzt kann ich auf Androidseite wunderbar mit dem wert vor dem $ arbeiten.

Allerdings habe ich es noch nicht geschafft ein Float in das chararray zu packen. Mit nem Integer klappt das via sprintf ja ganz gut.

Gibts sowas auch um Float in chararray zu packen?

sprintf ("%f", myfloat) geht leider nicht bei avrgcc.

such stattdessen mal nach dtostrf

Vielen Dank, damit klappt's. Zwar mit nem Double statt nem Float, aber das ist ja nicht von Nachteil.

Hier nochmal die Lösung falls jemand mit dem gleichen Problem bei der Suche auf dieses Thema stößt.

//DOUBLE TO CHARARRAY  
//der String der rauskommt (bzw das Chararray) hat immer die mit  char __width angegebene Länge,
//vorm Komma(bzw vor der Zahl vorm Kommma) wird mit " " aufgefüllt und nicht vorhandene Nachkommastellen werden zu 0 
//bis die angegebe Anzahl an Nachkommastellen erreicht ist 
double dtemperature;
dtemperature = analogRead(pin);
char dzusenden[25];
//dtostrf(double __val, char __width, char __prec, char *__s) (Double Wert, maximale String länge, maximale nachkommastellen, chararray)
dtostrf(dtemperature,25,8,dzusenden);
*/