Ons koppel die watermeter aan die slimhuis

Eens op 'n tyd was huisoutomatiseringstelsels, of soos dit dikwels "slimhuis" genoem word, verskriklik duur en net die rykes kon dit bekostig. Vandag op die mark kan jy redelike begrotingsstelle met sensors, knoppies / skakelaars en aktueerders vind om beligting, voetstukke, ventilasie, watertoevoer en ander verbruikers te beheer. En selfs die mees krom DIY-shnik kan by die skoonheid aansluit en toestelle vir 'n slim huis vir 'n goedkoop prys bymekaarmaak.

Ons koppel die watermeter aan die slimhuis

As 'n reël is die voorgestelde toestelle óf sensors óf aktueerders. Hulle maak dit maklik om scenario's te implementeer soos "wanneer die bewegingsensor geaktiveer word, skakel die lig aan" of "die skakelaar naby die uitgang skakel die lig in die hele woonstel af." Maar op een of ander manier het dit nie uitgewerk met telemetrie nie. Op sy beste is dit 'n grafiek van temperatuur en humiditeit, of oombliklike krag in 'n spesifieke uitlaat.

Ek het onlangs watermeters met 'n polsuitset geïnstalleer. Deur elke liter wat deur die toonbank geloop het, word die rietskakelaar geaktiveer en sluit die kontak. Die enigste ding om te doen is om aan die drade vas te klou en 'n bietjie voordeel daaruit te probeer kry. Ontleed byvoorbeeld waterverbruik volgens ure en dae van die week. Wel, as daar verskeie stygers vir water in die woonstel is, is dit geriefliker om al die huidige aanwysers op een skerm te sien as om moeilik bereikbare nisse met 'n flitslig te klim.

Onder die snit, my weergawe van 'n toestel gebaseer op ESP8266, wat pulse van watermeters tel en lesings na die slimhuisbediener via MQTT stuur. Ons sal in micropython programmeer deur die uasyncio-biblioteek te gebruik. Toe ek die firmware geskep het, het ek verskeie interessante probleme teëgekom, wat ek ook in hierdie artikel sal bespreek. Gaan!

Die skema

Ons koppel die watermeter aan die slimhuis

Die hart van die hele stroombaan is 'n module op die ESP8266 mikrobeheerder. ESP-12 was oorspronklik beplan, maar myne het geblyk gebrekkig te wees. Ek moes tevrede wees met die ESP-07-module, wat beskikbaar was. Gelukkig is hulle dieselfde wat gevolgtrekkings en funksionaliteit betref, die enigste verskil is in die antenna - die ESP-12 het dit ingebou, terwyl die ESP-07 'n eksterne een het. Selfs sonder 'n WiFi-antenna word die sein in my badkamer egter normaal opgevang.

Die binding van die module is standaard:

  • terugstelknoppie met 'n optrek en 'n kapasitor (alhoewel albei reeds binne die module is)
  • Die aktiveersein (CH_PD) word opgetrek na krag
  • GPIO15 grond toe getrek. Dit is net nodig aan die begin, maar ek hoef steeds nie meer aan hierdie been vas te klou nie

Om die module na die firmware-modus oor te dra, moet jy GPIO2 op die grond toemaak, en om dit geriefliker te maak, het ek die Boot-knoppie verskaf. In die normale toestand word hierdie pen na krag getrek.

Die toestand van die GPIO2-lyn word slegs aan die begin van die werking nagegaan - wanneer krag toegepas word of onmiddellik na 'n terugstelling. Die module begin dus óf soos gewoonlik, óf gaan in die firmware-modus. Sodra dit gelaai is, kan hierdie pen as 'n gewone GPIO gebruik word. Wel, aangesien daar reeds 'n knoppie daar is, kan u 'n nuttige funksie daaraan hang.

Vir programmering en ontfouting sal ek die UART gebruik, wat ek na die kam gebring het. Wanneer nodig, koppel ek bloot 'n USB-UART-adapter daar. Jy moet net onthou dat die module deur 3.3V aangedryf word. As jy vergeet om die adapter na hierdie spanning oor te skakel en 5V toe te pas, dan sal die module heel waarskynlik uitbrand.

