Suv hisoblagichini aqlli uyga ulash

Bir vaqtlar uyni avtomatlashtirish tizimlari yoki ular tez-tez atalgan "aqlli uy" juda qimmatga tushdi va ularni faqat boylar sotib olishi mumkin edi. Bugungi kunda bozorda yorug'lik, rozetkalar, shamollatish, suv ta'minoti va boshqa iste'molchilarni boshqarish uchun sensorlar, tugmalar / kalitlar va aktuatorlar bilan juda arzon to'plamlarni topishingiz mumkin. Hatto eng qiyshiq DIY odam ham go'zallik bilan shug'ullanishi va arzon narxda aqlli uy uchun asboblar yig'ishi mumkin.

Suv hisoblagichini aqlli uyga ulash

Odatda, tavsiya etilgan qurilmalar sensorlar yoki aktuatorlardir. Ular "harakat sensori ishga tushganda, chiroqlarni yoqing" yoki "chiqish yaqinidagi kalit butun kvartiradagi chiroqlarni o'chiradi" kabi stsenariylarni amalga oshirishni osonlashtiradi. Lekin qandaydir tarzda telemetriya bilan ishlar amalga oshmadi. Eng yaxshi holatda, bu harorat va namlikning grafigi yoki ma'lum bir rozetkadagi bir lahzali quvvat.

Men yaqinda puls chiqishi bilan suv hisoblagichlarini o'rnatdim. Hisoblagichdan o'tadigan har bir litr uchun qamish kaliti ishga tushiriladi va kontaktni yopadi. Faqat simlarga yopishib olish va undan foyda olishga harakat qilish qoladi. Masalan, suv iste'molini haftaning soati va kuni bo'yicha tahlil qiling. Xo'sh, agar kvartirada bir nechta suv ko'targichlari bo'lsa, chiroq bilan erishish qiyin bo'lgan joylarga chiqishdan ko'ra, barcha joriy ko'rsatkichlarni bitta ekranda ko'rish qulayroqdir.

Kesim ostida mening ESP8266 asosidagi qurilmamning versiyasi bor, u suv hisoblagichlaridan pulslarni hisoblaydi va MQTT orqali aqlli uy serveriga o'qishni yuboradi. Biz uasyncio kutubxonasidan foydalanib, micropythonda dasturlashtiramiz. Mikrodasturni yaratishda men bir nechta qiziqarli qiyinchiliklarga duch keldim, ularni ham ushbu maqolada muhokama qilaman. Bor!

Sxema

Suv hisoblagichini aqlli uyga ulash

Butun sxemaning yuragi ESP8266 mikrokontrolleridagi moduldir. ESP-12 dastlab rejalashtirilgan edi, lekin meniki nuqsonli bo'lib chiqdi. Biz mavjud bo'lgan ESP-07 modulidan mamnun bo'lishimiz kerak edi. Yaxshiyamki, ular pinlar va funksionallik jihatidan bir xil, farq faqat antennada - ESP-12 o'rnatilgan, ESP-07 esa tashqi. Biroq, Wi-Fi antennasi bo'lmasa ham, mening hammomimdagi signal an'anaviy tarzda qabul qilinadi.

Standart modul simlari:

  • tortuvchi va kondensatorli qayta o'rnatish tugmasi (garchi ikkalasi ham modul ichida bo'lsa ham)
  • Yoqish signali (CH_PD) quvvatga tortiladi
  • GPIO15 yerga tortiladi. Bu faqat boshida kerak, lekin men hali ham bu oyog'iga bog'laydigan hech narsam yo'q; endi menga kerak emas

Modulni proshivka rejimiga o'tkazish uchun siz GPIO2 ni erga qisqa tutashuv qilishingiz kerak va uni qulayroq qilish uchun men yuklash tugmachasini taqdim etdim. Oddiy holatda, bu pin quvvatga tortiladi.

GPIO2 liniyasining holati faqat ish boshida - quvvat yoqilganda yoki qayta o'rnatilgandan so'ng darhol tekshiriladi. Shunday qilib, modul odatdagidek ishga tushadi yoki proshivka rejimiga o'tadi. Yuklangandan so'ng, bu pin oddiy GPIO sifatida ishlatilishi mumkin. Xo'sh, u erda allaqachon tugma mavjud bo'lganligi sababli, unga foydali funktsiyani biriktirishingiz mumkin.

Dasturlash va disk raskadrovka uchun men taroqqa chiqadigan UART dan foydalanaman. Agar kerak bo'lsa, men u erga USB-UART adapterini ulayman. Siz faqat modul 3.3V quvvatga ega ekanligini yodda tutishingiz kerak. Agar siz adapterni ushbu kuchlanishga o'tkazishni va 5V ni etkazib berishni unutsangiz, modul yonib ketishi mumkin.

