Datenströme

cpb

Mitglied
Es sollen Daten unterschiedlichen Typs (Integers, Strings) zwischen unterschiedlichen Objekten, die zu verschiedenen Projekten gehören, auf eine spezielle Art ausgetauscht werden. Es geht um die Lejos-Firmware, die ermöglicht, Lego-NXT-Bausteine unter Java zu steuern. Um Daten zwischen dem PC und dem NXT-Baustein auszutauschen, gibt es zwei Klassen USBSend und USBReceive, über die Daten zwischen PC und NXT-Baustein ausgetauscht werden können. Und es geht nur über diese beiden Klassen.

Ich möchte in der USBSend-Klasse verschiedene Methoden verwenden, von denen aus dann jeweils Datenströme an USBReceive geschickt und von dort empfangen werden können. Für den Versand eines Zahlenarrays ist das schon erledigt. In USBSend gibt es die Methode usbsend.commitNumbers(n1,n2) und in USBReceive gibt es dien Methode usbreceive.receiveNumbers(), die die Daten verarbeitet.

Das Ziel ist jetzt, einen Selektor zu finden, der ermöglicht, in USBReceive verschiedene Methoden zu wählen, je nach dem, welche Methoden aus USBSend Daten geschickt haben.

In USBReceive gibt es eine main-Methode, innerhalb derer die unterschiedlichen Methoden je nach Selektorenwert gestartet werden sollen. Der Wert des Selektors muss gebunden an die in USBSend aufrufende Methode gewählt werden. Der muss dann über den Datenstrom übertragen werden, so dass er in der main-Methode von USBReceive verarbeitet werden kann.

Wenn das, was ich geschrieben habe, irgendwie verstehbar ist, gibt es jemanden, der eine Idee hat, wie das Problem gelöst werden kann?

Herzliche Grüße
Claus-Peter Becke
 

Tobse

Top Contributor
Du wählst den falschen Ansatz. Die Gegenstelle kann prinzipiell nicht wissen, welche Methode verwendet werden soll. Die einzige Lösung ist ein Protokoll, welches genau vorschreibt, wann welche Methode verwendet wird. Zur Laufzeit kannst du dir dann aber nicht sicher sein, dass du die richtige benutzt (denn das Protokoll musst du selbst entwerfen).

Um ein Protokoll kommst du nicht umher. Du kannst aber ein Todsimples wählen und mit dem Prinzip der Serialisierung arbeiten. Hier ein bisschen Pseudocode als Beispiel

Java:
/* über USB werden "Messages" versendet. Jede "Message" hat einen Typ
   und Nutzdaten, welche abhängig vom Typ formatiert sind.
   Die Typen kannst du frei festlegen, wie du sie brauchst.
*/

enum Type {
   TURN_WHEELS(0), // räder des NXT roboters drehen
   SET_LAMP_STATE(1); // den Zustand einer Lampe ändern

   public final int value;

   Type(int value) { this.value = value; }

   static Type ofValue(byte value) {
       for (Type t : this.values()) {
           if ((byte) t.value == value) { return t; }
       }
       throw new IllegalArgumentException("Unknown type #" + value);
   }
}

/** Nachrichten werden über USB gesendet */
abstract class Message {
    public final Type type;
    public final byte[] payload;
}

class TurnWheelsMessage extends Message {
    public TurnWheelsMessage(byte nUmdrehungen) {
       // hier findet die serialisierung statt: die informationen (= Konstruktorparameter) werden zu einem byte[] formatiert
       this.type = Type.TURN_WHEELS;
       this.payload = new byte[]{ nUmdrehungen };
    }

    // das ist eine Hilfsmethode. Diese Hilfsmethoden, wissen, wo im payload die gesuchte Information zu finden ist
    // der Rest deines Codes benutzt diese Hilfsmethoden und muss sich nicht darum kümmern, die man das byte[] liest
    public byte getNUmdrehungen() {
        return this.payload[0];
    }