Ek het geen probleme met elektrisiteit in die badkamer nie - die uitlaat is ongeveer 'n meter van die meters af geleë, so ek sal dit van 220V af aandryf. As 'n kragbron sal ek 'n klein hê blok HLK-PM03 deur Tenstar Robot. Persoonlik sukkel ek met analoog- en kragelektronika, en hier is 'n klaargemaakte kragtoevoer in 'n klein kassie.

Om die bedryfsmodusse aan te dui, het ek 'n LED verskaf wat aan GPIO2 gekoppel is. Ek het dit egter nie gesoldeer nie, want. die ESP-07-module het reeds 'n LED wat aan dieselfde GPIO2 gekoppel is. Maar laat dit op die bord wees - skielik wil ek hierdie LED na die saak bring.

Kom ons gaan oor na die interessantste. Watermeters het geen logika nie, hulle kan nie vir huidige lesings gevra word nie. Die enigste ding wat tot ons beskikking is, is impulse – om die kontakte van die rietskakelaar elke liter toe te maak. Ek het die rietskakelaaruitsette in GPIO12 / GPIO13. Ek sal die optrekweerstand programmaties binne die module aanskakel.

Ek het aanvanklik vergeet om weerstande R8 en R9 te verskaf en hulle is nie in my weergawe van die bord nie. Maar aangesien ek reeds die skema uitlê vir almal om te sien, is dit die moeite werd om hierdie oorsig reg te stel. Weerstande word benodig om nie die poort te verbrand as die firmware karig is en 'n eenheid op die pen sit nie, en die rietskakelaar kort hierdie lyn na grond (met 'n weerstand sal 'n maksimum van 3.3V / 1000Ω = 3.3mA vloei) .

Dit is tyd om te dink oor wat om te doen as die elektrisiteit uitgaan. Die eerste opsie is om die bediener te vra vir die aanvanklike waardes van die tellers aan die begin. Maar dit sal 'n aansienlike komplikasie van die uitruilprotokol vereis. Boonop hang die werkverrigting van die toestel in hierdie geval af van die toestand van die bediener. As die bediener nie begin het nie (of later begin het nadat die lig afgeskakel is), dan sou die watermeter nie die aanvanklike waardes kon aanvra nie en sou dit verkeerd werk.

Daarom het ek besluit om die berging van tellerwaardes te implementeer in 'n geheueskyfie wat via I2C gekoppel is. Ek het geen spesiale vereistes vir die grootte van flitsgeheue nie - jy hoef net 2 nommers te stoor (die aantal liters volgens warm- en kouewatermeters). Selfs die kleinste module sal doen. Maar jy moet aandag gee aan die aantal skryfsiklusse. Vir die meeste modules is dit 100 duisend siklusse, vir sommige tot 'n miljoen.

Dit wil voorkom asof 'n miljoen baie is. Maar vir 4 jaar van die lewe in my woonstel, het ek 'n bietjie meer as 500 kubieke meter water verbruik, dit is 500 duisend liter! En 500 duisend rekords in flits. En dit is net koue water. U kan die skyfie natuurlik elke paar jaar weer soldeer, maar dit het geblyk dat daar FRAM-skyfies is. Vanuit 'n programmeringsoogpunt is dit dieselfde I2C EEPROM, net met 'n baie groot aantal herskryfsiklusse (honderde miljoene). Dit is net totdat ek nog nie by 'n winkel met sulke mikrokringe kan uitkom nie, so vir eers sal die gewone 24LC512 staan.

Gedrukte stroombaanbord

Ek het aanvanklik beplan om 'n bord by die huis te maak. Daarom is die bord as eensydig ontwerp. Maar nadat ek 'n uur met 'n laseryster en 'n soldeermasker spandeer het (dit is op een of ander manier nie comme il faut daarsonder nie), het ek nietemin besluit om borde by die Chinese te bestel.

Ons koppel die watermeter aan die slimhuis

