Συνδέουμε τον μετρητή νερού στο smart home

Κάποτε, τα συστήματα οικιακού αυτοματισμού, ή «έξυπνο σπίτι» όπως συχνά αποκαλούνται, ήταν τρομερά ακριβά και μόνο οι πλούσιοι μπορούσαν να τα αντέξουν οικονομικά. Σήμερα στην αγορά μπορείτε να βρείτε αρκετά οικονομικά κιτ με αισθητήρες, κουμπιά/διακόπτες και ενεργοποιητές για τον έλεγχο του φωτισμού, των πριζών, του εξαερισμού, της παροχής νερού και άλλων καταναλωτών. Και ακόμη και τα πιο στραβά DIY-shnik μπορούν να συμμετάσχουν στην ομορφιά και να συναρμολογήσουν συσκευές για ένα έξυπνο σπίτι σε φθηνή τιμή.

Συνδέουμε τον μετρητή νερού στο smart home

Κατά κανόνα, οι προτεινόμενες συσκευές είναι είτε αισθητήρες είτε ενεργοποιητές. Διευκολύνουν την εφαρμογή σεναρίων όπως «όταν ενεργοποιείται ο αισθητήρας κίνησης, ανάψτε το φως» ή «ο διακόπτης κοντά στην έξοδο σβήνει το φως σε ολόκληρο το διαμέρισμα». Αλλά κατά κάποιο τρόπο δεν τα κατάφερε με την τηλεμετρία. Στην καλύτερη περίπτωση, αυτό είναι ένα γράφημα της θερμοκρασίας και της υγρασίας ή της στιγμιαίας ισχύος σε μια συγκεκριμένη πρίζα.

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

Κάτω από την περικοπή, η δική μου έκδοση μιας συσκευής που βασίζεται στο ESP8266, η οποία μετράει παλμούς από μετρητές νερού και στέλνει μετρήσεις στον έξυπνο οικιακό διακομιστή μέσω MQTT. Θα προγραμματίσουμε σε micropython χρησιμοποιώντας τη βιβλιοθήκη uasyncio. Κατά τη δημιουργία του υλικολογισμικού, συνάντησα αρκετές ενδιαφέρουσες δυσκολίες, τις οποίες θα συζητήσω επίσης σε αυτό το άρθρο. Πηγαίνω!

Το σχέδιο

Συνδέουμε τον μετρητή νερού στο smart home

Η καρδιά ολόκληρου του κυκλώματος είναι μια μονάδα στον μικροελεγκτή ESP8266. Το ESP-12 ήταν αρχικά σχεδιασμένο, αλλά το δικό μου αποδείχθηκε ελαττωματικό. Έπρεπε να είμαι ικανοποιημένος με τη μονάδα ESP-07, η οποία ήταν διαθέσιμη. Ευτυχώς είναι ίδιοι τόσο σε συμπεράσματα όσο και σε λειτουργικότητα, η μόνη διαφορά είναι στην κεραία - το ESP-12 την έχει ενσωματωμένη, ενώ το ESP-07 έχει εξωτερική. Ωστόσο, ακόμα και χωρίς κεραία WiFi, το σήμα στο μπάνιο μου πιάνεται κανονικά.

Το δέσιμο της ενότητας είναι στάνταρ:

  • κουμπί επαναφοράς με pull-up και πυκνωτή (αν και και τα δύο βρίσκονται ήδη μέσα στη μονάδα)
  • Το σήμα ενεργοποίησης (CH_PD) ενεργοποιείται
  • Το GPIO15 τραβήχτηκε στο έδαφος. Αυτό χρειάζεται μόνο στην αρχή, αλλά δεν χρειάζεται να κολλήσω πια σε αυτό το πόδι

Για να μεταφέρετε τη μονάδα στη λειτουργία υλικολογισμικού, πρέπει να κλείσετε το GPIO2 στο έδαφος και για να είναι πιο βολικό, έδωσα το κουμπί Εκκίνηση. Στην κανονική κατάσταση, αυτός ο πείρος τροφοδοτείται με ρεύμα.

Η κατάσταση της γραμμής GPIO2 ελέγχεται μόνο στην αρχή της λειτουργίας - όταν εφαρμόζεται ρεύμα ή αμέσως μετά την επαναφορά. Έτσι, η μονάδα είτε εκκινεί ως συνήθως είτε μεταβαίνει σε λειτουργία υλικολογισμικού. Μόλις φορτωθεί, αυτή η καρφίτσα μπορεί να χρησιμοποιηθεί ως κανονικό GPIO. Λοιπόν, καθώς υπάρχει ήδη ένα κουμπί εκεί, μπορείτε να κρεμάσετε κάποια χρήσιμη λειτουργία σε αυτό.

Για προγραμματισμό και αποσφαλμάτωση, θα χρησιμοποιήσω το UART, το οποίο έφερα στη χτένα. Όταν χρειάζεται, απλώς συνδέω έναν προσαρμογέα USB-UART εκεί. Απλά πρέπει να θυμάστε ότι η μονάδα τροφοδοτείται από 3.3V. Εάν ξεχάσετε να αλλάξετε τον προσαρμογέα σε αυτήν την τάση και να εφαρμόσετε 5 V, τότε η μονάδα πιθανότατα θα καεί.

Δεν έχω προβλήματα με το ρεύμα στο μπάνιο - η πρίζα βρίσκεται περίπου ένα μέτρο από τους μετρητές, οπότε θα το τροφοδοτήσω από 220V. Ως πηγή ενέργειας, θα έχω ένα μικρό μπλοκ HLK-PM03 από την Tenstar Robot. Προσωπικά δυσκολεύομαι πολύ με τα αναλογικά και τα ηλεκτρονικά ισχύος και ορίστε έτοιμο τροφοδοτικό σε μικρή θήκη.

Για να σηματοδοτήσω τους τρόπους λειτουργίας, παρείχα ένα LED συνδεδεμένο στο GPIO2. Ωστόσο, δεν το κόλλησα, γιατί. η μονάδα ESP-07 έχει ήδη ένα LED συνδεδεμένο στο ίδιο GPIO2. Αλλά ας είναι στον πίνακα - ξαφνικά θέλω να φέρω αυτό το LED στην θήκη.

Ας περάσουμε στα πιο ενδιαφέροντα. Οι μετρητές νερού δεν έχουν λογική, δεν μπορούν να τους ζητηθούν τρέχουσες ενδείξεις. Το μόνο που έχουμε στη διάθεσή μας είναι οι παρορμήσεις - κλείνοντας τις επαφές του διακόπτη καλαμιού κάθε λίτρο. Έχω τις εξόδους του διακόπτη καλαμιού στο GPIO12 / GPIO13. Θα ενεργοποιήσω την αντίσταση έλξης μέσω προγραμματισμού μέσα στη μονάδα.

