Απομακρυσμένος Έλεγχος Μικροελεγκτή Arduino μέσω Διασύνδεσης Web

Μέγεθος: px
Εμφάνιση ξεκινά από τη σελίδα:

Download "Απομακρυσμένος Έλεγχος Μικροελεγκτή Arduino μέσω Διασύνδεσης Web"

Transcript

1 Πτυχιακή Εργασία HOU-CS-UGP Απομακρυσμένος Έλεγχος Μικροελεγκτή Arduino μέσω Διασύνδεσης Web ΙΩΑΝΝΗΣ ΤΣΑΚΙΡΗΣ Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

2 ΕΑΠ, 2013 Η παρούσα διατριβή, η οποία εκπονήθηκε στα πλαίσια της ΘΕ ΠΛΗ40, και τα λοιπά αποτελέσματα της αντίστοιχης Πτυχιακής Εργασίας (ΠΕ) αποτελούν συνιδιοκτησία του ΕΑΠ και του φοιτητή, ο καθένας από τους οποίους έχει το δικαίωμα ανεξάρτητης χρήσης και αναπαραγωγής τους (στο σύνολο ή τμηματικά) για διδακτικούς και ερευνητικούς σκοπούς, σε κάθε περίπτωση αναφέροντας τον τίτλο και το συγγραφέα και το ΕΑΠ όπου εκπονήθηκε η ΠΕ καθώς και τον επιβλέποντα και την επιτροπή κρίσης. 2 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

3 Απομακρυσμένος Έλεγχος Μικροελεγκτή Arduino μέσω Διασύνδεσης Web ΙΩΑΝΝΗΣ ΤΣΑΚΙΡΗΣ Αθανάσιος Στουραϊτης Ανδρέας Φλώρος Κυριάκος Σγάρμπας Πανεπιστήμιο Πατρών Ιόνιο Πανεπιστήμιο Πανεπιστήμιο Πατρών Επιβλέπων Μέλος επιτροπής Μέλος επιτροπής Περίληψη: Η εργασία αφορά στην ανάπτυξη πρωτοτύπου βασισμένου στην πλακέτα μικροελεγκτή Arduino Uno, το οποίο θα ελέγχεται εξ αποστάσεως μέσω Web/REST interface. Το μεγαλύτερο μέρος της εργασίας επικεντρώνεται στο σχεδιασμό και την υλοποίηση (σε C++) του λογισμικού το οποίο εκτελεί η μονάδα, το οποίο ουσιαστικά αποτελείται από έναν εξυπηρετητή Παγκόσμιου Ιστού (web server) που μεταφράζει τα εισερχόμενα REST αιτήματα στις αντίστοιχες λειτουργίες του μικροελεγκτή. Επιπλέον, ως απόδειξη της ιδέας (proof of concept), κατασκευάζονται δύο παραδείγματα χρήσης της τηλεχειριζόμενης μονάδας: μια web εφαρμογή σε JavaScript η οποία ελέγχει μια δοκιμαστική κονσόλα, και μια παραθυρική εφαρμογή σε γλώσσα Java με την οποία ελέγχονται οι «συσκευές» μιας εικονικής κατοικίας-μακέτας. Στο τελευταίο μέρος, η εργασία πραγματεύεται μερικά πιο προχωρημένα ζητήματα, όπως: θέματα ασφάλειας, αξιοποίησης των διαθέσιμων μέσων αποθήκευσης, και οφέλη υλοποίησης του μοντέλου παραρητή. Λέξεις-κλειδιά: μικροελεγκτής, Arduino, REST API, απομακρυσμένος έλεγχος. Περιεχόμενο: κείμενο, εικόνες, προγράμματα σε γλώσσα C++, Java και ECMAScript (JavaScript), πρωτότυπα εφαρμογών. Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

4 Remote Control of Arduino Microcontroller via Web Interface IOANNIS TSAKIRIS Athanasios Stouraitis Andreas Floros Kyriakos Sgarbas University of Patras Ionian University University of Patras Supervisor Committee member Committee member Abstract: This work is about the development of a prototype based on the Arduino Uno microcontroller board, that will remotely controlled through a Web/REST interface. The majority of the work concentrates on the design and implementation (in C++) of the software being executed by the unit, which essentially consists of world wide web server that translates incoming REST requests into the corresponding functions of the microcontroller. Furthermore, as a proof of concept, there are two usage examples of the remotely controlled unit being built: a web application in JavaScript that controls a debugging console, and a window application in Java that controls the appliances of a virtual house-minitature. In the last part, there are some more advanced matters being discussed, like: security issues, utilization of the available storage media, and the benefits of implementing an observer-subscriber pattern. Key-words: microcontroller, Arduino, REST API, remote control. Content: text, images, programs written in C++, Java and ECMAscript (JavaScript), application prototypes. 4 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

5 Ευχαριστίες Θα ήθελα να ευχαριστήσω θερμά... Τον κύριο Αθανάσιο Στουραϊτη, επιβλέποντα στην διπλωματική εργασία και καθηγητή μου στην ΠΛΗ21, καθώς και τους κυρίους Ανδρέα Φλώρο και Κυριάκο Σγάρμπα, για την πολύτιμη καθοδήγηση και συμβολή τους στην επιτυχία της εργασίας. Τους υπόλοιπους καθηγητές που είχα κατά την πορεία μου στο ΕΑΠ: Ανδρέα Νεάρχου, Κλειώ Σγουροπούλου, Στέλιο Κώτσιο, Γιώργο Ποταμιά, Γιώργο Τσούλο, Αχιλλέα Καμέα, Ηλία Σταυρόπουλο, Δημήτρη Καλλέ, Δημήτρη Γκρίτζαλη και Δημήτρη Λυμπερόπουλο, για το αμέριστο ενδιαφέρον τους. Τον κύριο Σπύρο Λυκοθανάση και την κυρία Σπυριδούλα Κούνα για την άμεση απόκρισή τους σε οποιοδήποτε ζήτημα τους είχα απασχολήσει κατά τη διάρκεια της διατριβής μου. Τέλος, θα ήθελα να ευχαριστήσω ιδιαιτέρως... Τον παιδικό μου φίλο Νίκο Στιβακτάκη, ο οποίος με «μύησε» στον κόσμο του Arduino, για το ατέλειωτο ενδιαφέρον του και τις ανεκτίμητες συμβουλές που μου έδωσε καθ όλη την πορεία της εργασίας, της άπειρες ώρες συζητήσεων που μου αφιέρωσε και τα αμέτρητα s που ανταλλάξαμε. Την γυναίκα μου Παναγιώτα Σαρίκα για την συμπαράστασή της όλα αυτά τα χρόνια σπουδών μου, αλλά και για την πολύτιμη βοήθειά της στην επιλογή και στο «χτίσιμο» της εικονικής κατοικίας μακέτας. Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

6 6 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

7 στους γονείς μου Μιχάλη και Αικατερίνη Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

8 8 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

9 ΠΕΡΙΕΧΟΜΕΝΑ 1. Εισαγωγή Ανάλυση Γενικά περί Arduino Απομακρυσμένος Έλεγχος Επιλογή του REST ως μοντέλου Υφιστάμενη Κατάσταση Μεθοδολογία επίλυσης του προβλήματος Εργαλεία που θα χρησιμοποιηθούν Διάρθρωση της εργασίας Σχεδιασμός Μονάδα Arduino Υλικό Ενδοεπικοινωνία Σενάρια χρήσης REST API Λειτουργίες που θα υλοποιηθούν Επιλογή μεθόδου HTTP Μορφή αιτημάτων (request format) Μορφή απαντήσεων (response format) Συμβάσεις Ονοματολογία Τύποι αιτημάτων Εντολές Αναφορά σφαλμάτων (error reporting) Λογισμικό web server Γενικά Λειτουργία web server Ανάλυση Σχεδιασμός Πλακέτα Dashboard Control Panel JavaScript Web GUI Περιγραφή της εφαρμογής Σχέδιο υλοποίησης Βιβλιοθήκη Java Εικονική κατοικία-μακέτα Java GUI ελέγχου εικονικής κατοικίας-μακέτας Περιγραφή της εφαρμογής Λειτουργικότητα Σχέδιο υλοποίησης Υλοποίηση Πλακέτα Ελέγχου "Dashboard" Επιλογή πλακέτας ανάπτυξης Επιλογή ηλεκτρονικών εξαρτημάτων Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

10 3.1.3 Τοποθέτηση εξαρτημάτων - Συναρμολόγηση Σύνδεση με Arduino Χρήση πλακέτας Μονάδα Arduino Συναρμολόγηση Υλικού Λογισμικό ανάπτυξης Ανάπτυξη λογισμικού Βιβλιοθήκες Αρχικοποίηση Κύριος Βρόγχος (main loop) Κλάσεις Μοντέλου (model classes) Διεκπεραίωση σύνδεσης-πελάτη Λεκτική ανάλυση αιτήματος Έλεγχος καλής λειτουργίας Εφαρμογή JavaScript "Control Panel" Σημείο εισόδου Αρχικοποίηση Διαχείριση συμβάντων Περιοδική ανάγνωση εισόδων Εικονική Κατοικία Μακέτα Επιλογή μοντέλου Κατασκευή κατοικίας μινιατούρας Κύκλωμα εικονικών συσκευών Ολοκλήρωση Σερβομηχανισμός εξώπορτας Κουδούνι εξώπορτας Επιλογή θέσης πλακέτας Εσωτερικές καλωδιώσεις Σύνδεση με Arduino Java Βιβλιοθήκη (API) Μοντέλο εικονικής κατοικίας Εφαρμογή GUI Φόρμα παραθύρου εφαρμογής Σύνδεση με κλάση μοντέλου Διαχειριστές γεγονότων (event handlers) Περιοδική ανάγνωση συσκευών Ολοκλήρωση Θέματα Ασφάλειας Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

11 4.1.1 Αδυναμίες Πιστοποίηση Πρόσβασης Ασφαλές πρωτόκολλο HTTP Εικονικά Ιδιωτικά Δίκτυα Περιφερειακά Μέσα Αποθήκευσης Μνήμη EEPROM Κάρτα Micro-SD Εφαρμογές Αρχείο Ρυθμίσεων Παραμένουσα Κατάσταση Αρχείο Ημερολογίου Στατικό περιεχόμενο Μοντέλο Παρατηρητή Τεχνική Polling μειονεκτήματα Μοντέλο παρατηρητή πλεονεκτήματα Προσθήκη μοντέλου στο web server Υποστήριξη από το Java API Java GUI ελέγχου κατοικίας-μακέτας Συμπεράσματα Ικανοποίηση στόχου Προβλήματα Δυσκολίες Περιορισμοί μνήμης Ανάπτυξη / Αποσφαλμάτωση Ποιότητα βιβλιοθηκών Εντυπώσεις ΠΑΡΑΡΤΗΜΑ Α Κώδικας Arduino REST Webserver ΠΑΡΑΡΤΗΜΑ B Κώδικας Εφαρμογής Control Panel ΠΑΡΑΡΤΗΜΑ Γ Κώδικας Βιβλιοθήκης Java ΠΑΡΑΡΤΗΜΑ Δ Κώδικας Εφαρμογής Java ΠΑΡΑΡΤΗΜΑ E Σχέδια Κυκλωμάτων Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

12 12 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

13 1. Εισαγωγή Ανάλυση 1.1 Γενικά περί Arduino Το Arduino είναι ένας «ανοικτός» (open-source) μικροελεγκτής μιας πλακέτας (single-board) που βασίζεται στους μικροελεγκτές AVR της Atmel. Μια πλακέτα Arduino έχει πολύ λιτή σχεδίαση και αποτελείται από έναν 8-μπιτο AVR μικροελεγκτή μαζί με κάποια βοηθητικά εξαρτήματα, όπως ρυθμιστή τάσης, κρυσταλλικό ταλαντωτή, σειριακή ή USB θύρα κλπ. καθώς και ακροδέκτες για τις εισόδους και εξόδους του μικροελεγκτή [5]. Εικόνα 1: Μια από τις πρώτες πλακέτες Arduino που κυκλοφόρησαν. Διακρίνεται η σειριακή σύνδεση RS-232. Αξίζει να αναφερθεί ο τυποποιημένος τρόπος με τον οποίο εκτίθενται οι διάφορες υποδοχές πάνω σε μια πλακέτα Arduino, ο οποίος επιτρέπει την συνδεσιμότητα της κύριας πλακέτας με ένα πλήθος ανταλλάξιμων καρτών επέκτασης, ονομαζόμενες και ως «ασπίδες» (shields), όπως Ethernet, GPS, Wireless, LCD display, κλπ. Εικόνα 2: Arduino με δύο shields τοποθετημένες πάνω του Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

14 Ένα σημαντικό χαρακτηριστικό του Arduino είναι ότι ο μικροελεγκτής διαθέτει έναν προεγκατεστημένο boot loader, ώστε να μην απαιτείται εξωτερικός προγραμματιστής. Το υλικό του Arduino προγραμματίζεται με μια γλώσσα προγραμματισμού που βασίζεται στην Wiring παρόμοια με την C++, και μέσω ενός περιβάλλοντος ανάπτυξης (IDE) που βασίζεται στο Processing [94]. Εικόνα 3: Η έκδοση του περιβάλλοντος ανάπτυξης (IDE) του Arduino Τα προγράμματα του Arduino για να μπορέσουν να μεταγλωττιστούν και να τρέξουν, οφείλουν να ορίζουν τις δύο παρακάτω ειδικές μεθόδους: setup() εκτελείται μόνο μία φορά στην αρχή (για αρχικοποίηση) loop() εκτελείται διαδοχικά (κύριος βρόχος) Εικόνα 4: Η βασική ροής εκτέλεσης ενός προγράμματος για Arduino 14 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

15 Ο Arduino καθιστά εύκολη την ανάπτυξη εφαρμογών πληροφορικής που αλληλεπιδρούν με το φυσικό κόσμο (physical computing) [95]. Μέσω των εισόδων και εξόδων του μπορεί να αλληλεπιδρά με το περιβάλλον του, μετατρέποντας ηλεκτρικά σήματα (τάση) σε ψηφιακές αριθμητικές τιμές, και το αντίστροφο. Εικόνα 5: Ψηφιακή εγγραφή (έξοδος) Οι ακροδέκτες του Arduino μπορούν να προγραμματιστούν κατά το χρόνο εκτέλεσης (runtime) ώστε να λειτουργούν είτε ως είσοδοι είτε ως έξοδοι. Εικόνα 6: Ψηφιακή ανάγνωση (είσοδος) Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

16 Ένα μέρος των εισόδων του Arduino είναι αναλογικές, είναι εφοδιασμένες δηλαδή με έναν αναλογικό-ψηφιακό μετατροπέα (ADC), ενώ κάποιες έξοδοί του μπορούν να λειτουργήσουν και ως αναλογικές, με χρήση διαμόρφωσης πλάτους παλμού (Pulse Width Modulation PWM). Τα κορυφαία μοντέλα προσφέρουν επιπλέον και «καθαρόαιμες» αναλογικές εξόδους (ψηφιακό-σε-αναλογικό μετατροπείς DACs). Ο συγκεκριμένος αριθμός εισόδων-εξόδων, η ταχύτητα (CPU clock), καθώς και οι διαθέσιμοι πόροι, όπως η χωρητικότητα των μνημών RAM, EEPROM και Flash, διαφέρουν από μοντέλο σε μοντέλο. Η επιλογή του κατάλληλου μοντέλου καθορίζεται από τις ανάγκες τις εκάστοτε εφαρμογής [6]. Arduino Επεξεργαστής Flash EEPROM RAM είσ./έξ. Έξ. PWM Αναλ. είσοδοι Διαστάσεις ADK ATmega KB 4KB 8KB mm Diecimila ATmega168 16KB 0.5KB 1KB mm Due AT91SAM3X8E 512KB 0KB 96KB mm Duemilanove ATmega168/328P 16/32KB 0.5/1KB 1/2KB mm Fio ATmega328P 32KB 1KB 2KB mm 27.9mm Leonardo Atmega32u4 32KB 1KB 2KB mm LilyPad ATmega168V/328V 16KB 0.5KB 1KB mm Ø Mega ATmega KB 4KB 8KB mm Mega2560 ATmega KB 4KB 8KB mm Nano ATmega168/328 16/32KB 0.5/1KB 1/2KB mm Uno ATmega328P 32KB 1KB 2KB mm Πίνακας 1: Τα κύρια χαρακτηριστικά μερικών εκ των διαθέσιμων πλακετών Arduino 16 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

17 1.2 Απομακρυσμένος Έλεγχος Στις περισσότερες εφαρμογές ο Arduino χρησιμοποιείται σαν ένας «έξυπνος» ελεγκτής που παρακολουθεί το περιβάλλον του μέσω διαφόρων αισθητήρων, και αντιδρά κατάλληλα όταν χρειαστεί, βάσει ενός προκαθορισμένου προγράμματος το οποίο εκτελεί. Αυτό επαρκεί για συγκεκριμένες εφαρμογές αυτοματισμού που δεν απαιτούν εποπτεία ή τη φυσική μας παρουσία στον υπό έλεγχο χώρο. Σε περιπτώσεις όμως που δεν υπάρχει κάποιο προκαθορισμένο σχέδιο δράσης ή που δεν είναι παρών κάποιος χρήστης, είναι απαραίτητη η εξ αποστάσεως επικοινωνία και έλεγχος του μικροελεγκτή. Για παράδειγμα, θα μπορούσε κάποιος να επιθυμεί τη λήψη ειδοποιήσεων στο κινητό του μόλις ενεργοποιηθεί κάποιος συναγερμός ή κάποιος αισθητήρας ανίχνευσης καπνού στο διαμέρισμά του. Αντίστοιχα, κάποιος άλλος θα επιθυμούσε να μπορεί να ανοίγει την πόρτα του γκαράζ του καθώς βρίσκεται καθοδόν λίγο πριν φτάσει στο σπίτι του. Για τέτοιου είδους σενάρια είναι προφανές ότι πρέπει να καταφύγουμε σε κάποιου είδους εξ αποστάσεως έλεγχο του Arduino. Εάν επιθυμούμε τηλεχειρισμό πραγματικά μεγάλης κλίμακας είναι πολύ βολικό να χρησιμοποιήσουμε το Διαδίκτυο ως μέσο επικοινωνίας, αφού αυτό έχει διεισδύσει κυριολεκτικά σε κάθε σπίτι ή χώρο εργασίας, ενώ στις μέρες μας οι περισσότεροι άνθρωποι που ακολουθούν την τεχνολογία έχουν σχεδόν πάντα μαζί τους κάποια φορητή συσκευή (τηλέφωνο, tablet, υπολογιστή laptop, κλπ) συνδεδεμένη με το Διαδίκτυο, η οποία μπορεί να παίξει το ρόλο του «τηλεχειριστήριου». Η τεχνολογία για τη σύνδεση του Arduino με το Διαδίκτυο έχει ήδη αναπτυχθεί και είναι διαθέσιμη προς χρήση. «Έλεγχος» του Arduino μπορεί να σημαίνει οτιδήποτε και ο καθένας μπορεί να τον εκλάβει διαφορετικά. Είναι σκόπιμο λοιπόν να καθορίσουμε σαφώς τι ακριβώς θα εννοούμε από εδώ και στο εξής. Εφόσον δεν έχουν δοθεί συγκεκριμένες απαιτήσεις (requirements), είναι λογικό να σχεδιάσουμε το σύστημά μας έτσι ώστε να είναι «γενικής χρήσης», να παρέχει δηλαδή στον χρήστη του κάποια στοιχειώδη εργαλεία μέσα από τα οποία εκείνος θα μπορεί να χτίσει μια πιο σύνθετη λογική. Για να το πετύχουμε αυτό, αρκεί να κρατήσουμε τη διασύνδεση (interface) του συστήματός τηλεχειρισμού σε όσο πιο «χαμηλό επίπεδο» γίνεται, δηλαδή όσο γίνεται πιο Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

18 κοντά στις υποτυπώδεις λειτουργίες του Arduino, οι οποίες δεν είναι άλλες από την απλή ανάγνωση και εγγραφή τιμών σε ακροδέκτες, καθώς φυσικά και ο καθορισμός λειτουργίας ενός ακροδέκτης ως είσοδος ή έξοδος. Ο έλεγχος σερβομηχανισμών είναι επίσης πολύ εύκολο να υποστηριχθεί μιας και ο προγραμματισμός του είναι εξαιρετικά απλός. 1.3 Επιλογή του REST ως μοντέλου Προηγουμένως αναφέραμε ότι η επικοινωνία μεταξύ του χρήστη και του Arduino θα γίνεται μέσω Διαδικτύου. Αυτό φυσικά από μόνο του είναι ασαφές και προκύπτει εύλογα η ανάγκη καθορισμού του ακριβούς πρωτοκόλλου επικοινωνίας. Μια επιλογή θα ήταν να σχεδιάσουμε και να αναπτύξουμε ένα ιδιότυπο (custom) πρωτόκολλο βασισμένο σε συνδέσεις TCP ή σε πακέτα UDP. Κάτι τέτοιο θα μπορούσε να έχει κάποια πλεονεκτήματα, όπως για παράδειγμα μικρότερες απαιτήσεις σε λογική από πλευράς Arduino. Από την άλλη όμως αυτό ίσως να απαιτούσε από το σύστημαπελάτη τη χρήση πολύπλοκου δικτυακού κώδικα, ενώ θα ήταν απαγορευτικό για συγκεκριμένες πλατφόρμες που στερούνται δυνατότητες TCP sockets ή UDP πακέτων. Εναλλακτικά θα μπορούσαμε να υιοθετήσουμε το μοντέλο REST πάνω από απλές συνδέσεις HTTP. Το σημαντικότερο μειονέκτημα αυτής της επιλογής είναι ότι η υλοποίησή της απαιτεί την ανάπτυξη λογισμικού στην πλευρά του Arduino που να «καταλαβαίνει» ένα αρκετά πολύπλοκο πρωτόκολλο όπως το HTTP. Τα οφέλη όμως που θα προσέφερε μια τέτοια προσέγγιση είναι εξίσου σημαντικά, αφού αφενός καθιστά το σύστημά μας προσπελάσιμο ακόμη και από συστήματα με περιορισμένες δυνατότητες δικτύου, όπως web εφαρμογές γραμμένες σε JavaScript ή ακόμη και από shell scripts (μέσω προγραμμάτων όπως το wget ή το curl). Επιπλέον η χρήση REST συνιστά την κωδικοποίηση των ανταλλασσόμενων πληροφοριών σε μορφές ευανάγνωστες από τον άνθρωπο (human readable) όπως JSON ή XML, η οποία καθιστά εύκολη τη χρήση του συστήματος ακόμη και με υποτυπώδη εργαλεία, όπως ένα πρόγραμμα πλοήγησης ιστού (web browser). Ένα σημαντικό μειονέκτημα που θα πρέπει να αναγνωρίσουμε είναι ότι η αυτή η μέθοδος απαιτεί την έναρξη κάθε επικοινωνίας αποκλειστικά από το πρόγραμμαπελάτη. Αυτό καθιστά προβληματική την υποστήριξη κάποιου συστήματος παραγωγής συμβάντων (event triggering) από την πλευρά του Arduino. 18 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

19 1.4 Υφιστάμενη Κατάσταση Αναζητώντας στην βιβλιογραφία και στο Διαδίκτυο μπορούμε να βρούμε ένα αριθμό από εργασίες που πραγματεύονται το ίδιο ή παρόμοιο θέμα, δηλαδή τον έλεγχο ενός Arduino μέσω REST interface. Παρακάτω ακολουθεί μια σύντομη αναφορά σε μερικές εξ αυτών. RESTduino Αυτό το λογισμικό πλησιάζει περισσότερο από κάθε άλλο το στόχο τον οποίο καλούμαστε να πετύχουμε σε αυτή την εργασία. Αυτό που κάνει ουσιαστικά είναι βασική είσοδος/έξοδος μέσω απλών REST αιτημάτων. Δεν υπάρχει υποστήριξη για έλεγχο σερβομηχανισμών (servo), αν και ο συγγραφέας του αναφέρει ότι αυτό είναι μέσα στα μελλοντικά του σχέδια. Πιθανώς για να κρατηθεί χαμηλά η πολυπλοκότητα του κώδικα, το συγκεκριμένο REST API δεν επιτρέπει την ανάγνωση ή εγγραφή πολλών τιμών ταυτόχρονα, κάτι που αφενός θα αύξανε την απόδοση, και αφετέρου θα ήταν ακόμη και απαραίτητο σε κάποιες περιπτώσεις όπου οι καθυστέρηση ανάμεσα στις (γενικά αργές) HTTP συνδέσεις θα ήταν μη αποδεκτή. Το συγκεκριμένο λογισμικό φαίνεται να υλοποιεί τις απολύτως απαραίτητες λειτουργίες, χωρίς να προτείνει κάποιο μοντέλο Observer/Subscriber που να καθιστά αποδοτικότερη την ανίχνευση αλλαγών, χωρίς να απαιτείται συνεχής έλεγχος (polling) από την εφαρμογή [1]. arduino-tiny-rest Πρόκειται για ένα REST API που βασίζεται πάνω στον WiServer, έναν web server γενικής χρήσης για την κάρτα WiShield. Η συγκεκριμένη εφαρμογή δεν περιορίζεται μόνο σε βασική είσοδο/έξοδο αλλά υποστηρίζει και πιο προηγμένες λειτουργίες όπως ανάγνωση και εγγραφή από και προς την EEPROM, ενώ ένα άλλο αξιοσημείωτο χαρακτηριστικό είναι η υλοποίηση ενός μοντέλου Παρατηρητή (observer pattern), με το οποίο μπορούμε να εγγράψουμε (subscribe) εξωτερικά web services τα οποία θα λαμβάνουν σχετικά γεγονότα (events) μέσω εξερχόμενων REST αιτημάτων. Ένα σημαντικό μειονέκτημα είναι ότι η υλοποίηση προϋποθέτει την χρησιμοποίηση κάρτας WiShield της async_labs, κάτι που καθιστά απαγορευτική τη χρήση του λογισμικού σε συνδυασμό με κάποια άλλη shield, όπως Ethernet, Wi-Fi, ή Wi-Fly [2]. Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

20 Arduino Ethernet Shield Simple REST API Example Το συγκεκριμένο πρόγραμμα δεν αποτελεί κάποια ολοκληρωμένη γενική λύση αλλά είναι, όπως δηλώνει και το όνομά του, ένα παράδειγμα web server που εξυπηρετεί REST αιτήματα. Στο συγκεκριμένο παράδειγμα ο server μπορεί να θέσει ένα σύστημα συναγερμού εντός και εκτός λειτουργίας, ή να ενημερώσει τον χρήστη για την τρέχουσα κατάσταση του συναγερμού. Είναι μάλλον απίθανο το συγκεκριμένο λογισμικό να χρησιμοποιηθεί όπως είναι, μπορεί όμως αναμφίβολα να αποτελέσει μία βάση για περεταίρω ανάπτυξη για όποιον επιθυμεί να γράψει ένα REST web server [3]. Arduino RestServer Library Πρόκειται για μια πολύ καλά δομημένη βιβλιοθήκη που επιτρέπει στο Arduino να ανταποκρίνεται σε αιτήματα REST. Η αρχή λειτουργίας του στηρίζεται στον καθορισμό «πόρων» (resources) από τον προγραμματιστή, τα οποία στη συνέχεια εκτίθενται προς τα έξω. Αυτό σημαίνει ότι δεν αποτελεί από μόνο του μια λύση γενικής χρήσης, και απαιτεί το λογισμικό που τρέχει ο Arduino να γνωρίζει εκ των προτέρων (κατά το compile time) το σύνολο των πόρων που διαχειρίζεται, κάτι που πιθανώς να θεωρηθεί μειονέκτημα σε πολλές περιπτώσεις, αφού απαιτείται επαναπρογραμματισμός του Arduino σε κάθε διαφορετική εγκατάσταση όπου πρόκειται να χρησιμοποιηθεί [4]. Όπως μπορούμε να δούμε σύμφωνα με τα παραπάνω, έχουν πραγματοποιηθεί ήδη αρκετές προσεγγίσεις του θέματος. Αν και οι παραπάνω εργασίες μπορούν να αποτελέσουμε πηγή έμπνευσης και άντλησης ιδεών και τεχνογνωσίας, καμία από αυτές τις λύσεις δεν φαίνεται να ταυτίζεται πλήρως με τις απαιτήσεις τις παρούσας εργασίας και να συγκεντρώνει όλες τις επιθυμητές δυνατότητες σε μία ενιαία οντότητα. Συνεπώς, μπορούμε βάσιμα να ισχυριστούμε ότι η εκπόνηση και τα εξαγόμενα αποτελέσματα παρούσας εργασίας, δύναται να εμπλουτίσουν και να ωφελήσουν την επιστημονική και τεχνολογική κοινότητα. 20 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

21 1.5 Μεθοδολογία επίλυσης του προβλήματος Για την ενίσχυση της λειτουργικότητας του Arduino με ικανότητες δικτύωσης υπάρχουν πολλές επιλογές [7], οι πλέον διαδεδομένες εκ των οποίων είναι οι «κάρτες» επέκτασης (shields) για ενσύρματη σύνδεση Ethernet και ασύρματη σύνδεση Wi-Fi. Για τις ανάγκες της εργασίας προτείνεται η χρήση της κάρτας Ethernet, κυρίως για οικονομικούς λόγους, αφού η κάρτα Wi-Fi πωλείται σε τιμή υπερδιπλάσια από εκείνη της Ethernet. Επιπλέον, οι δύο κάρτες προγραμματίζονται με τρόπο παρεμφερή, και οι απαιτούμενες αλλαγές στον κώδικα ώστε να υποστηριχτεί η Wi-Fi shield είναι μικρές, για όποιον τυχόν επιθυμεί να υλοποιήσει τη μονάδα με ασύρματη δικτύωση. Για την κατασκευή της μονάδας αυτής καθ αυτής ουσιαστικά δεν απαιτείται άλλος εξοπλισμός σε υλικό πέρα από το ίδιο το Arduino Uno και την Ethernet shield. Έχοντας έτοιμο το υλικό μπορούμε να ξεκινήσουμε την ανάπτυξη του λογισμικού μέσα από το περιβάλλον ανάπτυξης προγραμμάτων (sketches) που προσφέρεται για το Arduino. Για τις ανάγκες τις εφαρμογής θα αναπτύξουμε έναν web server ο οποίος θα «ανοίγει» έναν TCP listener (τυπικά για το πρωτόκολλο HTTP στο port 80), από όπου θα περιμένει εισερχόμενες συνδέσεις. Το λογισμικό θα διαβάζει τα αιτήματα που θα δέχεται και θα τα μεταφράζει σε αντίστοιχες λειτουργίες του Arduino, ενώ θα επιστρέφει τα αποτελέσματα σε μορφή XML ή JSON. Τυπικά οι λειτουργίες αυτές θα περιλαμβάνουν: αλλαγή του mode ενός pin (input ή output), ανάγνωση και γράψιμο τιμών. Επιπλέον θα μελετηθεί ένα publish/subscribe μοντέλο με το οποίο το Arduino θα μπορεί να ειδοποιεί κάποιον πελάτη για αλλαγές που ανιχνεύθηκαν, και το οποίο θα υλοποιηθεί είτε με UDP πακέτα είτε με μόνιμα ανοικτό TCP connection. Σε αυτό το σημείο θα είναι χρήσιμο να υλοποιηθεί μια πλακέτα ελέγχου (dashboard) γενικής χρήσης η οποία θα είναι εφοδιασμένη με εξαρτήματα που θα μπορούν είτε να απεικονίσουν την κατάσταση εξόδου κάποιου pin εξόδου (π.χ. δίοδοι led) είτε να στείλουν τιμές σε κάποιο pin εισόδου (π.χ. push button ή μεταβλητή αντίσταση). Η πλακέτα αυτή θα βοηθήσει στην ανάπτυξη του λογισμικού και στον έλεγχο καλής λειτουργίας του. Όταν το λογισμικό του Arduino φτάσει στο στάδιο να υλοποιεί πλήρως το REST API, θα αναπτύξουμε μια απλή εφαρμογή σε JavaScript, η οποία θα συνδέεται με το Arduino και θα παρέχει στον χρήστη ένα εύχρηστο γραφικό interface. Η εφαρμογή θα κάνει χρήση της τεχνολογίας Ajax μέσω τις οποίας θα διοχετεύει τις εντολές προς το Arduino. Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

22 Για λόγους ευκολίας, για την ανάπτυξη της JavaScript εφαρμογής πιθανότατα θα χρησιμοποιήσουμε την βιβλιοθήκη jquery που καθιστά πολύ εύκολη τόσο την μεταχείριση της όψης μιας HTML σελίδας, όσο και την αποστολή Ajax αιτημάτων, ενώ ταυτόχρονα απαλλάσσει τον προγραμματιστή από προβλήματα ασυμβατότητας μεταξύ διαφόρων προγραμμάτων πλοήγησης (browsers). Τέλος θα κατασκευαστεί το «εικονικό σπίτι» (μακέτα) και η εφαρμογή Java που θα το διαχειρίζεται. Για την κατασκευή του σπιτιού-μινιατούρας θα επιλέξουμε ένα παιδικό παιχνίδι από αυτά που διατίθενται στο εμπόριο καθώς με αυτόν τον τρόπο μπορούμε να κρατήσουμε χαμηλά το κόστος και επίσης να μειωθεί ο χρόνος που θα δαπανηθεί για την κατασκευή του. Το σπιτάκι μινιατούρα θα εφοδιαστεί με διάφορα ηλεκτρικά ή ηλεκτρονικά εξαρτήματα τα οποία θα παίξουν το ρόλο «ηλεκτρικών συσκευών» μιας πραγματικής κατοικίας. Π.χ. λευκές δίοδοι led μπορούν να χρησιμοποιηθούν για την προσομοίωση φωτιστικών ή μια αντίσταση για την προσομοίωση καλοριφέρ. Επιπλέον το σπιτάκι θα διαθέτει εξαρτήματα αισθητήρες όπως π.χ. «θερμόμετρο» για ανάγνωση θερμοκρασίας ή push button για κουδούνι εξώπορτας, κλπ. Η ανάπτυξη του προγράμματος σε Java θα γίνει σε δύο στάδια: πρώτα θα γραφτεί μια βιβλιοθήκη (class library) που θα γεφυρώσει το περιβάλλον της Java και το REST API του Arduino. Με άλλα λόγια η βιβλιοθήκη αυτή θα μεταφράζει κλήσεις μεθόδων στα αντίστοιχα REST API αιτήματα, και θα μεταφράζει τα επιστρεφόμενα αποτελέσματα σε τύπους δεδομένων της Java. Πάνω σε αυτή τη βιβλιοθήκη θα βασιστούμε για να γράψουμε το τελικό παραθυρικό (Swing) πρόγραμμα το οποίο θα δημιουργεί ένα απλό γραφικό περιβάλλον μέσα από το οποίο ο χρήστης θα μπορεί να ελέγξει τις «συσκευές» του σπιτιού, να τους αλλάξει κατάσταση ή να διαβάσει τιμές από αυτές. Τέλος θα μελετήσουμε διάφορα θέματα ασφαλείας και τους κινδύνους που εγκυμονεί η συγκεκριμένη εφαρμογή και θα προσπαθήσουμε να προτείνουμε και να υλοποιήσουμε αντίμετρα και τεχνικές που θα επιτρέψουν την ασφαλή χρήση της κατασκευής πάνω από επισφαλείς δικτυακές συνδέσεις. Τα θέματα που θα εξετάσουμε θα είναι: πιστοποίηση/αυθεντικοποίηση, υποστήριξη ασφαλών συνδέσεων TLS/SSL, και χρήση εικονικών ιδιωτικών δικτύων (VPN). 22 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

23 1.6 Εργαλεία που θα χρησιμοποιηθούν Για την κατασκευή της μονάδας θα χρησιμοποιήσουμε μια πλακέτα ανάπτυξης μικροελεγκτή Arduino Uno, εφοδιασμένη με μια κάρτα Ethernet Shield. Η σύνδεση του Arduino με τον υπολογιστή στον οποίο θα γίνει η ανάπτυξη θα γίνει μέσω καλωδίου USB. Η δικτυακή σύνδεση Ethernet μπορεί να γίνει είτε απ ευθείας από το PC προς το Arduino (ad-hoc) είτε μέσω hub, switch, wireless access point κλπ [8]. Για την ανάπτυξη του λογισμικού του Arduino θα χρησιμοποιηθεί το επίσημο περιβάλλον ανάπτυξης (IDE) που προσφέρεται από τη σελίδα του Arduino. Το συγκεκριμένο IDE παρ όλο που είναι σαφώς υποδεέστερο σε σχέση με πιο «ώριμα» περιβάλλοντα (π.χ. Eclipse ή NetBeans), προσφέρει αρκετές ευκολίες, όπως χρωματισμό του κώδικα (code highlighting) και code auto-completion. Καθιστά πολύ εύκολη τη διαδικασία προγραμματισμού (flashing) του Arduino αφού προσφέρει τη δυνατότητα με μόνο έναν συνδυασμό πλήκτρων να μεταγλωττίσουμε το πρόγραμμά μας και να το «ανεβάσουμε» μέσω σειριακής ή USB θύρας στο Arduino. Επιπλέον διαθέτει serial port monitor που μπορεί να αποδειχθεί εξαιρετικά χρήσιμο κατά την αποσφαλμάτωση (debugging) των προγραμμάτων. Για την JavaScript εφαρμογή θα χρησιμοποιήσουμε έναν απλό επεξεργαστή κειμένου (π.χ. Notepad++), καθώς το μέγεθος της εφαρμογής αναμένεται να είναι πολύ μικρό και θα ήταν υπερβολή η χρήση κάποιου πιο σύνθετου περιβάλλοντος. Οι τεχνολογίες που θα χρησιμοποιηθούν για την εφαρμογή είναι εκείνες που συνθέτουν μια τυπική web εφαρμογή: HTML, CSS, και JavaScript. Για ευκολότερο προγραμματισμό θα χρησιμοποιήσουμε τη βιβλιοθήκη jquery. Η εφαρμογή θα δοκιμάζεται σε πρόγραμμα περιήγησης Mozilla Firefox ή Google Chrome. Για την ανάπτυξη του λογισμικού σε Java θα χρησιμοποιήσουμε το περιβάλλον ανάπτυξης NetBeans. Ο κυριότερος λόγος που θα το προτιμήσουμε είναι ο ενσωματωμένος editor παραθυρικών εφαρμογών, ο οποίος θα μας επιτρέψει να σχεδιάσουμε την φόρμα της εφαρμογής πολύ εύκολα, κυριολεκτικά «σέρνοντας και αφήνοντας» (drag-and-drop) τα διάφορα components, από την παλέτα πάνω στη φόρμα μας. Η παραθυρική εφαρμογή θα βασιστεί στην εργαλειοθήκη Swing της Java. Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

24 Για τις διάφορες ηλεκτρονικές κατασκευές που θα χρειαστεί να υλοποιήσουμε, θα χρησιμοποιήσουμε πλακέτες τύπου breadboard. Οι συγκεκριμένες πλακέτες δεν απαιτούν «κολλήσεις» και έτσι τόσο αυτές όσο και τα εξαρτήματα που τοποθετούνται επάνω τους μπορούν να επαναχρησιμοποιηθούν, γεγονός που τις καθιστά εξαιρετικά προτιμητέες για προτυποποίηση (prototyping) [9]. Για το λογισμικό του web server καθώς και για το Java API θα χρησιμοποιήσουμε το παράδειγμα του αντικειμενοστραφούς προγραμματισμού. Ειδικότερα όμως στο κομμάτι του web server πιθανώς να γίνουν κάποια trade-offs εις βάρος της καθαρής σχεδίασης και αναγνωσιμότητας του κώδικα, προς όφελος της ταχύτητας, απόδοσης, και ελαχιστοποίησης της κατανάλωσης πόρων, μιας και το υλικό του Arduino διαθέτει εξαιρετικά περιορισμένους διαθέσιμους πόρους, ειδικά σε ότι αφορά το μέγιστο επιτρεπόμενο μέγεθος του προγράμματος (sketch size) και της διαθέσιμης μνήμης RAM. Για τις γραφικές εφαρμογές (σε JavaScript και Java) θα χρησιμοποιήσουμε προγραμματισμό οδηγούμενο από γεγονότα (event-driven programming) καθώς και πολυνηματικό προγραμματισμό (multi-threaded programming). Παρ όλο που η ανάπτυξη και η δοκιμή του λογισμικού θα γίνει σε λειτουργικό σύστημα Windows 7, τα εργαλεία και προγράμματα που θα χρησιμοποιηθούν διατίθενται επίσης και για περιβάλλοντα GNU/Linux. Συνεπώς αυτό δε θα σταθεί εμπόδιο για όποιον θελήσει να αναπτύξει την κατασκευή χρησιμοποιώντας αποκλειστικά ελεύθερα και δωρεάν περιβάλλοντα και εργαλεία. Γλώσσες προγραμματισμού Περιβάλλοντα ανάπτυξης (IDE) Hardware C/C++ Arduino IDE Arduino Uno TCP/IP JavaScript (HTML, CSS, jquery) Java (Sockets, XML, Swing) Τεχνολογίες Δικτύων NetBeans Arduino Ethernet Shield HTTP Protocol Πίνακας 2: Σύνοψη τεχνολογιών και εργαλείων που θα χρησιμοποιηθούν Virtual Private Networks 24 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

25 1.7 Διάρθρωση της εργασίας Στο πρώτο κεφάλαιο της εργασίας κάναμε μια εισαγωγή στον Arduino και τη σημασία του απομακρυσμένου ελέγχου. Είδαμε τα οφέλη της επιλογής του REST ως μοντέλου σχεδίασης σε σύγκριση με διαφορετικές προσεγγίσεις. Εξετάσαμε παρεμφερείς εργασίες που έχουν πραγματοποιηθεί και κατά πόσον αυτές πλησιάζουν στον στόχο της παρούσας εργασίας. Καθορίσαμε εντός λογικών πλαισίων τη μεθοδολογία που θα ακολουθήσουμε για την επίλυση του προβλήματος και εντοπίσαμε τα εργαλεία που θα χρησιμοποιήσουμε. Το κείμενο που ακολουθεί έχει αφηγηματικό χαρακτήρα και περιγράφει βήμα προς βήμα την πρόοδο της διατριβής καθώς αυτή προχωράει. Στο δεύτερο κεφαλαίο ασχολούμαστε με το σχεδιασμό των συστημάτων και των επιμέρους υποσυστημάτων που πρέπει να υλοποιήσουμε. Περιγράφουμε αναλυτικά τα κυριότερα μέρη του λογισμικού και σχεδιάζονται αναλυτικά τα ηλεκτρονικά κυκλώματα του υλικού. Στο τρίτο περιγράφεται αναλυτικά η υλοποίηση του υλικού των μονάδων καθώς και του λογισμικού από το οποίο θα ελέγχονται. Ειδικά σε ότι αφορά το λογισμικό η ανάπτυξη γίνεται εκ του μηδενός και προχωράμε σταδιακά, στοχεύοντας και λύνοντας ένα υποπρόβλημα κάθε φορά, μέχρι την επίτευξη ενός ολοκληρωμένου προγράμματος. Στο τέλος αυτού του κεφαλαίου έχουμε υλοποιήσει πλήρως λειτουργικά παραδοτέα. Στο τέταρτο κεφάλαιο (Ολοκλήρωση) εξετάζουμε πως μπορούμε να βελτιώσουμε το τηλεχειριζόμενο σύστημα Arduino που σχεδιάσαμε και υλοποιήσαμε στα προηγούμενα κεφάλαια, εισάγοντας τη δυνατότητα παραγωγής συμβάντων (event triggering). Επιπλέον εξετάζουμε πως μπορούμε να εξασφαλίσουμε την επικοινωνία μεταξύ πελάτη και Arduino, ενώ προτείνονται κάποιοι τρόποι αξιοποίησης των διαθέσιμων περιφερειακών μνημών. Στο πέμπτο και τελευταίο κεφάλαιο γίνεται μια ανασκόπηση του συνόλου της εργασίας, τα συμπεράσματα που προέκυψαν, τα προβλήματα που παρουσιάστηκαν κατά την διάρκεια της εκπόνησης, και γίνεται μια αναφορά των εντυπώσεων που μου δημιουργήθηκαν από την ενασχόλησή μου με την παρούσα εργασία. Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

26 26 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

27 2. Σχεδιασμός 2.1 Μονάδα Arduino Υλικό Κεντρικό κομμάτι της μονάδας που θα κατασκευάσουμε αποτελεί φυσικά ο μικροελεγκτής Arduino. Θα χρειαστούμε συνεπώς μια πλακέτα ανάπτυξης Arduino Uno, ως βάση για την κατασκευή μας [10]. Η πλακέτα αυτή από μόνη της μπορεί να προγραμματιστεί και να χρησιμοποιηθεί αυτούσια (χωρίς χρήση επιπλέον εξαρτημάτων). Για να προσδώσουμε στον Arduino δυνατότητες σύνδεσης με δίκτυα, θα πρέπει να τον εφοδιάσουμε με μία πλακέτα επέκτασης δικτύου (shield). Για τις ανάγκες της εργασίας θα χρησιμοποιήσουμε την αυθεντική Ethernet shield που διατίθεται στο εμπόριο [11]. Η συγκεκριμένη πλακέτα, πέρα από την θύρα Ethernet, παρέχει επιπλέον ένα interface ανάγνωσης και εγγραφής καρτών Micro SD. Αυτό το χαρακτηριστικό θα μπορούσε ενδεχομένως να αξιοποιηθεί από το λογισμικό με διάφορους τρόπους (π.χ. αρχείο ρυθμίσεων, αποστολή στατικού περιεχομένου, logging, κλπ). Εικόνα 7: Πλακέτα ανάπτυξης Arduino Uno και Ethernet Shield στην οποία φαίνεται η υποδοχή κάρτας Micro SD κάτω δεξιά Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

28 Η «ασπίδα» Ethernet έχει στην κάτω πλευρά της ακροδέκτες που ταιριάζουν ακριβώς στις υποδοχές των ακροδεκτών της πλακέτας Arduino. Έτσι αφενός η πλακέτα της Ethernet στερεώνεται γερά ώστε να δημιουργηθεί μια στιβαρή ενιαία μονάδα, και αφετέρου όλοι οι ακροδέκτες του Arduino παραμένουν εκτεθειμένοι προς τα έξω, μέσω της ασπίδας Ethernet. Εικόνα 8: Ethernet shield τοποθετημένη διακρίνονται οι υποδοχή τροφοδοσίας και οι θύρες USB και Ethernet Πέρα από αυτές τις δύο πλακέτες (Arduino Uno και Ethernet Shield), δεν θα χρειαστούν επιπλέον εξαρτήματα για την κατασκευή της μονάδας, η οποία όπως φαίνεται, από πλευράς υλικού, πρόκειται να είναι πολύ λιτή Ενδοεπικοινωνία Η επικοινωνία μεταξύ του Arduino και της Ethernet shield γίνεται μέσω του Serial Peripheral Interface (SPI), ένα σύγχρονο σειριακό πρωτόκολλο που χρησιμοποιείται από μικροελεγκτές για να επικοινωνούν με μία ή περισσότερες περιφερειακές μονάδες, σε μικρές αποστάσεις [12]. Στην περίπτωση του Arduino, δεν υπάρχουν ειδικά κανάλια επικοινωνίας αποκλειστικά για την υλοποίηση του SPI. Για αυτό το λόγο συγκεκριμένοι ακροδέκτες του Arduino πρέπει να δεσμευτούν όταν απαιτείται επικοινωνία μεταξύ του Arduino και περιφερειακών μονάδων. 28 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

29 Για τον δίαυλο SPI, τυπικά χρειάζονται τρία κανάλια κοινά για όλες τις συσκευές: MISO (Master In Slave Out), MOSI (Master Out Slave In) και SCK (Serial Clock). Τα συγκεκριμένα κανάλια στον Arduino αντιστοιχίζονται κατά σύμβαση στους ακροδέκτες 11, 12 και 13. Επιπλέον, απαιτείται ένα κανάλι Slave Select (SS) για κάθε συσκευή που είναι συνδεδεμένη στον Arduino. Στην περίπτωση της Ethernet Shield, υλοποιούνται δύο συσκευές πάνω στην ίδια πλακέτα: η θύρα Ethernet και ο Micro SD reader. Οι ακροδέκτες SS που χρησιμοποιούν είναι οι 10 και 4, αντίστοιχα. Εικόνα 9: Διάγραμμα επικοινωνίας Arduino Uno και Ethernet Shield μέσω διαύλου SPI Όπως βλέπουμε, οι πέντε (5) από τους συνολικά είκοσι (20) ακροδέκτες εισόδουεξόδου του Arduino Uno, δεσμεύονται για την επικοινωνία μεταξύ Arduino και Ethernet shield. Πρέπει να σημειωθεί εδώ, ότι το SS κανάλι μιας συσκευής παραμένει δεσμευμένο ακόμη και αν δεν πρόκειται να χρησιμοποιηθεί το συγκεκριμένο περιφερειακό. Έτσι για παράδειγμα, ο ακροδέκτης 4 του Arduino, θα παραμείνει δεσμευμένος ακόμη και αν δεν σκοπεύουμε να χρησιμοποιήσουμε τον Micro SD reader. Ακροδέκτης Είσοδος Έξοδος Διαθέσιμος Ακροδέκτης Είσοδος Έξοδος Διαθέσιμος 0 Ψηφιακή Ψηφιακή Ναι 10~ Ψηφιακή PWM 1 Ψηφιακή Ψηφιακή Ναι 11~ Ψηφιακή PWM 2 Ψηφιακή Ψηφιακή Ναι 12 Ψηφιακή Ψηφιακή 3~ Ψηφιακή PWM Ναι 13 Ψηφιακή Ψηφιακή 4 Ψηφιακή Ψηφιακή Α0 Αναλογική Ψηφιακή Ναι 5~ Ψηφιακή PWM Ναι Α1 Αναλογική Ψηφιακή Ναι 6~ Ψηφιακή PWM Ναι Α2 Αναλογική Ψηφιακή Ναι 7 Ψηφιακή Ψηφιακή Ναι Α3 Αναλογική Ψηφιακή Ναι 8 Ψηφιακή Ψηφιακή Ναι Α4 Αναλογική Ψηφιακή Ναι 9~ Ψηφιακή PWM Ναι Α5 Αναλογική Ψηφιακή Ναι Πίνακας 3: Διαθέσιμοι και δεσμευμένοι ακροδέκτες της τελικής μονάδας Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

30 2.1.3 Σενάρια χρήσης Όντας εφοδιασμένος με θύρα Ethernet, ο Arduino μπορεί πλέον να συνδεθεί σε κάποιο δίκτυο Ethernet όπως οποιοσδήποτε άλλος υπολογιστής. Αυτό μπορεί να γίνει είτε συνδέοντάς τον με κάποια συσκευή δικτύου (π.χ. switch, router, access point, κλπ) ή με απευθείας σύνδεση σε έναν άλλο υπολογιστή (direct Ethernet connection). Εικόνα 10: Σχηματική αναπαράσταση χρήσης με απ ευθείας σύνδεση Ethernet Η σύνδεση της μονάδας σε ασύρματο router / access point θα επιτρέψει τον έλεγχό της από ασύρματες συσκευές όπως φορητούς υπολογιστές και smart phones. Αν ο router διαθέτει επιπλέον σύνδεση με το Διαδίκτυο τότε γίνεται εφικτός ο έλεγχος της μονάδας από οποιοδήποτε υπολογιστή ή άλλη συσκευή διαθέτει σύνδεση στο Internet. Εικόνα 11: Σχηματική αναπαράσταση χρήσης με σύνδεση σε ασύρματο access point 30 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

31 2.2 REST API Λειτουργίες που θα υλοποιηθούν Το REST API που θα αναπτύξουμε θα περιγράφει τον τρόπο με τον οποίο κάποιος θα μπορεί να στέλνει αιτήματα και εντολές προς το Arduino μέσω web. Για να είναι το API όσο πιο γενικό και πλήρες γίνεται, θα προσπαθήσουμε να υποστηρίξουμε όλες τις βασικές λειτουργίες που εκτελεί ο Arduino. Γι αυτό το σκοπό θα εξετάσουμε παρακάτω αναλυτικά ποιες είναι αυτές οι λειτουργίες [13]. Αλλαγή λειτουργίας ακροδέκτη (pinmode) Όπως έχει ήδη αναφερθεί, κάθε ακροδέκτης του Arduino μπορεί να χρησιμοποιηθεί είτε ως είσοδος είτε ως έξοδος (όχι ταυτόχρονα φυσικά). Μέσω της εντολής pinmode() μπορούμε να διαλέξουμε ποια ακριβώς λειτουργία θα επιτελεί ένας συγκεκριμένος ακροδέκτης. Π.χ. με pinmode(0,output); ορίζουμε ότι η ψηφιακή είσοδος-έξοδος «0» θα λειτουργεί ως έξοδος, ενώ με: pinmode(a1,input); ορίζουμε ότι η αναλογική είσοδος-έξοδος «Α1» θα λειτουργεί ως είσοδος. Εκτός από την λειτουργία INPUT ο Arduino διαθέτει επίσης την λειτουργία εισόδου INPUT_PULLUP. Η διαφορά είναι στο ότι η INPUT_PULLUP ενεργοποιεί κάποιες εσωτερικές αντιστάσεις 20ΚΩ που συνδέουν την είσοδο με την τάση τροφοδοσίας. Το πλεονέκτημα είναι ότι με αυτό τον τρόπο δεν απαιτείται εξωτερική συνδεσμολογία αντιστάσεων. Η είσοδος διαβάζει τιμή «HIGH» όταν ο ακροδέκτης είναι «στον αέρα», και «LOW» όταν συνδεθεί στην γείωση. Για λόγους, όμως, απλότητας το λογισμικό που θα αναπτύξουμε θα αγνοήσει αυτή τη λειτουργία, αφού αυξάνει την πολυπλοκότητα του, χωρίς να παρουσιάζει επιστημονικό ενδιαφέρον και χωρίς να προσφέρει κάποια σπουδαία χρησιμότητα. Ο καθορισμός της λειτουργίας των ακροδεκτών συνήθως λαμβάνει χώρα κατά την αρχικοποίηση του λογισμικού, δηλαδή στην μέθοδο setup(). Τεχνικά όμως μπορεί να συμβεί και σε οποιαδήποτε άλλη στιγμή του χρόνου εκτέλεσης. Ψηφιακή είσοδος/έξοδος (digitalread digitalwrite) Με αυτές τις δύο μεθόδους μπορούμε να διαβάσουμε και να γράψουμε αντίστοιχα ψηφιακές τιμές σε κάποιον ακροδέκτη του Arduino. Οι επιτρεπτές τιμές είναι δύο: «LOW» και «HIGH», και αντιστοιχούν σε χαμηλή (0V) και υψηλή τάση (5V). Είναι αυτονόητο ότι πριν επιχειρήσουμε να διαβάσουμε ή να γράψουμε έναν ακροδέκτη θα πρέπει να τον έχουμε θέσει προηγουμένως σε output mode. Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

32 Αναλογική είσοδος/έξοδος (analogread analogwrite) Εκτός από καθαρά ψηφιακά σήματα, ο Arduino μπορεί να διαχειριστεί και αναλογική είσοδο/έξοδο. Οι ακροδέκτες που είναι ικανοί για αναλογική είσοδο/έξοδο είναι συγκεκριμένοι. Οι αναλογικές είσοδοι χαρακτηρίζονται από το αρχικό γράμμα «Α» (π.χ. Α0, Α1, κλπ) ενώ οι αναλογικές έξοδοι (PWM) διακρίνονται από μία περισπωμένη δίπλα από τον αριθμό τους (π.χ. 3~, 5~, 6~, κλπ). Κατά την αναλογική είσοδο ο Arduino μεταφράζει γραμμικά την τάση εισόδου του ακροδέκτη με τη βοήθεια ενός αναλογικού-σε-ψηφιακό μετατροπέα (Analog-to- Digital Converter ADC), από 0 5 Volt σε μια 12-bit αριθμητική τιμή [14]. Εικόνα 12: Σχέση μεταξύ στάθμης τάσεως αναλογικής εισόδου και επιστρεφόμενης τιμής από την analogread() Με έναν αρκετά διαφορετικό τρόπο, κατά την αναλογική έξοδο ο Arduino μεταφράζει μια αριθμητική τιμή στο εύρος σε έναν παλμό διαμόρφωσης πλάτους (Pulse Width Modulation PWM) που αντιστοιχεί σε μέση τάση από 0 5 Volt. Η έξοδος στέλνει ψηφιακούς παλμούς σχετικά υψηλής συχνότητας (περίπου 490Hz), οι οποίες εκλαμβάνονται από συγκεκριμένα ήδη συσκευών και εξαρτημάτων (π.χ. φωτοδίοδοι, αντιστάσεις αλλά ακόμη και κινητήρες συνεχούς ρεύματος) ως αναλογικό σήμα [15]. Εικόνα 13: Ενδεικτικά παραδείγματα κυματομορφών PWM εξόδου για διαφορετικές τιμές analogwrite() 32 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

33 Έλεγχος σερβομηχανισμών (Servo) Πρόκειται για μια ειδική περίπτωση αναλογικής εξόδου για τον έλεγχο σερβομηχανισμών (servo) [16]. Μέσω μια ειδικής βιβλιοθήκης (Servo library) ο Arduino μπορεί να παραγάγει ένα είδος παλμών κατάλληλων για τον έλεγχο σερβομηχανισμών. Με χρήση αυτής της βιβλιοθήκης ο προγραμματιστής μπορεί να καθορίσει τη γωνιακή θέση ενός σερβομηχανισμού, από 0 ως 180 μοίρες, με τρόπο παρόμοιο με εκείνον της αναλογικής εγγραφής [17]. Ο συγκεκριμένος τρόπος λειτουργίας, δεν υλοποιείται ως ειδικό mode από το Arduino, αφού από την σκοπιά του Arduino μια έξοδος Servo είναι τυπικά μια απλή ψηφιακή έξοδος. Αυτό σημαίνει ότι κάθε ακροδέκτης του Arduino είναι σε θέση να ελέγξει σερβομηχανισμούς, εφόσον όλοι μπορούν να λειτουργήσουν ως ψηφιακές έξοδοι Επιλογή μεθόδου HTTP Σε ένα REST API, κατά παράδοση, χρησιμοποιείται η μέθοδος GET του πρωτοκόλλου HTTP, για την ανάγνωση της κατάστασης (state) κάποιου πόρου (resource), ενώ οι μέθοδοι POST ή PUT χρησιμοποιούνται για την τροποποίησή του[18]. Λαμβάνοντας υπόψη την περιορισμένη υπολογιστική ισχύ του Arduino (κυρίως σε ότι αφορά το μέγεθος προγράμματος και της διαθέσιμης RAM) θα κρατήσουμε το API μας όσο γίνεται πιο απλό, υλοποιώντας όλα τα είδη αιτημάτων με την μέθοδο GET. Η συγκεκριμένη μέθοδος έχει το πλεονέκτημα ότι όλες οι πληροφορίες που πρέπει να αποσταλούν στο Arduino βρίσκονται στην πρώτη γραμμή του αιτήματος HTTP, το οποίο καθιστά εύκολη την ανάγνωσή τους (parsing) και στη συγκεκριμένη περίπτωση μας επιτρέπει ακόμη και να αγνοήσουμε τις υπόλοιπες επικεφαλίδες του αιτήματος. GET HTTP/1.1 Host: Connection: keep-alive User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) Chrome/ Safari/ Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Encoding: gzip,deflate,sdch Accept-Language: en-us,en;q=0.8 Accept-Charset: ISO ,utf-8;q=0.7,*;q=0.3 Εικόνα 14: Τυπικό παράδειγμα αιτήματος HTTP GET του Google Chrome Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

34 2.2.3 Μορφή αιτημάτων (request format) Θα εξετάσουμε τώρα τη μορφή που θα έχουν τα αιτήματα που θα αναγνωρίζει ο Arduino. Είναι σκόπιμο κάθε REST αίτημα να ξεκινάει με μια σύντομη προέκταση (prefix) που θα υποδεικνύει ότι το συγκεκριμένο URI είναι μέρος του API, π.χ.: /api/ αυτό θα το διαφοροποιήσει σε σχέση με πιθανά άλλα αιτημάτων που θα μπορούσαν να υποστηριχθούν, όπως π.χ. εξυπηρέτηση στατικού περιεχομένου (σελίδες HTML, εικόνες, κλπ), και θα επιτρέψει στο λογισμικό να αναγνωρίσει άμεσα ότι πρόκειται για αίτημα του API και να το διαχειριστεί κατάλληλα. Αμέσως μετά την προέκταση /api/ θα ακολουθεί ο τύπος του αιτήματος ή αλλιώς η «εντολή» που πρόκειται να εκτελεστεί (π.χ. ανάγνωση, εγγραφή, αλλαγή λειτουργίας ακροδέκτη, κλπ). Προαιρετικά η εντολή θα ακολουθείται από ένα πλήθος παραμέτρων και τιμών. Τυπικά, σε ένα GET αίτημα, οι παράμετροι διαχωρίζονται από το υπόλοιπο URI με τον χαρακτήρα '?', ενώ διαχωρίζονται μεταξύ τους με τον χαρακτήρα '&'. Το όνομα της παραμέτρου διαχωρίζεται από την τιμή με ένα '='. Έτσι η τελική μορφή ενός τυπικού αιτήματος του API θα είναι: /api/εντολή?παράμ1=τιμή1&παράμ2=τιμή2& Μορφή απαντήσεων (response format) Σε κάθε αίτημα ο Arduino εκτός από το να επιτελεί την αντίστοιχη λειτουργία, θα πρέπει επίσης να στέλνει και μια απάντηση (feedback) με την οποία να ενημερώνει τον χρήστη του API για το αν το αίτημα διεκπεραιώθηκε επιτυχώς, αν υπήρξαν σφάλματα (και ποια), ή να επιστρέφει το state της συσκευής (π.χ. ανάγνωση τιμής ακροδέκτη). Τυπικά σε ένα REST API για τις απαντήσεις των αιτημάτων χρησιμοποιείται το συντακτικό JSON (JavaScript Object Notation) ή η γλώσσα σήμανσης XML (extensible Markup Language) [19]. <actors> <actor> <name>jake Gyllenhaal</name> <age>32</age> </actor> <actor> <name>tim Robbins</name> <age>54</age> </actor> <actor> <name>ron Livingston</name> <age>45</age> </actor> </actors> { "actors": { "actor": [ { "name": "Jake Gyllenhaal", "age": "32", { "name": "Tim Robbins", "age": "54", { "name": "Ron Livingston", "age": "45" ] Εικόνα 15: Σύγκριση κωδικοποίησης της ίδιας πληροφορίας χρησιμοποιώντας XML (αριστερά) και JSON (δεξιά) 34 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

35 Η JSON φαίνεται να έχει απλούστερη μορφή, να είναι πιο ευανάγνωστη στο ανθρώπινο μάτι και να παράγει μικρότερη έξοδο σε σχέση με την XML. Από την άλλη όμως η ανάγνωση της JSON, αν και υποστηρίζεται εγγενώς από την JavaScript, πιθανόν να μην υποστηρίζεται από κάποιες γλώσσες προγραμματισμού. Συγκεκριμένα για την Java η ανάγνωση της JSON υποστηρίζεται με χρήση εξωτερικών (third-party) βιβλιοθηκών, σε αντίθεση με την XML που υποστηρίζεται από το JDK. Επιπλέον, ο όγκος της πληροφορίας που πρόκειται να διακινήσουμε είναι σχετικά μικρός και έτσι το πλεονέκτημα της JSON ως πιο ελαφριά (light-weight) επιλογή δεν παίζει καθοριστικό ρόλο. Λαμβάνοντας υπόψη και το γεγονός ότι η XML μπορεί να αποκωδικοποιηθεί εύκολα από την γλώσσα JavaScript (είτε με χρήση jquery είτε με το DOM), μπορούμε να καταλήξουμε ασφαλώς στην επιλογή της XML Συμβάσεις Ονοματολογία Κατά την επικοινωνία μεταξύ πελάτη (προγραμματιστή) και του Arduino μέσω του REST API, ενδέχεται να αποστέλλονται δεδομένα που θα αφορούν ακροδέκτες (pins), τον τρόπο λειτουργίας τους (pin mode) και τις τιμές ανάγνωσης/εγγραφής. Είναι σαφές ότι πρέπει να υπάρξουν κάποιες συμβάσεις ώστε οι δύο πλευρές να «μιλούν» την ίδια γλώσσα. Σε κάθε ακροδέκτη (pin) του Arduino αποδίδουμε ένα αναγνωριστικό όνομα (identifier) που τον προσδιορίζει και τον διακρίνει σε σχέση με τους υπόλοιπους. Κάθε αναγνωριστικό θα ξεκινάει με ένα πρόθεμα που δηλώνει το είδος του ακροδέκτη (ψηφιακός ή αναλογικός) και θα ολοκληρώνεται με έναν αριθμό. Οι ψηφιακοί ακροδέκτες θα λαμβάνουν το πρόθεμα D ενώ οι αναλογικοί το A. Ακροδέκτης Αναγνωριστικό Ακροδέκτης Αναγνωριστικό Ακροδέκτης Αναγνωριστικό 0 D0 6~ D6 Α1 A1 1 D1 7 D7 Α2 A2 2 D2 8 D8 Α3 A3 3~ D3 9~ D9 Α4 A4 5~ D5 Α0 A0 Α5 A5 Πίνακας 4: Διαχειρίσιμοι ακροδέκτες και τα αναγνωριστικά τους Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

36 Επιπλέον θα αποδώσουμε από έναν χαρακτηριστικό κωδικό για κάθε τρόπο λειτουργίας ενός ακροδέκτη (pin mode). Το πλήθος των πιθανόν mode είναι μικρό, έτσι ο κωδικός μπορεί να αποτελείται από έναν χαρακτήρα που θα παραπέμπει στον τρόπο λειτουργίας. Οι κωδικοί που προτείνονται είναι οι εξής: Κωδικός i o s Τρόπος λειτουργίας ακροδέκτη είσοδος (ψηφιακή ή αναλογική) έξοδος (ψηφιακή ή PWM) έλεγχος σερβομηχανισμού Πίνακας 5: Υποστηριζόμενοι τρόποι λειτουργίας (modes) και οι κωδικοί τους Παρατηρούμε ότι ο τρόπος λειτουργίας ακροδέκτη (mode) δεν καθορίζει αν η είσοδος ή έξοδος είναι ψηφιακή ή αναλογική. Αυτό εξαρτάται αποκλειστικά από τον εκάστοτε ακροδέκτη. Συγκεκριμένοι ακροδέκτες συμπεριφέρονται ως αναλογικές είσοδοι ενώ κάποιοι άλλοι ως αναλογικές έξοδοι (PWM) Τύποι αιτημάτων Εντολές Τώρα που έχουμε μια γενική ιδέα της μορφή των αιτημάτων και απαντήσεων του REST API, μπορούμε να προχωρήσουμε στη δημιουργία του συνόλου εντολών που θα αναγνωρίζει ο Arduino. Οι λειτουργίες που πρέπει να υλοποιηθούν είναι: αλλαγή τρόπου λειτουργίας ακροδέκτη, ανάγνωση και εγγραφή. Στη γενική περίπτωση που κάποια εντολή εκτελεστεί με επιτυχία, και εφόσον δεν χρειάζεται να σταλούν επιπλέον πληροφορίες προς την εφαρμογή. Για την αλλαγή τρόπου λειτουργίας ακροδέκτη ορίζουμε την εντολή mode η οποία θα δέχεται μία παράμετρο στην οποία θα αποδίδεται μία τιμή: /api/mode?pin=mode Ως όνομα παραμέτρου θα χρησιμοποιείται το αναγνωριστικό του ακροδέκτη του οποίου τον τρόπο λειτουργίας θέλουμε να αλλάξουμε, ενώ η τιμή της παραμέτρου θα είναι ο κωδικός που αντιστοιχεί στον τρόπο λειτουργίας (mode). 36 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

37 Για παράδειγμα, η εντολή: /api/mode?d0=o θα έχει ως αποτέλεσμα η ψηφιακή είσοδος/έξοδος «0» να λειτουργεί ως έξοδος, ενώ η εντολή: /api/mode?a1=i θέτει την αναλογική είσοδο/έξοδο «Α1» ώστε να λειτουργεί ως είσοδος. Είναι πιθανόν, κατά την αρχικοποίηση μιας εφαρμογής, να χρειαστεί να καθορίσουμε τον τρόπο λειτουργίας πολλών διαφορετικών ακροδεκτών. Θα ήταν μη αποδοτικό να στέλνουμε για κάθε ακροδέκτη από μια ξεχωριστή εντολή mode, κάθε μια από τις οποίες μεταφράζεται σε ένα (σχετικά) «αργό» αίτημα HTTP. Επιπλέον, υπάρχει περίπτωση οι απαιτήσεις κάποιας εφαρμογής να προϋποθέτουν ότι τα modes κάποιων ακροδεκτών να πρέπει να καθοριστούν «ταυτόχρονα», δηλαδή με την ελάχιστη δυνατή χρονική καθυστέρηση. Για να μπορέσουμε να καλύψουμε τις παραπάνω περιπτώσεις, μπορούμε να επεκτείνουμε την εντολή mode, ώστε να μπορεί να λαμβάνει περισσότερες από μια παραμέτρους, και έτσι να μπορεί να καθορίσει τον τρόπο λειτουργίας περισσότερων του ενός ακροδεκτών στο ίδιο αίτημα. Έτσι η γενική μορφή της εντολής mode γίνεται: /api/mode?pin1=mode1&pin2=mode2&...&pinκ=modeκ Με αυτό το τρόπο, οι δύο παραπάνω εντολές θα μπορούν να σταλούν με μία μόνο εντολή mode σε ένα μόνο αίτημα HTTP: /api/mode?d0=o&a1=i Πιθανά σφάλματα που μπορούν να προκύψουν στην εντολή mode είναι: 1. «άγνωστο αναγνωριστικό ακροδέκτη» και 2. «άγνωστος κωδικός τρόπου λειτουργίας (mode)» Όταν αλλάζει ο τρόπος λειτουργίας κάποιου ακροδέκτη, είναι σημαντικό να ειδοποιηθεί η εφαρμογή το συντομότερο δυνατό για την νέα τιμή του συγκεκριμένου ακροδέκτη. Για αυτό το λόγο κρίνεται σκόπιμο κατά την επιτυχή αλλαγή λειτουργίας ενός ακροδέκτη, να επιστρέφεται άμεσα, ως απάντηση, η νέα τιμή. Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

38 Η απάντηση του Arduino μετά από μια επιτυχή εντολή mode, θα έχει την παρακάτω μορφή: <pins> <pin id= pin1 value= value1 /> <pin id= pin2 value= value2 />... <pin id= pink value= valuek /> </pins> Ο κόμβος-ρίζα <pins> θα περιέχει από ένα κόμβο-παιδί <pin> για κάθε ακροδέκτη του οποίου άλλαξε ο τρόπος λειτουργίας (mode). Κάθε κόμβος <pin> περιέχει την πληροφορία για την τρέχουσα τιμή (value) του εκάστοτε ακροδέκτη. Να θυμηθούμε σε αυτό το σημείο ότι ένα αίτημα με πολλαπλούς ακροδέκτες, είτε θα πρέπει να έχει πλήρη επιτυχία, που εγγυάται ότι όλα τα modes αλλάχθηκαν, είτε θα παρουσιάσει σφάλμα, που σημαίνει ότι κανένα mode δεν άλλαξε. Κατά τρόπο αντίστοιχο με την εντολή mode, ορίζουμε την εντολή write η οποία θα επιτελεί την λειτουργίας της εγγραφής/απόδοσης τιμών: /api/write?pin=value Ως όνομα παραμέτρου θα χρησιμοποιείται και πάλι το αναγνωριστικό του ακροδέκτη του οποίου την τιμή εξόδου θέλουμε να τον τροποποιήσουμε, ενώ η τιμή της παραμέτρου θα είναι η αριθμητική τιμή η οποία αντιστοιχεί στην τιμή που θέλουμε να αποδώσουμε, και η οποία θα πρέπει να βρίσκεται εντός του επιτρεπόμενου εύρους τιμών, ανάλογα με τον τύπο εξόδου του συγκεκριμένου ακροδέκτη. Τύπος εξόδου Εύρος τιμών Σημειώσεις Ψηφιακή = LOW, 1 = HIGH Αναλογική PWM = πάντα LOW, 255 = πάντα HIGH Αναλογική Servo θέση σερβομηχανισμού σε μοίρες Πίνακας 6: Υποστηριζόμενα εύρη τιμών για κάθε τύπο εξόδου 38 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

39 Είναι αυτονόητο πως η συγκεκριμένη εντολή (write) θα πρέπει χρησιμοποιείται μόνο σε συνδυασμό με ακροδέκτες που έχουν οριστεί να λειτουργούν ως έξοδοι. Για τους ίδιους λόγους με την εντολή mode, η γενική μορφή της εντολής write θα επεκταθεί ώστε να μπορεί να αλλάξει την τιμή πολλαπλών ακροδεκτών «ταυτόχρονα», σε ένα και μοναδικό αίτημα. Η γενική μορφή δηλαδή θα είναι: /api/mode?pin1=value1&pin2=value2&...&pinκ=valueκ Ένα παράδειγμα χρήσης της εντολής write θα μπορούσε να είναι το: /api/mode?d0=1&d6=128&a5=90 που έχει ως αποτέλεσμα: η ψηφιακή έξοδος «0» να πάρει την τιμή «1» που αντιστοιχεί σε υψηλή (HIGH) στάθμη εξόδου 5V, η PWM έξοδος «6~» να εναλλάσσει την έξοδό παραμένοντας σε ίσα χρονικά διαστήματα στην κάθε στάθμη, δημιουργώντας έτσι μια μέση τάση εξόδου που αντιστοιχεί στα 2.5V, και ο σερβομηχανισμός που οδηγείται από την αναλογική είσοδο «Α5» να μεταβεί στην θέση των 90 μοιρών. Πιθανά σφάλματα που μπορούν να προκύψουν στην εντολή write είναι: 3. «άγνωστο αναγνωριστικό ακροδέκτη» 4. «ο ακροδέκτης δεν είναι έξοδος» 5. «μη αριθμητική τιμή» 6. «τιμή εκτός ορίων» Για τη λειτουργία της ανάγνωσης θα ορίσουμε αντίστοιχα την εντολή read, η οποία στη γενική μορφή της θα περιλαμβάνει από μια παράμετρο για κάθε ένα ακροδέκτη την τιμή του οποίου επιθυμούμε να διαβάσουμε: /api/read?pin1&pin2&...&pinκ Το όνομα κάθε παραμέτρου θα είναι το αναγνωριστικό του ακροδέκτη στον οποίο αντιστοιχεί. Όπως βλέπουμε επειδή σε αυτήν την περίπτωση δεν αποδίδουμε τιμές σε ακροδέκτες, δεν υπάρχουν τιμές στις παραμέτρους. Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

40 Σε ένα επιτυχημένο αίτημα read, περιμένουμε από τον Arduino να μας επιστρέψει την τιμή καθενός ακροδέκτη από αυτούς που ζητήθηκαν. Η απάντηση θα μοιάζει πολύ (για την ακρίβεια θα είναι ακριβώς η ίδια) με εκείνη που αποστέλλεται μετά από μία επιτυχημένη εντολή mode: <pins> <pin id= pin1 value= value1 /> <pin id= pin2 value= value2 />... <pin id= pink value= valuek /> </pins> Για παράδειγμα, έστω ότι αποστέλλουμε στον Arduino την εντολή: /api/read?d0&a1 με την οποία ζητάμε τις τιμές της ψηφιακής εισόδου «0» και της αναλογικής εισόδου «Α1». Μια πιθανή απάντηση που θα μπορούσαμε να λάβουμε θα ήταν η: <pins> <pin id= D0 value= 1 /> <pin id= A1 value= 234 /> </pins> που μας λέει ότι η ψηφιακή είσοδος «0» διαβάζει σήμα υψηλής στάθμης (1=HIGH), ενώ η αναλογική είσοδος «Α1» διαβάζει την τιμή 234 που αντιστοιχεί σε τάση εισόδου 1.14 Volt, περίπου. Κατά αντιστοιχία με την εντολή write, έτσι και με την read, θεωρούμε αυτονόητο ότι θα πρέπει να χρησιμοποιείται αποκλειστικά και μόνο σε ακροδέκτες που έχουν τεθεί σε κατάσταση λειτουργίας εισόδου. Πιθανά σφάλματα που μπορούν να προκύψουν στην εντολή read είναι: 7. «άγνωστο αναγνωριστικό ακροδέκτη» 8. «ο ακροδέκτης δεν είναι είσοδος» 40 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

41 Πιθανόν σε κάποιες εφαρμογές να ήταν χρήσιμη η ύπαρξη μιας ειδικής εντολής ανάγνωσης που να επιστρέφει σε μία απάντηση τις τιμές όλων των ακροδεκτών που λειτουργούν ως είσοδοι, χωρίς να απαιτείται ο προσδιορισμός καθενός εξ αυτών μέσω παραμέτρων. Γι αυτό το λόγο θα ορίσουμε μια ειδική περίπτωση της εντολής ανάγνωσης, την read-all: /api/read-all Όπως βλέπουμε η read-all δεν επιδέχεται καμία παράμετρο. Η μορφή της εξόδου της δεν θα διαφέρει από αυτήν της read. Επειδή όμως είναι πιθανό να μην υπάρχει κανένας ακροδέκτης σε λειτουργία εισόδου, υπάρχει η πιθανότητα η απάντηση που θα επιστρέψει η read-all να είναι ένα κενό σύνολο. Επειδή η εντολή read-all δεν δέχεται παραμέτρους, είναι απαλλαγμένη από κάθε πιθανό σφάλμα της εντολής read, αφού αυτά σχετίζονται αποκλειστικά με τις παραμέτρους ακροδεκτών. Τέλος, πέρα από τις εντολές που χαρτογραφούν βασικές λειτουργίες του Arduino, μια εφαρμογή πιθανό να χρειαστεί να λάβει μια εποπτική εικόνα της τρέχουσας κατάστασης των ακροδεκτών του Arduino, δηλαδή μια λίστα όλων των ακροδεκτών στην οποία θα διαφαίνονται όλα τα χαρακτηριστικά τους: αναγνωριστικό είδος εξόδου (ψηφιακή ή PWM) τρέχουσα κατάσταση λειτουργίας (mode) τρέχουσα τιμή Για αυτό το σκοπό, ορίζουμε την εντολή list: /api/list Η εντολή list, όπως και η read-all, δεν λαμβάνει καμία παράμετρο. Η απάντηση που επιστρέφει περιλαμβάνει όλους τους ακροδέκτες τους οποίους μπορεί να διαχειριστεί η μονάδα (δηλαδή εξαιρούνται οι ακροδέκτες που έχουν δεσμευτεί για τον δίαυλο επικοινωνίας SPI). Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

42 Η απάντηση της list θα έχει την παρακάτω μορφή: <pins> <pin id= pin 1 pwm= pwm 1 mode= mode 1 value= value 1 /> <pin id= pin 2 pwm= pwm 2 mode= mode 2 value= value 2 />... <pin id= pin k pwm= pwm k mode= mode k value= value k /> </pins> Κάθε κόμβος <pin> περιλαμβάνει τα παρακάτω χαρακτηριστικά: id: το αναγνωριστικό του ακροδέκτη pwm: είδος εξόδου. 0 = ψηφιακή έξοδος, 1 = αναλογική (PWM) έξοδος mode: κωδικός τρέχουσας κατάστασης λειτουργίας (mode) value: τρέχουσα τιμή εισόδου ή εξόδου Όπως και η read-all, έτσι και η list δεν δίνει κανένα πιθανό σφάλμα. Σε περίπτωση που αποσταλεί στον Arduino κάποια άλλη εντολή πέρα από αυτές που ορίσαμε παραπάνω (mode, write, read, read-all, list) τότε ο Arduino θα πρέπει να απαντάει με έναν σχετικό κωδικό σφάλματος Αναφορά σφαλμάτων (error reporting) Σε περίπτωση σφάλματος ο Arduino θα στέλνει μια απάντηση XML της μορφής: <error code= XXX /> με την οποία θα ειδοποιεί τον προγραμματιστή για την ύπαρξη σφάλματος. Ο κωδικός σφάλματος XXX θα υποδεικνύει το είδος του σφάλματος. Σε περίπτωση που προκύψει κάποιο σφάλμα πρέπει να μην πραγματοποιείται καμία αλλαγή της κατάστασης (state) του Arduino, για την αποφυγή ασταθών καταστάσεων. Κωδικός Περιγραφή 100 μη υποστηριζόμενη εντολή 101 άγνωστο αναγνωριστικό ακροδέκτη 102 άγνωστος κωδικός λειτουργίας (mode) 103 μη αριθμητική τιμή 104 τιμή εκτός ορίων 105 ο ακροδέκτης δεν είναι έξοδος 106 ο ακροδέκτης δεν είναι είσοδος Πίνακας 7: Σύνοψη πιθανών σφαλμάτων και οι κωδικοί που τους αποδίδονται 42 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

43 2.3 Λογισμικό web server Γενικά Ο web server που θα αναπτύξουμε προορίζεται να τρέξει σε έναν «υπολογιστή» με πολύ περιορισμένους πόρους. Πιο συγκεκριμένα, το μέγεθος της μνήμης RAM του Arduino Uno είναι μόλις 2KB, ενώ σημαντικό μέρος από αυτή είναι δεσμευμένο πριν καν ξεκινήσει η εκτέλεση του προγράμματός μας. Επιπλέον, το μέγεθος του εκτελέσιμου κώδικα δεν θα πρέπει να ξεπερνάει τα 32KB της μνήμης Flash που διαθέτει ο Arduino. Με αυτά τα δεδομένα πρέπει προχωρήσουμε στην σχεδίαση του web server με κύριο γνώμονα την λιτή σχεδίαση και την οικονομία στη μνήμη. Όπου κριθεί απαραίτητο θα πρέπει να κάνουμε κάποιες «θυσίες» π.χ. στην αναγνωσιμότητα-συντηρησιμότητα του κώδικα, ή να θέσουμε κάποια όρια στις δυνατότητες του λογισμικού. Το υλικό του Arduino προγραμματίζεται με μία γλώσσα βασισμένη στην Wiring, παρόμοια με την C/C++ [20]. Αυτό σημαίνει ότι έχουμε στην διάθεσή μας μια γλώσσα που υποστηρίζει εγγενώς τον Αντικειμενοστραφή Προγραμματισμό (OOP). Θα κάνουμε χρήση αυτού του χαρακτηριστικού τακτοποιώντας τη λογική και τα δεδομένα σε κλάσεις με συγκεκριμένες ευθύνες Λειτουργία web server Η λειτουργία που πρέπει να επιτελεί ο web server είναι σχετικά απλή και συγκεκριμένη: αρχικά εγκαθιστά έναν TCP listener στην θύρα 80, και στην συνέχεια «περιμένει» για εισερχόμενα αιτήματα (incoming requests). Μόλις καταφθάσει κάποιο αίτημα, αυτό θα πρέπει να αναλυθεί λεκτικά ώστε να αποκωδικοποιηθεί το νόημά του, να εκτελεστούν οι απαραίτητες λειτουργίες στο μοντέλο, και να απαντηθεί [21]. Γενικά από έναν web server έχουμε την απαίτηση να μπορεί να εξυπηρετεί πολλά αιτήματα «ταυτόχρονα» ή παράλληλα. Αυτό συμβαίνει κυρίως γιατί συνήθως ένας web server δέχεται καταιγισμό αιτημάτων, ενώ μέχρι την διεκπεραίωση ενός αιτήματος, μεσολαβούν αρκετά διαστήματα όπου ο επεξεργαστής βρίσκεται σε κατάσταση αναμονής περιμένοντας δεδομένα από το δίσκο ή το δίκτυο. Αν οι web servers δεν εκμεταλλεύονταν αυτά τα «κενά» εξυπηρετώντας πολλαπλά αιτήματα ταυτόχρονα, τότε ο χρόνος εξυπηρέτησης ενός αιτήματος θα μεγάλωνε δραματικά [22]. Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

44 Για να μπορέσουμε να γράψουμε έναν web server που να μπορεί να εξυπηρετεί πολλαπλές ταυτόχρονες συνδέσεις είναι απαραίτητο να μπορούμε να διαχειριστούμε μέσω της βιβλιοθήκης δικτύου πολλαπλές συνδέσεις, ενώ η υποστήριξη νημάτων (threads) θα διευκόλυνε πολύ τον προγραμματισμό, χωρίς όμως να είναι απαραίτητη. Δυστυχώς σε αυτήν την πλατφόρμα δεν έχουμε τίποτα από τα δύο. Οι βιβλιοθήκες του Arduino δεν υποστηρίζουν με κανέναν τρόπο πολυνηματικό προγραμματισμό. Έχουν γραφτεί από τρίτους κάποιες βιβλιοθήκες που προσομοιώνουν έως ένα βαθμό την υποστήριξη νημάτων, πάσχουν όμως από σοβαρούς περιορισμούς, όπως για παράδειγμα ότι ένα νήμα δεν πρέπει σε καμία περίπτωση να μπλοκάρει (block), αφού κάτι τέτοιο θα είχε αποτέλεσμα να μπλοκάρουν και όλα τα υπόλοιπα νήματα, γεγονός που τις καθιστά ακατάλληλες για μια εφαρμογή όπως έναν web server [23]. Επιπλέον, ο τρόπος με τον οποίο είναι γραμμένη η βιβλιοθήκη Ethernet του Arduino καθιστά δύσκολη εως αδύνατη τη διαχείριση άνω της μίας ανοικτών συνδέσεων, αφού δεν διαχωρίζει τις νέες από τις ήδη υπάρχουσες εισερχόμενες συνδέσεις [24]. Αυτό σημαίνει ότι από την στιγμή που θα λάβουμε μια εισερχόμενη σύνδεση, θα πρέπει να τη διεκπεραιώσουμε πλήρως μέχρι να μπορέσουμε να εξυπηρετήσουμε την επόμενη. Για να αποφύγουμε το ενδεχόμενο να δημιουργηθεί συνωστισμός συνδέσεων στην ουρά, θα πρέπει η διεκπεραίωση του κάθε αιτήματος να πραγματοποιείται σε όσο το δυνατόν μικρότερο χρόνο. Σε περίπτωση που κάποια σύνδεση στέλνει δεδομένα πολύ αργά, θα πρέπει να κλείνεται μετά από παρέλευση κάποιου χρονικού ορίου (time out). Μεγάλοι χρόνοι διεκπεραίωσης σε συνδυασμό με υψηλή συχνότητα αιτημάτων ενδέχεται να προκαλέσει υπερχείληση (overflow) του πίνακα εισερχόμενων συνδέσεων της βιβλιοθήκης Ethernet, με αποτέλεσμα την άρνηση εξυπηρέτησης (denial of service). Σύμφωνα με τα παραπάνω φαίνεται ότι η διαδοχική εξυπηρέτηση ενός μόνο αιτήματος τη φορά αποτελεί μονόδρομο. Από την άλλη όμως, αυτό δεν θα πρέπει να προβληματίζει ιδιαίτερα, αφού, σύμφωνα με τις απαιτήσεις της εφαρμογής, ο συγκεκριμένος web server αναμένεται ότι ως επί το πλείστον θα δέχεται σχετικά αραιά διαδοχικά αιτήματα από έναν μόνο πελάτη κάθε φορά. 44 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

45 2.3.3 Ανάλυση Σχεδιασμός Ο TCP listener θα εγκαθίσταται στην μέθοδο αρχικοποίησης setup() που εκτελείται μία φορά στην αρχή. Στην συνέχεια, μέσα στην μέθοδο loop() θα γίνεται έλεγχος για την ύπαρξη εισερχόμενης σύνδεσης. Σε περίπτωση που υπάρχει κάποια εισερχόμενη σύνδεση, τότε αυτή θα δίνεται σε μια εξειδικευμένη κλάση Controller η οποία θα αναλαμβάνει την διεκπεραίωσή της (dispatch). Η κλάση Controller θα ζητάει στη συνέχεια από μια κλάση HttpRequestParser να διαβάσει τα δεδομένα από την εισερχόμενη σύνδεση, και να τα αποκωδικοποιήσει ως αίτημα HTTP. Το επιστρεφόμενο αποτέλεσμα θα είναι μια δομή HttpRequest, που θα περιέχει τις πληροφορίες του αιτήματος, δηλαδή το path του αιτήματος (το τμήμα ενός URL που ακολουθεί το όνομα host) και τυχών παραμέτρους που περάστηκαν. Στη συνέχεια ο Controller, έχοντας στη διάθεσή του τις πληροφορίες από την δομή HttpRequest, γνωρίζει πως να διεκπεραιώσει το αίτημα. Μια κλάση Model, θα διασυνδέει τον Controller με τους διαθέσιμους ακροδέκτες της μονάδας. Θα ορίσουμε μια κλάση Pin η οποία θα αντιστοιχεί σε έναν ακροδέκτη του Arduino και θα περιέχει όλα τα χαρακτηριστικά του, ενώ θα παρέχει και τις απαραίτητες μεθόδους για την ανάγνωση και τροποποίησή τους. Ο controller αφού μεταχειριστεί κατάλληλα τα αντικείμενα Pin που του δόθηκαν από την Model, θα στέλνει πίσω την σύνδεση την XML απάντηση και θα την κλείνει. Τέλος, θα επιστρέφει τον έλεγχο πίσω στη μέθοδο loop() ώστε να μπορεί να εξυπηρετηθεί το επόμενο αίτημα. Παρακάτω ακολουθούν τα UML διαγράμματα κλάσεων και αλληλεπίδρασης των οντοτήτων που αναγνωρίσαμε ως τώρα. Φυσικά υπάρχει πάντα το ενδεχόμενο, καθώς προχωράμε με την υλοποίηση του λογισμικού, να υπάρξουν τροποποιήσεις στις παραπάνω κλάσεις. Σε γενικές γραμμές όμως τα παρακάτω διαγράμματα μας δίνουν μια καλή γενική ιδέα όσον αφορά το στήσιμο του λογισμικού του web server. Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

46 Εικόνα 16: Διάγραμμα αλληλεπίδρασης οντοτήτων του web server Εικόνα 17: Διάγραμμα των σημαντικότερων κλάσεων και δομών του web server 46 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

47 2.4 Πλακέτα Dashboard Η μονάδα Arduino που θα κατασκευάσουμε είναι γενικής χρήσης, θα πρέπει δηλαδή να μπορεί να προγραμματίζεται έτσι ώστε να καλύπτει οποιαδήποτε περίπτωση χρήσης. Για αυτό το λόγο θα ήταν ιδιαίτερα χρήσιμο να έχουμε στη διάθεσή μας ένα εργαλείο ώστε να μπορούμε με εύκολο και άμεσο τρόπο να γράφουμε και να διαβάζουμε τιμές από τον Arduino. Ένα τέτοιο εργαλείο θα βοηθήσει στις δοκιμές καλής λειτουργίας του λογισμικού και της αποσφαλμάτωσής του. Για να ελέγξουμε τη λειτουργία κάποιου ακροδέκτη εισόδου χρειαζόμαστε ένα κύκλωμα που να εφαρμόζει ένα σήμα συγκεκριμένης στάθμης στον ακροδέκτη. Για μια ψηφιακή είσοδο αρκεί ένας διακόπτης ή button και μια σχετικά μεγάλη (π.χ. 10ΚΩ) αντίσταση [25], ενώ για μια αναλογική είσοδο μπορούμε να χρησιμοποιήσουμε μια μεταβλητή αντίσταση (ποτενσιόμετρο) των 10ΚΩ [26]. Εικόνα 18: Κύκλωμα ελέγχου ψηφιακής (αριστερά) και αναλογικής (δεξιά) εισόδου Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

48 Για να παρακολουθήσουμε τις διακυμάνσεις της στάθμης κάποιου ακροδέκτη εξόδου, αρκεί να τον συνδέσουμε με μια φωτοδίοδο (led) σε σειρά με μία αντίσταση των 330Ω που θα παίξει τον ρόλο περιοριστή ρεύματος [27]. Το ίδιο κύκλωμα μπορεί να χρησιμοποιηθεί τόσο για ψηφιακές εξόδους, όσο και για αναλογικές (PWM), αφού η ένταση της λάμψης της φωτοδιόδου μπορεί να δώσει μια αρκετά καλή ένδειξη της τιμής της αναλογικής εξόδου [28]. Εικόνα 19: Κύκλωμα ελέγχου εξόδων Επειδή ο τρόπος λειτουργίας κάθε ακροδέκτη προγραμματίζεται δυναμικά, θα πρέπει και τα δύο είδη κυκλώματος (είσοδος ή έξοδος) να συνυπάρχουν ταυτόχρονα. Αυτό όμως θα δημιουργούσε σύγκρουση ανάμεσα στο κύκλωμα ελέγχου εισόδου και του ακροδέκτη του Arduino, όταν αυτός προγραμματιστεί να λειτουργήσει ως έξοδος [29]. Για να ξεπεράσουμε αυτό το πρόβλημα, μπορούμε να χρησιμοποιήσουμε έναν απλό μεταγωγικό διακόπτη τριών επαφών δύο θέσεων (SPDT), ο οποίος θα εγγυάται ότι ανά πάσα στιγμή μόνο ένα από τα δύο κυκλώματα θα συνδέεται στον ακροδέκτη. Έτσι το τελικό σχέδιο του Κυκλώματος Ελέγχου διαμορφώνεται ως εξής: Εικόνα 20: Τελικά κυκλώματα ελέγχου ψηφιακής (αριστερά) και αναλογικής (δεξιά) εισόδου 48 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

49 Προφανώς το κύκλωμα ελέγχου των ψηφιακών εισόδων θα πρέπει να υλοποιηθεί εννιά (9) φορές ενώ το κύκλωμα ελέγχου αναλογικών εισόδων έξι (6) φορές. Τα κυκλώματα ελέγχου που περιγράφονται παραπάνω είναι ακατάλληλα για τη δοκιμασία εξόδων ελέγχου σερβομηχανισμών, γιατί κάτι τέτοιο θα αυξήσει σημαντικά την πολυπλοκότητα και το κόστος. Για τις δοκιμές που αφορούν σερβομηχανισμούς θα χρησιμοποιηθούν ad hoc λύσεις όπου ένα servo θα συνδέεται προσωρινά στον υπό δοκιμή ακροδέκτη. 9x 6x 15x 15x 6x 15x R1 αντιστάσεις 10ΚΩ R2 μεταβλητές αντιστάσεις 10ΚΩ R3 αντιστάσεις 330Ω D1 φωτοδίοδοι (led) SW1 μπουτόν push-on SW2 μεταγωγικοί διακόπτες SPDT Πίνακας 8: Τα εξαρτήματα που θα χρειαστούν για την κατασκευή του κυκλώματος Dashboard Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

50 2.5 Control Panel JavaScript Web GUI Περιγραφή της εφαρμογής Η εφαρμογή θα αποτελείται από μία «οθόνη» στην οποία θα εμφανίζει από ένα χειριστήριο (control) για κάθε διαθέσιμο ακροδέκτη. Σε κάθε χειριστήριο θα φαίνεται η τρέχουσα κατάσταση λειτουργίας (mode) και η τρέχουσα τιμή του ακροδέκτη. Εικόνα 21: Σχηματική απεικόνιση της οθόνης της JavaScript εφαρμογής Control Panel Στο πάνω μέρος του χειριστηρίου θα υπάρχει ένα μενού επιλογών το οποίο αφενός θα δηλώνει το τρέχον επιλεγμένο mode, ενώ αφετέρου θα μπορούμε μέσω αυτού να αλλάξουμε το mode για τον συγκεκριμένο ακροδέκτη. Οι επιλογές που θα περιλαμβάνει το μενού θα είναι: input, output και servo. Το κάτω μέρος του χειριστηρίου θα εξαρτάται από το είδος του ακροδέκτη (ψηφιακός ή αναλογικός) και από το επιλεγμένο mode (είσοδος, έξοδος, servo). Για ψηφιακές τιμές θα εμφανίζεται ένα «κουτάκι» (checkbox) το οποίο όταν είναι τσεκαρισμένο θα αντιστοιχεί στην τιμή HIGH και όταν είναι μη τσεκαρισμένο θα αντιστοιχεί στην τιμή LOW. Για αναλογικές τιμές θα εμφανίζονται απλά κουτιά κειμένου (textbox). Στα χειριστήρια που λειτουργούν ως έξοδοι ο χρήστης θα μπορεί να αλλάξει την τιμή του ακροδέκτη. Μόλις η εφαρμογή ανιχνεύσει ότι ο χρήστης άλλαξε την τιμή, τότε θα στέλνει στον Arduino την νέα τιμή. 50 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

51 Τα χειριστήρια που λειτουργούν ως είσοδοι δεν θα μπορούν να αλλάζουν από το χρήστη (read-only) και θα ενημερώνονται αποκλειστικά από την εφαρμογή. Ανά τακτά χρονικά διαστήματα μερικών δευτερολέπτων, η εφαρμογή θα «ρωτάει» τον Arduino τις τιμές όλων των εισόδων (polling), και μόλις παραλαμβάνει τις πιο πρόσφατες τιμές θα ενημερώνει κατάλληλα τα χειριστήρια. Σε περίπτωση που ο χρήστης επιλέξει κάποιο άλλο τρόπο λειτουργίας από το μενού, τότε η εφαρμογή θα ενημερώνει τον Arduino, και θα τροποποιεί κατάλληλα τη μορφή του χειριστηρίου Σχέδιο υλοποίησης Ως σημείο εισόδου (entry point) της εφαρμογής από την πλευρά του χρήστη, θα χρησιμοποιήσουμε μια σελίδα HTML η οποία απλά θα φορτώνει την JavaScript της εφαρμογής και θα παρέχει έναν placeholder μέσα στον οποίο η εφαρμογή θα σχεδιάζει το γραφικό interface. Για να ξεκινήσει ο χρήστης την εφαρμογή απλά θα πρέπει να την ανοίξει μέσα σε κάποιο πρόγραμμα περιήγησης (browser). Αρχικά η εφαρμογή θα στέλνει ένα αίτημα /api/list στον Arduino για να πάρει μια λίστα με όλους τους διαθέσιμους ακροδέκτες και την τρέχουσα κατάστασή τους. Για κάθε έναν ακροδέκτη θα δημιουργήσει την HTML που αντιστοιχεί στο χειριστήριο του ακροδέκτη και θα την εισάγει μέσα στον placeholder. Αφού φορτωθεί στη σελίδα όλος ο απαραίτητος HTML κώδικας, ανατίθενται διάφορες συναρτήσεων χειρισμού γεγονότων (event handlers). Τα γεγονότα που πρέπει να ανιχνεύονται να διαχειρισθούν είναι: αλλαγή του μενού επιλογής τρόπου λειτουργίας (mode) και αλλαγή τιμής checkbox ή textbox από το χρήστη. Σε περίπτωση που ανιχνευτεί αλλαγή στο μενού επιλογής mode, τότε η εφαρμογή θα στέλνει ένα αίτημα /api/mode, με το οποίο θα ειδοποιεί τον Arduino να αλλάξει το mode για τον συγκεκριμένο ακροδέκτη. Μόλις λάβει επιβεβαίωση από το API θα τροποποιεί την μορφή του χειριστηρίου, κρύβοντας και εμφανίζοντας διάφορα HTML στοιχεία (elements), έτσι ώστε να ταιριάζει με το νέο mode λειτουργίας. Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

52 Αν ανιχνευτεί αλλαγή σε ένα checkbox ή textbox τότε η εφαρμογή θα στέλνει στον Arduino στέλνει ένα αίτημα /api/write με το οποίο ενημερώνει για την νέα τιμή του συγκεκριμένου ακροδέκτη. Η εφαρμογή θα πρέπει να «θυμάται» την προηγούμενη τιμή της εξόδου, γιατί σε περίπτωση που για κάποιο λόγο ο Arduino αναφέρει κάποιο σφάλμα τότε αυτή θα πρέπει να επαναφέρει την τελευταία γνωστή έγκυρη τιμή. Τέλος, η εφαρμογή θα πρέπει να ενημερώνει την οθόνη με πρόσφατες τιμές των εισόδων του Arduino. Αυτό θα γίνει εγκαθιστώντας κάποιον handler πάνω σε έναν timer, ο οποίος θα στέλνει ανά μερικά δευτερόλεπτα ένα αίτημα /api/read-all με το οποίο θα ζητάει από τον Arduino τις τιμές των εισόδων. Μόλις έρθει απάντηση με τις τιμές θα ενημερώνεται η οθόνη. Για να τερματίσουμε την εφαρμογή θα πρέπει απλώς να κλείσουμε την σελίδα από το πρόγραμμα πλοήγησης (browser). Όπως βλέπουμε η εφαρμογή Control Panel ακολουθεί καθαρά μια σχεδίαση προγραμματισμού καθοδηγούμενου από γεγονότα (event-driven programming), όπου δεν υπάρχει κάποιο «main loop» ελέγχου, αλλά αντίθετα η εφαρμογή βρίσκεται σε κατάσταση αδράνειας (idle), έως ότου κάποιο γεγονός (event) ενεργοποιήσει κάποιον event handler, ο οποίος τότε ανακτά τον έλεγχο και αναλαμβάνει δράση. Γεννήτρια γεγονότων Γεγονός Χειρισμός Χρήστης Αλλαγή mode Αλλαγή τιμής Αποστολή /api/mode στον Arduino Αποστολή /api/write στον Arduino Χρονιστής (Timer) Παρέλευση χρόνου (time-out) Αποστολή /api/read-all στον Arduino Arduino Λήψη XML απάντησης Ενημέρωση της οθόνης (view update) Πίνακας 9: Γεγονότα (events) που δημιουργούνται και διαχειρίζονται από την εφαρμογή 52 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

53 2.6 Βιβλιοθήκη Java Το Java API είναι ο συνδετικός κρίκος μεταξύ του REST API και της γλώσσας προγραμματισμού Java. Μέσω του Java API ο προγραμματιστής της Java θα μπορεί να διαχειριστεί τη μονάδα του Arduino απλά καλώντας Java μεθόδους, χωρίς να γνωρίζει τις τεχνικές λεπτομέρειες του REST API. Φυσικά η ύπαρξη ενός επιπέδου Java πάνω στο REST API θα μπορούσε να χαρακτηρισθεί περιττή. Κάλλιστα θα μπορούσε ένας προγραμματιστής να αναλάβει να υλοποιήσει ο ίδιος την επικοινωνία με τον Arduino (αποστολή HTTP αιτημάτων, αποκωδικοποίηση XML απαντήσεων, κλπ). Επιπλέον, η συγγραφή μιας βιβλιοθήκης γενικής χρήσης πιθανόν να μην ικανοποιεί τις προγραμματιστικές προτιμήσεις του συνόλου των προγραμματιστών. Ωστόσο κρίνεται ωφέλιμο να προτείνουμε ένα API που θα επιτελεί πολύ βασικές λειτουργίες, το οποίο να μπορεί μεν να χρησιμοποιηθεί ως έχει, και να αποτελέσει δε τη βάση για περαιτέρω ανάπτυξη. Για το σκοπό αυτό θα αναπτύξουμε μια πολύ απλή Java βιβλιοθήκη, η οποία θα αποτελεί ουσιαστικά έναν μεσάζοντα (proxy) ανάμεσα στη Java και το REST API. Μια κλάση Arduino που θα αποτελεί το κεντρικό κομμάτι της βιβλιοθήκης θα παρέχει μεθόδους οι οποίες θα αντιστοιχούν στα REST αιτήματα. Εικόνα 22: Αντιστοιχία μεθόδων κλάσης Arduino και αιτημάτων/εντολών του REST API Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

54 Οι παράμετροι (arguments) που θα δέχεται κάθε μέθοδος σχετίζονται άμεσα με το είδος του REST αιτήματος στο οποίο αντιστοιχεί, όπως και ο τύπος επιστρεφόμενης τιμής. Κάθε μέθοδος θα αναλαμβάνει να ετοιμάσει το αντίστοιχο REST αίτημα, θα το αποστέλλει στον Arduino, και στη συνέχεια θα αποκωδικοποιεί (εφόσον υπάρχει) την XML απάντηση και θα την επιστρέφει. Σε περίπτωση που ανιχνευθεί κάποιο σφάλμα θα ρίπτεται κάποια εξαίρεση (exception). Επειδή το πλήθος των ακροδεκτών που θα πραγματεύονται σε κάθε κλήση δεν είναι συγκεκριμένο θα χρησιμοποιούνται τύποι συλλογών (collections) για την συναλλαγή τους. Για παράδειγμα, στην περίπτωση της write() όπου αντιστοιχίζονται τιμές πάνω σε ακροδέκτες θα χρησιμοποιούνται δομές αντιστοίχισης, όπως maps, ενώ αν αποστέλλονται απλές ομάδες αντικειμένων θα χρησιμοποιούνται σύνολα (sets). Για την ειδική περίπτωση της μεθόδου list() θα δημιουργήσουμε μια ειδική δομή PinData η οποία θα ενσωματώνει όλα τα χαρακτηριστικά ενός ακροδέκτη, ενώ για την αναπαράσταση των mode θα ορίσουμε μια απαρίθμηση (enumeration) που θα περιέχει τις τρεις επιτρεπτές τιμές. Εικόνα 23: Διάγραμμα κλάσεων της βιβλιοθήκης Java 54 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

55 2.7 Εικονική κατοικία-μακέτα Για την κατασκευή της εικονικής κατοικίας μακέτας θα επιλέξουμε κάποιο έτοιμο παιδικό παιχνίδι σχετικά μικρών διαστάσεων (όχι μεγαλύτερο των 30 cm) που θα βρούμε στο εμπόριο. Αυτό θα κρατήσει το κόστος και το χρόνο που θα δαπανηθεί για την κατασκευή του σε σχετικά χαμηλά επίπεδα, και θα μας επιτρέψει να επικεντρωθούμε στα επί της ουσίας. Η μακέτα που θα κατασκευάσουμε θα εξοπλιστεί με ένα πλήθος από ηλεκτρικά και ηλεκτρονικά εξαρτήματα, τα οποία θα ελέγχονται από τον Arduino, και θα προσομοιώνουν εικονικές ηλεκτρικές συσκευές μιας πραγματικής κατοικίας. Κρατώντας το μέγεθος της μακέτας σχετικά μικρό, καταφέρνουμε να τη «γεμίσουμε» με σχετικά μικρό αριθμό εξαρτημάτων. Θεωρείται σκόπιμο το μοντέλο της μακέτας που θα επιλέξουμε να διαθέτει φωτοδιαπερατά παράθυρα, καθώς αυτό θα μας επιτρέψει να χρησιμοποιήσουμε εικονικές συσκευές εσωτερικού φωτισμού. Επιπλέον, θα φροντίσουμε η εξωτερική πόρτα της κατοικίας-μακέτας να είναι αρθρωτή (hinged), δίνοντάς μας τη δυνατότητα να την προσδέσουμε στο βραχίονα κάποιου σερβομηχανισμού. Εικόνα 24: Ενδεικτικό παράδειγμα εικονικής κατοικίας μακέτας Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

56 Εξάρτημα Σερβομηχανισμός Λευκές φωτοδιόδοι (led) Φωτοδίοδος RGB Αντίσταση ισχύος Αισθητήρας θερμοκρασίας Διακόπτης μπουτόν Ρόλος Αυτόματο άνοιγμα/κλείσιμο εξώπορτας «Λαμπτήρες» φωτισμού Φωτιστικό τύπου Philips LivingColors Σύστημα θέρμανσης (καλοριφέρ) Θερμόμετρο Θερμοστάτης Κουδούνι εξώπορτας Πίνακας 10: Εξαρτήματα «συσκευές» της εικονικής κατοικίας μακέτας Τα περισσότερα εξαρτήματα μπορούν να συνδεθούν απ' ευθείας με τους ακροδέκτες του Arduino, όπως ο σερβομηχανισμός, ο αισθητήρας θερμοκρασίας, και ο διακόπτης μπουτόν. Μερικά εξαρτήματα, όπως ο σερβομηχανισμός, ο αισθητήρας θερμοκρασίας και οι φωτοδίοδοι (με αντίσταση 330Ω εν σειρά), μπορούν να τροφοδοτηθούν από τα 5 Volt που δίνει ο ίδιος ο Arduino, χωρίς να απαιτούν χρήση εξωτερικού τροφοδοτικού, αφού η κατανάλωσή τους είναι χαμηλή. Η αντίσταση ισχύος (καλοριφέρ) αναμένεται «τραβήξει» σημαντική ποσότητα ρεύματος, με αποτέλεσμα να δημιουργήσει σημαντικές πτώσεις τάσης αν τροφοδοτηθεί από τα 5V του Arduino. Για αυτό το λόγο θα τροφοδοτήσουμε την αντίσταση από τα 9V του τροφοδοτικού. Για να πετύχουμε ρυθμό παραγωγής θερμότητας 1 Watt θα πρέπει να επιλέξουμε την τιμή της αντίστασης στα 82Ω. Η αντίσταση ισχύος θα ενεργοποιείται από τον Arduino μέσω ενός τρανζίστορ 2N2222. Για να εξασφαλίσουμε ρεύμα συλλέκτη (Ic) τουλάχιστον 200mA, και επειδή για το 2N2222 ισχύει ότι hfe 100, μπορούμε να επιλέξουμε την τιμή της αντίστασης βάσης στα 2.2ΚΩ [30]. Οι λευκές φωτοδίοδοι θα συνδεθούν σε απλές ψηφιακές εξόδους και θα μπορούν είτε να είναι σβηστές είτε αναμμένες. Η φωτοδίοδος RGB θα συνδεθεί σε τρεις εξόδους PWM για να μπορεί να παράγάγει κάθε πιθανό συνδυασμό χρωμάτων και αποχρώσεων. Η αντίσταση ισχύος καλοριφέρ θα ελέγχεται επίσης από PWM έξοδο για να μπορούμε να ρυθμίσουμε την ένταση με την οποία θα παράγει θερμότητα. Ο αισθητήρας θερμοκρασίας, επειδή επιστρέφει ένα συνεχές εύρος τιμών, θα συνδεθεί σε μια από τις αναλογικές εισόδους, ενώ για το μπουτόν (κουδούνι εξώπορτας) το οποίο επιστρέφει διακριτές τιμές, αρκεί μια ψηφιακή είσοδος. Τέλος, ο ακροδέκτης ελέγχου του servo της εξώπορτας μπορεί να συνδεθεί σε οποιοδήποτε ακροδέκτη του Arduino, αφού όλοι τους μπορούν να προγραμματιστούν ως οδηγοί σερβομηχανισμών. 56 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

57 Εικόνα 25: Κυκλωματικό διάγραμμα της εικονικής κατοικίας μακέτας R1,R2,R3,R4,R5 R6 R7 R8 D1,D2,D3 D4,D5 Q1 IC1 Servo αντιστάσεις 330Ω αντίσταση 2.2ΚΩ αντίσταση ισχύος 82Ω / 5Watt «τουβλάκι» αντίσταση 10KΩ φωτοδίοδος RGB λευκές φωτοδίοδοι led τρανζίστορ 2N2222 αισθητήρας θερμοκρασίας TMP36 σερβομηχανισμός 9g Πίνακας 11: Κατάλογος ηλεκτρονικών εξαρτημάτων εικονικής κατοικίας μακέτας Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

58 2.8 Java GUI ελέγχου εικονικής κατοικίας-μακέτας Περιγραφή της εφαρμογής Λειτουργικότητα Η γραφική εφαρμογή Java που θα φτιαχτεί για τον έλεγχο των εικονικών συσκευών της εικονικής κατοικίας-μακέτας θα αποτελείται από μια απλή οθόνη η οποία θα περιλαμβάνει διάφορα χειριστήρια με τα οποία ο χρήστης θα μπορεί να διαβάσει ή να αλλάξει την κατάσταση κάποιας συσκευής. Εικόνα 26: Σχεδιάγραμμα οθόνης εφαρμογής εικονικής κατοικίας μακέτας H εξώπορτα έχει δύο καταστάσεις: κλειστή και ανοικτή. Άρα για τον έλεγχό της θα χρησιμοποιήσουμε ένα toggle button, δηλαδή ένα κουμπί με δύο εναλλασσόμενες καταστάσεις. Όταν το κουμπί επιλεγεί (select) θα ανοίγει η πόρτα του σπιτιού, ενώ όταν αποεπιλεγεί (deselect) τότε η πόρτα θα κλείνει. Το κείμενο του κουμπιού θα αλλάζει κάθε φορά που αλλάζει κατάσταση έτσι ώστε να πληροφορεί τον χρήστη για την ενέργεια που θα προκαλέσει η επόμενη εναλλαγή. Όταν η πόρτα είναι κλειστή το κείμενο του κουμπιού θα είναι Open ενώ όταν η πόρτα είναι ανοικτή το κείμενο θα είναι Close. 58 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

59 Με τον ίδιο ακριβώς τρόπο θα πραγματοποιήσουμε τον έλεγχο των δύο φωτιστικών (λευκές φωτοδίοδοι). Κάθε λαμπτήρας φωτισμού έχει επίσης δύο πιθανές καταστάσεις: είτε είναι αναμμένος, είτε είναι σβηστός. Ένα toggle button θα εναλλάσσει των λαμπτήρα (φωτοδίοδο) μεταξύ των δύο αυτών καταστάσεων. Όταν το κουμπί επιλεγεί η φωτοδίοδος θα ανάβει, ενώ όταν αποεπιλεγεί η φωτοδίοδος θα σβήνει. Και σε αυτή την περίπτωση το κείμενο του κάθε κουμπιού θα εξαρτάται από την τρέχουσα κατάστασή του, έτσι ώστε να δηλώνει την ενέργεια που θα πραγματοποιηθεί στην επόμενη εναλλαγή. Όταν η φωτοδίοδος είναι σβηστή το κείμενο του κουμπιού θα είναι Turn On, ενώ όταν είναι αναμμένη το κείμενο θα είναι Turn Off. Για την πολύχρωμη φωτοδίοδο RGB θα χρησιμοποιήσουμε από έναν slider για κάθε μια από τις τρεις συνιστώσες φωτοδιόδους (κόκκινη, πράσινη και μπλε). Σε αντίθεση με τον τρόπο που χειριστήκαμε τις λευκές φωτοδιόδους, για τις έγχρωμες θα χρησιμοποιήσουμε ένα χειριστήριο με το οποίο μπορούμε να επιλέξουμε μια τιμή μέσα από ένα μεγάλο εύρος. Αυτό θα μας επιτρέψει να ρυθμίζουμε με ακρίβεια την ένταση της φωτεινότητας κάθε διόδου και να μπορούμε έτσι να πετύχουμε οποιοδήποτε χρωματικό συνδυασμό και απόχρωση επιθυμούμε. Το αριστερό άκρο του slider θα αντιστοιχεί σε πλήρη συσκότιση της φωτοδιόδου, ενώ το δεξί άκρο στη μέγιστη φωτεινότητα. Παρομοίως θα ελέγχεται και η αντίσταση ισχύος. Μέσω ενός slider θα μπορούμε είτε να την απενεργοποιήσουμε εντελώς (αριστερό άκρο), είτε να την θέσουμε στη μέγιστη δυνατή ισχύ (δεξί άκρο), αλλά και σε οποιαδήποτε άλλη ενδιάμεση κατάσταση. Για την ένδειξη της θερμοκρασίας θα χρησιμοποιήσουμε ένα κουτί κειμένου, μέσα στο οποίο θα απεικονίζουμε την θερμοκρασία που αντιστοιχεί στην ένδειξη που θα στέλνει ο Arduino. Για το κουδούνι της εξώπορτας δεν θα χρησιμοποιήσουμε κάποια οπτική ένδειξη, αλλά αντιθέτως η εφαρμογή θα προσομοιώνει ένα διτονικό (two-tone) κουδούνι. Όταν το μπουτόν πιεστεί θα ακούγεται από τα ηχεία του υπολογιστή ένας υψηλός τόνος (ding), ενώ όταν απελευθερωθεί θα ακούγεται ένα χαμηλός τόνος (dong). Έτσι, σε ένα πλήρη κύκλο χρήσης του κουδουνιού θα ακουστεί ένας συνδυασμένος ήχος ding-dong [31]. Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

60 2.8.2 Σχέδιο υλοποίησης Για την διαχείριση μακέτας θα υλοποιήσουμε μια κλάση ScaleModel που θα παίζει το ρόλο του μοντέλου (model) της εικονικής κατοικίας μακέτας. Η κλάση αυτή θα ορίζει μεθόδους ελέγχου των «συσκευών» της μακέτας που θα ενθυλακώνουν τις τεχνικές λεπτομέρειες, όπως π.χ. ποιος ακροδέκτης αντιστοιχεί σε ποια συσκευή, αποκωδικοποίηση τιμών, κλπ. Έτσι η τελική εφαρμογή-πελάτης θα μπορεί να ελέγξει πλήρως την μακέτα σε πιο αφηρημένο ανώτερο επίπεδο χωρίς να χρειάζεται να γνωρίζει τι ακριβώς συμβαίνει στα «παρασκήνια» [32]. Με αυτόν τον τρόπο πετυχαίνουμε μια «χαλαρή σύνδεση» (loose coupling) ανάμεσα στην τελική εφαρμογή-πελάτη και την Java βιβλιοθήκη χειρισμού του Arduino, η οποία θεωρείται γενικά μια καλή πρακτική στην ανάπτυξη λογισμικού και συστημάτων, με ποικίλα οφέλη όπως εναλλαξιμότητα (interchangeability) των διαφόρων τμημάτων, καλύτερη αναγνωσιμότητα (readability) του κώδικα και ευκολότερη συντήρηση γενικότερα [33]. Εικόνα 27: Η κλάση ScaleModel που θα αποτελεί το interface χειρισμού της μακέτας 60 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

61 Για το παράθυρο της εφαρμογής θα δημιουργήσουμε μια κλάση που θα επεκτείνει (extends) την βασική κλάση πλαισίων (JFrame) της καθιερωμένης βιβλιοθήκης γραφικών περιβαλλόντων της Java (Swing). Μέσα στο πλαίσιο της εφαρμογής θα τοποθετήσουμε όλα τα απαραίτητα controls, έτσι ώστε αυτή να πάρει την όψη που περιγράψαμε νωρίτερα. Με κατάλληλους χειριστές γεγονότων (event handlers) που θα ορίσουμε σε συγκεκριμένα controls, θα μπορούμε να ανιχνεύσουμε πότε ο χρήστης επιθυμεί να αλλάξει την κατάσταση κάποια συσκευής. Όταν συμβεί αυτό θα καλείται η αντίστοιχη μέθοδος της κλάσης ScaleModel, η οποία με τη σειρά θα καλεί τη Java βιβλιοθήκη για τον Arduino. Επιπλέον, η εφαρμογή θα πρέπει με κάποιο τρόπο να ρωτάει ανά μερικά δευτερόλεπτα από τον Arduino (πάντα βέβαια μέσω της ScaleModel) την ένδειξη της θερμοκρασίας καθώς και την κατάσταση του μπουτόν κουδουνιού της εξώπορτας. Η πιο πρόσφατη ένδειξη θερμοκρασίας θα πρέπει να απεικονίζεται στο αντίστοιχο κουτί κειμένου, ενώ σε περίπτωση που ανιχνευθεί αλλαγή στην κατάσταση του μπουτόν θα πρέπει να παράγεται ο αντίστοιχος ήχος από τα μεγάφωνα του υπολογιστή. Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

62 62 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

63 3. Υλοποίηση 3.1 Πλακέτα Ελέγχου "Dashboard" Επιλογή πλακέτας ανάπτυξης Για την τοποθέτηση του συνόλου των εξαρτημάτων της πλακέτας Dashboard θα επιλέξουμε μια επαναχρησιμοποιήσιμη πλακέτα τύπου breadboard. Οι συγκεκριμένες πλακέτες πλεονεκτούν έναντι των απλών διάτρητων πλακετών στο ότι δεν απαιτούν κολλήσεις, με συνέπεια τόσο οι ίδιες οι πλακέτες όσο και τα ηλεκτρονικά εξαρτήματα τα οποία τοποθετούνται πάνω σε αυτές να παραμένουν άθικτα και να μπορούν να χρησιμοποιηθούν πολλαπλές φορές [9]. Εικόνα 28: Πλακέτα τύπου breadboard 400 σημείων (επαφών) Οι πλακέτες τύπου breadboard διαθέτουν οπές μέσα στις οποίες στερεώνονται οι ακροδέκτες των ηλεκτρονικών εξαρτημάτων του κυκλώματος. Οι οπές επικοινωνούν ηλεκτρικά μεταξύ τους με διάφορους τρόπους σχηματίζοντας οριζόντια και κάθετα κανάλια, ούτως ώστε με κατάλληλη τοποθέτηση των εξαρτημάτων να ελαχιστοποιείται ο απαιτούμενος αριθμός επιπλέον συνδέσεων [9]. Εικόνα 29: Σχηματική αναπαράσταση μιας συνήθους εσωτερικής συνδεσμολογίας πλακέτας τύπου breadboard Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

64 Για το συγκεκριμένο κύκλωμα που θέλουμε να υλοποιήσουμε και με γνώμονα το είδος και πλήθος των εξαρτημάτων, θα χρειαστούμε μια μεγάλη πλακέτα 830 σημείων. Η συγκεκριμένη πλακέτα διαθέτει 4 κανάλια τροφοδοσίας που διατρέχουν κατά μήκος των δύο μεγάλων πλευρών της, και 63 διπλά κατακόρυφα κανάλια γενικής χρήσης. Εικόνα 30: Πλακέτα 830 σημείων παρόμοια με αυτήν που θα χρησιμοποιήσουμε Επιλογή ηλεκτρονικών εξαρτημάτων Τα υλικά του κυκλώματος που θα κατασκευάσουμε αποτελούνται κυρίως από μικρά εξαρτήματα, όπως αντιστάσεις και φωτοδίοδοι, τα περισσότερα από τα οποία μπορούν εύκολα να τοποθετηθούν πάνω στην πλακέτα Breadboard. Για τις μεταβλητές αντιστάσεις και τα μπουτόν θα πρέπει να επιλεχθούν μικρά εξαρτήματα ειδικά για τοποθέτηση πάνω σε πλακέτες και όχι για σασί. Εικόνα 31: Μερικά ενδεικτικά εξαρτήματα που θα χρησιμοποιηθούν 64 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

65 Το σημείο του κυκλώματος που ίσως μας προβληματίσει περισσότερο είναι οι μεταγωγικοί διακόπτες που καθορίζουν την λειτουργία καθενός καναλιού (είσοδος ή έξοδος), αφού συνήθως ακόμη και οι μικρότεροι διακόπτες είναι σχετικά ογκώδεις, κάτι που τους καθιστά ακατάλληλους για τοποθέτηση πάνω σε πλακέτα, και επιπλέον έχουν σχετικά υψηλό συνολικό κόστος, αφού θα χρειαστούμε δεκαπέντε από δαύτους. Μια αρκετά ικανοποιητική λύση για αυτό το πρόβλημα είναι η χρήση γεφυρών (jumpers) αντί για πραγματικούς μεταγωγικούς διακόπτες [34]. Χρησιμοποιώντας τρεις ακροδέκτες εν σειρά, και μία γέφυρα jumper μπορούμε να συνδέσουμε τον μεσαίο ακροδέκτη με έναν εκ των δύο ακριανών ακροδεκτών. Έτσι επιτυγχάνουμε στην ουσία να εξομοιώσουμε την λειτουργία της μεταγωγικού διακόπτη. Επειδή η αλλαγή της λειτουργίας ενός καναλιού από είσοδο σε έξοδο και αντίστροφα, είναι κάτι που δεν αναμένεται να συμβαίνει συχνά, η δυσκολία που εισάγεται εξ αιτίας της μεταγωγής μέσω της γέφυρας jumper μπορεί να θεωρηθεί αμελητέα. Εικόνα 32: Σχηματική αναπαράσταση υλοποίησης μεταγωγικού διακόπτη με χρήση γέφυρας jumper Οποιεσδήποτε επιπλέον συνδέσεις μεταξύ των ηλεκτρονικών εξαρτημάτων δεν μπορούν να υλοποιηθούν μέσω των προκατασκευασμένων συνδέσεων της πλακέτας, θα πραγματοποιηθούν χρησιμοποιώντας συμπαγές μονόκλωνο σύρμα (solid hook-up wire). Το συγκεκριμένο σύρμα είναι αρκετά εύκαμπτο ώστε να μπορούμε να του δώσουμε εύκολα το επιθυμητό σχήμα με μία πένσα, ενώ παράλληλα είναι αρκετά σκληρό ώστε να το διατηρήσει [35]. Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

66 3.1.3 Τοποθέτηση εξαρτημάτων - Συναρμολόγηση Η διάταξη των εξαρτημάτων πάνω στην πλακέτα μπορεί να πραγματοποιηθεί με διάφορους τρόπους, και καθένας που επιθυμεί να υλοποιήσει την κατασκευή μπορεί να ακολουθήσει οποιαδήποτε προσέγγιση θεωρεί εκείνος καλύτερη. Παρακάτω ακολουθεί μια πρόταση τοποθέτησης πάνω σε μια πλακέτα 830 σημείων, και οι απαραίτητες συνδέσεις. Εικόνα 33: Πρόταση τοποθέτησης των εξαρτημάτων για την πλακέτα Dashboard Έχοντας προκαθορίσει την θέση κάθε εξαρτήματος και σύνδεσης πάνω στην πλακέτα μπορούμε τώρα να προχωρήσουμε στην υλοποίηση της κατασκευής τοποθετώντας τα υλικά πάνω στην πλακέτα. Εικόνα 34: Η πλακέτα Dashboard ολοκληρωμένη. 66 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

67 3.1.4 Σύνδεση με Arduino Η πλακέτα Dashboard απαιτεί ένα αρκετά μεγάλο αριθμό γραμμών σύνδεσης με τον Arduino. Πιο συγκεκριμένα θα χρειαστούμε δεκαπέντε κανάλια για είσοδο/έξοδο και δύο γραμμές τροφοδοσίας (5V και γείωση), δηλαδή συνολικά δεκαεπτά ξεχωριστές γραμμές. Ένας πολύ βολικός τρόπος για να υλοποιήσουμε το μεγάλο αυτό το πλήθος συνδέσεων, είναι με χρήση ταινίας καλωδίων (ribbon cable) [36]. Στο εμπόριο διατίθενται μεγάλη ποικιλία τέτοιων καλωδίων. Οι ταινίες διαθέτουν πλήθος συρμάτων που συνήθως υπερκαλύπτει τις ανάγκες μας. Επειδή οι υποδοχές των ακροδεκτών τόσο του Arduino όσο και της πλακέτας Dashboard είναι θηλυκές, θα πρέπει να προσέξουμε η ταινία καλωδίων που θα προμηθευτούμε να διαθέτει αρσενικούς ακροδέκτες και από τις δύο πλευρές. Εικόνα 35: Ταινία καλωδίων (ribbon) 40 δρόμων Για να διευκολύνουμε την σύνδεση και αποσύνδεση των γραμμών από και προς τον Arduino, μπορούμε να χρησιμοποιήσουμε ένα σετ στοιβαζόμενων ακροδεκτών (stackable headers) στο άκρο της ταινίας που συνδέεται στον Arduino [37]. Εικόνα 36: Stackable header set για Arduino Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

68 Εικόνα 37: Πλακέτα Dashboard με ribbon cable και headers Εικόνα 38: Πλακέτα Dashboard συνδεδεμένη με τον Arduino 68 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

69 3.1.5 Χρήση πλακέτας Για να χρησιμοποιήσουμε την πλακέτα Dashboard αρκεί να την συνδέσουμε σε κάποια μονάδα Arduino, και αμέσως μετά μπορούμε να γράψουμε ή να διαβάσουμε τιμές από και προς αυτήν. Για να διαβάσουμε τιμές από έναν ακροδέκτη, θα πρέπει να τοποθετήσουμε την γέφυρα jumper που αντιστοιχεί στην συγκεκριμένη έξοδο στην θέση που συνδέει το τμήμα με την φωτοδίοδο, και να θέσουμε το mode του ακροδέκτη ώστε εκείνος να λειτουργεί ως έξοδος. Η φωτοδίοδος τότε θα λάμψει με ένταση ανάλογη της τάσης εξόδου του ακροδέκτη [27][28]. Αντίστοιχα, για να γράψουμε τιμές σε έναν ακροδέκτη, θα πρέπει να τοποθετήσουμε την γέφυρα jumper στην θέση που συνδέει το τμήμα με το μπουτόν ή το ποτενσιόμετρο, και να θέσουμε το mode του ακροδέκτη ώστε εκείνος να λειτουργεί ως είσοδος. Η τιμή που θα διαβάζει ο Arduino για αυτή την είσοδο θα εξαρτάται από την κατάσταση (ελεύθερο ή πιεσμένο) του μπουτόν για τις ψηφιακές εισόδους [25], ή από την γωνία της θέσης του ποτενσιόμετρου για τις αναλογικές εισόδους [26]. Αυτό που θα πρέπει να προσέξουμε είναι οι γέφυρες jumper που καθορίζουν πότε ένα κανάλι λειτουργεί ως είσοδος και πότε ως έξοδος να βρίσκονται πάντα σε συμφωνία με τον τρόπο λειτουργίας (mode) του αντίστοιχου ακροδέκτη του Arduino. Διαφορετικά υπάρχει περίπτωση να προκύψουν ηλεκτρικές ροές μεγάλης έντασης ικανές να προκαλέσουν μερική ή ολική καταστροφή του μικροελεγκτή [29]. Εικόνα 39: Ενδεικτικό σενάριο λανθασμένης χρήσης που μπορεί να οδηγήσει σε βραχυκύκλωμα Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

70 3.2 Μονάδα Arduino Συναρμολόγηση Υλικού Όπως είδαμε ήδη κατά τη φάση της σχεδίασης, το υλικό της μονάδας είναι πολύ λιτό και αποτελείται μόνο από δύο επιμέρους τμήματα: μία μονάδα ανάπτυξης Arduino Uno, και μία πλακέτα επέκτασης Ethernet Shield με ενσωματωμένο SD Card Reader. Εικόνα 40: Πλακέτα ανάπτυξης Arduino Uno και Ethernet Shield Η συναρμολόγηση του υλικού είναι τετριμμένη, και περιορίζεται απλά στην τοποθέτηση της πλακέτας της ασπίδας πάνω στην πλακέτα Arduino Uno. Οι δύο πλακέτες συγκρατούνται γερά μεταξύ τους μέσω των ακροδεκτών στο κάτω μέρος της πλακέτας ασπίδας, οι οποίοι σφηνώνουν στις αντίστοιχες υποδοχές της πλακέτας του Arduino. Η πλακέτα της Ethernet Shield διαθέτει στο πάνω μέρος της επιπλέον υποδοχές ώστε να οι ακροδέκτες του Arduino να παραμείνουν εκτεθειμένοι προς τα έξω. Εικόνα 41: Ethernet shield τοποθετημένο πάνω στον Arduino Uno 70 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

71 Η σύνδεση της μονάδας με τον υπολογιστή στον οποίο θα γίνει η ανάπτυξη θα γίνει μέσω ενός απλού Type A-to-Type B USB καλωδίου [8], ενώ για τη σύνδεση Ethernet θα χρησιμοποιήσουμε ένα κοινό καλώδιο Ethernet με βύσματα RJ45 [11]. Εικόνα 42: Τυπικά καλώδια USB (Type A-to-Type B) και Ethernet (RJ45) Το καλώδιο USB παίζει διπλό ρόλο κατά την ανάπτυξη της μονάδας αφού χρησιμοποιείται αφενός ως σύνδεση δεδομένων για τον προγραμματισμό (flashing) του Arduino, δηλαδή την αποστολή του εκτελέσιμου κώδικα, αλλά αφετέρου εκμεταλλευόμαστε τα 5V της USB θύρας για να τροφοδοτήσουμε το Arduino με ηλεκτρικό ρεύμα, χωρίς να χρειαζόμαστε εξωτερικό τροφοδοτικό [10]. Εικόνα 43: Η μονάδα συναρμολογημένη και συνδεδεμένη με υπολογιστή. Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

72 Μέσω της USB σύνδεσης, και σε συνδυασμό με τους κατάλληλους οδηγούς (drivers), η μονάδα Arduino γίνεται αντιληπτή από τον υπολογιστή σαν μια σειριακή θύρα (COM port) μέσω της οποίας μπορούμε να προγραμματίσουμε τον Arduino [38] Λογισμικό ανάπτυξης Για να ξεκινήσουμε να δουλεύουμε με την ανάπτυξη του web server για τον Arduino, κατεβάζουμε αρχικά την τελευταία έκδοση του λογισμικού που προσφέρεται από την επίσημη σελίδα για την πλατφόρμα που μας ενδιαφέρει. Το λογισμικό διατίθεται για Windows, Mac OSX και Linux, και έχει τη μορφή ενός αρχείου.zip το οποίο περιλαμβάνει το περιβάλλον ανάπτυξης (IDE), ένα πλήθος από βιβλιοθήκες που μπορούμε να χρησιμοποιήσουμε, έτοιμα παραδείγματα, καθώς και τους drivers για το ίδιο το Arduino. Προγράμματα οδήγησης (drivers) Ειδικά για την περίπτωση των Microsoft Windows θα χρειαστεί να εγκαταστήσουμε τους drivers του Arduino προτού να μπορέσουμε να το χρησιμοποιήσουμε. Χωρίς τους drivers ο Arduino μόλις συνδεθεί στην USB θύρα εμφανίζεται ως «άγνωστη συσκευή» στη Διαχείριση Συσκευών (Device Manager) [38]. Εικόνα 44: Ο Arduino όπως εμφανίζεται πριν την εγκατάσταση των οδηγών 72 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

73 Για να εγκαταστήσουμε τους drivers απλώς επιλέγουμε ενημέρωση του προγράμματος οδηγού και επιλέγουμε ως τοποθεσία αναζήτησης τον κατάλογο στον οποίο αποσυμπιέσαμε προηγουμένως το αρχείο με το λογισμικό του Arduino. Υπάρχει πιθανότητα τα Windows να εμφανίσουν κάποια προειδοποίηση ότι ο εκδότης του οδηγού δεν μπορεί να επιβεβαιωθεί (verify). Σε αυτήν την περίπτωση επιλέγουμε την εγκατάσταση του οδηγού όπως και να έχει. Μετά την εγκατάσταση των οδηγών ο Arduino θα πρέπει να εμφανίζεται στη λίστα συσκευών ως Arduino UNO R3, ενώ δίπλα σε αυτό το όνομα θα εμφανίζεται και το όνομα της σειριακής θύρας (COM port) που του έχει αντιστοιχηθεί. Ο αριθμός της σειριακής θύρας μπορεί να διαφέρει από υπολογιστή σε υπολογιστή [38]. Εικόνα 45: Μετά την εγκατάσταση του οδηγού. Διακρίνεται το όνομα της σειριακής θύρας (COM3) Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

74 Ολοκληρωμένο περιβάλλον ανάπτυξης (IDE) Αφού ολοκληρωθεί η εγκατάσταση μπορούμε να ξεκινήσουμε το περιβάλλον ανάπτυξης (IDE) του Arduino [39].. Αυτό το κάνουμε εκτελώντας το αρχείο arduino.exe που βρίσκεται στην κορυφή του καταλόγου μέσα στον οποίο αποσυμπιέσαμε το αρχεία του Arduino Εικόνα 46: Το εκτελέσιμο πρόγραμμα του περιβάλλοντος ανάπτυξης του Arduino (Windows 7) Το μεγαλύτερο μέρος του περιβάλλοντος ανάπτυξης Arduino καταλαμβάνει ο επεξεργαστής κειμένου (text editor) με τον οποίο θα γράψουμε τον κώδικα του λογισμικού. Εικόνα 47: Η οθόνη του περιβάλλοντος ανάπτυξης του Arduino. 74 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

75 Πριν ξεκινήσουμε την συγγραφή του κώδικα είναι σημαντικό να βεβαιωθούμε ότι έχουμε ρυθμίσει την σωστή σειριακή θύρα (COM port) που αντιστοιχεί στον Arduino, καθώς και να συμφωνεί ο τύπος της πλακέτας Arduino που χρησιμοποιούμε με την αντίστοιχη ρύθμιση. Από το μενού επιλέγουμε Tools και μετά Board και Serial Port [38]. Εικόνα 48: Επιλογή πλακέτας ανάπτυξης Arduino Εικόνα 49: Επιλογή σειριακής θύρας Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

76 Μια πολύ χρήσιμη συντόμευση πληκτρολογίου είναι η Ctrl-U με την οποία το περιβάλλον μεταγλωττίζει την τρέχουσα έκδοση του κώδικα, και εφόσον αυτή είναι επιτυχής την αποστέλλει άμεσα μέσω της USB σύνδεσης στον Arduino. Η αποστολή του εκτελέσιμου στον Arduino (flashing) μπορεί να διαρκέσει αρκετό χρόνο, ειδικά όταν το μέγεθος του προγράμματος αρχίζει να μεγαλώνει σημαντικά. Σε αυτές τις περιπτώσεις, και αν απλά θέλουμε να ελέγξουμε την συντακτική ορθότητα του κώδικα μετά από κάποιες αλλαγές, μπορούμε απλώς να χρησιμοποιήσουμε τη συντόμευση Ctrl-R η οποία μεταγλωττίζει τον κώδικα χωρίς να τον στέλνει στον Arduino [39]. Μετά από κάθε μεταγλώττιση εμφανίζεται στο κάτω τμήμα του περιβάλλοντος ανάπτυξης ένα μήνυμα που μας ενημερώνει για το μέγεθος (σε bytes) του τελικού εκτελέσιμου κώδικα. Το μέγιστο επιτρεπόμενο μέγεθος εκτελέσιμου ποικίλει ανάλογα με τον τύπο του Arduino που χρησιμοποιείται. Για την περίπτωση του Arduino Uno το μέγιστο επιτρεπόμενο μέγεθος εκτελέσιμου είναι τα bytes. Ακόμη και αν το πρόγραμμά μας είναι σχετικά μικρό, οι βιβλιοθήκες που χρησιμοποιεί μπορεί να είναι πολύ μεγάλες και να αυξήσουν υπερβολικά το μέγεθος του τελικού εκτελέσιμου [40]. Εικόνα 50: Μέγιστο επιτρεπόμενο μέγεθος εκτελέσιμου για το Arduino Uno είναι τα bytes 76 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

77 3.2.3 Ανάπτυξη λογισμικού Βιβλιοθήκες Το λογισμικό που θα αναπτύξουμε για τον Arduino κάνει εκτενή χρήση του υλικού της Ethernet Shield. Για να μπορέσουμε να προγραμματίσουμε την κάρτα Ethernet θα πρέπει να συμπεριλάβουμε την βιβλιοθήκη Ethernet.h [41]. Η συγκεκριμένη βιβλιοθήκη κάνει χρήση της βιβλιοθήκης W5100.ο που είναι ο οδηγός (driver) του chip W5100 της WIZnet, ο οποίος βρίσκεται σε κάθε Ethernet Shield. Η W5100.ο με τη σειρά της χρησιμοποιεί σύμβολα που ορίζονται στο αρχείο SPI.h που αφορούν την επικοινωνία του Arduino με περιφερειακές μονάδες υλικού, το οποίο θα πρέπει κατά συνέπεια επίσης να συμπεριληφθεί [42]. Επιπλέον θα χρησιμοποιήσουμε την βιβλιοθήκη Servo.h. H βιβλιοθήκη αυτή επιτρέπει σε οποιονδήποτε ακροδέκτη να οδηγήσει έναν σερβομηχανισμό (servo) [17]. Θα την χρειαστούμε γιατί σύμφωνα με το σχέδιο θέλουμε η μονάδα που θα κατασκευάσουμε να είναι σε θέση να οδηγεί και σερβομηχανισμούς. Συνοψίζοντας, θα χρειαστεί να συμπεριλάβουμε τις παρακάτω οδηγίες #include: #include <SPI.h> #include <Ethernet.h> #include <Servo.h> Αρχικοποίηση Μόλις ξεκινήσει η εκτέλεση του προγράμματος θα πρέπει να γίνουν κάποια αρχικοποίηση. Η αρχικοποίηση σε ένα πρόγραμμα Arduino συμβαίνει μέσα στην ειδική μέθοδο setup(). Αρχικά θα πρέπει να ρυθμίσουμε την κάρτα δικτύου Ethernet, το οποίο συμπεριλαμβάνει την απόδοση κάποιας διεύθυνσης IP, αλλά επιπλέον και μιας διεύθυνσης MAC [43]. Σε αντίθεση με την πλειοψηφία των καρτών Ethernet για υπολογιστές οι οποίες έρχονται με μία διεύθυνση MAC απευθείας πάνω στο υλικό, οι κάρτες Ethernet Shield δεν διαθέτουν διεύθυνση MAC πάνω στο υλικό αλλά πρέπει να τους αποδοθεί μία από το λογισμικό κατά το χρόνο εκτέλεσης (run time) [11]. Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

78 Για την διεύθυνση IP είμαστε ελεύθεροι να χρησιμοποιήσουμε οποιαδήποτε διεύθυνση επιτρέπεται στο δίκτυο στο οποίο θα συνδέσουμε το Arduino. Σε περίπτωση που συνδέσουμε το Arduino στο τοπικό δίκτυό μας ή απευθείας στον υπολογιστή που εργαζόμαστε, θα χρησιμοποιήσουμε μια εσωτερική διεύθυνση IP. Συνήθως η εσωτερική διεύθυνση IP που χρησιμοποιείται για τον Arduino είναι μία εκ των ή Η διεύθυνση MAC που αντιστοιχεί στην κάρτα Ethernet Shield βρίσκεται γραμμένη σε ένα αυτοκόλλητο στην πίσω όψη της πλακέτας [11]. Στην συγκεκριμένη κάρτα που θα χρησιμοποιήσουμε η διεύθυνση MAC είναι 90:A2:DA:0D:33:89. Εικόνα 51: Η διεύθυνση MAC μιας Ethernet Shield βρίσκεται στην πίσω όψη της πλακέτας 78 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

79 Εισάγουμε τον κώδικα για την αρχικοποίηση της Ethernet μέσα στην μέθοδο setup(). Για να κάνουμε τον κώδικα λίγο πιο ευανάγνωστο ορίζουμε κάποιες σταθερές στις οποίες αποδίδουμε τις αριθμητικές τιμές των διευθύνσεων [43]. #define MY_MAC (byte[]){ 0x90, 0xA2, 0xDA, 0x0D, 0x33, 0x89 #define MY_IP IPAddress(192,168,1,177) void setup() { Ethernet.begin( MY_MAC, MY_IP ); Έχοντας αποδώσει διευθύνσεις MAC και IP στην κάρτα Ethernet μπορούμε τώρα να εγκαταστήσουμε έναν Ethernet Server στην θύρα 80, η οποία είναι η εξ ορισμού θύρα για το πρωτόκολλο HTTP [44]. void setup() { Ethernet.begin( MY_MAC, MY_IP ); EthernetServer server(80); server.begin(); Όπως έχουμε ήδη αναφέρει, η κάρτα Ethernet Shield διαθέτει επιπλέον και έναν SD Card Reader, δηλαδή υλοποιεί δύο περιφερειακά σε μία πλακέτα. Ο Arduino επικοινωνεί με τα περιφερειακά μέσω του πρωτοκόλλου SPI [12]. Το συγκεκριμένο πρωτόκολλο απαιτεί συγκεκριμένοι ακροδέκτες να χρησιμοποιούνται ως "Slave Select" για την αποφυγή συγκρούσεων (collisions) μεταξύ των περιφερειακών, τα οποία χρησιμοποιούν κοινά κανάλια ανταλλαγής δεδομένων και συγχρονισμού. Για να είμαστε σίγουροι ότι δε θα υπάρξουν συγκρούσεις μεταξύ του ελεγκτή Ethernet και του SD Card Reader, θα απενεργοποιήσουμε εντελώς τον SD Card Reader αφού προς το παρόν δεν θα τον χρησιμοποιήσουμε. Για να το κάνουμε αυτό αρκεί να θέσουμε μια υψηλή στάθμη τάσης στον ακροδέκτη 4, που είναι το "slave select" για τον SD Card Reader [45]. void setup() { pinmode(4,output); digitalwrite(4,high); Ethernet.begin( MY_MAC, MY_IP ); EthernetServer server(80); server.begin(); Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

80 Τέλος, θα χρειαστεί να δημιουργήσουμε ένα αντικείμενο της κλάσης Model, η οποία είναι εκείνη που θα γνωρίζει τις τεχνικές λεπτομέρειες των ακροδεκτών του Arduino, και θα αποτελεί τον μεσάζοντα ανάμεσα στο REST API και στο υλικό του Arduino. Προς το παρόν δεν θα ασχοληθούμε με τις λεπτομέρειες της υλοποίησης της κλάσης Model, και θα δημιουργήσουμε απλώς μια κενή κλάση ώστε ο κώδικάς μας να μπορεί να μεταγλωττιστεί χωρίς σφάλματα. class Model { ; void setup() { pinmode(4,output); digitalwrite(4,high); Model model; Ethernet.begin( MY_MAC, MY_IP ); EthernetServer server(80); server.begin(); Κύριος Βρόγχος (main loop) Σε ένα τυπικό πρόγραμμα για Arduino ο κύριος βρόγχος (main loop) υλοποιείται μέσω της μεθόδου loop() η οποία εκτελείται ατερμόνως από τον Arduino. Η λογική του κώδικα που θα τρέχει σε κάθε επανάληψη είναι απλή. Το πρόγραμμα απλά θα ελέγχει αν υπάρχει νέα σύνδεση πελάτη (client connection). Αυτό θα γίνεται μέσω της μεθόδου available() του Ethernet Server [44]. Σε περίπτωση που η μέθοδος αυτή επιστρέψει κάποια σύνδεση, τότε θα δημιουργείται ένα αντικείμενο της κλάσης Controller, στο οποίο θα πρέπει να περάσουμε τόσο μια αναφορά στο αντικείμενο της σύνδεση πελάτη, όσο και μια αναφορά στο αντικείμενο του μοντέλου. Μετά θα καλείται η μέθοδος dispatch() του Controller, η οποία θα αναλαμβάνει να εξυπηρετήσει τη σύνδεση. void loop() { if (EthernetClient client = server.available()) { Controller controller(client,model); controller.dispatch(); 80 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

81 Γίνεται φανερό ότι τόσο το αντικείμενο του Ethernet Server (server) όσο και το μοντέλο (model) θα πρέπει να βρίσκονται σε ένα πεδίο δράσης (scope) κοινό και στις δύο μεθόδους setup() και loop(). Για αυτό το λόγο θα μεταφέρουμε της δηλώσεις (declarations) των server και model έξω από τη μέθοδο setup() όπου αρχικά είχαν τοποθετηθεί, κάνοντάς τις έτσι καθολικές (global) μεταβλητές. Model model; EthernetServer server(80); void setup() { pinmode(4,output); digitalwrite(4,high); Model model; Ethernet.begin( MY_MAC, MY_IP ); EthernetServer server(80); server.begin(); Σε αυτό το σημείο δε θα ασχοληθούμε με τις λεπτομέρειες της κλάσης Controller. Προς το παρόν, και για να μπορούμε να μεταγλωττίσουμε αρκεί να ορίσουμε μια πολύ απλή έκδοση της κλάσης, η οποία απλά θα περιέχει τον κατασκευαστή (constructor) και μία μέθοδο-στέλεχος (stub method) dispatch() η οποία απλά θα κλείνει τη σύνδεση στον πελάτη. class Controller { private: EthernetClient& client; Model& model; public: Controller( EthernetClient& client, Model& model ): client(client), model(model) { ; void dispatch() { client.stop(); while (client.connected()); Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

82 Κλάσεις Μοντέλου (model classes) Στη συνέχεια θα προχωρήσουμε στην ανάπτυξη των κλάσεων Model και Pin, που θα αποτελέσουν τη διασύνδεση (interface) μεταξύ του υλικού του Arduino και του REST API που θα υλοποιηθεί από την κλάση Controller. Οι κλάσεις αυτές θα επιτρέπουν στον κώδικα-πελάτη (client code) να διαβάζουν και να τροποποιούν την κατάσταση (state) του υλικού, αλλά σε υψηλότερο επίπεδο. Η κλάση Pin αντιστοιχεί σε έναν και μόνο ακροδέκτη του Arduino. Η κλάση αυτή γνωρίζει τις αμετάβλητες ιδιότητες του ακροδέκτη, όπως το αναγνωριστικό του (ID), τον αριθμό ακροδέκτη, το αν υποστηρίζει διαμόρφωση πλάτους (PWM), αλλά και τις μεταβλητές ιδιότητές του, όπως τον τρέχοντα τρόπο λειτουργίας (mode) ή την τιμή εισόδου/εξόδου. Κάθε ανάγνωση ή τροποποίηση αυτών των ιδιοτήτων του ακροδέκτη θα γίνονται μέσω κατάλληλων μεθόδων αυτής της κλάσης. Η κλάση Model από την άλλη θα εξυπηρετεί απλά μια σύνθεση (composition) του συνόλου των ακροδεκτών, και η κύρια λειτουργικότητά της είναι αφενός να ορίσει το σύνολο των διαθέσιμων ακροδεκτών, και αφετέρου να αναζητά και να επιστρέφει αναφορές (references) σε αντικείμενα ακροδεκτών (Pin) με κριτήριο το αναγνωριστικό ή τη θέση του ακροδέκτη. Η κλάση Pin θα πρέπει να περιλαμβάνει κάποιες χαρακτηριστικές ιδιότητες του ακροδέκτη όπως: το αναγνωριστικό του, τον αριθμό ακροδέκτη, το αν υποστηρίζει PWM ή όχι, και ο τρέχων τρόπος λειτουργίας του (mode). Με εξαίρεση τον αριθμό ακροδέκτη ο οποίος θα χρησιμοποιηθεί μόνο εσωτερικά από την κλάση (για την επιτέλεση των λειτουργιών ανάγνωση και εγγραφής) τα υπόλοιπα πεδία θα πρέπει να είναι δημόσιες (public) ώστε να βρίσκονται στη διάθεση του κώδικα-πελάτη. class Pin { private: uint8_t number; public: char *id; bool pwm; char mode; ; 82 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

83 Κάποιες ιδιότητες όπως το αναγνωριστικό (id), ο αριθμός ακροδέκτη (number) και η ικανότητα διαμόρφωσης πλάτους (pwm) παραμένουν σταθερές καθ' όλη τη διάρκεια ζωής του αντικείμενου. Για αυτό το λόγο θα τα ορίσουμε ως σταθερές (constants). class Pin { private: const uint8_t number; public: const char *id; const bool pwm; char mode; ; Για την δημιουργία αντικειμένων αυτής της κλάσης θα ορίσουμε έναν κατασκευαστή (constructor) ο οποίος θα δέχεται παραμέτρους για τις τιμές αναγνωριστικού, αριθμού ακροδέκτης, και ικανότητας διαμόρφωσης πλάτους και θα τις αποθηκεύει στα αντίστοιχα πεδία της κλάσης. Για το πεδίο του τρόπου λειτουργίας (mode) θα χρησιμοποιείται εξ ορισμού πάντα η τιμή 'i' (input), καθώς το συγκεκριμένο mode είναι το ασφαλέστερο κατά την έναρξη λειτουργίας του Arduino [29]. Pin( const char *id, uint8_t number, bool pwm ): id(id), number(number), pwm(pwm), mode('i'), lastvalue(0) { Η κλάση Pin θα χρειαστεί επιπλέον μερικά ακόμη ιδιωτικά (private) πεδία για να μπορεί να λειτουργήσει, ένα αριθμητικό πεδίο για την αποθήκευση της τελευταίας τιμής εξόδου, και ένα αντικείμενο σερβομηχανισμού (Servo) που θα χρησιμοποιείται όταν ο ακροδέκτης τεθεί σε λειτουργία "servo". Το πεδίο αποθήκευσης της τελευταία τιμής εξόδου είναι απαραίτητο γιατί το API του Arduino δίνει δυνατότητα ανάγνωση για ψηφιακούς ακροδέκτες και "servo", αλλά όχι για αναλογικές εξόδους (PWM) [46]. Για αυτό το λόγο η συγκεκριμένη λειτουργικότητα θα χρειαστεί να υλοποιηθεί από εμάς με ένα απλό ενιαίο πεδίο για κάθε τύπο εξόδου. class Pin { private: uint8_t lastvalue; Servo servo; ; Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

84 Τώρα που η κλάση έχει όλα τα απαραίτητα πεδία, μπορούμε να προχωρήσουμε με τον ορισμό των διαφόρων μεθόδων μέσω των οποίων θα χειραγωγείται ο ακροδέκτης. Αρχικά θα ορίσουμε την μέθοδο setmode() με την οποία θα μπορούμε να αλλάξουμε τον τρόπο λειτουργίας του ακροδέκτη. Η συγκεκριμένη μέθοδος αρχικά θα αποθηκεύει το νέο mode στο αντίστοιχο πεδίο του αντικειμένου, ενώ ανάλογα με την τιμή του θα καλεί είτε την μέθοδο pinmode() του Arduino, είτε την μέθοδο attach() του αντίστοιχου αντικείμενου Servo για την έναρξη λειτουργίας σερβομηχανισμού. void setmode( char mode ) { switch (this->mode = mode) { case 'i': pinmode(number,input); break; case 'o': pinmode(number,output); break; case 's': servo.attach(number); break; Σε περίπτωση που ο τρόπος λειτουργίας (mode) αλλάξει από servo ('s') σε είσοδο ('i') ή έξοδο ('ο'), θα πρέπει να αποσυνδέσουμε (detach) τον ακροδέκτη από το αντικείμενο Servo, ώστε να μην υπάρξουν παρεμβολές από τη βιβλιοθήκη Servo με την κανονική λειτουργία του ακροδέκτη. void setmode( char mode ) { if (servo.attached()) { servo.detach(); switch (this->mode = mode) { Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

85 Σε αυτό το σημείο δεν θα χρησιμοποιήσουμε αμυντικό προγραμματισμό. Θεωρούμε ότι ο κώδικας-πελάτης της μεθόδου γνωρίζει τι κάνει και έτσι δεν ελέγχουμε αν η τιμή της μεταβλητής mode βρίσκεται μέσα στο σύνολο των αποδεκτών τιμών ('i', 'o' ή 's'). Ο κώδικας-πελάτης, δηλαδή στη συγκεκριμένη περίπτωση η κλάση Controller, θα πρέπει να φροντίσει να ελέγξει αν η τιμή είναι αποδεκτή. Είναι όμως απαραίτητο να εφοδιάσουμε την κλάση με μια κατάλληλη μέθοδο που να πραγματοποιεί αυτόν τον έλεγχο. bool ismodeallowed( char mode ) { return mode == 'i' mode == 'o' mode == 's'; Συνεχίζουμε με τον ορισμό των μεθόδων της κλάσης Pin, έχοντας σειρά αυτή τη φορά η μέθοδος setvalue() με την οποία μπορούμε να αποδώσουμε μια τιμή σε έναν ακροδέκτη που βρίσκεται σε λειτουργία εξόδου ή servo. Αν ο ακροδέκτης λειτουργεί ως έξοδος, τότε θα καλέσουμε μία από τις μεθόδους analogwrite() ή digitalwrite() του Arduino, ανάλογα με το αν ο ακροδέκτης υποστηρίζει PWM ή όχι, αντίστοιχα, ενώ αν ο ακροδέκτης είναι σε servo mode, τότε θα καλέσουμε την μέθοδο write() του αντικειμένου Servo. Σε κάθε περίπτωση, η τιμή αποθηκεύεται στο πεδίο lastvalue σε περίπτωση που ζητηθεί αργότερα. void setvalue( uint8_t value ) { switch (mode) { case 'o': if (pwm) { analogwrite(number,lastvalue = value); else { digitalwrite(number,lastvalue = value); break; case 's': servo.write(lastvalue = value); break; Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

86 Και σε αυτήν την περίπτωση δεν ελέγχουμε αν η τιμή είναι αποδεκτή. Θα αναθέσουμε και πάλι αυτήν την ευθύνη στον κώδικα-πελάτη. Για αυτό το σκοπό θα ορίσουμε μια μέθοδο με την οποία ο Controller θα μπορεί να ελέγξει αν μια τιμή είναι αποδεκτή πριν καλέσει την setvalue(). Για εξόδους PWM οποιαδήποτε τιμή στο εύρος είναι αποδεκτή [47], ενώ για ψηφιακές εξόδους επιτρέπονται μόνο οι τιμές HIGH και LOW [48]. Για έναν ακροδέκτη που λειτουργεί σε servo mode οι επιτρεπόμενες τιμές είναι από 0 ως 180 μοίρες [17], που αντιστοιχούν στη γωνία θέσης. bool isvalueallowed( uint8_t value ) { switch (mode) { case 'o': return pwm value == HIGH value == LOW; case 's': return value >= 0 && value <= 180; default: return false; Τέλος, ορίζουμε την μέθοδο getvalue() η οποία θα επιστρέφει την τρέχουσα τιμή του ακροδέκτη. Για ακροδέκτες που λειτουργούν ως είσοδοι θα καλούνται οι μέθοδοι analogread() ή digitalreader(), ανάλογα με το είδος της εισόδου (αναλογική ή ψηφιακή), ενώ για ακροδέκτες που λειτουργούν ως έξοδοι θα επιστρέφουμε την τελευταία τιμή που τους αποδόθηκε (lastvalue). Για να εξακριβώσουμε αν μια είσοδος είναι αναλογική ή όχι, ελέγχουμε τον πρώτο χαρακτήρα του αναγνωριστικού της. Αν ο πρώτος χαρακτήρας είναι ο 'Α' τότε θεωρούμε ότι η είσοδος είναι αναλογική, διαφορετικά ψηφιακή. 86 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

87 Για τις ανάγκες αυτής της μεθόδου, θα ορίσουμε και μια βοηθητική μέθοδο isinput() η οποία θα ελέγχει και θα επιστρέφει το αν ο συγκεκριμένος ακροδέκτης βρίσκεται σε λειτουργία εισόδου. Η μέθοδος αυτή θα οριστεί ως δημόσια (public) αφού μπορεί να φανεί χρήσιμη και στον κώδικα-πελάτη. Πρέπει να προσέξουμε ο τύπος δεδομένων που επιστρέφει η getvalue() να είναι αρκετά μεγάλος ώστε να μπορεί να επιστρέψει τιμές στο εύρος που επιστρέφει μια αναλογική είσοδος. uint16_t getvalue() { if (isinput()) { if (id[0] == 'A') { return analogread(number); else { return digitalread(number); else { return lastvalue; bool isinput() { return mode == 'i'; Η κλάση Model θα περιέχει έναν πίνακα με όλους τους διαθέσιμους ακροδέκτες καθώς και μια σταθερά που θα δηλώνει το πλήθος τους. Τα πεδία αυτά θα είναι δημόσια ώστε να έχει σε αυτά άμεση πρόσβαση ο κώδικας-πελάτης. class Model { public: static const uint8_t pincount = 15; Pin *pins[pincount]; ; Model() { Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

88 Μέσα στον κατασκευαστή (constructor) της βάσης θα αποδίδονται οι ιδιότητες του κάθε ακροδέκτη, ενώ για κάθε έναν από αυτούς θα καλείται η μέθοδος setmode() ώστε να αρχικοποιηθούν σε κατάσταση λειτουργίας εισόδου. Model() { pins[0] = new Pin( "D0", 0, false ); pins[1] = new Pin( "D1", 1, false ); pins[2] = new Pin( "D2", 2, false ); pins[3] = new Pin( "D3", 3, true ); pins[4] = new Pin( "D5", 5, true ); pins[5] = new Pin( "D6", 6, true ); pins[6] = new Pin( "D7", 7, false ); pins[7] = new Pin( "D8", 8, false ); pins[8] = new Pin( "D9", 9, true ); pins[9] = new Pin( "A0", A0, false ); pins[10] = new Pin( "A1", A1, false ); pins[11] = new Pin( "A2", A2, false ); pins[12] = new Pin( "A3", A3, false ); pins[13] = new Pin( "A4", A4, false ); pins[14] = new Pin( "A5", A5, false ); for (uint8_t i = 0; i < pincount; i++) { pins[i]->setmode(pins[i]->mode); Για εύκολη αναζήτηση ενός ακροδέκτη με βάση το αναγνωριστικό του, η κλάση Model θα ορίζει μια δημόσια μέθοδο pinbyid() η οποία θα επιστρέφει έναν δείκτη στο αντικείμενο Pin του ακροδέκτη με το συγκεκριμένο αναγνωριστικό (id). Σε περίπτωση που δεν βρεθεί κανένας ακροδέκτης με αυτό το αναγνωριστικό, τότε θα επιστρέφεται η τιμή NULL. Pin* pinbyid( const char *id ) { for (uint8_t i = 0; i < pincount; i++) { if (strcmp(pins[i]->id,id) == 0) { return pins[i]; return NULL; 88 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

89 Διεκπεραίωση σύνδεσης-πελάτη Συνεχίζουμε με την υλοποίηση της μεθόδου dispatch() που αφορά την διεκπεραίωση μιας εισερχόμενης σύνδεσης-πελάτη (client connection). Αρχικά η μέθοδος θα καλεί την μέθοδο parse() ενός αντικειμένου της κλάσης HttpRequestParser, η οποία θα γνωρίζει πως να αποκωδικοποιεί ένα εισερχόμενο αίτημα HTTP σε μια δομή δεδομένων HttpRequest. class Controller { private: HttpRequest httprequest; EthernetClient& client; Model& model; public: Controller( EthernetClient& client, Model& model ): client(client), model(model) { ; void dispatch() { HttpRequestParser parser(client); parser.parse(httprequest); client.stop(); while (client.connected()); Η δομή HttpRequest θα περιέχει διάφορες πληροφορίες που αφορούν το εισερχόμενο αίτημα όπως: το είδος της μεθόδου (GET, POST, κλπ), τη διαδρομή (path) του αιτήματος, καθώς και το πλήθος και τις τιμές των παραμέτρων που αναγνωρίσθηκαν. Τα πεδία αυτά θα είναι αλφαριθμητικά της γλώσσας C, δηλαδή πίνακες χαρακτήρων που τελειώνουν με τον μηδενικό χαρακτήρα '\0' (null-terminated strings). Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

90 Τα μεγέθη των αλφαριθμητικών θα είναι τόσο μεγάλα όσο χρειάζεται για να χωρούν τις προβλεπόμενες τιμές. Για τη φύλαξη των παραμέτρων θα χρησιμοποιήσουμε έναν πίνακα ζευγαριών ονόματος-τιμής. Επειδή το πλήθος των παραμέτρων αναμένεται να μην ξεπερνά τον αριθμό των διαθέσιμων ακροδεκτών, το μέγεθος του πίνακα αυτού θα είναι ίσο με το Model::pinCount. struct HttpRequestParam { char name[16]; char value[16]; ; struct HttpRequest { public: char method[16]; char path[128]; uint8_t paramcount; HttpRequestParam params[model::pincount]; ; Προς το παρόν δεν θα ασχοληθούμε με τις λεπτομέρειες της κλάσης HttpRequestParser. Για την ώρα αρκεί να θεωρήσουμε ότι η συγκεκριμένη κλάση θα δέχεται μια αναφορά σε ένα αντικείμενο σύνδεσης πελάτη (Client), και θα έχει μια μέθοδο parse() η οποία θα πραγματοποιεί την αποκωδικοποίηση του αιτήματος. Η μέθοδος αυτή θα λαμβάνει ως παράμετρο μια αναφορά σε μια δομή HttpRequest στην οποία θα αποθηκεύει τις τιμές που αποκωδικοποιεί. class HttpRequestParser { private: Client& client; public: HttpRequestParser( Client& client ): client(client) { ; void parse( HttpRequest& request ) { // TODO: implement actual parsing here. 90 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

91 Συνεχίζουμε με την υλοποίηση της μεθόδου dispatch(). Αυτό που έχει σειρά τώρα είναι να ελέγξουμε την τιμή του path της δομής httprequest. Σε αυτό το σημείο δεν θα ασχοληθούμε ακόμη με το είδος της μεθόδου HTTP, και θα υποθέσουμε ότι πρόκειται για μέθοδο GET. Σύμφωνα με το σχέδιο τα αιτήματα που αφορούν εντολές του REST API θα πρέπει να ξεκινούν με το πρόθεμα "/api/". Σε περίπτωση που αυτό δεν ισχύει o Controller θα στέλνει μια HTTP απάντηση με κωδικό 404 που αντιστοιχεί στο σφάλμα "Not Found" [49]. if (strncmp(httprequest.path,"/api/",5) == 0) { // further processing else { sendhttperror(f("404 Not Found")); Για την αποστολή του κωδικού σφάλματος HTTP δημιουργούμε την παρακάτω ιδιωτική μέθοδο γενικής χρήσης, που λαμβάνει ως όρισμα ένα αλφαριθμητικό που περιέχει των κωδικό και μια λεκτική περιγραφή. void sendhttperror( FlashStringHelper *httpcodeandmessage ) { client.print(f("http/1.1 ")); client.print(httpcodeandmessage); client.print(f( "\r\n" "Content-Type: text/html\r\n" "Connection: close\r\n" "\r\n" "<html>" "<head><title>")); client.print(httpcodeandmessage); client.print(f("</title><head><body><h1>")); client.print(httpcodeandmessage); client.print(f("</h1><body></html>")); Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

92 Σε περίπτωση που το path πράγματι ξεκινά με το πρόθεμα "/api/", τότε οι χαρακτήρες που ακολουθούν το πρόθεμα θα αποτελούν την εντολή του API που πρέπει να εκτελεσθεί. Για την περαιτέρω επεξεργασία του αιτήματος η dispatch() θα καλεί μια νέα ιδιωτική μέθοδο apicommand() η οποία θα λαμβάνει ως όρισμα ένα αλφαριθμητικό με την εντολή που πρέπει να εκτελεστεί. if (strncmp(httprequest.path,"/api/",5) == 0) { apicommand(httprequest.path + 5); else { sendhttperror(f("404 Not Found")); Η μέθοδος apicommand() θα ελέγχει αν η εντολή αυτή ανήκει στο σύνολο των εντολών του API. Σε περίπτωση που αναγνωρισθεί θα καλείται μια άλλη ιδιωτική μέθοδος, η οποία θα αντιστοιχεί στη συγκεκριμένη εντολή, που θα διεκπεραιώνει την εντολή. Διαφορετικά, σε περίπτωση που η εντολή δεν αναγνωρισθεί, θα καλείται μια ιδιωτική μέθοδος apiunsupportedcommand(). void apicommand( char *command ) { if (strcmp(command,"list") == 0) { apilist(); else if (strcmp(command,"mode") == 0) { apimode(); else if (strcmp(command,"write") == 0) { apiwrite(); else if (strcmp(command,"read-all") == 0) { apireadall(); else if (strcmp(command,"read") == 0) { apiread(); else { apiunsupportedcommand(); 92 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

93 Η μέθοδος apiunsupportedcommand() θα στέλνει μια απάντηση σφάλματος με τον κωδικό που αντιστοιχεί στον σφάλμα "μη υποστηριζόμενη εντολή", δηλαδή το 100. void apiunsupportedcommand() { client.print(f( "HTTP/ OK\r\n" "Content-Type: text/xml\r\n" "Access-Control-Allow-Origin: *\r\n" "Connection: close\r\n" "\r\n")); client.print(f("<error code=\"100\"/>\r\n")); Γίνεται αμέσως αντιληπτό ότι θα χρειαστεί να πραγματοποιήσουμε κάποια αναδιάρθρωση (refactoring) στον κώδικα. Καταρχήν διαισθανόμαστε ότι στο μέλλον θα χρειαστεί να αποστείλουμε κι άλλους κωδικούς σφάλματος πέρα από τον 100. Αυτό σημαίνει ότι θα ήταν καλό να ορίσουμε μια γενική ιδιωτική μέθοδο απάντησης κωδικού σφάλματος apierror(), η οποία θα δέχεται τον κωδικό ως όρισμα και θα παράγει την κατάλληλη έξοδο. void apierror( uint8_t errorcode ) { client.print(f( "HTTP/ OK\r\n" "Content-Type: text/xml\r\n" "Access-Control-Allow-Origin: *\r\n" "Connection: close\r\n" "\r\n")); client.print(f("<error code=\"")); client.print(errorcode); client.print(f("\"/>\r\n")); Έτσι, η apiunsupportedcommand() θα πρέπει τώρα να καλεί την apierror(). void apiunsupportedcommand() { apierror(100); Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

94 Κάτι άλλο που φαίνεται να χρειάζεται άμεση διόρθωση, είναι η παρουσία του φαινομενικά "μαγικού" αριθμού 100 μέσα στον κώδικα. Ορίζοντας μνημονικά ονόματα για τους κωδικούς σφαλμάτων πετυχαίνουμε να κάνουμε τον κώδικά μας πολύ πιο ευανάγνωστο και λιγότερο επιρρεπή σε λογικά λάθη. #define API_ERROR_UNSUPPORTED_COMMAND 100 #define API_ERROR_INVALID_PIN_ID 101 #define API_ERROR_UNKNOWN_MODE 102 #define API_ERROR_NON_NUMERIC_VALUE 103 #define API_ERROR_VALUE_NOT_ALLOWED 104 #define API_ERROR_PIN_NOT_AN_OUTPUT 105 #define API_ERROR_PIN_NOT_AN_INPUT 106 Έτσι, η μέθοδος apiunsupportedcommand() μπορεί τώρα να χρησιμοποιήσει το μνημονικό API_ERROR_UNSUPPORTED_COMMAND αντί του μαγικού αριθμού 100, και να γίνει πιο ευανάγνωστη. void apiunsupportedcommand() { apierror(api_error_unsupported_command); Τέλος, παρατηρούμε ότι στην μέθοδο apierror() αποστέλλεται ένας αρκετά μεγάλος αριθμός από HTTP headers οι οποίοι είναι κοινοί για κάθε XML απάντηση του REST API. Θα ήταν κακή τακτική και μεγάλη σπατάλη στο μέγεθος του εκτελέσιμου κώδικα αν αυτοί οι headers επαναλαμβάνοντας σε πολλαπλά σημεία στον κώδικα. Για αυτό, είναι καλό να εξάγουμε την εντολή που στέλνει τους headers σε μια ξεχωριστή ιδιωτική μέθοδο sendxmlheaders() η οποία θα καλείται από την apierror() αλλά και από τις υπόλοιπες μεθόδους εντολών του API. void sendxmlheaders() { client.print(f( "HTTP/ OK\r\n" "Content-Type: text/xml\r\n" "Access-Control-Allow-Origin: *\r\n" "Connection: close\r\n" "\r\n")); 94 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

95 Τροποποιούμε την apierror() ώστε να χρησιμοποιεί την sendxmlheaders(). void apierror( uint8_t errorcode ) { sendxmlheaders(); client.print(f("<error code=\"")); client.print(errorcode); client.print(f("\"/>\r\n")); Στη συνέχεια ολοκληρώνουμε την κλάση Controller υλοποιώντας τις μεθόδους εντολών του REST API. Ξεκινάμε με τη μέθοδο apilist() η οποία υλοποιεί την εντολή /api/list. Η εντολή αυτή δεν δέχεται καμία παράμετρο και απλώς διατρέχει όλα τα αντικείμενα Pin της κλάσης Model και τυπώνει τις πληροφορίες τους στην έξοδο σε μορφή XML. void apilist() { sendxmlheaders(); client.print(f("<pins>\r\n")); for (uint8_t i = 0; i < model.pincount; i++) { Pin& pin = *model.pins[i]; client.print(f("<pin id=\"")); client.print(pin.id); client.print(f("\" pwm=\"")); client.print(pin.pwm); client.print(f("\" mode=\"")); client.print(pin.mode); client.print(f("\" value=\"")); client.print(pin.getvalue()); client.print(f("\"/>\r\n")); client.print(f("</pins>\r\n")); Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

96 Συνεχίζουμε με την μέθοδο apimode() που υλοποιεί την εντολή /api/mode. Η μέθοδος αυτή θα ελέγχει αρχικά όλες τις παραμέτρους του αιτήματος. Για κάθε παράμετρο θα πρέπει το όνομα να αντιστοιχεί σε ένα αποδεκτό αναγνωριστικό ακροδέκτη, και η τιμή να αντιστοιχεί σε έναν αποδεκτό τρόπο λειτουργίας (mode). Αυτό εξακριβώνεται με χρήση των μεθόδων pinbyid() και ismodeallowed() των κλάσεων Model και Pin αντίστοιχα. Σε περίπτωση που κάτι από αυτά δεν συμφωνεί θα αποστέλλεται ο αντίστοιχος κωδικός σφάλματος. Αν όλες οι παράμετροι βρεθούν σωστές, τότε θα καλείται η μέθοδος setmode() της κλάσης Pin για να ρυθμιστεί το νέο mode, και θα παράγεται η έξοδος XML που αντιστοιχεί σε μια επιτυχή κλήση της εντολής /api/mode. void apimode() { Pin *pins[model::pincount]; for (uint8_t i = 0; i < httprequest.paramcount; i++) { Pin *pin = model.pinbyid(httprequest.params[i].name); if (pin == NULL) { apierror(api_error_invalid_pin_id); return; if (!pin->ismodeallowed(httprequest.params[i].value[0])) { apierror(api_error_unknown_mode); return; pins[i] = pin; sendxmlheaders(); client.print(f("<pins>\r\n")); for (int8_t i = 0; i < httprequest.paramcount; i++) { Pin& pin = *pins[i]; pin.setmode(httprequest.params[i].value[0]); client.print(f("<pin id=\"")); client.print(pin.id); client.print(f("\" value=\"")); client.print(pin.getvalue()); client.print(f("\"/>\r\n")); client.print(f("</pins>\r\n")); 96 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

97 Η μέθοδος apiwrite() υλοποιεί την εντολή /api/write. Και αυτή η μέθοδος θα ελέγχει για κάθε παράμετρο αν το όνομα να αντιστοιχεί σε ένα αποδεκτό αναγνωριστικό ακροδέκτη, και αν η τιμή είναι αποδεκτή για τον συγκεκριμένο ακροδέκτη αριθμητική τιμή. Οι τιμές θα πρέπει να είναι αριθμητικές και να είναι αποδεκτές σύμφωνα με την isvalueallowed() της κλάσης Pin. Σε περίπτωση που κάτι από αυτά δεν συμφωνεί θα αποστέλλεται ο αντίστοιχος κωδικός σφάλματος. Αν όλες οι παράμετροι βρεθούν σωστές, τότε θα καλείται η μέθοδος setvalue() της κλάσης Pin και θα παράγεται ένα κενό XML tag <ok/> ως έξοδος. void apiwrite() { Pin *pins[model::pincount]; uint8_t values[model::pincount]; for (uint8_t i = 0; i < httprequest.paramcount; i++) { Pin *pin = model.pinbyid(httprequest.params[i].name); if (pin == NULL) { apierror(api_error_invalid_pin_id); return; if (pin->isinput()) { apierror(api_error_pin_not_an_output); return; if (! isdigit(httprequest.params[i].value[0])) { apierror(api_error_non_numeric_value); return; uint8_t value = atoi(httprequest.params[i].value); if (! pin->isvalueallowed(value)) { apierror(api_error_value_not_allowed); return; pins[i] = pin; values[i] = value; for (uint8_t i = 0; i < httprequest.paramcount; i++) { Pin& pin = *pins[i]; pin.setvalue(values[i]); sendxmlheaders(); client.print(f("<ok/>\r\n")); Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

98 Ολοκληρώνουμε το σύνολο των εντολών του API με τις εντολές ανάγνωσεις τιμών /api/read και /api/real-all, για τις οποίες θα ορίσουμε αντίστοιχα τις μεθόδους apiread() και apireadall(). Η /api/read δέχεται από μια παράμετρο (χωρίς τιμή) για κάθε ακροδέκτη που επιθυμούμε να διαβάσουμε. Η apiread() λοιπόν θα διατρέχει όλες τις παραμέτρους και θα ελέγχει αν αντιστοιχούν σε κάποιον ακροδέκτη με μια κλήση στην μέθοδο pinbyid() της κλάσης Model. Αν ο ακροδέκτης δεν υπάρχει ή δεν λειτουργεί ως είσοδος θα επιστρέφεται ένα σχετικό μήνυμα λάθους. Διαφορετικά θα παράγεται η έξοδος XML με τις τιμές των ακροδεκτών που ζητήθηκαν. void apiread() { Pin *pins[model::pincount]; for (uint8_t i = 0; i < httprequest.paramcount; i++) { Pin *pin = model.pinbyid(httprequest.params[i].name); if (pin == NULL) { apierror(api_error_invalid_pin_id); return; if (! pin->isinput()) { apierror(api_error_pin_not_an_input); return; pins[i] = pin; sendxmlheaders(); client.print(f("<pins>\r\n")); for (uint8_t i = 0; i < httprequest.paramcount; i++) { Pin& pin = *pins[i]; client.print(f("<pin id=\"")); client.print(pin.id); client.print(f("\" value=\"")); client.print(pin.getvalue()); client.print(f("\"/>\r\n")); client.print(f("</pins>\r\n")); 98 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

99 Σε αντίθεση με την apiread(), Η apireadall() δεν δέχεται παραμέτρους και απλώς διατρέχει όλους τους ακροδέκτες του Arduino, εντοπίζει αυτούς που λειτουργούν ως είσοδοι και τυπώνει την XML με την τρέχουσα τιμή του καθενός. void apireadall() { sendxmlheaders(); client.print(f("<pins>\r\n")); for (uint8_t i = 0; i < model.pincount; i++) { Pin& pin = *model.pins[i]; if (pin.isinput()) { client.print(f("<pin id=\"")); client.print(pin.id); client.print(f("\" value=\"")); client.print(pin.getvalue()); client.print(f("\"/>\r\n")); client.print(f("</pins>\r\n")); Σε αυτό το σημείο έχουμε ολοκληρώσει την υλοποίηση του συνόλου των εντολών του REST API. Αυτό που έχει μείνει είναι να χειριστούμε σωστά το είδος της μεθόδου HTTP του αιτήματος. Όπως έχουμε σχεδιάσει, όλα τα αιτήματα του REST API θα υλοποιούνται με την μέθοδο GET. Εκτός όμως από την GET, κάποιες υλοποιήσεις πελατών HTTP δύναται να στείλουν κάποιο αίτημα με την μέθοδο OPTIONS, με το οποίο ζητούν από τον web server να ενημερωθούν για τις υποστηριζόμενες διαθέσιμες μεθόδους [50]. Μη εξυπηρέτηση αυτού του αιτήματος μπορεί να προκαλέσει πρόβλημα στην υποστήριξη συγκεκριμένων υλοποιήσεων. Είναι πολύ σημαντικό ότι στην απάντηση του αιτήματος της μεθόδου OPTIONS γνωστοποιείται προς τον πελάτη ότι ο web server δεν υποστηρίζει συμπιεσμένη κωδικοποίηση περιεχομένου (gzip), αλλά μόνο ασυμπίεστη (deflate) [51]. void dispatch() { HttpRequestParser parser(client); parser.parse(httprequest); if (strcmp(httprequest.method,"options") == 0) { client.print(f( "HTTP/ OK\r\n" Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

100 "Access-Control-Allow-Origin: *\r\n" "Access-Control-Allow-Methods: GET, OPTIONS\r\n" "Content-Encoding: deflate\r\n" "Connection: close\r\n" "\r\n")); else { if (strncmp(httprequest.path,"/api/",5) == 0) { apicommand(httprequest.path + 5); else { sendhttperror(f("404 Not Found"));... Για να ολοκληρώσουμε την μέθοδο dispatch() του Controller, απομένει να εξασφαλίσουμε ότι κάθε άλλο είδος μεθόδου πέρα από τις GET και OPTIONS θα απορρίπτεται με ένα σφάλμα HTTP με κωδικό 405, που αντιστοιχεί στο λάθος "Method Not Allowed" [49]. void dispatch() { HttpRequestParser parser(client); parser.parse(httprequest); if (strcmp(httprequest.method,"options") == 0) { client.print(f( "HTTP/ OK\r\n" "Access-Control-Allow-Origin: *\r\n" "Access-Control-Allow-Methods: GET, OPTIONS\r\n" "Content-Encoding: deflate\r\n" "Connection: close\r\n" "\r\n")); else if (strcmp(httprequest.method,"get")) { sendhttperror(f("405 Method Not Allowed")); else { if (strncmp(httprequest.path,"/api/",5) == 0) { apicommand(httprequest.path + 5); else { sendhttperror(f("404 Not Found")); Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

101 Λεκτική ανάλυση αιτήματος Το τελευταίο κομμάτι του λογισμικού που έχει απομείνει για την ολοκλήρωση του web server είναι η λεκτική ανάλυση του αιτήματος, δηλαδή η υλοποίηση της μεθόδου parse() της κλάσης HttpRequestParser. Για να διατηρήσουμε την πολυπλοκότητα του parser σε χαμηλά επίπεδα θα εκμεταλλευθούμε το γεγονός ότι από το HTTP αίτημα χρειαζόμαστε μονάχα τις πληροφορίες που βρίσκονται στην πρώτη γραμμή, οι υπόλοιπες γραμμές (headers) μπορούν να αγνοηθούν [52]. Τα κυριότερα μέρη ενός τυπικού HTTP αιτήματος Ο parser αρχικά θα μηδενίζει όλες τις θέσεις μνήμης της δομής HttpRequest. Με αυτό το τρόπο εξασφαλίζουμε ότι η δομή δεν περιέχει τυχαία σκουπίδια που βρίσκονται στη μνήμη. void parse( HttpRequest& httprequest ) { memset(&httprequest,0,sizeof(httprequest)); Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

102 Στη συνέχεια θα ξεκινάει να διαβάζει τους χαρακτήρες που αποτελούν τη μέθοδο του HTTP αιτήματος. Το όνομα της μεθόδου τελειώνει όταν διαβαστεί κάποιος χαρακτήρας κενού διαστήματος. void parse( HttpRequest& httprequest ) { memset(&httprequest,0,sizeof(httprequest)); char *cp = httprequest.method; while (client.connected()) { if (client.available()) { if ((*cp = client.read()) == ' ') { *cp = '\0'; break; cp++; Κατόπιν διαβάζουμε τους χαρακτήρες που αποτελούν το path του αιτήματος. Το path τελειώνει όταν διαβαστεί ένας χαρακτήρας κενού διαστήματος, ή όταν διαβαστεί ο χαρακτήρας '?', κάτι που δηλώνει ότι ακολουθούν παράμετροι. bool hasparams = false; cp = httprequest.path; while (client.connected()) { if (client.available()) { *cp = client.read(); if (*cp == ' ' *cp == '?') { hasparams = (*cp == '?'); *cp = '\0'; break; cp++; 102 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

103 Αμέσως μόλις διαβαστεί το path, και σε περίπτωση που διαπιστώσουμε ότι ακολουθούν παράμετροι, ξεκινάμε την ανάγνωσή τους. Οι παράμετροι μεταξύ τους διαχωρίζονται με έναν χαρακτήρα '&', ενώ σε κάθε παράμετρο το τμήμα που περιέχει την τιμή (αν υπάρχει) διαχωρίζεται από το όνομα με τον χαρακτήρα '='. Η λίστα των παραμέτρων τερματίζεται όταν βρεθεί κάποιος κενός χαρακτήρας. while (hasparams && client.connected()) { cp = httprequest.params[httprequest.paramcount].name; while (client.connected()) { if (client.available()) { *cp = client.read(); if (*cp == ' ' *cp == '=' *cp == '&') { if (*cp == '=') { *cp = '\0'; cp = httprequest.params[httprequest.paramcount].value; continue; httprequest.paramcount++; hasparams = (*cp!= ' '); *cp = '\0'; break; cp++; Σε αυτό το σημείο σταματάμε τη διαδικασία λεκτικής ανάλυσης του αιτήματος, αφού έχουμε ήδη αποκωδικοποιήσει όλες τις πληροφορίες που χρειαζόμασταν και η μέθοδος επιστρέφει. Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

104 3.2.4 Έλεγχος καλής λειτουργίας Έχοντας ολοκληρώσει την ανάπτυξη του λογισμικού του web server, θα πραγματοποιήσουμε πρώτα μερικούς ελέγχους πριν το χρησιμοποιήσουμε σε πραγματικές συνθήκες, για να επιβεβαιώσουμε ότι λειτουργεί σωστά. Θα το κάνουμε αυτό στέλνοντας αιτήματα του REST API για κάθε μια εντολή χρησιμοποιώντας ένα πρόγραμμα γραμμής εντολών (command line) ικανό να παράγει αιτήματα HTTP, όπως το wget [53]. Μετά από κάθε αίτημα θα ελέγχουμε το επιστρεφόμενο αποτέλεσμα για να δούμε αν είναι το αναμενόμενο. Παράλληλα θα συνδέσουμε στον Arduino την πλακέτα Dashboard που ήδη έχουμε στη διάθεσή μας, για να μπορούμε να εφαρμόζουμε τιμές στις εισόδους του Arduino, αλλά και για να διαπιστώσουμε αν ο Arduino αποκρίνεται σωστά σε εντολές που μεταβάλουν την κατάσταση των ακροδεκτών του. Αρχικά ελέγχουμε αν ο Arduino αποκρίνεται σωστά σε μια εντολή /api/list. Στην γραμμή εντολών του υπολογιστή μας δίνουμε την παρακάτω εντολή και εξετάζουμε το αποτέλεσμα. C:\Users\Giannis>wget -qo - <pins> <pin id="d0" pwm="0" mode="i" value="0"/> <pin id="d1" pwm="0" mode="i" value="0"/> <pin id="d2" pwm="0" mode="i" value="0"/> <pin id="d3" pwm="1" mode="i" value="0"/> <pin id="d5" pwm="1" mode="i" value="0"/> <pin id="d6" pwm="1" mode="i" value="0"/> <pin id="d7" pwm="0" mode="i" value="0"/> <pin id="d8" pwm="0" mode="i" value="0"/> <pin id="d9" pwm="1" mode="i" value="0"/> <pin id="a0" pwm="0" mode="i" value="0"/> <pin id="a1" pwm="0" mode="i" value="0"/> <pin id="a2" pwm="0" mode="i" value="0"/> <pin id="a3" pwm="0" mode="i" value="0"/> <pin id="a4" pwm="0" mode="i" value="0"/> <pin id="a5" pwm="0" mode="i" value="0"/> </pins> Όπως βλέπουμε το επιστρεφόμενο αποτέλεσμα είναι η απάντηση σε μορφή XML που αναμενόταν. Όλοι οι ακροδέκτες έχουν αρχικοποιηθεί ως είσοδοι όπως έπρεπε και γενικά η εντολή φαίνεται να λειτουργεί σωστά. 104 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

105 Στη συνέχεια δοκιμάζουμε να μεταβάλουμε τον τρόπο λειτουργίας τριών ακροδεκτών από είσοδο σε έξοδο ή servo χρησιμοποιώντας την εντολή /api/mode. Πριν το επιχειρήσουμε αυτό έχουμε φροντίσει να μετακινήσουμε στην πλακέτα Dashboard της γέφυρες jumper που αντιστοιχούν σε αυτούς τους ακροδέκτες στην θέση «έξοδος» ώστε να αποφύγουμε τυχών βραχυκυκλώματα. Στο παρακάτω παράδειγμα θα επιχειρήσουμε να αλλάξουμε τον τρόπο λειτουργίας των ακροδεκτών D2 και D3 σε ψηφιακή και αναλογική (PWM) έξοδο, αντίστοιχα, καθώς και τον τρόπο λειτουργίας του ακροδέκτη Α0 σε ελεγκτή servo. Τα διπλά εισαγωγικά γύρω από το URL είναι απαραίτητα εξαιτίας του χαρακτήρα &. C:\Users\Giannis>wget -qo - "http:// /api/mode?d2=o&d3=o&a0=s" <pins> <pin id="d2" value="0"/> <pin id="d3" value="0"/> <pin id="a0" value="0"/> </pins> Η εντολή εκτελέστηκε με επιτυχία και ο Arduino μας επέστρεψε την τρέχουσα τιμή εξόδου για καθένα από τους τρεις ακροδέκτες. Καλούμε ξανά την εντολή /api/list για να επιβεβαιώσουμε την αλλαγή του mode για αυτούς τους ακροδέκτες. C:\Users\Giannis>wget -qo - <pins> <pin id="d0" pwm="0" mode="i" value="0"/> <pin id="d1" pwm="0" mode="i" value="0"/> <pin id="d2" pwm="0" mode="o" value="0"/> <pin id="d3" pwm="1" mode="o" value="0"/> <pin id="d5" pwm="1" mode="i" value="0"/> <pin id="d6" pwm="1" mode="i" value="0"/> <pin id="d7" pwm="0" mode="i" value="0"/> <pin id="d8" pwm="0" mode="i" value="0"/> <pin id="d9" pwm="1" mode="i" value="0"/> <pin id="a0" pwm="0" mode="s" value="0"/> <pin id="a1" pwm="0" mode="i" value="0"/> <pin id="a2" pwm="0" mode="i" value="0"/> <pin id="a3" pwm="0" mode="i" value="0"/> <pin id="a4" pwm="0" mode="i" value="0"/> <pin id="a5" pwm="0" mode="i" value="0"/> </pins> Στην φωτοδίοδο που αντιστοιχεί στη έξοδο Α0 θα πρέπει ήδη να παρατηρούμε μια αμυδρή λάμψη η οποία οφείλεται στους παλμούς μικρής διάρκειας που στέλνει ο Arduino για την οδήγηση σερβομηχανισμού. Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

106 Στη συνέχεια θα επιχειρήσουμε να γράψουμε από μία τιμή σε καθένα από τους ακροδέκτες που θέσαμε σε λειτουργία εξόδου με την εντολή /api/write. Στην ψηφιακή έξοδο D2 θα δώσουμε την τιμή 1 που αντιστοιχεί σε υψηλή στάθμη εξόδου και θα πρέπει να κάνει την φωτοδίοδο να λάμψει με τη μέγιστη δυνατή ένταση. Στην αναλογική (PWM) έξοδο D3 θα δώσουμε την τιμή 16 η οποία θα έχει ως αποτέλεσμα η φωτοδίοδος να λάμψει με αισθητά χαμηλότερη ένταση. C:\Users\Giannis>wget -qo - "http:// /api/write?d2=1&d3=16" <ok/> Η απάντηση του Arduino μας πληροφορεί ότι εντολή εκτελέσθηκε επιτυχώς, ενώ ταυτόχρονα μπορούμε να επιβεβαιώσουμε οπτικά το αποτέλεσμα στην δοκιμαστική πλακέτα Dashboard. Εικόνα 52: Οπτική επιβεβαίωση καλής λειτουργίας της πλακέτας Dashboard Κατόπιν θα δοκιμάσουμε να διαβάσουμε όλες τις εισόδους του Arduino με την εντολή /api/read-all. C:\Users\Giannis>wget -qo - <pins> <pin id="d0" value="0"/> <pin id="d1" value="0"/> <pin id="d5" value="0"/> <pin id="d6" value="0"/> <pin id="d7" value="0"/> <pin id="d8" value="0"/> <pin id="d9" value="0"/> <pin id="a1" value="0"/> <pin id="a2" value="0"/> <pin id="a3" value="0"/> <pin id="a4" value="0"/> <pin id="a5" value="0"/> </pins> 106 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

107 Όπως μπορούμε να δούμε, στην έξοδο που έστειλε ο Arduino αναφέρονται όλοι οι ακροδέκτες που λειτουργούν ως είσοδοι. Συνεχίζουμε τις δοκιμές με την εντολή /api/read που απέμεινε. Θα δοκιμάσουμε να διαβάσουμε την ψηφιακή είσοδο D9 και την αναλογική είσοδο A5. Πριν στείλουμε την εντολή στον Arduino θα στρέψουμε το ποτενσιόμετρο που αντιστοιχεί στην αναλογική είσοδο Α5 περίπου στη μέση, ενώ θα κρατήσουμε πατημένο το μπουτόν που αντιστοιχεί στη ψηφιακή είσοδο D9. Κατόπιν στέλνουμε στον Arduino την παρακάτω εντολή και ελέγχουμε το αποτέλεσμα. C:\Users\Giannis>wget -qo - "http:// /api/read?d9&a5" <pins> <pin id="d9" value="1"/> <pin id="a5" value="528"/> </pins> Το αποτέλεσμα φαίνεται σωστό. Παρατηρούμε ότι η τιμή της εισόδου της D9 άλλαξε από 0 σε 1 που σημαίνει ότι ο Arduino διαβάζει υψηλή στάθμη, ενώ η αναλογική είσοδος Α5 διαβάζει την τιμή 528 που φαίνεται σωστή αφού βρίσκεται κοντά στην μέση του εύρους τιμών Τέλος ολοκληρώνουμε την δοκιμή προσπαθώντας να στείλουμε εσκεμμένα λάθος δεδομένα προς το Arduino και ελέγχοντας αν οι κωδικοί λάθους είναι οι σωστοί. C:\Users\Giannis>wget -qo - <error code="100"/> (μη υποστηριζόμενη εντολή) C:\Users\Giannis>wget -qo - <error code="101"/> (άγνωστο αναγνωριστικό ακροδέκτη) C:\Users\Giannis>wget -qo - <error code="102"/> (άγνωστος κωδικός λειτουργίας) C:\Users\Giannis>wget -qo - <error code="103"/> (μη αριθμητική τιμή) C:\Users\Giannis>wget -qo - <error code="104"/> (τιμή εκτός ορίων) C:\Users\Giannis>wget -qo - <error code="105"/> (ο ακροδέκτης δεν είναι έξοδος) C:\Users\Giannis>wget -qo - <error code="106"/> (ο ακροδέκτης δεν είναι είσοδος) Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

108 3.3 Εφαρμογή JavaScript "Control Panel" Σημείο εισόδου Το σημείο εκκίνησης της εφαρμογής JavaScript θα είναι μια απλή σελίδα HTML την οποία ο χρήστης θα πρέπει να ανοίξει σε κάποιο πρόγραμμα πλοήγησης (web browser). Η σελίδα αυτή ουσιαστικά θα παρέχει στην εφαρμογή τον απαραίτητο χώρο μέσα στον οποίο θα λαμβάνει χώρα η αλληλεπίδραση με τον χρήστη, ενώ θα αναλαμβάνει επίσης την εκκίνηση του απαραίτητου κώδικα JavaScript. <html> <head> <title>arduino Control Panel</title> <meta http-equiv="content-type" content="text/html; charset=utf-8"/> </head> <body> </body> </html> Η εφαρμογή θα κάνει χρήση της βιβλιοθήκης jquery για τους λόγους που έχουν ήδη αναφερθεί [54]. Μπορούμε να εισάγουμε την βιβλιοθήκη jquery στην εφαρμογή μέσω ενός δικτύου διανομής περιεχομένου (Content Delivery Network - CDN) όπως αυτό του Google. <html> <head> <title>arduino Control Panel</title> <meta http-equiv="content-type" content="text/html; charset=utf-8"/> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min.js"></script> </head> <body> </body> </html> 108 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

109 O κώδικας JavaScript της εφαρμογής θα τοποθετηθεί μέσα σε ένα μπλοκ <script></script> ακριβώς πριν από το τελικό tag </html> της σελίδας. Θα φροντίσουμε η εκτέλεση του κώδικα θα ξεκινήσει μόλις το πρόγραμμα πλοήγησης μας ενημερώσει ότι η σελίδα φορτώθηκε, μέσω ενός συμβάντος ready προερχόμενο από το αντικείμενο document [55]. <html> <head> <title>arduino Control Panel</title> <meta http-equiv="content-type" content="text/html; charset=utf-8"/> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min.js"></script> </head> <body> </body> <script type="text/javascript"> $(document).ready( function() { ); </script> </html> Αρχικοποίηση Σε πρώτη φάση η εφαρμογή θα πρέπει να παραγάγει την απαιτούμενη HTML για τη σχεδίαση των χειριστηρίων (controls) των ακροδεκτών. Για αυτό το λόγο αρχικά στο Arduino την εντολή /api/list για να λάβει μια λίστα με όλους τους ακροδέκτες. $(document).ready( function() { $.ajax( { url: 'http:// /api/list', type: 'GET', datatype: 'xml', success: function(xml) { var $response = $(xml); var $error = $response.find('error'); if ($error.size() > 0) { var code = $error.attr('code'); alert('error CODE: '+code); else { // create controls for all pins ); ); Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

110 Προς το παρόν παραλείπουμε τις λεπτομέρειες για το σχεδιασμό των χειριστηρίων. Αυτό που μπορούμε να παρατηρήσουμε σε αυτό το σημείο είναι ότι χρειάστηκε να γράψουμε αρκετό κώδικα για την αποστολή μιας εντολής προς το Arduino, που είναι κάτι που θα χρειαστεί να επαναλάβουμε πολλές φορές κατά την ανάπτυξη της εφαρμογής. Για αυτό το λόγο θα ήταν καλό να δημιουργήσουμε μια συνάρτηση arduinorestapi() η οποία θα εσωκλείει τα μέρη του κώδικα αυτού που μένουν σταθερά για κάθε περίπτωση ενώ θα παραμετροποιεί τα σημεία που μεταβάλλονται, όπως το είδος τις εντολής και η λογική που εκτελείται σε περίπτωση επιτυχούς εκτέλεσης. function arduinorestapi( command, onsuccess ) { $.ajax( { url: 'http:// /api/'+command, type: 'GET', datatype: 'xml', success: function(xml) { var $response = $(xml); var $error = $response.find('error'); if ($error.size() > 0) { var code = error.attr('code'); alert('error CODE: '+code); else { if (onsuccess) { onsuccess($response); ); Τώρα που έχουμε στη διάθεση μας αυτή τη συνάρτηση μπορούμε να ξαναγράψουμε τον κώδικα που ζητάει από τον Arduino τη λίστα των ακροδεκτών με πολύ απλούστερο και ευανάγνωστο τρόπο. <script type="text/javascript">..... arduinorestapi('list',function($pins) { // create controls for all pins ); </script> 110 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

111 Έχοντας στη διάθεσή μας τη λίστα με τους ακροδέκτες ($pins) μπορούμε τώρα να παραγάγουμε τα χειριστήρια (controls) της εφαρμογής και να τα εισάγουμε στη σελίδα. Αρχικά διατρέχουμε έναν-έναν όλους τους ακροδέκτες. arduinorestapi('list',function($pins) { $pins.find('pin').each( function() { // generate and insert HTML for pin control ); ); Για τον κώδικα HTML που απαιτείται για κάθε ακροδέκτη, θα δημιουργήσουμε μία συνάρτηση generatehtmlforpincontrol() η οποία θα λαμβάνει υπόψη της διάφορα στοιχεία που αφορούν τον ακροδέκτη, όπως αν είναι αναλογικός η ψηφιακός, και θα παράγει ως αποτέλεσμα τον κώδικα HTML που χρειάζεται. function generatehtmlforpincontrol( $pin ) { // TODO: generate and return HTML code Έτσι, στην συνάρτηση αρχικοποίησης απλά θα χρειαστεί να καλέσουμε αυτή τη συνάρτηση για κάθε ακροδέκτη, και να προσαρτήσουμε στην σελίδα τον κώδικα που θα μας επιτρέψει. arduinorestapi('list',function($pins) { $pins.find('pin').each( function() { var html = generatehtmlforpincontrol($(this)); $('body').append(html); ); ); Όπως έχουμε σχεδιάσει, κάθε χειριστήριο θα αποτελείται από δύο τμήματα. Στο πάνω τμήμα θα υπάρχει ένα μενού επιλογών με το οποίο θα διαλέγουμε τον τρόπο λειτουργίας του ακροδέκτη (mode). Στο συγκεκριμένο μενού αποδίδουμε την τάξη "mode" έτσι ώστε να μπορέσουμε αργότερα να του αναθέσουμε κάποιο χειριστή συμβάντων (event handler) [56]. <label>mode:</label> <select class="mode"> <option value="i">input</option> <option value="o">output</option> <option value="s">servo</option> </select> Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

112 Το κάτω τμήμα του ακροδέκτη θα είναι μεταβλητό. Ουσιαστικά θα αποτελείται από τρία διαφορετικά χειριστήρια, εκ των οποίων μόνο το ένα θα είναι ορατό κάθε στιγμή. Το ποιο εκ των τριών θα είναι ορατό θα εξαρτάται από τον επιλεγμένο τρόπο λειτουργίας. Για τον τρόπο λειτουργίας εισόδου υπάρχουν δύο ενδεχόμενα. Το ένα ενδεχόμενο είναι η είσοδος να είναι αναλογική. Σε αυτή τη περίπτωση θα χρειαστεί να εμφανίσουμε ένα πεδίο κειμένου (textbox) το οποίο να είναι μόνο για ανάγνωση (read only). <label>value:</label> <input type="text" readonly value=""> Το άλλο ενδεχόμενο είναι η είσοδος να είναι ψηφιακή. Σε αυτή τη περίπτωση εμφανίζουμε ένα κουτάκι επιλογής (checkbox). Για να εξασφαλίσουμε ότι ο χρήστης δεν θα μπορεί να αλλάξει την κατάσταση αυτού του κουτιού θα το ορίσουμε ως απενεργοποιημένο (disabled). <label>status:</label> <input type="checkbox" disabled> Για τον τρόπο λειτουργίας εξόδου υπάρχουν και πάλι δύο ενδεχόμενα. Το ένα ενδεχόμενο είναι η έξοδος να είναι αναλογική (PWM). Σε αυτή τη περίπτωση θα χρειαστεί να εμφανίσουμε ένα πεδίο κειμένου (textbox) στο οποίο ο χρήστης θα μπορεί να εισάγει την τιμή που επιθυμεί. <label>value:</label> <input type="text" value=""> Το άλλο ενδεχόμενο είναι η έξοδος να είναι ψηφιακή. Σε αυτή τη περίπτωση εμφανίζουμε ένα κουτάκι επιλογής (checkbox) του οποίου την κατάσταση θα μπορεί να αλλάξει ο χρήστης όπως επιθυμεί. <label>status:</label> <input type="checkbox > 112 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

113 Τέλος, για το mode servo θα εμφανίσουμε απλώς ένα πεδίο κειμένου (textbox) στο οποίο ο χρήστης θα μπορεί να εισάγει την τιμή που επιθυμεί. <label>angle:</label> <input type="text" value=""> Καθένα από αυτά τα τρία χειριστήρια θα πρέπει να περιστοιχιστεί από ένα <div> μπλοκ, το καθένα διαφορετικής τάξης (class), ώστε να μπορούμε προγραμματιστικά να εμφανίσουμε και να αποκρύψουμε όποια από αυτά επιθυμούμε. <div class="i"> <!-- input HTML --> </div> <div class="o"> <!-- output HTML --> </div> <div class="s"> <!-- servo HTML --> </div> Και τα δύο τμήματα του χειριστηρίου κάθε ακροδέκτη θα περικλείονται μέσα σε ένα μπλοκ <fieldset> όπου το id του θα είναι το αναγνωριστικό του ακροδέκτη, έτσι ώστε να μπορούμε εύκολα να αναγνωρίζουμε σε ποιον ακροδέκτη αναφέρεται κάποιο συμβάν αλλά και πως να γνωρίζουμε ακριβώς ποιο πεδίο κειμένου θα πρέπει να ενημερώσουμε όταν λάβουμε την τιμή εισόδου κάποιου ακροδέκτη. <fieldset id="..."> <legend>...</legend> <!-- pin control HTML --> </field> Με βάση τα παραπάνω, μπορούμε να ξεκινήσουμε τώρα την υλοποίηση της συνάρτησης generatehtmlforpincontrol(). Αρχικά θα πρέπει να εξάγουμε από το αντικείμενο $pin του ακροδέκτη τις διάφορες τιμές που περιέχει, καθώς θα μας χρειαστούν και τις αποθηκεύουμε στις αντίστοιχες μεταβλητές. function generatehtmlforpincontrol( $pin ) { var id = $pin.attr('id'); var pwm = $pin.attr('pwm') > 0; var mode = $pin.attr('mode'); var value = $pin.attr('value'); // TODO: generate and return HTML code Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

114 Έπειτα αποθηκεύουμε σε μια μεταβλητή την HTML που αφορά το μενού επιλογής τρόπου λειτουργίας (mode). Ανάλογα με την τιμή του τρέχοντος mode επιλέγουμε την αντίστοιχη επιλογή (option). var modehtml = '<label>mode:</label>'+ '<select class="mode">'+ '<option value="i"'+(mode == 'i'? ' selected' : '')+'>Input</option>'+ '<option value="o"'+(mode == 'o'? ' selected' : '')+'>Output</option>'+ '<option value="s"'+(mode == 's'? ' selected' : '')+'>Servo</option>'+ '</select>'; Στην συνέχεια παράγουμε και αποθηκεύουμε την HTML για το χειριστήριο εισόδου, η οποία εξαρτάται από το αν η είσοδος είναι αναλογική ή ψηφιακή. Για να εξακριβώσουμε μια από τις δύο περιπτώσεις ισχύει θα ελέγξουμε τον πρώτο χαρακτήρα του αναγνωριστικού του ακροδέκτη. Αν ο πρώτος χαρακτήρας είναι 'Α' τότε η είσοδος είναι αναλογική αλλιώς είναι ψηφιακή. Σε κάθε περίπτωση λαμβάνεται υπόψη η τρέχουσα τιμή του ακροδέκτη (value) και τροποποιείται ανάλογα ο κώδικας. Σε περίπτωση που το επιλεγμένο mode δεν είναι είσοδος, τότε φροντίζουμε να αποκρύψουμε το συγκεκριμένο μπλοκ (display:none) [57]. var inputhtml; if (id[0] == 'A') { inputhtml = '<label>value:</label>'+ '<input type="text" readonly value="'+ (mode == 'i'? value : '')+'">'; else { inputhtml = '<label>status:</label>'+ '<input type="checkbox" disabled '+ (mode == 'i' && value == 1? 'checked' : '')+'>'; inputhtml = '<div class="i" style="'+ (mode!= 'i'? 'display:none;' : '')+'">'+inputhtml+'</div>'; 114 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

115 Κατόπιν έχει σειρά η HTML για το χειριστήριο εξόδου, η οποία επίσης εξαρτάται από το αν η έξοδος είναι αναλογική ή ψηφιακή. Αυτό μπορούμε να το εξακριβώσουμε λαμβάνοντας υπόψη την τιμή pwm του ακροδέκτη. Σε κάθε περίπτωση λαμβάνεται υπόψη η τρέχουσα τιμή του ακροδέκτη και τροποποιείται ανάλογα ο κώδικας. Σε περίπτωση που το επιλεγμένο mode δεν είναι έξοδος, τότε φροντίζουμε να αποκρύψουμε το συγκεκριμένο μπλοκ. var outputhtml; if (pwm) { outputhtml = '<label>value:</label>'+ '<input type="text" value="'+ (mode == 'o'? value : '')+'">'; else { outputhtml = '<label>status:</label>'+ '<input type="checkbox" '+ (mode == 'o' && value == 1? 'checked' : '')+'>'; outputhtml = '<div class="o" style="'+ (mode!= 'o'? 'display:none;' : '')+'">'+outputhtml+'</div>'; Τέλος, παράγουμε και αποθηκεύουμε την HTML για το χειριστήριο servo. Λαμβάνουμε υπόψη την τρέχουσα τιμή του ακροδέκτη (value) και τροποποιούμε ανάλογα τον κώδικα. Σε περίπτωση που το επιλεγμένο mode δεν είναι servo, τότε φροντίζουμε να αποκρύψουμε το συγκεκριμένο μπλοκ. var servohtml = '<label>angle:</label>'+ '<input type="text" value="'+(mode == 'o'? value : '')+'">'; servohtml = '<div class="s" style="'+ (mode!= 's'? 'display:none;' : '')+'">'+servohtml+'</div>'; Αφού έχουμε στη διάθεσή μας όλα τα επί μέρους τμήματα του χειριστηρίου, μπορούμε να τα ενώσουμε όλα σε ένα ενιαίο κώδικα HTML και να τον επιστρέψουμε. Στο πεδίο <legend> του fieldset θα εμφανίσουμε το αναγνωριστικό του ακροδέκτη, στο οποίο προσάπτουμε τον χαρακτήρα '~' για να δείξουμε ποιοι ακροδέκτες υποστηρίζουν αναλογική έξοδο (PWM). return '<fieldset id="'+id+'"><legend>'+id+(pwm? '~' : '')+'</legend>'+ modehtml+inputhtml+outputhtml+servohtml+'</field>'; Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

116 Έτσι ολοκληρώσαμε την συνάρτηση generatehtmlforpincontrol(). Σε αυτό το στάδιο μπορούμε να κάνουμε την πρώτη δοκιμή της εφαρμογής ανοίγοντας την εφαρμογή σε ένα πρόγραμμα περιήγησης. Εικόνα 53: Η όψη της εφαρμογής χωρίς χρήση CSS Όπως μπορούμε να δούμε το αποτέλεσμα δείχνει ότι όντως οι πληροφορίες των ακροδεκτών διαβάστηκαν σωστά από τον Arduino, όμως τα χειριστήρια έχουν απροσδόκητη εμφάνιση και αποτέλεσμα δεν είναι ικανοποιητικό. 116 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

117 Για να βελτιώσουμε λίγο την όψη της εφαρμογής, προσθέτουμε μερικές οδηγίες CSS μέσα σε ένα <style> tag μέσα στο <head> μπλοκ της σελίδας [58]. <style> * { font-family: Verdana; body { font-size: 12px; fieldset { display: inline-block; border: #000 1px solid; background-color: #f8f8f8; margin: 10px; min-height: 70px; min-width: 100px; legend { border: #000 1px solid; background-color: #e8e8e8; padding: 2px 5px; input[type="text"] { width: 60px; </style> Όπως μπορούμε να δούμε αν φορτώσουμε ξανά τη σελίδα το αποτέλεσμα είναι τώρα πολύ πιο ικανοποιητικό και πλησιάζει περισσότερο στην εικόνα της εφαρμογής που είχαμε αρχικά σχεδιάσει. Εικόνα 54: Η όψη της εφαρμογής μετά τη χρήση CSS Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

118 3.3.3 Διαχείριση συμβάντων Έχοντας πλέον έτοιμη όλη την απαραίτητη HTML της εφαρμογής φορτωμένη στην σελίδα, σειρά έχει η ανάθεση διαχειριστών συμβάντων (event handlers) πάνω σε συγκεκριμένα χειριστήρια της σελίδας, έτσι ώστε η εφαρμογή να αντιδρά σωστά στις ενέργειες του χρήστη. Υπάρχουν δύο είδη ενεργειών, το ένα είναι η αλλαγή τρόπου λειτουργίας ακροδέκτη (mode), και το άλλο είναι η αλλαγή της τιμής ενός ακροδέκτη που λειτουργεί ως έξοδος ή servo. Για να ανιχνεύσουμε την αλλαγή του επιλεγμένου τρόπου λειτουργίας ενός ακροδέκτη, θα χρειαστεί να αναθέσουμε έναν διαχειριστή συμβάντος τύπου "change" σε όλα τα μενού επιλογής τρόπου λειτουργίας [59]. $('select.mode').change( function() { ); Το πρώτο πράγμα που πρέπει κάνουμε όταν λάβουμε ένα συμβάν αλλαγής του τρόπου λειτουργίας ενός ακροδέκτη, είναι να βρούμε σε ποιον ακριβώς ακροδέκτη αναφέρεται. Αυτό θα το κάνουμε αναζητώντας τον «γονέα» <fieldset> του μενού <select> στο οποίο έλαβε χώρα το συμβάν [60]. Η ιδιότητα id αυτού του γονέα θα μας δώσει το αναγνωριστικό του ακροδέκτη. $('select.mode').change( function() { var $modeselect = $(this); var $fieldset = $modeselect.parents('fieldset'); var id = $fieldset.attr('id'); ); Αμέσως μετά πρέπει ανιχνεύσουμε ποιο ακριβώς είναι το νέο mode που επιλέχθηκε. Αυτό μπορούμε να το βρούμε πολύ απλά ζητώντας την τιμή του χειριστηρίου του μενού. $('select.mode').change( function() { var $modeselect = $(this); var $fieldset = $modeselect.parents('fieldset'); var id = $fieldset.attr('id'); var newmode = $modeselect.val(); ); 118 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

119 Τώρα έχουμε στη διάθεσή μας και το αναγνωριστικό του ακροδέκτη αλλά και το νέο επιθυμητό τρόπο λειτουργίας. Έτσι μπορούμε να στείλουμε στον Arduino το κατάλληλο αίτημα /api/mode ώστε να ζητήσουμε την αλλαγή του τρόπου λειτουργίας. $('select.mode').change( function() { var $modeselect = $(this); var $fieldset = $modeselect.parents('fieldset'); var id = $fieldset.attr('id'); var newmode = $modeselect.val(); arduinorestapi( 'mode?'+id+'='+newmode, function($pins) { ); ); Μετά από επιτυχή εκτέλεση του αιτήματος, θα έχουμε στη διάθεσή μας τη νέα τιμή του ακροδέκτη, την οποία και θα πρέπει να εμφανίσουμε στο αντίστοιχο χειριστήριο το οποίο εξαρτάται από το είδος του ακροδέκτη αλλά και από τον νέο τρόπο λειτουργίας. arduinorestapi( 'mode?'+id+'='+newmode, function($pins) { var value = $pins.find('pin').attr('value'); switch (newmode) { case 'i':..... break; case 'o':..... break; case 's':..... break; ); Για την περίπτωση που ο ακροδέκτης θα λειτουργεί ως είσοδος, τότε αν αυτή είναι αναλογική απλώς θα αποδώσουμε την τιμή που λάβαμε στο αντίστοιχο κουτί κειμένου. Αν είναι ψηφιακή τότε επιλέγουμε ή αποεπιλέγουμε την ιδιότητα checked στο αντίστοιχο κουτάκι, ανάλογα με την τιμή. case 'i': var $div = $fieldset.find('.input'); if (id[0] == 'A') { $div.find('input').val(value); else { $div.find('input').attr('checked',value? 'checked' : ''); break; Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

120 Αν ο ακροδέκτης οριστεί να λειτουργεί ως έξοδος τότε ακολουθούμε παρόμοια διαδικασία με αυτήν της εισόδου για να εμφανίσουμε την νέα τιμή. Για να αναγνωρίσουμε αν η έξοδος είναι ψηφιακή ή αναλογική, ελέγχουμε αν το κείμενο στο tag <legend> του συγκεκριμένου χειριστηρίου περιέχει τον χαρακτήρα '~'. Σε αυτήν την περίπτωση η έξοδος είναι αναλογική (PWM), αλλιώς θεωρούμε ότι είναι ψηφιακή. case 'o': var $div = $fieldset.find('.output'); if ($fieldset.find('legend').text().indexof('~')!= -1) { $div.find('input').val(value); else { $div.find('input').attr('checked',value > 0); break; Τέλος αν ο ακροδέκτης τεθεί σε λειτουργία servo, τότε απλά θέτουμε στο κουτί κειμένου που του αντιστοιχεί την αριθμητική τιμή που λάβαμε. case 's': var $div = $fieldset.find('.servo'); $div.find('input').text(value); break; Πέρα από το να εμφανίσουμε σωστά την νέα τιμή του ακροδέκτη, αυτό που πρέπει να κάνουμε είναι να κρύψουμε το χειριστήριο που αντιστοιχεί στον προηγούμενο τρόπο λειτουργίας, και κατόπιν να εμφανίσουμε το χειριστήριο που αντιστοιχεί στον νέο τρόπο λειτουργίας. Για να κρύψουμε το χειριστήριο που αντιστοιχεί στο προηγούμενο mode, αρκεί να εντοπίσουμε το <div> που είναι σε ορατή κατάσταση και να καλέσουμε την μέθοδο hide(). Για να εμφανίσουμε το χειριστήριο που αντιστοιχεί στο νέο mode αρκεί να αναζητήσουμε το <div> που έχει κλάση ίδια με το νέο επιλεγμένο mode. $fieldset.find('div:visible').hide(); $fieldset.find('.'+newmode).show(); 120 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

121 Στη συνέχεια θα ασχοληθούμε με συμβάντα που αφορούν ενέργειες του χρήστη για αλλαγή της τιμής κάποιου ακροδέκτη που λειτουργεί ως έξοδος ή ελεγκτή σερβομηχανισμού (servo). Αρχικά θα εξετάσουμε την περίπτωση που ο χρήστης αλλάζει την κατάσταση κάποιου κουτιού επιλογής (checkbox) κάποιας ψηφιακής εξόδου. Ακολουθούμε παρόμοια διαδικασία όπως κάναμε για το συμβάν αλλαγής mode για να εντοπίσουμε το αναγνωριστικό του ακροδέκτη στον οποίο αναφέρεται το συμβάν. Έπειτα ανιχνεύουμε τη νέα επιθυμητή με βάση το αν το κουτί επιλογής είναι επιλεγμένο ή όχι. Τέλος αφού έχουμε συλλέξει όλες τις απαραίτητες πληροφορίες μπορούμε να στείλουμε το κατάλληλο αίτημα /api/write στον Arduino. $('.o input[type="checkbox"]').click( function() { var checkbox = $(this); var id = checkbox.parents('fieldset').attr('id'); var value = checkbox.attr('checked')? 1 : 0; arduinorestapi('write?'+id+'='+value); ); Αμέσως μετά θα εξετάσουμε την περίπτωση που ο χρήστης αλλάζει την τιμή κάποιου κουτιού κειμένου (textbox) κάποιας αναλογικής εξόδου. Εντοπίζουμε το αναγνωριστικό του ακροδέκτη ακολουθώντας τη συνηθισμένη διαδικασία, ενώ λαμβάνουμε την νέα τιμή από το ίδιο το κουτί κειμένου. Έπειτα στέλνουμε το κατάλληλο αίτημα /api/write στον Arduino. $('.o input[type="text"]').change( function() { var textbox = $(this); var id = textbox.parents('fieldset').attr('id'); var value = parseint(textbox.val()); arduinorestapi('write?'+id+'='+value); ); Τέλος εξετάζουμε την περίπτωση που ο χρήστης αλλάζει την τιμή κάποιου κουτιού κειμένου κάποιου ακροδέκτη που λειτουργεί ως servo. Η διαδικασία είναι πανομοιότυπη με εκείνη της αναλογικής εξόδου. $('.s input').change( function() { var textbox = $(this); var id = textbox.parents('fieldset').attr('id'); var value = parseint(textbox.val()); arduinorestapi('write?'+id+'='+value); ); Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

122 3.3.4 Περιοδική ανάγνωση εισόδων Αυτό που έχει μείνει για να ολοκληρωθεί η λειτουργικότητα της εφαρμογής "Control Panel" είναι να διαβάζονται περιοδικά, π.χ. κάθε μερικά δευτερόλεπτα, οι τιμές όλων των εισόδων του Arduino, και να ενημερώνονται τα χειριστήρια των αντίστοιχων ακροδεκτών. Αυτό που χρειαζόμαστε αρχικά είναι μια συνάρτηση updateinputs() που να στέλνει ένα αίτημα /api/read-all στον Arduino, και στη συνέχεια να εμφανίζει την τιμή εισόδου κάθε ακροδέκτη στο αντίστοιχο χειριστήριο. function updateinputs() { arduinorestapi( 'read-all', function($pins) { $pins.find('pin').each( function() { var id = $(this).attr('id'); var value = $(this).attr('value'); var $div = $('fieldset#'+id+'.i'); if (id[0] == 'A') { $div.find('input').val(value); else { $div.find('input').attr('checked',value > 0); ); ); Στη συνέχεια μένει να φροντίσουμε η συγκεκριμένη συνάρτηση να καλείται ανά τακτά χρονικά διαστήματα. Επιλέγουμε αυθαίρετα μια περίοδο της τάξης των περίπου δύο δευτερολέπτων. Η γλώσσα JavaScript διαθέτει την εντολή settimeout() η οποία μπορεί να προγραμματίσει την εκτέλεση μιας διαδικασίας μετά από κάποιο χρονικό διάστημα, αλλά χωρίς επαναλήψεις [61]. Παρ' όλα αυτά μπορούμε να χρησιμοποιήσουμε αυτήν την εντολή για να εκτελέσουμε την updateinputs() ανά δύο δευτερόλεπτα, με το εξής τέχνασμα. Αρχικά ορίζουμε μια συνάρτηση scheduleinputupdate() η οποία προγραμματίζει την εκτέλεση της updateinputs() μετά από δύο δευτερόλεπτα. function scheduleinputupdate() { settimeout(updateinputs,2000); 122 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

123 Στη συνέχεια τροποποιούμε την updateinputs() έτσι ώστε να καλεί και αυτή με τη σειρά της την scheduleinputupdate(), μόλις ολοκληρωθεί επιτυχώς η προηγούμενη εκτέλεσή της. function updateinputs() { arduinorestapi( 'read-all', function(apiresponse) { apiresponse.find('pin').each( function() { var id = $(this).attr('id'); var value = $(this).attr('value'); var ipanel = $('fieldset#'+id+'.ipanel'); if (id[0] == 'A') { ipanel.find('input').val(value); else { ipanel.find('input').attr('checked',value > 0); ); scheduleinputupdate(); ); Με αυτό το τρόπο πετυχαίνουμε κάθε φορά που ολοκληρώνεται η εκτέλεση της updateinputs() να προγραμματίζεται εκ νέου η επόμενη εκτέλεσή της μετά από το διάστημα των δύο δευτερολέπτων. Το μόνο που μένει είναι να προσθέσουμε στην συνάρτηση αρχικοποίησης της εφαρμογής μια αρχική κλήση στη συνάρτηση scheduleinputupdate() έτσι ώστε να ξεκινήσει ο αέναος κύκλος ενημέρωσης των εισόδων του Arduino. arduinorestapi('list',function($pins) {..... scheduleinputupdate(); ); Εναλλακτικά κάποιος θα μπορούσε να εξετάσει το ενδεχόμενο χρήσης της συνάρτησης setinterval() η οποία εκτελεί κάποια έκφραση κάθε ένα συγκεκριμένο χρονικό διάστημα [62]. Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

124 3.4 Εικονική Κατοικία Μακέτα Επιλογή μοντέλου Για την κατασκευή της εικονικής κατοικίας μακέτας θα επιλέξουμε το παιδικό παιχνίδι «Build A House» της εταιρίας Creative Toys Ltd. [63], το οποίο σε γενικές γραμμές ανταποκρίνεται στις προδιαγραφές που είχαμε θέσει κατά τη φάση της σχεδίασης, όπως παράθυρα, ανοιγόμενη πόρτα με μεντεσέ, κλπ. Εικόνα 55: Η συσκευασία του παιχνιδιού «Build A House» της Creative Toys Ltd Το συγκεκριμένο παιχνίδι αποτελείται από δομικά υλικά βασισμένα κυρίως στο γύψο, όπως τούβλα και κεραμίδια, τα οποία ο παίκτης πρέπει να χρησιμοποιήσει για να χτίσει (στην κυριολεξία) το σπίτι. Τα υλικά στερεώνονται μεταξύ τους με μια ειδική κόλλα που περιλαμβάνεται και η οποία εφαρμόζεται ανάμεσα στα τούβλα με ένα μυστρί. Εικόνα 56: Μερικά υλικά του παιχνιδιού. Διακρίνονται τα τούβλα, η ειδική κόλλα (σκόνη) και το μυστρί για την εφαρμογή της κόλλας 124 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

125 3.4.2 Κατασκευή κατοικίας μινιατούρας Για την κατασκευή του σπιτιού ακολουθούμε σε γενικές γραμμές τις οδηγίες και τα σχεδιαγράμματα που συνοδεύουν το παιχνίδι. Το χτίσιμο ακολουθεί μια πορεία από κάτω προς τα πάνω, όπως σε ένα πραγματικό κτίριο. Το πρώτο επίπεδο τούβλων τοποθετείται πάνω σε μια ειδική βάση από πεπιεσμένο χαρτόνι, ενώ τα επόμενα επίπεδα χτίζονται πάνω στις προηγούμενες στρώσεις τούβλων. Μια πρώτη «παρατυπία» που θα κάνουμε είναι να προβλέψουμε να παραλείψουμε ένα τουβλάκι στην πίσω όψη της κατοικίας, στο πρώτο επίπεδο τούβλων. Αυτό θα μας επιτρέψει αργότερα να περάσουμε από εκεί ένα «πλακέ» καλώδιο τύπου ταινίας (ribbon cable) για τις ανάγκες σύνδεσης της κατοικίας με τον Arduino. Το κενό που μένει μπορούμε να το καλύψουμε με ένα τουβλάκι το οποίο θα σμιλέψουμε έτσι ώστε να εφαρμόζει καλά, φροντίζοντας όμως πάντα να μην το κολλήσουμε ώστε να μπορούμε ανά πάσα στιγμή να αφαιρούμε το καλώδιο σύνδεσης. Εικόνα 57: Στην πίσω όψη παραλείπουμε να κολλήσουμε ένα τουβλάκι για το πέρασμα των καλωδίων. Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

126 Επίσης θα πρέπει να προσέξουμε είναι ότι θα πρέπει να έχουμε τη δυνατότητα να τοποθετήσουμε τον ηλεκτρικό εξοπλισμό μέσα στο σπιτάκι, αλλά και να μπορούμε να τον συντηρούμε όποτε χρειαστεί. Ένας τρόπος να το πετύχουμε αυτό είναι να χωρίσουμε οριζοντίως το σπιτάκι σε δύο τμήματα, πάνω και κάτω. Αυτό μπορεί να γίνει εύκολα αφήνοντας μια στρώση από τούβλα χωρίς να κολληθεί με την ακριβώς από κάτω. Έτσι ουσιαστικά δημιουργούμε ένα «καπάκι» το οποίο μπορούμε να ανασηκώνουμε και να αποκτούμε πρόσβαση στο εσωτερικό. Εικόνα 58: Η μακέτα κατασκευάζεται σε δύο αποσπώμενα μέρη ώστε να καθίσταται εύκολη η πρόσβαση στο εσωτερικό Κατά τη χρήση της κατασκευής το «καπάκι» θα τοποθετείται στο κάτω μέρος και έτσι θα σχηματίζεται η ολοκληρωμένη κατασκευή. Επειδή το «καπάκι» δεν θα είναι κολλημένο με το κάτω μέρος, η κατασκευή θα είναι εκ των πραγμάτων ασταθής και θα πρέπει να επιστήσουμε την προσοχή μας κατά τη χρήση ώστε να μην την καταστρέψουμε από κάποια απροσεξία. Εικόνα 59: Η μακέτα ολοκληρωμένη με τα δύο μέρη τοποθετημένα το ένα πάνω στο άλλο 126 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

127 3.4.3 Κύκλωμα εικονικών συσκευών Έχοντας ολοκληρώσει την κατασκευή της κατοικίας μακέτας μπορούμε να προχωρήσουμε τώρα στην υλοποίηση του κυκλώματος των εικονικών ηλεκτρικών συσκευών του σπιτιού, δηλαδή με ηλεκτρονικά εξαρτήματα που θα εξομοιώνουν τη λειτουργία ηλεκτρικού εξοπλισμού που βρίσκεται σε μια πραγματική κατοικία. Κάποια ηλεκτρονικά εξαρτήματα όπως το κουδούνι (μπουτόν) και το σέρβο (αυτόματο άνοιγμα-κλείσιμο εξώπορτας) θα πρέπει εκ των πραγμάτων να στερεωθούν απ ευθείας πάνω στην μακέτα. Τα υπόλοιπα όμως εξαρτήματα θα μπορούσαμε να τα συγκεντρώσουμε σε μια μικρή πλακέτα breadboard ώστε να πετύχουμε μια ακόμη πιο «τακτοποιημένη» κατασκευή. Με βάση το είδος και το πλήθος των εξαρτημάτων μπορούμε να επιλέξουμε μια πλακέτα mini-breadboard, δηλαδή μια πλακέτα breadboard 170 σημείων. Η συγκεκριμένη πλακέτα περιέχει 17 κατακόρυφα διπλά κανάλια, ενώ δεν παρέχει οριζόντια κανάλια για την τροφοδοσία. Όπως συμβαίνει με τις περισσότερες πλακέτες breadboard, έτσι και η συγκεκριμένη έρχεται με ένα αυτοκόλλητο στην πίσω όψη της, το οποίο μπορούμε να εκμεταλλευτούμε για να την στερεώσουμε σε κάποιον από τους εσωτερικούς τοίχους της μακέτας. Εικόνα 60: Πλακέτα mini-breadboard 170 σημείων με αυτοκόλλητο στην πίσω όψη Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

128 Στη συνέχεια μελετάμε τον τρόπο με τον οποίο θα τοποθετήσουμε τα διάφορα ηλεκτρονικά εξαρτήματα της κατασκευής πάνω στην πλακέτα σύμφωνα με το αρχικό σχέδιο. Ιδιαίτερη προσοχή θα πρέπει να δώσουμε στην αντίσταση ισχύος η οποία έχει ιδιαίτερα μεγάλο μέγεθος σε σχέση με τα υπόλοιπα εξαρτήματα. Εικόνα 61: Ενδεικτική τοποθέτηση εξαρτημάτων κατοικίας μινιατούρας πάνω στην πλακέτα breadboard Έχοντας σχεδιάσει τον τρόπο με τον οποίο θα τακτοποιηθούν τα περισσότερα εξαρτήματα πάνω στην mini breadboard, προχωράμε στην συναρμολόγηση της πλακέτας. Προς το παρόν θα τοποθετήσουμε όλες τις φωτοδιόδους πάνω στην πλακέτα. Σε περίπτωση που χρειαστεί κάποια φωτοδίοδος να φωτίσει ένα δωμάτιο του σπιτιού που βρίσκεται σε άλλο σημείο από αυτό στο οποίο θα μπει η πλακέτα θα χρησιμοποιήσουμε κάποια καλωδίωση ώστε να οδηγήσουμε τη φωτοδίοδο στο επιθυμητό σημείο. Εικόνα 62: Η πλακέτα mini breadboard με τα υλικά τοποθετημένα επάνω της 128 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

129 3.4.4 Ολοκλήρωση Σερβομηχανισμός εξώπορτας Ο σερβομηχανισμός θα πρέπει να τοποθετηθεί έτσι ώστε να μπορεί με τον βραχίονά του να στρέφει την πόρτα μέσα σε ένα εύρος γωνίας περίπου 0 60 μοιρών. Υπάρχουν πολλές επιλογές όσον αφορά το σημείο τοποθέτησης του σέρβο και τον τρόπο ζεύξης του βραχίονα με την πόρτα. Ίσως η απλούστερη λύση είναι να τοποθετηθεί ο σερβομηχανισμός ακριβώς πίσω από την πόρτα (στο «τυφλό» σημείο της). Με αυτόν τον τρόπο πετυχαίνουμε να έχουμε πολύ μικρή απόσταση μεταξύ του σέρβο και της πόρτας και να καθίσταται εξαιρετικά εύκολη η μεταξύ τους μηχανική ζεύξη. Εικόνα 63: Κάτοψη κατοικίας-μινιατούρας με πρόταση τοποθέτησης σερβομηχανισμού εξώπορτας Για την μηχανική ζεύξη μεταξύ του σερβομηχανισμού και της πόρτας μπορούμε να χρησιμοποιήσουμε απλώς ένα οποιοδήποτε σκληρό υλικό το οποίο θα είναι ικανό να μεταφέρει την σχετικά μικρή μηχανική τάση από τον βραχίονα του σέρβο προς την πόρτα. Για παράδειγμα θα μπορούσαμε να χρησιμοποιήσουμε έναν μεταλλικό συνδετήρα ή ένα κομμάτι σκληρού σύρματος (solid core wire). Τα συγκεκριμένα υλικά είναι αρκετά εύκαμπτα ώστε να μπορέσουμε εύκολα να διαμορφώσουμε το σχήμα τους, ενώ θα διατηρήσουν αυτό το σχήμα όταν δεχτούν τη σχετικά μικρή δύναμη που απαιτείται για το άνοιγμα και κλείσιμο της πόρτας. Ο μεταλλικός σύνδεσμος μπορεί εύκολα να στερεωθεί στον βραχίονα του σερβομηχανισμού απλά περνώντας μέσα από κάποια εκ των οπών του. Προτιμούμε την εξωτερική οπή η οποία βρίσκεται στη μέγιστη απόσταση από τον άξονα περιστροφής του βραχίονα και έτσι προσφέρει τη μεγαλύτερη δυνατή ροπή. Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

130 Στην πλευρά της πόρτας θα χρειαστεί να επινοήσουμε κάποιο τρόπο ώστε να στερεώσουμε τον σύνδεσμο μέσω κάποιας ιδιοκατασκευής. Για παράδειγμα θα μπορούσαμε να βρούμε ένα μικρό κομμάτι πλαστικό σε σχήμα «Γ», στο οποίο θα ανοίξουμε από δύο οπές σε κάθε επιφάνεια. Η πλάγια οπή θα χρησιμοποιηθεί για να βιδώσουμε το πλαστικό πάνω στην ξύλινη πόρτα, ενώ στην άνωθεν οπή θα μπει το άλλο άκρο του μεταλλικού συνδέσμου. Εικόνα 64: Πρόταση άρθρωσης συνδέσμου για την πόρτα Ο μεταλλικός σύνδεσμος θα πρέπει να είναι αρκετά χαλαρός μέσα στις οπές του βραχίονα και της πλαστικής άρθρωσης της πόρτας ώστε να κινείται ελεύθερα χωρίς να «κολλάει» καθώς τα διάφορα μέρη θα κινούνται. Αυτό μπορούμε να το πετύχουμε φροντίζοντας το πάχος του μεταλλικού συνδέσμου να είναι αρκετά μικρότερο από τη διάμετρο των οπών. Για τη στερέωση του σερβομηχανισμού πάνω στην μακέτα μπορούμε να χρησιμοποιήσουμε κάποιο είδος κόλλας, όπως hot glue, ή κάποια ταινία διπλής όψης. Εναλλακτικά θα μπορούσαμε να στερεώσουμε το σώμα του σέρβο χρησιμοποιώντας κάποια «σφήνα» μεταξύ αυτού και των τοίχων [64]. Εικόνα 65: Ο σερβομηχανισμός τοποθετημένος και συνδεδεμένος με την εσωτερική πλευρά της πόρτας 130 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

131 Κουδούνι εξώπορτας Το κουδούνι (μπουτόν) για την εξώπορτα είναι το δεύτερο και τελευταίο εξάρτημα που δεν τοποθετείται επάνω στην πλακέτα αλλά πρέπει εκ των πραγμάτων να τοποθετηθεί πάνω στην μακέτα. Η θέση του πρέπει να είναι φυσικά σε κάποιον εξωτερικό τοίχο της μακέτας, κατά προτίμηση ακριβώς δίπλα στην εξώπορτα. Για να μπορέσει να ενωθεί ηλεκτρικά με το υπόλοιπο κύκλωμα θα χρειαστεί οι ακροδέκτες του μπουτόν να διαπεράσουν το παχύ στρώμα του τοίχου. Ο διακόπτης μπουτόν που έχουμε στη διάθεσή μας έχει σχετικά κοντούς ακροδέκτες που δεν ξεπερνούν τα 5mm. Το μήκος αυτός δεν επαρκεί για να διαπεράσουν τα τούβλα του τοίχου τα οποία έχουν πάχος γύρω στα mm. Το πρόβλημα αυτό μπορούμε εύκολα να το ξεπεράσουμε προεκτείνοντας τους ακροδέκτες του μπουτόν με σκληρό σύρμα μήκους mm, το οποίο μπορούμε να κολλήσουμε με καλάι (solder). Τέλος, αρκεί να καταφέρουμε να περάσουμε τους μεγάλους πλέον ακροδέκτες του μπουτόν μέσα από τον γύψινο τοίχο. Αυτό μπορεί να γίνει ανοίγοντας δύο λεπτές οπές με ένα δράπανο στα σημεία όπου οι ακροδέκτες τέμνουν τον τοίχο. Χρειάζεται ιδιαίτερη προσοχή ώστε να μην δημιουργηθούν ρωγμές στον τοίχο που πιθανόν να καταστρέψουν την μακέτα. Αφού οι ακροδέκτες του μπουτόν έχουν περάσει μέσα από τον τοίχο, μπορούμε να κολλήσουμε το μπουτόν επάνω στον τοίχο ώστε να δέσει γερά με την υπόλοιπη κατασκευή, χρησιμοποιώντας κάποια κόλλα ή ταινία διπλής όψης. Εικόνα 66: Το μπουτόν κουδουνιού τοποθετημένο δίπλα από την εξώπορτα Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

132 Επιλογή θέσης πλακέτας Η πλακέτα μεταφέρει επάνω της τα περισσότερα εξαρτήματα του κυκλώματος. Με εξαίρεση τις φωτοδιόδους των οποίων το αποτέλεσμα είναι το μόνο που επιθυμούμε να γίνεται άμεσα ορατό από το εξωτερικό της κατοικίας, τα υπόλοιπα εξαρτήματα δεν έχει ιδιαίτερη σημασία σε ποιο σημείο μέσα στην μακέτα θα βρίσκονται. Συνεπώς μπορούμε να διαλέξουμε την θέση της πλακέτας έτσι ώστε οι φωτοδίοδοι να βρίσκονται κοντά στα παράθυρα της μακέτας. Η κατοικία μινιατούρα που έχουμε στη διάθεσή μας διαθέτει έναν μεγάλο τοίχο ακριβώς πίσω από το πάνω παράθυρο ο οποίος είναι κατάλληλος για να στεγάσει πάνω του την mini breadboard. Το σημείο αυτό είναι κεντρικό και μπορεί να εξυπηρετήσει εύκολα οποιεσδήποτε καλωδιώσεις στο εσωτερικό της κατοικίας, δεν εμποδίζει το ελεύθερο άνοιγμα και κλείσιμο της πόρτας, και επιπλέον η θέση αυτή κάνει άμεσα ορατές τις φωτοδιόδους μέσα από το πάνω παράθυρο. Συνεπώς φαίνεται ότι αυτό είναι το καταλληλότερο σημείο για την τοποθέτηση της πλακέτας. Εικόνα 67: Προτεινόμενη θέση τοποθέτησης πλακέτας μέσα στην κατοικία-μινιατούρα Η πλακέτα μπορεί να στερεωθεί στην επιφάνεια του τοίχου χρησιμοποιώντας το αυτοκόλλητο που βρίσκεται στην πίσω όψη της. Η μεγάλη επιφάνειά του εγγυάται ότι θα κολληθεί πολύ γερά. Σε περίπτωση που θέλουμε να μπορούμε να αφαιρούμε εύκολα την πλακέτα όταν το επιθυμούμε, μπορούμε εναλλακτικά να χρησιμοποιήσουμε σφήνες. 132 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

133 Εσωτερικές καλωδιώσεις Οι σερβομηχανισμοί διαθέτουν συνήθως ένα μικρό πλακέ καλώδιο μήκους περίπου 10 15cm με θηλυκό βύσμα στην άκρη, το οποίο μπορεί να συνδεθεί απ ευθείας πάνω στους ακροδέκτες που έχουμε τοποθετήσει πάνω στην πλακέτα. Ιδιαίτερη προσοχή θέλει η σύνδεση του βύσματος πάνω στην πλακέτα, αφού σε περίπτωση που τοποθετηθεί ανάποδα μπορεί να προκαλέσει δυσλειτουργία ή βλάβη στο σέρβο. Για το μπουτόν απλά μπορούμε να χρησιμοποιήσουμε ένα μικρό ζευγάρι κοινά καλώδια με τα οποία θα ενώσουμε τους ακροδέκτες που προεξέχουν στην εσωτερική πλευρά του τοίχου με τις αντίστοιχες επαφές πάνω στην πλακέτα. Επειδή η πλακέτα βρίσκεται τοποθετημένη πίσω από το πάνω παράθυρο της μακέτας, και οι τρεις φωτοδίοδοι να φωτίζουν το επάνω παράθυρο της κατοικίας, ενώ το κάτω έχει μείνει αφώτιστο. Για να το λύσουμε αυτό μπορούμε να χρησιμοποιήσουμε ένα μικρό καλώδιο ώστε να οδηγήσουμε την δεύτερη λευκή φωτοδίοδο μακριά από την πλακέτα, ακριβώς πίσω από το κάτω παράθυρο. Τέλος, σε περίπτωση που το επιθυμούμε, μπορούμε να χρησιμοποιήσουμε ένα ακόμη κοντό καλώδιο για να οδηγήσουμε τον αισθητήρα θερμοκρασίας TMP36 κοντά στην αντίσταση ισχύος, έτσι ώστε να αντιλαμβάνεται γρηγορότερα τις μεταβολές της θερμοκρασίας που προκαλεί η χρήση του «καλοριφέρ». Εικόνα 68: Καλώδια σύνδεσης σερβομηχανισμού και κουδουνιού εξώπορτας Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

134 Σύνδεση με Arduino Όπως κάναμε και με τη δοκιμαστική πλακέτα Dashboard έτσι και τώρα θα χρησιμοποιήσουμε μια καλωδιοταινία (ribbon cable) για να συνδέσουμε τους τελικούς ακροδέκτες της πλακέτας της κατοικίας με τον Arduino [36]. Για τις ανάγκες σύνδεσης της κατοικίας με τον Arduino θα χρειαστούμε μια καλωδιοταινία με 12 κανάλια. Εικόνα 69: Η καλωδιοταινία που συνδέει τον Arduino με όλα τα ηλεκτρικά μέρη της κατοικίας-μινιατούρας Στην πλευρά του καλωδίου που συνδέεται στον Arduino θα χρησιμοποιήσουμε και πάλι ένα σετ στοιβαζόμενων ακροδεκτών (stackable headers) οι οποίοι θα κάνουν ευκολότερη τη σύνδεση και αποσύνδεση της κατοικίας από και προς τον Arduino [37]. Εικόνα 70: Η πλακέτα με την καλωδιοταινία και τους stackable headers στην άλλη άκρη 134 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

135 Η χρήση της καλωδιοταινίας είναι ιδιαίτερα βολική αφού επιτρέπει το εύκολο πέρασμα όλων των συνδέσεων προς τον Arduino μέσα από το τουβλάκι που είχαμε προβλέψει να μην κολλήσουμε κατά τη φάση του χτισίματος, δίνοντας έτσι τελικά στην κατασκευή ένα κομψό φινίρισμα. Εικόνα 71: Η πίσω όψη της ολοκληρωμένης κατασκευής Για να συνδέσουμε την κατοικία μινιατούρα στον Arduino απλά εισάγουμε τους stackable headers του καλωδίου στους αντίστοιχους ακροδέκτες της μονάδας. Εικόνα 72: Η κατοικία-μινιατούρα ολοκληρωμένη και συνδεδεμένη με τη μονάδα Arduino (αριστερά) Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

136 3.5 Java Βιβλιοθήκη (API) Η βιβλιοθήκη Java που θα δημιουργήσουμε για να γεφυρώσουμε το REST API του Arduino με τη γλώσσα προγραμματισμού Java θα αποτελείται κυρίως από την κλάση Arduino. Η κλάση αυτή θα παρέχει στον προγραμματιστή από μία δημόσια μέθοδο για κάθε εντολή του REST API, η οποία θα αναλαμβάνει την αποστολή των αιτημάτων προς τον Arduino, καθώς και την αποκωδικοποίηση και επιστροφή των αποτελεσμάτων. package com.giannistsakiris.arduino; public class Arduino {... Ξεκινάμε τη συγγραφή των δημόσιων μεθόδων της κλάσης «παραδοσιακά» με την μέθοδο list() η οποία θα στέλνει στον Arduino ένα αίτημα /api/list. Μετά από επιτυχή εκτέλεση η μέθοδος θα πρέπει να αποκωδικοποιήσει την XML απάντηση και να επιστρέψει τα δεδομένα τον ακροδεκτών σε μορφή συνόλου (set), συνεπώς η μέθοδος αυτή θα πρέπει να επιστρέφει μια τιμή τύπου Set. Το αίτημα /api/list δεν δέχεται παραμέτρους, άρα και η μέθοδος αυτή δεν θα δέχεται ορίσματα παραμέτρων. package com.giannistsakiris.arduino; import java.util.set; public class Arduino { public Set list() { Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

137 Η τιμή που θα επιστρέφει η μέθοδος list() θα είναι ένα σύνολο από αντικείμενα, καθένα εκ των οποίων θα αντιστοιχεί σε έναν ακροδέκτη του Arduino και θα πρέπει να μπορεί να μεταφέρει όλες τις σχετικές πληροφορίες του, όπως το αναγνωριστικό του (id), την υποστήριξη ή όχι διαμόρφωσης πλάτους (pwm), τον τρέχοντα τρόπο λειτουργίας (mode) και την τρέχουσα τιμή του (value). Για αυτό το λόγο κρίνεται σκόπιμο να δημιουργήσουμε μια κλάση PinData ικανή να αντιπροσωπεύσει της πληροφορίες ενός ακροδέκτη. Η συγκεκριμένη κλάση θα είναι ουσιαστικά μια «δομή» δεδομένων (data structure), δηλαδή θα περιέχει μονάχα δημόσια πεδία και καθόλου μεθόδους. package com.giannistsakiris.arduino; public class PinData { public String id; public char mode; public boolean pwm; public int value; Τώρα που έχουμε στη διάθεσή μας το νέο αυτό τύπο μπορούμε να τροποποιήσουμε τον τύπο επιστρεφόμενης τιμής της list() ώστε να καταστήσουμε απόλυτα σαφές ότι το σύνολο που επιστρέφει περιέχει αντικείμενα τύπου PinData. public Set<PinData> list() {... Το πρώτο πράγμα που πρέπει να κάνει η μέθοδος list() είναι να δημιουργήσει το κατάλληλο αντικείμενο URL που αντιστοιχεί στο αίτημα /api/list και να προσπαθήσει να το «ανοίξει» [65]. Τόσο η κατασκευή του αντικειμένου URL τόσο και το άνοιγμα του εγκυμονούν την πιθανότητα ρίψης εξαιρέσεων (exceptions), τις οποίες προς το παρόν θα αγνοήσουμε απλώς δηλώνοντάς τες στο τμήμα throws της μεθόδου. public Set<PinData> list() throws MalformedURLException, IOException { URL url = new URL("http:// /api/list"); url.openconnection();... Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

138 Για να περιορίσουμε το πλήθος των πιθανών διαφορετικών εξαιρέσεων που μπορούν να ριφθούν, μπορούμε να δημιουργήσουμε μια εξαίρεση ArduinoException η οποία θα περιέχει την αρχική εξαίρεση «φωλιασμένη» (nested). Η αρχική εξαίρεση-αιτία μπορεί οποιαδήποτε στιγμή να ληφθεί αργότερα μέσω της μεθόδου getcause(). package com.giannistsakiris.arduino; public class ArduinoException extends Exception { public ArduinoException(Throwable cause) { super(cause); Έχοντας στη διάθεσή μας την εξαίρεση ArduinoException μπορούμε τώρα να τροποποιήσουμε την μέθοδο list() περικλείοντας τον κώδικα που ενδέχεται να ρίψει εξαιρέσεις μέσα σε ένα τμήμα try/catch, το οποίο θα απλώς θα ρίπτει την αντίστοιχη εξαίρεση ArduinoException. public Set<PinData> list() throws ArduinoException { try { URL url = new URL("http:// /api/list"); url.openconnection(); catch (IOException ex) { throw new ArduinoException(ex);... Η απάντηση που θα λάβουμε από το Arduino θα είναι μορφής XML. Ένας απλός τρόπος για να την αποκωδικοποιήσουμε αυτήν την απάντηση είναι μέσω της κλάσης DocumentBuilder της βιβλιοθήκης ανάπτυξης της Java (JDK). Για να δημιουργήσουμε ένα αντικείμενο αυτής της κλάσης μπορούμε να χρησιμοποιήσουμε την αντίστοιχη κλάση-κατασκευαστή DocumentBuilderFactory [66]. import javax.xml.parsers.documentbuilder; import javax.xml.parsers.documentbuilderfactory; import javax.xml.parsers.parserconfigurationexception; 138 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

139 public class Arduino { public Set<PinData> list() throws ArduinoException { try { DocumentBuilder docbuilder = DocumentBuilderFactory. newinstance().newdocumentbuilder(); URL url = new URL("http:// /api/list"); url.openconnection();... catch (IOException ParserConfigurationException ex) { throw new ArduinoException(ex); Για να αποκωδικοποιήσουμε την XML απάντηση σε ένα αντικείμενο Document, το οποίο θα μπορούμε να διαχειριστούμε ευκολότερα, δεν έχουμε παρά να καλέσουμε την μέθοδο parse() του αντικειμένου DocumentBuilder που δημιουργήσαμε πρωτύτερα. Η μέθοδος αυτή δέχεται ως όρισμα το αντικείμενο ροής (stream) που μεταφέρει το κείμενο της απάντησης XML. Μπορούμε να λάβουμε μια αναφορά σε αυτό το αντικείμενο χρησιμοποιώντας την μέθοδο getinputstream() της σύνδεσης URL που ανοίξαμε προηγουμένως. public class Arduino { public Set<PinData> list() throws ArduinoException { try { DocumentBuilder docbuilder = DocumentBuilderFactory. newinstance().newdocumentbuilder(); URL url = new URL("http:// /api/list"); InputStream instream = url.openconnection().getinputstream(); Document doc = docbuilder.parse(instream);... catch (IOException ParserConfigurationException SAXException ex) { throw new ArduinoException(ex); Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

140 Τώρα που έχουμε την απάντηση XML αποκωδικοποιημένη σε ένα αντικείμενο Document, μπορούμε να δημιουργήσουμε το σύνολο με τα δεδομένα τον ακροδεκτών που θα επιστρέφει η μέθοδος list(). Αρχικά δημιουργούμε ένα κενό σύνολο τύπου HashSet, και διατρέχουμε όλα στοιχεία <pin> που περιέχονται μέσα στο αντικείμενο Document. public Set<PinData> list() throws ArduinoException { try {... Document doc = docbuilder.parse(instream); NodeList pinnodes = doc.getelementsbytagname("pin"); Set<PinData> pins = new HashSet<>(); for (int i = 0; i < pinnodes.getlength(); i++) {... return pins; catch (IOException ParserConfigurationException SAXException ex) { throw new ArduinoException(ex); Για κάθε ακροδέκτη θα δημιουργήσουμε από ένα αντικείμενο PinData, το οποίο θα γεμίσουμε με τις πληροφορίες που περιέχονται στα γνωρίσματα (attributes) του αντίστοιχου κόμβου, και στη συνέχεια θα το προσθέσουμε στο σύνολο pins. Για να αντλήσουμε τις πληροφορίες από τα γνωρίσματα ενός στοιχείου <pin> θα καλέσουμε τη μέθοδο getattributes() του αντίστοιχου κόμβου. Επειδή οι τιμές των γνωρισμάτων είναι αλφαριθμητικά (strings) σε κάποια από τα πεδία της δομής PinData θα πρέπει να φροντίσουμε να μετατρέψουμε τις τιμές στον κατάλληλο τύπο. public Set<PinData> list() throws ArduinoException { try { DocumentBuilder docbuilder = DocumentBuilderFactory. newinstance().newdocumentbuilder(); URL url = new URL("http:// /api/list"); InputStream instream = url.openconnection().getinputstream(); Document doc = docbuilder.parse(instream); NodeList pinnodes = doc.getelementsbytagname("pin"); Set<PinData> pins = new HashSet<>(); for (int i = 0; i < pinnodes.getlength(); i++) { 140 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

141 NamedNodeMap pinattr = pinnodes.item(i).getattributes(); PinData pin = new PinData(); pin.id = pinattr.getnameditem("id").getnodevalue(); pin.pwm = pinattr.getnameditem("pwm").getnodevalue().equals("1"); pin.mode = pinattr.getnameditem("mode").getnodevalue().charat(0); pin.value = Integer.parseInt(pinAttr.getNamedItem("value").getNodeValue()); pins.add(pin); return pins; catch (IOException ParserConfigurationException SAXException ex) { throw new ArduinoException(ex); Συνεχίζουμε τώρα με την μέθοδο mode() η οποία θα στέλνει στον Arduino ένα αίτημα /api/mode. Το αίτημα /api/mode λαμβάνει ως παραμέτρους ζευγάρια τιμών που αποτελούνται από το αναγνωριστικό ενός ακροδέκτη και τον επιθυμητό νέο τρόπο λειτουργίας (mode). Άρα η μέθοδος mode() θα λαμβάνει ως όρισμα ένα χάρτη (map) ο οποίος θα αντιστοιχεί αλφαριθμητικά (αναγνωριστικό) σε χαρακτήρες (τρόπος λειτουργίας). Το αίτημα επιστρέφει την τρέχουσα τιμή για κάθε ακροδέκτη του οποίου το mode άλλαξε, συνεπώς η τιμή που θα επιστρέφει θα πρέπει επίσης να είναι ένας χάρτης που θα αντιστοιχεί αλφαριθμητικά (αναγνωριστικό) σε ακεραίους (τρέχουσα τιμή). public Map<String,Integer> mode(map<string,character> params) {... Αρχικά θα πρέπει να δημιουργήσουμε το κατάλληλο URL ανάλογα με τα δεδομένα που περιέχονται στον χάρτη params. Ένας τρόπος για να το πετύχουμε αυτό είναι χρησιμοποιώντας την κλάση StringBuilder που μας παρέχει έναν εύκολο και αποδοτικό τρόπο να «χτίζουμε» αλφαριθμητικά [67]. Στην αρχή δημιουργούμε έναν string builder ο οποίος περιέχει το αρχικό κομμάτι του URL που περιέχει την IP του Arduino και το αίτημα /api/mode, στον οποίο προσαρτούμε στην συνέχεια τα διάφορα επιμέρους τμήματα των παραμέτρων του URL. Στο τέλος καλούμε την μέθοδο tostring()που θα μας επιστρέψει το τελικό αλφαριθμητικό. Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

142 public Map<String,Integer> mode(map<string,character> params) { StringBuilder sb = new StringBuilder("http:// /api/mode?"); for (String key : params.keyset()) { sb.append(key).append('=').append(params.get(key)).append('&'); sb.setlength(sb.length()-1); /* αφαίρεση τελικού '?' ή '&' */ URL url = new URL(sb.toString());... Στη συνέχεια, με τρόπο αντίστοιχο με αυτόν που ακολουθήσαμε για την list(), στέλνουμε το αίτημα στον Arduino, αποκωδικοποιούμε την απάντηση, και χτίζουμε τον χάρτη που πρέπει να επιστρέψουμε χρησιμοποιώντας τα δεδομένα της απάντησης. public Map<String,Integer> mode(map<string,character> params) throws ArduinoException { try { DocumentBuilder docbuilder = DocumentBuilderFactory. newinstance().newdocumentbuilder(); StringBuilder sb = new StringBuilder("http:// /api/mode?"); for (String key : params.keyset()) { sb.append(key).append('=').append(params.get(key)).append('&'); sb.setlength(sb.length()-1); /* αφαίρεση τελικού '?' ή '&' */ URL url = new URL(sb.toString()); InputStream instream = url.openconnection().getinputstream(); Document doc = docbuilder.parse(instream); NodeList pinnodes = doc.getelementsbytagname("pin"); Map<String,Integer> map = new HashMap<>(); for (int i = 0; i < pinnodes.getlength(); i++) { NamedNodeMap pinattr = pinnodes.item(i).getattributes(); String id = pinattr.getnameditem("id").getnodevalue(); int value = Integer.parseInt(pinAttr.getNamedItem("value").getNodeValue()); map.put(id,value); return map; catch (IOException ParserConfigurationException SAXException ex) { throw new ArduinoException(ex); 142 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

143 Παρατηρούμε σε αυτό το σημείο ότι υπάρχει κώδικας στις μεθόδους list() και mode() ο οποίος επαναλαμβάνεται και ο οποίος είναι γενικής χρήσης, και αφορά την αποστολή του αιτήματος και αποκωδικοποίηση της απάντησης. Όπως ενδείκνυται σε αυτές τις περιπτώσεις θα κάνουμε ένα πρώτο refactoring μεταφέροντας των κώδικα αυτό σε μια βοηθητική ιδιωτική μέθοδο executerestapi() η οποία θα δέχεται ως όρισμα το URL της εντολής REST API και θα επιστρέφει την αποκωδικοποιημένη απάντηση. private Document executerestapi( String restapiurl ) throws ArduinoException { try { DocumentBuilder docbuilder = DocumentBuilderFactory. newinstance().newdocumentbuilder(); URL url = new URL(restApiUrl); InputStream instream = url.openconnection().getinputstream(); return docbuilder.parse(instream); catch (SAXException IOException ParserConfigurationException ex) { throw new ArduinoException(ex); Τώρα τροποποιούμε τις list() και mode() ώστε να χρησιμοποιούν αυτήν την μέθοδο. Έτσι γλιτώνουμε την επανάληψη κώδικα (code duplication) και ο κώδικας γίνεται πιο ευανάγνωστος. public Set<PinData> list() throws ArduinoException { Document doc = executerestapi("http:// /api/list"); NodeList pinnodes = doc.getelementsbytagname("pin"); Set<PinData> pins = new HashSet<>(); for (int i = 0; i < pinnodes.getlength(); i++) {... return pins; public Map<String,Integer> mode(map<string,character> params) throws ArduinoException { StringBuilder sb = new StringBuilder("http:// /api/mode?"); for (String key : params.keyset()) { sb.append(key).append('=').append(params.get(key)).append('&'); sb.setlength(sb.length()-1); /* αφαίρεση τελικού '?' ή '&' */ Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

144 Document doc = executerestapi(sb.tostring()); NodeList pinnodes = doc.getelementsbytagname("pin"); Map<String,Integer> map = new HashMap<>(); for (int i = 0; i < pinnodes.getlength(); i++) {... return map; Ακόμη και τώρα όμως παρατηρούμε ότι υπάρχει επανάληψη κώδικα. Για παράδειγμα το αλφαριθμητικό "http:// /api/" φαίνεται να παραμένει ίδιο, αφού σε κάθε περίπτωση αλλάζει μόνο το όνομα της εντολής. Επιπλέον, η μέθοδος mode() φαίνεται να σπαταλά πολύ λογική σε λεπτομέρειες που αφορούν το χτίσιμο του αλφαριθμητικού του URL, εργασία που ενδεχομένως θα επιτελέσουν και οι υπόλοιπες μέθοδοι με παραμέτρους. Θα ήταν λοιπόν σκόπιμο να τροποποιηθεί η μέθοδος executerestapi() έτσι ώστε να δέχεται σαν ορίσματα μόνο το όνομα της εντολής (π.χ. "list" ή "mode") και ένα χάρτη (Map) με τις παραμέτρους. Έτσι η ευθύνη κατασκευής του αλφαριθμητικού του URL φεύγει από τις εξειδικευμένες μεθόδους, και μεταφέρεται αποκλειστικά στην μέθοδο executerestapi(). private Document executerestapi( String command, Map<String,String> params ) throws ArduinoException { try { DocumentBuilder docbuilder = DocumentBuilderFactory. newinstance().newdocumentbuilder(); StringBuilder sb = new StringBuilder(); sb.append("http:// /api/").append(command).append('?'); for (String key : params.keyset()) { sb.append(key).append('=').append(params.get(key)).append('&'); sb.setlength(sb.length()-1); /* αφαίρεση τελικού '?' ή '&' */ URL url = new URL(sb.toString()); InputStream instream = url.openconnection().getinputstream(); return docbuilder.parse(instream); catch (SAXException IOException ParserConfigurationException ex) { throw new ArduinoException(ex); 144 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

145 Στην συνέχεια δημιουργούμε δευτερεύουσες βοηθητικές εκδόσεις της γενικής μεθόδου executerestapi() οι οποίες θα διευκολύνουν τη χρήση της από τις μεθόδους list() και mode(). Για την μέθοδο list() θα χρησιμοποιούμε την τεχνική υπερφόρτωσης μεθόδων (method overloading) [68], και θα δημιουργήσουμε μια έκδοση η οποία δεν θα λαμβάνει παραμέτρους και θα προωθεί στην κύρια μέθοδο απλώς ένα κενό χάρτη. private Document executerestapi( String command ) throws ArduinoException { Map<String,String> params = new HashMap<>(); return executerestapi(command,params); Η μέθοδος list() μπορεί να τροποποιηθεί τώρα έτσι ώστε να κάνει χρήση της νέας έκδοσης της μεθόδου και γίνει ακόμη πιο απλή και ευανάγνωστη. public Set<PinData> list() throws ArduinoException { Document doc = executerestapi("list"); NodeList pinnodes = doc.getelementsbytagname("pin"); Set<PinData> pins = new HashSet<>(); for (int i = 0; i < pinnodes.getlength(); i++) { NamedNodeMap pinattr = pinnodes.item(i).getattributes(); PinData pin = new PinData(); pin.id = pinattr.getnameditem("id").getnodevalue(); pin.pwm = pinattr.getnameditem("pwm").getnodevalue().equals("1"); pin.mode = pinattr.getnameditem("mode").getnodevalue().charat(0); pin.value = Integer.parseInt(pinAttr.getNamedItem("value").getNodeValue()); pins.add(pin); return pins; Για την μέθοδο mode() θα δημιουργήσουμε μια έκδοση που θα μετατρέπει το όρισμα των παραμέτρων από χάρτη αλφαριθμητικού σε χαρακτήρα, σε χάρτη από αλφαριθμητικό σε αλφαριθμητικό. Επειδή τα ορίσματα αυτής της μεθόδου έχουν τον ίδιο τύπο με τα ορίσματα της κυρίας μεθόδου (String και Map), αυτό έχει ως αποτέλεσμα οι δύο μέθοδοι να έχουν την ίδια εξάλειψη (erasure) [69]. Συνεπώς σε αυτήν την περίπτωση δε μπορούμε να χρησιμοποιήσουμε την τεχνική υπερφόρτωσης μεθόδων. Αντί αυτής θα τροποποιήσουμε αναγκαστικά το όνομα της βοηθητικής μεθόδου. Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

146 private Document executerestapichar( String command, Map<String,Character> params ) throws ArduinoException { Map<String,String> newparams = new HashMap<>(); for (String key : params.keyset()) { newparams.put(key,params.get(key).tostring()); return executerestapi(command,newparams); Κατόπιν τροποποιούμε και την μέθοδο mode() έτσι ώστε να κάνει χρήση της νέας μεθόδου έτσι ώστε να γίνει και αυτή πιο καθαρογραμμένη και ευανάγνωστη. public Map<String,Integer> mode(map<string,character> params) throws ArduinoException { Document doc = executerestapichar("mode",params); NodeList pinnodes = doc.getelementsbytagname("pin"); Map<String,Integer> map = new HashMap<>(); for (int i = 0; i < pinnodes.getlength(); i++) { NamedNodeMap pinattr = pinnodes.item(i).getattributes(); String id = pinattr.getnameditem("id").getnodevalue(); int value = Integer.parseInt(pinAttr.getNamedItem("value").getNodeValue()); map.put(id,value); return map; Τώρα που έχουμε καθορίσει ένα καθαρό τρόπο για την υλοποίηση των μεθόδων, συνεχίζουμε με τις υπόλοιπες δημόσιες μεθόδους της κλάσης Arduino. Σειρά έχει η μέθοδος write() η οποία μοιάζει πολύ με την mode(). Κυριότερη διαφορά της write() είναι ότι οι τιμές των παραμέτρων της είναι αριθμητικές και όχι χαρακτήρες. Για τις ανάγκες αυτής της μεθόδου θα δημιουργήσουμε μια έκδοση της executerestapi() η οποία θα δέχεται παραμέτρους σε μορφή χάρτη αλφαριθμητικών σε ακεραίους. Μια άλλη διαφορά είναι ότι το αίτημα /api/write δεν επιστρέφει τιμές. Ως εκ τούτου ο τύπος επιστροφής της write() θα οριστεί σε αυτή την περίπτωση ως void. public void write(map<string, Integer> params) throws ArduinoException { executerestapiint("write", params); 146 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

147 private Document executerestapiint(string command, Map<String, Integer> params) throws ArduinoException { Map<String, String> newparams = new HashMap<>(); for (String key : params.keyset()) { newparams.put(key, params.get(key).tostring()); return executerestapi(command, newparams); Συνεχίζουμε υλοποιώντας τη μέθοδο read() η οποία στέλνει στον Arduino το αίτημα /api/read. Αυτό το αίτημα δέχεται ένα πλήθος από παραμέτρους χωρίς τιμές, συνεπώς η μέθοδος θα πρέπει να δέχεται ως είσοδο ένα σύνολο (Set) αλφαριθμητικών που θα αντιστοιχούν στα αναγνωριστικά των ακροδεκτών που θα διαβαστούν. Για τις ανάγκες αυτής της μεθόδου θα δημιουργήσουμε μια έκδοση της executerestapi() η οποία θα δέχεται παραμέτρους σε μορφή συνόλου, τις οποίες θα μετατρέπει σε παραμέτρους χωρίς τιμές (null). Η read() θα επιστρέφει απλά ένα χάρτη που θα περιέχει τα αναγνωριστικά των ακροδεκτών και τιμές τους, όπως γίνεται και με τη μέθοδο mode(). public Map<String,Integer> read( Set<String> pins ) throws ArduinoException { Document doc = executerestapi("read",pins); NodeList pinnodes = doc.getelementsbytagname("pin"); Map<String, Integer> map = new HashMap<>(); for (int i = 0; i < pinnodes.getlength(); i++) { NamedNodeMap pinattr = pinnodes.item(i).getattributes(); String id = pinattr.getnameditem("id").getnodevalue(); int value = Integer.parseInt(pinAttr.getNamedItem("value").getNodeValue()); map.put(id, value); return map; private Document executerestapi(string command, Set<String> params) throws ArduinoException { Map<String, String> newparams = new HashMap<>(); for (String param : params) { newparams.put(param,null); return executerestapi(command, newparams); Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

148 Ολοκληρώνουμε τις δημόσιες μεθόδους της κλάσης Arduino με την μέθοδο readall() η οποία θα αποστέλλει στον Arduino το αίτημα /api/read-all. Η μέθοδος αυτή δεν δέχεται καμία παράμετρο, ενώ η τιμή που επιστρέφει ακολουθεί την ίδια λογική με αυτήν της μεθόδου read(). public Map<String,Integer> readall() throws ArduinoException { Document doc = executerestapi("read-all"); NodeList pinnodes = doc.getelementsbytagname("pin"); Map<String, Integer> map = new HashMap<>(); for (int i = 0; i < pinnodes.getlength(); i++) { NamedNodeMap pinattr = pinnodes.item(i).getattributes(); String id = pinattr.getnameditem("id").getnodevalue(); int value = Integer.parseInt(pinAttr.getNamedItem("value").getNodeValue()); map.put(id, value); return map; Στη συνέχεια θα διαχειριστούμε της περιπτώσεις που το Arduino απαντήσει σε κάποιο αίτημά μας με κάποιον κωδικό σφάλματος. Σε αυτήν περίπτωση θα πρέπει να υψώσουμε μια κατάλληλη εξαίρεση RestApiException που θα υλοποιήσουμε, η οποία θα μεταφέρει στον προγραμματιστή τον κωδικό σφάλματος που προέκυψε. Ο κωδικός σφάλματος θα βρίσκεται στη διάθεση του χρήστη μέσω μιας μεθόδου geterrorcode(). package com.giannistsakiris.arduino; public class RestApiException extends Exception { private int errorcode; public RestApiException( int errorcode ) { this.errorcode = errorcode; public int geterrorcode() { return errorcode; 148 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

149 Κατόπιν τροποποιούμε τη μέθοδο executerestapi() έτσι ώστε να ελέγχει αν το έγγραφο που αποκωδικοποιήθηκε από την απάντηση του Arduino περιέχει έναν κόμβο <error> στην κορυφή του. Σε αυτήν την περίπτωση θα διαβάζει τον κωδικό σφάλματος από τα γνωρίσματα του κόμβου και θα υψώνει μια εξαίρεση RestApiException. Διαφορετικά θα επιστρέφει κανονικά το αντικείμενο του εγγράφου. private Document executerestapi(string command, Map<String, String> params) throws ArduinoException { try { DocumentBuilder docbuilder = DocumentBuilderFactory. newinstance().newdocumentbuilder(); StringBuilder sb = new StringBuilder(); sb.append("http://").append(host).append("/api/").append(command).append('?'); for (String key : params.keyset()) { sb.append(key).append('=').append(params.get(key)).append('&'); sb.setlength(sb.length() - 1); /* αφαίρεση τελικού '?' ή '&' */ URL url = new URL(sb.toString()); InputStream instream = url.openconnection().getinputstream(); Document document = docbuilder.parse(instream); Node root = document.getfirstchild(); if (root.getnodename().equals("error")) { Node codeattr = root.getattributes().getnameditem("code"); int errorcode = Integer.parseInt(codeAttr.getTextContent()); throw new RestApiException(errorCode); return document; catch (SAXException IOException ParserConfigurationException RestApiException ex) { throw new ArduinoException(ex); Τέλος, το μόνο που φαίνεται να έχει απομείνει είναι να αφαιρέσουμε την IP διεύθυνση από τη μέθοδο executerestapi(), και μεταφέροντάς την σε ένα ιδιωτικό πεδίο host. Αυτό θα μας δώσει τη δυνατότητα να καθορίσουμε την IP του Arduino στον κατασκευαστή (constructor) της κλάσης, και να την κάνουμε έτσι γενικής χρήσης. Φυσικά, για λόγους ευκολίας μπορούμε να συνεχίσουμε προσφέρουμε και έναν εξορισμού κατασκευαστή (default constructor) ο οποίος να αποδίδει αυτόματα την συγκεκριμένη IP διεύθυνση, όταν δεν καθορίζεται ρητά κάποια άλλη. Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

150 public class Arduino { private String host; public Arduino( String host ) { this.host = host; public Arduino() { this(" "); private Document executerestapi(string command, Map<String, String> params) throws ArduinoException { try { DocumentBuilder docbuilder = DocumentBuilderFactory. newinstance().newdocumentbuilder(); StringBuilder sb = new StringBuilder(); sb.append("http://").append(host).append("/api/").append(command).append('?');... catch (SAXException IOException ParserConfigurationException ex) { throw new ArduinoException(ex); Μοντέλο εικονικής κατοικίας Όπως έχουμε σχεδιάσει, θα δημιουργήσουμε μια κλάση ScaleModel που θα αντικατοπτρίζει την εικονική κατοικία μακέτα. Η κλάση αυτή θα αποτελεί ένα επίπεδο αφαίρεσης (abstraction layer) ανάμεσα στη βιβλιοθήκη Java που υλοποιήσαμε και στην διεπαφή χρήστη (user interface). Θα διαθέτει μεθόδους υψηλού επιπέδου, όπως opendoor(), οι οποίες θα καλούν με τη σειρά του τη βιβλιοθήκη Java του Arduino. Η κλάση ScaleModel θα εμπεριέχει ένα αντικείμενο της κλάσης Arduino που θα αποτελεί τη διεπαφή της με τη μονάδα Arduino και το REST API. Κάθε μέθοδος αυτής της κλάσης θα μεταφράζεται σε κλήση της αντίστοιχης μεθόδου της κλάσης Arduino. Η κλάση ScaleModel θα γνωρίζει τις «τεχνικές λεπτομέρειες» για την επιτέλεση λειτουργιών ανώτερου επιπέδου, π.χ. θα γνωρίζει ποια τιμή θα πρέπει να θέσει σε ποιον ακροδέκτη για να ανοίξει την πόρτα της κατοικίας και ποια για την κλείσει. 150 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

151 Το πρώτο πράγμα που θα πρέπει να φροντίσει αυτή η κλάση, θα είναι να θέσει τον κάθε ακροδέκτη του Arduino που συνδέεται σε κάποια συσκευή της εικονικής κατοικίας, στον κατάλληλο τρόπο λειτουργίας (mode). package com.giannistsakiris.arduino; import java.util.hashmap; import java.util.map; public final class ScaleModel { private Arduino arduino; public ScaleModel() throws ArduinoException { arduino = new Arduino(); Map<String,Character> modes = new HashMap<>(); modes.put("d0",'o'); /* Lamp #1 : output */ modes.put("d1",'o'); /* Lamp #2 : output */ modes.put("d2",'i'); /* Doorbell : input */ modes.put("d3",'o'); /* Red Led : output */ modes.put("d5",'o'); /* Green Led : output */ modes.put("d6",'o'); /* Blue Led : output */ modes.put("d7",'s'); /* Door : servo */ modes.put("d9",'o'); /* Heating : output */ modes.put("a0",'i'); /* Thermometer : input */ arduino.mode(modes); Στην συνέχεια ορίζουμε της μεθόδους υψηλού που επιπέδου της κλάσης. Ξεκινάμε με τις μεθόδους για το άναμμα και το σβήσιμο των λευκών φωτοδιόδων, που αντιστοιχούν στις «λάμπες» φωτισμού της κατοικίας. Οι συγκεκριμένες δίοδοι είναι συνδεδεμένες στους ψηφιακούς ακροδέκτες D0 και D1 του Arduino, και θα πρέπει να τους αποδίδονται οι τιμές 1 και 0 για το άναμμα και το σβήσιμο, αντίστοιχα. public void turnlamp1on() throws ArduinoException { Map<String,Integer> values = new HashMap<>(); values.put("d0",1); arduino.write(values); Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

152 public void turnlamp1off() throws ArduinoException { Map<String,Integer> values = new HashMap<>(); values.put("d0",0); arduino.write(values); public void turnlamp2on() throws ArduinoException { Map<String,Integer> values = new HashMap<>(); values.put("d1",1); arduino.write(values); public void turnlamp2off() throws ArduinoException { Map<String,Integer> values = new HashMap<>(); values.put("d1",0); arduino.write(values); Παρατηρούμε ότι υπάρχει μια σημαντική επαναληψιμότητα του κώδικα η οποία μπορεί εύκολα να αποφευχθεί ορίζοντας μια βοηθητική μέθοδο writesinglevalue() η οποία θα λέει στο Arduino να θέσει μια τιμή σε έναν ακροδέκτη. Η μέθοδος αυτή θα δέχεται ως ορίσματα το αναγνωριστικό του ακροδέκτη του οποίου την τιμή θέλουμε να αλλάξουμε, και την νέα τιμή την οποία επιθυμούμε να λάβει. private void writesinglevalue( String pinid, int value ) throws ArduinoException { Map<String, Integer> values = new HashMap<>(); values.put(pinid,value); arduino.write(values); Έτσι τώρα οι μέθοδοι για το άναμμα και το σβήσιμο των φωτοδιόδων μπορούν να κάνουν χρήση της βοηθητικής μεθόδου και να γραφτούν πιο απλά και ευανάγνωστα. public void turnlamp1on() throws ArduinoException { writesinglevalue("d0",1); public void turnlamp1off() throws ArduinoException { writesinglevalue("d0",0); 152 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

153 public void turnlamp2on() throws ArduinoException { writesinglevalue("d1",1); public void turnlamp2off() throws ArduinoException { writesinglevalue("d1",0); Συνεχίζουμε με τις μεθόδους που θα καθορίζουν τη φωτεινότητα καθενός χρώματος του εικονικού λαμπτήρα «LivingColors». Θα υπάρχει από μια μέθοδος για καθένα χρώμα (κόκκινο, πράσινο, μπλε) η οποία θα δέχεται μια τιμή από 0 έως 255 η οποία θα αντιστοιχεί στην επιθυμητή ένταση φωτισμού. Η τιμή αυτή θα διοχετεύεται όπως είναι στον αντίστοιχο ακροδέκτη του Arduino (D3, D5, D6). public void setredlevel( int level ) throws ArduinoException { writesinglevalue("d3",level); public void setgreenlevel( int level ) throws ArduinoException { writesinglevalue("d5",level); public void setbluelevel( int level ) throws ArduinoException { writesinglevalue("d6",level); Με τον ίδιο ακριβώς τρόπο θα ορίσουμε την μέθοδο setheatinglevel() για τη ρύθμιση της έντασης της λειτουργίας της αντίστασης-ισχύος «καλοριφέρ». Η τιμή που θα δέχεται θα είναι επίσης από 0 έως 255 και θα αντιστοιχεί στην επιθυμητή ένταση λειτουργίας του «καλοριφέρ». Η τιμή αυτή επίσης θα διοχετεύεται χωρίς καμία μετατροπή στον ακροδέκτη του Arduino στον οποίο συνδέεται το εικονικό «καλοριφέρ», δηλαδή στον ακροδέκτη D9. public void setheatinglevel( int level ) throws ArduinoException { writesinglevalue("d9",level); Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

154 Στη συνέχεια θα ορίσουμε τις μεθόδους opendoor() και closedoor() που θα χρησιμοποιηθούν για άνοιγμα και κλείσιμο της πόρτας, αντίστοιχα. Ο σερβομηχανισμός βρίσκεται συνδεδεμένος στον ακροδέκτη D7 του Arduino. Για το άνοιγμα της πόρτας αρκεί να θέσουμε τη γωνία του σερβομηχανισμού στις 45 μοίρες. Για να κλείσουμε την πόρτα αρκεί να τον επαναφέρουμε σε γωνία 0 μοιρών. public void opendoor() throws ArduinoException { writesinglevalue("d7",45); public void closedoor() throws ArduinoException { writesinglevalue("d7",0); Σε αυτό το σημείο ολοκληρώσαμε τις μεθόδους που αλλάζουν κατάσταση στις συσκευές της εικονικής κατοικίας. Στη συνέχεια θα προσθέσουμε τις μεθόδους που μας πληροφορούν για την κατάσταση κάποιων συσκευών, όπως η κατάσταση του μπουτόν του κουδουνιού της εξώπορτας (πατημένο ή όχι) ή την ένδειξη του θερμόμετρου. Όπως είναι αναμενόμενο θα χρειαστούμε κάποια βοηθητική μέθοδο αντίστοιχη της writesinglevalue(), η οποία όμως αυτή τη φορά να διαβάζει μια τιμή από τον Arduino. Θα ονομάσουμε τη μέθοδο αυτή readsinglevalue(). H μέθοδος θα δέχεται ένα μοναδικό αλφαριθμητικό όρισμα το οποίο θα αντιστοιχεί στο αναγνωριστικό του ακροδέκτη που επιθυμούμε να διαβάσουμε. Θα καλεί την μέθοδο read() της κλάσης Arduino και θα επιστρέφει ως αποτέλεσμα τη μια και μοναδική τιμή που διάβασε. private int readsinglevalue( String pinid ) throws ArduinoException { Set<String> pinids = new HashSet<>(); pinids.add(pinid); Map<String,Integer> values = arduino.read(pinids); return values.get(pinid); Τώρα μπορούμε να ορίσουμε την μέθοδο readdoorbell() η οποία θα χρησιμοποιεί την readsinglevalue() για να διαβάσει την τιμή του μπουτόν από τον ακροδέκτη D2 στον οποίο βρίσκεται συνδεδεμένο το μπουτόν του κουδουνιού εξώ- 154 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

155 πορτας. Η μέθοδος θα επιστρέφει true αν διάβασε τιμή μεγαλύτερη του 0, διαφορετικά θα επιστρέφει false. public boolean readdoorbell() throws ArduinoException { return readsinglevalue("d2") > 0; Τέλος, απομένει μόνο να ορίσουμε την μέθοδο readtemperature() η οποία θα διαβάζει την εσωτερική θερμοκρασία της εικονικής κατοικίας, μέσω του αισθητήρα TMP36 με τον οποίο την έχουμε εφοδιάσει. Και σε αυτήν την περίπτωση θα χρησιμοποιήσουμε την βοηθητική μέθοδο readsinglevalue() για να διαβάσουμε την τιμή του της αναλογικής εισόδου Α0 στην οποία βρίσκεται συνδεδεμένος ο αισθητήρας θερμοκρασίας. Για ευκολία, θα επιθυμούσαμε η τιμή που θα επιστρέφει η μέθοδος να αντιστοιχεί σε βαθμούς Κελσίου. Η τιμή όμως που επιστρέφει ο Arduino κυμαίνεται από 0 έως 1023 η οποία αντιστοιχεί σε τάση εισόδου 0 έως 5 Volt. Με βάση τα παραπάνω μπορούμε να συντάξουμε έναν μαθηματικό τύπο που να μετατρέπει την τιμή εισόδου του Arduino (I) στην αντίστοιχη τάση εισόδου (V). V I I 1023 Για να μετατρέψουμε τώρα την τάση εισόδου (V) σε βαθμούς Κελσίου θα πρέπει να λάβουμε υπόψη μας κάποια χαρακτηριστικά του αισθητήρα TMP36, όπως ότι η έξοδός του είναι 0.5 Volt στους 0 C ενώ το «βήμα» του είναι 0.01 Volt ανά 1 C [70]. Με βάση αυτά τα στοιχεία μπορούμε να συντάξουμε τον παρακάτω μαθηματικό τύπο που μετατρέπει την τάση εισόδου του Arduino (V) σε βαθμούς Κελσίου (C). V 0.5 C 100 ( V 0.5) 100 V Συνδυάζοντας τους δύο παραπάνω τύπους μπορούμε υπολογίσουμε την θερμοκρασία (C) σε βαθμούς Κελσίου από την ένδειξη εισόδου (I) του Arduino. C 100 V ( I) I 50 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

156 Έτσι τελικά η readtemperature() μπορεί να κάνει χρήση του παραπάνω απλού τύπου για να επιστρέψει την θερμοκρασία σε βαθμούς Κελσίου. public double readtemperature() throws ArduinoException { double value = readsinglevalue("a0"); return * value - 50; Τέλος, θα εξασφαλίσουμε ότι κατά την έναρξη της λειτουργίας της, οι συσκευές θα αρχικοποιηθούν σε κάποιες ευνόητες καταστάσεις π.χ. τα φώτα και το καλοριφέρ θα πρέπει να είναι σβηστά, η πόρτα του σπιτιού κλειστή, κλπ. Αυτό θα το κάνουμε μέσω μιας ιδιωτικής μεθόδου initdevices() η οποία θα καλείται από τον κατασκευαστή της κλάσης Arduino. public final class ScaleModel {... public ScaleModel() throws ArduinoException {... initdevices(); private void initdevices() throws ArduinoException { /* Φώτα σβηστά */ turnlamp1off(); turnlamp2off(); /* Λάμπα RGB σβηστή */ setredlevel(0); setgreenlevel(0); setbluelevel(0); /* Καλοριφέρ εκτός λειτουργίας */ setheatinglevel(0); /* Πόρτα κλειστή */ closedoor(); Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

157 3.5.3 Εφαρμογή GUI Φόρμα παραθύρου εφαρμογής Για τη διεπαφή χρήστη (GUI) της γραφικής εφαρμογής διαχείρισης της εικονικής κατοικίας-μινιατούρας σε Java, θα χρησιμοποιήσουμε τα εργαλεία οπτικού σχεδιασμού που διαθέτει το ολοκληρωμένο περιβάλλον ανάπτυξης NetBeans της Oracle [71]. Κάνουμε δεξί κλικ στο πακέτο (package) της εφαρμογής μας, και από το μενού επιλογών του NetBeans επιλέγουμε τη δημιουργία μιας νέα φόρμας (New JFrame Form). Στο παράθυρο διαλόγου που θα ανοίξει απλά πρέπει να εισάγουμε το όνομα της νέας φόρμας που θα φτιάξουμε. Ονομάζουμε τη νέα φόρμα ScaleModelFrame και πατάμε το κουμπί Finish. Το περιβάλλον NetBeans δημιουργεί τότε μια νέα κλάση ScaleModelFrame που αντιστοιχεί στο Frame της εφαρμογής, και την ανοίγει στον editor οπτικού σχεδιασμού. Η φόρμα αρχικά δεν περιέχει τίποτα μέσα της. Για να την γεμίσουμε μπορούμε να χρησιμοποιήσουμε τα διαθέσιμα components που υπάρχουν στην «παλέτα» που βρίσκεται στα δεξιά. Εικόνα 73: Η αρχικά κενή φόρμα της εφαρμογής και η παλέτα των components στα δεξιά Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

158 Ξεκινάμε να προσθέτουμε components από την παλέτα μέσα στην φόρμα, έτσι ώστε να δώσουμε τη μορφή που είχαμε αποφασίσει κατά τη φάση του σχεδιασμού. Σε κάθε component που προσθέτουμε φροντίζουμε να αλλάξουμε το όνομά του σε κάτι που να παραπέμπει στο σκοπό χρήσης του, όπως doortogglebutton, heatingslider, κλπ. Αυτό θα αποδειχθεί ιδιαίτερα χρήσιμο αργότερα, όταν θα αναθέσουμε διαχειριστές γεγονότων (event handlers) πάνω σε αυτά τα components. Για το άνοιγμα και το κλείσιμο της εξώπορτας, καθώς και το άναμμα και σβήσιμο των λευκών φωτοδιόδων, θα χρησιμοποιήσουμε από την παλέτα κουμπιά τύπου toggle buttons. Όσον αφορά το κείμενο των κουμπιών αυτών μπορούμε να επιλέξουμε προς το παρόν αυθαίρετα οτιδήποτε, αφού αργότερα θα αλλάξουμε το κείμενο αυτό δυναμικά ανάλογα με την κατάσταση του κουμπιού. Για τον έλεγχο των χρωματιστών φωτοδιόδων (RGB Led) καθώς και της αντίστασης ισχύος («καλοριφέρ») θα χρησιμοποιήσουμε sliders. Στις ιδιότητες αυτών των slider θα επιλέξουμε το εύρος τιμών να κυμαίνεται από 0 έως 255 ώστε να ταιριάζει με τις τιμές που δέχονται οι PWM έξοδοι του Arduino. Για την ένδειξη τις θερμοκρασίας θα χρησιμοποιήσουμε ένα απλό text field.το κείμενό του θα το αφήσουμε κενό αφού έτσι κι αλλιώς εκεί θα εμφανίζεται η ένδειξη της θερμοκρασίας που θα διαβάζουμε από τον αισθητήρα. Φροντίζουμε να θέσουμε το πεδίο αυτό ως not-editable ώστε να μη μπορεί ο χρήστης να αλλάζει την τιμή ένδειξης. Εικόνα 74: Η φόρμα της εφαρμογής αφού προσθέσαμε τα απαραίτητα components 158 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

159 Με εξαίρεση τις ετικέτες (labels) που τοποθετήσαμε, η φόρμα θα πρέπει να περιλαμβάνει τα παρακάτω components: doortogglebutton lamp1togglebutton lamp2togglebutton temperaturetextfield redslider greenslider blueslider heatingslider Σύνδεση με κλάση μοντέλου Η φόρμα που δημιουργήσαμε θα αποτελεί την διασύνδεση (interface) μεταξύ του χρήστη και της κλάσης μοντέλου ScaleModel που δημιουργήσαμε πρωτύτερα. Για να το πετύχουμε αυτό θα εισάγουμε στην φόρμα ένα ιδιωτικό πεδίο-αναφορά σε ένα αντικείμενο ScaleModel, το οποίο θα δημιουργείται μέσα στον κατασκευαστή της φόρμας. package com.giannistsakiris.arduino; public class ScaleModelFrame extends javax.swing.jframe { private ScaleModel scalemodel; public ScaleModelFrame() throws ArduinoException { initcomponents(); scalemodel = new ScaleModel(); Διαχειριστές γεγονότων (event handlers) Έχοντας τοποθετήσει όλα τα απαραίτητα components μέσα στη φόρμα της εφαρμογής, σειρά έχει τώρα να αναθέσουμε διαχειριστές γεγονότων (event handlers) σε όποια αυτά απαιτείται ώστε η εφαρμογή να αντιδρά σωστά στις εντολές του χρήστη. Για να προσθέσουμε έναν διαχειριστή γεγονότων σε ένα component απλά κάνουμε διπλό κλικ πάνω σε αυτό το component. Αυτό δημιουργεί έναν διαχειριστή γεγονότων για την πιο συνηθισμένη χρήση του component, και τον συνδέει με αυτό. Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

160 Ξεκινάμε κάνοντας διπλό κλικ πάνω στο κουμπί της πόρτας. Αυτό έχει αποτέλεσμα να δημιουργηθεί η μέθοδος doortogglebuttonactionperformed(). private void doortogglebuttonactionperformed(java.awt.event.actionevent evt) { // TODO add your handling code here: Μέσα σε αυτή τη μέθοδο θα προσθέσουμε τον κώδικα ο οποίο θα ανοίγει και θα κλείνει την πόρτα της κατοικίας-μινιατούρας. Αρχικά θα πρέπει να ελέγξουμε την κατάσταση του toggle button, δηλαδή αν πατήθηκε ή αν ξεπατήθηκε. Αυτό θα το κάνουμε χρησιμοποιώντας τη μέθοδο isselected() του doortogglebutton. private void doortogglebuttonactionperformed(java.awt.event.actionevent evt) { if (doortogglebutton.isselected()) { // κουμπί πατημένο else { // κουμπί ξεπατημένο Όταν το κουμπί είναι πατημένο η εφαρμογή θα πρέπει να πει στον Arduino (μέσω της ScaleModel) να ανοίξει την πόρτα, ενώ αν είναι ξεπατημένο, τότε να την κλείσει. Σε περίπτωση που ριφθεί κάποια εξαίρεση, θα εμφανίσουμε ένα παράθυρο με το οποίο θα ενημερώσουμε το χρήστη για το είδος του σφάλματος που προέκυψε μέσω της μεθόδου JOptionPane.showMessageDialog() που μας παρέχει η βιβλιοθήκη Swing της Java. Ανάλογα με την νέα κατάσταση του component (πατημένο ή όχι) θα πρέπει να αλλάξουμε και το κείμενο του, ώστε να ενημερώνει τον χρήστη για το ποιο αποτέλεσμα θα επιφέρει η επόμενη χρήση του. private void doortogglebuttonactionperformed(java.awt.event.actionevent evt) { try { if (doortogglebutton.isselected()) { scalemodel.opendoor(); doortogglebutton.settext("close"); else { scalemodel.closedoor(); doortogglebutton.settext("open"); 160 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

161 catch (ArduinoException ex) { JOptionPane.showMessageDialog(this,ex.getMessage()); Με αντίστοιχο τρόπο εργαζόμαστε για να προσθέσουμε τους χειριστές γεγονότων για το άναμμα και το σβήσιμο των λευκών φωτοδιόδων. Σε αυτή τη περίπτωση οι event handlers θα ανατεθούν στα components lamp1togglebutton και lamp2togglebutton. private void lamp1togglebuttonactionperformed(java.awt.event.actionevent evt) { try { if (lamp1togglebutton.isselected()) { scalemodel.turnlamp1on(); lamp1togglebutton.settext("turn Off"); else { scalemodel.turnlamp1off(); lamp1togglebutton.settext("turn On"); catch (ArduinoException ex) { JOptionPane.showMessageDialog(this,ex.getMessage()); private void lamp2togglebuttonactionperformed(java.awt.event.actionevent evt) { try { if (lamp2togglebutton.isselected()) { scalemodel.turnlamp2on(); lamp2togglebutton.settext("turn Off"); else { scalemodel.turnlamp2off(); lamp2togglebutton.settext("turn On"); catch (ArduinoException ex) { JOptionPane.showMessageDialog(this,ex.getMessage()); Στη συνέχεια θα προσθέσουμε τον event handler για τον ολισθητή (slider) της κόκκινης φωτοδιόδου. Για να ανιχνεύσουμε αλλαγές στην θέση του slider θα πρέπει να διαχειριστούμε το γεγονός statechanged. Για να προσθέσουμε έναν event handler για Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

162 αυτό το γεγονός, κάνουμε δεξί κλικ πάνω στον slider της κόκκινης φωτοδιόδου, και από το μενού επιλέγουμε Events Change statechanged. private void redsliderstatechanged(javax.swing.event.changeevent evt) { // TODO add your handling code here: Για να διαβάσουμε την τιμή του slider καλούμε απλά τη μέθοδο getvalue(). Την τιμή αυτή θα στείλουμε στον Arduino μέσω της κλάσης ScaleModel. private void redsliderstatechanged(javax.swing.event.changeevent evt) { try { int level = redslider.getvalue(); scalemodel.setredlevel(level); catch (ArduinoException ex) { JOptionPane.showMessageDialog(this,ex.getMessage()); Με τον ίδιο τρόπο ολοκληρώνουμε την προσθήκη διαχειριστών γεγονότων της εφαρμογής, προσθέτοντας event handlers για τους ολισθητές της πράσινης και της μπλε φωτοδιόδου, καθώς και της αντίστασης ισχύος «καλοριφέρ». private void greensliderstatechanged(javax.swing.event.changeevent evt) { try { int level = greenslider.getvalue(); scalemodel.setgreenlevel(level); catch (ArduinoException ex) { JOptionPane.showMessageDialog(this,ex.getMessage()); private void bluesliderstatechanged(javax.swing.event.changeevent evt) { try { int level = blueslider.getvalue(); scalemodel.setbluelevel(level); catch (ArduinoException ex) { JOptionPane.showMessageDialog(this,ex.getMessage()); 162 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

163 private void heatingsliderstatechanged(javax.swing.event.changeevent evt) { try { int level = heatingslider.getvalue(); scalemodel.setheatinglevel(level); catch (ArduinoException ex) { JOptionPane.showMessageDialog(this,ex.getMessage()); Περιοδική ανάγνωση συσκευών Για την περιοδική ανάγνωση της ένδειξης του αισθητήρα θερμοκρασίας και της κατάστασης του κουδουνιού της εξώπορτας θα χρειαστούμε ένα νέο thread το οποίο θα τρέχει παράλληλα και θα διαβάζει περιοδικά (π.χ. ανά δευτερόλεπτο) τις τιμές αυτών των συσκευών μέσω της ScaleModel [72]. Το thread αυτό θα δημιουργείται και θα ξεκινάει μέσα στον κατασκευαστή (constructor) της φόρμας ScaleModelFrame. public ScaleModelFrame () throws ArduinoException { initcomponents(); scalemodel = new ScaleModel(); new Thread() public void run() { // περιοδική ανάγνωση θερμοκρασίας και μπουτόν κουδουνιού.start(); Το thread αυτό θα πρέπει να τρέχει έναν ατέρμονα βρόγχο κάθε ένα δευτερόλεπτο, όπου σε κάθε επανάληψη θα διαβάζονται οι τιμές των συσκευών και θα λαμβάνουν χώρα οι απαραίτητες ενέργειες ενημέρωσης του χρήστη. Για να πετύχουμε την επανάληψη κάθε ένα δευτερόλεπτο θα χρησιμοποιήσουμε τη μέθοδο sleep() της κλάσης Thread. H μέθοδος αυτή λαμβάνει ως όρισμα το χρονικό διάστημα που θέλουμε να παρέλθει μέχρι την επόμενη επανάληψη, εκφρασμένο σε χιλιοστά του δευτερολέπτου. public ScaleModelFrame() throws ArduinoException { initcomponents(); Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

164 scalemodel = new ScaleModel(); new Thread() public void run() { try { while (true) { Thread.sleep(1000); catch (InterruptedException ex) { // τερματισμός του thread σε περίπτωση διακοπής.start(); Για την ενημέρωση της ένδειξης της θερμοκρασίας θα χρησιμοποιήσουμε την μέθοδο readtemperature() της ScaleModel για να διαβάσουμε την θερμοκρασία σε βαθμούς Κελσίου. Στη συνέχεια θα φορμάρουμε μέσω της κλάσης DecimalFormat την τιμή αυτή έτσι ώστε να έχει ένα δεκαδικό ψηφίο, και θα την εμφανίσουμε στο αντίστοιχο πεδίο κειμένου της φόρμας. public ScaleModelFrame() throws ArduinoException { initcomponents(); scalemodel = new ScaleModel(); new Thread() public void run() { DecimalFormat df = new DecimalFormat("#.#"); try { while (true) { double temperature = scalemodel.readtemperature(); temperaturetextfield.settext(df.format(temperature)); Thread.sleep(1000); catch (InterruptedException ArduinoException ex) { // τερματισμός του thread σε περίπτωση διακοπής ή σφάλματος.start(); 164 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

165 Για το κουδούνι της εξώπορτας θα πρέπει να εργαστούμε διαφορετικά. Αντί να εμφανίσουμε κάπου την κατάσταση του μπουτόν, θέλουμε να ηχήσουμε από το μεγάφωνο του υπολογιστή έναν ήχο κουδουνιού κάθε φορά που το μπουτόν αλλάζει κατάσταση, προσομοιώνοντας έτσι δηλαδή τη συμπεριφορά ενός πραγματικού κουδουνιού εξώπορτας. Για να μπορέσουμε να ανιχνεύσουμε τις αλλαγές στην κατάσταση του μπουτόν θα πρέπει να χρησιμοποιήσουμε μια μεταβλητή στην οποία θα αποθηκεύουμε την προηγούμενη κατάσταση του μπουτόν. Αν η νέα τιμή που διαβάζουμε είναι διαφορετική αυτό θα σημαίνει αλλαγή στην κατάσταση του μπουτόν. public ScaleModelFrame() throws ArduinoException { initcomponents(); scalemodel = new ScaleModel(); new Thread() public void run() { DecimalFormat df = new DecimalFormat("#.#"); boolean prevdoorbellstate = false; try { while (true) { double temperature = scalemodel.readtemperature(); temperaturetextfield.settext(df.format(temperature)); boolean doorbellstate = scalemodel.readdoorbell(); if (prevdoorbellstate!= doorbellstate) { if (doorbellstate) { // το μπουτόν πατήθηκε else { // το μπουτόν ξεπατήθηκε prevdoorbellstate = doorbellstate; Thread.sleep(1000); catch (InterruptedException ArduinoException ex) { // τερματισμός του thread σε περίπτωση διακοπής ή σφάλματος.start(); Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

166 Για να προσδώσουμε στο κουδούνι μας ρεαλιστική «χροιά» θα πρέπει να ακούγεται διαφορετικός ήχος σε κάθε αλλαγή κατάστασης, π.χ. «ding» στο πάτημα και «dong» στο ξεπάτημα. Ο ευκολότερος τρόπος να παίξουμε κάποιον ήχο στον υπολογιστή, είναι μέσω αρχείων ήχου (π.χ..wav). Τα αρχεία αυτά μπορούμε είτε να τα κατεβάσουμε από το Διαδίκτυο με σχετική αναζήτηση, είτε να τα ηχογραφήσουμε μόνοι μας χρησιμοποιώντας τον ήχο ενός πραγματικού κουδουνιού. Για να «παίξουμε» τα αρχεία ήχου θα χρησιμοποιήσουμε την κλάση AudioSystem της Java [73]. Αρχικά γράφουμε μια μέθοδο playsound() που θα ανοίγει και θα παίζει ένα αρχείο ήχου. private void playsound(final String path) { try { File file = new File(path); Clip clip = AudioSystem.getClip(); AudioInputStream inputstream = AudioSystem.getAudioInputStream(file); clip.open(inputstream); clip.start(); catch (LineUnavailableException UnsupportedAudioFileException IOException ex) { System.err.println(ex.getMessage()); Τέλος, τροποποιούμε τον κώδικα του thread έτσι να καλεί την playsound() έτσι ώστε να παράγεται ο αντίστοιχος ήχος σε κάθε αλλαγή κατάστασης του μπουτόν. while (true) { double temperature = scalemodel.readtemperature(); temperaturetextfield.settext(df.format(temperature)); boolean doorbellstate = scalemodel.readdoorbell(); if (prevdoorbellstate!= doorbellstate) { if (doorbellstate) { playsound("ding.wav"); else { playsound("dong.wav"); prevdoorbellstate = doorbellstate; Thread.sleep(1000); 166 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

167 4. Ολοκλήρωση 4.1 Θέματα Ασφάλειας Αδυναμίες Το σύστημα που υλοποιήσαμε στηρίζεται στη χρήση του πρωτοκόλλου HTTP, το οποίο στη βασική του μορφή χωρίς τη χρήση κρυπτογραφίας είναι ιδιαίτερα ευπαθές σε επιθέσεις υποκλοπής (eavesdropping) και «ενδιάμεσου» (man-in-the-middle attack) [74]. Αυτό σημαίνει ότι οποιοσδήποτε δύναται να παρεμβληθεί ανάμεσα σε εμάς και τη μονάδα Arduino, όχι μόνο μπορεί να υποκλέψει τις εντολές που στέλνουμε και τις απαντήσεις που λαμβάνουμε, αλλά και να τις τροποποιήσει κατά βούληση. Με άλλα λόγια τίθεται σε κίνδυνο η εμπιστευτικότητα (confidentiality) και η ακεραιότητα (integrity) της μεταξύ πελάτη και Arduino επικοινωνίας. Εικόνα 75: Σχηματική αναπαράσταση παραδείγματος επίθεσης «ενδιάμεσου» (man-in-the-middle attack) Επιπλέον, στην τρέχουσα μορφή του, το σύστημα υποστηρίζει έλεγχο πρόσβασης (access control) αφού δεν διαθέτει κανέναν τρόπο πιστοποίησης (authentication) και εξουσιοδότησης (authorization) του χρήστη, απλώς εκτελεί όλες τις εντολές που καταφθάνουν χωρίς να λαμβάνει υπόψη ποιος είναι αυτός που τις στέλνει. Έτσι οποιοσδήποτε θα μπορούσε να ελέγξει τη μονάδα, αρκεί να έχει στη διάθεσή του την IP διεύθυνσή της. Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

168 4.1.2 Πιστοποίηση Πρόσβασης Η Βασική Πιστοποίηση Πρόσβασης (Basic Access Authentication) μέσα στα πλαίσια του πρωτοκόλλου HTTP είναι μια μέθοδος με την οποία ένα πρόγραμμα-πελάτη HTTP αποστέλλει ένα όνομα χρήστη (username) και ένα συνθηματικό (password) σε κάθε αίτημα, έτσι ώστε να πιστοποιήσει (authenticate) τον εαυτό του [75]. Στην άλλη άκρη, το σύστημα ελέγχει την ορθότητα των διαπιστευτηρίων (credentials) και αναλόγως προχωράει σε απόρριψη ή σε προώθηση του αιτήματος, υλοποιώντας έτσι ένα βασικό έλεγχο πρόσβασης (access control). Ένας εξυπηρετητής ιστού (web server) που λαμβάνει κάποιο αίτημα πρόσβασης σε κάποιον πόρο (resource) ο οποίος δεν είναι δημόσιος, μπορεί να ειδοποιήσει το πρόγραμμα-πελάτη ότι θα πρέπει να προβεί σε πιστοποίηση μέσω ενός κωδικού κατάστασης 401 [49] Επιπλέον ο web server θα πρέπει να συμπεριλάβει μια ειδική επικεφαλίδα (header) WWW-Authenticate, με την οποία δηλώνει το είδος πιστοποίησης (Basic) και το αναγνωριστικό της ομάδας των πόρων για τους οποίους ζητείται η πιστοποίηση (realm). HTTP/ Authorization Required WWW-Authenticate: Basic realm="arduino REST API" Content-Length: 0 Εικόνα 76: Τυπική μορφή απάντησης σε μη εξουσιοδοτημένο πελάτη Στο άλλο άκρο της σύνδεσης, ο πελάτης θα πρέπει να επαναλάβει το αίτημα εκ νέου, αυτή τη φορά φροντίζοντας όμως να συμπεριλάβει μια επικεφαλίδα Authorization με την οποία αποστέλλει το όνομα χρήστη και το συνθηματικό. Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== Εικόνα 77: Επικεφαλίδα Authorization. Στο παράδειγμα αποστέλλεται το όνομα χρήστη giannis και συνθηματικό qwe123 Το πρώτο συνθετικό της επικεφαλίδας είναι η λέξη Basic η οποία δηλώνει ότι ο πελάτης επιθυμεί απλή πιστοποίηση. Το δεύτερο συνθετικό περιέχει το όνομα χρήστη και το συνθηματικό σε κωδικοποίηση «βάσης του 64» (base-64 encoding). Αν και το όνομα χρήστη και το συνθηματικό μοιάζουν να είναι κρυπτογραφημένα, στην πραγματικότητα είναι απλό κείμενο (plain text), αφού οποιοσδήποτε μπορεί απλά να το αποκωδικοποιήσει, χωρίς τη χρήση κάποιου μυστικού κλειδιού (secret key). 168 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

169 Η υλοποίηση της απλής πιστοποίησης στον web server θα απαιτούσε την επεξεργασία όλων των επικεφαλίδων του αιτήματος, και όχι μόνο της πρώτης γραμμής όπως γίνεται τώρα. Επιπλέον κατά πάσα πιθανότητα θα υπήρχε ανάγκη υλοποίησης ρουτίνας αποκωδικοποίησης βάσης-64. Κάτι τέτοιο θα αύξανε σημαντικά την πολυπλοκότητα του κώδικα χωρίς να παρουσιάζει κάποιο αξιόλογο πλεονέκτημα. Η ευθύνη πιστοποίησης θα μπορούσε να μεταφερθεί εναλλακτικά στο ίδιο το REST API. Ένας απλός τρόπος θα ήταν σε κάθε αίτημα του API ο πελάτης να αποστέλλει ένα όνομα χρήστη (username) και ένα συνθηματικό (password) ως μέρος των παραμέτρων. /api/list?username=giannis&password=qwe123 Εικόνα 78: Παράδειγμα εναλλακτικού τρόπου πιστοποίησης υλοποιημένης ως μέρος του REST API Ο κώδικας της μεθόδου apicommand() της κλάσης Controller θα πρέπει τότε να τροποποιηθεί ώστε αρχικά να εντοπίζει τις παραμέτρους username και password. void apicommand( char *command ) { char *username = "", *password = ""; for (uint8_t i = 0; i < httprequest.paramcount; i++) { if (strcmp(httprequest.params[i].name,"username") == 0) { username = httprequest.params[i].value; else if (strcmp(httprequest.params[i].name,"password") == 0) { password = httprequest.params[i].value; // TODO: authenticate username/password if (strcmp(command,"list") == 0) { apilist(); else if (strcmp(command,"mode") == 0) { apimode(); else if (strcmp(command,"write") == 0) { apiwrite(); else if (strcmp(command,"read-all") == 0) { apireadall(); else if (strcmp(command,"read") == 0) { apiread(); else { apiunsupportedcommand(); Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

170 Στη συνέχεια θα γράψουμε μια ιδιωτική μέθοδο authorize() η οποία θα λαμβάνει ως ορίσματα το όνομα χρήστη και συνθηματικό και θα αποφασίσει αν αυτά ταιριάζουν ή όχι, επιστρέφοντας αντίστοιχα true ή false. boolean authorize( char *username, char *password ) { if (strcmp(username,"giannis") == 0 && strcmp(password,"qwe123") == 0) { return true; else { return false; Εφόσον επιθυμούμε το σύστημα να αναγνωρίζει περισσότερους από έναν διαφορετικούς χρήστες, θα μπορούσαμε εναλλακτικά να φυλάξουμε σε έναν πίνακα από ζεύγη αλφαριθμητικών, τα ονόματα χρηστών και τα αντίστοιχα συνθηματικά, τα οποία η μέθοδος θα διέτρεχε και θα έλεγχε κάποιο από αυτά ταιριάζει με τα διαπιστευτήρια. boolean authorize ( char *username, char *password ) { char *users[][2] = { { "giannis", "qwe123", { "giorgos", "zxc234", { "manolis", "ui67!$", ; for (uint8_t i = 0; i < sizeof(users)/sizeof(users[0]) ; i++) { if (strcmp(username,users[i][0]) == 0 && strcmp(password,users[i][1]) == 0) { return true; return false; Τέλος, απλά τροποποιούμε την μέθοδο apicommand() ώστε να επιστρέφει έναν νέο ειδικό κωδικό λάθους που θα αφορά μη εξουσιοδοτημένη πρόσβαση, σε περίπτωση που η πιστοποίηση αποτύχει. void apicommand( char *command ) { char *username = "", *password = ""; for (uint8_t i = 0; i < httprequest.paramcount; i++) { if (strcmp(httprequest.params[i].name,"username") == 0) { username = httprequest.params[i].value; else if (strcmp(httprequest.params[i].name,"password") == 0) { password = httprequest.params[i].value; if (!authorize(username,password)) { apierror(api_error_authorization_required); return; Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

171 4.1.3 Ασφαλές πρωτόκολλο HTTP Το ασφαλές πρωτόκολλο HTTP ή για συντομία HTTPS είναι ουσιαστικά η διοχέτευση HTTP αιτημάτων μέσα από ασφαλείς συνδέσεις τύπου TLS (Transport Layer Security) ή παλαιότερου τύπου SSL (Secure Socket Layer) [76]. Η εφαρμογή του εξασφαλίζει την επικοινωνία μεταξύ του πελάτη και του web server ως προς την εμπιστευτικότητα και την ακεραιότητά της, αφού όποιος επιχειρήσει να την υποκλέψει θα λάβει δεδομένα που φαινομενικά μοιάζουν με «σκουπίδια». Κατ επέκταση κάθε προσπάθεια αλλοίωσης της κρυπτογραφημένης επικοινωνίας θα είχε ως αποτέλεσμα απλά την μη εγκυρότητα των λαμβανόμενων δεδομένων. Εικόνα 79: Σχηματική αναπαράσταση αποκατάστασης ασφαλούς επικοινωνίας με χρήση κρυπτογραφίας TLS/SSL Η υποστήριξη του πρωτόκολλου θα είχε μεγάλο όφελος όσον αφορά την διασφάλιση της επικοινωνίας, δυστυχώς όμως η βιβλιοθήκες που υλοποιούν τα κείμενα πρωτόκολλα κρυπτογραφίας TLS/SSL είναι εξαιρετικά ογκώδεις [77]. Για παράδειγμα η πλήρης «ανοικτή» υλοποίηση OpenSSL έχει αποτύπωμα (footprint) μεγαλύτερο του 1MB, ενώ η MatrixSSL, η μικρότερη διαθέσιμη υλοποίηση για embedded συστήματα, έχει αποτύπωμα της τάξης των 50KB, το οποίο είναι μεν πολύ συμπυκνωμένο, είναι δε εξαιρετικά μεγάλο για να μπορέσει να χρησιμοποιηθεί στον Arduino UNO με 32KB μέγιστο μέγεθος προγράμματος, και μόλις 2KB μνήμη RAM, γεγονός που καθιστά πρακτικά αδύνατη την υποστήριξη HTTPS στον Arduino [78]. Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

172 4.1.4 Εικονικά Ιδιωτικά Δίκτυα Τα εικονικά ιδιωτικά δίκτυα (VPN Virtual Private Networks) επεκτείνουν τα «συμβατικά» ιδιωτικά δίκτυα όπως Ethernet ή Wi-Fi, κατά μήκος δημόσιων δικτύων όπως το Διαδίκτυο. Επιτρέπουν δηλαδή σε έναν απομακρυσμένο υπολογιστή που βρίσκεται εκτός ενός τοπικού δικτύου να αποκτήσει ίσα δικαιώματα πρόσβασης με οποιοδήποτε άλλον τοπικό υπολογιστή, αφού πρώτα πιστοποιήσει τον εαυτό του με χρήση συνθηματικών. Η επικοινωνία μεταξύ του εξωτερικού υπολογιστή και του τοπικού δικτύου προστατεύεται με χρήση τεχνικών κρυπτογραφίας [79]. Εικόνα 80: Διάγραμμα χρήσης VPN. Ο εξωτερικός υπολογιστής λειτουργεί σαν είναι μέρος του τοπικού δικτύου (LAN) Για την υλοποίηση ενός VPN σε ένα δίκτυο τοπική εμβέλειας (LAN) όπως για παράδειγμα το δίκτυο μιας σύγχρονης κατοικίας ή ενός μικρού γραφείου, αρκεί ένας υπολογιστής με πρόσβαση στο δημόσιο δίκτυο, να παίξει το ρόλο του εξυπηρετητή (VPN server). Ο υπολογιστής αυτός θα πρέπει να υλοποιεί κάποιο πρωτόκολλο υλοποίησης εικονικών ιδιωτικών δικτύων, όπως για παράδειγμα το PPTP, και είναι εκείνος που θα δέχεται και θα πιστοποιεί τους εξωτερικούς πελάτες του δικτύου. Μόλις ένας εξωτερικός υπολογιστής πιστοποιήσει τον εαυτό του στο τοπικό δίκτυο, ο εξυπηρετητής του αναθέτει μια τοπική διεύθυνση IP και πλέον ο υπολογιστής αποκτά ίδια δικαιώματα πρόσβασης στο τοπικό δίκτυο με οποιονδήποτε άλλο τοπικό υπολογιστή που έχει συνδεθεί με Ethernet ή Wi-Fi. 172 Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

173 Η χρήση εικονικού ιδιωτικού δικτύου θα επέτρεπε την ασφαλή επικοινωνία μεταξύ ενός υπολογιστή που βρίσκεται οπουδήποτε στο Διαδίκτυο και της μονάδας Arduino η οποία θα βρίσκεται σε κάποιο τοπικό LAN. Το σημαντικότερο πλεονέκτημα αυτής της λύσης είναι ότι υλοποιείται σε επίπεδο υποδομής δικτύου και δεν απαιτεί καμία τροποποίηση στο λογισμικό του web server ή του πελάτη. Εικόνα 81: Εφαρμογή δικτύου VPN σε συνδυασμό με τη μονάδα Arduino Οι περισσότεροι σύγχρονοι δρομολογητές (routers) που συναντάμε σε τυπικά τοπικά δίκτυα LAN υποστηρίζουν εγγενώς τη δημιουργία δικτύων VPN, παίζοντας οι ίδιοι το ρόλο του VPN server. Επιπλέον τα περισσότερα λειτουργικά συστήματα υπολογιστών και φορητών συσκευών (smart phones, tablets, κλπ) υποστηρίζουν και καθιστούν την ένταξη σε δίκτυα VPN εξαιρετικά απλή διαδικασία. Εικόνα 82: Ρυθμίσεις VPN Server σε δρομολογητή Linksys WRT54G με λειτουργικό DD-WRT Ελληνικό Ανοικτό Πανεπιστήμιο: Πτυχιακή Εργασία - HOU-CS-UGP

Bread Online. Παναγιώτης Ιωαννίδης Επιβλέπων καθηγητής: Μηνάς Δασυγένης

Bread Online. Παναγιώτης Ιωαννίδης Επιβλέπων καθηγητής: Μηνάς Δασυγένης Bread Online Σχεδιασμός και μετατροπή μιας απλής οικιακής συσκευής σε επαναπρογραμματιζόμενη συσκευή IP Παναγιώτης Ιωαννίδης Επιβλέπων καθηγητής: Μηνάς Δασυγένης Πανεπιστήμιο Δυτικής Μακεδονίας Τμήμα Μηχανικών

Διαβάστε περισσότερα

Πλακέτα Arduino. 1ο ΕΠΑΛ Περάματος - 7ο ΕΚ Πειραιά

Πλακέτα Arduino. 1ο ΕΠΑΛ Περάματος - 7ο ΕΚ Πειραιά Πλακέτα Arduino Το 2005 oι Massimo Banzi και David Cueartielles στο Ivrea Δημιουργούν την υπολογιστική πλατφόρμα Arduino. Το Arduino είναι βασισμένο σε μια απλή μητρική πλακέτα ανοικτού κώδικα, με ενσωματωμένο

Διαβάστε περισσότερα

Σχεδιασμός και υλοποίηση κυκλώματος μέτρησης κατανάλωσης ισχύος

Σχεδιασμός και υλοποίηση κυκλώματος μέτρησης κατανάλωσης ισχύος Σχεδιασμός και υλοποίηση κυκλώματος μέτρησης κατανάλωσης ισχύος Φοιτητής Φετινίδης Αναστάσιος Επιβλέπων Δασυγένης Μηνάς Μάρτιος 2014 1 Περιεχόμενα παρουσίασης Εισαγωγή Θεωρητικό υπόβαθρο Υλικό μέρος του

Διαβάστε περισσότερα

Ι ΑΣΚΩΝ ΚΑΘΗΓΗΤΗΣ: ΚΑΘΗΓΗΤΗΣ ΕΦΑΡΜΟΓΩΝ. ΤΕΙ ΥΤΙΚΗΣ ΜΑΚΕ ΟΝΙΑΣ d.fotiadis@kastoria.teikoz.gr

Ι ΑΣΚΩΝ ΚΑΘΗΓΗΤΗΣ: ΚΑΘΗΓΗΤΗΣ ΕΦΑΡΜΟΓΩΝ. ΤΕΙ ΥΤΙΚΗΣ ΜΑΚΕ ΟΝΙΑΣ d.fotiadis@kastoria.teikoz.gr Ι ΑΣΚΩΝ ΚΑΘΗΓΗΤΗΣ: ΦΩΤΙΑ ΗΣ Α. ΗΜΗΤΡΗΣ M.Sc. ΚΑΘΗΓΗΤΗΣ ΕΦΑΡΜΟΓΩΝ ΤΜΗΜΑ ΜΗΧΑΝΙΚΩΝ ΠΛΗΡΟΦΟΡΙΚΗΣ Τ.Ε. ΣΧΟΛΗ ΤΕΧΝΟΛΟΓΙΚΩΝ ΕΦΑΡΜΟΓΩΝ (Σ.Τ.ΕΦ.) ΤΕΙ ΥΤΙΚΗΣ ΜΑΚΕ ΟΝΙΑΣ d.fotiadis@kastoria.teikoz.gr Ασύγχρονη σειριακή

Διαβάστε περισσότερα

ΡΟΜΠΟΤΙΚΗ. ΕΡΓΑΣΙΑ ΠΑΝΩ ΣΤΗΝ ΑΡΧΙΤΕΚΤΟΝΙΚΗ ΝΧΤ ΚΑΙ ΤΑ ΠΡΩΤΟΚΟΛΛΑ ΕΠΙΚΟΙΝΩΝΙΑΣ BLUETOOTH, I2C και serial communication

ΡΟΜΠΟΤΙΚΗ. ΕΡΓΑΣΙΑ ΠΑΝΩ ΣΤΗΝ ΑΡΧΙΤΕΚΤΟΝΙΚΗ ΝΧΤ ΚΑΙ ΤΑ ΠΡΩΤΟΚΟΛΛΑ ΕΠΙΚΟΙΝΩΝΙΑΣ BLUETOOTH, I2C και serial communication ΡΟΜΠΟΤΙΚΗ ΕΡΓΑΣΙΑ ΠΑΝΩ ΣΤΗΝ ΑΡΧΙΤΕΚΤΟΝΙΚΗ ΝΧΤ ΚΑΙ ΤΑ ΠΡΩΤΟΚΟΛΛΑ ΕΠΙΚΟΙΝΩΝΙΑΣ BLUETOOTH, I2C και serial communication ΜΠΑΝΤΗΣ ΑΝΤΩΝΙΟΣ 533 ΤΣΙΚΤΣΙΡΗΣ ΔΗΜΗΤΡΙΟΣ 551 ΑΡΧΙΤΕΚΤΟΝΙΚΗ ΤΟΥ ΡΟΜΠΟΤ LEGO NXT Το ρομπότ

Διαβάστε περισσότερα

ΒΑΣΙΚΕΣ ΠΛΗΡΟΦΟΡΙΕΣ. Τίτλος Μαθήματος. Διαλέξεις - Θεωρητική Διδασκαλία, Εποπτευόμενο Εργαστήριο Επίδειξη, Μελέτες (Projects)

ΒΑΣΙΚΕΣ ΠΛΗΡΟΦΟΡΙΕΣ. Τίτλος Μαθήματος. Διαλέξεις - Θεωρητική Διδασκαλία, Εποπτευόμενο Εργαστήριο Επίδειξη, Μελέτες (Projects) ΒΑΣΙΚΕΣ ΠΛΗΡΟΦΟΡΙΕΣ Τίτλος Μαθήματος Μικροελεγκτές και Ενσωματωμένα συστήματα Ανάπτυξη και Εφαρμογές Κωδικός Μαθήματος Μ2 Θεωρία / Εργαστήριο Θεωρία + Εργαστήριο Πιστωτικές μονάδες 4 Ώρες Διδασκαλίας 2Θ+1Ε

Διαβάστε περισσότερα