Su sayğacını ağıllı evə bağlayırıq

Bir zamanlar ev avtomatlaşdırma sistemləri və ya onlar tez-tez "ağıllı ev" adlanan sistemlər olduqca bahalı idi və onları yalnız zənginlər ödəyə bilərdi. Bu gün bazarda işıqlandırma, rozetkalar, havalandırma, su təchizatı və digər istehlakçılara nəzarət etmək üçün sensorlar, düymələr / açarlar və aktuatorlar olan kifayət qədər büdcə dəstləri tapa bilərsiniz. Və hətta ən əyri DIY-shnik gözəlliyə qoşula və ucuz qiymətə ağıllı ev üçün cihazlar yığa bilər.

Su sayğacını ağıllı evə bağlayırıq

Bir qayda olaraq, təklif olunan qurğular ya sensorlar, ya da aktuatorlardır. Onlar “hərəkət sensoru işə salındıqda işığı yandırın” və ya “çıxışın yaxınlığındakı açar bütün mənzildə işığı söndürür” kimi ssenariləri həyata keçirməyi asanlaşdırır. Ancaq nədənsə telemetriya ilə nəticə vermədi. Ən yaxşı halda, bu, müəyyən bir çıxışda temperatur və rütubətin və ya ani gücün qrafikidir.

Bu yaxınlarda nəbz çıxışı olan su sayğaclarını quraşdırdım. Sayğacdan keçən hər litr vasitəsilə qamış açarı işə salınır və kontaktı bağlayır. Qalan tək şey naqillərdən yapışıb ondan bir fayda əldə etməyə çalışmaqdır. Məsələn, həftənin saatları və günləri ilə su istehlakını təhlil edin. Yaxşı, mənzildə su üçün bir neçə qaldırıcı varsa, o zaman bütün mövcud göstəriciləri bir ekranda görmək, fənər ilə çətin əldə edilə bilən boşluqlara qalxmaqdan daha rahatdır.

Kəsmə altında, su sayğaclarından impulsları sayan və oxunuşları MQTT vasitəsilə ağıllı ev serverinə göndərən ESP8266 əsasında cihazım versiyası. Uasyncio kitabxanasından istifadə edərək micropython-da proqramlaşdıracağıq. Firmware yaratarkən bir neçə maraqlı çətinliklə qarşılaşdım, bunları da bu məqalədə müzakirə edəcəyəm. Get!

Sxem

Su sayğacını ağıllı evə bağlayırıq

Bütün dövrənin ürəyi ESP8266 mikro nəzarət cihazındakı moduldur. ESP-12 əvvəlcə planlaşdırıldı, amma mənimki qüsurlu oldu. Mən mövcud ESP-07 modulu ilə kifayətlənməli idim. Xoşbəxtlikdən, onlar həm nəticələr, həm də funksionallıq baxımından eynidır, yeganə fərq antenadadır - ESP-12-də quraşdırılmış, ESP-07-də ​​isə xarici var. Bununla belə, WiFi antenası olmasa belə, vanna otağımdakı siqnal normal olaraq tutulur.

Modulun bağlanması standartdır:

  • açılan və kondansatörlü sıfırlama düyməsi (hər ikisi artıq modulun içərisində olsa da)
  • Aktivləşdirmə siqnalı (CH_PD) gücə qədər çəkilir
  • GPIO15 yerə çəkildi. Bu, yalnız başlanğıcda lazımdır, amma yenə də bu ayağımdan yapışmağa ehtiyac yoxdur

Modulu proshivka rejiminə ötürmək üçün GPIO2-ni yerə bağlamaq lazımdır və onu daha rahat etmək üçün Boot düyməsini təqdim etdim. Normal vəziyyətdə, bu pin gücə qədər çəkilir.

GPIO2 xəttinin vəziyyəti yalnız əməliyyatın əvvəlində - enerji verildikdə və ya sıfırlamadan dərhal sonra yoxlanılır. Beləliklə, modul ya həmişəki kimi yüklənir, ya da proqram təminatı rejiminə keçir. Yükləndikdən sonra bu pin adi GPIO kimi istifadə edilə bilər. Yaxşı, orada artıq bir düymə olduğundan, ona bəzi faydalı funksiyaları asmaq olar.

Proqramlaşdırma və ayıklama üçün tarağa gətirdiyim UART-dan istifadə edəcəyəm. Lazım olanda oraya sadəcə USB-UART adapterini qoşuram. Yadda saxlamaq lazımdır ki, modul 3.3V ilə işləyir. Adapteri bu gərginliyə keçirməyi və 5V tətbiq etməyi unutsanız, modul çox güman ki, yanacaq.