Αρχικά, ξέχασα να δώσω αντιστάσεις R8 και R9 και δεν υπάρχουν στην δική μου έκδοση της πλακέτας. Αλλά επειδή ήδη παρουσιάζω το σχέδιο για να το δουν όλοι, αξίζει να διορθώσω αυτήν την παράβλεψη. Απαιτούνται αντιστάσεις για να μην καεί η θύρα εάν το υλικολογισμικό είναι λάθη και τοποθετεί μια μονάδα στον πείρο και ο διακόπτης καλαμιού βραχυκυκλώνει αυτή τη γραμμή στη γείωση (με αντίσταση, θα ρέει το πολύ 3.3 V / 1000Ω = 3.3 mA) .

Ήρθε η ώρα να σκεφτείτε τι θα κάνετε αν σβήσει το ρεύμα. Η πρώτη επιλογή είναι να ζητήσετε από τον διακομιστή τις αρχικές τιμές των μετρητών στην αρχή. Αλλά αυτό θα απαιτούσε μια σημαντική περιπλοκή του πρωτοκόλλου ανταλλαγής. Επιπλέον, η απόδοση της συσκευής σε αυτήν την περίπτωση εξαρτάται από την κατάσταση του διακομιστή. Εάν μετά την απενεργοποίηση του φωτός ο διακομιστής δεν εκκινούσε (ή εκκινούσε αργότερα), τότε ο μετρητής νερού δεν θα μπορούσε να ζητήσει τις αρχικές τιμές​​και θα λειτουργούσε λανθασμένα.

Ως εκ τούτου, αποφάσισα να εφαρμόσω την αποθήκευση των τιμών μετρητή σε ένα τσιπ μνήμης συνδεδεμένο μέσω I2C. Δεν έχω ειδικές απαιτήσεις για το μέγεθος της μνήμης flash - πρέπει να αποθηκεύσετε μόνο 2 αριθμούς (τον αριθμό των λίτρων σύμφωνα με τους μετρητές ζεστού και κρύου νερού). Ακόμη και η μικρότερη ενότητα θα κάνει. Αλλά πρέπει να δώσετε προσοχή στον αριθμό των κύκλων εγγραφής. Για τις περισσότερες ενότητες, αυτό είναι 100 χιλιάδες κύκλοι, για μερικές έως και ένα εκατομμύριο.

Φαίνεται ότι ένα εκατομμύριο είναι πολλά. Αλλά για 4 χρόνια ζωής στο διαμέρισμά μου, κατανάλωσα λίγο περισσότερα από 500 κυβικά μέτρα νερού, αυτό είναι 500 χιλιάδες λίτρα! Και 500 χιλιάδες δίσκοι σε φλας. Και αυτό είναι απλώς κρύο νερό. Μπορείτε, φυσικά, να επανακολλάτε το τσιπ κάθε δύο χρόνια, αλλά αποδείχθηκε ότι υπάρχουν τσιπ FRAM. Από προγραμματιστική άποψη, πρόκειται για το ίδιο I2C EEPROM, μόνο με πολύ μεγάλο αριθμό κύκλων επανεγγραφής (εκατοντάδες εκατομμύρια). Αυτό είναι μόνο μέχρι που δεν μπορώ ακόμα να φτάσω σε ένα κατάστημα με τέτοια μικροκυκλώματα, οπότε προς το παρόν το συνηθισμένο 24LC512 θα σταθεί.

Τυπωμένη πλακέτα κυκλώματος

Αρχικά σχεδίαζα να φτιάξω σανίδα στο σπίτι. Ως εκ τούτου, η σανίδα σχεδιάστηκε ως μονόπλευρη. Αλλά αφού πέρασα μια ώρα με ένα σίδερο λέιζερ και μια μάσκα συγκόλλησης (κάπως δεν είναι comme il faut χωρίς αυτό), αποφάσισα ακόμα να παραγγείλω σανίδες από τους Κινέζους.

Συνδέουμε τον μετρητή νερού στο smart home

Σχεδόν πριν παραγγείλω την πλακέτα, συνειδητοποίησα ότι εκτός από το τσιπ μνήμης flash, μπορείτε να συνδέσετε κάτι άλλο χρήσιμο στο δίαυλο I2C, για παράδειγμα, μια οθόνη. Το τι ακριβώς να βγει σε αυτό είναι ακόμα ένα ερώτημα, αλλά πρέπει να το αναπαράγετε στον πίνακα. Λοιπόν, αφού επρόκειτο να παραγγείλω σανίδες στο εργοστάσιο, δεν υπήρχε λόγος να περιοριστώ σε μια πλακέτα μονής όψης, επομένως οι γραμμές I2C είναι οι μόνες στο πίσω μέρος της πλακέτας.

Με την καλωδίωση μονής κατεύθυνσης συνδέθηκε και ένα μεγάλο τζάμι. Επειδή η πλακέτα σχεδιάστηκε μονόπλευρα, στη συνέχεια σχεδιάστηκε να τοποθετηθούν τα κομμάτια και τα εξαρτήματα SMD στη μία πλευρά και τα εξαρτήματα εξόδου, οι σύνδεσμοι και το τροφοδοτικό από την άλλη. Όταν έλαβα τις σανίδες ένα μήνα αργότερα, ξέχασα το αρχικό σχέδιο και κόλλησα όλα τα εξαρτήματα στην μπροστινή πλευρά. Και μόνο όταν ήρθε η συγκόλληση του τροφοδοτικού αποδείχθηκε ότι τα συν και τα πλην ήταν διαζευγμένα αντίστροφα. Έπρεπε να καλλιεργήσω με άλτες. Στην παραπάνω εικόνα, έχω ήδη αλλάξει την καλωδίωση, αλλά η γείωση μεταφέρεται από το ένα μέρος της πλακέτας στο άλλο μέσω των ακίδων του κουμπιού εκκίνησης (αν και θα ήταν δυνατό να σχεδιάσετε ένα κομμάτι στο δεύτερο στρώμα).

Αποδείχθηκε έτσι

Συνδέουμε τον μετρητή νερού στο smart home

Корпус

Το επόμενο βήμα είναι το σώμα. Εάν έχετε εκτυπωτή 3D, αυτό δεν είναι πρόβλημα. Δεν ενοχλήθηκα πολύ - απλώς σχεδίασα ένα κουτί του σωστού μεγέθους και έκανα εγκοπές στα σωστά σημεία. Το κάλυμμα είναι στερεωμένο στο σώμα με μικρές βίδες με αυτοκόλλητη τομή.

