12/01/2007

@ Το Dialpan του Asterisk

Το αρχείο extensions.conf[1], γνωστό και ως dialplan, είναι η καρδιά κάθε Asterisk συστήματος. Στο dialplan ορίζεται ποιες συσκευές μπορούν να πραγματοποιήσουν και να δεχτούν κλήσεις και με ποιόν τρόπο και σειρά αυτό επιτυγχάνεται. Με λίγα λόγια, το dialplan είναι μία λίστα παραμετροποιήσιμων οδηγιών ή βημάτων, οργανωμένα σε contexts, τα οποία ακολουθεί το Asterisk σειριακά. Στην δημοσίευση αυτή θα δούμε πως δομείται το dialplan και κάποιες από τις βασικές λειτουργίες που μπορεί να εκτελέσει.

Η δομή του Dialplan

Ήδη από το παράδειγμα του mini-PBX είχαμε μια πρώτη επαφή με τη δομή του dialplan. Το dialplan αποτελείται από 4 κύρια μέρη: τα contexts, τις extensions, τους αριθμούς προτεραιότητας και τις εφαρμογές. Παρακάτω θα εξηγήσουμε ποια είναι η χρησιμότητά τους, πως συντάσσονται, και πως μπορούν να συνδυαστούν για να δημιουργήσουν ένα dialplan.

Contexts

Το dialplan χωρίζεται σε τομείς που ονομάζονται contexts. Ένα context ξεκινάει με το όνομά του μέσα σε τετραγωνικά άγκιστρα [ ] και τελειώνει όταν ξεκινήσει το επόμενο. Στο παράδειγμα του mini-PBX ότι βρίσκεται ανάμεσα στο [general] και στο [mini-pbx-internal] ανήκει στο context [general].

[general]
; Δηλώσεις του
context general...

[mini-pbx-internal]

Το context είναι μία ομάδα από extensions. Δηλαδή, μέσω των context, μπορούμε να χωρίσουμε το πλήθος των extension που έχουμε στο σύστημά μας και να τις χειριστούμε εντελώς ανεξάρτητα. Οι extensions που βρίσκονται μέσα σε ένα context μπορούν να αλληλεπιδράσουν μόνο με αυτές που υπάγονται στο ίδιο context εκτός αν έχει δηλωθεί διαφορετικά με τη μέθοδο include. Άρα τα contexts μας επιτρέπουν να κρύψουμε ή να κάνουμε απροσπέλαστες συγκεκριμένες extensions από άλλες extensions.

Σαν παράδειγμα θα μπορούσαμε να δούμε το τηλεφωνικό σύστημα μίας υποθετικής εταιρίας, όπου όλες οι εσωτερικές κλήσεις δρομολογούνται μέσα από το Asterisk. Το dialplan της εταιρίας περιέχει τα contexts: [internal], [operator], [outgoing], [vice-president] και [president]. Όλοι οι υπάλληλοι συνδέονται στο context [internal] το οποίο περιλαμβάνει και το context [operator]. Στο context [operator], υπάρχουν οι χειριστές που μπορούν να παρέχουν εξωτερικές γραμμές στους υπαλλήλους (μεταφορά κλήσης σε εξωτερικό νούμερο). Τα contexts [vice-president] και [president] στα οποία συνδέονται μόνο ο πρόεδρος και ο αντιπρόεδρος της εταιρίας, περιλαμβάνουν το ένα το άλλο καθώς επίσης και το context [internal].

[internal]
include => operator
include => vice-president

[operator]

[outgoing]

[vice-president]
include => internal
include => outgoing
include => president
include => outgoing

[president]
include => internal
include => outgoing
include => vice-president

Με τον παραπάνω τρόπο έχουμε καταφέρει να βάλουμε κάποια ιεραρχία στον τρόπο των κλήσεων. Όλοι οι υπάλληλοι μπορούν να καλέσουν ο ένας τον άλλον, να συνδεθούν με το κέντρο (operator) για να ζητήσουν εξωτερική γραμμή καθώς επίσης και να καλέσουν τον αντιπρόεδρο. Μόνο ο αντιπρόεδρος μπορεί να καλέσει τον πρόεδρο. Ο πρόεδρος και ο αντιπρόεδρος μπορούν να καλέσουν όλους τους υπαλλήλους του [internal] και κατ’ επέκταση και τους χειριστές του [operator], μπορούν επίσης να καλέσουν απευθείας εξωτερικές γραμμές μέσω του [outgoing]. Από τα παραπάνω μπορούμε να συμπεράνουμε για τη μέθοδο incude ότι, το context που κάνει include κάποιο άλλο, έχει άμεση πρόσβαση στις extensions του άλλου καθώς επίσης και στις extensions οποιουδήποτε context έχει κάνει include αυτό με τη σειρά του, κ.ο.κ.