Banyoda elektrik enerjisi ilə bağlı heç bir problemim yoxdur - çıxış sayğaclardan təxminən bir metr məsafədə yerləşir, ona görə də onu 220V-dən gücləndirəcəyəm. Güc mənbəyi olaraq məndə kiçik olacaq blok HLK-PM03 Tenstar Robot tərəfindən. Şəxsən mən analoq və güc elektronikası ilə çətinlik çəkirəm və burada kiçik bir qutuda hazır enerji təchizatı var.

İş rejimlərinə siqnal vermək üçün GPIO2-yə qoşulmuş bir LED təqdim etdim. Bununla belə, mən onu lehimləməmişəm, çünki. ESP-07 modulunda artıq eyni GPIO2-yə qoşulmuş bir LED var. Ancaq lövhədə olsun - birdən bu LED-i işə gətirmək istəyirəm.

Ən maraqlısına keçək. Su sayğaclarının məntiqi yoxdur, onlardan cari oxunuşlar tələb oluna bilməz. Bizim üçün mövcud olan yeganə şey impulslardır - hər litr qamış açarının kontaktlarını bağlamaq. GPIO12 / GPIO13-də reed keçid çıxışlarım var. Mən modulun içərisində proqramlı şəkildə açılan rezistoru açacağam.

Əvvəlcə R8 və R9 rezistorlarını verməyi unutdum və onlar lövhənin mənim versiyamda yoxdur. Amma mən artıq hər kəsin görəcəyi sxemi tərtib etdiyim üçün bu nöqsanı düzəltməyə dəyər. Rezistorlar, proqram təminatı səhvdirsə və pin üzərində bir vahid quraşdırırsa, portu yandırmamaq üçün lazımdır və qamış açarı bu xətti yerə qısaldır (rezistor ilə maksimum 3.3V / 1000Ω = 3.3mA axacaq) .

Elektrik sönsə nə edəcəyini düşünmək vaxtıdır. Birinci seçim serverdən başlanğıcda sayğacların ilkin dəyərlərini soruşmaqdır. Lakin bu, mübadilə protokolunun əhəmiyyətli dərəcədə mürəkkəbləşməsini tələb edəcək. Üstəlik, bu vəziyyətdə cihazın performansı serverin vəziyyətindən asılıdır. İşığı söndürdükdən sonra server başlamazsa (və ya daha sonra işə salınsa), su sayğacı ilkin dəyərləri tələb edə bilməyəcək və səhv işləyəcək.

Buna görə də, sayğac dəyərlərinin saxlanmasını I2C vasitəsilə birləşdirilən yaddaş çipində həyata keçirmək qərarına gəldim. Fləş yaddaşın ölçüsünə heç bir xüsusi tələbim yoxdur - yalnız 2 nömrə saxlamaq lazımdır (isti və soyuq su sayğaclarına görə litrlərin sayı). Ən kiçik modul belə edəcək. Ancaq yazma dövrlərinin sayına diqqət yetirmək lazımdır. Əksər modullar üçün bu, 100 min dövrə, bəziləri üçün bir milyona qədərdir.

Deyəsən, bir milyon çox şeydir. Ancaq mənzilimdə yaşadığım 4 il ərzində mən 500 kubmetrdən bir az çox su sərf etdim, bu 500 min litrdir! Və flaşda 500 min qeyd. Və bu sadəcə soyuq sudur. Əlbəttə ki, hər iki ildən bir çipi yenidən lehimləyə bilərsiniz, lakin FRAM çipləri olduğu ortaya çıxdı. Proqramlaşdırma nöqteyi-nəzərindən bu, eyni I2C EEPROM-dur, yalnız çox sayda təkrar yazma dövrü (yüz milyonlarla). Bu, hələ də belə mikrosxemləri olan bir mağazaya gedə bilməyənə qədər, buna görə də hələlik adi 24LC512 dayanacaq.

Çap olunmuş lövhə

Əvvəlcə evdə taxta hazırlamağı planlaşdırırdım. Buna görə də lövhə birtərəfli olaraq hazırlanmışdır. Ancaq lazer ütü və lehim maskası ilə bir saat sərf etdikdən sonra (onsuz bir şey olmaz), buna baxmayaraq, çinlilərdən lövhələr sifariş etmək qərarına gəldim.

Su sayğacını ağıllı evə bağlayırıq

