Mir verbannen de Waassermeter un d'Smart Home

Eemol waren Hausautomatiséierungssystemer, oder "Smart Home" wéi se dacks genannt goufen, schrecklech deier an nëmmen déi Räich konnten se leeschten. Haut um Maart fannt Dir zimlech preiswerte Kits mat Sensoren, Knäppercher / Schalter an Aktuatoren fir Beliichtung, Sockets, Belëftung, Waasserversuergung an aner Konsumenten ze kontrolléieren. An och déi kromme DIY Persoun kann sech u Schéinheet bedeelegen an Apparater fir e Smart Heem zu engem preiswerte Präis montéieren.

Mir verbannen de Waassermeter un d'Smart Home

Typesch sinn déi proposéiert Apparater entweder Sensoren oder Aktuatoren. Si maachen et einfach Szenarie ëmzesetzen wéi "wann e Bewegungssensor ausgeléist gëtt, schalt d'Luuchten un" oder "de Schalter no bei der Sortie schalt d'Luuchten an der ganzer Wunneng aus." Awer iergendwéi hunn d'Saachen net mat Telemetrie geklappt. Am beschten ass et eng Grafik vun der Temperatur a Fiichtegkeet, oder momentan Kraaft op engem spezifeschen Outlet.

Ech hunn viru kuerzem Waassermeter mat Pulsausgang installéiert. Fir all Liter, deen duerch de Meter geet, gëtt de Rietschalter ageschalt a mécht de Kontakt zou. Dat eenzegt wat nach ze maachen ass, ass un d'Drähten ze hänken a probéiert dovunner ze profitéieren. Zum Beispill analyséiert Waasserverbrauch no Stonn an Dag vun der Woch. Gutt, wann et e puer Waasser Risers an der Wunneng sinn, ass et méi bequem all déi aktuell Indicateuren op engem Écran ze gesinn wéi an schwéier-ze-erreechen Nischen mat Taschenlampe klammen.

Ënnert dem Schnëtt ass meng Versioun vun engem Apparat baséiert op ESP8266, deen Impulser vu Waassermeter zielt a Liesungen iwwer MQTT op den Smart Home Server schéckt. Mir programméiere am Micropython mat der uasyncio Bibliothéik. Wann Dir d'Firmware erstallt, sinn ech op e puer interessant Schwieregkeeten komm, déi ech och an dësem Artikel diskutéieren. Gitt!

De Schema

Mir verbannen de Waassermeter un d'Smart Home

D'Häerz vum ganze Circuit ass e Modul um ESP8266 Mikrokontroller. ESP-12 war ursprénglech geplangt, awer mäin huet sech als defekt erausgestallt. Mir hu misse mat dem ESP-07 Modul zefridde sinn, dee verfügbar war. Glécklecherweis sinn se d'selwecht souwuel a punkto Pins a Funktionalitéit, deen eenzegen Ënnerscheed ass an der Antenne - den ESP-12 huet en agebaute, während den ESP-07 eng extern huet. Awer och ouni WiFi Antenne gëtt d'Signal a mengem Buedzëmmer normalerweis opgeholl.

Standard Modul wiring:

  • Reset Knäppchen mat Pull-up a Kondensator (obwuel béid schonn am Modul sinn)
  • D'Aktivéierungssignal (CH_PD) gëtt op Kraaft gezunn
  • GPIO15 gëtt op de Buedem gezunn. Dëst ass just am Ufank gebraucht, awer ech hunn nach näischt un dësem Been ze befestigen, ech brauch et net méi

Fir de Modul an d'Firmware-Modus ze setzen, musst Dir GPIO2 op de Buedem kierzen, a fir et méi bequem ze maachen, hunn ech e Boot Knäppchen geliwwert. Am normalen Zoustand gëtt dëse Pin un d'Muecht gezunn.

Den Zoustand vun der GPIO2 Linn gëtt nëmmen am Ufank vun der Operatioun iwwerpréift - wann d'Muecht applizéiert gëtt oder direkt no engem Reset. Also de Modul boott entweder wéi gewinnt oder geet an de Firmware Modus. Eemol gelueden, kann dëse Pin als normale GPIO benotzt ginn. Gutt, well et schonn e Knäppchen do ass, kënnt Dir e puer nëtzlech Funktioun derbäi befestigen.

Fir Programméiere an Debugging wäert ech den UART benotzen, deen op e Kamm erausgeet. Wann néideg, verbannen ech einfach en USB-UART Adapter do. Dir musst just drun erënneren datt de Modul mat 3.3V ugedriwwe gëtt. Wann Dir vergiess den Adapter op dës Spannung ze wiesselen an 5V ze liwweren, wäert de Modul héchstwahrscheinlech ausbrennen.