Τέλος δύο ειδικά contexts του αρχείου /etc/usr/extensions.conf τα οποία δεν περιέχουν κανόνες κλήσεις, αλλά γενικές ιδιότητες και μεταβλητές είναι τα general και globals.

[general]

Στο context [general] ορίζονται κάποιες ιδιότητες του extensions.conf και γενικές συμπεριφορές του αρχείου. Για παράδειγμα μπορούμε να επιλέξουμε αν θέλουμε να σώζουμε το αρχείο από την κονσόλα του Asterisk με την εντολή save dialplan, θέτοντας τις επιλογές static=yes και writeprotect=no. Με την επιλογή autofallthrough μπορούμε να ορίσουμε το Asterisk να κλείνει το κανάλι της κλήσης αν του τελειώσουν οι διαθέσιμες επιλογές που του έχουμε ορίσει μέσω των extensions. Με την επιλογή clearglobalvars, το Asterisk με κάθε επανεκκίνση (εντολή restart) ή επαναφόρτωση των αρχείων του (εντολή reload), θα διαβάζει εκ νέου τις μεταβλητές που βρίσκονται στο [globals], σε κάθε άλλη περίπτωση ότι υπάρχει στο [globals] θα συνεχίζει να υπάρχει ακόμα και αν το διαγράψουμε από το extensions.conf. Τέλος με την επιλογή priorityjumping=yes, οι εφαρμογές που υποστηρίζουν άλματα προτεραιότητας ανάλογα με το αποτέλεσμα της λειτουργίας τους (Dial()) μπορούν να τα πράξουν. Παρόλο που αυτή η δυνατότητα συνεχίζει να ισχύει μέχρι και σήμερα, έχει σταματήσει να αναπτύσσεται και θα ήταν καλό να αποφευχθεί η χρήση της για να επιτευχθεί συμβατότητα των ρυθμίσεων με μελλοντικές εκδόσεις.

[globals]

Στο context [globals] ορίζονται μεταβλητές οι οποίες ισχύουν για όλες τις extensions ανεξαρτήτου context που περιέχονται, και διευκολύνουν πολύ στην αναγνωσιμότητα του dialplan και στην αποφυγή τυπογραφικών λαθών.

Η σύνταξη μία global μεταβλητής είναι:

[globals]
ΟΝΟΜΑ_ΜΕΤΑΒΛΗΤΗΣ=ΤΙΜΗ

Για να χρησιμοποιήσουμε την τιμή της μεταβλητής μέσα στο dialplan θα πρέπει να την καλέσουμε ως ${ΟΝΟΜΑ_ΜΕΤΑΒΛΗΤΗΣ}.

Εναλλακτικά μπορούμε να δηλώσουμε μια global μεταβλητή, δυναμικά, μέσα από το dialplan με την εφαρμογή SetGlobalVar(ΟΝΟΜΑ_ΜΕΤΑΒΛΗΤΗΣ=ΤΙΜΗ)


Εκτός από τις global μεταβλητές, στο Asterisk υπάρχουν και οι channel μεταβλητές, οι οποίες έχουν ισχύ μόνο για τη διάρκεια μίας κλήσης. Στις channel μεταβλητές θα αναφερθούμε σε επόμενη δημοσίευση.

Extensions

Όπως αναφέρθηκε και παραπάνω, μέσα σε κάθε context περιέχεται μία ή περισσότερες extensions. Μία extension, είναι μία οδηγία που θα ακολουθήσει το Asterisk αν δεχθεί μία κλήση ή αν κάποιος χρήστης πληκτρολογήσει κάποια ψηφία. Οι extensions λοιπόν, ορίζουν το πώς η κλήση θα κινηθεί μέσα από μία σειρά αποφάσεων και παραδοχών.