Συνδέουμε τον μετρητή νερού στο smart home

Ανέφερα ήδη ότι το κουμπί εκκίνησης μπορεί να χρησιμοποιηθεί ως κουμπί γενικής χρήσης - οπότε ας το φέρουμε στον μπροστινό πίνακα. Για να γίνει αυτό, σχεδίασα ένα ειδικό "πηγάδι" όπου βρίσκεται το κουμπί.

Συνδέουμε τον μετρητή νερού στο smart home

Υπάρχουν επίσης στελέχη στο εσωτερικό της θήκης, στα οποία τοποθετείται η πλακέτα και στερεώνεται με μία μόνο βίδα M3 (δεν υπήρχε άλλος χώρος στην πλακέτα)

Η οθόνη επιλέχθηκε ήδη όταν εκτύπωσα την πρώτη έκδοση της θήκης που ταιριάζει. Ένας τυπικός εκτυπωτής δύο γραμμών δεν χωρούσε σε αυτή τη θήκη, αλλά στο κάτω μέρος της κάννης υπήρχε μια οθόνη OLED SSD1306 128 × 32. Είναι μικρό, αλλά δεν τον κοιτάζω κάθε μέρα - θα κυλήσει.

Υπολογίζοντας έτσι κι εκεί, πώς θα τοποθετηθούν τα καλώδια από αυτό, αποφάσισα να κολλήσω την οθόνη στη μέση της θήκης. Εργονομία, φυσικά, κάτω από την πλίνθο - το κουμπί είναι στην κορυφή, η οθόνη είναι στο κάτω μέρος. Αλλά είπα ήδη ότι η ιδέα να βιδώσω την οθόνη ήρθε πολύ αργά και ήμουν πολύ τεμπέλης να ξανακαλώσω την πλακέτα για να μετακινήσω το κουμπί.

Συναρμολογημένη συσκευή. Η μονάδα οθόνης είναι κολλημένη στην εγκοπή με θερμή κόλλα

Συνδέουμε τον μετρητή νερού στο smart home

Συνδέουμε τον μετρητή νερού στο smart home

Το τελικό αποτέλεσμα μπορεί να δει κανείς στο KDPV

Firmware

Ας προχωρήσουμε στο κομμάτι του λογισμικού. Για τέτοιες μικρές χειροτεχνίες, μου αρέσει πολύ να χρησιμοποιώ τη γλώσσα Python (μικροπύθωνας) - ο κώδικας είναι πολύ συμπαγής και κατανοητός. Ευτυχώς, δεν χρειάζεται να κατεβείτε στο επίπεδο των καταχωρητών για να συμπιέσετε τα μικροδευτερόλεπτα - τα πάντα μπορούν να γίνουν από τον python.

Φαίνεται ότι όλα είναι απλά, αλλά όχι πολύ - η συσκευή έχει πολλές ανεξάρτητες λειτουργίες:

  • Ο χρήστης πατά ένα κουμπί και κοιτάζει την οθόνη
  • Λίτρα σημειώνουν και ενημερώνουν τις τιμές στη μνήμη flash
  • Η μονάδα παρακολουθεί το σήμα WiFi και επανασυνδέεται εάν είναι απαραίτητο
  • Λοιπόν, χωρίς μια λάμπα που αναβοσβήνει, δεν μπορείτε καθόλου

Είναι αδύνατο να παραδεχτούμε ότι μια λειτουργία δεν λειτούργησε αν η άλλη, για κάποιο λόγο, είναι ηλίθια. Έχω ήδη φάει κάκτους σε άλλα έργα και τώρα εξακολουθώ να βλέπω δυσλειτουργίες όπως "έχασα άλλο ένα λίτρο επειδή η οθόνη ενημερωνόταν εκείνη τη στιγμή" ή "ο χρήστης δεν μπορεί να κάνει τίποτα ενώ η μονάδα συνδέεται στο WiFi". Φυσικά, ορισμένα πράγματα μπορούν να γίνουν μέσω διακοπών, αλλά μπορεί να αντιμετωπίσετε περιορισμό στη διάρκεια, την ένθεση των κλήσεων ή τη μη ατομική αλλαγή των μεταβλητών. Λοιπόν, ο κώδικας που κάνει τα πάντα και αμέσως μετατρέπεται γρήγορα σε χάος.

В πιο σοβαρό έργο Χρησιμοποίησα κλασικό προληπτικό multitasking και FreeRTOS, αλλά σε αυτήν την περίπτωση, το μοντέλο αποδείχθηκε πολύ πιο κατάλληλο κορουτίνες και βιβλιοθήκες uasync . Επιπλέον, η εφαρμογή των κορουτινών Python είναι απλώς μια βόμβα - όλα γίνονται απλά και άνετα για τον προγραμματιστή. Απλώς γράψτε τη δική σας λογική, απλώς πείτε μου πού μπορείτε να κάνετε εναλλαγή μεταξύ των νημάτων.

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

#####################################
# Counter class - implements a single water counter on specified pin
#####################################
class Counter():
    debounce_ms = const(25)
    
    def __init__(self, pin_num, value_storage):
        self._value_storage = value_storage
        
        self._value = self._value_storage.read()
        self._value_changed = False

        self._pin = Pin(pin_num, Pin.IN, Pin.PULL_UP)

        loop = asyncio.get_event_loop()
        loop.create_task(self._switchcheck())  # Thread runs forever

Κάθε μετρητής χειρίζεται μια παρουσία της κλάσης Counter. Πρώτα απ 'όλα, η αρχική τιμή του μετρητή αφαιρείται από το EEPROM (value_storage) - έτσι υλοποιείται η ανάκτηση μετά από διακοπή ρεύματος.

Ο πείρος προετοιμάζεται με ένα ενσωματωμένο pull-up στο τροφοδοτικό: εάν ο διακόπτης καλαμιού είναι κλειστός, η γραμμή είναι μηδέν, εάν η γραμμή είναι ανοιχτή, σύρεται μέχρι το τροφοδοτικό και ο ελεγκτής διαβάζει ένα.

Επίσης, ξεκινά μια ξεχωριστή εργασία εδώ, η οποία θα μετρήσει το pin. Κάθε μετρητής θα εκτελέσει τη δική του εργασία. Εδώ είναι ο κωδικός της

    """ Poll pin and advance value when another litre passed """
    async def _switchcheck(self):
        last_checked_pin_state = self._pin.value()  # Get initial state

        # Poll for a pin change
        while True:
            state = self._pin.value()
            if state != last_checked_pin_state:
                # State has changed: act on it now.
                last_checked_pin_state = state
                if state == 0:
                    self._another_litre_passed()

            # Ignore further state changes until switch has settled
            await asyncio.sleep_ms(Counter.debounce_ms)

