SunSPOT:Οδηγίες Χρήσης
Από DistrSys
Πίνακας περιεχομένων |
Προγραμματισμός των SPOTs
Στο κεφάλαιο αυτό θα παρουσιάσουμε τα βασικά στοιχεία που χρειάζεται κάποιος να γνωρίζει για την ανάπτυξη εφαρμογών στην πλατφόρμα SUN SPOT. Θα παρουσιάσουμε χρήσιμα και απαραίτητα εργαλεία για την υλοποίηση και την εγκατάσταση των προγραμμάτων στα SPOTs που προσφέρονται από την SUN στο αναπτυξιακό kit. Επίσης θα αναφερθούμε στην δομή που πρέπει να έχουν οι εφαρμογές και θα δείξουμε πως μπορεί κάποιος να χρησιμοποιήσει τις πρότυπες βιβλιοθήκες του SUN SPOT για να δουλέψει με το radio και τους αισθητήρες του eDEMO Board. Τέλος θα παρουσιάσουμε παράδειγμα εφαρμογής και θα αναλύσουμε τα κύρια στοιχεία του. Ο προγραμματισμός στην πλατφόρμα SUN SPOT [1] γίνεται με την γλώσσα Java. Συγκεκριμένα οι εφαρμογές ακολουθούν τις προδιαγραφές του MIDP(Mobile Information Device Profile) που κτίζεται πάνω στο CLDC και προσθέτει ένα επιπλέον API για εφαρμογές σε embedded συσκευές. Το MIDP χρησιμοποιείται σε πολλά embedded συστήματα και συσκευές όπως για παράδειγμα τα κινητά τηλέφωνα. Τα συγκεκριμένα προγράμματα που ακολουθούν τις παραπάνω προδιαγραφές καλούνται MIDlets και έχουν συγκεκριμένη δομή και περιορισμούς. Τα MIDlets τρέχουν σε μια μικρή Java ME(J2ME) VM που λέγεται Squawk VM. Όπως είχαμε αναφέρει και σε προηγούμενη ενότητα τα SPOT δεν έχουν λειτουργικό σύστημα, αλλά τον ρόλο του OS τον αναλαμβάνει η Squawk VM. Μαζί με τα MIDlet του χρήστη αλλά σε “χαμηλότερο” επίπεδο τρέχουν και μια πλειάδα άλλων εφαρμογών που δεν είναι άμεσα “ορατές” στον χρήστη:
- O bootloader – που είναι υπεύθυνος για την USB σύνδεση, εκκινεί τις εφαρμογές και επικοινωνεί με τα ant scripts του PC που είναι συνδεδεμένο το SPOT.
- bootstrap suite – που περιλαμβάνει τις πρότυπες κλάσεις της Java ME.
- library suite – που περιλαμβάνει την βιβλιοθήκη με τις κλάσεις σχετικές με το SUN SPOT.
Η Squawk VM χρησιμοποιεί ανεξάρτητες περιοχές για εκτέλεση εφαρμογών, τα isolates. Κάθε isolate συνιστά ένα διαφορετικό σύνολο από threads και αντικείμενα που σχετίζονται με αυτά. Το SPOT έχει πάντα ένα master isolate, στο οποίο τρέχουν deamon threads που διαχειρίζονται βασικές λειτουργίες του. Αυτά τα threads είναι τμήμα της βασικής βιβλιοθήκης του SUN SPOT και φροντίζουν για την διαχείριση ενέργειας (απενεργοποιώντας υποσυστήματα που δεν χρησιμοποιούνται), παρακολουθούν την κατάσταση της USB και αποτελούν μέρος της υλοποίησης του radiostack. Τα υπόλοιπα isolate που μπορεί να δημιουργηθούν καλούνται child isolates. Η προκαθορισμένη(default) συμπεριφορά του SPOT είναι οι εφαρμογές του χρήστη να τρέχουν στο master isolate αν και αυτό δεν είναι υποχρεωτικό. Τα threads χρησιμοποιούνται στα MIDlet μέσω της κλάσης Thread, και υπάρχει η δυνατότητα να ρυθμιστεί η προτεραιότητά τους από 1(Thread.MIN_PRIORITY) ως 10(Thread.MAX_PRIORITY). Επίσης υπάρχει η δυνατότητα να δοθούν και υψηλότερες προτεραιότητες όπως οι “προτεραιότητες συστήματος”(system priorities), αυτές όμως αφορούν ορισμένα deamon threads και δεν προορίζονται για χρήση από τα MIDlets. Εδώ πρέπει να αναφέρουμε ότι για την σωστή λειτουργία των threads των βιβλιοθηκών του SPOT, οι προγραμματιστές θα πρέπει να αναθέτουν προτεραιότητα χαμηλότερη από 5(Thread.NORMAL) όταν οι εφαρμογές τους είναι cpu-bounded. Υψηλές προτεραιότητες μπορεί να προκαλέσουν προβλήματα στα threads της SPOT library, όπως την απώλεια broadcast μηνυμάτων.
Στην Java SE μια εφαρμογή θα πρέπει να περιέχει μία main() μέθοδο ή να υλοποιεί το Applet interface αν πρόκειται να εκτελεστεί από έναν browser. Όμως στην Java ME, που υλοποιεί η Squawk VM, κάθε εφαρμογή που υλοποιούμε πρέπει να είναι συμβατή με το πρότυπο MIDlet. Όλες οι εφαρμογές για τα SPOT πρέπει να κληρονομούν(extends) τα στοιχεία της κλάσης MIDlet και να υλοποιούν τις μεθόδους:
- startApp() - Η μέθοδος αυτή καλείται όταν πρόκειται να εκτελεστεί το MIDlet.
- PauseApp() - Η μέθοδος αυτή καλείται όταν πρόκειται να ανασταλεί η εκτέλεση του MIDlet.
- destroyApp() - Η μέθοδος αυτή καλείται όταν το MIDlet τερματίζεται από το σύστημα, όπως σε περιπτώσεις που το isolate που εκτελείται το MIDlet καταστραφεί με την μέθοδο ”Isolate.exit()” είτε τερματίσει η VM με την μέθοδο VM.stopVM() είτε το MIDlet προκαλέσει μια εξαίρεση εκτός της MIDletStateChangeException.
Προαιρετικά μπορεί να υπάρχει μια μέθοδος δημιουργός(constructor) χωρίς ορίσματα. Για τον τερματισμό ενός MIDlet από τον προγραμματιστή πρέπει να χρησιμοποιείται η μέθοδοςnotifyDestroyed(), οι εφαρμογές δεν πρέπει ποτέ να καλούν την μέθοδο System.exit(). Όλες λοιπόν οι εφαρμογές για τα SPOT έχουν την παρακάτω βασική δομή (εισάγουμε όλες τις κλάσεις που χρειαζόμαστε προκειμένου να έχουμε πλήρη λειτουργικότητα):
import com.sun.spot.peripheral.Spot;
import com.sun.spot.sensorboard.EDemoBoard;
import com.sun.spot.sensorboard.peripheral.ITriColorLED;
import com.sun.spot.peripheral.radio.IRadioPolicyManager;
import com.sun.spot.io.j2me.radiostream.*;
import com.sun.spot.io.j2me.radiogram.*;
import com.sun.spot.util.*;
import java.io.*;
import javax.microedition.io.*;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;
public class SunSpotApplication extends MIDlet {
protected void startApp() throws MIDletStateChangeException {
}
protected void pauseApp() {
}
protected void destroyApp(boolean unconditional) throws MIDletStateChangeException {
}
}
Η δομή των φακέλων όταν αναπτύσσουμε μια εφαρμογή για τα SPOT πρέπει να έχουν συγκεκριμένη δομή. Στο φάκελο κάθε εφαρμογής πρέπει να υπάρχουν 2 αρχεία, ένα αρχείο build.xml και ένα build.properties που χρησιμοποιούνται από τα ant scripts για την μετάφραση και την εκτέλεση των MIDlet. Επιπλέον πρέπει να έχουμε και 3 υποφακέλους, έναν με όνομα src που τοποθετούμε τους καταλόγους με τα αρχεία πηγαίου κώδικα και έναν με τον όνομα resources/META-INF που περιγράφεται στην επόμενη παράγραφο. Τέλος αν η εφαρμογή μας χρησιμοποιεί το NetBeans IDE τότε θα υπάρχει και ένας τρίτος φάκελος με όνομα nbproject, με τα περιεχόμενα του NetBeans project. Στο φάκελο resources/META-INF υπάρχει το αρχείο MANIFEST.MF που περιέχει πληροφορίες που χρησιμοποιούνται από την Squawk VM για την εκκίνηση των εφαρμογών. Συγκεκριμένα περιέχει τα όνομα των αρχικών κλάσεων των MIDlets και ορισμένες ιδιότητες αυτών. Επιπλέον o φάκελος μπορεί να περιλαμβάνει αρχεία που ορίζει ο προγραμματιστής και είναι διαθέσιμα στην εφαρμογή όταν εκτελείται. Η δομή ενός τυπικού MANIFEST.MF είναι:
MIDlet-Name: Air Text demo MIDlet-Version: 1.0.0 MIDlet-Vendor: Sun Microsystems Inc MIDlet-1: AirText, , org.sunspotworld.demo.AirTextDemo MicroEdition-Profile: IMP-1.0 MicroEdition-Configuration: CLDC-1.1
Η σύνταξη κάθε γραμμής είναι <property-name>:<space><property-value. Η πιο σημαντική γραμμή για κάθε πρόγραμμα είναι η MIDlet-1:<όρισμα 1>,<όρισμα 2>,<όρισμα 3>. Το πρώτο όρισμα είναι μια περιγραφή της εφαρμογής και το τρίτο όρισμα είναι η κύρια κλάση του MIDlet. Το δεύτερο όρισμα είναι μια εικόνα που σχετίζεται με το MIDlet, αλλά στην παρούσα έκδοση δεν υποστηρίζεται αυτή η επιλογή. Μέσα από την εφαρμογή μπορούμε να διαβάσουμε τις τιμές για τις παραπάνω ιδιότητες χρησιμοποιώντας την μέθοδο <όνομα MIDlet>.getAppProperty("<property-name>"). Όλα τα αρχεία μέσα στον φάκελο resources είναι διαθέσιμα στην εφαρμογή κατά την διάρκεια που εκτελείται, μπορούμε να ανοίξουμε ένα stream εισόδου για να διαβάσουμε τα περιεχόμενά τους μέσω της μεθόδου InputStream is = getClass().getResourceAsStream("/<όνομα αρχείου>").
Βιβλιοθήκες του συστήματος
Στην ενότητα αυτή θα δούμε τα περιεχόμενα και την λειτουργία των βασικών βιβλιοθηκών του SUN SPOT, αυτές είναι: η βιβλιοθήκη συσκευών(device library), h βιβλιοθήκη για ασύρματη επικοινωνία(radio communication library) και τη βιβλιοθήκη για τον έλεγχο του sensor board. Επίσης θα δώσουμε παραδείγματα για το πως μπορούμε να χειριστούμε διαφορετικά υποσυστήματα του SUN SPOT και του sensor board μέσω κλάσεων αυτών των βιβλιοθηκών.
Device Library
H βιβλιοθήκη συσκευών βρίσκεται στο spotlib_device.jar και στο spotlib_common.jar. Ο πηγαίος κώδικας της βιβλιοθήκης(των δυο παραπάνω jar) βρίσκεται στο spolib_source.jar στο φάκελο “Sun\SunSPOT\sdk\src“ του sdk και περιέχει drivers για τις παρακάτω συσκευές:
- On-board LEDs
- Τους διαύλους PIO,USART και τα ρολόγια/μετρητές του επεξεργαστή
- Τον πομποδέκτη CC2420
- Το SPI interface
- Την ενσωματωμένη flash μνήμη
Επίσης περιλαμβάνει κλάσεις για τον χειρισμό του basestation(com.sun.spot.peripheral.basestation), κλάσεις για έλεγχο των SPOT μέσω του basestation, OTA(Over The Air), ένα framework για την επικοινωνία εφαρμογών που εκτελούνται σε διαφορετικά isolates , και ένα handler για το attention κουμπί. Κάθε στοιχείο και υποσύστημα ελέγχεται από τους παραπάνω drivers, ενώ στον προγραμματιστή δίνεται πρόσβαση στα υποσυστήματα μέσω των παρακάτω interfaces:
Κλάσεις που υλοποιούν τα παραπάνω interfaces δημιουργούνται από ένα αντικείμενο της κλάσης Spot που είναι υπεύθυνο για την δημιουργία των drivers και διαχείριση της πρόσβασης σε αυτόν. Για παράδειγμα για να εκλέξουμε το πράσινο LED του Main Board χρησιμοποιούμε τον ακόλουθο κώδικα:
Iled theLed = Spot.getInstance().getGreenLed(); theLed.setOn(); theLed.setOff();
Radio Communication Library
Πρόκειται για την βιβλιοθήκη που είναι υπεύθυνη για την δημιουργία και τον έλεγχο όλων των συνδέσεων και πρωτοκόλλων, όπως radiostream και radiogram. Οι κλάσεις που υλοποιούν τμήματα του radio stack πάνω από το MAC layer βρίσκονται στο multihoplib_rt.jar ο αντίστοιχος πηγαίος κώδικας στο multihoplib_source.jar. H τωρινή έκδοση του SUN SPOT SDK χρησιμοποιεί το GCF(Generic Connection Framework) για την δημιουργία συνδέσεων και την ασύρματη επικοινωνία μεταξύ των SPOTs χρησιμοποιώντας δυο πρωτόκολλα:
- radiostream – Το radiostream παρέχει αξιόπιστη μετάδοση δεδομένων μεταξύ δυο κόμβων, επίσης χρησιμοποιεί buffers για καλλίτερη απόδοση. Το πρωτόκολλο αυτό είναι stream-based.
- Radiogram – Στο radiogram πρωτόκολλο η μεταφορά δεδομένων γίνεται με datagrams, και δεν παρέχει καμία εγγύηση ότι τα πακέτα θα παραλειφθούν σωστά ή ότι θα φτάσουν στον προορισμό με την σειρά που στάλθηκαν. Όταν ένα πακέτο στέλνεται μέσω άλλων κόμβων(περισσότερα του ενός hop), υπάρχει περίπτωση να χαθεί χωρίς να παρέχεται κάποια ειδοποίηση είτε να παραλειφθεί από τον προορισμό περισσότερες από μια φορές είτε να μην φτάσει με την σειρά που στάλθηκε. Ενώ στην περίπτωση που τα datagrams στέλνονται σε γειτονικό κόμβο(ένα hop), η μόνη περίπτωση είναι κάποια να παραλειφθούν περισσότερες από μια φορές.
Παράδειγμα χρήσης του radiostream Για να συνδεθούμε με ένα κόμβο με το πρωτόκολλο radiostream χρησιμοποιούμε τον παρακάτω κώδικα για να “ανοίξουμε” μια σύνδεση:
RadiostreamConnection conn = (RadiostreamConnection)Connector.open("radiostream://:");
To <destinationAddr> είναι η 64-bit διεύθυνση του προορισμού και η <portNo> είναι ένας αριθμός από 1 ως 255 που χαρακτηρίζει μοναδικά την συγκεκριμένη σύνδεση. Ο προγραμματιστής μπορεί να επιλέξει οποιοδήποτε αριθμό port θέλει από την παραπάνω περιοχή εκτός των ports 1 ως 31 που είναι δεσμευμένα για χρήση από το σύστημα. Προκειμένου να μπορούν δυο κόμβοι να χρησιμοποιήσουν ένα radiostream για να επικοινωνήσουν μεταξύ τους πρέπει και οι δυο να ανοίξουν ένα RadiostreamConnection στην την ίδια port και με τις αντίστοιχες διευθύνσεις. Αφού έχουμε ανοίξει μια radiostream σύνδεση μπορούμε να πάρουμε το stream εισόδου και εξόδου αυτός της σύνδεσης για να μεταδώσουμε δεδομένα, για παράδειγμα:
DataInputStream dis = conn.openDataInputStream(); DataOutputStream dos = conn.openDataOutputStream();
Παρακάτω ακολουθεί να απλό παράδειγμα με δυο προγράμματα:
Program 1
RadiostreamConnection conn = (RadiostreamConnection)
Connector.open("radiostream://0014.4F01.0000.0006:100");
DataInputStream dis = conn.openDataInputStream();
DataOutputStream dos = conn.openDataOutputStream();
try
dos.writeUTF("Hello up there");
dos.flush();
System.out.println ("Answer was: " + dis.readUTF());
catch (NoRouteException e)
System.out.println ("No route to 0014.4F01.0000.0006");
finally
dis.close();
dos.close();
conn.close();
Program 2
RadiostreamConnection conn = (RadiostreamConnection)
Connector.open("radiostream://0014.4F01.0000.0007:100");
DataInputStream dis = conn.openDataInputStream();
DataOutputStream dos = conn.openDataOutputStream();
try {
String question = dis.readUTF();
if (question.equals("Hello up there"))
dos.writeUTF("Hello down there");
else
dos.writeUTF("What???");
dos.flush();
catch (NoRouteException e)
System.out.println ("No route to 0014.4F01.0000.0007");
finally
dis.close();
dos.close();
conn.close();
Σε αυτό το παράδειγμα τα δυο προγράμματα ανοίγουν ένα radiostream connection και τα αντίστοιχα stream εισόδου/εξόδου. Μετά το πρόγραμμα 2 αναμένει να λάβει δεδομένα από το stream και το πρόγραμμα 1 στέλνει το μήνυμα "Hello up there". Αν το μήνυμα παραλειφθεί σωστά το πρόγραμμα 2 απαντά “Hello down there” και η απάντηση τυπώνεται στο System.out stream από το πρόγραμμα 1. Τα δεδομένα στέλνονται ασύρματα όποτε γεμίσει το buffer του radiostream, αν θέλουμε να σταλούν τα δεδομένα άμεσα πρέπει να χρησιμοποιήσουμε την εντολή flush(). Επίσης αν το πρόγραμμα 2 ξεκινήσει πριν το 1 υπάρχει περίπτωση να χαθεί το μήνυμα "Hello up there", αφού η αποστολή θα γίνει πριν το πρόγραμμα 1 ζητήσει δεδομένα. Για σίγουροι ότι θα εκτελεστούν με την σωστή σειρά πρέπει να εφαρμόσουμε κάποιο είδος handshake ή να εισάγουμε μια καθυστέρηση πριν πρόγραμμα 2 στείλει το μήνυμα. Σε περίπτωση που δεν μπορεί να βρεθεί μια διαδρομή προς τον προορισμό προκαλείται μια εξαίρεση NoRouteException.
Σε κάθε μετάδοση ενός πακέτου το MAC layer περιμένει την αποστολή ενός ACK(πακέτο επιβεβαίωσης), που δηλώνει την επιτυχή λήψη του πακέτου. Σε περίπτωση που ο τελικός προορισμός βρίσκεται σε απόσταση ένα hop, το MAC-level ack είναι αρκετό για να διασφαλίσουμε την σωστή λήψη. Σε διαφορετική περίπτωση το radiostream θα ζητήσει από τον προορισμό να στείλει ένα πακέτο επιβεβαίωσης πίσω στον αρχικό αποστολέα. Για την βελτίωση της απόδοσης του πρωτοκόλλου όταν στέλνουμε δεδομένα, το output stream δεν περιμένει το ACK από τον προορισμό αλλά επιστρέφει άμεσα. Σε περίπτωση που δεν παραλάβουμε το πακέτο επιβεβαίωσης σε κάποιο προκαθορισμένο χρονικό διάστημα τότε ξαναστέλνεται αυτόματα. Μετά από ένα αριθμό αποτυχημένων προσπαθειών προκαλείται μια εξαίρεση NoMeshLayerAckException στην επόμενη προσπάθεια για αποστολή δεδομένων. Σε αυτή την περίπτωση δεν μπορούμε να ξέρουμε ποσά από τα δεδομένα που έχουμε στείλει έχουν πράγματι φτάσει στον προορισμό. Τέλος μια άλλη εξαίρεση που μπορεί να προκληθεί και στα στα δυο πρωτόκολλα (radiostream radiogram), είναι η ChannelBusyException. Αυτή η εξαίρεση είναι ένδειξη ότι το κανάλι είναι απασχολημένο, δηλαδή ότι μεταδίδουν άλλες συσκευές.
Παράδειγμα χρήσης του radiogram
To πρωτόκολλο radiogram ακολουθεί το μοντέλο client-server, και παρέχει επικοινωνία μεταξύ δυο συσκευών μέσω datagrams.
Για να “ανοίξουμε” μια σύνδεση από την πλευρά του server χρησιμοποιούμε τον ακόλουθο κώδικα:
RadiogramConnection conn = (RadiogramConnection)Connector.open("radiogram://:");
H <portNo> είναι ένας αριθμός από 1 ως 255 που χαρακτηρίζει μοναδικά την συγκεκριμένη σύνδεση. Ο προγραμματιστής μπορεί να επιλέξει οποιοδήποτε αριθμό port θέλει από την παραπάνω περιοχή εκτός των ports 1 ως 31 που είναι δεσμευμένα για χρήση από το σύστημα. Για να “ανοίξουμε” μια σύνδεση από την πλευρά του client χρησιμοποιούμε τον ακόλουθο κώδικα:
RadiogramConnection conn =(RadiogramConnection)Connector.open("radiogram://:");
To <destinationAddr> είναι η 64-bit διεύθυνση του server και η <portNo> είναι η port που έχει ανοίξει το radiogram connection ο server που θέλουμε να συνδεθούμε. Τα δεδομένα μεταξύ του client και του server στέλνονται ως datagrams, προκειμένου να στείλουμε δεδομένα πρέπει να κατασκευάσουμε ένα αντικείμενο datagram από το connection που έχουμε ανοίξει, να γράψουμε σε αυτό τα δεδομένα που θέλουμε και μετά να το στείλουμε στον προορισμό μέσω του connection. Παρακάτω παραθέτουμε ένα παράδειγμα που έχει την ίδια λειτουργία με τα προγράμματα 1 και 2 της προηγούμενης ενότητας:
Client end
RadiogramConnection conn =
(RadiogramConnection)Connector.open("radiogram://0014.4F01.0000.0006:100");
Datagram dg = conn.newDatagram(conn.getMaximumLength());
try {
dg.writeUTF("Hello up there");
conn.send(dg);
conn.receive(dg);
System.out.println ("Received: " + dg.readUTF());
} catch (NoRouteException e) {
System.out.println ("No route to 0014.4F01.0000.0006");
} finally {
conn.close();
}
Server end
RadiogramConnection conn = (RadiogramConnection) Connector.open("radiogram://:100");
Datagram dg = conn.newDatagram(conn.getMaximumLength());
Datagram dgreply = conn.newDatagram(conn.getMaximumLength());
try {
conn.receive(dg);
String question = dg.readUTF();
dgreply.reset(); // reset stream pointer
dgreply.setAddress(dg); // copy reply address from input
if (question.equals("Hello up there")) {
dgreply.writeUTF("Hello down there");
} else {
dgreply.writeUTF("What???");
}
conn.send(dgreply);
} catch (NoRouteException e) {
System.out.println ("No route to " + dgreply.getAddress());
} finally {
conn.close();
}nn.close();
}
Ορισμένα χαρακτηριστικά των datagrams που θα πρέπει να αναφέρουμε είναι:
- Τα datagrams που στέλνονται μέσω ενός datagram connection πρέπει να έχουν κατασκευαστεί από αυτό, χρησιμοποιώντας την μέθοδο newDatagram() του ανοιχτού connection. Δεν μπορούμε να στείλουμε datagrams σε ένα connection, τα οποία έχουν προέλθει από ένα άλλο.
- Ένα datagram connection που έχει ανοιχτεί για μια συγκεκριμένη διεύθυνση δεν μπορεί να χρησιμοποιηθεί για να σταλούν πακέτα σε άλλο προορισμό. Αν προσπαθήσουμε να στείλουμε ένα datagram σε διαφορετικό προορισμό θα προκληθεί εξαίρεση.
- Είναι δυνατό να έχουμε στην ίδια συσκευή να έχουμε ανοικτό ένα server και ένα client connection στην ίδια port. Τα datagrams που λαμβάνονται και προορίζονται για την συγκεκριμένη port θα προωθούνται στο server connection.
- Στην τωρινή υλοποίηση του πρωτοκόλλου στα SPOT όταν ο server κλείσει το connection, τότε κλείνει αυτόματα και το connection του client.
Τα radiograms connections μπορούν να χρησιμοποιηθούν για την αποστολή broadcast πακέτων. Η προκαθορισμένη συμπεριφορά είναι να αποστέλλονται τα broadcast σε ακτίνα 2 hop για να μπορούν να λαμβάνονται από κοντινές συσκευές που όμως βρίσκονται εκτός ακτίνας μετάδοσης. Ο κώδικας για τη αποστολή broadcasts πακέτων είναι ο εξής:
DatagramConnection conn =(DatagramConnection)Connector.open("radiogram://broadcast:");
Για την λήψη broadcasts πακέτων δεν μπορεί να χρησιμοποιηθεί το παραπάνω connection αλλά πρέπει να ορίσουμε ένα client connection ως εξής:
RadiogramConnection conn = (RadiogramConnection)Connector.open("radiogram://:");
Αν θέλουμε να τροποποιήσουμε τον αριθμό των hops που θα μεταβεί το broadcast μήνυμα τότε μπορούμε να χρησιμοποιήσουμε την παρακάτω μέθοδο στο “ανοιχτό” connection θέτοντας το n στον αριθμό των hops που θέλουμε :
((RadiogramConnection)conn).setMaxBroadcastHops(n);
Εδώ πρέπει να προσθέσουμε ότι ο μηχανισμός μετάδοσης broadcast δεν παρέχει καμία εγγύηση για σωστή παραλαβή, αφού δεν χρησιμοποιεί πακέτα επιβεβαίωσης, ούτε πραγματοποιούνται αναμεταδώσεις broadcast πακέτων. Στην περίπτωση που το μέγεθος των broadcast πακέτων είναι μεγάλο, τότε στέλνεται σε τμήματα(fragments) και αυξάνεται η πιθανότητα να συμβεί κάποιο σφάλμα σε τουλάχιστο ένα από αυτά. Το μέγιστο μέγεθος δεδομένων(payload) των broadcast πακέτων που μπορούμε να στείλουμε είναι 1260 bytes, ενώ το payload των πακέτων του 802.15.4 radio είναι περίπου 100 bytes. Στην περίπτωση που στέλνονται πολλά fragments τα SPOTs αντιμετωπίζουν και ένα επιπλέον πρόβλημα που οδηγεί σχεδόν σίγουρα στην απώλεια δεδομένων. Το πρόβλημα είναι ότι η συσκευή που δέχεται τα πακέτα έχει ένα περιορισμό στο πόσο γρήγορα μπορεί να αδειάζει τον packet buffer, κάτι που μπορεί να επιδεινώνεται σε περιπτώσεις που δέκτης έχει μεγάλο αριθμό από ενεργά threads ή τυχαίνει να καλεί συχνά τον gc(garbage collector). Για αυτό τον λόγο προτείνεται να μην στέλνουμε broadcast radiograms με περισσότερα από 200 bytes δεδομένων, δηλαδή το πολύ 2 radio packets για κάθε broadcast και να εισάγουμε μια καθυστέρηση 20ms μεταξύ διαδοχικών broadcast ώστε να προλαβαίνει ο δέκτης να τα παραλαμβάνει.
Sensor Board library
Πρόκειται για την βιβλιοθήκη που περιέχει όλες τις κλάσεις και τα interfaces που χρειαζόμαστε για την διαχείριση των συστημάτων και των αισθητήρων του eDEMO Board. Αυτές βρίσκονται στο αρχείο transducerlib_rt.jar. Σε αυτή την ενότητα θα παρουσιάσουμε συνοπτικά με παραδείγματα πως μπορούμε να χρησιμοποιήσουμε τις κλάσης αυτής της βιβλιοθήκης για να χειριστούμε το επιταχυνσιόμετρο, τα LED, τον αισθητήρα φωτεινότητας, τον αισθητήρα θερμοκρασίας, τα τις ψηφιακές θύρες εισόδου/εξόδου καθώς και τους διακόπτες. Για περισσότερες λεπτομέρειες προτρέπουμε τον αναγνώστη να διαβάσει τα αντιστοιχία τμήματα του javadoc και να μελετήσει τα παραδείγματα που βρίσκονται στον φάκελο “[SpotSDKdirectory]/doc/AppNotes/”.
Eπιταχυνσιόμετρο Το επιταχυνσιόμετρο μετράει την επιτάχυνση και στους τρεις άξονες. Ο Z-άξονας είναι κατακόρυφος σε σχέση με τα board του SPOT. O X-άξονας είναι παράλληλος στην σειρά των LEDs του sensor board, ενώ ο Y-άξονας είναι παράλληλος στην μεγάλη πλευρά του SPOT. Στην παρακάτω εικόνα βλέπουμε τους άξονες στους οποίους μετράει την επιτάχυνση ο αισθητήρας καθώς και την κατεύθυνση(σημειώνεται με +) προς την οποία όταν αυξάνεται η επιτάχυνση ο αισθητήρας δίνει μεγαλύτερες τιμές.
Για να χρησιμοποιήσουμε το επιταχυνσιόμετρο στις εφαρμογές μας, πρώτα δημιουργούμε ένα αντικείμενο από το αντίστοιχο interface της βιβλιοθήκης(IAccelerometer3D):
import com.sun.spot.sensorboard.EDemoBoard; import com.sun.spot.sensorboard.IAccelerometer3d; //.... IAccelerometer3D ourAccel = EDemoBoard.getInstance().getAccelerometer();
Και έπειτα μπορούμε να πάρουμε της τιμές για την επιτάχυνση στους άξονες με τις ακόλουθες μεθόδους:
double x-accel = ourAccel.getAccelX(); double y-accel = ourAccel.getAccelY(); double z-accel = ourAccel.getAccelZ();
Οι τιμές που επιστρέφονται είναι σε μονάδες G(m2/s). Επιπλέον υπάρχει η μέθοδος getAccel() που επιστρέφει την συνισταμένη επιτάχυνση από τους τρεις άξονες. Το interface του επιταχυνσιομέτρου παρέχει μεθόδους για την μέτρηση της επιτάχυνσης σε σχέση με μια ορισμένη προηγμένη τιμή της. Με αυτό τον τρόπο μπορούμε να αφαιρέσουμε από τις μετρήσεις μας τιμές που προτίθενται από την επιτάχυνσης της βαρύτητας.
ourAccel.setRestOffsets(); double z-relative-accel = ourAccel.getRelativeAccelZ();
Μια ακόμα λειτουργία που μας παρέχεται είναι ο υπολογισμός του προσανατολισμού του SPOT σε σχέση με την επιτάχυνσή του. Οι τιμές αυτές είναι σε radians και επιστρέφονται από τις μεθόδους, getTiltX(), getTiltY(), getTiltZ().
LEDs
Υπάρχουν οκτώ LEDs στο sensor board, διατεταγμένα σε μία σειρά ξεκινώντας από το LED1 στην αριστερή πλευρά και καταλήγοντας στο LED8 στην δεξιά. Κάθε LED έχει τρεις ενσωματωμένες πηγές φωτεινότητας, μια πράσινη, μια μπλε και μια κόκκινη. Η ισχύς κάθε πηγής μπορεί να κυμανθεί από 0 ως 255, τιμή 0 σημαίνει ότι η πηγή δεν εκπέμπει ενώ τιμή 255 ότι έχει πλήρη φωτεινότητα.
Παρακάτω παραθέτουμε πως μπορούμε να χρησιμοποιήσουμε τα LED και τον αντίστοιχο κώδικα.
1.Αρχικοποίηση των LED
Import com.sun.spot.sensorboard.EDemoBoard; Import com.sun.spot.sensorboard.ITriColorLED; ///.... ITriColorLED[] ourLEDs = EDemoBoard.getInstance().getLEDs();
2.Ανάθεση χρώματος στα LEDs – Η ανάθεση χρώματος στα LEDs γίνεται χρησιμοποιώντας την μέθοδο setRGB(int red, int green, int blue), κάθε όρισμα μπορεί να πάρει τιμές από 0 ως 255 ανάλογα με το πόσο έντονο θέλουμε να έχει το αντίστοιχο χρώμα στον τελικό συνδυασμό.
//Στα πρώτα δυο LEDs θέτουμε έντονο κόκκινο χρώμα, στα επόμενα δυο //έντονο πράσινο, στα επόμενα δυο έντονο μπλε και στα τελευταία δυο λευκό // έντονο κόκκινο ourLEDs[0].setRGB(255,0,0); ourLEDs[1].setRGB(255,0,0); // έντονο πράσινο ourLEDs[2].setRGB(0,255,0); ourLEDs[3].setRGB(0,255,0); // έντονο μπλε ourLEDs[4].setRBG(0,0,255); ourLEDs[5].setRGB(0,0,255); // λευκό ourLEDs[6].setRGB(255,255,255); ourLEDs[7].setRGB(255,255,255);
3.Ενεργοποίηση των LEDs
4.Απενεργοποίηση των LEDs
Επίσης μπορούμε να εξετάσουμε την κατάσταση των LEDs χρησιμοποιώντας τις παρακάτω μεθόδους isOn(), getRed(),getGreen(), και getBlue().
Κουμπιά – Switches
Το sensor board έχει δυο κουμπιά που διαχειρίζονται από την κλάση Iswitch. Υπάρχουν δυο τρόποι για να δουλέψουμε με τα κουμπιά του sensor board: 1.Ελέγχοντας συνεχώς την κατάσταση των switches(αν πιέζονται ή όχι) χρησιμοποιώντας ένα βρόγχο και τις μεθόδους waitForChange(); και isOpen().
Import com.sun.spot.sensorboard.EDemoBoard;
Import com.sun.spot.sensorboard.ISwitch;
ISwitch[] ourSwitches = EDemoBoard.getInstance().getSwitches();
if(ourSwitches[0].isOpen()){
// Αν ο διακόπτης ήταν ανοιχτός περιμένουμε για να πιεστεί
ourSwitches[0].waitForChange();
}
// Περιμένουμε για απελευθέρωση του διακόπτη
ourSwitches[0].waitForChange();
2.Σε περίπτωση που δεν θέλουμε να αναμένουμε για αναμένουμε για αλλαγή της κατάστασης του διακόπτη μπορούμε να χρησιμοποιήσουμε listeners. Συγκεκριμένα η εφαρμογή μας θα πρέπει να κάνει implements το IswitchListener interface και να περιλαμβάνει τις μεθόδους switchPressed(ISwitch sw) και switchReleased(ISwitch sw). Η πρώτη εκτελείται κάθε φορά που ο διακόπτης πιέζεται ενώ η δεύτερη κάθε φορά που ο διακόπτης απελευθερώνεται. Οι συναρτήσεις αυτές καλούνται με όρισμα τον διακόπτη που άλλαξε κατάσταση. Το παρακάτω παράδειγμα είναι ένα πλήρες MIDlet που τυπώνει “switch 1” όταν πιεστεί ο πρώτος διακόπτης και “switch 2” για τον δεύτερο.
Import com.sun.spot.sensorboard.EDemoBoard;
Import com.sun.spot.sensorboard.ISwitch;
public class BroadcastCount extends MIDlet implements ISwitchListener
protected void startApp() throws MIDletStateChangeException
ISwitch switches[] = EdemoBoard.getInstance().getSwitches();
switches[0].addISwitchListener(this);
switches[1].addISwitchListener(this);
protected void pauseApp()
protected void destroyApp()
public void switchReleased(ISwitch sw)
public void switchPressed(ISwitch sw)
if (sw == switches[0])
System.out.println(“switch 1”);
else
System.out.println(“switch 2”);
Αισθητήρας φωτεινότητας
Ο μετρητής φωτεινότητας διαχειρίζεται από την κλάση IlightSensor του EdemoBoard. Για να πάρουμε μια τιμή για την στιγμιαία ένταση της φωτεινότητας του περιβάλλοντος μπορούμε να χρησιμοποιήσουμε την μέθοδο getalue(), όπως φαίνεται στο παρακάτω παράδειγμα:
Import com.sun.spot.sensorboard.EDemoBoard; Import com.sun.spot.sensorboard.peripheral.ILightSensor; ILightSensor ourLightSensor = EdemoBoard.getInstance().getLightSensor(); int lightSensorReading = ourLightSensor.getValue();
Αν η πηγή που μετράμε δεν έχει σταθερή φωτεινότητα, όπως οι λάμπες φθορισμού, η παραπάνω μέθοδος δεν θα έχει τα αναμενόμενα αποτελέσματα. Αν και στο ανθρώπινο μάτι δεν είναι ορατές οι γρήγορες αλλαγές στην ένταση, ο αισθητήρας θα μας δίνει τιμές για τις γρήγορες διακυμάνσεις. Ένας τρόπος για να ξεπεράσουμε αυτό το πρόβλημα είναι να υπολογίζουμε τον μέσο όρο της φωτεινότητας παίρνοντας ένα ικανοποιητικό αριθμό δειγμάτων. Αυτό γίνεται αυτόματα με την μέθοδο getAverageValue(n) που επιστρέφει τον μέσο όρο n δειγμάτων που λαμβάνονται διαδοχικά χρησιμοποιώντας 1ms καθυστέρηση στις μεταξύ τους μετρήσεις. Η προκαθορισμένη τιμή για τα δείγματα αν δεν δοθεί όρισμα είναι 17. Οι τιμές που επιστρέφουν οι παραπάνω μέθοδοι κυμαίνονται από 0 ως 750, με 0 αναπαριστούμε το απόλυτο σκοτάδι. Στον παρακάτω πίνακα δίνουμε την αντιστοιχία των τιμών που δίνει ο αισθητήρας σε διαφορετικές συνθήκες φωτεινότητας:
Αισθητήρας θερμοκρασίας
Πρόκειται για τον πιο απλό από όλους τους αισθητήρες, η κλάση που σχετίζεται με αυτόν παρέχει μεθόδους για την άμεση λήψη τιμών σε κλίμακα Celsius ή Fahrenheit. Όμως επειδή πρόκειται για έναν ενσωματωμένο αισθητήρα οι τιμές επηρεάζονται από πηγές θερμότητας στο εσωτερικό του SPOT, όπως cpu, κυκλώματα φόρτισης κλπ. Αν χρειαζόμαστε αξιόπιστες μερίσεις της θερμοκρασίας του περιβάλλοντος προτείνεται η χρήση ενός εξωτερικού μετρητή που μπορούμε να συνδέσουμε με το SPOT μέσω των GIO θυρών. Ακολουθεί ένα παράδειγμα μέτρησης θερμοκρασίας:
Import com.sun.spot.sensorboard.EDemoBoard; Import com.sun.spot.sensorboard.io.ITemperatureInput; ITemperatureInput ourTempSensor = EdemoBoard.getADCTemperature(); double celsiusTemp = ourTempSensor.getCelsius(); double fahrenheitTemp= ourTempSensor.getFahrenheit();
Ανάπτυξη πρότυπης εφαρμογής
Σε αυτή την ενότητα θα ασχοληθούμε με την ανάπτυξη μιας απλής εφαρμογής για τα SUN SPOT που θα χρησιμοποιεί το radio για μετάδοση μηνυμάτων, τα LED για ένδειξη παραλαβής μηνυμάτων από άλλα SPOT, και τα switches της συσκευής που θα προκαλούν την μετάδοση μηνυμάτων. Ο σκοπός αυτής της ενότητας είναι η εξοικείωση του αναγνώστη με την δομή των MIDlets και των βασικών κλάσεων της βιβλιοθήκης των SUN SPOT μέσα από ένα πλήρες λειτουργικό παράδειγμα. Η εφαρμογή που θα παρουσιάσουμε, θα χρησιμοποιεί το
Δημιουργία των κατάλληλων φακέλων του project Αν έχουμε εγκαταστήσει το NetBeans IDE μπορούμε να δημιουργήσουμε μια νέα εφαρμογή για τα SUN SPOT από το μενού File -> New Project και επιλέγοντας “Sun SPOT Application” από την κατηγορία “General”. Το NetBeans θα δημιουργήσει τους κατάλληλους φακέλους και αρχεία , και ένα βασικό MIDlet για να ξεκινήσουμε. Αν δεν έχουμε NetBeans μπορούμε να χρησιμοποιήσουμε σαν template το κώδικα που μας δίνεται από το SUN SPOT SDK στον φάκελο “C:\Sun\SunSPOT\Demos\CodeSamples\SunSpotApplicationTemplate”, αντιγράφοντας τον κατάλογο αυτό σε μια άλλη θέση που θα αναπτύξουμε την εφαρμογή μας. Αν επιλέξουμε την δεύτερη επιλογή, θα πρέπει να αλλάξουμε στο αρχείο MANIFEST.MF τις τιμές που σχετίζονται με το όνομα της εφαρμογής μας.
Κώδικας και επεξήγηση της εφαρμογής Στην αρχή περιλαμβάνουμε όλες τις μεταβλητές που είναι αναγκαίες για την εφαρμογή και στιγμιότυπα των κλάσεων που διαχειρίζονται τα LEDs και τα Switchs. Η μεταβλητή color χρησιμοποιείται σαν δείκτης για τον πίνακα colors, και παίρνει τιμές {1,2,3} , αυτές αντιστοιχούν στα χρώματα κόκκινο, πράσινο, μπλε. Η μεταβλητή αυτή δηλώνει το τρέχων χρώμα των LEDs του SPOT. Η μεταβλητή count, αποθηκεύει τον αριθμό που σχηματίζεται στα LEDs(ως δυαδική αναπαράσταση). Τέλος έχουμε μια μεταβλητή tx για τον broadcast connection και άλλη μια xdg για τα datagram πακέτα που θα στέλνουμε.
public class BroadcastCount extends MIDlet implements ISwitchListener
private static final int CHANGE_COLOR = 1;
private static final int CHANGE_COUNT = 2;
private ITriColorLED leds[] = EDemoBoard.getInstance().getLEDs();
private ISwitch switches[] = EDemoBoard.getInstance().getSwitches();
private int count = -1;
private int color = 0;
private LEDColor[] colors = { LEDColor.RED, LEDColor.GREEN, LEDColor.BLUE };
private RadiogramConnection tx = null;
private Radiogram xdg;
Ακολουθεί το κύριο σώμα της εφαρμογής. Στην αρχή θέτουμε τα LEDs στο προκαθορισμένο χρώμα(πράσινο) με την συνάρτηση showColor() , προσθέτουμε listeners για τα δυο switches και ανοίγουμε δυο broadcast connections, ένα tx για την αποστολή μηνυμάτων και ένα rx για την λήψη. Και τα δυο χρησιμοποιούν την port 123. Ακολουθεί ένας ατέρμων βρόγχος στον όποιο περιμένουμε για την λήψη ενός πακέτου. Κάθε πακέτο αποτελείται από 3 integers, ο πρώτος δηλώνει την εντολή(1 για αλλαγή χρώματος των LEDs και 2 για αλλαγή του αριθμού που σχηματίζουν), ο δεύτερος είναι ο αριθμός που πρέπει να θέσουμε στα LEDs, και ο τρίτος το χρώμα. Όταν παραλειφθεί ένα datagram διαβάζουμε αυτές τις τρεις τιμές και στην συνέχεια ανάλογα με την εντολή(αλλαγή χρώματος ή αριθμού), εκτελούμε την αντίστοιχη συνάρτηση(showColor(), showCount()).
protected void startApp() throws MIDletStateChangeException {
System.out.println("Broadcast Counter MIDlet");
showColor(color);
RoutingPolicy rp = new RoutingPolicy(RoutingPolicy.ALWAYS);
RoutingPolicyManager.getInstance().policyHasChanged(rp);
switches[0].addISwitchListener(this);
switches[1].addISwitchListener(this);
try
tx = (RadiogramConnection)Connector.open("radiogram://broadcast:123");
xdg = (Radiogram)tx.newDatagram(20);
RadiogramConnection rx = (RadiogramConnection)Connector.open("radiogram://:123");
Radiogram rdg = (Radiogram)rx.newDatagram(20);
while (true)
try
rx.receive(rdg);
System.out.println("Received packet from " + rdg.getAddress());
int cmd = rdg.readInt();
int newCount = rdg.readInt();
int newColor = rdg.readInt();
if (cmd == CHANGE_COLOR)
System.out.println("Received packet from " + rdg.getAddress());
showColor(newColor);
else
showCount(newCount, newColor);
catch (IOException ex)
System.out.println("Error receiving packet: " + ex);
ex.printStackTrace();
} catch (IOException ex)
System.out.println("Error opening connections: " + ex);
ex.printStackTrace();
protected void pauseApp()
// This will never be called by the Squawk VM
protected void destroyApp(boolean arg0) throws MIDletStateChangeException
// Only called if startApp throws any exception other than MIDletStateChangeException
Στην συνέχεια έχουμε τις συναρτήσεις για αλλαγή χρώματος και αριθμού στα LEDs, που καλούνται όταν παραλαμβάνουμε πακέτα με τις αντίστοιχες εντολές.
private void showCount(int count, int color)
for (int i = 7, bit = 1; i >= 0; i--, bit <<= 1)
if ((count & bit) != 0) {
leds[i].setColor(colors[color]);
leds[i].setOn();
} else {
leds[i].setOff();
private void showColor(int color)
for (int i = 0; i < 8; i++)
leds[i].setColor(colors[color]);
leds[i].setOn();
Τέλος οι παρακάτω δυο συναρτήσεις, υλοποιούν το IswitchListener interface και καλούνται όταν έχουμε κάποια αλλαγή στην κατάσταση του switch του SPOT. Αυτή η εφαρμογή ανταποκρίνεται στο πλήρες “πάτημα” ενός switch, δηλαδή όταν πιέσουμε και στην συνέχεια απελευθερώσουμε ένα διακόπτη. Όταν μια τέτοια ενέργεια ανιχνευθεί το SPOT καλεί την συνάρτηση switchReleased(). Σε αυτήν ελέγχουμε ποιος διακόπτης έχει απελευθερωθεί, αν πρόκειται για τον αριστερό τότε στέλνουμε την εντολή για αλλαγή χρώματος στα γειτονικά SPOT, ενώ σε αντίθετη περίπτωση στέλνουμε εντολή για αλλαγή του αριθμού που δείχνουν τα LEDs.
public void switchReleased(ISwitch sw)
int cmd;
if (sw == switches[0])
cmd = CHANGE_COLOR;
if (++color >= colors.length) { color = 0; }
count = -1;
} else {
cmd = CHANGE_COUNT;
count++;
try
System.out.println("Sending packet to ");
xdg.reset();
xdg.writeInt(cmd);
xdg.writeInt(count);
xdg.writeInt(color);
tx.send(xdg);
} catch (IOException ex)
System.err.println("Error sending packet: " + ex);
ex.printStackTrace();
public void switchPressed(ISwitch sw)