    // in dieser methode wird das byte[] wieder eingelesen; damit erreichst du ebenfalls, dass nur in dieser
    // Klasse (und sonst nirgends im Code verstreut) die Logik steht, wie das byte[] für TURN_WHEELS aufgebaut ist
    public static TurnWheelsMessage fromPayload(byte[] payload) {
        if (payload == null || payload.length != 1) throw new IllegalArgumentException();

        this(payload[0]);
    }
}

//.. u.s.w. für jede Message

// Diese klasse kümmert sich ausschließlich um die Zuordnung Typ -> Message
class MessageReader {
    public Message readMessage(Type type, byte[] payload) {
        if (type == Type.TURN_WHEELS) {
            return TurnWheelsMessage.fromPayload(payload);
        }
        else throw new Exception("Unknown message type " + type);
    }
}

// Diese Klasse versendet und empfängt Nachrichten. Das Protokoll ist nun sehr simpel geworden:
// 1 byte - Typ der Nachricht
// 4 byte - länge des payloads
// X byte - der Payload
// Dieses einfache Protokoll entscheidet, wann welche Methode auf deinem USBSender und USBReceiver
// aufgerufen wird. Alle weiteren informationen werden in den Nachtichten-Klassen abgebildet
class USBConnection {
    private MessageReader msgReader;
    private USBSender usbSender;
    private USBReceiver usbReceiver;
  
    public USBConnection(MessageReader msgReader, USBSender usbSender, USBReceiver usbReceiver) {
        this.msgReader = msgReader;
        this.usbSender = usbSender;
        this.usbReceiver = usbReceiver;
    }

    public void send(Message m) {
        // ich kenne die Klasse USBSender nicht - das hier ist nur "Pseudocode"
        this.usbSender.writeByte((byte) m.type);
        this.usbSender.writeInt(m.payload.length);
        this.usbSender.writeByteArray(m.payload);
    }

    public Message receive() {
        // ich kenne die Klasse USBReceiver nicht - das hier ist nur "Pseudocode"
        byte type = this.usbReceiver.readByte();
        int payloadLength = this.usbReceiver.readInt();
        byte[] payload = new byte[payloadLength];
        this.usbReceiver.readBytes(payload); // hier musst du irgendwie payloadLength bytes in das payload array schreiben
        return MessageReader.readMessage(Type.ofValue(type), payload);
    }
}

// diese Klassen kannst du dann verwenden

class Fernsteuerung {
  private USBConnection conn;

  public Fernsteuerung(USBConnection conn) { this.conn = conn; }

  public void dreheRaeder(byte nUmdrehungen) {
    this.conn.send(new TurnWheelsMessage(nUmdrehungen));
  }
}

// und auf der anderen Seite dann (stark vereinfacht):

class CommandReceiver {
  private USBConnection conn;
  private Motor motor;

  public CommandReceiver(USBConnection conn, Motor motor) {
    this.conn = conn;
    this.motor = motor;
  }

  public void doNext() {
    Message m = conn.receive();
    if (Message instanceof TurnWheelsMessage) {
      this.motor.drehe(((TurnWheelsMessage) m).getNUmdrehungen());
    }
    else throw new IllegalStateException("Unknown message of type #" + m.type + " and class " + m.getClass().getSimpleName());
  }
}

Hier sind jetzt noch ein paar Quick+Dirty sachen drin, die man schönder machen kann. Aber die Grundstruktur bietet dir viel Flexibilität und hält die Kommunikation über USB simpel.

P.S.: auf keinen Fall die Serialisierung von Java benutzen (ObjectOutputStream und ObjectInputStream). Die sind zunächst bequem, bringen aber auf Dauer fast nur Probleme mit sich.
 
Zuletzt bearbeitet:

cpb

Mitglied
Vielen Dank für Deine Tipps. Vorerst wird es für mich schwierig sein, Deine Ideen zu verfolgen, da ich alles auf die von der Lejos-Community bereitgestellten Klassen USBSend und USBReceive abgestellt habe.

In der Zwischenzeit hatte ich die Idee, in jeder Daten sendenden Methode zunächst einen String-Selektor zu schicken, der in einem String-Datenstrom verarbeitet wird, um damit die geeignete Empfangsmethode zu selegieren. Dann erst beginnt der Integer-Datenstrom, der die vom NXT-Baustein zu veraerbeitenden Daten enthält.
 