Απαιτείται μια καθυστέρηση 25 ms για να φιλτράρει την αναπήδηση των επαφών και ταυτόχρονα ρυθμίζει πόσο συχνά ξυπνά η εργασία (ενώ αυτή η εργασία κοιμάται, άλλες εργασίες λειτουργούν). Κάθε 25 ms, η λειτουργία ξυπνά, ελέγχει τον πείρο και αν οι επαφές του διακόπτη καλαμιού είναι κλειστές, τότε έχει περάσει άλλο ένα λίτρο από τον μετρητή και αυτό πρέπει να υποβληθεί σε επεξεργασία.

    def _another_litre_passed(self):
        self._value += 1
        self._value_changed = True

        self._value_storage.write(self._value)

Η επεξεργασία του επόμενου λίτρου είναι ασήμαντη - ο μετρητής απλώς αυξάνεται. Λοιπόν, θα ήταν ωραίο να γράψετε μια νέα τιμή σε μια μονάδα flash USB.

Για ευκολία στη χρήση, παρέχονται "αξεσουάρ".

    def value(self):
        self._value_changed = False
        return self._value

    def set_value(self, value):
        self._value = value
        self._value_changed = False

Λοιπόν, τώρα ας χρησιμοποιήσουμε τα charms του python και τη βιβλιοθήκη uasync και ας κάνουμε το αντικείμενο μετρητή σε αναμονή (πώς μπορώ να το μεταφράσω στα ρωσικά; Αυτό που μπορεί να αναμένεται;)

    def __await__(self):
        while not self._value_changed:
            yield from asyncio.sleep(0)

        return self.value()

    __iter__ = __await__  

Αυτή είναι μια τόσο εύχρηστη συνάρτηση που περιμένει μέχρι να ενημερωθεί η τιμή του μετρητή - η συνάρτηση ξυπνά από καιρό σε καιρό και ελέγχει τη σημαία _value_changed. Το κόλπο αυτής της συνάρτησης είναι ότι ο κωδικός κλήσης μπορεί να αποκοιμηθεί σε μια κλήση σε αυτήν τη συνάρτηση και να κοιμηθεί μέχρι να ληφθεί μια νέα τιμή.

Τι γίνεται όμως με τις διακοπές;Ναι, σε αυτό το σημείο μπορείς να με τρολάρεις λέγοντας ότι ο ίδιος είπε για διακοπές, αλλά στην ουσία κανόνισε ένα ηλίθιο pin poll. Στην πραγματικότητα οι διακοπές είναι το πρώτο πράγμα που προσπάθησα. Στο ESP8266, μπορείτε να οργανώσετε μια διακοπή στο μπροστινό μέρος, ακόμη και να γράψετε έναν χειριστή διακοπής για αυτήν τη διακοπή στην python. Σε αυτή τη διακοπή, μπορείτε να ενημερώσετε την τιμή μιας μεταβλητής. Πιθανώς, αυτό θα ήταν αρκετό εάν ο μετρητής ήταν μια εξαρτημένη συσκευή - μια συσκευή που περιμένει μέχρι να ζητηθεί αυτή η τιμή.

Δυστυχώς (ή ευτυχώς;), η συσκευή μου είναι ενεργή, θα πρέπει η ίδια να στέλνει μηνύματα μέσω του πρωτοκόλλου MQTT και να γράφει δεδομένα στην EEPROM. Και εδώ υπάρχουν ήδη περιορισμοί - δεν μπορείτε να εκχωρήσετε μνήμη σε διακοπές και να χρησιμοποιήσετε μια μεγάλη στοίβα, πράγμα που σημαίνει ότι μπορείτε να ξεχάσετε την αποστολή μηνυμάτων μέσω του δικτύου. Υπάρχουν buns όπως το micropython.schedule () που σας επιτρέπουν να εκτελέσετε κάποιο είδος συνάρτησης "το συντομότερο δυνατό", αλλά τίθεται το ερώτημα "ποιο είναι το νόημα;". Ξαφνικά, στέλνουμε κάποιο είδος μηνύματος αυτή τη στιγμή, και τότε μια διακοπή σπάει και χαλάει τις τιμές των μεταβλητών. Ή, για παράδειγμα, έχει έρθει μια νέα τιμή μετρητή από τον διακομιστή ενώ δεν έχουμε καταγράψει ακόμα την παλιά. Γενικά, πρέπει να αποκλείσετε τον συγχρονισμό ή να βγείτε με κάποιο τρόπο διαφορετικά.

Και από καιρό σε καιρό Σφάλμα εκτέλεσης: προγραμματίστε τα πλήρη σφάλματα στοίβας και ποιος ξέρει γιατί;

Με τη ρητή δημοσκόπηση και το uasync, σε αυτή την περίπτωση, κατά κάποιο τρόπο αποδεικνύεται πιο όμορφο και πιο αξιόπιστο.

Πήρα δουλειά με την EEPROM σε μια μικρή τάξη

class EEPROM():
    i2c_addr = const(80)

    def __init__(self, i2c):
        self.i2c = i2c
        self.i2c_buf = bytearray(4) # Avoid creation/destruction of the buffer on each call


    def read(self, eeprom_addr):
        self.i2c.readfrom_mem_into(self.i2c_addr, eeprom_addr, self.i2c_buf, addrsize=16)
        return ustruct.unpack_from("<I", self.i2c_buf)[0]    
        
    
    def write(self, eeprom_addr, value):
        ustruct.pack_into("<I", self.i2c_buf, 0, value)
        self.i2c.writeto_mem(self.i2c_addr, eeprom_addr, self.i2c_buf, addrsize=16)

Είναι δύσκολο να δουλέψεις με byte απευθείας σε python και είναι byte που γράφονται στη μνήμη. Έπρεπε να περιφράξω τη μετατροπή μεταξύ ακέραιου αριθμού και byte χρησιμοποιώντας τη βιβλιοθήκη ustruct.

Για να μην μεταδίδεται το αντικείμενο I2C και η διεύθυνση του κελιού μνήμης κάθε φορά, τα τύλιξα όλα σε ένα μικρό και βολικό κλασικό

class EEPROMValue():
    def __init__(self, i2c, eeprom_addr):
        self._eeprom = EEPROM(i2c)
        self._eeprom_addr = eeprom_addr
        

    def read(self):
        return self._eeprom.read(self._eeprom_addr)


    def write(self, value):
        self._eeprom.write(self._eeprom_addr, value)

Το ίδιο το αντικείμενο I2C δημιουργείται με αυτές τις παραμέτρους