Ech hu keng Probleemer mat Elektrizitéit am Buedzëmmer - den Outlet läit ongeféier engem Meter vun de Meter, also wäert ech mat 220V ugedriwwe ginn. Als Muecht Quell wäert ech eng kleng hunn Block HLK-PM03 vum Tenstar Robot. Perséinlech hunn ech eng schwéier Zäit mat Analog a Kraaftelektronik, awer hei ass eng fäerdeg Kraaftversuergung an engem klenge Fall.

Fir de Betribsmodus ze signaliséieren, hunn ech eng LED mat GPIO2 ugeschloss. Wéi och ëmmer, ech hunn et net ofgeschnidden, well ... Den ESP-07 Modul huet schonn eng LED, an et ass och mat GPIO2 verbonnen. Awer loosst et um Bord sinn, am Fall wou ech dës LED an de Fall erausginn wëll.

Loosst eis op den interessantsten Deel goen. Waasser Meter hu keng Logik; Dir kënnt se net fir aktuell Liesungen froen. Dat eenzegt wat eis verfügbar ass ass Impulser - d'Kontakter vum Rietschalter all Liter zoumaachen. Meng Reedschalterausgaben sinn mat GPIO12 / GPIO13 verbonnen. Ech aktivéieren den Pull-up Resistor programmatesch am Modul.

Am Ufank hunn ech vergiess Widderstänn R8 an R9 ze bidden a meng Versioun vum Board huet se net. Awer well ech schonn d'Diagramm posten fir jiddereen ze gesinn, ass et derwäert dës Iwwerwaachung ze korrigéieren. Widderstänn sinn néideg fir den Hafen net ze verbrennen wann d'Firmware glitches an de Pin op eng setzt, an de Reedschalter verkierzt dës Linn op de Buedem (mat dem Widderstand maximal 3.3V / 1000Ohm = 3.3mA wäert fléissen).

Et ass Zäit fir nozedenken wat ze maachen wann de Stroum erausgeet. Déi éischt Optioun ass fir initial Konterwäerter vum Server um Start ze froen. Awer dëst géif eng bedeitend Komplikatioun vum Austauschprotokoll erfuerderen. Ausserdeem hänkt d'Leeschtung vum Apparat an dësem Fall vum Zoustand vum Server of. Wann de Server net ufänkt nodeems d'Kraaft ausgeschalt gouf (oder méi spéit ugefaang huet), kéint de Waassermeter net initial Wäerter ufroen a funktionnéiert net korrekt.

Dofir hunn ech décidéiert Spuerkontwerter an engem Memory Chip ëmzesetzen, deen iwwer I2C verbonnen ass. Ech hu keng speziell Ufuerderunge fir d'Gréisst vum Flash Memory - Dir musst nëmmen 2 Zuelen späicheren (d'Zuel vun de Liter no de waarme a kale Waasser Meter). Och de klengste Modul wäert maachen. Awer Dir musst op d'Zuel vun den Opnamzyklen oppassen. Fir déi meescht Moduler ass dëst 100 dausend Zyklen, fir e puer bis zu enger Millioun.

Et géif schéngen datt eng Millioun vill ass. Awer während de 4 Joer a mengem Appartement wunnen, hunn ech e bësse méi wéi 500 Kubikmeter Waasser verbraucht, dat sinn 500 Tausend Liter! A 500 dausend records am Flash. An dat ass just kal Waasser. Dir kënnt natierlech den Chip all e puer Joer resolderéieren, awer et stellt sech eraus datt et FRAM Chips sinn. Aus enger Programméierungssiicht ass dëst deeselwechte I2C EEPROM, nëmme mat enger ganz grousser Unzuel vu Schreifzyklen (honnerte vu Millioune). Et ass just datt ech nach ëmmer net an de Buttek mat sou Mikrokreesser kommen, also fir de Moment wäert de gewéinleche 24LC512 stoen.

Gedréckte Circuit Board

Am Ufank hunn ech geplangt d'Brett doheem ze maachen. Dofir gouf de Board als eensäiteg entworf. Awer nodeems ech eng Stonn mat engem Laser Eisen an enger Lötmaske verbruecht hunn (et ass iergendwéi net comme il faut ouni et), hunn ech nach ëmmer decidéiert d'Brieder vun de Chinesen ze bestellen.

Mir verbannen de Waassermeter un d'Smart Home