Tobse

Top Contributor
Meine Lösung ist nicht viel anders; diese Information wird in meinem Beispiel vom Type Enum dargestellt.

Wenn du aber an jeder Stelle, wo du mit USB-Daten arbeitest, diese Überprüfung machen musst, verteilst du das Wissen über dein Datenformat auf die ganze Anwendung und hast viel Doppelten Code. Das lässt sich mit einer Struktur wie der obigen verhindern.

Du kannst erstmal deine Idee umsetzen und dann sehen ob du zufrieden damit bist. Unter der Annahme, dass du es nicht bist, kannst du dann meinen Ansatz versuchen (und dann verstehst du ihn vermutlich auch besser).
 

cpb

Mitglied
Hallo Tobse,
ich habe meine Ideen in Code umgesetzt, der erfreulicherweise weitgehend funktioniert. Die Daten, die gestreamt werden sollen, erreichen die Klasse, in der sie verarbeitet werden sollen. Da allerdings tritt ein Problem auf. In der übergebenden Klasse

USBReceive
.
.
addition.setThresholdRed(thresholdRed);
.
.
wird an das addition-object, das die Berechnungen ausführt, der Wert für thresholdRed übergeben. Er kommt an, wie ich durch einen Ausgabetest ermittelt habe.

Er wird dann in der Additions-Klasse in einer while-Schleife weiterverarbeitet.