Η παραδοσιακή έννοια του όρου extension αναφέρεται στο νούμερο κάποιας συσκευής που συνδέεται στο PBX. Στο Asterisk όμως σημαίνει πολλά περισσότερα. Μια extension μπορεί να είναι μόνο μία γραμμή που να συσχετίζει την extension με τη συσκευή, όπως στο παράδειγμα του mini-PBX, η extension 111 συσχετίζεται μέσω της Dial() με το softphone του χρήστη sip-user. Εκτός από μια συσκευή, μία extension μπορεί να συσχετιστεί με ένα φωνητικό ταχυδρομείο, με μία ουρά αναμονής, ή ακόμα και με πλήθος συσκευών.

Μία extension στο dialplan, ορίζεται με τη λέξη exten ακολουθούμενη από τα σύμβολα = και >. Ο γενικός τύπος ορισμού μίας extension είναι:

exten => νούμερο(ή όνομα),προτεραιότητα,εφαρμογή.

Κάθε γραμμή ξεκινάει με την εντολή exten =>. Στη συνέχεια ακολουθεί το νούμερο (ή το όνομα) της extension. Το νούμερο αυτό δείχνει στο Asterisk ποια σειρά εντολών θα πρέπει να τρέξει. Αυτό το νούμερο μπορεί να ανιχνευτεί μέσω τριών βασικών τρόπων: (1) Η τηλεφωνική εταιρία το στέλνει μαζί με την κλήση (DID[2]), (2) Οι χρήστες το πληκτρολογούν στο πληκτρολόγιο του τερματικού τους, (3) Μέσω κάποιων ειδικών extensions που ορίζονται από το Asterisk.

Οι πιο συνηθισμένες από αυτές τις ειδικές extensions είναι:

  • s (start): Χρησιμοποιείται όταν δεν είναι δυνατόν, να γνωρίζει το Asterisk με ποια ακριβώς extension επιθυμεί να συνδεθεί ο καλών. Αυτή η περίπτωση συναντάτε όταν η κλήση προέρχεται απ’ το δημόσιο τηλεφωνικό δίκτυο και δεν έχει συμφωνηθεί κάποιο DID νούμερο με τον πάροχο τηλεφωνίας.
  • t (timeout): Αν απαιτείται από το χρήστη να εισάγει δεδομένα όπως συμβαίνει σ’ ένα IVR σύστημα και ο χρήστης καθυστερήσει, τότε θα εκτελεστεί η ειδική extension t.
  • i (invalid): Αν απαιτείται από το χρήστη να εισάγει δεδομένα και τα δεδομένα που εισήγαγε είναι μη αποδεκτά, τότε θα εκτελεστεί η ειδική extension i.
  • fax: Αν το Asterisk αντιληφθεί ότι η εισερχόμενη κλήση είναι μήνυμα fax, τότε θα εκτελεστεί η ειδική extension fax.

Προτεραιότητες

Μετά το όνομα της extension, υπάρχει η προτεραιότητα. Το Asterisk ξεκινάει πάντα με την προτεραιότητα 1, εκτελεί την εφαρμογή και προχωράει στην επόμενη προτεραιότητα, 2 ή γενικότερα x+1. Κάποιες εφαρμογές όπως η Dial(), μπορούν να αναγκάσουν το Asterisk να κάνει άλμα προτεραιοτήτων και να πάει κατευθείαν στην προτεραιότητα x+101 (όπου x o αριθμός της προτεραιότητας της εφαρμογής που προκάλεσε το άλμα), δυνατότητα η οποία μας επιτρέπει να δρομολογήσουμε τις κλήσεις βασιζόμενοι σε αποφάσεις, όπως για παράδειγμα, αν ο προορισμός είναι κατειλημμένος. Εκτός από τον αριθμητικό συμβολισμό των προτεραιοτήτων, οι δημιουργοί του Asterisk, γνωρίζοντας το μέγεθος που μπορεί να αποκτήσει ένα σύνθετο dialplan και τις επιπτώσεις που θα είχε έστω και η παραμικρή αλλαγή σε αυτό, δημιούργησαν την ειδική προτεραιότητα n. Η προτεραιότητα αυτή, παίρνει αμέσως την επόμενη τιμή από την προηγούμενη της. Δηλαδή το υποθετικό dialplan:

exten => 111,1,εφαρμογή_Α()
exten
=> 111,2,εφαρμογή_Β()
exten
=> 111,3,εφαρμογή_Γ()