i2c = I2C(freq=400000, scl=Pin(5), sda=Pin(4))

Προσεγγίζουμε το πιο ενδιαφέρον - την υλοποίηση της επικοινωνίας με τον διακομιστή μέσω MQTT. Λοιπόν, δεν χρειάζεται να εφαρμόσετε το ίδιο το πρωτόκολλο - το βρήκα στο Διαδίκτυο έτοιμη ασύγχρονη υλοποίηση. Εδώ θα το χρησιμοποιήσουμε.

Όλα τα πιο ενδιαφέροντα συλλέγονται στην κατηγορία CounterMQTTClient, η οποία βασίζεται στη βιβλιοθήκη MQTTClient. Ας ξεκινήσουμε από την περιφέρεια

#####################################
# Class handles both counters and sends their status to MQTT
#####################################
class CounterMQTTClient(MQTTClient):

    blue_led = Pin(2, Pin.OUT, value = 1)
    button = Pin(0, Pin.IN)

    hot_counter = Counter(12, EEPROMValue(i2c, EEPROM_ADDR_HOT_VALUE))
    cold_counter = Counter(13, EEPROMValue(i2c, EEPROM_ADDR_COLD_VALUE))

Εδώ δημιουργούνται και διαμορφώνονται οι ακίδες λαμπτήρων και κουμπιών, καθώς και αντικείμενα μετρητή κρύου και ζεστού νερού.

Με την προετοιμασία, δεν είναι όλα τόσο ασήμαντα

    def __init__(self):
        self.internet_outage = True
        self.internet_outages = 0
        self.internet_outage_start = ticks_ms()

        with open("config.txt") as config_file:
            config['ssid'] = config_file.readline().rstrip()
            config['wifi_pw'] = config_file.readline().rstrip()
            config['server'] = config_file.readline().rstrip()
            config['client_id'] = config_file.readline().rstrip()
            self._mqtt_cold_water_theme = config_file.readline().rstrip()
            self._mqtt_hot_water_theme = config_file.readline().rstrip()
            self._mqtt_debug_water_theme = config_file.readline().rstrip()

        config['subs_cb'] = self.mqtt_msg_handler
        config['wifi_coro'] = self.wifi_connection_handler
        config['connect_coro'] = self.mqtt_connection_handler
        config['clean'] = False
        config['clean_init'] = False
        super().__init__(config)

        loop = asyncio.get_event_loop()
        loop.create_task(self._heartbeat())
        loop.create_task(self._counter_coro(self.cold_counter, self._mqtt_cold_water_theme))
        loop.create_task(self._counter_coro(self.hot_counter, self._mqtt_hot_water_theme))
        loop.create_task(self._display_coro())

Για να ορίσετε τις παραμέτρους της βιβλιοθήκης mqtt_as, χρησιμοποιείται ένα μεγάλο λεξικό διαφορετικών ρυθμίσεων - config. Οι περισσότερες από τις προεπιλεγμένες ρυθμίσεις λειτουργούν για εμάς, αλλά πολλές ρυθμίσεις πρέπει να οριστούν ρητά. Για να μην ορίσω τις ρυθμίσεις απευθείας στον κώδικα, τις αποθηκεύω σε ένα αρχείο κειμένου config.txt. Αυτό σας επιτρέπει να αλλάξετε τον κωδικό ανεξάρτητα από τις ρυθμίσεις, καθώς και να προσαρμόσετε πολλές ίδιες συσκευές με διαφορετικές παραμέτρους.

Το τελευταίο μπλοκ κώδικα ξεκινά πολλές κορουτίνες για να εξυπηρετήσει διάφορες λειτουργίες συστήματος. Ακολουθεί ένα παράδειγμα κορουτίνας που σερβίρει μετρητές

    async def _counter_coro(self, counter, topic):
        # Publish initial value
        value = counter.value()
        await self.publish(topic, str(value))

        # Publish each new value
        while True:
            value = await counter
            await self.publish_msg(topic, str(value))

Η κορουτίνα περιμένει σε ένα βρόχο για μια νέα τιμή μετρητή και μόλις εμφανιστεί, στέλνει ένα μήνυμα μέσω του πρωτοκόλλου MQTT. Το πρώτο κομμάτι κώδικα στέλνει την αρχική τιμή ακόμα κι αν δεν ρέει νερό μέσα από τον μετρητή.

Η βασική κλάση MQTTClient εξυπηρετείται μόνη της, ξεκινά μια σύνδεση WiFi και επανασυνδέεται όταν η σύνδεση χαθεί. Όταν αλλάξει η κατάσταση της σύνδεσης WiFi, η βιβλιοθήκη μας ενημερώνει καλώντας το wifi_connection_handler

    async def wifi_connection_handler(self, state):
        self.internet_outage = not state
        if state:
            self.dprint('WiFi is up.')
            duration = ticks_diff(ticks_ms(), self.internet_outage_start) // 1000
            await self.publish_debug_msg('ReconnectedAfter', duration)
        else:
            self.internet_outages += 1
            self.internet_outage_start = ticks_ms()
            self.dprint('WiFi is down.')
            
        await asyncio.sleep(0)

Η συνάρτηση ειλικρινά γλείφεται από τα παραδείγματα. Σε αυτήν την περίπτωση, μετράει τον αριθμό των διακοπών (internet_outages) και τη διάρκειά τους. Όταν αποκατασταθεί η σύνδεση, αποστέλλεται ένας χρόνος αδράνειας στον διακομιστή.

Παρεμπιπτόντως, ο τελευταίος ύπνος χρειάζεται μόνο για να γίνει ασύγχρονη η συνάρτηση - στη βιβλιοθήκη καλείται μέσω αναμονής και μπορούν να κληθούν μόνο οι λειτουργίες στο σώμα των οποίων υπάρχει άλλη αναμονή.

Εκτός από τη σύνδεση στο WiFi, πρέπει επίσης να δημιουργήσετε μια σύνδεση με τον μεσίτη (διακομιστή) MQTT. Αυτό γίνεται επίσης από τη βιβλιοθήκη και έχουμε την ευκαιρία να κάνουμε κάτι χρήσιμο όταν δημιουργηθεί η σύνδεση

    async def mqtt_connection_handler(self, client):
        await client.subscribe(self._mqtt_cold_water_theme)
        await client.subscribe(self._mqtt_hot_water_theme)