Demək olar ki, lövhəni sifariş etməzdən əvvəl anladım ki, flash yaddaş çipindən əlavə, I2C avtobusuna başqa faydalı bir şey, məsələn, displey bağlaya bilərsiniz. Bunun üçün dəqiq nə çıxarmaq hələ sualdır, ancaq onu lövhədə yetişdirmək lazımdır. Yaxşı, fabrikdə lövhələr sifariş edəcəyim üçün özümü birtərəfli lövhə ilə məhdudlaşdırmağın mənası yox idi, buna görə də I2C xətləri lövhənin arxasındakı yeganədir.

Bir böyük tıxac da birtərəfli naqillərlə birləşdirildi. Çünki lövhə bir tərəfli çəkildi, sonra izlərin və SMD komponentlərinin bir tərəfə, digər tərəfdən isə çıxış komponentlərinin, birləşdiricilərin və enerji təchizatının yerləşdirilməsi planlaşdırılırdı. Bir ay sonra lövhələri alanda mən orijinal planı unutdum və ön tərəfdəki bütün komponentləri lehimlədim. Və yalnız enerji təchizatını lehimləməyə gəldikdə, artı və mənfi tərəflərin əksinə ayrıldığı ortaya çıxdı. Mən tullananlarla əkinçilik etməli oldum. Yuxarıdakı şəkildə, mən artıq naqilləri dəyişdirmişəm, lakin zəmin Boot düyməsinin sancaqları vasitəsilə lövhənin bir hissəsindən digərinə köçürülür (baxmayaraq ki, ikinci təbəqədə bir iz çəkmək mümkün olardı).

Belə çıxdı

Su sayğacını ağıllı evə bağlayırıq

Mənzil

Növbəti addım bədəndir. Əgər 3D printeriniz varsa, bu problem deyil. Çox narahat olmadım - sadəcə düzgün ölçülü bir qutu çəkdim və lazımi yerlərdə kəsiklər etdim. Qapaq gövdəyə kiçik özünü vurma vintləri ilə bərkidilir.

Su sayğacını ağıllı evə bağlayırıq

Artıq qeyd etdim ki, Boot düyməsini ümumi təyinatlı düymə kimi istifadə etmək olar - buna görə də onu ön panelə gətirək. Bunu etmək üçün düymənin yaşadığı xüsusi bir "quyu" çəkdim.

Su sayğacını ağıllı evə bağlayırıq

Korpusun içərisində lövhənin quraşdırıldığı və bir M3 vinti ilə sabitləndiyi kötüklər də var (lövhədə daha çox yer yox idi)

Mən korpusun ilk uyğun versiyasını çap edəndə displey artıq seçilmişdi. Standart iki xəttli printer bu işə uyğun gəlmədi, lakin barelin altındakı OLED displey SSD1306 128 × 32 idi. Kiçikdir, amma mən hər gün ona baxmıram - yuvarlanacaq.

Oradan naqillərin necə çəkiləcəyini, o tərəfə, o tərəfə təxmin edərək, ekranı korpusun ortasına yapışdırmaq qərarına gəldim. Erqonomika, əlbəttə ki, plintusun altında - düymə yuxarıda, displey aşağıdadır. Amma mən artıq dedim ki, ekranı vidalamaq ideyası çox gec gəldi və düyməni hərəkət etdirmək üçün lövhəni yenidən tel çəkmək üçün çox tənbəl idim.

Yığılmış cihaz. Ekran modulu isti yapışqan ilə snota yapışdırılır

Su sayğacını ağıllı evə bağlayırıq

Su sayğacını ağıllı evə bağlayırıq

Son nəticəni KDPV-də görmək olar

Firmware

Proqram təminatı hissəsinə keçək. Belə kiçik sənətkarlıq üçün Python dilindən istifadə etməyi çox sevirəm (mikropiton) - kod çox yığcam və başa düşüləndir. Xoşbəxtlikdən, mikrosaniyələri sıxışdırmaq üçün registrlər səviyyəsinə enməyə ehtiyac yoxdur - hər şeyi pitondan etmək olar.

Görünür, hər şey sadədir, lakin çox deyil - cihaz bir neçə müstəqil funksiyaya malikdir:

  • İstifadəçi bir düyməyə toxunur və ekrana baxır
  • Litrlər flaş yaddaşdakı dəyərləri qeyd edir və yeniləyir
  • Modul WiFi siqnalını izləyir və lazım olduqda yenidən qoşulur
  • Yaxşı, yanıb-sönən bir lampa olmadan, ümumiyyətlə edə bilməzsiniz