Banyoda elektr energiyasi bilan bog'liq muammolarim yo'q - rozetka hisoblagichlardan bir metrga yaqin joylashgan, shuning uchun men 220V quvvatga ega bo'laman. Quvvat manbai sifatida menda kichik bo'ladi HLK-PM03 bloki Tenstar Robot tomonidan. Shaxsan men analog va quvvat elektronikasi bilan qiynalaman, lekin bu erda kichik bir holatda tayyor quvvat manbai.

Ish rejimlari haqida signal berish uchun men GPIO2 ga ulangan LEDni taqdim etdim. Biroq, men uni lehimlamadim, chunki ... ESP-07 modulida allaqachon LED mavjud va u GPIO2 ga ham ulangan. Ammo agar men ushbu LEDni korpusga chiqarmoqchi bo'lsam, u taxtada bo'lsin.

Keling, eng qiziqarli qismga o'tamiz. Suv hisoblagichlari mantiqqa ega emas, siz ulardan joriy ko'rsatkichlarni so'rashingiz mumkin emas. Biz uchun mavjud bo'lgan yagona narsa - bu impulslar - har litrda qamish kalitining kontaktlarini yopish. Mening reed switch chiqishlari GPIO12/GPIO13 ga ulangan. Men modul ichidagi tortishish qarshiligini dasturiy ravishda yoqaman.

Dastlab, men R8 va R9 rezistorlarini berishni unutganman va mening plata versiyamda ular yo'q. Ammo men allaqachon hamma ko'rishi uchun diagrammani joylashtirganim uchun, bu nazoratsizlikni tuzatishga arziydi. Rezistorlar, agar proshivka ishlamay qolsa va pinni bittaga o'rnatsa, portni yoqib yubormaslik uchun kerak bo'ladi va qamish kaliti bu chiziqni erga qisqa tutadi (rezistor bilan maksimal 3.3V/1000Ohm = 3.3mA oqadi).

Elektr o'chib qolsa, nima qilish kerakligi haqida o'ylash vaqti keldi. Birinchi variant - boshida serverdan dastlabki hisoblagich qiymatlarini so'rash. Ammo bu almashinuv protokolini sezilarli darajada murakkablashtirishni talab qiladi. Bundan tashqari, bu holda qurilmaning ishlashi server holatiga bog'liq. Agar server quvvat o'chirilgandan so'ng (yoki keyinroq ishga tushirilgandan keyin) ishga tushmasa, suv hisoblagichi dastlabki qiymatlarni talab qila olmaydi va to'g'ri ishlamaydi.

Shuning uchun men I2C orqali ulangan xotira chipida hisoblagich qiymatlarini saqlashni amalga oshirishga qaror qildim. Menda flesh xotira hajmiga hech qanday maxsus talablar yo'q - siz faqat 2 raqamni saqlashingiz kerak (issiq va sovuq suv hisoblagichlariga ko'ra litrlar soni). Hatto eng kichik modul ham buni amalga oshiradi. Lekin siz ro'yxatga olish davrlari soniga e'tibor berishingiz kerak. Ko'pgina modullar uchun bu 100 ming tsikl, ba'zilari uchun milliongacha.

Million juda ko'p ko'rinadi. Ammo kvartiramda yashagan 4 yil davomida men 500 kubometrdan bir oz ko'proq suv iste'mol qildim, bu 500 ming litr! Va fleshda 500 ming yozuvlar. Va bu shunchaki sovuq suv. Siz, albatta, har ikki yilda bir marta chipni qayta lehimlashingiz mumkin, ammo FRAM chiplari borligi ma'lum bo'ldi. Dasturlash nuqtai nazaridan, bu bir xil I2C EEPROM, faqat juda ko'p miqdordagi qayta yozish tsikllari (yuzlab millionlar). Men hali ham bunday mikrosxemalar bilan do'konga kira olmayapman, shuning uchun hozircha odatiy 24LC512 turadi.

Bosilgan elektron plata