Bal ier ech de Board bestallt hunn, hunn ech gemierkt datt ech nieft dem Flash Memory Chip och soss eppes nëtzlech mam I2C Bus konnektéieren, wéi zum Beispill e Display. Wat genee fir erauszekréien ass nach ëmmer eng Fro, awer et muss op de Board geréckelt ginn. Gutt, well ech Brieder vun der Fabréck bestellen, war et kee Sënn fir mech op eng eenzeg Säit ze limitéieren, sou datt d'I2C Linnen déi eenzeg sinn op der Récksäit vum Bord.

Et war och ee grousse Problem mat der eent-Manéier wiring. Well De Board gouf als eensäiteg gezeechent, sou datt d'Bunnen an d'SMD Komponenten geplangt waren op der enger Säit ze setzen, an d'Ausgangskomponenten, Stecker a Stroumversuergung op der anerer. Wéi ech d'Brieder e Mount méi spéit krut, hunn ech den urspréngleche Plang vergiess an all d'Komponenten op der viischter Säit solderéiert. A nëmmen wann et ëm d'Soldering vun der Stroumversuergung koum, huet sech erausgestallt datt de Plus an de Minus ëmgedréint waren. Ech hat mat Sprénger ze Bauerenhaff. Am Bild hei uewen hunn ech d'Verdrahtung scho geännert, awer de Buedem gëtt vun engem Deel vum Board an en aneren duerch d'Pins vum Boot Knäppchen transferéiert (obwuel et méiglech wier eng Streck op der zweeter Schicht ze zéien).

Et huet sech esou erausgestallt

Mir verbannen de Waassermeter un d'Smart Home

Logement

Den nächste Schrëtt ass de Kierper. Wann Dir en 3D Drécker hutt, ass dëst kee Problem. Ech hunn net ze vill gestéiert - ech hunn just eng Këscht vun der richteger Gréisst gezeechent an op déi richteg Plazen Ausschnëtter gemaach. De Cover ass mam Kierper mat klenge selbsttappende Schrauwen befestegt.

Mir verbannen de Waassermeter un d'Smart Home

Ech hu scho gesot datt de Boot Knäppchen als allgemeng Zweck benotzt ka ginn - also wäerte mir et op der Frontpanel weisen. Fir dëst ze maachen, hunn ech e spezielle "Wuel" gezeechent, wou de Knäppchen lieft.

Mir verbannen de Waassermeter un d'Smart Home

Am Fall sinn et och Stécker, op deenen d'Brett installéiert ass a mat enger eenzeger M3 Schraube geséchert ass (et war kee Plaz méi um Bord)

Ech hunn den Affichage scho gewielt wéi ech déi éischt Probe Versioun vum Fall gedréckt hunn. E Standard Zwee-Linn Lieser huet net an dësem Fall gepasst, awer am ënneschten war en OLED Display SSD1306 128 × 32. Et ass e bësse kleng, awer ech muss net all Dag kucken - et ass ze vill fir mech.

Dës Manéier erauszefannen an datt wéi d'Drähten dovun ofgeleet ginn, hunn ech beschloss den Display an der Mëtt vum Fall ze hänken. Ergonomie, natierlech, ass ënner dem Par - de Knäppchen ass uewen, den Affichage ass um ënnen. Awer ech hu scho gesot datt d'Iddi fir den Ecran ze befestegt ass ze spéit komm an ech war ze faul fir de Board z'änneren fir de Knäppchen ze réckelen.

Den Apparat ass zesummegesat. Den Displaymodul gëtt mat waarme Klebstoff op de Schnëtt gepecht

Mir verbannen de Waassermeter un d'Smart Home

Mir verbannen de Waassermeter un d'Smart Home

D'Ennresultat kann op KDPV gesi ginn

Firmware

Loosst eis op de Software Deel goen. Fir kleng Handwierker wéi dëst, benotzen ech wierklech Python (mikropython) - de Code ass ganz kompakt a verständlech. Glécklecherweis ass et net néideg op de Registerniveau erof ze goen fir Mikrosekonnen auszedrécken - alles kann aus Python gemaach ginn.

Et schéngt, datt alles einfach ass, awer net ganz einfach - den Apparat huet e puer onofhängeg Funktiounen:

  • De Benotzer dréckt de Knäppchen a kuckt um Display
  • Liter ticken an update Wäerter am Flash Memory
  • De Modul iwwerwaacht de WiFi-Signal a verbënnt erëm wann néideg
  • Gutt, ouni eng blénkeg Glühbir ass et onméiglech

Dir kënnt net dovun ausgoen datt eng Funktioun net funktionnéiert huet wann eng aner aus iergendengem Grond festhält. Ech hu scho bei anere Projeten voll mat Kaktusse gefillt an elo gesinn ech nach ëmmer Feeler am Stil vun "nach e Liter verpasst well den Ecran dee Moment aktualiséiert gouf" oder "de Benotzer kann näischt maachen wärend de Modul sech ukoppelt Wifi." Natierlech kënnen e puer Saache gemaach ginn duerch Ënnerbriechungen, awer Dir kënnt Aschränkungen iwwer Dauer, Nesting vun Uriff oder net-atomar Ännerunge fir Variabelen lafen. Gutt, de Code, deen alles mécht, gëtt séier a Mush.

В méi sérieux Projet Ech hunn klassesch preemptive Multitasking a FreeRTOS benotzt, awer an dësem Fall huet de Modell vill méi gëeegent coroutines an uasync Bibliothéiken . Ausserdeem ass d'Python Implementatioun vu Coroutines einfach erstaunlech - alles gëtt einfach a bequem fir de Programméierer gemaach. Schreift einfach Är eege Logik, sot mir just a wéi enge Plazen Dir tëscht Streame wiessele kënnt.

Ech proposéieren d'Ënnerscheeder tëscht preemptiven a kompetitive Multitasking als optional Fach ze studéieren. Loosst eis endlech op de Code weidergoen.

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

All Konter gëtt vun enger Instanz vun der Counter Klass gehandhabt. Als éischt gëtt den initialen Konterwäert vum EEPROM (value_storage) subtrahéiert - dat ass wéi d'Erhuelung no engem Stroumausfall ëmgesat gëtt.

De Pin gëtt initialiséiert mat engem agebaute Pull-up op d'Energieversuergung: wann de Reedschalter zou ass, ass d'Linn null, wann d'Linn op ass, gëtt se op d'Energieversuergung gezunn an de Controller liest een.

Eng separat Aufgab gëtt och hei lancéiert, déi de Pin pollen. All Konter wäert seng eege Aufgab lafen. Hei ass hire Code

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

Eng Verzögerung vun 25ms ass néideg fir de Kontakt Bounce ze filteren, a gläichzäiteg reguléiert et wéi dacks d'Aufgab erwächt (während dës Aufgab schléift, lafen aner Aufgaben). All 25ms erwächt d'Funktioun, kontrolléiert de Pin a wann d'Red-Schalter-Kontakter zougemaach sinn, dann ass en anere Liter duerch de Meter passéiert an dëst muss veraarbecht ginn.

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

        self._value_storage.write(self._value)

D'Veraarbechtung vum nächste Liter ass trivial - de Konter erhéicht einfach. Gutt, et wier flott den neie Wäert op e Flash Drive ze schreiwen.

Fir einfach ze benotzen, ginn "Accessoren" geliwwert

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

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

Gutt, loosst eis elo vun de Genoss vum Python an der uasync Bibliothéik profitéieren an e waartbare Konterobjekt maachen (wéi kënne mir dëst op Russesch iwwersetzen? Deen deen Dir kënnt erwaarden?)

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

        return self.value()

    __iter__ = __await__  

Dëst ass sou eng praktesch Funktioun déi waart bis de Konterwäert aktualiséiert gëtt - d'Funktioun erwächt vun Zäit zu Zäit a kontrolléiert den _value_changed Fändel. Déi cool Saach iwwer dës Funktioun ass datt den Uruffcode ka schlofen wann Dir dës Funktioun rufft a schlofen bis en neie Wäert kritt gëtt.

Wat iwwer Ënnerbriechungen?Jo, op dësem Punkt kënnt Dir mech troll, sot Dir selwer iwwer Ënnerbriechungen gesot, mä a Wierklechkeet Dir hutt eng domm PIN Ëmfro gemaach. Eigentlech Ënnerbriechungen sinn dat éischt wat ech probéiert hunn. Am ESP8266 kënnt Dir e Rand Ënnerbriechung organiséieren, a souguer en Handler fir dësen Ënnerbriechung am Python schreiwen. An dësem Ënnerbriechung kann de Wäert vun enger Variabel aktualiséiert ginn. Wahrscheinlech wier dëst genuch wann de Comptoir e Sklave-Apparat wier - een deen waart bis e fir dëse Wäert gefrot gëtt.

Leider (oder glécklecherweis?) ass mäin Apparat aktiv, et muss selwer Messagen iwwer de MQTT-Protokoll schécken an Daten op EEPROM schreiwen. An hei kommen Aschränkungen an d'Spill - Dir kënnt d'Erënnerung net an Ënnerbriechungen allocéieren an e grousse Stack benotzen, dat heescht datt Dir vergiesse kënnt Messagen iwwer d'Netz ze schécken. Et gi Bunnen wéi micropython.schedule () déi Iech erlaabt eng Funktioun "sou séier wéi méiglech" ze lafen, awer d'Fro stellt sech, "wat ass de Punkt?" Wat wa mir elo eng Zort Message schécken, an da kënnt en Ënnerbriechung eran a verduerft d'Wäerter vun de Variablen. Oder, zum Beispill, en neie Konterwäert ass vum Server ukomm wärend mir deen alen nach net opgeschriwwen hunn. Am Allgemengen musst Dir d'Synchroniséierung blockéieren oder iergendwéi anescht erauskommen.

A vun Zäit zu Zäit RuntimeError: Zäitplang Stack voll Crashen a wien weess firwat?

Mat explizit Ëmfroen an Uasync, an dësem Fall gëtt et iergendwéi méi schéin an zouverlässeg

Ech hunn d'Aarbecht mat EEPROM an eng kleng Klass bruecht

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)

Am Python ass et schwéier direkt mat Bytes ze schaffen, awer et sinn d'Bytes déi an d'Erënnerung geschriwwe ginn. Ech hu missen d'Konversioun tëscht Ganzt a Bytes mat der ustruct-Bibliothéik fërderen.

Fir den I2C Objet an d'Adress vun der Erënnerungszelle net all Kéier ze transferéieren, hunn ech alles an engem klenge a praktesche Klassiker gewéckelt

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)

Den I2C Objet selwer gëtt mat dëse Parameteren erstallt

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

Mir kommen zum interessantsten Deel - d'Ëmsetzung vun der Kommunikatioun mam Server iwwer MQTT. Gutt, et ass net néideg de Protokoll selwer ëmzesetzen - ech hunn et um Internet fonnt fäerdeg asynchron Ëmsetzung. Dëst ass wat mir wäerte benotzen.

All déi interessantst Saache ginn an der CounterMQTTClient Klass gesammelt, déi op der Bibliothéik MQTTClient baséiert. Loosst eis vun der Peripherie ufänken

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

Hei kënnt Dir Glühbirnen Pins a Knäppercher erstellen an konfiguréieren, souwéi kal a waarm Waasser Meter Objete.

Mat Initialiséierung ass net alles sou trivial

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

Fir d'Betribsparameter vun der mqtt_as Bibliothéik ze setzen, gëtt e grousst Wierderbuch vu verschiddenen Astellungen benotzt - config. Déi meescht vun de Standardastellunge si gutt fir eis, awer vill Astellunge mussen explizit agestallt ginn. Fir d'Astellungen net direkt am Code ze schreiwen, späicheren ech se an der Textdatei config.txt. Dëst erlaabt Iech de Code onofhängeg vun den Astellungen z'änneren, wéi och e puer identesch Geräter mat verschiddene Parameteren ze riven.

De leschte Block vum Code fänkt e puer Coroutinen un fir verschidde Funktiounen vum System ze déngen. Zum Beispill, hei ass eng Coroutine déi d'Servicer counters

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

D'Koroutine waart an enger Loop op en neie Konterwäert a schéckt, soubal et erscheint, e Message iwwer de MQTT Protokoll. Dat éischt Stéck Code schéckt den initialen Wäert och wann kee Waasser duerch de Comptoir fléisst.

D'Basis Klass MQTTClient déngt selwer, initiéiert eng WiFi Verbindung a reconnectéiert wann d'Verbindung verluer ass. Wann et Ännerungen am Zoustand vun der WiFi Verbindung sinn, informéiert d'Bibliothéik eis andeems Dir wifi_connection_handler rufft

    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)

D'Funktioun ass éierlech aus Beispiller kopéiert. An dësem Fall zielt et d'Zuel vun den Outages (Internet_outages) an hir Dauer. Wann d'Verbindung restauréiert ass, gëtt eng Idle Zäit un de Server geschéckt.

Iwwregens ass de leschte Schlof nëmme gebraucht fir d'Funktioun asynchron ze maachen - an der Bibliothéik gëtt et via await genannt, an nëmme Funktiounen, deenen hire Kierper eng aner await enthält, kënne genannt ginn.

Zousätzlech fir mat WiFi ze verbannen, musst Dir och eng Verbindung mam MQTT Broker (Server) opbauen. D'Bibliothéik mécht dat och, a mir kréien d'Méiglechkeet eppes nëtzlech ze maachen wann d'Verbindung etabléiert ass

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

Hei abonnéieren mir op e puer Messagen - de Server huet elo d'Fäegkeet déi aktuell Konterwäerter ze setzen andeems Dir de entspriechende Message schéckt.

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

Dës Funktioun veraarbecht erakommen Messagen, an ofhängeg vum Thema (Messagetitel) ginn d'Wäerter vun engem vun de Konter aktualiséiert

E puer Hëllefsfunktiounen

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

Dës Funktioun schéckt e Message wann d'Verbindung etabléiert ass. Wann et keng Verbindung ass, gëtt de Message ignoréiert.

An dëst ass just eng praktesch Funktioun déi Debugging Messagen generéiert a schéckt.

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

Sou vill Text, a mir hunn nach keng LED blénkt. Hei

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

Ech hunn 2 Blinkmodi geliwwert. Wann d'Verbindung verluer ass (oder et gëtt just etabléiert), blénkt den Apparat séier. Wann d'Verbindung etabléiert ass, blénkt den Apparat eemol all 5 Sekonnen. Wann néideg, kënnen aner Blinkmodi hei ëmgesat ginn.

Awer d'LED ass just verwinnt. Mir hunn och op d'Display gezielt.

    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)

Dëst ass wat ech geschwat hunn - wéi einfach a praktesch et ass mat Coroutinen. Dës kleng Funktioun beschreift d'GANG Benotzererfarung. D'Coroutin waart einfach op de Knäppche gedréckt a schalt den Display fir 3 Sekonnen un. Den Ecran weist déi aktuell Metermessungen.

Et sinn nach e puer kleng Saachen iwwreg. Hei ass d'Funktioun déi dës ganz Entreprise (nei) ufänkt. D'Haaptschleife schéckt just eemol d'Minutt verschidde Debugginginformatiounen. Am Allgemengen zitéieren ech et wéi et ass - ech mengen net datt et néideg ass ze vill ze kommentéieren

   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)

Gutt, e puer méi Astellungen a Konstanten fir d'Beschreiwung ze kompletéieren

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

Et fänkt alles esou un

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

Eppes ass mat menger Erënnerung geschitt

Also, all Code ass do. Ech hunn d'Dateien eropgelueden mat dem Ampy Utility - et erlaabt Iech se op den internen (deen am ESP-07 selwer) Flash Drive eropzelueden an dann aus dem Programm als reguläre Fichieren zougräifen. Do hunn ech och d'mqtt_as eropgelueden, uasyncio, ssd1306 a Sammlungsbibliothéiken déi ech benotzt hunn (an mqtt_as benotzt).

Mir starten an ... Mir kréien e MemoryError. Ausserdeem, wat ech méi probéiert hunn ze verstoen wou genau d'Erënnerung leeft, wat méi Debug-Prints ech plazéiert hunn, wat fréier dëse Feeler erschéngt. Eng kuerz Google Sich huet mech zum Verständnis bruecht datt de Mikrokontroller am Prinzip nëmmen 30 kB Erënnerung huet, an deem 65 kB Code (inklusiv Bibliothéiken) einfach net passen.

Mee et gëtt e Wee eraus. Et stellt sech eraus datt de Mikropython de Code net direkt vun enger .py Datei ausféiert - dës Datei gëtt als éischt kompiléiert. Ausserdeem gëtt et direkt um Mikrokontroller kompiléiert, an Bytecode ëmgewandelt, deen dann an der Erënnerung gespäichert gëtt. Gutt, fir datt de Compiler funktionnéiert, braucht Dir och e gewësse Betrag u RAM.

Den Trick ass de Mikrokontroller vun der Ressourceintensiver Kompilatioun ze retten. Dir kënnt d'Dateien op engem grousse Computer kompiléieren an de fäerdege Bytecode an de Mikrokontroller eroplueden. Fir dëst ze maachen, musst Dir d'Micropython Firmware eroflueden a bauen mpy-cross Utility.

Ech hunn net e Makefile geschriwwen, awer manuell duerchgaang an all déi néideg Dateien (inklusiv Bibliothéiken) esou eppes zesummegesat

mpy-cross water_counter.py

Alles wat bleift ass fir Dateien mat der .mpy Extensioun eropzelueden, net ze vergiessen fir d'éischt déi entspriechend .py aus dem Dateiesystem vum Apparat ze läschen.

Ech hunn all Entwécklung am Programm (IDE?) ESPlorer. Et erlaabt Iech Scripten op de Mikrokontroller eropzelueden an se direkt auszeféieren. A mengem Fall, all Logik an Kreatioun vun all Objete läit am water_counter.py (.mpy) Fichier. Awer fir datt dat alles automatesch ufänkt, muss et och e Fichier mam Numm main.py am Ufank sinn. Ausserdeem sollt et genee .py sinn, an net virauskompiléiert .mpy. Hei sinn seng trivial Inhalter

import water_counter

Mir starten et - alles funktionnéiert. Awer gratis Erënnerung ass alarméierend kleng - ongeféier 1kb. Ech hunn nach ëmmer Pläng fir d'Funktionalitéit vum Apparat auszebauen, an dëse Kilobyte ass kloer net genuch fir mech. Awer et huet sech erausgestallt datt et och e Wee aus dësem Fall gëtt.

Hei ass d'Saach. Och wann d'Dateien an Bytecode kompiléiert sinn an am internen Dateiesystem wunnen, sinn se a Wierklechkeet nach ëmmer an de RAM gelueden a vun do aus ausgefouert. Awer et stellt sech eraus datt de Mikropython Bytecode direkt aus Flash Memory ausféiere kann, awer dofir musst Dir se direkt an d'Firmware bauen. Et ass net schwéier, och wann et zimmlech Zäit op mengem Netbook gedauert huet (nëmmen do hunn ech zoufälleg Linux).

Den Algorithmus ass wéi follegt:

  • Eroflueden an installéieren ESP Open SDK. Dës Saach montéiert e Compiler a Bibliothéike fir Programmer fir den ESP8266. Assemblée no den Instruktiounen op der Haaptsäit vum Projet (ech hunn d'STANDALONE = jo Astellung gewielt)
  • Download micropython Zorte
  • Setzt déi erfuerderlech Bibliothéiken an Ports / esp8266 / Moduler am Mikropythonbaum
  • Mir sammelen d'Firmware no den Instruktiounen an der Datei ports/esp8266/README.md
  • Mir lued d'Firmware op de Mikrokontroller erop (ech maachen dat op Windows mat ESP8266Flasher Programmer oder Python esptool)

Dat ass et, elo 'import ssd1306' wäert de Code direkt vun der Firmware ophiewen an RAM gëtt net dofir verbraucht. Mat dësem Trick hunn ech nëmmen de Bibliothéikscode an d'Firmware eropgelueden, während den Haaptprogrammcode vum Dateiesystem ausgefouert gëtt. Dëst erlaabt Iech de Programm einfach z'änneren ouni d'Firmware nei ze kompiléieren. Am Moment hunn ech iwwer 8.5kb fräi RAM. Dëst erlaabt eis an Zukunft vill verschidde nëtzlech Funktionalitéiten ëmzesetzen. Gutt, wann et guer net genuch Erënnerung ass, da kënnt Dir den Haaptprogramm an d'Firmware drécken.

Also wat solle mir elo doriwwer maachen?

Ok, d'Hardware ass soldered, d'Firmware ass geschriwwe, d'Këscht ass gedréckt, den Apparat ass op der Mauer hänke bliwwen a glécklech eng Glühbir blénkt. Awer fir de Moment ass et alles eng schwaarz Këscht (wuertwiertlech a bildlech) an et ass nach ëmmer vu wéineg Notzung. Et ass Zäit eppes mat de MQTT Messagen ze maachen, déi op de Server geschéckt ginn.

Meng "Smart Home" dréint weider Majordomo System. De MQTT Modul kënnt entweder aus der Këscht, oder ass einfach aus dem Add-on Maart installéiert - ech erënnere mech net wou ech et hierkënnt. MQTT ass net eng selbststänneg Saach - Dir braucht e sougenannte. broker - e Server deen MQTT Messagen un d'Clienten kritt, sortéiert a weidergeleet. Ech benotzen Moustique, déi (wéi majordomo) um selwechten Netbook leeft.

Nodeems den Apparat op d'mannst eemol e Message geschéckt huet, erschéngt de Wäert direkt an der Lëscht.

Mir verbannen de Waassermeter un d'Smart Home

Dës Wäerter kënnen elo mat Systemobjekter verbonne ginn, si kënnen an Automatisatiounsskripte benotzt ginn a verschidde Analysen ënnerworf ginn - all dat ass iwwer den Ëmfang vun dësem Artikel. Ech kann de Majordomo System fir jiddereen, deen interesséiert Kanal Electronics An Lens - e Frënd baut och e Smart Heem a schwätzt kloer iwwer d'Opstellung vum System.

Ech weisen Iech just e puer Grafike. Dëst ass eng einfach Grafik vun deegleche Wäerter

Mir verbannen de Waassermeter un d'Smart Home
Et kann gesi ginn datt bal keen d'Waasser nuets benotzt huet. E puer Mol ass een op d'Toilette gaangen, an et schéngt, datt de Reverse Osmose-Filter e puer Liter pro Nuecht suckt. Moies klëmmt de Konsum däitlech. Ech benotzen normalerweis Waasser aus engem Kessel, awer dunn wollt ech e Bad huelen an temporär op d'Stad waarmt Waasser wiesselen - dat ass och kloer an der ënneschter Grafik ze gesinn.

Aus dëser Grafik hunn ech geléiert datt fir op d'Toilette 6-7 Liter Waasser ze goen, eng Dusch brauch 20-30 Liter, d'Platen wäschen erfuerdert ongeféier 20 Liter, an e Bad erfuerdert 160 Liter. Meng Famill verbraucht iergendwou ongeféier 500-600 Liter pro Dag.

Fir déi, déi besonnesch virwëtzeg sinn, kënnt Dir d'Records fir all eenzel Wäert kucken

Mir verbannen de Waassermeter un d'Smart Home

Vun hei hunn ech geléiert datt wann de Krunn op ass, Waasser mat enger Geschwindegkeet vun ongeféier 1 Liter pro 5 s fléisst.

Awer an dëser Form sinn d'Statistiken wahrscheinlech net ganz bequem ze kucken. Majordomo huet och d'Méiglechkeet Konsum Charts vum Dag, Woch a Mount ze gesinn. Hei ass zum Beispill eng Verbrauchsgrafik a Baren

Mir verbannen de Waassermeter un d'Smart Home

Bis elo hunn ech nëmmen daten fir eng Woch. An engem Mount wäert dës Grafik méi indikativ sinn - all Dag wäert eng separat Kolonn hunn. D'Bild ass liicht verwinnt vun den Upassunge vun de Wäerter déi ech manuell aginn (déi gréisste Kolonn). An et ass nach net kloer, ob ech déi alleréischt Wäerter falsch gesat hunn, bal e Kubus manner, oder ob dëst e Feeler an der Firmware ass an net all Liter gezielt goufen. Brauche méi Zäit.

D'Grafiken selwer brauchen nach e bësse Magie, Wäisswäschen, Molerei. Vläicht wäert ech och eng Grafik vum Erënnerungsverbrauch fir Debuggingzwecker bauen - am Fall wou eppes do leeft. Vläicht wäert ech iergendwéi Perioden weisen wou et keen Internet war. Fir de Moment ass dat alles um Niveau vun Iddien.

Konklusioun

Haut ass mäi Appartement e bësse méi schlau ginn. Mat sou engem klengen Apparat wäert et méi bequem sinn fir mech Waasserverbrauch am Haus ze iwwerwaachen. Wann ech fréier indignéiert war op "erëm, mir hunn vill Waasser an engem Mount verbraucht", elo kann ech d'Quell vun dësem Konsum fannen.

E puer kënnen et komesch fannen d'Messungen um Bildschierm ze kucken, wann et e Meter ewech vum Meter selwer ass. Awer an der net wäiter Zukunft plangen ech an en anert Appartement ze plënneren, wou et e puer Waasserstécker gëtt, an d'Meter selwer wäerten héchstwahrscheinlech op der Landung sinn. Also e Fernliesenapparat wäert ganz nëtzlech sinn.

Ech plangen och d'Funktionalitéit vum Apparat auszebauen. Ech kucken schon op motoriséiert Ventile. Elo, fir de Kessel op Stadwaasser ze wiesselen, muss ech 3 Krunnen an enger schwéier z'erreechen Nisch maachen. Et wier vill méi praktesch dëst mat engem Knäppchen mat der entspriechender Indikatioun ze maachen. Gutt, natierlech ass et derwäert de Schutz géint Leckage ëmzesetzen.

Am Artikel beschriwwen ech meng Versioun vun engem Apparat baséiert op ESP8266. Menger Meenung no sinn ech mat enger ganz interessanter Versioun vu Mikropython Firmware mat Coroutinen komm - einfach a flott. Ech hu probéiert vill vun den Nuancen a Mängel ze beschreiwen, déi ech während der Campagne begéint hunn. Vläicht hunn ech alles ze vill am Detail beschriwwen; perséinlech, als Lieser, ass et méi einfach fir mech iwwer déi onnéideg Saachen ze sprangen wéi spéider nozedenken wat net gesot gouf.

Wéi ëmmer sinn ech oppe fir konstruktiv Kritik.

Quelltext
Circuit an Verwaltungsrot
Fall Modell

Source: will.com

Setzt e Commentaire