Əgər digər funksiya nədənsə uğursuz olarsa, bir funksiyanın işləmədiyini güman edə bilməzsiniz. Mən artıq digər layihələrdə kaktuslar yemişəm və indi də “displey həmin anda yeniləndiyi üçün daha bir litr əldən verdim” və ya “modul WiFi-yə qoşulduqda istifadəçi heç nə edə bilməz” kimi nasazlıqlar görürəm. Əlbəttə ki, bəzi şeylər kəsilmələr vasitəsilə edilə bilər, lakin siz müddət, zənglərin yuvalanması və ya dəyişənlərin atomsuz dəyişməsi ilə bağlı məhdudiyyətlə üzləşə bilərsiniz. Yaxşı, hər şeyi edən və dərhal tez bir qarışıqlığa çevrilən kod.

В daha ciddi layihə Mən klassik preemptive multitasking və FreeRTOS istifadə etdim, lakin bu halda model daha uyğun oldu coroutines və uasync kitabxanaları . Üstəlik, koroutinlərin Python tətbiqi sadəcə bir bombadır - hər şey proqramçı üçün sadə və rahat şəkildə edilir. Sadəcə öz məntiqinizi yazın, sadəcə mövzular arasında keçid edə biləcəyinizi söyləyin.

Mən isteğe bağlı olaraq qabaqlayıcı və rəqabətli multitasking arasındakı fərqləri öyrənməyi təklif edirəm. İndi nəhayət koda keçək.

#####################################
# 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

Hər bir sayğac Counter sinifinin nümunəsi ilə idarə olunur. Əvvəla, sayğacın ilkin dəyəri EEPROM-dan (value_storage) çıxarılır - elektrik kəsilməsindən sonra bərpa belə həyata keçirilir.

Pəncərə enerji təchizatı üçün quraşdırılmış çəkmə ilə işə salınır: qamış açarı bağlıdırsa, xətt sıfırdır, xətt açıqdırsa, enerji mənbəyinə çəkilir və nəzarətçi birini oxuyur.