Εδώ εγγραφούμε σε πολλά μηνύματα - ο διακομιστής έχει πλέον τη δυνατότητα να ορίσει τις τρέχουσες τιμές των μετρητών στέλνοντας το κατάλληλο μήνυμα.

    def mqtt_msg_handler(self, topic, msg):
        topicstr = str(topic, 'utf8')
        self.dprint("Received MQTT message topic={}, msg={}".format(topicstr, msg))

        if topicstr == self._mqtt_cold_water_theme:
            self.cold_counter.set_value(int(msg))

        if topicstr == self._mqtt_hot_water_theme:
            self.hot_counter.set_value(int(msg))

Αυτή η συνάρτηση επεξεργάζεται τα εισερχόμενα μηνύματα και ανάλογα με το θέμα (όνομα μηνύματος), οι τιμές ενός από τους μετρητές ενημερώνονται

Μερικές βοηθητικές λειτουργίες

    # Publish a message if WiFi and broker is up, else discard
    async def publish_msg(self, topic, msg):
        self.dprint("Publishing message on topic {}: {}".format(topic, msg))
        if not self.internet_outage:
            await self.publish(topic, msg)
        else:
            self.dprint("Message was not published - no internet connection")

Αυτή η λειτουργία είναι υπεύθυνη για την αποστολή ενός μηνύματος εάν δημιουργηθεί η σύνδεση. Εάν δεν υπάρχει σύνδεση, το μήνυμα αγνοείται.

Και αυτή είναι απλώς μια βολική λειτουργία που δημιουργεί και στέλνει μηνύματα εντοπισμού σφαλμάτων.

    async def publish_debug_msg(self, subtopic, msg):
        await self.publish_msg("{}/{}".format(self._mqtt_debug_water_theme, subtopic), str(msg))

Τόσο πολύ κείμενο και δεν έχουμε αναβοσβήσει ακόμα το LED. Εδώ

    # Blink flash LED if WiFi down
    async def _heartbeat(self):
        while True:
            if self.internet_outage:
                self.blue_led(not self.blue_led()) # Fast blinking if no connection
                await asyncio.sleep_ms(200) 
            else:
                self.blue_led(0) # Rare blinking when connected
                await asyncio.sleep_ms(50)
                self.blue_led(1)
                await asyncio.sleep_ms(5000)

Παρείχα 2 τρόπους αναλαμπής. Εάν η σύνδεση χαθεί (ή μόλις δημιουργηθεί), τότε η συσκευή θα αναβοσβήνει γρήγορα. Εάν δημιουργηθεί η σύνδεση, η συσκευή αναβοσβήνει κάθε 5 δευτερόλεπτα. Εάν είναι απαραίτητο, εδώ μπορούν να εφαρμοστούν άλλοι τρόποι αναβοσβήνει.

Αλλά το LED είναι τόσο, περιποιητικό. Κουνηθήκαμε και στην οθόνη.

    async def _display_coro(self):
        display = SSD1306_I2C(128,32, i2c)
    
        while True:
            display.poweron()
            display.fill(0)
            display.text("COLD: {:.3f}".format(self.cold_counter.value() / 1000), 16, 4)
            display.text("HOT:  {:.3f}".format(self.hot_counter.value() / 1000), 16, 20)
            display.show()
            await asyncio.sleep(3)
            display.poweroff()

            while self.button():
                await asyncio.sleep_ms(20)

Αυτό μιλούσα - πόσο απλό και βολικό είναι με τις κορουτίνες. Αυτή η μικρή συνάρτηση περιγράφει ΟΛΕΣ τις αλληλεπιδράσεις των χρηστών. Η κορουτίνα απλώς περιμένει να πατηθεί το κουμπί και ανάβει την οθόνη για 3 δευτερόλεπτα. Η οθόνη εμφανίζει τις τρέχουσες ενδείξεις του μετρητή.

Έχουν μείνει μερικά μικρά πράγματα ακόμα. Εδώ είναι η λειτουργία που (ξανα)ξεκινά όλη αυτή την οικονομία. Ο κύριος βρόχος ασχολείται μόνο με την αποστολή διαφόρων πληροφοριών εντοπισμού σφαλμάτων μία φορά το λεπτό. Γενικά, το δίνω όπως είναι - δεν χρειάζεται να σχολιάσω συγκεκριμένα, νομίζω

   async def main(self):
        while True:
            try:
                await self._connect_to_WiFi()
                await self._run_main_loop()
                    
            except Exception as e:
                self.dprint('Global communication failure: ', e)
                await asyncio.sleep(20)

    async def _connect_to_WiFi(self):
        self.dprint('Connecting to WiFi and MQTT')
        sta_if = network.WLAN(network.STA_IF)
        sta_if.connect(config['ssid'], config['wifi_pw'])
        
        conn = False
        while not conn:
            await self.connect()
            conn = True

        self.dprint('Connected!')
        self.internet_outage = False

    async def _run_main_loop(self):
        # Loop forever
        mins = 0
        while True:
            gc.collect()  # For RAM stats.
            mem_free = gc.mem_free()
            mem_alloc = gc.mem_alloc()

            try:
                await self.publish_debug_msg("Uptime", mins)
                await self.publish_debug_msg("Repubs", self.REPUB_COUNT)
                await self.publish_debug_msg("Outages", self.internet_outages)
                await self.publish_debug_msg("MemFree", mem_free)
                await self.publish_debug_msg("MemAlloc", mem_alloc)
            except Exception as e:
                self.dprint("Exception occurred: ", e)
            mins += 1

            await asyncio.sleep(60)

Λοιπόν, μερικές ακόμη ρυθμίσεις και σταθερές για την πληρότητα της περιγραφής

#####################################
# Constants and configuration
#####################################


config['keepalive'] = 60
config['clean'] = False
config['will'] = ('/ESP/Wemos/Water/LastWill', 'Goodbye cruel world!', False, 0)

MQTTClient.DEBUG = True

EEPROM_ADDR_HOT_VALUE = const(0)
EEPROM_ADDR_COLD_VALUE = const(4)

Όλα ξεκινούν κάπως έτσι

client = CounterMQTTClient()
loop = asyncio.get_event_loop()
loop.run_until_complete(client.main())

Κάτι συνέβη στη μνήμη μου

Οπότε, όλος ο κώδικας είναι εκεί. Ανέβασα τα αρχεία χρησιμοποιώντας το βοηθητικό πρόγραμμα ampy - σας επιτρέπει να τα ανεβάσετε στην εσωτερική (αυτή στην ίδια την ESP-07) μονάδα flash και, στη συνέχεια, να αποκτήσετε πρόσβαση από το πρόγραμμα ως κανονικά αρχεία. Εκεί ανέβασα επίσης τις βιβλιοθήκες mqtt_as, uasyncio, ssd1306 και συλλογών που χρησιμοποίησα (που χρησιμοποιούνται μέσα στο mqtt_as).