while (cs.getLightValue() < thresholdRed) {
Motor.A.backward();

Das allerdings funktioniert nicht. Hast Du eine Erklärung?
 

Tobse

Top Contributor
Da fehlen einige infos und code. Das lässt sich so nicht sagen. Ich weiss auch garnicht, was dein Roboter tun soll (von dem bisschen Code sieht es so aus, dass er rückwärts fahren soll, bis ein Lichtsensor einen Rotwert höher thresholdRed liefert?)
 

cpb

Mitglied
Ich versuche, Dir kurz zu erklären, was der Roboter tun soll. Der Roboter simuliert eine Turing-Maschine, die gebaut ist nach einer Bauanleitung, die sich ihrerseits auf ein Modell bezieht, das die holländische Firma CWI (http://www.legoturingmachine.org/lego-turing-machine/) anlässlich des 100. Geburtstags Turings im Jahre 2012 veröffentlicht hat.

Meine GUI enthält textfields zur Eingabe der Zahlen, einen Button zum Start der Berechnung. Leider kann ich keinen Snapshot der GUI posten, da das Bild auf keinem Webserver zur Verfügung steht. Neben den eben genannten Komponenten gehören noch Buttons und Textfields zur Ermittlung und Ausgabe von Licht- und Schwellenwerten dazu, die den Ablauf der Berechnung steuern.

Die Klasse USBReceive übernimmt die Werte über USBSend von der GUI und übergibt sie an die Klasse Addition, innerhalb derer die Berechnung ausgeführt wird. Alle Werte werden ermittelt und auf der GUI angezeigt. Ebenso auf dem NXT Baustein, so dass klar ist, dass die Werte in der Klasse Addition ankommen. Leider aber werden sie nicht in der while-Schleife verarbeitet. Um das genau zu dokumentieren, poste ich den code der beiden Klassen USBReceive und Addition.

USBReceive
Java:
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import lejos.nxt.LCD;
import lejos.nxt.Motor;
import lejos.nxt.comm.USB;
import lejos.nxt.comm.USBConnection;

/**
* Die Klasse USBReceive hat die Aufgabe, den InputStream zu verarbeiten,
* der von der Klasse USBSend ausgeht. Es ist empfehlenswert, die main-
* Methode als alleinige Methode zu benutzen, da dann auf überschauare
* Weise sichergestellt werden kann, dass die In- und OutputStreams solange
* wie benötigt geöffnet verfügbar sind. Es muss darauf geachtet werden,
* dass die Verarbeitung dann im static-Modus erfolgt.  
*/

public class USBReceive {


    public static void main(String [] args) throws IOException {

        LCD.drawString("waiting", 0, 0);

        while (true) {

            USBConnection conn = USB.waitForConnection();
            DataInputStream dIn = conn.openDataInputStream();
            DataOutputStream dOut = conn.openDataOutputStream();
            Brightness brightness = new Brightness();
            Addition addition = new Addition();
           
            int select = 0;
            int red,white,black;

            select = dIn.readInt();

            if (select == 1) {

                int [] data = new int[2];

                for (int i = 0; i < data.length; i++)
                    data[i] = dIn.readInt();

                addition.setNumbers(data[0],data[1]);
                addition.startTM();
            }

            if (select == 2) {
                red = brightness.getValue();
                dOut.writeInt(red);
                dOut.flush();
            }

            if (select == 3) {
                white = brightness.getValue();
                dOut.writeInt(white);
                dOut.flush();
            }

            if (select == 4) {
                black = brightness.getValue();
                dOut.writeInt(black);
                dOut.flush();
            }

            if (select == 5) {
                int thresholdRed;

                int [] data = new int[2];

                for (int i = 1; i < data.length; i++)
                    data[i] = dIn.readInt();

                thresholdRed = data[1];
                LCD.drawInt(thresholdRed, 0, 1);
                addition.setThresholdRed(thresholdRed);
            }

            if (select == 6) {
                int thresholdWhite;
               
                int [] data = new int[2];

                for (int i = 1; i < data.length; i++)
                    data[i] = dIn.readInt();
               
                thresholdWhite = data[1];
                LCD.drawInt(thresholdWhite, 0, 2);
                addition.setThresholdWhite(thresholdWhite);
            }

            if (select == 7) {
                Motor.C.setSpeed(45);
                Motor.C.rotate(-27);
            }

            if (select == 8) {
                Motor.C.setSpeed(45);
                Motor.C.rotate(27);
            }

            if (select == 9) {
                Motor.A.setSpeed(720);
                Motor.A.forward();
            }

            if (select == 10) {
                Motor.A.setSpeed(720);
                Motor.A.backward();
            }

            if (select == 11) {
                Motor.A.stop();
            }

            dOut.close();
            dIn.close();
            conn.close();
        }
    }
}

Die Klasse Addition sieht so aus:

Java:
import lejos.nxt.ColorSensor;
import lejos.nxt.LCD;
import lejos.nxt.Motor;
import lejos.nxt.SensorPort;

/**
* Dem Objekt addition der Klasse Addition werden die Zahlen,
* die über die GUI eingegeben worden sind, übergeben.
* Die Addition ist die unäre. Zunächst wird das Band auf die Startposition
* bewegt. Zwei unäre Zahlen werden gesetzt. Danach erfolgt die Addition beider
* Zahlen. Der Lesekopf beginnt am Ende der Eingabe, liest die Bits, bis eine 0
* gelesen wird. Das Bit wird auf 1 gesetzt. Anschließend läuft das Band,
* bis die Startposition unter dem Lesekopf ist. Danach läuft das Band um 1 Bit
* zurück. Das unter dem Schreibkopf befindliche Bit wird auf 0 gesetzt.
* Die Addition ist abgeschlossen.
*/

public class Addition {

    int number1, number2;
    ColorSensor cs;
    final int SPEEDA=720,SPEEDB=180,SPEEDC=45; // Geschwindigkeiten
    int thresholdRed=0,thresholdWhite=0;
    final int ANGLEC=27, ANGLEB=180;
    final int GREEN=1,Blue=2,RED=3;
    int value;


    public void setNumbers(int i, int j) {
        this.number1=i;
        LCD.drawString("Addn1", 0, 3);
        LCD.drawInt(number1, 6, 3);
        this.number2=j;
        LCD.drawString("Addn2", 0, 4);
        LCD.drawInt(number2, 6, 4);
    }

    public void setThresholdRed(int t) {
        this.thresholdRed = t;
        LCD.drawString("AddtR", 0, 5);
        LCD.drawInt(thresholdRed, 6, 5);
    }

    public void setThresholdWhite(int t) {
        this.thresholdWhite = t;
        LCD.drawString("AddtW", 0, 6);
        LCD.drawInt(thresholdWhite, 6, 6);
    }

    /**
     * Die Startposition für die Eingabe wird erreicht, indem das Band solange bewegt wird,
     * bis der Schwellenwert rot erreicht ist.
     */

    public void startPositionOfTape() {

        cs = new ColorSensor(SensorPort.S3);
        cs.setFloodlight(true);
        cs.setFloodlight(RED);
        Motor.C.setSpeed(SPEEDC);
        Motor.C.rotate(-ANGLEC);       
        Motor.A.setSpeed(SPEEDA);

        while (cs.getLightValue() < thresholdRed) {
            Motor.A.backward();           
        }
        Motor.A.stop();
        Motor.C.rotate(ANGLEC);
        cs.setFloodlight(false);
        Motor.A.rotate(720);
    }

    private void setNumber1() {

        Motor.B.setSpeed(SPEEDB);
        Motor.A.setSpeed(SPEEDA);

        for (int i = 0; i < number1; i++) {
            Motor.B.rotate(ANGLEB);
            Motor.A.rotate(1800);       
        }       
    }

    private void setNumber2() {

        Motor.A.setSpeed(720);   
        Motor.B.setSpeed(SPEEDB);

        for (int i = 0; i < number2; i++) {           
            Motor.A.rotate(1800);
            Motor.B.rotate(ANGLEB);
        }       
    }

    public void add() {

        cs.setFloodlight(true);
        cs.setFloodlight(RED);
        Motor.C.setSpeed(SPEEDC);
        Motor.C.rotate(-ANGLEC);
        Motor.B.setSpeed(SPEEDB);
        Motor.A.setSpeed(SPEEDA);

        while (cs.getLightValue() < thresholdRed) {

            if (cs.getLightValue() < thresholdWhite)
                Motor.A.rotate(-1800);

            else {
                Motor.C.rotate(ANGLEC);
                Motor.B.rotate(ANGLEB);
                Motor.C.rotate(-ANGLEC);
            }
        }
        Motor.A.stop();
        Motor.C.rotate(ANGLEC);
        Motor.A.rotate(1800);
        Motor.B.rotate(-ANGLEB);
    }

    public void startTM() {

        startPositionOfTape();       
        setNumber1();
        setNumber2();
        add();
    }
}

Ich habe keine Erklärung dafür, dass die while-Schleife die Werte threholdRed und thresholdWhite nicht verarbeitet. Beide Attribute sind als fields global deklariert, so dass sie infolgedessen nach der Wertzuweisung durch die Setter-Methoden auch in der while-Schleife bekannt sein sollten.
 

Tobse

Top Contributor
Du musst erstmal herausfinden, welche Werte deine Variablen zu Beginn der Methode add() haben. Debugger benutzen oder direkt in der Methode Debug-Code einbauen. Ich kann nicht erahnen, welche Werte diese Variablen haben sollen und kann deshalb auch noch nicht sagen, was die while-Schleife tatsächlich macht. Wenn du die Werte der Variablen direkt vor der while Schleife postest, kann ich mir anschauen was da schief läuft :)
 

cpb

Mitglied
Ich habe jetzt die drawInt-Methoden vor die add()-Methode gesetzt und die folgenden Ergebnisse erhalten. Die Lichtwerte für die Farben werden korrekt angezeigt und auch die Schwellenwerte werden in USBReceive richtig angegeben. Beim letzten Durchlauf waren es die Werte:
Red: 48
White: 40
Black: 26,
thresholdRed: 44
thresholdWhite: 33.

Vor der add-Methode aber werden für beide Schwellenwerte die Werte 0 ausgegeben.
 

cpb

Mitglied
Hallo Tobse,
ich habe den Fehler beheben können. Ich habe den Fehler gemacht, statt einer Getter- eine Setter-Methode benutzt zu haben. Jetzt, nach Austausch, funktioniert alles wie gewünscht. Hab noch einmal vielen Dank für Dein Interesse und Deine Unterstützung.
 

Neue Themen


Oben