Həmçinin, burada pin sorğusu verəcək ayrı bir tapşırıq işə salınır. Hər sayğac öz vəzifəsini yerinə yetirəcək. Budur onun kodu

    """ 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)

Kontaktların sıçrayışını süzgəcdən keçirmək üçün 25 ms gecikmə tələb olunur və eyni zamanda tapşırığın nə qədər tez-tez oyanmasını tənzimləyir (bu tapşırıq yatarkən, digər tapşırıqlar işləyir). Hər 25 ms-dən bir funksiya oyanır, pinini yoxlayır və qamış keçid kontaktları bağlanıbsa, sayğacdan başqa bir litr keçib və bunun işlənməsi lazımdır.

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

        self._value_storage.write(self._value)

Növbəti litrin işlənməsi mənasızdır - sayğac sadəcə artır. Yaxşı, USB flash sürücüsünə yeni bir dəyər yazmaq yaxşı olardı.

İstifadə rahatlığı üçün "aksesuarlar" təmin edilir.

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

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

Yaxşı, indi python və uasync kitabxanasının ləzzətlərindən istifadə edək və sayğac obyektini gözləyək edək (onu rus dilinə necə tərcümə edə bilərəm? Gözlənilən biri?)

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

        return self.value()

    __iter__ = __await__  

Bu, sayğac dəyərinin yenilənməsini gözləyən o qədər lazımlı funksiyadır - funksiya vaxtaşırı oyanır və _value_changed bayrağını yoxlayır. Bu funksiyanın hiyləsi ondan ibarətdir ki, zəng kodu bu funksiyaya edilən zəng zamanı yuxuya gedə və yeni dəyər alınana qədər yata bilər.

Bəs fasilələr haqqında nə demək olar?Bəli, bu məqamda sən məni trollaya bilərsən ki, o, özü də fasilələr haqqında deyib, amma əslində o, axmaq bir sorğu təşkil edib. Əslində müdaxilələr cəhd etdiyim ilk şeydir. ESP8266-da siz ön tərəfdə kəsmə təşkil edə və hətta bu kəsmə üçün python-da kəsmə işləyicisi yaza bilərsiniz. Bu fasilədə siz dəyişənin dəyərini yeniləyə bilərsiniz. Yəqin ki, sayğac qul cihazı olsaydı, bu kifayət edərdi - bu dəyər tələb olunana qədər gözləyən cihaz.

Təəssüf ki (və ya xoşbəxtlikdən?), mənim cihazım aktivdir, özü MQTT protokolu vasitəsilə mesaj göndərməli və məlumatları EEPROM-a yazmalıdır. Və burada məhdudiyyətlər artıq gəlir - fasilələrdə yaddaş ayıra və böyük bir yığın istifadə edə bilməzsiniz, yəni şəbəkə üzərindən mesaj göndərməyi unuda bilərsiniz. micropython.schedule () kimi çörəylər var ki, hansısa funksiyanı “ancaq və dərhal” işə salmağa imkan verir, lakin “məqsədi nədir?” sualı yaranır. Birdən biz indi bir növ mesaj göndəririk və sonra fasilə pozulur və dəyişənlərin dəyərlərini korlayır. Və ya, məsələn, köhnəsini hələ qeyd etmədiyimiz halda serverdən yeni sayğac dəyəri gəldi. Ümumiyyətlə, sinxronizasiyanı bloklamaq və ya bir şəkildə fərqli bir şəkildə çıxmaq lazımdır.

Və vaxtaşırı RuntimeError: cədvəl yığınının tam qəzaları və bunun səbəbini kim bilir?

Açıq səsvermə və uasync ilə, bu halda, birtəhər daha gözəl və daha etibarlı olur.

Mən kiçik bir sinifdə EEPROM ilə iş tapdım

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)

Pythonda birbaşa baytlarla işləmək çətindir və yaddaşa yazılan baytlardır. Ustruct kitabxanasından istifadə edərək tam ədəd və baytlar arasında çevrilməni çəpərləməli oldum.

I2C obyektini və yaddaş hüceyrəsinin ünvanını hər dəfə ötürməmək üçün hamısını kiçik və rahat bir klassikaya sardım.

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 obyektinin özü bu parametrlərlə yaradılmışdır

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

Ən maraqlısına - MQTT vasitəsilə serverlə əlaqənin həyata keçirilməsinə yaxınlaşırıq. Yaxşı, protokolun özünü həyata keçirməyə ehtiyac yoxdur - mən onu İnternetdə tapdım hazır asinxron icra. Burada istifadə edəcəyik.

Ən maraqlıları MQTTClient kitabxanasına əsaslanan CounterMQTTClient sinfində toplanır. Periferiyadan başlayaq

#####################################
# 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))

Burada ampul və düymə sancaqları, həmçinin soyuq və isti su sayğacları obyektləri yaradılır və konfiqurasiya edilir.

Başlanğıc ilə hər şey o qədər də əhəmiyyətsiz deyil

    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 kitabxanasının parametrlərini təyin etmək üçün müxtəlif parametrlərin böyük lüğətindən istifadə olunur - config. Defolt parametrlərin əksəriyyəti bizim üçün işləyir, lakin bir çox parametrlər açıq şəkildə qurulmalıdır. Parametrləri birbaşa kodda təyin etməmək üçün onları config.txt mətn faylında saxlayıram. Bu, parametrlərdən asılı olmayaraq kodu dəyişdirməyə, həmçinin müxtəlif parametrləri olan bir neçə eyni cihazı pərçimləməyə imkan verir.

Son kod bloku müxtəlif sistem funksiyalarına xidmət etmək üçün bir neçə koroutinləri işə salır. Budur sayğaclara xidmət edən bir korutin nümunəsi

    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))

Koroutin dövrədə yeni sayğac dəyərini gözləyir və görünən kimi MQTT protokolu vasitəsilə mesaj göndərir. İlk kod parçası sayğacdan su axmasa belə ilkin dəyəri göndərir.

MQTTClient baza sinfi özünə xidmət edir, WiFi bağlantısı yaradır və əlaqə kəsildikdə yenidən qoşulur. WiFi bağlantısının vəziyyəti dəyişdikdə, kitabxana wifi_connection_handler-ə zəng edərək bizə məlumat verir

    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)

Funksiya nümunələrdən vicdanla yalandı. Bu zaman kəsilmələrin sayını (internet_kesintiləri) və onların müddətini hesablayır. Bağlantı bərpa edildikdə, serverə boş vaxt göndərilir.

Yeri gəlmişkən, sonuncu yuxu yalnız funksiyanın asinxron olması üçün lazımdır - kitabxanada gözləmə vasitəsilə çağırılır və yalnız bədənində başqa bir gözləmə olan funksiyaları çağırmaq olar.

WiFi-yə qoşulmaqla yanaşı, MQTT brokeri (server) ilə də əlaqə yaratmalısınız. Bu da kitabxana tərəfindən həyata keçirilir və əlaqə qurulanda biz faydalı iş görmək imkanı əldə edirik

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

Burada bir neçə mesaja abunə oluruq - server indi müvafiq mesaj göndərməklə sayğacların cari dəyərlərini təyin etmək imkanına malikdir.

    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))

Bu funksiya daxil olan mesajları emal edir və mövzudan (mesajın adı) asılı olaraq sayğaclardan birinin dəyərləri yenilənir.

Bir neçə köməkçi funksiya

    # 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")

Bu funksiya əlaqə qurulduqda mesaj göndərmək üçün məsuliyyət daşıyır. Bağlantı yoxdursa, mesaj nəzərə alınmır.

Və bu, yalnız debug mesajlarını yaradan və göndərən rahat bir funksiyadır.

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

Bu qədər mətn var və biz hələ LED-i söndürməmişik. Burada

    # 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)

Mən yanıb-sönmənin 2 rejimini təmin etdim. Bağlantı kəsilibsə (və ya yeni qurulursa), cihaz tez yanıb-sönəcək. Bağlantı qurulubsa, cihaz hər 5 saniyədən bir yanıb-sönür. Lazım gələrsə, burada yanıb-sönmənin digər rejimləri də həyata keçirilə bilər.

Ancaq LED çox ərköyündür. Biz də ekranda yelləndik.

    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)

Bu barədə danışırdım - koroutinlərlə nə qədər sadə və rahatdır. Bu kiçik funksiya BÜTÜN istifadəçi qarşılıqlı əlaqəsini təsvir edir. Koroutin sadəcə düymənin basılmasını gözləyir və ekranı 3 saniyə ərzində yandırır. Ekranda cari sayğac göstəriciləri göstərilir.

Hələ bir neçə xırda şey qalıb. Bütün bu iqtisadiyyatı (yenidən) işə salan funksiya budur. Əsas dövrə yalnız dəqiqədə bir dəfə müxtəlif sazlama məlumatlarının göndərilməsi ilə əlaqədardır. Ümumiyyətlə, mən onu olduğu kimi verirəm - məncə, konkret şərh verməyə ehtiyac yoxdur

   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)

Yaxşı, təsvirin tamlığı üçün daha bir neçə parametr və sabitlər

#####################################
# 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)

Hər şey belə başlayır

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

Yaddaşımda bir şey oldu

Beləliklə, bütün kodlar oradadır. Faylları ampy yardım proqramından istifadə edərək yüklədim - bu, onları daxili (ESP-07-nin özündə olan) flash sürücüyə yükləməyə və sonra proqramdan normal fayllar kimi daxil olmağa imkan verir. Orada mən də istifadə etdiyim mqtt_as, uasyncio, ssd1306 və kolleksiya kitabxanalarını yüklədim (mqtt_as daxilində istifadə olunur).

Biz başlayırıq və... MemoryError alırıq. Üstəlik, yaddaşın tam olaraq harada sızdığını anlamağa nə qədər çox çalışsam, çapları nə qədər çox düzəltdimsə, bu səhv bir o qədər tez baş verdi. Qısa bir google məni başa düşməyə vadar etdi ki, mikrokontrollerdə, prinsipcə, 30 kb kodun (kitabxanalarla birlikdə) heç bir şəkildə uyğun gəlməyən cəmi 65 kb yaddaş var.

Ancaq bir çıxış yolu var. Belə çıxır ki, micropython kodu birbaşa .py faylından icra etmir - bu fayl əvvəlcə tərtib edilir. Üstəlik, o, birbaşa mikrokontrollerdə tərtib edilir, sonra yaddaşda saxlanılan baytkoda çevrilir. Yaxşı, kompilyatorun işləməsi üçün də müəyyən miqdarda RAM lazımdır.

Hiylə mikrokontrolleri resurs-intensiv tərtibdən xilas etməkdir. Siz böyük bir kompüterdə faylları tərtib edə və hazır bayt kodunu mikrokontrollerə yükləyə bilərsiniz. Bunun üçün micropython proqram təminatını yükləməli və qurmalısınız mpy-cross yardım proqramı.

Mən bir Makefile yazmadım, lakin bütün lazımi faylları (kitabxanalar daxil olmaqla) əl ilə keçdim və bu kimi tərtib etdim.

mpy-cross water_counter.py

Yalnız .mpy uzantısı olan faylları doldurmaq qalır, əvvəlcə müvafiq .py fayllarını cihazın fayl sistemindən silməyi unutmayın.

Bütün inkişafları proqramda (IDE?) ESPlorer-də etdim. O, skriptləri mikrokontrollerə yükləməyə və dərhal icra etməyə imkan verir. Mənim vəziyyətimdə bütün məntiq və bütün obyektlərin yaradılması water_counter.py (.mpy) faylında yerləşir. Amma bütün bunların başlanğıcda avtomatik başlaması üçün main.py adlı fayl da olmalıdır. Üstəlik, o, dəqiq .py olmalıdır və əvvəlcədən tərtib edilməmiş .mpy olmalıdır. Budur onun mənasız məzmunu

import water_counter

Başlayırıq - hər şey işləyir. Ancaq boş yaddaş hədələyici dərəcədə kiçikdir - təxminən 1 kb. Hələ də cihazın funksionallığını genişləndirmək planlarım var və bu kilobayt mənim üçün açıq şəkildə kifayət etməyəcək. Amma məlum oldu ki, çıxış yolu var.

Məsələ bundadır. Fayllar bayt koduna yığılsa və daxili fayl sistemində yerləşsə də, əslində RAM-a yüklənir və hər halda oradan icra olunur. Ancaq məlum oldu ki, micropython bayt kodunu birbaşa flash yaddaşdan icra edə bilər, lakin bunun üçün onu birbaşa proqram təminatına qurmaq lazımdır. Bu, çətin deyil, baxmayaraq ki, mənim netbukda kifayət qədər vaxt apardım (yalnız orada Linux var idi).

Alqoritm aşağıdakı kimidir:

  • Yükləyin və quraşdırın ESP Açıq SDK. Bu şey ESP8266 altında proqramlar üçün kompilyator və kitabxanalar qurur. O, layihənin əsas səhifəsindəki təlimatlara uyğun yığılır (MƏSTƏK=bəli parametrini seçdim)
  • Download mikropiton növləri
  • Lazımi kitabxanaları micropython ağacının içərisindəki portlara/esp8266/modullara atın
  • Fayldakı təlimatlara uyğun olaraq proqram təminatını toplayırıq ports/esp8266/README.md
  • Mikrokontrollerə proqram təminatını yükləyin (mən bunu Windows-da ESP8266Flasher proqramlarından və ya Python-un esptool-dan istifadə edərək edirəm)

Hər şey, indi 'import ssd1306' kodu birbaşa proqram təminatından qaldıracaq və RAM bunun üçün xərclənməyəcək. Bu hiylə ilə mən yalnız kitabxana kodunu proshivkaya yüklədim, əsas proqram kodu isə fayl sistemindən icra olunur. Bu, proqram təminatını yenidən tərtib etmədən proqramı dəyişdirməyi asanlaşdırır. Hazırda məndə təxminən 8.5 kb pulsuz RAM var. Bu, gələcəkdə kifayət qədər müxtəlif faydalı funksiyaları həyata keçirməyə imkan verəcəkdir. Yaxşı, ümumiyyətlə kifayət qədər yaddaş yoxdursa, əsas proqramı proqram təminatına daxil edə bilərsiniz.

Və indi bununla nə etmək lazımdır?

Yaxşı, dəmir parçası lehimlənib, proshivka yazılıb, qutu çap olunub, cihaz divara yapışıb və işıq sevinclə yanıb-sönür. Ancaq indiyə qədər bunların hamısı qara qutudur (hərfi və məcazi mənada) və ondan hələ də az məna var. Serverə göndərilən MQTT mesajları ilə nəsə etmək vaxtıdır.

Mənim "ağıllı evim" fırlanır Majordomo sistemi. MQTT modulu ya qutudan çıxarılıb, ya da əlavə bazardan asanlıqla quraşdırılıb - haradan gəldiyini xatırlamıram. MQTT özünü təmin edən bir şey deyil - sözdə ehtiyacınız var. broker - mesajları qəbul edən, çeşidləyən və MQTT müştərilərinə yönləndirən server. Mən (majordomo kimi) eyni netbukda işləyən ağcaqanaddan istifadə edirəm.

Cihaz ən azı bir dəfə mesaj göndərdikdən sonra dəyər dərhal siyahıda görünəcək.

Su sayğacını ağıllı evə bağlayırıq

Bu dəyərlər indi sistem obyektləri ilə əlaqələndirilə bilər, onlar avtomatlaşdırma skriptlərində istifadə edilə və müxtəlif təhlillərə məruz qala bilər - bütün bunlar bu məqalənin əhatə dairəsindən kənardadır. Majordomo sistemi ilə maraqlananlara tövsiyə edə bilərəm Lensdə Kanal Elektronikası - bir dost da ağıllı ev tikir və başa düşülən şəkildə sistemin qurulmasından danışır.

Mən sizə sadəcə bir neçə qrafik göstərəcəyəm. Bu, gündəlik dəyərlərin sadə bir qrafikidir

Su sayğacını ağıllı evə bağlayırıq
Gecə sudan demək olar ki, heç kimin istifadə etmədiyi də görünür. Bir neçə dəfə kimsə tualetə getdi və əks osmoz filtri gecə bir neçə litr əmdiyinə bənzəyir. Səhər istehlakı əhəmiyyətli dərəcədə artır. Mən adətən qazandan su istifadə edirəm, amma sonra vanna qəbul etmək istədim və müvəqqəti olaraq şəhər isti suyuna keçdim - bu da aşağı qrafikdə aydın görünür.

Bu cədvəldən öyrəndim ki, tualetə getmək 6-7 litr, duş qəbul etmək 20-30 litr, qab-qacaq yumaq təxminən 20 litr, vanna qəbul etmək üçün isə 160 litr su lazımdır. Gün ərzində ailəm təxminən 500-600 litr istehlak edir.

Xüsusilə maraqlı olanlar üçün hər bir fərdi dəyər üçün qeydlərə baxa bilərsiniz.

Su sayğacını ağıllı evə bağlayırıq

Buradan öyrəndim ki, kran açıq olanda su 1 saniyəyə təxminən 5 litr sürətlə axır.

Amma bu formada statistikaya baxmaq yəqin ki, o qədər də əlverişli deyil. majordomo həmçinin gün, həftə və ay üzrə istehlak qrafiklərinə baxmaq imkanına malikdir. Burada, məsələn, sütunlarda istehlakın qrafiki

Su sayğacını ağıllı evə bağlayırıq

İndiyə qədər yalnız bir həftəlik məlumatım var. Bir aydan sonra bu qrafik daha aydın olacaq - ayrı bir sütun hər günə uyğun olacaq. Şəkil əl ilə daxil etdiyim dəyərlərin düzəlişləri ilə bir az korlanmışdır (ən böyük sütun). Və hələ aydın deyil ki, mən ilk dəyərləri səhv olaraq bir kub az təyin etmişəm və ya bu, proqram təminatında bir səhvdir və bütün litrlər nəzərə alınmayıb. Daha çox vaxt lazımdır.

Qrafiklərin üstündə, hələ də sehrləmək, ağartmaq, rəngləmək lazımdır. Bəlkə də ayıklama məqsədləri üçün yaddaş istehlakının qrafikini quracağam - birdən orada nəsə sızır. Bəlkə də internetin olmadığı dövrləri birtəhər göstərərəm. Bütün bunlar ideya səviyyəsində fırlanırkən.

Nəticə

Bu gün mənzilim bir az daha ağıllı oldu. Belə kiçik bir cihazla evdə su sərfiyyatına nəzarət etmək mənim üçün daha rahat olacaq. Əgər əvvəllər “bir ayda yenə çoxlu su sərf olundu” deyə qəzəblənirdimsə, indi bu istehlakın mənbəyini tapa bilərəm.

Sayğacın özündən bir metr məsafədədirsə, ekrandakı oxunuşlara baxmaq kimsə üçün qəribə görünəcək. Ancaq çox da uzaq olmayan gələcəkdə başqa bir mənzilə köçməyi planlaşdırıram, burada bir neçə su qaldırıcısı olacaq və sayğacların özləri, çox güman ki, enişdə yerləşəcəklər. Beləliklə, uzaqdan oxuma cihazı çox əlverişli olardı.

Mən cihazın funksional imkanlarını da genişləndirməyi planlaşdırıram. Mən artıq motorlu klapanlara baxıram. İndi, qazanxana-şəhər suyunu dəyişdirmək üçün, çətin əldə edilən yuvada 3 kranı çevirməliyəm. Bunu müvafiq göstərici ilə bir düymə ilə etmək daha rahat olardı. Əlbəttə ki, sızmalara qarşı qorunma tətbiq etməyə dəyər.

Məqalədə ESP8266 əsasında cihazın versiyasını söylədim. Fikrimcə, koroutinlərdən istifadə edərək micropython proqram təminatının çox maraqlı bir versiyasını əldə etdim - sadə və gözəl. Kampaniya zamanı rastlaşdığım bir çox nüansları və tıxacları təsvir etməyə çalışdım. Bəlkə də hər şeyi çox təfərrüatlı şəkildə təsvir etdim, şəxsən mənim üçün bir oxucu kimi, artıqlığı israf etmək, sonradan söylənməyənləri düşünməkdən daha asandır.

Həmişə olduğu kimi, konstruktiv tənqidə açığım.

Mənbə kodu
Sxematik və lövhə
Case modeli

Mənbə: www.habr.com

Добавить комментарий