Ξεκινάμε και... Λαμβάνουμε MemoryError. Επιπλέον, όσο περισσότερο προσπαθούσα να καταλάβω πού ακριβώς διέρρεε η μνήμη, όσο περισσότερο διόρθωνα τις εκτυπώσεις, τόσο νωρίτερα εμφανιζόταν αυτό το σφάλμα. Ένα σύντομο google με οδήγησε να καταλάβω ότι στον μικροελεγκτή, καταρχήν, υπάρχουν μόνο 30 kb μνήμης στην οποία 65 kb κώδικα (μαζί με βιβλιοθήκες) δεν χωρούν με κανέναν τρόπο.

Υπάρχει όμως διέξοδος. Αποδεικνύεται ότι το micropython δεν εκτελεί κώδικα απευθείας από ένα αρχείο .py - αυτό το αρχείο μεταγλωττίζεται πρώτα. Επιπλέον, μεταγλωττίζεται απευθείας στον μικροελεγκτή, μετατρέπεται σε bytecode, ο οποίος στη συνέχεια αποθηκεύεται στη μνήμη. Λοιπόν, ο μεταγλωττιστής χρειάζεται επίσης μια συγκεκριμένη ποσότητα μνήμης RAM για να λειτουργήσει.

Το κόλπο είναι να σώσετε τον μικροελεγκτή από μεταγλώττιση που απαιτεί πόρους. Μπορείτε να μεταγλωττίσετε αρχεία σε έναν μεγάλο υπολογιστή και να ανεβάσετε έτοιμο bytecode στον μικροελεγκτή. Για να γίνει αυτό, πρέπει να κατεβάσετε το υλικολογισμικό micropython και να το δημιουργήσετε βοηθητικό πρόγραμμα mpy-cross.

Δεν έγραψα ένα Makefile, αλλά πέρασα με μη αυτόματο τρόπο και μεταγλωττίζω όλα τα απαραίτητα αρχεία (συμπεριλαμβανομένων των βιβλιοθηκών) όπως αυτό

mpy-cross water_counter.py

Μένει μόνο να συμπληρώσετε τα αρχεία με την επέκταση .mpy, θυμηθείτε να αφαιρέσετε πρώτα τα αντίστοιχα αρχεία .py από το σύστημα αρχείων της συσκευής.

Έκανα όλη την ανάπτυξη στο πρόγραμμα (IDE;) ESPlorer. Σας επιτρέπει να ανεβάσετε σενάρια στον μικροελεγκτή και να τα εκτελέσετε αμέσως. Στην περίπτωσή μου, όλη η λογική και η δημιουργία όλων των αντικειμένων βρίσκονται στο αρχείο water_counter.py (.mpy). Αλλά για να ξεκινήσουν όλα αυτά αυτόματα στην αρχή, πρέπει να υπάρχει και ένα αρχείο που ονομάζεται main.py. Επιπλέον, πρέπει να είναι ακριβώς .py και όχι προμεταγλωττισμένο .mpy. Εδώ είναι το ασήμαντο περιεχόμενό του

import water_counter

Ξεκινάμε - όλα λειτουργούν. Αλλά η ελεύθερη μνήμη είναι απειλητικά μικρή - περίπου 1 kb. Έχω ακόμα σχέδια να επεκτείνω τη λειτουργικότητα της συσκευής και αυτό το kilobyte προφανώς δεν θα είναι αρκετό για μένα. Αλλά αποδείχθηκε ότι υπάρχει διέξοδος.

Το θέμα είναι αυτό. Παρόλο που τα αρχεία μεταγλωττίζονται σε bytecode και βρίσκονται στο εσωτερικό σύστημα αρχείων, στην πραγματικότητα φορτώνονται στη μνήμη RAM και εκτελούνται από εκεί ούτως ή άλλως. Αλλά αποδεικνύεται ότι η micropython μπορεί να εκτελέσει bytecode απευθείας από τη μνήμη flash, αλλά για αυτό πρέπει να το δημιουργήσετε απευθείας στο υλικολογισμικό. Δεν είναι δύσκολο, αν και χρειάστηκε αρκετός χρόνος στο netbook μου (μόνο εκεί είχα Linux).

Ο αλγόριθμος είναι αυτός:

  • Λήψη και εγκατάσταση ESP Άνοιγμα SDK. Αυτό το πράγμα δημιουργεί έναν μεταγλωττιστή και βιβλιοθήκες για προγράμματα κάτω από το ESP8266. Συναρμολογείται σύμφωνα με τις οδηγίες στην κεντρική σελίδα του έργου (διάλεξα τη ρύθμιση STANDALONE=yes)
  • Λήψη μικροπύθωνα είδη
  • Ρίξτε τις απαραίτητες βιβλιοθήκες σε ports/esp8266/modules μέσα στο δέντρο micropython
  • Συλλέγουμε το υλικολογισμικό σύμφωνα με τις οδηγίες στο αρχείο ports/esp8266/README.md
  • Ανεβάστε το υλικολογισμικό στον μικροελεγκτή (το κάνω στα Windows χρησιμοποιώντας τα προγράμματα ESP8266Flasher ή το esptool της Python)

Όλα, τώρα το 'import ssd1306' θα ανεβάσει τον κωδικό απευθείας από το υλικολογισμικό και η RAM δεν θα δαπανηθεί για αυτό. Με αυτό το κόλπο, ανέβασα μόνο τον κωδικό της βιβλιοθήκης στο firmware, ενώ ο κύριος κώδικας του προγράμματος εκτελείται από το σύστημα αρχείων. Αυτό καθιστά εύκολη την τροποποίηση του προγράμματος χωρίς την εκ νέου μεταγλώττιση του υλικολογισμικού. Αυτή τη στιγμή, έχω περίπου 8.5 kb δωρεάν RAM. Αυτό θα μας επιτρέψει να εφαρμόσουμε πολλές διαφορετικές χρήσιμες λειτουργίες στο μέλλον. Λοιπόν, εάν δεν υπάρχει καθόλου αρκετή μνήμη, τότε μπορείτε να ωθήσετε το κύριο πρόγραμμα στο υλικολογισμικό.

Και τι να το κάνεις τώρα;

Οκ, το κομμάτι σίδερο είναι κολλημένο, το firmware είναι γραμμένο, το κουτί είναι τυπωμένο, η συσκευή έχει κολλήσει στον τοίχο και το φως αναβοσβήνει χαρούμενα. Αλλά μέχρι στιγμής όλα αυτά είναι ένα μαύρο κουτί (κυριολεκτικά και μεταφορικά) και εξακολουθεί να υπάρχει λίγο νόημα από αυτό. Ήρθε η ώρα να κάνετε κάτι με τα μηνύματα MQTT που αποστέλλονται στον διακομιστή.

Το «έξυπνο σπίτι» μου περιστρέφεται Σύστημα Majordomo. Η μονάδα MQTT είτε είναι εκτός συσκευασίας είτε εγκαθίσταται εύκολα από την αγορά πρόσθετων - δεν θυμάμαι από πού προήλθε. Το MQTT δεν είναι ένα αυτάρκης πράγμα - χρειάζεστε ένα λεγόμενο. broker - ένας διακομιστής που δέχεται, ταξινομεί και προωθεί μηνύματα σε πελάτες MQTT. Χρησιμοποιώ το mosquitto, το οποίο (όπως το majordomo) τρέχει στο ίδιο netbook.

Αφού η συσκευή στείλει ένα μήνυμα τουλάχιστον μία φορά, η τιμή θα εμφανιστεί αμέσως στη λίστα.

Συνδέουμε τον μετρητή νερού στο smart home

Αυτές οι τιμές μπορούν πλέον να συσχετιστούν με αντικείμενα συστήματος, μπορούν να χρησιμοποιηθούν σε σενάρια αυτοματισμού και να υποβληθούν σε διάφορες αναλύσεις - όλα αυτά δεν εμπίπτουν στο πεδίο εφαρμογής αυτού του άρθρου. Όποιος ενδιαφέρεται για το σύστημα majordomo, μπορώ να προτείνω Ηλεκτρονικά καναλιών σε φακό - ένας φίλος κατασκευάζει επίσης ένα έξυπνο σπίτι και μιλάει κατανοητά για τη ρύθμιση του συστήματος.

Θα σας δείξω μόνο μερικά γραφήματα. Αυτό είναι ένα απλό γράφημα τιμών ανά ημέρα

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

Από αυτόν τον πίνακα, έμαθα ότι το να πηγαίνεις στην τουαλέτα είναι 6-7 λίτρα νερό, το ντους είναι 20-30 λίτρα, το πλύσιμο των πιάτων είναι περίπου 20 λίτρα και το μπάνιο απαιτεί 160 λίτρα. Την ημέρα η οικογένειά μου καταναλώνει κάπου 500-600l.

Για όσους είναι ιδιαίτερα περίεργοι, μπορείτε να δείτε τις εγγραφές για κάθε μεμονωμένη τιμή.

Συνδέουμε τον μετρητή νερού στο smart home

Από εδώ έμαθα ότι όταν η βρύση είναι ανοιχτή, το νερό ρέει με ταχύτητα περίπου 1 λίτρου σε 5 δευτερόλεπτα.

Αλλά σε αυτή τη μορφή, τα στατιστικά μάλλον δεν είναι πολύ βολικά για να τα δούμε. Το majordomo έχει επίσης τη δυνατότητα προβολής διαγραμμάτων κατανάλωσης ανά ημέρα, εβδομάδα και μήνα. Εδώ, για παράδειγμα, είναι ένα γράφημα της κατανάλωσης σε στήλες

Συνδέουμε τον μετρητή νερού στο smart home

Μέχρι στιγμής έχω μόνο μια εβδομάδα δεδομένα. Σε ένα μήνα, αυτό το γράφημα θα είναι πιο αποκαλυπτικό - μια ξεχωριστή στήλη θα αντιστοιχεί σε κάθε μέρα. Η εικόνα είναι ελαφρώς αλλοιωμένη από τις προσαρμογές των τιμών που εισάγω χειροκίνητα (η μεγαλύτερη στήλη). Και δεν είναι ακόμη σαφές αν έβαλα εσφαλμένα τις πρώτες τιμές σχεδόν έναν κύβο λιγότερο ή αν πρόκειται για σφάλμα στο υλικολογισμικό και δεν ελήφθησαν υπόψη όλα τα λίτρα. Χρειάζομαι περισσότερο χρόνο.

Πάνω από τα ίδια τα γραφήματα, πρέπει ακόμα να σκιαγραφήσετε, να λευκάνετε, να ζωγραφίσετε. Ίσως θα δημιουργήσω επίσης ένα γράφημα της κατανάλωσης μνήμης για σκοπούς εντοπισμού σφαλμάτων - ξαφνικά κάτι διαρρέει εκεί. Ίσως εμφανίσω με κάποιο τρόπο τις περιόδους που δεν υπήρχε Διαδίκτυο. Ενώ όλα αυτά περιστρέφονται στο επίπεδο της ιδέας.

Συμπέρασμα

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

Θα φανεί παράξενο σε κάποιον να κοιτάξει τις ενδείξεις στην οθόνη αν απέχει ένα μέτρο από τον ίδιο τον μετρητή. Αλλά στο όχι πολύ μακρινό μέλλον, σκοπεύω να μετακομίσω σε άλλο διαμέρισμα, όπου θα υπάρχουν αρκετοί ανυψωτήρες νερού και οι ίδιοι οι μετρητές, πιθανότατα, θα βρίσκονται στην προσγείωση. Έτσι, μια συσκευή απομακρυσμένης ανάγνωσης θα ήταν πολύ βολική.

Σκοπεύω επίσης να επεκτείνω τη λειτουργικότητα της συσκευής. Κοιτάζω ήδη μηχανοκίνητες βαλβίδες. Τώρα, για να αλλάξω το νερό της πόλης του λέβητα, πρέπει να γυρίσω 3 βρύσες σε μια δυσπρόσιτη θέση. Θα ήταν πολύ πιο βολικό να το κάνετε αυτό με ένα κουμπί με την αντίστοιχη ένδειξη. Λοιπόν, φυσικά, αξίζει να εφαρμοστεί προστασία από διαρροές.

Στο άρθρο, είπα τη δική μου έκδοση της συσκευής που βασίζεται στο ESP8266. Κατά τη γνώμη μου, πήρα μια πολύ ενδιαφέρουσα έκδοση του υλικολογισμικού micropython χρησιμοποιώντας κορουτίνες - απλή και όμορφη. Προσπάθησα να περιγράψω τις πολλές αποχρώσεις και τα εμπόδια που συνάντησα κατά τη διάρκεια της εκστρατείας. Ίσως περιέγραψα τα πάντα με υπερβολική λεπτομέρεια, για μένα προσωπικά, ως αναγνώστη, είναι πιο εύκολο να σπαταλήσω το πλεόνασμα παρά να σκεφτώ τι έμεινε ανείπωτο αργότερα.

Όπως πάντα, είμαι ανοιχτός σε εποικοδομητική κριτική.

Κωδικός πηγής
Σχηματική και πίνακας
Μοντέλο υπόθεσης

Πηγή: www.habr.com

Προσθέστε ένα σχόλιο