Dastlab men taxtani uyda yasashni rejalashtirgandim. Shuning uchun, taxta bir tomonlama ishlab chiqilgan. Ammo lazerli dazmol va lehim niqobi bilan bir soat vaqt o'tkazganimdan so'ng (qandaydir bo'lsa-da, u holda bu comme il faut emas), men hali ham xitoylardan taxtalarga buyurtma berishga qaror qildim.

Suv hisoblagichini aqlli uyga ulash

Doskaga buyurtma berishdan deyarli oldin men flesh-xotira chipiga qo'shimcha ravishda I2C avtobusiga displey kabi boshqa foydali narsalarni ulashim mumkinligini angladim. Unga aniq nima chiqarish kerakligi hali ham savol, lekin uni doskada yo'naltirish kerak. Xo'sh, men zavoddan taxtalarga buyurtma bermoqchi bo'lganim uchun, o'zimni bir tomonlama taxta bilan cheklashning ma'nosi yo'q edi, shuning uchun I2C liniyalari taxtaning orqa tomonidagi yagona narsadir.

Bir tomonlama simlarni ulashda ham bitta katta muammo bor edi. Chunki Kengash bir tomonlama chizilgan, shuning uchun treklar va SMD komponentlarini bir tomondan, chiqish komponentlarini, ulagichlarni va quvvat manbaini boshqa tomondan joylashtirish rejalashtirilgan edi. Bir oy o'tgach, taxtalarni olganimda, men asl rejani unutib qo'ydim va old tomondan barcha komponentlarni lehimladim. Va faqat elektr ta'minotini lehimlash haqida gap ketganda, ortiqcha va minus teskari simli ekanligi ma'lum bo'ldi. Men jumpers bilan dehqonchilik qilishim kerak edi. Yuqoridagi rasmda men allaqachon simlarni o'zgartirdim, lekin er yuklash tugmasi pinlari orqali taxtaning bir qismidan ikkinchisiga o'tkaziladi (garchi ikkinchi qatlamda trekni chizish mumkin bo'lsa ham).

Bu shunday chiqdi

Suv hisoblagichini aqlli uyga ulash

Qobiq

Keyingi qadam tanadir. Agar sizda 3D printer bo'lsa, bu muammo emas. Men juda ko'p bezovta qilmadim - men shunchaki kerakli o'lchamdagi qutini chizdim va kerakli joylarda kesiklar qildim. Qopqoq tanaga kichik o'z-o'zidan tejamkor vintlar bilan biriktirilgan.

Suv hisoblagichini aqlli uyga ulash

Men yuklash tugmasidan umumiy maqsadli tugma sifatida foydalanish mumkinligini aytib o'tdim - shuning uchun biz uni old panelda ko'rsatamiz. Buning uchun men tugma yashaydigan maxsus "quduq" chizdim.

Suv hisoblagichini aqlli uyga ulash

Korpus ichida shuningdek, taxta o'rnatilgan va bitta M3 vint bilan mahkamlangan tirgaklar mavjud (taxtada bo'sh joy yo'q edi)

Men ishning birinchi namunaviy versiyasini chop etganimda displeyni tanlaganman. Standart ikki qatorli o'quvchi bu holatga mos kelmadi, ammo pastki qismida OLED displey SSD1306 128 × 32 edi. Bu biroz kichik, lekin men har kuni unga qarashim shart emas - bu men uchun juda ko'p.

Shu tarzda va undan simlar qanday o'tkazilishini bilib, men displeyni korpusning o'rtasiga yopishtirishga qaror qildim. Ergonomika, albatta, past darajada - tugma tepada, displey pastda. Lekin men allaqachon aytdimki, displeyni biriktirish g'oyasi juda kech kelgan va tugmachani siljitish uchun taxtani qayta ulashga juda dangasa bo'ldim.

Qurilma yig'ilgan. Displey moduli issiq elim bilan snotga yopishtirilgan

Suv hisoblagichini aqlli uyga ulash

Suv hisoblagichini aqlli uyga ulash

Yakuniy natijani KDPV da ko'rish mumkin

Firmware

Keling, dasturiy ta'minot qismiga o'tamiz. Bunday kichik hunarmandchilik uchun men Python dan foydalanishni juda yaxshi ko'raman (mikropiton) - kod juda ixcham va tushunarli bo'lib chiqadi. Yaxshiyamki, mikrosoniyalarni siqib chiqarish uchun registr darajasiga tushishning hojati yo'q - hamma narsani Python-dan qilish mumkin.

Ko'rinishidan, hamma narsa oddiy, ammo unchalik oddiy emas - qurilma bir nechta mustaqil funktsiyalarga ega:

  • Foydalanuvchi tugmani bosadi va displeyga qaraydi
  • Litrlar flesh-xotiradagi qiymatlarni belgilaydi va yangilaydi
  • Modul WiFi signalini nazorat qiladi va kerak bo'lganda qayta ulanadi
  • Xo'sh, miltillovchi lampochkasiz bu mumkin emas

Agar biron bir sababga ko'ra boshqasi tiqilib qolgan bo'lsa, bitta funktsiya ishlamagan deb taxmin qila olmaysiz. Men allaqachon boshqa loyihalarda kaktuslar bilan to‘lganman va hozir ham “displey o‘sha paytda yangilanayotgani uchun yana bir litrni o‘tkazib yubordim” yoki “modul ulanayotganda foydalanuvchi hech narsa qila olmaydi” tarzidagi nosozliklarni ko‘raman. Wi-fi." Albatta, ba'zi narsalarni uzilishlar orqali amalga oshirish mumkin, lekin siz davomiylik, qo'ng'iroqlarni joylashtirish yoki o'zgaruvchilarga atom bo'lmagan o'zgarishlar bo'yicha cheklovlarga duch kelishingiz mumkin. Xo'sh, hamma narsani tezda bajaradigan kod mushga aylanadi.

В jiddiyroq loyiha Men klassik preemptive multitasking va FreeRTOS dan foydalandim, ammo bu holda model ancha mos bo'lib chiqdi. coroutines va uasync kutubxonalari . Bundan tashqari, Python koroutinlarini amalga oshirish juda ajoyib - dasturchi uchun hamma narsa sodda va qulay tarzda amalga oshiriladi. Faqat o'z mantiqingizni yozing, faqat qaysi joylarda oqimlarni almashtirishingiz mumkinligini ayting.

Men ixtiyoriy mavzu sifatida preemptiv va raqobatbardosh ko'p vazifa o'rtasidagi farqlarni o'rganishni taklif qilaman. Endi nihoyat kodga o'tamiz.

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

Har bir hisoblagich Counter sinfining namunasi tomonidan boshqariladi. Avvalo, hisoblagichning dastlabki qiymati EEPROM (value_storage) dan chiqariladi - elektr uzilishidan keyin tiklanish shunday amalga oshiriladi.

PIN quvvat manbaiga o'rnatilgan tortishish bilan ishga tushiriladi: agar qamish kaliti yopiq bo'lsa, chiziq nolga teng, agar chiziq ochiq bo'lsa, u quvvat manbaiga tortiladi va boshqaruvchi bittasini o'qiydi.

Bu erda alohida vazifa ham ishga tushiriladi, u pinni so'raydi. Har bir hisoblagich o'z vazifasini bajaradi. Mana uning kodi

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

Kontaktning sakrashini filtrlash uchun 25 ms kechikish kerak bo'ladi va shu bilan birga u vazifa qanchalik tez-tez uyg'onishini tartibga soladi (bu vazifa uxlayotgan paytda, boshqa vazifalar bajariladi). Har 25 ms funktsiya uyg'onadi, pinni tekshiradi va agar qamish kaliti kontaktlari yopilgan bo'lsa, u holda hisoblagichdan yana bir litr o'tdi va bu qayta ishlanishi kerak.

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

        self._value_storage.write(self._value)

Keyingi litrni qayta ishlash ahamiyatsiz - hisoblagich shunchaki ortadi. Xo'sh, yangi qiymatni flesh-diskga yozish yaxshi bo'lar edi.

Foydalanish qulayligi uchun "aksessorlar" taqdim etiladi

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

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

Keling, Python va uasync kutubxonasining zavqlaridan foydalanamiz va kutiladigan hisoblagich ob'ektini yaratamiz (buni rus tiliga qanday tarjima qilishimiz mumkin? Siz kutgan narsa?)

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

        return self.value()

    __iter__ = __await__  

Bu shunday qulay funksiyaki, hisoblagich qiymati yangilanmaguncha kutadi - funksiya vaqti-vaqti bilan uyg'onadi va _value_changed bayrog'ini tekshiradi. Ushbu funktsiyaning ajoyib tomoni shundaki, qo'ng'iroq kodi ushbu funktsiyani chaqirganda uxlab qolishi va yangi qiymat olinmaguncha uxlashi mumkin.

Uzilishlar haqida nima deyish mumkin?Ha, hozir siz uzilishlar haqida o'zingiz aytganingizni aytib, meni trollashingiz mumkin, lekin aslida siz ahmoqona so'rov o'tkazdingiz. Aslida uzilishlar men sinab ko'rgan birinchi narsadir. ESP8266-da siz chekka uzilishni tashkil qilishingiz va hatto Python-da ushbu uzilish uchun ishlov beruvchini yozishingiz mumkin. Ushbu uzilishda o'zgaruvchining qiymati yangilanishi mumkin. Ehtimol, agar hisoblagich qul qurilma bo'lsa, bu etarli bo'lar edi - bu qiymat so'raguncha kutadigan qurilma.

Afsuski (yoki xayriyatki?) mening qurilmam faol, u o'zi MQTT protokoli orqali xabarlar yuborishi va EEPROMga ma'lumotlarni yozishi kerak. Va bu erda cheklovlar paydo bo'ladi - siz uzilishlarda xotirani ajrata olmaysiz va katta stekdan foydalana olmaysiz, ya'ni siz tarmoq orqali xabar yuborishni unutishingiz mumkin. Micropython.schedule() kabi bulkalar mavjud bo'lib, ular sizga "iloji boricha tezroq" qandaydir funktsiyalarni bajarishga imkon beradi, ammo "nima maqsadda?" Degan savol tug'iladi. Agar biz hozir qandaydir xabar yuborayotgan bo'lsak-chi, keyin uzilish kelib, o'zgaruvchilar qiymatlarini buzsa. Yoki, masalan, biz eskisini yozmaganimizda, serverdan yangi hisoblagich qiymati keldi. Umuman olganda, siz sinxronlashni bloklashingiz yoki undan boshqacha tarzda chiqib ketishingiz kerak.

Va vaqti-vaqti bilan RuntimeError: to'liq stekning ishdan chiqishini rejalashtirish va nima uchun kim biladi?

Aniq so'rov va uasync bilan, bu holda u qandaydir tarzda yanada chiroyli va ishonchli bo'lib chiqadi

Men EEPROM bilan ishlashni kichik sinfga olib keldim

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 baytlar bilan bevosita ishlash qiyin, lekin xotiraga yoziladigan baytlardir. Ustruct kutubxonasidan foydalanib, butun son va baytlar o'rtasidagi konversiyani to'sib qo'yishim kerak edi.

I2C ob'ektini va xotira katakchasi manzilini har safar o'tkazmaslik uchun men hammasini kichik va qulay klassikaga o'rab oldim.

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 ob'ektining o'zi ushbu parametrlar bilan yaratilgan

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

Biz eng qiziqarli qismga keldik - MQTT orqali server bilan aloqani amalga oshirish. Xo'sh, protokolning o'zini amalga oshirishning hojati yo'q - men uni Internetda topdim tayyor asenkron amalga oshirish. Bu biz foydalanadigan narsadir.

Barcha qiziqarli narsalar MQTTClient kutubxonasiga asoslangan CounterMQTTClient sinfida to'plangan. Keling, periferiyadan boshlaylik

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

Bu erda siz lampochkaning pinlari va tugmachalarini, shuningdek, sovuq va issiq suv hisoblagichlarini yaratishingiz va sozlashingiz mumkin.

Boshlash bilan hamma narsa unchalik ahamiyatsiz emas

    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 kutubxonasining ish parametrlarini o'rnatish uchun turli xil sozlamalarning katta lug'ati ishlatiladi - config. Standart sozlamalarning aksariyati biz uchun yaxshi, lekin ko'p sozlamalar aniq o'rnatilishi kerak. Sozlamalarni to'g'ridan-to'g'ri kodga yozmaslik uchun ularni config.txt matn faylida saqlayman. Bu sozlamalardan qat'iy nazar kodni o'zgartirishga, shuningdek, turli parametrlarga ega bir nechta bir xil qurilmalarni perchin qilish imkonini beradi.

Kodning oxirgi bloki tizimning turli funktsiyalarini bajarish uchun bir nechta koroutinlarni ishga tushiradi. Misol uchun, bu erda hisoblagichlarga xizmat ko'rsatadigan koroutin mavjud

    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 yangi hisoblagich qiymatini kutadi va u paydo bo'lishi bilanoq MQTT protokoli orqali xabar yuboradi. Kodning birinchi qismi hisoblagich orqali suv oqmasa ham dastlabki qiymatni yuboradi.

MQTTClient tayanch klassi o'ziga xizmat qiladi, Wi-Fi ulanishini boshlaydi va ulanish yo'qolganda qayta ulanadi. Wi-Fi ulanishi holatida o'zgarishlar yuz berganda, kutubxona wifi_connection_handler ga qo'ng'iroq qilish orqali bizga xabar beradi.

    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)

Funktsiya misollardan halol ko'chiriladi. Bunday holda, u uzilishlar sonini (internet_utages) va ularning davomiyligini hisoblaydi. Ulanish tiklanganda, serverga bo'sh vaqt yuboriladi.

Aytgancha, oxirgi uyqu faqat funktsiyani asinxron qilish uchun kerak bo'ladi - kutubxonada u await orqali chaqiriladi va faqat tanasi boshqa kutishni o'z ichiga olgan funktsiyalarni chaqirish mumkin.

WiFi-ga ulanishdan tashqari, siz MQTT brokeriga (serveriga) ulanishni ham o'rnatishingiz kerak. Kutubxona buni ham qiladi va ulanish o'rnatilganda biz foydali narsalarni qilish imkoniyatiga ega bo'lamiz

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

Bu erda biz bir nechta xabarlarga obuna bo'lamiz - server endi tegishli xabarni yuborish orqali joriy hisoblagich qiymatlarini o'rnatish imkoniyatiga ega.

    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 kiruvchi xabarlarni qayta ishlaydi va mavzuga (xabar sarlavhasiga) qarab hisoblagichlardan birining qiymatlari yangilanadi.

Bir nechta yordamchi funktsiyalar

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

Agar ulanish o'rnatilgan bo'lsa, bu funksiya xabar yuboradi. Agar ulanish bo'lmasa, xabar e'tiborga olinmaydi.

Va bu nosozliklarni tuzatish xabarlarini yaratadigan va yuboradigan qulay funktsiyadir.

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

Juda ko'p matn va biz hali LEDni o'chirmaganmiz. Bu yerga

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

Men 2 ta miltillovchi rejimni taqdim etdim. Agar ulanish uzilgan bo'lsa (yoki u endigina o'rnatilayotgan bo'lsa), qurilma tezda miltillaydi. Agar ulanish o'rnatilgan bo'lsa, qurilma har 5 soniyada bir marta miltillaydi. Agar kerak bo'lsa, bu erda boshqa miltillash rejimlari ham amalga oshirilishi mumkin.

Lekin LED shunchaki erkalaydi. Biz ham namoyishni maqsad qilganmiz.

    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)

Men bu haqda gapirgan edim - bu koroutinlar bilan qanchalik sodda va qulay. Bu kichik funksiya BARCHA foydalanuvchi tajribasini tavsiflaydi. Korutin shunchaki tugma bosilishini kutadi va displeyni 3 soniya davomida yoqadi. Displeyda joriy hisoblagich ko'rsatkichlari ko'rsatiladi.

Hali bir nechta kichik narsalar qoldi. Bu butun korxonani (qayta) ishga tushiradigan funksiya. Asosiy tsikl daqiqada bir marta turli xil disk raskadrovka ma'lumotlarini yuboradi. Umuman olganda, men buni qanday bo'lsa, shunday qilib keltiraman - menimcha, ortiqcha izoh berishning hojati yo'q

   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)

Xo'sh, tavsifni to'ldirish uchun yana bir nechta sozlamalar va doimiylar

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

Hammasi shunday boshlanadi

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

Xotiramda nimadir yuz berdi

Shunday qilib, barcha kodlar mavjud. Men fayllarni ampy yordam dasturidan foydalangan holda yukladim - bu ularni ichki (ESP-07-ning o'zida) flesh-diskga yuklash va keyin dasturdan oddiy fayllar sifatida kirish imkonini beradi. U erda men mqtt_as, uasyncio, ssd1306 va men foydalanadigan to'plamlar kutubxonalarini ham yukladim (mqtt_as ichida ishlatilgan).

Biz uni ishga tushiramiz va ... Bizda MemoryError paydo bo'ladi. Bundan tashqari, xotira aniq qayerda oqayotganini tushunishga qanchalik ko'p harakat qilsam, qanchalik ko'p disk raskadrovka nashrlarini joylashtirsam, bu xato shunchalik erta paydo bo'ldi. Qisqa Google qidiruvi meni mikrokontrollerda atigi 30 kB xotiraga ega ekanligini tushunishga olib keldi, unga 65 kB kod (shu jumladan kutubxonalar) sig'maydi.

Ammo chiqish yo'li bor. Ma'lum bo'lishicha, micropython to'g'ridan-to'g'ri .py faylidan kodni bajarmaydi - bu fayl birinchi bo'lib kompilyatsiya qilinadi. Bundan tashqari, u to'g'ridan-to'g'ri mikrokontrollerda kompilyatsiya qilinadi, bayt-kodga aylanadi, keyinchalik u xotirada saqlanadi. Xo'sh, kompilyator ishlashi uchun sizga ma'lum miqdorda RAM kerak bo'ladi.

Bu hiyla-nayrang mikrokontrollerni resurs talab qiladigan kompilyatsiyadan qutqarishdir. Katta kompyuterda fayllarni kompilyatsiya qilishingiz va tayyor baytekodni mikrokontrollerga yuklashingiz mumkin. Buning uchun siz micropython proshivkasini yuklab olishingiz va qurishingiz kerak mpy-cross yordam dasturi.

Men Makefile faylini yozmadim, lekin shunga o'xshash barcha kerakli fayllarni (shu jumladan kutubxonalarni) qo'lda ko'rib chiqdim va tuzdim.

mpy-cross water_counter.py

Faqat .mpy kengaytmali fayllarni yuklash qoladi, avvalo mos keladigan .py faylini qurilmaning fayl tizimidan o'chirishni unutmang.

Men dasturda (IDE?) ESPlorerda barcha ishlanmalarni qildim. Bu mikrokontrollerga skriptlarni yuklash va ularni darhol bajarish imkonini beradi. Mening holatimda, barcha ob'ektlarning barcha mantiqiy va yaratilishi water_counter.py (.mpy) faylida joylashgan. Lekin bularning barchasi avtomatik ravishda boshlanishi uchun boshida main.py nomli fayl ham bo'lishi kerak. Bundan tashqari, u aniq .py bo'lishi kerak va oldindan tuzilgan .mpy emas. Mana uning ahamiyatsiz mazmuni

import water_counter

Biz uni ishga tushiramiz - hamma narsa ishlaydi. Ammo bo'sh xotira juda kichik - taxminan 1 kb. Menda hali ham qurilmaning funksionalligini kengaytirish rejalari bor va bu kilobayt men uchun etarli emas. Ammo bu holatda ham chiqish yo'li borligi ma'lum bo'ldi.

Gap shundaki. Fayllar bayt-kodga kompilyatsiya qilingan va ichki fayl tizimida joylashgan bo'lsa ham, aslida ular hali ham RAMga yuklanadi va u erdan bajariladi. Ammo ma'lum bo'lishicha, micropython baytekodni to'g'ridan-to'g'ri flesh-xotiradan bajarishi mumkin, ammo buning uchun uni to'g'ridan-to'g'ri proshivkaga qurish kerak. Bu qiyin emas, garchi mening netbukimga ancha vaqt ketgan bo'lsa ham (faqat o'sha erda menda Linux bo'lgan).

Algoritm quyidagicha:

  • Yuklab oling va o'rnating ESP Open SDK. Bu narsa ESP8266 dasturlari uchun kompilyator va kutubxonalarni yig'adi. Loyihaning asosiy sahifasidagi ko'rsatmalarga muvofiq yig'ilgan (men STANDALONE=ha sozlamasini tanladim)
  • Yuklab oling mikropiton turlari
  • Kerakli kutubxonalarni micropython daraxti ichidagi portlar/esp8266/modullarga joylashtiring
  • Biz proshivkani fayldagi ko'rsatmalarga muvofiq yig'amiz ports/esp8266/README.md
  • Biz mikrodasturni mikrokontrollerga yuklaymiz (men buni Windows-da ESP8266Flasher dasturlari yoki Python esptool yordamida qilaman)

Hammasi shunday, endi "import ssd1306" kodni to'g'ridan-to'g'ri proshivkadan olib tashlaydi va buning uchun RAM sarflanmaydi. Ushbu hiyla bilan men faqat kutubxona kodini proshivkaga yukladim, asosiy dastur kodi esa fayl tizimidan bajariladi. Bu sizga proshivkani qayta kompilyatsiya qilmasdan dasturni osongina o'zgartirish imkonini beradi. Ayni paytda menda taxminan 8.5 kb bepul operativ xotira bor. Bu bizga kelajakda juda ko'p turli xil foydali funktsiyalarni amalga oshirish imkonini beradi. Xo'sh, agar xotira umuman bo'lmasa, asosiy dasturni proshivkaga surishingiz mumkin.

Xo'sh, endi bu haqda nima qilishimiz kerak?

OK, apparat lehimlangan, proshivka yozilgan, quti chop etilgan, qurilma devorga yopishtirilgan va yorug'lik chiroqini xursandchilik bilan miltillaydi. Ammo hozircha bularning barchasi qora quti (so'zma-so'z va majoziy ma'noda) va u hali ham kam foyda keltiradi. Serverga yuborilgan MQTT xabarlari bilan nimadir qilish vaqti keldi.

Mening "aqlli uyim" aylanmoqda Majordomo tizimi. MQTT moduli qutidan chiqadi yoki qo'shimcha bozordan osongina o'rnatiladi - uni qayerdan olganimni eslay olmayman. MQTT o'z-o'zidan etarli narsa emas - sizga shunday deb atalmish kerak. broker - MQTT xabarlarini qabul qiluvchi, saralaydigan va mijozlarga yo'naltiruvchi server. Men chivindan foydalanaman, u (majordomo kabi) bir xil netbukda ishlaydi.

Qurilma kamida bir marta xabar yuborgandan so'ng, qiymat darhol ro'yxatda paydo bo'ladi.

Suv hisoblagichini aqlli uyga ulash

Ushbu qiymatlar endi tizim ob'ektlari bilan bog'lanishi mumkin, ular avtomatlashtirish skriptlarida ishlatilishi va turli xil tahlillarga duchor bo'lishi mumkin - bularning barchasi ushbu maqola doirasidan tashqarida. Men qiziqqan har bir kishiga majordomo tizimini tavsiya qilishim mumkin Electronics In Lens kanali - do'stim ham aqlli uy qurmoqda va tizimni o'rnatish haqida aniq gapiradi.

Men sizga bir nechta grafiklarni ko'rsataman. Bu kundalik qiymatlarning oddiy grafigi

Suv hisoblagichini aqlli uyga ulash
Kechasi suvdan deyarli hech kim foydalanmaganini ko'rish mumkin. Bir necha marta kimdir hojatxonaga bordi va teskari osmoz filtri kechasiga bir necha litr so'rayotganga o'xshaydi. Ertalab iste'mol sezilarli darajada oshadi. Men odatda qozondan suv ishlataman, lekin keyin hammomni qabul qilmoqchi bo'ldim va vaqtincha shahar issiq suviga o'tdim - bu pastki grafikda ham aniq ko'rinadi.

Bu grafikdan bilib oldimki, hojatxonaga borish uchun 6-7 litr, dush qabul qilish uchun 20-30 litr, idish-tovoq yuvish uchun 20 litr, vannaga tushish uchun esa 160 litr suv kerak bo‘ladi. Mening oilam kuniga taxminan 500-600 litr iste'mol qiladi.

Ayniqsa qiziqqanlar uchun har bir alohida qiymat uchun yozuvlarni ko'rishingiz mumkin

Suv hisoblagichini aqlli uyga ulash

Bu yerdan men kran ochiq bo'lsa, suv taxminan 1 soniyada 5 litr tezlikda oqayotganini bilib oldim.

Ammo bu shaklda statistikani ko'rish juda qulay emas. Majordomo shuningdek, iste'mol jadvallarini kun, hafta va oy bo'yicha ko'rish imkoniyatiga ega. Bu erda, masalan, barlardagi iste'mol grafigi

Suv hisoblagichini aqlli uyga ulash

Hozircha menda faqat bir haftalik ma'lumotlar bor. Bir oy ichida bu grafik ko'proq indikativ bo'ladi - har bir kun alohida ustunga ega bo'ladi. Rasm men qo'lda kiritgan qiymatlarni o'zgartirish bilan biroz buzilgan (eng katta ustun). Va men birinchi qiymatlarni noto'g'ri o'rnatdimmi, deyarli bir kub kamroqmi yoki bu proshivkadagi xatomi va barcha litrlar hisoblanmaganmi, hali aniq emas. Ko'proq vaqt kerak.

Grafiklarning o'zi hali ham sehr, oqlash, bo'yashga muhtoj. Ehtimol, men disk raskadrovka maqsadida xotira iste'moli grafigini ham tuzaman - agar u erda biror narsa oqayotgan bo'lsa. Ehtimol, men qandaydir tarzda Internet bo'lmagan davrlarni ko'rsataman. Hozircha bularning barchasi g'oyalar darajasida.

xulosa

Bugun mening kvartiram biroz aqlli bo'ldi. Bunday kichik qurilma bilan uyda suv sarfini kuzatish men uchun qulayroq bo'ladi. Agar ilgari men "yana bir oyda ko'p suv iste'mol qildik" deb g'azablangan bo'lsam, endi bu iste'molning manbasini topishim mumkin.

Ba'zilar, agar u hisoblagichning o'zidan bir metr uzoqlikda bo'lsa, ekrandagi ko'rsatkichlarga qarash g'alati tuyulishi mumkin. Ammo yaqin kelajakda men boshqa kvartiraga ko'chib o'tishni rejalashtirmoqdaman, u erda bir nechta suv ko'targichlari bo'ladi va hisoblagichlarning o'zlari, ehtimol, maydonchada joylashgan bo'ladi. Shunday qilib, masofadan o'qish qurilmasi juda foydali bo'ladi.

Shuningdek, men qurilmaning funksionalligini kengaytirishni rejalashtirmoqdaman. Men allaqachon motorli klapanlarni ko'rib chiqyapman. Endi qozonni shahar suviga o'tkazish uchun men erishish qiyin bo'lgan joyda 3 ta kranni burishim kerak. Buni tegishli ko'rsatkichga ega bitta tugma bilan qilish ancha qulayroq bo'ladi. Albatta, qochqinlardan himoya qilishni amalga oshirishga arziydi.

Maqolada men ESP8266 asosidagi qurilmamning versiyasini tasvirlab berdim. Menimcha, men micropython proshivkasining juda qiziqarli versiyasini coroutines yordamida o'ylab topdim - oddiy va chiroyli. Kampaniya davomida uchratgan ko‘plab nozikliklar va kamchiliklarni tasvirlashga harakat qildim. Ehtimol, men hamma narsani juda batafsil tasvirlab bergandirman; shaxsan, o'quvchi sifatida, keyinroq aytilmagan narsalarni o'ylab ko'rishdan ko'ra, keraksiz narsalarni o'tkazib yuborish osonroq.

Har doimgidek, men konstruktiv tanqidga ochiqman.

Manba kodi
O'chirish va plata
Case modeli

Manba: www.habr.com