Kuunganisha mita ya maji kwa nyumba nzuri

Hapo zamani za kale, mifumo ya kiotomatiki ya nyumbani, au "smart home" kama ilivyokuwa ikiitwa mara nyingi, ilikuwa ghali sana na ni matajiri tu ndio wangeweza kumudu. Leo kwenye soko unaweza kupata vifaa vya bei nafuu na sensorer, vifungo / swichi na watendaji wa kudhibiti taa, soketi, uingizaji hewa, usambazaji wa maji na watumiaji wengine. Na hata mtu aliyepotoka zaidi wa DIY anaweza kujihusisha na urembo na kukusanya vifaa vya nyumba nzuri kwa bei ya bei rahisi.

Kuunganisha mita ya maji kwa nyumba nzuri

Kwa kawaida, vifaa vilivyopendekezwa ni sensorer au actuators. Hurahisisha kutekeleza matukio kama vile "kitambuzi cha mwendo kinapowashwa, washa taa" au "swichi iliyo karibu na njia ya kutoka huzima taa katika ghorofa nzima." Lakini kwa namna fulani mambo hayakufanya kazi na telemetry. Bora zaidi, ni grafu ya halijoto na unyevunyevu, au nguvu ya papo hapo kwenye kituo mahususi.

Hivi majuzi niliweka mita za maji na pato la kunde. Kwa kila lita inayopitia mita, kubadili mwanzi kunawashwa na kufunga mawasiliano. Kitu pekee kilichobaki kufanya ni kushikamana na waya na kujaribu kupata faida kutoka kwake. Kwa mfano, chambua matumizi ya maji kwa saa na siku ya wiki. Naam, ikiwa kuna maji kadhaa ya maji katika ghorofa, basi ni rahisi zaidi kuona viashiria vyote vya sasa kwenye skrini moja kuliko kupanda kwenye niches ngumu kufikia na tochi.

Chini ya kukata kuna toleo langu la kifaa kulingana na ESP8266, ambayo huhesabu mipigo kutoka kwa mita za maji na kutuma usomaji kupitia MQTT kwa seva ya nyumbani smart. Tutapanga kwa micropython kwa kutumia maktaba ya uasyncio. Wakati wa kuunda firmware, nilikutana na matatizo kadhaa ya kuvutia, ambayo nitazungumzia pia katika makala hii. Nenda!

Mpango

Kuunganisha mita ya maji kwa nyumba nzuri

Moyo wa mzunguko mzima ni moduli kwenye microcontroller ya ESP8266. ESP-12 ilipangwa awali, lakini yangu iligeuka kuwa na kasoro. Ilitubidi kuridhika na moduli ya ESP-07, ambayo ilikuwa inapatikana. Kwa bahati nzuri, wao ni sawa kwa suala la pini na utendaji, tofauti pekee ni katika antenna - ESP-12 ina moja iliyojengwa, wakati ESP-07 ina moja ya nje. Walakini, hata bila antenna ya WiFi, ishara katika bafuni yangu inapokelewa kawaida.

Wiring moduli ya kawaida:

  • kitufe cha kuweka upya na kuvuta-up na capacitor (ingawa zote mbili tayari ziko ndani ya moduli)
  • Mawimbi ya kuwezesha (CH_PD) hutolewa kwa nguvu
  • GPIO15 inavutwa chini. Hii inahitajika tu mwanzoni, lakini bado sina chochote cha kushikamana na mguu huu; sihitaji tena

Ili kuweka moduli katika hali ya firmware, unahitaji kuzunguka kwa muda mfupi GPIO2 chini, na ili iwe rahisi zaidi, nilitoa kifungo cha Boot. Katika hali ya kawaida, pini hii hutolewa kwa nguvu.

Hali ya mstari wa GPIO2 inachunguzwa tu mwanzoni mwa operesheni - wakati nguvu inatumiwa au mara baada ya kuweka upya. Kwa hivyo moduli ama buti kama kawaida au huenda katika hali ya firmware. Baada ya kupakiwa, pini hii inaweza kutumika kama GPIO ya kawaida. Kweli, kwa kuwa tayari kuna kitufe hapo, unaweza kuambatisha kazi muhimu kwake.

Kwa programu na utatuzi nitatumia UART, ambayo ni pato kwa kuchana. Inapohitajika, mimi huunganisha tu adapta ya USB-UART hapo. Unahitaji tu kukumbuka kuwa moduli inaendeshwa na 3.3V. Ukisahau kubadili adapta kwa voltage hii na ugavi 5V, moduli itakuwa na uwezekano mkubwa wa kuchoma.

Sina shida na umeme katika bafuni - plagi iko karibu mita kutoka mita, kwa hivyo nitawezeshwa na 220V. Kama chanzo cha nguvu nitakuwa na ndogo zuia HLK-PM03 na Tenstar Robot. Kwa kibinafsi, nina wakati mgumu na analog na umeme wa umeme, lakini hapa kuna usambazaji wa umeme ulio tayari katika kesi ndogo.

Ili kuashiria hali za uendeshaji, nilitoa LED iliyounganishwa kwenye GPIO2. Walakini, sikuifungua, kwa sababu ... Moduli ya ESP-07 tayari ina LED, na pia imeunganishwa na GPIO2. Lakini iwe kwenye ubao, ikiwa ninataka kutoa LED hii kwa kesi.

Wacha tuendelee kwenye sehemu ya kuvutia zaidi. Mita za maji hazina mantiki; huwezi kuwauliza kwa usomaji wa sasa. Kitu pekee kinachopatikana kwetu ni msukumo - kufunga mawasiliano ya kubadili mwanzi kila lita. Matokeo yangu ya kubadili mwanzi yameunganishwa kwa GPIO12/GPIO13. Nitawezesha kontakt ya kuvuta-up kwa utaratibu ndani ya moduli.

Hapo awali, nilisahau kutoa resistors R8 na R9 na toleo langu la bodi halina. Lakini kwa kuwa tayari ninachapisha mchoro kwa kila mtu kuona, inafaa kurekebisha uangalizi huu. Resistors zinahitajika ili si kuchoma bandari ikiwa firmware glitches na kuweka siri kwa moja, na mwanzi kubadili shorts mstari huu chini (pamoja na resistor upeo wa 3.3V/1000Ohm = 3.3mA itapita).

Ni wakati wa kufikiria nini cha kufanya ikiwa umeme utazimwa. Chaguo la kwanza ni kuomba maadili ya awali ya kaunta kutoka kwa seva mwanzoni. Lakini hii itahitaji shida kubwa ya itifaki ya kubadilishana. Aidha, utendaji wa kifaa katika kesi hii inategemea hali ya seva. Ikiwa seva haikuanza baada ya nguvu kuzimwa (au kuanza baadaye), mita ya maji haitaweza kuomba maadili ya awali na haitafanya kazi kwa usahihi.

Kwa hivyo, niliamua kutekeleza kuokoa maadili kwenye chip ya kumbukumbu iliyounganishwa kupitia I2C. Sina mahitaji maalum ya saizi ya kumbukumbu ya flash - unahitaji tu kuokoa nambari 2 (idadi ya lita kulingana na mita za maji moto na baridi). Hata moduli ndogo kabisa itafanya. Lakini unahitaji kulipa kipaumbele kwa idadi ya mizunguko ya kurekodi. Kwa moduli nyingi hii ni mizunguko elfu 100, kwa zingine hadi milioni.

Inaweza kuonekana kuwa milioni ni nyingi. Lakini wakati wa miaka 4 ya kuishi katika nyumba yangu, nilitumia zaidi ya mita za ujazo 500 za maji, hiyo ni lita elfu 500! Na rekodi elfu 500 kwenye flash. Na hayo ni maji baridi tu. Unaweza, kwa kweli, kuuza tena chip kila baada ya miaka kadhaa, lakini zinageuka kuwa kuna chips za FRAM. Kutoka kwa mtazamo wa programu, hii ni I2C EEPROM sawa, tu na idadi kubwa sana ya mizunguko ya kuandika upya (mamia ya mamilioni). Ni kwamba bado siwezi kufika kwenye duka na microcircuits vile, hivyo kwa sasa 24LC512 ya kawaida itasimama.

Bodi ya mzunguko iliyochapishwa

Hapo awali, nilipanga kutengeneza bodi nyumbani. Kwa hivyo, bodi iliundwa kama ya upande mmoja. Lakini baada ya kutumia saa moja na chuma cha laser na mask ya solder (ni kwa namna fulani si comme il faut bila hiyo), bado niliamua kuagiza bodi kutoka kwa Kichina.

Kuunganisha mita ya maji kwa nyumba nzuri

Karibu kabla ya kuagiza ubao, niligundua kuwa pamoja na chip ya kumbukumbu ya flash, naweza kuunganisha kitu kingine muhimu kwa basi ya I2C, kama vile onyesho. Nini hasa kwa pato kwake bado ni swali, lakini inahitaji kupitishwa kwenye ubao. Kweli, kwa kuwa ningeagiza bodi kutoka kwa kiwanda, hakukuwa na maana ya kujizuia kwa ubao wa upande mmoja, kwa hivyo mistari ya I2C ndiyo pekee iliyo upande wa nyuma wa ubao.

Pia kulikuwa na shida moja kubwa na wiring ya njia moja. Kwa sababu Bodi ilitolewa kwa upande mmoja, hivyo nyimbo na vipengele vya SMD vilipangwa kuwekwa upande mmoja, na vipengele vya pato, viunganishi na ugavi wa umeme kwa upande mwingine. Nilipopokea bodi mwezi mmoja baadaye, nilisahau kuhusu mpango wa awali na kuuza vipengele vyote upande wa mbele. Na tu linapokuja suala la kuuza umeme iligeuka kuwa plus na minus walikuwa wired kinyume. Ilinibidi kulima na warukaji. Katika picha hapo juu, tayari nimebadilisha wiring, lakini ardhi inahamishwa kutoka sehemu moja ya bodi hadi nyingine kupitia pini za kifungo cha Boot (ingawa itawezekana kuteka wimbo kwenye safu ya pili).

Ikawa hivi

Kuunganisha mita ya maji kwa nyumba nzuri

Nyumba

Hatua inayofuata ni mwili. Ikiwa una printer ya 3D, hii sio tatizo. Sikujisumbua sana - nilichora tu kisanduku cha saizi inayofaa na nikakata sehemu zinazofaa. Kifuniko kinaunganishwa na mwili na screws ndogo za kujipiga.

Kuunganisha mita ya maji kwa nyumba nzuri

Tayari nilitaja kuwa kitufe cha Boot kinaweza kutumika kama kitufe cha kusudi la jumla - kwa hivyo tutaionyesha kwenye paneli ya mbele. Ili kufanya hivyo, nilichota "vizuri" maalum ambapo kifungo kinaishi.

Kuunganisha mita ya maji kwa nyumba nzuri

Ndani ya kesi hiyo pia kuna karatasi ambazo bodi imewekwa na kulindwa na screw moja ya M3 (hakukuwa na nafasi zaidi kwenye ubao)

Nilichagua onyesho tayari wakati nilichapisha toleo la sampuli la kwanza la kesi hiyo. Msomaji wa kawaida wa mstari mbili haukufaa katika kesi hii, lakini chini kulikuwa na onyesho la OLED SSD1306 128Γ—32. Ni kidogo kidogo, lakini sihitaji kuitazama kila sikuβ€”ni nyingi sana kwangu.

Kufikiria kwa njia hii na kwamba jinsi waya zingepitishwa kutoka kwake, niliamua kubandika onyesho katikati ya kesi hiyo. Ergonomics, kwa kweli, iko chini ya kiwango - kitufe kiko juu, onyesho liko chini. Lakini tayari nilisema kwamba wazo la kuambatisha onyesho lilikuja kuchelewa sana na nilikuwa mvivu sana kuweka tena ubao ili kusonga kitufe.

Kifaa kinakusanyika. Moduli ya kuonyesha imefungwa kwa snot na gundi ya moto

Kuunganisha mita ya maji kwa nyumba nzuri

Kuunganisha mita ya maji kwa nyumba nzuri

Matokeo ya mwisho yanaweza kuonekana kwenye KDPV

Firmware

Wacha tuendelee kwenye sehemu ya programu. Kwa ufundi mdogo kama huu, napenda sana kutumia Python (micropython) - msimbo unageuka kuwa compact sana na inaeleweka. Kwa bahati nzuri, hakuna haja ya kwenda chini kwa kiwango cha rejista ili kufinya microseconds - kila kitu kinaweza kufanywa kutoka kwa Python.

Inaonekana kwamba kila kitu ni rahisi, lakini si rahisi sana - kifaa kina kazi kadhaa za kujitegemea:

  • Mtumiaji anabofya kitufe na kutazama onyesho
  • Lita huweka alama na kusasisha maadili katika kumbukumbu ya flash
  • Moduli hufuatilia mawimbi ya WiFi na kuunganisha tena ikiwa ni lazima
  • Naam, bila balbu ya mwanga inayowaka haiwezekani

Hauwezi kudhani kuwa chaguo la kukokotoa halikufanya kazi ikiwa nyingine imekwama kwa sababu fulani. Tayari nimeshajaza cacti katika miradi mingine na sasa bado naona hitilafu katika mtindo wa "kukosa lita nyingine kwa sababu onyesho lilikuwa likisasishwa wakati huo" au "mtumiaji hawezi kufanya chochote wakati moduli inaunganishwa kwa WiFi." Bila shaka, baadhi ya mambo yanaweza kufanywa kwa kukatizwa, lakini unaweza kukutana na vikwazo kwa muda, kuweka kiota cha simu, au mabadiliko yasiyo ya atomiki kwa vigezo. Kweli, nambari ambayo hufanya kila kitu haraka hubadilika kuwa mush.

Π’ mradi mkubwa zaidi Nilitumia multitasking ya mapema na FreeRTOS, lakini katika kesi hii mfano uligeuka kuwa unaofaa zaidi. coroutines na maktaba za uasync . Kwa kuongezea, utekelezaji wa Python wa coroutines ni wa kushangaza tu - kila kitu kinafanywa kwa urahisi na kwa urahisi kwa programu. Andika tu mantiki yako mwenyewe, niambie tu ni katika maeneo gani unaweza kubadilisha kati ya mitiririko.

Ninapendekeza kusoma tofauti kati ya shughuli nyingi za mapema na za ushindani kama somo la hiari. Sasa hebu hatimaye tuendelee kwenye msimbo.

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

Kila kaunta inashughulikiwa na mfano wa darasa la Counter. Awali ya yote, thamani ya awali ya kukabiliana imetolewa kutoka kwa EEPROM (value_storage) - hii ndio jinsi urejeshaji baada ya kushindwa kwa nguvu kutekelezwa.

Pini imeanzishwa na kuvuta-kujengwa ndani ya ugavi wa umeme: ikiwa kubadili mwanzi imefungwa, mstari ni sifuri, ikiwa mstari umefunguliwa, hutolewa hadi kwenye usambazaji wa umeme na mtawala anasoma moja.

Kazi tofauti pia imezinduliwa hapa, ambayo itachagua pini. Kila kaunta itaendesha kazi yake. Hii hapa kanuni yake

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

Ucheleweshaji wa 25ms unahitajika ili kuchuja bounce ya mawasiliano, na wakati huo huo inasimamia mara ngapi kazi inaamka (wakati kazi hii imelala, kazi nyingine zinafanyika). Kila 25ms kazi huamka, huangalia pini na ikiwa mawasiliano ya kubadili mwanzi imefungwa, basi lita nyingine imepita kupitia mita na hii inahitaji kusindika.

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

        self._value_storage.write(self._value)

Usindikaji wa lita inayofuata ni ndogo - counter inaongezeka tu. Naam, itakuwa nzuri kuandika thamani mpya kwenye gari la flash.

Kwa urahisi wa matumizi, "accessors" hutolewa

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

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

Naam, sasa hebu tunufaike na furaha ya Python na maktaba ya uasync na tutengeneze kipengee cha kaunta kinachoweza kusubiri (tunawezaje kutafsiri hii katika Kirusi? Kile unachoweza kutarajia?)

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

        return self.value()

    __iter__ = __await__  

Hiki ni chaguo rahisi cha kukokotoa ambacho hungoja hadi thamani ya kaunta isasishwe - kitendakazi huamka mara kwa mara na kuangalia _value_changed bendera. Jambo la kupendeza kuhusu chaguo hili la kukokotoa ni kwamba msimbo wa kupiga simu unaweza kusinzia unapoita chaguo hili la kukokotoa na kulala hadi thamani mpya ipokewe.

Vipi kuhusu kukatizwa?Ndio, kwa wakati huu unaweza kunikanyaga, ukisema kwamba wewe mwenyewe ulisema juu ya usumbufu, lakini kwa kweli ulifanya kura ya siri ya kijinga. Kwa kweli kukatiza ndio jambo la kwanza nilijaribu. Katika ESP8266, unaweza kupanga usumbufu wa makali, na hata kuandika kidhibiti cha usumbufu huu kwenye Python. Katika usumbufu huu, thamani ya kutofautisha inaweza kusasishwa. Pengine, hii itakuwa ya kutosha ikiwa counter ingekuwa kifaa cha watumwa - moja ambayo inasubiri hadi iombewe kwa thamani hii.

Kwa bahati mbaya (au kwa bahati nzuri?) kifaa changu kinatumika, lazima chenyewe kitume ujumbe kupitia itifaki ya MQTT na kuandika data kwa EEPROM. Na hapa vizuizi vinakuja - huwezi kutenga kumbukumbu kwa usumbufu na kutumia safu kubwa, ambayo inamaanisha unaweza kusahau kutuma ujumbe kwenye mtandao. Kuna vifungashio kama vile micropython.schedule() vinavyokuruhusu kuendesha baadhi ya chaguo za kukokotoa "haraka iwezekanavyo," lakini swali hutokea, "ni nini maana?" Itakuwaje ikiwa tunatuma aina fulani ya ujumbe hivi sasa, kisha usumbufu unakuja na kuharibu maadili ya vigeu. Au, kwa mfano, thamani mpya ya kaunta ilifika kutoka kwa seva huku tukiwa bado hatujaandika ile ya zamani. Kwa ujumla, unahitaji kuzuia maingiliano au uondoke kwa namna fulani tofauti.

Na mara kwa mara RuntimeError: ratibisha rafu ya kuacha kufanya kazi na nani anajua ni kwa nini?

Kwa upigaji kura wa wazi na uasync, katika kesi hii kwa njia fulani inageuka kuwa nzuri zaidi na ya kuaminika

Nilileta kazi na EEPROM kwa darasa dogo

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)

Katika Python, ni vigumu kufanya kazi moja kwa moja na ka, lakini ni byte ambazo zimeandikwa kwa kumbukumbu. Ilinibidi kuweka uzio kati ya nambari kamili na ka kwa kutumia maktaba ya ustruct.

Ili sio kuhamisha kitu cha I2C na anwani ya seli ya kumbukumbu kila wakati, niliifunga yote kwa muundo mdogo na rahisi.

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)

Kitu cha I2C yenyewe kinaundwa na vigezo hivi

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

Tunakuja sehemu ya kuvutia zaidi - utekelezaji wa mawasiliano na seva kupitia MQTT. Kweli, hakuna haja ya kutekeleza itifaki yenyewe - niliipata kwenye mtandao tayari-alifanya asynchronous utekelezaji. Hii ndio tutatumia.

Mambo yote ya kuvutia zaidi yanakusanywa katika darasa la CounterMQTTClient, ambalo linategemea MQTTClient ya maktaba. Wacha tuanze kutoka pembezoni

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

Hapa unaweza kuunda na kusanidi pini za balbu na vifungo, pamoja na vitu vya mita za baridi na za moto.

Kwa uanzishaji, sio kila kitu ni kidogo sana

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

Ili kuweka vigezo vya uendeshaji wa maktaba ya mqtt_as, kamusi kubwa ya mipangilio tofauti hutumiwa - config. Mipangilio mingi ya chaguo-msingi ni sawa kwetu, lakini mipangilio mingi inahitaji kuwekwa kwa uwazi. Ili nisiandike mipangilio moja kwa moja kwenye msimbo, ninaihifadhi kwenye faili ya maandishi config.txt. Hii inakuwezesha kubadilisha msimbo bila kujali mipangilio, na pia rivet vifaa kadhaa vinavyofanana na vigezo tofauti.

Kizuizi cha mwisho cha msimbo huanza coroutines kadhaa kutumikia kazi mbalimbali za mfumo. Kwa mfano, hapa kuna utaratibu ambao huduma zinapingana

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

Coroutine inasubiri kitanzi kwa thamani mpya ya kukabiliana na, mara tu inapoonekana, hutuma ujumbe kupitia itifaki ya MQTT. Sehemu ya kwanza ya msimbo hutuma thamani ya awali hata kama hakuna maji yanayotiririka kupitia kaunta.

Darasa la msingi la MQTTClient hujihudumia, huanzisha muunganisho wa WiFi na huunganisha tena muunganisho unapopotea. Wakati kuna mabadiliko katika hali ya muunganisho wa WiFi, maktaba hutujulisha kwa kupiga simu 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)

Kazi imenakiliwa kwa uaminifu kutoka kwa mifano. Katika kesi hii, inahesabu idadi ya kukatika (internet_outages) na muda wao. Wakati muunganisho umerejeshwa, wakati wa kutofanya kazi hutumwa kwa seva.

Kwa njia, usingizi wa mwisho unahitajika tu kufanya kazi ya asynchronous - katika maktaba inaitwa kupitia kusubiri, na kazi tu ambazo mwili wake una kusubiri mwingine unaweza kuitwa.

Mbali na kuunganisha kwenye WiFi, unahitaji pia kuanzisha uhusiano na wakala wa MQTT (seva). Maktaba hufanya hivi pia, na tunapata fursa ya kufanya kitu muhimu wakati muunganisho umeanzishwa

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

Hapa tunajiandikisha kwa ujumbe kadhaa - seva sasa ina uwezo wa kuweka viwango vya sasa vya kupinga kwa kutuma ujumbe unaofanana.

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

Kazi hii huchakata ujumbe unaoingia, na kulingana na mada (kichwa cha ujumbe), maadili ya moja ya vihesabu husasishwa.

Kazi kadhaa za msaidizi

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

Kitendaji hiki hutuma ujumbe ikiwa unganisho umeanzishwa. Ikiwa hakuna muunganisho, ujumbe hupuuzwa.

Na hii ni kazi rahisi ambayo hutoa na kutuma ujumbe wa utatuzi.

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

Maandishi mengi, na bado hatujapepesa LED. Hapa

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

Nimetoa njia 2 za kupepesa. Ikiwa muunganisho umepotea (au unaanzishwa tu), kifaa kitaangaza haraka. Ikiwa muunganisho umeanzishwa, kifaa humeta mara moja kila sekunde 5. Ikihitajika, njia zingine za kupepesa zinaweza kutekelezwa hapa.

Lakini LED ni pampering tu. Pia tulilenga onyesho.

    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)

Hivi ndivyo nilivyokuwa nikizungumza - jinsi ilivyo rahisi na rahisi kwa coroutines. Chaguo hili ndogo la kukokotoa linaelezea hali NZIMA ya mtumiaji. Coroutine inasubiri tu kitufe kubonyezwa na kuwasha onyesho kwa sekunde 3. Onyesho linaonyesha usomaji wa mita wa sasa.

Bado kuna vitu vichache vilivyobaki. Hapa kuna kazi ambayo (re) inaanzisha biashara hii yote. Kitanzi kikuu hutuma tu taarifa mbalimbali za utatuzi mara moja kwa dakika. Kwa ujumla, ninainukuu jinsi ilivyo - sidhani kama kuna haja ya kutoa maoni mengi sana

   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)

Kweli, mipangilio michache zaidi na vidhibiti kukamilisha maelezo

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

Yote huanza hivi

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

Kitu kilitokea kwenye kumbukumbu yangu

Kwa hivyo, kanuni zote zipo. Nilipakia faili kwa kutumia matumizi ya ampy - hukuruhusu kuzipakia kwa ndani (ile iliyo kwenye ESP-07 yenyewe) flash drive na kisha kuipata kutoka kwa programu kama faili za kawaida. Huko pia nilipakia mqtt_as, uasyncio, ssd1306 na maktaba za makusanyo nilizotumia (zinazotumika ndani ya mqtt_as).

Tunazindua na... Tunapata MemoryError. Kwa kuongezea, kadiri nilivyojaribu kuelewa ni wapi kumbukumbu ilikuwa ikivuja, kadiri nilivyoweka uchapishaji wa debug, mapema kosa hili lilionekana. Utafutaji mfupi wa Google uliniongoza kuelewa kuwa kidhibiti kidogo kina, kwa kanuni, kB 30 tu ya kumbukumbu, ambayo 65 kB ya msimbo (pamoja na maktaba) haiwezi kutoshea.

Lakini kuna njia ya kutoka. Inabadilika kuwa micropython haitekelezi msimbo moja kwa moja kutoka kwa faili ya .py - faili hii inatungwa kwanza. Kwa kuongeza, imeundwa moja kwa moja kwenye microcontroller, ikageuka kuwa bytecode, ambayo huhifadhiwa kwenye kumbukumbu. Kweli, ili mkusanyaji afanye kazi, unahitaji pia kiasi fulani cha RAM.

Ujanja ni kuokoa microcontroller kutoka kwa mkusanyiko mkubwa wa rasilimali. Unaweza kukusanya faili kwenye kompyuta kubwa na kupakia bytecode iliyopangwa tayari kwenye microcontroller. Ili kufanya hivyo, unahitaji kupakua firmware ya micropython na kujenga matumizi ya mpy-cross.

Sikuandika Makefile, lakini nilipitia na kukusanya faili zote muhimu (pamoja na maktaba) kitu kama hiki.

mpy-cross water_counter.py

Kilichosalia ni kupakia faili ukitumia kiendelezi cha .mpy, bila kusahau kufuta kwanza .py inayolingana kutoka kwa mfumo wa faili wa kifaa.

Nilifanya maendeleo yote katika programu (IDE?) ESPlorer. Inakuruhusu kupakia hati kwa kidhibiti kidogo na utekeleze mara moja. Katika kesi yangu, mantiki yote na uundaji wa vitu vyote iko kwenye faili ya water_counter.py (.mpy). Lakini ili haya yote yaanze kiotomatiki, lazima pia kuwe na faili inayoitwa main.py mwanzoni. Zaidi ya hayo, inapaswa kuwa .py haswa, na sio kukusanywa mapema .mpy. Hapa kuna yaliyomo ndani yake

import water_counter

Tunazindua - kila kitu kinafanya kazi. Lakini kumbukumbu ya bure ni ndogo sana - karibu 1kb. Bado nina mipango ya kupanua utendaji wa kifaa, na kilobyte hii haitoshi kwangu. Lakini ikawa kwamba kuna njia ya kutoka kwa kesi hii pia.

Hili hapa jambo. Ingawa faili zimekusanywa kwa bytecode na hukaa kwenye mfumo wa faili wa ndani, kwa kweli bado hupakiwa kwenye RAM na kutekelezwa kutoka hapo. Lakini zinageuka kuwa micropython inaweza kutekeleza bytecode moja kwa moja kutoka kwa kumbukumbu ya flash, lakini kwa hili unahitaji kuijenga moja kwa moja kwenye firmware. Sio ngumu, ingawa ilichukua muda mrefu kwenye netbook yangu (hapo tu nilipata kuwa na Linux).

Algorithm ni kama ifuatavyo:

  • Pakua na usakinishe ESP Fungua SDK. Jambo hili linakusanya mkusanyaji na maktaba za programu za ESP8266. Imekusanywa kulingana na maagizo kwenye ukurasa kuu wa mradi (nilichagua STANDALONE=ndio mpangilio)
  • Shusha aina za micropython
  • Weka maktaba zinazohitajika katika bandari/esp8266/moduli ndani ya mti wa mikropython
  • Tunakusanya firmware kulingana na maagizo kwenye faili bandari/esp8266/README.md
  • Tunapakia firmware kwa microcontroller (mimi hufanya hivi kwenye Windows kwa kutumia programu za ESP8266Flasher au Python esptool)

Hiyo ndiyo yote, sasa 'kuagiza ssd1306' itainua msimbo moja kwa moja kutoka kwa firmware na RAM haitatumiwa kwa hili. Kwa hila hii, nilipakia nambari ya maktaba tu kwenye firmware, wakati nambari kuu ya programu inatekelezwa kutoka kwa mfumo wa faili. Hii inakuwezesha kurekebisha programu kwa urahisi bila kurejesha firmware. Kwa sasa nina takriban 8.5kb ya RAM ya bure. Hii itaturuhusu kutekeleza utendakazi mwingi muhimu katika siku zijazo. Naam, ikiwa hakuna kumbukumbu ya kutosha kabisa, basi unaweza kushinikiza programu kuu kwenye firmware.

Kwa hiyo tufanye nini kuhusu hilo sasa?

Sawa, vifaa vinauzwa, firmware imeandikwa, sanduku limechapishwa, kifaa kimefungwa kwenye ukuta na kwa furaha huangaza balbu ya mwanga. Lakini kwa sasa yote ni sanduku nyeusi (literally na kwa mfano) na bado ni ya matumizi kidogo. Ni wakati wa kufanya jambo na ujumbe wa MQTT unaotumwa kwa seva.

"Smart home" yangu inazunguka Mfumo wa Majordomo. Moduli ya MQTT inatoka kwenye kisanduku, au inasakinishwa kwa urahisi kutoka kwenye soko la programu jalizi - sikumbuki niliipata kutoka wapi. MQTT sio kitu cha kujitegemea - unahitaji kinachojulikana. wakala - seva inayopokea, kupanga na kusambaza ujumbe wa MQTT kwa wateja. Ninatumia mbu, ambayo (kama majordomo) inaendeshwa kwenye netbook moja.

Baada ya kifaa kutuma ujumbe angalau mara moja, thamani itaonekana mara moja kwenye orodha.

Kuunganisha mita ya maji kwa nyumba nzuri

Maadili haya sasa yanaweza kuhusishwa na vitu vya mfumo, yanaweza kutumika katika hati za kiotomatiki na kufanyiwa uchambuzi mbalimbali - yote ambayo ni zaidi ya upeo wa makala hii. Ninaweza kupendekeza mfumo mkuu kwa yeyote anayevutiwa chaneli ya Elektroniki Katika Lenzi - rafiki pia anajenga nyumba nzuri na anazungumza waziwazi kuhusu kuanzisha mfumo.

Nitakuonyesha tu michoro kadhaa. Hii ni grafu rahisi ya maadili ya kila siku

Kuunganisha mita ya maji kwa nyumba nzuri
Inaweza kuonekana kuwa karibu hakuna mtu aliyetumia maji usiku. Mara kadhaa mtu alienda kwenye choo, na inaonekana kwamba kichujio cha nyuma cha osmosis kinanyonya lita kadhaa kwa usiku. Asubuhi, matumizi yanaongezeka kwa kiasi kikubwa. Kawaida mimi hutumia maji kutoka kwa boiler, lakini basi nilitaka kuoga na kubadili kwa muda maji ya moto ya jiji - hii pia inaonekana wazi kwenye grafu ya chini.

Kutoka kwenye grafu hii nilijifunza kuwa kwenda kwenye choo kunahitaji lita 6-7 za maji, kuoga kunahitaji lita 20-30, kuosha sahani kunahitaji lita 20, na kuoga kunahitaji lita 160. Familia yangu hutumia mahali pengine karibu lita 500-600 kwa siku.

Kwa wale ambao wanatamani sana, unaweza kuangalia rekodi kwa kila thamani ya mtu binafsi

Kuunganisha mita ya maji kwa nyumba nzuri

Kuanzia hapa nilijifunza kwamba wakati bomba limefunguliwa, maji hutiririka kwa kasi ya takriban lita 1 kwa 5 s.

Lakini katika fomu hii takwimu ni pengine si rahisi sana kuangalia. Majordomo pia ina uwezo wa kutazama chati za matumizi kwa siku, wiki na mwezi. Hapa, kwa mfano, ni grafu ya matumizi katika baa

Kuunganisha mita ya maji kwa nyumba nzuri

Kufikia sasa nina data kwa wiki moja tu. Katika mwezi, grafu hii itakuwa dalili zaidi - kila siku itakuwa na safu tofauti. Picha imeharibiwa kidogo na marekebisho ya maadili ambayo mimi huingiza kwa mikono (safu kubwa zaidi). Na bado haijulikani wazi ikiwa niliweka kimakosa maadili ya kwanza, karibu mchemraba kidogo, au ikiwa hii ni mdudu kwenye firmware na sio lita zote zilihesabiwa. Haja muda zaidi.

Grafu zenyewe bado zinahitaji uchawi, kupaka nyeupe, uchoraji. Labda pia nitaunda grafu ya utumiaji wa kumbukumbu kwa madhumuni ya kurekebisha - ikiwa kuna kitu kinachovuja hapo. Labda kwa namna fulani nitaonyesha vipindi wakati hapakuwa na mtandao. Kwa sasa, yote haya ni katika ngazi ya mawazo.

Hitimisho

Leo nyumba yangu imekuwa nadhifu kidogo. Kwa kifaa hicho kidogo, itakuwa rahisi zaidi kwangu kufuatilia matumizi ya maji ndani ya nyumba. Ikiwa mapema nilikasirika "tena, tulitumia maji mengi kwa mwezi," sasa ninaweza kupata chanzo cha matumizi haya.

Wengine wanaweza kuona ni ajabu kutazama usomaji kwenye skrini ikiwa ni mita mbali na mita yenyewe. Lakini katika siku zijazo sio mbali sana, nina mpango wa kuhamia ghorofa nyingine, ambapo kutakuwa na maji kadhaa ya maji, na mita zenyewe zitakuwa ziko kwenye kutua. Kwa hivyo kifaa cha kusoma kwa mbali kitakuwa muhimu sana.

Pia ninapanga kupanua utendaji wa kifaa. Tayari ninaangalia valves za injini. Sasa, ili kubadili boiler kwa maji ya jiji, ninahitaji kugeuza bomba 3 kwenye niche ngumu kufikia. Itakuwa rahisi zaidi kufanya hivyo kwa kifungo kimoja na dalili inayolingana. Kweli, kwa kweli, inafaa kutekeleza ulinzi dhidi ya uvujaji.

Katika makala niliyoelezea toleo langu la kifaa kulingana na ESP8266. Kwa maoni yangu, nilikuja na toleo la kuvutia sana la firmware ya micropython kwa kutumia coroutines - rahisi na nzuri. Nilijaribu kuelezea nuances nyingi na mapungufu ambayo nilikutana nayo wakati wa kampeni. Labda nilielezea kila kitu kwa undani sana; kibinafsi, kama msomaji, ni rahisi kwangu kuruka vitu visivyo vya lazima kuliko kufikiria baadaye kile ambacho kiliachwa bila kusemwa.

Kama kawaida, niko wazi kwa ukosoaji wa kujenga.

Msimbo wa chanzo
Mzunguko na bodi
Mfano wa kesi

Chanzo: mapenzi.com

Kuongeza maoni