Θα μετατρεπόταν με την xρήση της ειδικής προτεραιότητας n σε:

exten => 111,1,εφαρμογή_Α()
exten
=> 111,n,εφαρμογή_Β() ; n1 = 1+1 (=2)
exten
=> 111,n,εφαρμογή_Γ() ; n2 = n1+1 (=3)

Σε ένα dialplan μήκους τριών γραμμών αυτό δε δείχνει τόσο σημαντικό, αλλά ας υποθέσουμε ότι είχαμε 45 γραμμές στο αρχείο μας και ότι διαπιστώναμε ότι θέλουμε η εφαρμογή που βρίσκεται στη γραμμή 23, τελικά να εκτελεστεί στο τέλος. Τότε θα έπρεπε χειροκίνητα να αλλάξουμε όλες τις παραπάνω αριθμήσεις. Με αποτέλεσμα η πιθανότητα τυπογραφικού λάθους και συνεπώς εσφαλμένης λειτουργίας να αυξάνεται σημαντικά.

Εφαρμογές

Τέλος έχουμε την εφαρμογή. Μέσω των εφαρμογών υλοποιούνται οι βασικές λειτουργίες του Asterisk. Οι εφαρμογές εκτελούν συγκεκριμένες λειτουργίες σε μία κλήση, όπως αναπαραγωγή ήχου, επεξεργασία τονικών ήχων εισόδου, ή ακόμα και τερματισμό της κλήσης. Μπορούμε να δούμε όλες τις διαθέσιμες εφαρμογές που έχουμε στο Asterisk, γράφοντας στην κονσόλα του Asterisk την εντολή:

*CLI> show applications

Κάποιες από τις πιο συνήθεις εφαρμογές που χρησιμοποιούνται είναι:

  • Answer(): Απαντάει τη γραμμή. Πολλές εφαρμογές απαιτούν η γραμμή να έχει απαντηθεί πρώτα από το Asterisk προτού μπορέσουν να εκτελεστούν (Playback, BackGround, κ.λ.π.).
  • Hangup(): Εκτελεί την ακριβώς αντίθετη λειτουργία από την Answer. Κλείνει δηλαδή το κανάλι επικοινωνίας που δημιουργήθηκε από κάποια άλλη εφαρμογή.
  • Playback(αρχείο ήχου): Αυτή η εφαρμογή αναπαραγάγει ένα αρχείο ήχου .wav ή .gsm
  • BackGround(αρχείο ήχου): Λειτουργεί παρόμοια με το Playback με τη διαφορά ότι ακούει για τονικούς ήχους.
  • Queue(όνομα ουράς | επιλογές): Η εφαρμογή Queue τοποθετεί την κλήση στην ουρά αναμονής με το όνομα και τις επιλογές που δηλώθηκαν κατά την εκτέλεση της. Θα πρέπει ήδη να υπάρχει δηλωμένη αντίστοιχη ουρά στο αρχείο queues.conf
  • Wait(δευτερόλεπτα): Προκαλεί παύση για καθορισμένο χρονικό διάστημα προκειμένου να εκτελεστούν χρονοβόρες διαδικασίες όπως εγγραφή και ανάγνωση από αρχείο.
  • NoOp(αλφαρηθμιτικό): Η εφαρμογή αυτή δεν εκτελεί κάποια συγκεκριμένη λειτουργία. Τυπώνει μονάχα το αλφαριθμητικό που έλαβε ως όρισμα, στην κονσόλα του Asterisk. Χρησιμεύει όταν ψάχνουμε για σφάλματα στο dialplan.
  • Voicemail(extension): Στέλνει την κλήση στον αυτόματο τηλεφωνητή της extension που δόθηκε σαν όρισμα κατά την εκτέλεσή της. H υλοποίηση της Voicemail αναπαραγάγει κάποια ηχογραφημένα μηνύματα ως χαιρετισμούς.

o
Τοποθετώντας το γράμμα u πριν από την extension αναπαραγάγετε ο χαιρετισμός «Μη διαθέσιμος».
o Τοποθετώντας το γράμμα b πριν από την extension αναπαραγάγετε ο χαιρετισμός «Κατειλημμένος».
o Τοποθετώντας το γράμμα s πριν από την extension δεν αναπαραγάγετε κανένας χαιρετισμός.