Amper voordat ek die bord bestel het, het ek besef dat jy benewens die flitsgeheue-skyfie nog iets nuttigs aan die I2C-bus kan koppel, byvoorbeeld 'n skerm. Wat presies om dit uit te voer, is nog steeds 'n vraag, maar jy moet dit op die bord teel. Wel, aangesien ek planke by die fabriek gaan bestel het, was dit geen sin om myself tot 'n eensydige bord te beperk nie, so die I2C-lyne is die enigstes op die agterkant van die bord.

Een groot paal is ook met die eenrigtingbedrading verbind. Omdat die bord is eensydig geteken, dan is die bane en SMD-komponente beplan om aan die een kant geplaas te word, en die uitsetkomponente, verbindings en kragtoevoer aan die ander kant. Toe ek die planke 'n maand later ontvang het, het ek van die oorspronklike plan vergeet en al die komponente aan die voorkant gesoldeer. En eers as dit by die soldering van die kragtoevoer kom, het dit geblyk dat die plus en minus omgekeerd geskei is. Ek moes met springers boer. In die prentjie hierbo het ek reeds die bedrading verander, maar die grond word van een deel van die bord na 'n ander oorgedra deur die penne van die Boot-knoppie (hoewel dit moontlik sou wees om 'n baan op die tweede laag te teken).

Dit het so uitgedraai

Ons koppel die watermeter aan die slimhuis

behuising

Die volgende stap is die liggaam. As jy 'n 3D-drukker het, is dit nie 'n probleem nie. Ek het nie veel gepla nie - ek het net 'n boks van die regte grootte geteken en uitknipsels op die regte plekke gemaak. Die deksel is aan die liggaam vasgemaak met klein selftappende skroewe.

Ons koppel die watermeter aan die slimhuis

Ek het reeds genoem dat die Boot-knoppie as 'n algemene knoppie gebruik kan word - so kom ons bring dit na die voorpaneel. Om dit te doen, het ek 'n spesiale "put" geteken waar die knoppie woon.

Ons koppel die watermeter aan die slimhuis

Daar is ook stompe in die kas waarop die bord geïnstalleer en met 'n enkele M3-skroef vasgemaak word (daar was nie meer spasie op die bord nie)

Die skerm is reeds gekies toe ek die eerste gepaste weergawe van die tas gedruk het. 'n Standaard tweelyndrukker het nie in hierdie houer gepas nie, maar onder in die loop was 'n OLED-skerm SSD1306 128 × 32. Dit is klein, maar ek staar nie elke dag na hom nie - dit sal rol.

Ek het hierdie manier en dat geskat, hoe die drade daaruit gelê gaan word, en ek het besluit om die skerm in die middel van die kas te plak. Ergonomie, natuurlik, onder die voetstuk - die knoppie is bo, die skerm is aan die onderkant. Maar ek het reeds gesê dat die idee om die skerm te skroef te laat gekom het en ek was te lui om die bord weer te bedraad om die knoppie te skuif.

Gemonteerde toestel. Die vertoonmodule word met warm gom aan die snot vasgeplak

Ons koppel die watermeter aan die slimhuis

Ons koppel die watermeter aan die slimhuis

Die eindresultaat kan op KDPV gesien word

voeg

Kom ons gaan aan na die sagteware deel. Vir sulke klein handwerk gebruik ek baie graag die Python-taal (mikropyton) - die kode is baie kompak en verstaanbaar. Gelukkig is dit nie nodig om af te gaan na die vlak van registers om mikrosekondes uit te druk nie - alles kan vanaf luislang gedoen word.

Dit blyk dat alles eenvoudig is, maar nie baie nie - die toestel het verskeie onafhanklike funksies:

  • Die gebruiker tik 'n knoppie en kyk na die skerm
  • Liters merk en werk waardes op in flitsgeheue
  • Die module monitor die WiFi-sein en herkoppel indien nodig
  • Wel, sonder 'n flikkerende gloeilamp kan jy glad nie

Dit is onmoontlik om te erken dat een funksie nie gewerk het as die ander een, om een ​​of ander rede, dom is nie. Ek het al kaktusse in ander projekte geëet en nou sien ek steeds foute soos "het nog 'n liter gemis omdat die skerm op daardie oomblik opgedateer het" of "die gebruiker kan niks doen terwyl die module aan WiFi koppel nie". Natuurlik kan sommige dinge deur onderbrekings gedoen word, maar jy kan 'n beperking op duur, nes van oproepe of nie-atomiese verandering van veranderlikes ondervind. Wel, die kode wat alles doen en dadelik vinnig in 'n gemors verander.

В ernstiger projek Ek het klassieke preemptive multitasking en FreeRTOS gebruik, maar in hierdie geval was die model baie meer geskik coroutines en uasync biblioteke . Boonop is die Python-implementering van coroutines net 'n bom - alles word eenvoudig en gerieflik vir die programmeerder gedoen. Skryf net jou eie logika, vertel my net waar jy tussen drade kan wissel.

Ek stel voor om die verskille tussen voorkomende en mededingende multitasking as opsioneel te bestudeer. Kom ons kom nou uiteindelik by die kode.

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

Elke teller word deur 'n instansie van die Counter-klas hanteer. Eerstens word die aanvanklike waarde van die teller van die EEPROM (value_storage) afgetrek - dit is hoe herstel na 'n kragonderbreking geïmplementeer word.

Die pen word geïnisialiseer met 'n ingeboude optrek na die kragtoevoer: as die rietskakelaar toe is, is die lyn nul, as die lyn oop is, word dit opgetrek na die kragtoevoer en die beheerder lees een.

Ook word 'n aparte taak hier geloods, wat die pen sal poll. Elke teller sal sy eie taak uitvoer. Hier is haar kode

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

'n Vertraging van 25ms is nodig om die weiering van kontakte te filter, en terselfdertyd reguleer dit hoe gereeld die taak wakker word (terwyl hierdie taak slaap, werk ander take). Elke 25ms word die funksie wakker, kontroleer die pen, en as die rietskakelkontakte gesluit is, dan het nog 'n liter deur die toonbank gegaan en dit moet verwerk word.

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

        self._value_storage.write(self._value)

Om die volgende liter te verwerk is onbenullig – die toonbank neem net toe. Wel, dit sal lekker wees om 'n nuwe waarde op 'n USB-flash drive te skryf.

Vir gemak van gebruik word "toegangers" verskaf.

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

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

Wel, kom ons gebruik nou die sjarme van luislang en die uasync-biblioteek en maak die teenvoorwerp wagbaar (hoe kan ek dit in Russies vertaal? Die een wat verwag kan word?)

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

        return self.value()

    __iter__ = __await__  

Dit is so 'n handige funksie wat wag totdat die tellerwaarde opgedateer is - die funksie word van tyd tot tyd wakker en kontroleer die _value_changed vlag. Die truuk van hierdie funksie is dat die oproepkode aan die slaap kan raak op 'n oproep na hierdie funksie en slaap totdat 'n nuwe waarde ontvang word.

Maar wat van onderbrekings?Ja, op hierdie stadium kan jy my trol en sê dat hy self gesê het oor onderbrekings, maar eintlik het hy 'n dom speldpeiling gereël. Eintlik is onderbrekings die eerste ding wat ek probeer het. In die ESP8266 kan jy 'n onderbreking aan die voorkant organiseer, en selfs 'n onderbrekingshanteerder vir hierdie onderbreking in luislang skryf. In hierdie onderbreking kan jy die waarde van 'n veranderlike opdateer. Dit sou waarskynlik genoeg wees as die teller 'n slaaftoestel was - een wat wag totdat dit vir hierdie waarde gevra word.

Ongelukkig (of gelukkig?) is my toestel aktief, dit behoort self boodskappe via die MQTT-protokol te stuur en data na EEPROM te skryf. En hier kom beperkings reeds in - jy kan nie geheue in onderbrekings toeken en 'n groot stapel gebruik nie, wat beteken dat jy kan vergeet om boodskappe oor die netwerk te stuur. Daar is broodjies soos micropython.schedule () wat jou toelaat om 'n soort funksie "so gou as moontlik" uit te voer, maar die vraag ontstaan ​​"wat is die punt?". Skielik stuur ons nou 'n soort boodskap, en dan breek 'n onderbreking in en bederf die waardes van veranderlikes. Of, byvoorbeeld, 'n nuwe tellerwaarde het van die bediener af aangekom terwyl ons nog nie die ou een aangeteken het nie. Oor die algemeen moet jy sinchronisasie blokkeer of op een of ander manier anders uitkom.

En van tyd tot tyd RuntimeError: skedule stapel vol ineenstortings en wie weet hoekom?

Met eksplisiete peiling en uasync, in hierdie geval, blyk dit op een of ander manier mooier en betroubaarder te wees.

Ek het in 'n klein klas werk met EEPROM uitgeneem

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)

Dit is moeilik om direk met grepe in luislang te werk, en dit is grepe wat na die geheue geskryf word. Ek moes die omskakeling tussen 'n heelgetal en grepe omhein deur die ustruct-biblioteek te gebruik.

Om nie die I2C-voorwerp en die adres van die geheuesel elke keer oor te dra nie, het ek dit alles in 'n klein en gerieflike klassieke toegedraai

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)

Die I2C-voorwerp self word met hierdie parameters geskep

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

Ons benader die interessantste - die implementering van kommunikasie met die bediener via MQTT. Wel, jy hoef nie die protokol self te implementeer nie - ek het dit op die internet gevind klaargemaakte asynchrone implementering. Hier sal ons dit gebruik.

Al die interessantste word versamel in die CounterMQTTClient-klas, wat gebaseer is op die biblioteek MQTTClient. Kom ons begin by die periferie

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

Hier word gloeilamp- en knoppiepennetjies geskep en gekonfigureer, sowel as koue- en warmwatermetervoorwerpe.

Met inisialisering is alles nie so onbenullig nie

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

Om die parameters van die mqtt_as-biblioteek te stel, word 'n groot woordeboek van verskillende instellings gebruik - config. Die meeste van die verstekinstellings werk vir ons, maar baie instellings moet eksplisiet gestel word. Om nie die instellings direk in die kode voor te skryf nie, stoor ek dit in 'n tekslêer config.txt. Dit laat jou toe om die kode te verander ongeag die instellings, asook om verskeie identiese toestelle met verskillende parameters te klink.

Die laaste blok kode begin verskeie koroutines om verskeie stelselfunksies te dien. Hier is 'n voorbeeld van 'n koroutine wat tellers bedien

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

Die koroutine wag in 'n lus vir 'n nuwe tellerwaarde, en sodra dit verskyn, stuur dit 'n boodskap via die MQTT-protokol. Die eerste stukkie kode stuur die aanvanklike waarde selfs al is daar geen water wat deur die toonbank vloei nie.

Die basisklas MQTTClient dien homself, inisieer 'n WiFi-verbinding en herkoppel wanneer die verbinding verloor word. Wanneer die toestand van die WiFi-verbinding verander, stel die biblioteek ons ​​in kennis deur wifi_connection_handler te skakel

    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)

Die funksie word eerlik uit die voorbeelde gelek. In hierdie geval tel dit die aantal onderbrekings (internet_onderbrekings) en hul duur. Wanneer die verbinding herstel word, word 'n ledige tyd na die bediener gestuur.

Terloops, die laaste slaap is net nodig vir die funksie om asynchroon te word - in die biblioteek word dit deur wag genoem, en slegs funksies in die liggaam waarvan daar nog 'n wag is, kan geroep word.

Benewens die verbinding met WiFi, moet u ook 'n verbinding met die MQTT-makelaar (bediener) vestig. Dit word ook deur die biblioteek gedoen, en ons kry die geleentheid om iets nuttigs te doen wanneer die verbinding tot stand gebring is

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

Hier teken ons in op verskeie boodskappe - die bediener het nou die vermoë om die huidige waardes van die tellers te stel deur die toepaslike boodskap te stuur.

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

Hierdie funksie verwerk inkomende boodskappe, en afhangende van die onderwerp (boodskapnaam), word die waardes van een van die tellers opgedateer

'n Paar helperfunksies

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

Hierdie funksie is verantwoordelik vir die stuur van 'n boodskap as die verbinding tot stand gebring is. As daar geen verbinding is nie, word die boodskap geïgnoreer.

En dit is net 'n gerieflike funksie wat ontfoutingsboodskappe genereer en stuur.

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

Soveel teks en ons het nog nie die LED geknip nie. Hier

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

Ek het 2 maniere van flikker verskaf. As die verbinding verloor word (of dit word net tot stand gebring), dan sal die toestel vinnig flikker. As die verbinding bewerkstellig word, flikker die toestel elke 5 sekondes. Indien nodig, kan ander maniere van flikker hier geïmplementeer word.

Maar die LED is so, bederf. Ons het ook by die vertoning geswaai.

    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)

Dit is waaroor ek gepraat het - hoe eenvoudig en gerieflik is dit met koroutines. Hierdie klein funksie beskryf ALLE gebruikerinteraksie. Die coroutine wag net dat die knoppie gedruk word en skakel die skerm vir 3 sekondes aan. Die skerm wys die huidige meterlesings.

Daar is nog 'n paar klein goedjies oor. Hier is die funksie wat hierdie hele ekonomie (her)begin. Die hooflus is slegs gemoeid met die stuur van verskeie ontfoutingsinligting een keer per minuut. Oor die algemeen gee ek dit soos dit is - ek hoef nie spesifiek kommentaar te lewer nie, dink ek

   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)

Wel, nog 'n paar instellings en konstantes vir volledigheid van beskrywing

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

Dit begin alles so

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

Iets het met my geheue gebeur

So, al die kode is daar. Ek het die lêers opgelaai deur die ampy-hulpprogram te gebruik - dit laat jou toe om dit op die interne (die een in die ESP-07 self) flitsskyf op te laai en dit dan vanaf die program as normale lêers te verkry. Daar het ek ook die mqtt_as, uasyncio, ssd1306 en versamelingsbiblioteke wat ek gebruik het (gebruik binne mqtt_as) opgelaai.

Ons begin en ... Ons ontvang MemoryError. Boonop, hoe meer ek probeer verstaan ​​het presies waar die geheue lek, hoe meer ek die afdrukke ontfout het, hoe vroeër het hierdie fout plaasgevind. ’n Kort google het my laat verstaan ​​dat daar in beginsel in die mikrobeheerder net 30 kb geheue is waarin 65 kb se kode (saam met biblioteke) op geen manier pas nie.

Maar daar is 'n uitweg. Dit blyk dat micropython nie kode direk vanaf 'n .py-lêer uitvoer nie - hierdie lêer word eers saamgestel. Boonop word dit direk op die mikrobeheerder saamgestel, verander dit in greepkode, wat dan in die geheue gestoor word. Wel, die samesteller benodig ook 'n sekere hoeveelheid RAM om te werk.

Die truuk is om die mikrobeheerder te red van hulpbron-intensiewe samestelling. Jy kan lêers op 'n groot rekenaar saamstel en klaargemaakte greepkode na die mikrobeheerder oplaai. Om dit te doen, moet jy die micropython-firmware aflaai en bou mpy-cross nut.

Ek het nie 'n Makefile geskryf nie, maar het handmatig deurgegaan en al die nodige lêers (insluitend biblioteke) so saamgestel

mpy-cross water_counter.py

Dit bly net om die lêers met die .mpy-uitbreiding in te vul, en onthou om eers die ooreenstemmende .py-lêers van die toestel se lêerstelsel te verwyder.

Ek het al die ontwikkeling in die program (IDE?) ESPlorer gedoen. Dit laat jou toe om skrifte na die mikrobeheerder op te laai en dit dadelik uit te voer. In my geval is al die logika en die skepping van alle voorwerpe in die water_counter.py (.mpy) lêer geleë. Maar vir dit alles om outomaties by die begin te begin, moet daar ook 'n lêer genaamd main.py wees. Boonop moet dit presies .py wees, en nie vooraf saamgestelde .mpy nie. Hier is die onbenullige inhoud daarvan

import water_counter

Ons begin – alles werk. Maar vrye geheue is dreigend klein - ongeveer 1kb. Ek het nog planne om die funksionaliteit van die toestel uit te brei, en hierdie kilogreep sal natuurlik nie vir my genoeg wees nie. Maar dit het geblyk dat daar 'n uitweg is.

Die punt is dit. Alhoewel die lêers in greepkode saamgestel is en op die interne lêerstelsel woon, word hulle eintlik in RAM gelaai en in elk geval van daar af uitgevoer. Maar dit blyk dat micropython greepkode direk vanaf flitsgeheue kan uitvoer, maar hiervoor moet jy dit direk in die firmware inbou. Dit is nie moeilik nie, alhoewel dit 'n ordentlike hoeveelheid tyd op my netbook geneem het (net daar het ek Linux gehad).

Die algoritme is soos volg:

  • Laai af en installeer ESP oop SDK. Hierdie ding bou 'n samesteller en biblioteke vir programme onder die ESP8266. Dit word saamgestel volgens die instruksies op die hoofblad van die projek (ek het die STANDALONE=ja-instelling gekies)
  • Download mikropython soorte
  • Gooi die nodige biblioteke in poorte/esp8266/modules binne die mikropytonboom
  • Ons versamel die firmware volgens die instruksies in die lêer ports/esp8266/README.md
  • Laai die firmware op na die mikrobeheerder (ek doen dit op Windows met die ESP8266Flasher-programme of Python se esptool)

Alles, nou sal 'import ssd1306' die kode direk vanaf die firmware verhoog en RAM sal nie hiervoor bestee word nie. Met hierdie truuk het ek slegs die biblioteekkode na die firmware opgelaai, terwyl die hoofprogramkode vanaf die lêerstelsel uitgevoer word. Dit maak dit maklik om die program te verander sonder om die firmware te hersaamstel. Op die oomblik het ek ongeveer 8.5 kb RAM vry. Dit sal ons in staat stel om heelwat verskillende nuttige funksies in die toekoms te implementeer. Wel, as daar glad nie genoeg geheue is nie, kan u die hoofprogram in die firmware indruk.

En wat om nou daarmee te doen?

Ok, die stuk yster is gesoldeer, die firmware is geskryf, die boks is gedruk, die toestel sit vas aan die muur en die lig flikker vrolik. Maar tot dusver is dit alles 'n swart boks (letterlik en figuurlik) en daar is nog min sin daaruit. Dit is tyd om iets te doen met die MQTT-boodskappe wat na die bediener gestuur word.

My "slim huis" draai aan Majordomo stelsel. Die MQTT-module is óf uit die boks óf maklik geïnstalleer vanaf die byvoegingsmark - ek kan nie onthou waar dit vandaan kom nie. MQTT is nie 'n selfversorgende ding nie - jy benodig 'n sg. makelaar - 'n bediener wat boodskappe aan MQTT-kliënte aanvaar, sorteer en aanstuur. Ek gebruik muskiet, wat (soos majordomo) op dieselfde netbook werk.

Nadat die toestel minstens een keer 'n boodskap gestuur het, sal die waarde onmiddellik in die lys verskyn.

Ons koppel die watermeter aan die slimhuis

Hierdie waardes kan nou met stelselvoorwerpe geassosieer word, dit kan in outomatiseringsskrifte gebruik word en aan verskeie ontledings onderwerp word - dit alles is buite die bestek van hierdie artikel. Wie in die majordomo-stelsel belangstel, kan ek aanbeveel Kanaalelektronika in lens - 'n vriend bou ook 'n slim huis en praat verstaanbaar oor die opstel van die stelsel.

Ek sal jou net 'n paar grafieke wys. Dit is 'n eenvoudige grafiek van waardes per dag

Ons koppel die watermeter aan die slimhuis
Daar kan gesien word dat byna niemand die water snags gebruik het nie. 'n Paar keer het iemand toilet toe gegaan, en dit lyk of die tru-osmose-filter 'n paar liter per nag suig. In die oggend neem verbruik aansienlik toe. Gewoonlik gebruik ek water uit die ketel, maar toe wou ek gaan bad en het tydelik oorgeskakel na stadswarmwater – dit is ook duidelik sigbaar in die onderste grafiek.

Uit hierdie grafiek het ek geleer dat toilet toe 6-7 liter water is, stort is 20-30 liter, skottelgoed was ongeveer 20 liter, en om te bad vereis 160 liter. Gedurende die dag verbruik my gesin iewers rondom 500-600l.

Vir diegene wat veral nuuskierig is, kan u na die rekords vir elke individuele waarde kyk.

Ons koppel die watermeter aan die slimhuis

Van hier af het ek geleer dat wanneer die kraan oop is, water teen 'n spoed van ongeveer 1 liter in 5 sekondes vloei.

Maar in hierdie vorm is die statistieke waarskynlik nie baie gerieflik om na te kyk nie. majordomo het ook die vermoë om verbruikskaarte volgens dag, week en maand te sien. Hier is byvoorbeeld 'n grafiek van verbruik in kolomme

Ons koppel die watermeter aan die slimhuis

Tot dusver het ek net een week se data. Oor 'n maand sal hierdie grafiek meer onthullend wees - 'n aparte kolom sal met elke dag ooreenstem. Die prentjie word effens bederf deur die aanpassings van die waardes wat ek met die hand invoer (die grootste kolom). En dit is nog nie duidelik of ek die heel eerste waardes amper 'n kubus minder verkeerd gestel het nie, en of dit 'n fout in die firmware is en nie alle liters is in ag geneem nie. Benodig meer tyd.

Bo die grafieke self moet jy nog toor, bleik, verf. Miskien sal ek ook 'n grafiek bou van geheueverbruik vir ontfoutingsdoeleindes - skielik lek iets daar. Miskien sal ek op een of ander manier die tydperke vertoon toe daar geen internet was nie. Terwyl dit alles draai op die vlak van die idee.

Gevolgtrekking

Vandag het my woonstel 'n bietjie slimmer geword. Met so 'n klein toestel sal dit vir my geriefliker wees om die waterverbruik in die huis te monitor. As ek vroeër verontwaardig was "daar is weer baie water in 'n maand verbruik", kan ek nou die bron van hierdie verbruik vind.

Dit sal vir iemand vreemd lyk om na die lesings op die skerm te kyk as dit 'n meter van die meter self is. Maar in die nie te verre toekoms beplan ek om na 'n ander woonstel te verhuis, waar daar verskeie waterstygers sal wees, en die meters self sal waarskynlik op die landing geleë wees. Dus sal 'n afstandleestoestel baie handig wees.

Ek beplan ook om die funksionaliteit van die toestel uit te brei. Ek kyk al na gemotoriseerde kleppe. Nou, om die ketel-stad-water oor te skakel, moet ek 3 krane draai in 'n moeilik bereikbare nis. Dit sal baie geriefliker wees om dit te doen met een knoppie met die ooreenstemmende aanduiding. Wel, dit is natuurlik die moeite werd om beskerming teen lekkasies te implementeer.

In die artikel het ek my weergawe van die toestel vertel wat gebaseer is op ESP8266. Na my mening het ek 'n baie interessante weergawe van die mikropython-firmware met behulp van coroutines gekry - eenvoudig en mooi. Ek het probeer om die vele nuanses en jambes wat ek tydens die veldtog teëgekom het, te beskryf. Miskien het ek alles in te veel besonderhede beskryf, vir my persoonlik is dit as leser makliker om die oordaad te verkwis as om uit te dink wat later ongesê gelaat is.

Soos altyd is ek oop vir konstruktiewe kritiek.

Bronkode
Skematiese en bord
Geval model

Bron: will.com

Voeg 'n opmerking