Για να λειτουργήσει η Voicemail, θα πρέπει να έχει δημιουργηθεί για την extension, ο αντίστοιχος λογαριασμός στο αρχείο voicemail.conf

  • VoicemailMain(): Αυτή η εφαρμογή, επιτρέπει στους χρήστες να ακούσουν τα μηνύματα τους, να ηχογραφήσουν προσαρμοσμένους χαιρετισμούς και να αλλάξουν κάποιες ρυθμίσεις του λογαριασμού τους. Μπορεί να πάρει σαν όρισμα τη θυρίδα που επιθυμούμε να συνδεθεί κάποιος χρήστης απευθείας, χωρίς την ανάγκη αυθεντικοποίησης του.
  • Dial (Τεχνολογία/id, επιλογές, χρόνος): Η εφαρμογή Dial() λέει στο Asterisk να καλέσει μέσω της «τεχνολογίας» τον αριθμό «id» και όταν η γραμμή απαντηθεί να ενώσει τα δύο άκρα της επικοινωνίας ανεξαρτήτου τεχνολογίας που χρησιμοποιούν,
Οι επιλογές που μπορούμε να ορίσουμε στην Dial είναι:

o t: επιτρέπει στον καλούμενο τη μεταφορά της κλήσης πατώντας τo πλήκτρο # ή όποιον άλλον συνδυασμό πλήκτρων έχει ορίστει στο αρχείο features.conf
o Τ: επιτρέπει στον καλούντα τη μεταφορά της κλήσης πατώντας το πλήκτρο # ή όποιον άλλον συνδυασμό πλήκτρων έχει ορίστει στο αρχείο features.conf
o r: μιμείται τον ήχο κλήσης στο ακουστικό του καλούντα.
o m: παρέχει μουσική κατά τη διάρκεια της αναμονής
o g: Αν ο προορισμός κλείσει τη γραμμή τότε συνεχίζει στην επόμενη προτεραιότητα του extension.
o w: Επιτρέπει την ηχογράφηση της κλήσης από τον καλούμενο με το πάτημα της αλληλουχίας τον πλήκτρων που ενεργοποιούν το automon όπως αυτά έχουν οριστεί στο αρχείο features.conf.
o W: Επιτρέπει την ηχογράφηση της κλήσης από τον καλούντα με το πάτημα της αλληλουχίας τον πλήκτρων που ενεργοποιούν το automon όπως αυτά έχουν οριστεί στο αρχείο features.conf.


Οι επιλογές w, W που αναφέρονται στο αρχείο features.conf θα εξεταστούν σε επόμενες δημοσιεύσεις. Για την ώρα αρκεί να αναφερθούν.

Η Dial παρέχει επίσης τη δυνατότητα ταυτόχρονης κλήσης πολλών προορισμών, με τη χρήση του συμβόλου “&” ανάμεσα στους προορισμούς που θέλουμε να καλέσουμε. Για παράδειγμα: Dial(SIP/user1&SIP/user2&...&IAX2/user1,20,r)

Αν επιστρέψουμε στο αρχείο extensions.conf του mini-PBX που δημιουργήσαμε και το διαβάσουμε έχοντας υπόψη τα παραπάνω, μπορούμε να καταλάβουμε πόσο απλοϊκό είναι. Ας το βελτιώσουμε εφαρμόζοντας κάποια από αυτά που αναλύσαμε προηγουμένως.

Αλλάζουμε το αρχείο /etc/asterisk/extensions.conf όπως φαίνεται παρακάτω,

[general]
autofallthrough=yes
clearglobalvars=yes
priorityjumping=yes

[globals]
SIP-USER=SIP/sip-user
IAX-USER=IAX2/iax-user

[mini-pbx-internal]

; sip-user
exten => 111,1,Dial(${SIP-USER},20,r)
exten => 111,2,Playback(vm-nobodyavail)
exten => 111,3,Hangup()
exten => 111,102,Playback(all-circuits-busy-now)
exten => 111,103,Hangup()

; iax-user
exten => 222,1,Dial(${IAX-USER},20,r)
exten => 222,2,Playback(vm-nobodyavail)
exten => 222,3,Hangup()
exten => 222,102,Playback(all-circuits-busy-now)
exten => 222,103,Hangup()

 
Στην κονσόλα του Asterisk γράφουμε την εντολή:

*CLI> extensions reload[3]

Η οποία θα πρέπει να έχει το παρακάτω αποτέλεσμα


Στα .config αρχεία, οτιδήποτε υπάρχει μετά από το σύμβολο του ερωτηματικού (;) θεωρείται σχόλιο και δε διαβάζεται από το Asterisk.


Το αλλαγμένο αρχείο extensions.conf του mini-PBX μπορεί πλέον να κάνει τα εξής:

exten => 111,1,Dial(${SIP-USER},20,r)

Κλήση όπως και προηγούμενος, χρησιμοποιώντας πλέον την τιμή της global μεταβλητής ${SIP-USER}. H συσκευή του sip-user θα χτυπήσει για 20 δευτερόλεπτα.

exten => 111,2,Playback(vm-nobodyavail)

Αν περάσουν τα 20 δευτερόλεπτα και ο sip-user δεν απαντήσει τη γραμμή, τότε το Asterisk θα πάει στην επόμενη προτεραιότητα (2) και θα αναπαράγει με τη χρήση της Playback() το ηχητικό μήνυμα: Nobody is available to take your call at the moment .

exten => 222,3,Hangup()

Αφού τελειώσει η αναπαραγωγή του μηνύματος, το Asterisk θα πάει στην προτεραιότητα 3 και θα κλείσει τη γραμμή.

exten => 111,102,Playback(all-circuits-busy-now)

Σε περίπτωση που ο χρήστης sip-user είναι κατειλημμένος. Η εφαρμογή Dial θα στείλει το Asterisk στην προτεραιότητα x+101 (όπου x=1, η προτεραιότητα που είχε η εφαρμογή Dial()) και θα αναπαραγάγει το ηχητικό μήνυμα: All circuits are busy now.

exten => 111,103,Hangup()

Τέλος αφού τελειώσει η αναπαραγωγή του μηνύματος. To Asterisk θα πάει στην επόμενη προτεραιότητα και θα κλείσει τη γραμμή.

Στην κονσόλα του Asterisk θα δούμε κάτι ανάλογο με την παρακάτω εικόνα αν καλέσουμε από το kiax την extension του sip-user <111> και δεν απαντηθεί η γραμμή μέσα σε 20 δευτερόλεπτα.




Οι αλλαγές που κάναμε για τον χρήστη iax-user λειτουργούν με πανομοιότυπο τρόπο με αυτές που αναλύσαμε προηγουμένως για τον χρήστη sip-user.


[1] Σε μελλοντικές εκδόσεις του Asterisk, το αρχείο extensions.conf θα αντικατασταθεί από το extensons.ael το οποίο αλλάζει ριζικά την δομή του dialplan. Η νέα μορφή του dialplan θα θυμίζει πολύ περισσότερα κάποια γλώσσα προγραμματισμού παρά ένα .ini αρχείο.

[2] Το DID – Direct Inward Dialing (διεπιλογή εισόδου, γνωστό στην Ευρώπη και ως DDI) είναι ένα χαρακτηριστικό που προσφέρεται από τηλεφωνικούς παρόχους, με σκοπό να επιτρέπει σε μια εταιρεία να εκχωρεί έναν προσωπικό αριθμό σε κάθε χρήστη, χωρίς να απαιτείται χωριστή τηλεφωνική γραμμή για τον καθένα. Με τον τρόπο αυτό, η κίνηση στην τηλεφωνία μπορεί να διαχωριστεί και η διαχείρισή της να γίνεται ευκολότερα. Για παράδειγμα, αν διαθέτουμε 10 συνεχόμενα τηλεφωνικά νούμερα, μπορούμε να συμφωνήσουμε με τον τηλεφωνικό μας πάροχο να αποστέλλει μόνο το τελευταίο νούμερο που πληκτρολογήθηκε (0-9) και να φτιάξουμε τις αντίστοιχές 10 extensions στο dialplan.

[3] Μόνο με την εντολή reload επαναφωρτώνουμε όλα τα αρχεία ρυθμίσεων του Asterisk. Μπορούμε όμως να αναφέρουμε συγκεκριμένα πιο αρχείο επιθυμούμε να επαναφωρτώσουμε έτσι ώστε να μειώσουμε τον χρόνο επαναλειτουργίας του συστήματός μας

Δεν υπάρχουν σχόλια: