Kunagi olid koduautomaatikasĂŒsteemid, vĂ”i nagu neid sageli nimetatakse, ânutikoduâ, ÀÀrmiselt kallid ja neid said endale lubada vaid rikkad. TĂ€napĂ€eval vĂ”ib turult leida piisavalt eelarvesĂ”bralikke komplekte anduritega, nuppude/lĂŒlititega ja tĂ€iturite seadmetega, mis haldavad valgustust, pistikupesi, ventilatsiooni, veevarustust ja teisi tarbijaid. Isegi kĂ”ige vĂ€hem oskuslik DIY-huviline vĂ”ib kergesti alustada nutikoduseadmete kokkupanekut odava hinnaga.

Tavaliselt on pakutavad seadmed kas andurid vĂ”i tĂ€iturmehhanismid. Need vĂ”imaldavad lihtsalt ellu viia stsenaariume nagu âliikumisanduri aktiveerimisel sĂŒĂŒta tuliâ vĂ”i âukse kĂ”rval olev lĂŒliti kustutab kogu korteri tuledâ. Kuid telemeetria ei Ă”nnestunud eriti hĂ€sti. Parimal juhul on see temperatuuri ja niiskuse graafik vĂ”i hetkmugavus konkreetse pistikupesaga.
Hiljuti paigaldasin endale veemÔÔtjad pulsivĂ€ljundiga. Iga liitri puhul, mis mööda mÔÔtjast voolab, aktiveerib magnetkontakt ja sulgeb kontakti. JÀÀnud on vĂ€ike asi â ĂŒhendada juhtmed ja proovida sellest kasu saada. NĂ€iteks analĂŒĂŒsida veetarbimist tundide ja nĂ€dalapĂ€evade kaupa. Kui korteris on mitu veetorustikku, siis on mugavam nĂ€ha kĂ”iki praeguseid nĂ€itajaid ĂŒhel ekraanil, kui ronida raskesti ligipÀÀsetavatesse niĆĄĆĄidesse taskulambiga.
Allpool on minu versioon seadmeid ESP8266 baasil, mis loendab veemÔÔtjate impulsse ja saadab MQTT kaudu andmed nutikodu serverisse. Programmeerime micropythonis, kasutades uasyncio raamatukogu. Firmware'i loomise kÀigus sattusin mitmete huvitavate raskuste otsa, millest rÀÀgin ka selles artiklis. Alustame!
Schema

Kogu skeemi sĂŒdameks on ESP8266 mikrokontrolleri moodul. Alguses plaaniti ESP-12, kuid minu oma osutus defektaalseks. Pidin leppima ESP-07 mooduliga, mis oli saadaval. Ănneks on need vĂ”rdsed ning vĂ€ljundite ja funktsionaalsuse poolest, erinevus on ainult antennis â ESP-12-l on sisemine antenn, samas kui ESP-07-l on vĂ€line. TĂ”si, isegi ilma antennita on WiFi signaal minu vannitoas normaalne.
Mooduli kinnitus on standardne:
- reset-nupp koos tÔukemehhanismi ja kondensaatoriga (kuigi mÔlemad on juba sees moodsul)
- signaal enable (CH_PD) on tĂ”mmatud toite kĂŒlge
- GPIO15 on maapinn tĂ”mmatud maapinnale. See on vajalik ainult kĂ€ivitamisel, kuid mul ei ole rohkem sellele jalale ĂŒhendamiseks midagi.
Modi hĂ”ivamisreĆŸiimi lĂŒlitamiseks peab GPIO2 olema ĂŒhendatud maapinnaga ja mugavuse huvides olen ma ette nĂ€inud Boot-nupu. Tavalises olekus tĂ”mbab see pin toiteseisu.
GPIO2 olekut kontrollitakse ainult töö alguses â pĂ€rast toite sisselĂŒlitamist vĂ”i kohe pĂ€rast taaskĂ€ivitamist. Seega kas moduleerub nagu tavaliselt vĂ”i lĂŒlitub flashing-reĆŸiimi. PĂ€rast laadimist saab seda vĂ€ljundit kasutada tavalise GPIO-na. Ja kuna seal on juba nupp, siis saab sellele lisada mĂ”nda kasulikku funktsiooni.
Programmeermiseks ja silumiseks kasutan UART-i, mis on vĂ€ljundis. Kui on vaja, ĂŒhendage lihtsalt USB-UART-adapter sinna. Pea meeles, et moodul on toiteks 3,3V. Kui unustad adapteri selle pingele lĂŒlitada ja rakendad 5V, siis tĂ”enĂ€oliselt moodul puruneb.
Elektriga vannitoas mul probleeme ei ole â pistik on umbes meetri kaugusel arvestitest, nii et varustan 220V-st. Toiteallikana töötab mul vĂ€ike firma Tenstar Robot. Isiklikult on mul analoog- ja vĂ”imsuselektroonikaga kitsas, aga siin on valmis toiteplokk vĂ€ikese korpuse sees.
TööreĆŸiimide signalisatsiooniks olen ma ette nĂ€inud LED-i, mis on ĂŒhendatud GPIO2-ga. Kuigi ma ei hakkanud seda tööle panema, kuna ESP-07 moodulil on juba LED, mis on samuti ĂŒhendatud sama GPIO2-ga. Kuid plaadil olgu see â Ă€kki ma tahan selle LED-i korpusele viia.
Liigume kĂ”ige huvitavama juurde. Veearvestitel ei ole mingit loogikat, me ei saa kĂŒsida hetkeseisundeid. Ainus, mis meile kĂ€tte saadav on, on impulsid â kontaktide sulgemine iga liitri kohta. Minu geneetilised kontaktid on viidud GPIO12/ GPIO13. TĂ”mbav takisti lĂŒlitan programmikohas moodulisse.
Alguses unustasin, et peab olema R8 ja R9 takistid ning minu versioonil neid ei ole. Kuid kuna ma juba jagan skeemi ĂŒldiseks vaatamiseks, siis tasub see viga parandada. Takistid on vajalikud, et port ei kahjustuks, kui lĂ€hteprogramm hakkab takistama ning seadistab pinu, ja geneetilised kontaktid lĂŒhistavad selle joone maapinnaga (takisti korral voolab maksimaalselt 3,3V/1000Ω = 3,3mA).
On veidi mĂ”elda, mida teha, kui elekter kaob. Esimene variant on alguses kĂŒsida serverilt algseid arve vÀÀrtusi. Kuid see tooks endaga kaasa protokolli vahetuse mĂ€rkimisvÀÀrse keerukuse. Veelgi enam, seadmestiku töövĂ”ime sĂ”ltub sel juhul serveri seisundist. Kui pĂ€rast voolu katkestamist server ei kĂ€ivitu (vĂ”i kĂ€ivitub hiljem), ei saaks veearvesti algseid vÀÀrtusi kĂŒsida ja hakkaks valesti tööle.
SeetĂ”ttu otsustasin salvestada arvestite vÀÀrtused I2C kaudu ĂŒhendatud mĂ€lukiipi. Mul ei ole suurusele mĂ€lukiibile erilisi nĂ”udeid â tuleb salvestada vaid 2 numbrit (kuupmeetrite arv sooja ja kĂŒlma vee arvestites). Isegi vĂ€ikseim moodul sobib. Kuid kirjutamistsĂŒklite arvu tuleks tĂ€hele panna. Enamikul moodulitest on see 100 tuhat tsĂŒklit, mĂ”nel kuni miljon.
Tundub, et miljon on palju. Kuid nelja aasta jooksul, mil olen elanud oma korteris, olen tarbinud veidi ĂŒle 500 kuupmeetri vett, see on 500 tuhat liitrit! Ja 500 tuhat salvestust mĂ€llu. Ja see on ainult kĂŒlm vesi. Loomulikult vĂ”iksin kiipi iga paari aasta tagant ĂŒmber jootma, kuid selgus, et on olemas FRAM kiibid. Programmeerimise seisukohalt on see sama, mis I2C EEPROM, ainult vĂ€ga suure arvu kirjutamise tsĂŒklitega (sadu miljoneid). Siiski pole mul siiani olnud aega poodi minna, kus sellised kiibid on, seega jÀÀb tavaliselt 24LC512.
TrĂŒkkplaat
Alguses kavandasin plaadi kodustes tingimustes. SeetĂ”ttu projekteeriti plaat ĂŒhetasandilisena. Kuid veetnud tunni laserpooniga ja jootmismaskiga (ilma selleta ei ole kuidagi Ă”ige), otsustasin siiski tellida plaadid hiinlastelt.

Peaaegu enne plaadi tellimist mĂ”istsin, et lisaks mĂ€lukiibile vĂ”ib I2C bussi ĂŒhendada ka midagi muud kasulikku, nĂ€iteks ekraani. Mis just sellele kuvada â see on veel kĂŒsimus, kuid plaadi peal tuleb see Ă€ra teha. Noh, kuna plaanisin tellida plaate tehases, ei olnud enam mĂ”tet end ĂŒhetasandilise plaadiga piirata, seega on I2C read plaadi tagakĂŒljel ainukesed.
Ăhepoolse kaabliga oli seotud ka ĂŒks suur viga. Kuna plaat joonistati ĂŒhepoolne, plaaniti rajada takistid ja SMD komponendid ĂŒhele poole, samas kui vĂ€ljundkomponendid, pistikud ja toiteallikas teisele. Kui sain kuu aja pĂ€rast plaadid, unustasin algse plaani ja keevitasin kĂ”ik komponendid esiplaadi kĂŒlge. Ja ainult siis, kui jĂ”udsin toiteallika keevitamisele, selgus, et pluss ja miinus on vastupidised. Pidin improviseerima juhtmetega. Ălaltoodud pildil olen juba muutnud jaotust, kuid maa ĂŒhendatakse plaadi ĂŒhe osast teise lĂ€bi Boot-nupu vĂ€ljundite (kuigi oleks saanud teisel kihil kaala joonistada).
See vÀlja kukkus nii.

Korpus
JÀrgmine samm on kast. Kui sul on 3D printer, pole see probleem. Ma ei hakanud liiga palju vaeva nÀgema - lihtsalt joonistasin vajaliku suurusega kasti ja lÔikasin vajalikesse kohtadesse avad. Kate kinnitub korpusele vÀikeste kruvidega.

Olen juba maininud, et Boot-nuppu saab kasutada universaalse nupu kujul - viime selle esipaneelile. Selleks joonistasin spetsiaalse "aukude" koha, kus nupp asub.

Korpuse sees asuvad ka kĂ€nnud, kuhu plaat paigaldatakse ja mille kĂŒlge see kinnitatakse ĂŒhekordse M3 kruviga (plaadil polnud rohkem ruumi).
Ekraani valisin juba siis, kui printisin esimese prooviversiooni korpusest. Tavaline kahekohaline ekraan ei mahtunud sellesse korpusesse, aga varudest leidsin OLED ekraani SSD1306 128Ă32. Veidi vĂ€ike, aga mul pole teda iga pĂ€ev vaadata - sobib.
MĂ”eldes ja nii ja naa, kuidas juhtmed sellest lĂ€bi viia, otsustasin kleebida ekraani korpuse keskele. Ergonomika on loomulikult madal - nupp ĂŒlal, ekraan all. Kuid olen juba öelnud, et idee ekraani kinnitamiseks tuli liiga hilja ja oli laiskus plaati uuesti disainida, et nupp viia.
Seade on kokku pandud. Ekraanimoodul on liimitud kuuma liimiga.


LÔpptulemust saab nÀha KDPV-l.
Puhversioon
Liigume tarkvaralise osa juurde. Selliste vĂ€ikeste projektide jaoks meeldib mulle kasutada Python keelt ()- kood on vĂ€ga kompaktne ja arusaadav. Ănneks pole siin vaja liikuda registrite tasandile, et mikrosekundeid vĂ€lja pigistada - kĂ”ik on vĂ”imalik teha Pythonist.
Tundub, et kÔik on lihtne, aga tegelikult on seadmes mitu sÔltumatut funktsiooni:
- Kasutaja vajutab nuppu ja vaatab ekraanile
- Liitrid tikuvad ja vÀrskendavad vÀÀrtusi flash-mÀlus
- Modul jĂ€lgib WiFi-signaali ja vajadusel uuesti ĂŒhendab
- Aga vilkuva lambita ei saa kĂŒll hakkama
Ei saa lubada, et ĂŒks funktsioon ei töötaks, kui teine mingil pĂ”hjusel jÀÀb seisma. Olen muudes projektides saanud piisavalt kaktuseid, nĂŒĂŒd nĂ€en pidevalt tĂ”rkeid nagu "jĂ€tsime ĂŒhe liitri vahele, kuna just siis vĂ€rskendati ekraani" vĂ”i "kasutaja ei saa midagi teha, kuni moodul ĂŒhendub WiFi-ga". Muidugi, mĂ”ned asjad saab teha katkestuste kaudu, aga vĂ”ib tekkida piirangud kestuse, pesastamise, vĂ”i mitte aatomaarsete muutuste tĂ”ttu. Ja kood, mis tegeleb kĂ”igega korraga, muutub kiiresti pudruks.
Uues kasin klassikalist jaotavat mitmeĂŒlesandelist töötlemist ja FreeRTOS-i, kuid antud juhul osutus sobivamaks mudel . Ja Python'i kooruide rakendus on lihtsalt suurepĂ€rane â arendaja jaoks on kĂ”ik tehtud lihtsalt ja mugavalt. Kirjutad lihtsalt oma loogika, lihtsalt ĂŒtled, kus kohtades saab vahetada voogude vahel.
Jagatud ja konkurentsivĂ”imeline mitmeĂŒlesandeline töötlemine vĂ”iks olla valikuline uurimiseks. Aga nĂŒĂŒd, lĂ”puks, liigutame koodi juurde.
#####################################
# 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 foreverIga loenduri töötleb Counter klassi eksemplar. Esiteks lugesime EEPROM-ist (value_storage) loenduri algvÀÀrtuse â nii teeme energiakatkestuse jĂ€rgselt taastamise.
Pin initsialiseeritakse sisseehitatud tĂ”mbamisega toite poole: kui reed on suletud â on joon null, kui avatud, tĂ”mbab joon toite poole ja kontroller loeb ĂŒhte.
Samuti kĂ€ivitatakse siin eraldi ĂŒlesanne, mis kĂŒsib pinni. Iga loendur kĂ€ivitab oma ĂŒlesande. Siin on selle kood
""" KĂŒsige pinni ja edastage vÀÀrtus, kui teine liiter möödus """
async def _switchcheck(self):
last_checked_pin_state = self._pin.value() # Saage algne olek
# KĂŒsige pinni muutust
while True:
state = self._pin.value()
if state != last_checked_pin_state:
# Oleku muutus: toimige nĂŒĂŒd.
last_checked_pin_state = state
if state == 0:
self._another_litre_passed()
# Ignoreerige edasisi oleku muutusi, kuni lĂŒlitus on stabiliseerunud
await asyncio.sleep_ms(Counter.debounce_ms)25 ms viivitus on vajalik kontaktide krahhi filtreerimiseks ning reguleerib, kui sageli ĂŒlesanne Ă€rkab (kui see ĂŒlesanne magab, töötavad teised ĂŒlesanded). Iga 25 ms funktsioon Ă€rkab, kontrollib pinni ja kui hermeetilised kontaktid on suletud, tĂ€hendab see, et lĂ€bi arvesti on lĂ€binud jĂ€rgmine liiter ja see tuleb töödelda.
def _another_litre_passed(self):
self._value += 1
self._value_changed = True
self._value_storage.write(self._value)JĂ€rgmise liitri töötlemine on triviaalne â lihtsalt suurendatakse arvestit. Ja uus vÀÀrtus oleks hea ka mĂ€lule salvestada.
Kasutusmugavuse huvides on ette nÀhtud "juurdepÀÀsud"
def value(self):
self._value_changed = False
return self._value
def set_value(self, value):
self._value = value
self._value_changed = FalseAga nĂŒĂŒd kasutame Pythoni ja uasynci raamatukogu eeliseid ning teeme arvesti oodatavaks (kuidas seda eesti keelde tĂ”lkida? Seda, mida saab oodata?).
def __await__(self):
while not self._value_changed:
yield from asyncio.sleep(0)
return self.value()
__iter__ = __await__ See on mugav funktsioon, mis ootab, kuni arvesti vÀÀrtus uuendatakse â funktsioon Ă€rkab aeg-ajalt ja kontrollib _value_changed lippu. Selle funktsiooni eripĂ€ra on see, et vĂ€ljakutsuv kood vĂ”ib selle funktsiooni kutsumise ajal uinuda ja magada, kuni uus vÀÀrtus on saadud.
Aga kuidas on katkestustega?Jah, sel hetkel vĂ”ite mind naljatada, sest ma ĂŒtlesin katkestuste kohta, kuid tegelikult tegin ma lolli pinni kĂŒsitlust. Tegelikult on katkestused esimesed, mida ma proovisin. ESP8266-s on vĂ”imalik korraldada katkestust tĂ”usul ja isegi kirjutada katkestuse töötleja Pythoni keeles. Selles katkestuses saab muuta muutuja vÀÀrtust. Ilmselt oleks sellest piisavalt, kui arvesti oleks alluv seade â selline, mis ootab, kuni temalt kĂŒsitakse seda vÀÀrtust.
Kahjuks (vĂ”i Ă”nneks?) on minu seade aktiivne, see peab ise sisaldama sĂ”numeid MQTT protokolli kaudu ja salvestama andmeid EEPROM-i. Siin tulevad mĂ€ngu piirangud â katkestustes ei saa mĂ€lu eraldada ega kasutada suurt steki, mistĂ”ttu vĂ”ib unustada sĂ”numite saatmise vĂ”rgus. On selliseid kasulikke funktsioone nagu micropython.schedule(), mis vĂ”imaldavad kĂ€ivitada mĂ”nda funktsiooni âniipea kui see on vĂ”imalikâ, kuid tekib kĂŒsimus âkui palju sellest kasu on?â. Ăkki me saadame just praegu mĂ”ne sĂ”numi, ja siis sekkub katkestus ja rikub muutujate vÀÀrtused. VĂ”i nĂ€iteks, serverilt on tulnud uus arvu vÀÀrtus, kui me ei ole veel eelmist salvestanud. ĂhesĂ”naga, tuleb ehitada sĂŒnkroniseerimine vĂ”i leida mĂ”ni muu lahendus.
Ja aeg-ajalt tuleb ette RuntimeError: schedule stack full, ja kes seda teab, miks?
Kuid otsese kĂŒsitluse ja uasync-iga tundub see sel juhul kuidagi ilusam ja usaldusvÀÀrsem.
EEPROM-i töötlemise viis on vÀlja viidud vÀikese klassi.
class EEPROM():
i2c_addr = const(80)
def __init__(self, i2c):
self.i2c = i2c
self.i2c_buf = bytearray(4) # VÀltige puhverdamise loomist/ hÀvitamist iga kÔne jaoks
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)Pythoni keeles on otse baitidega töötamine keeruline, kuid just baitidesse salvestatakse mÀlu. Tuli vÀlja mÔelda konversioon tÀisarvu ja baitide vahel, kasutades ustruct raamatukogu.
Et iga kord mitte edastada I2C objekti ja mÀluaadressi, olen selle kogu kokku pakkinud vÀikese ja mugava klassina.
class EEPROMValue():
def __init__(self, i2c, eeprom_addr):
self._eeprom = EEPROM(i2c)
self._eeprom_addr = eeprom_addr
def read(self):
return self._eeprom.read(self._eeprom_addr)
def write(self, value):
self._eeprom.write(self._eeprom_addr, value)I2C objekt loomine toimub jÀrgmiste parameetritega.
i2c = I2C(freq=400000, scl=Pin(5), sda=Pin(4))JĂ”uame kĂ”ige huvitavamani â MQTT serveriga suhtlemise rakendamine. Protokolli ennast pole vaja rakendada â internetis on leidunud . Seda me hakkame kasutama.
Kogu huvitav osa on koondunud klassi CounterMQTTClient, mis pÔhineb raamatukogu MQTTClient-l. Alustame periferiast.
#####################################
# 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))Siin luuakse ja seadistatakse lambipirni ja nupu pins, samuti kĂŒlma ja kuuma vee arvestite objektid.
Alguses ei ole kÔik nii triviaalne
def __init__(self):
self.internet_outage = True
self.internet_outages = 0
self.internet_outage_start = ticks_ms()
with open("config.txt") as config_file:
config['ssid'] = config_file.readline().rstrip()
config['wifi_pw'] = config_file.readline().rstrip()
config['server'] = config_file.readline().rstrip()
config['client_id'] = config_file.readline().rstrip()
self._mqtt_cold_water_theme = config_file.readline().rstrip()
self._mqtt_hot_water_theme = config_file.readline().rstrip()
self._mqtt_debug_water_theme = config_file.readline().rstrip()
config['subs_cb'] = self.mqtt_msg_handler
config['wifi_coro'] = self.wifi_connection_handler
config['connect_coro'] = self.mqtt_connection_handler
config['clean'] = False
config['clean_init'] = False
super().__init__(config)
loop = asyncio.get_event_loop()
loop.create_task(self._heartbeat())
loop.create_task(self._counter_coro(self.cold_counter, self._mqtt_cold_water_theme))
loop.create_task(self._counter_coro(self.hot_counter, self._mqtt_hot_water_theme))
loop.create_task(self._display_coro())Mqtt_as teegi seadete mÀÀramiseks kasutatakse suurt sĂ”nastikku erinevate seadistustega â config. Enamik vaikeseadeid sobib meile, kuid paljusid seadeid tuleb mÀÀrata selgesĂ”naliselt. Et mitte seadeid otse koodis mÀÀrata, hoian need tekstifailis config.txt. See vĂ”imaldab muuta koodi sĂ”ltumatult seadistustest ning luua mitu sama seadet erinevate parameetritega.
Viimane koodiblokk kĂ€ivitab mitu korutiini erinevate sĂŒsteemi funktsioonide teenindamiseks. NĂ€iteks korutiin, mis teenindab loendureid.
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))Korutiin ootab tsĂŒkli jooksul uut loenduri vÀÀrtust ja niipea, kui see ilmub â saadab sĂ”numi MQTT protokolli kaudu. Esimene koodilĂ”ik saadab algvÀÀrtuse isegi siis, kui vesi loenduri kaudu ei voola.
PĂ”hiklass MQTTClient teenindab ennast ise, alustab WiFi ĂŒhendust ja uuendab ĂŒhendust, kui see katkeb. WiFi ĂŒhenduse oleku muutustest teavitab meid raamatukogu, kutsudes ĂŒles wifi_connection_handler.
async def wifi_connection_handler(self, state):
self.internet_outage = not state
if state:
self.dprint('WiFi on pĂŒsti.')
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 on maas.')
await asyncio.sleep(0)Funktsioon on ausalt öeldes kopeeritud nĂ€idetest. Antud juhul loendab see interneti katkestuste (internet_outages) arvu ja nende kestust. Ăhenduse taastumise korral saadetakse serverisse seisaku aeg.
Muide, viimane sleep on vajalik ainult selleks, et funktsioon muutuks asĂŒnkroonselt â raamatukogus kutsutakse seda vĂ€lja lĂ€bi await, samas vĂ”ivad kutsuda ainult funktsioonid, mille kehas on teine await.
Lisaks WiFi-le tuleb luua ĂŒhendus ka MQTT vahendajaga (serveriga). Sellega tegeleb samuti raamatukogu, ja meil on vĂ”imalus teha midagi kasulikku, kui ĂŒhendus on loodud.
async def mqtt_connection_handler(self, client):
await client.subscribe(self._mqtt_cold_water_theme)
await client.subscribe(self._mqtt_hot_water_theme)Siin registreerime end mitmete sĂ”numite jaoks â serveril on nĂŒĂŒd vĂ”imalus mÀÀrata jooksvad arvesti vÀÀrtused, saates vastava sĂ”numi.
def mqtt_msg_handler(self, topic, msg):
topicstr = str(topic, 'utf8')
self.dprint("Saadud MQTT sÔnumi teema={}, 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))See funktsioon töötleb saabunud sĂ”numeid, ja sĂ”ltuvalt teemast (sĂ”numi nimest) uuendatakse ĂŒhe arvesti vÀÀrtuseid.
Paari abifunktsiooni
# 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")See funktsioon tegeleb sĂ”numi saatmisega, kui ĂŒhendus on loodud. Kui ĂŒhendust pole â sĂ”numit ignoreeritakse.
Ja see on lihtsalt mugav funktsioon, mis koostab ja saadab silumiseks mÔeldud sÔnumeid.
async def publish_debug_msg(self, subtopic, msg):
await self.publish_msg("{}\{}".format(self._mqtt_debug_water_theme, subtopic), str(msg))
Nii palju teksti, ja me pole siiani LED-d vilkuma pannud. Siin on see.
# 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)Olen ette nĂ€inud 2 vilkumise reĆŸiimi. Kui ĂŒhendus kadus (vĂ”i see alles luuakse), vilgub seade kiiresti. Kui ĂŒhendus on loodud â seade vilgub korra 5 sekundi jooksul. Soovi korral saab siia rakendada ka teisi vilkumise reĆŸiime.
Aga LED on see lihtsalt lÔbutsemine. Oleme siiani tahvelarvuti peale ka lÀinud.
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)See, this is what I was talking about â how simple and convenient it is with coroutines. This small function describes ALL interaction with the user. The coroutine simply waits for a button press and activates the display for 3 seconds. The current readings of the counters are shown on the display.
There are still a couple of small things left. Hereâs the function that (re)starts all of this. The main loop is merely responsible for sending various debug information once a minute. In general, I'm providing it as is â I don't think it needs much commentary.
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)Well, a couple more settings and constants for completeness of description.
#####################################
# 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)This is how it all starts.
client = CounterMQTTClient()
loop = asyncio.get_event_loop()
loop.run_until_complete(client.main())Something has happened to my memory.
So, the entire code is here. I uploaded the files using the ampy utility â it allows uploading them to the internal flash storage (the one in the ESP-07) and then accessing them from the program like regular files. I also uploaded the libraries mqtt_as, uasyncio, ssd1306, and collections (used within mqtt_as).
We run it and⊠get a MemoryError. The more I tried to understand where exactly the memory was leaking, the earlier this error occurred as I placed debug prints. A quick Google search led me to realize that the microcontroller has only 30KB of memory, while 65KB of code (including libraries) simply cannot fit.
Kuid vĂ€ljapÀÀs on olemas. TĂ”epoolest, micropython ei kĂ€ivita koodi otse .py failist â see fail kompileeritakse esmalt. Veelgi enam, see kompileeritakse otse mikrokontrolleril, muutudes byte-koodiks, mida seejĂ€rel sĂ€ilitatakse mĂ€lus. Ja kompilatori tööks on samuti vajalik teatud hulk operatiivmĂ€lu.
Trikk seisneb selles, et vabastada mikrokontroller ressursimahukast kompileerimisest. Failid saab kompileerida suurel arvutil, ja mikrokontrollerisse laadida juba valmis byte-kood. Selleks tuleb alla laadida micropythoni pĂŒsivara ja koguda .
Ma ei hakanud kirjutama Makefile'i, vaid kÀisin kÀsitsi kÔik vajalikud failid (sealhulgas teegid) umbes nii kokku:
mpy-cross water_counter.pyJÀÀnud on ainult laadida failid .mpy laiendiga, unustamata eelnevalt kustutada vastavad .py seadme failisĂŒsteemist.
Kogu arendust tegin programmiga (IDE?) ESPlorer. See vÔimaldab skripte mikrokontrollerisse laadida ja neid kohe kÀivitada. Minu puhul on kogu loogika ja kÔigi objektide loomine failis water_counter.py (.mpy). Kuid et kÔik see automaatselt kÀivituks, peab olema ka fail nimega main.py. See peab olema just .py, mitte eelnevalt kompileeritud .mpy. Siin on selle triviaalne sisu.
import water_counterKĂ€ivitame â kĂ”ik töötab. Kuid vaba mĂ€lu on Ă€hvardavalt vĂ€he â umbes 1kb. Mul on veel plaane seadme funktsionaalsuse laiendamiseks ning sellest kilobaidist jÀÀb mulle selgelt vĂ€heks. Kuid selleks on samuti lahendus.
Asi on selline. Isegi kui failid on kompileeritud byte-koodiks ja asuvad sisemises failisĂŒsteemis, laaditakse need ikkagi operatiivmĂ€lu ja tĂ€idetakse sealt. Kuid osutub, et micropython oskab byte-koodi tĂ€ita otse flash-mĂ€lust, kuid selleks tuleb see otse pĂŒsivara sisse ehitada. See ei ole keeruline, kuigi minu netbookil vĂ”ttis see palju aega (seal oli mul Linux).
Algoritm on jÀrgmine:
- Laadi alla ja installi . See asi kogub ESP8266 jaoks mÔeldud tarkvara kompilaatorit ja teeke. See kogutakse projekti peamise lehe juhiste jÀrgi (valisin STANDALONE=yes installatsiooni).
- Laadi alla
- Vajalikud teegid tuleb asetada ports/esp8266/modules kausta micropythoni puus.
- Kogume pĂŒsivara vastavalt instruktsioonile failis
- Laadime pĂŒsivara mikrokontrollerisse (teen seda Windowsis programmide ESP8266Flasher vĂ”i Pythonis kirjutatud esptool'iga)
NĂŒĂŒd, kui âimport ssd1306â kĂ€ivitab koodi otse pĂŒsivara seest, ei kulu selleks enam operatiivmĂ€lu. Nii sain pĂŒsivara salvestada ainult teeki koodi, samal ajal kui peaeesmĂ€rk programmi kĂ€ivitatakse failisĂŒsteemist. See vĂ”imaldab mul programmi hĂ”lpsasti muuta ilma pĂŒsivara uuesti kompileerimata. Praegu on mul vabalt umbes 8,5 kB RAM-i. See vĂ”imaldab tulevikus lisada veel palju kasulikku funktsionaalsust. Kui mĂ€lu peaks tĂ”eliselt vĂ€heks jÀÀma, siis saan ka pĂ”hikoodi pĂŒsivara sisse suruda.
Ja mida nĂŒĂŒd sellega teha?
Okei, riistvara on kokku löödud, pĂŒsivara on kirjutatud, karp on vĂ€lja trĂŒkitud, seade on seina kĂŒlge kinnitatud ja rÔÔmsalt vilgub lamp. Aga kuni see on lihtsalt must kast (nii sĂ”nasĂ”nalises kui ka kujundlikus tĂ€henduses), on sellest veel vĂ€he kasu. On aeg tegeleda MQTT sĂ”numitega, mis saadetakse serverisse.
Minu 'nutikodu' töötab . MQTT modul on kas juba karbis olemas vĂ”i kergesti paigaldatav lisade turult â ei mĂ€leta, kust see mul tuli. MQTT on ise piisav ainult siis, kui on olemas nn maakler â server, mis vĂ”tab, sorteerib ja suunab MQTT sĂ”numid klientidele. Kasutan mosquitto, mis (nagu ka majordomo) töötab kĂ”ik samal netbook'il.
PÀrast seda, kui seade vÀhemalt korra sÔnumi saadab, ilmub vÀÀrtus kohe nimekirja.

Need vÀÀrtused saab nĂŒĂŒd siduda sĂŒsteemi objektidega, neid saab kasutada automatiseerimise stsenaariumites ja lĂ€bi viia erinevaid analĂŒĂŒse â kĂ”ik see ei kuulu selle artikli, fookuse alla. Kes on huvitatud majordomo sĂŒsteemist, sellele soovitan â sĂ”ber, kes samuti ehitab nutikat kodu ja selgitab sĂŒsteemi seadistamist arusaadavalt.
NÀitan vaid paar graafikut. See on lihtne ööpÀevase vÀÀrtuste graafik

On nĂ€ha, et öösel ei kasutanud keegi vett. Korduvalt kĂ€idi tualetis ja tundub, et pöördosmoosi filter imeb paar liitrit öö jooksul. Hommikuks suureneb tarbimine oluliselt. Tavaliselt kasutan vett boilerist, aga seekord soovisin vanni vĂ”tta ja lĂŒlitasin ajutiselt sisse linna kuuma vee â see on samuti alumisel graafikul hĂ€sti nĂ€htav.
Sellelt graafikult sain teada, et tualetti minek on 6-7 liitrit vett, duƥi all kÀimine 20-30 liitrit, nÔude pesemine umbes 20 liitrit ja vannitamseks on vaja 160 liitrit. Minu perel on pÀevas tarbimist umbes 500-600 liitrit.
Eriti uudishimulikele on saadaval kirjed iga eraldi vÀÀrtuse kohta.

Siit sain teada, et avatud kraanist voolab vesi kiiresti umbes 1 liiter 5 sekundi jooksul.
Kuid sellisel kujul on statistikat ilmselt ebamugav vaadata. Majordomos on veel vÔimalusi vaadata tarbimist graafikute jÀrgi pÀevade, nÀdalate ja kuude kaupa. NÀiteks, siin on tarbimise grafika veergudena.

Praegu on mul andmeid ainult nĂ€dalaks. Kuu aja pĂ€rast on see graafik informatiivsem â igale pĂ€evale vastab eraldi veerg. Andmete kĂ€sitsi korrektsioonid rikkusid natuke pilti (suurim veerg). Ja praegu ei ole selge, kas ma valisin esialgsete vÀÀrtuste koha pealt liiga madala arvu vĂ”i on see tarkvara viga ja mitte kĂ”ik liitrid arvesse ei lĂ€inud. Vajatakse rohkem aega.
Graafikute kallal tuleb veel töötada, valgendada ja vĂ€rvida. VĂ”ib-olla hakkan ka mĂ€lutarbimise graafiku koostamisega tegelema â Ă€kki seal lekib midagi. VĂ”ib-olla kavatsen kuvada ka perioode, mil internetti ei olnud. Praegu on kĂ”ik see ideetasemel.
KokkuvÔte
TĂ€na muutus mu korter natuke nutikamaks. Sellise vĂ€ikese seadmega on mul mugavam jĂ€lgida veetarbimist kodus. Kui varem hĂ€dasin, et "taas palju vett tarbitud", siis nĂŒĂŒd suudan leidmiseks selle tarbimise allika.
MÔned vÔivad leida kummalisena vaadata nÀidikute ekraanilt, kui see on arvestist meetri kaugusel. Kuid mitte vÀga kauges tulevikus plaanin ma elada teises korteris, kus on mitu veetoru ja arvestid asuvad tÔenÀoliselt trepikojas. Seega on kaugloetav seade tÔeliselt teretulnud.
Seadme funktsionaalsust kavatsema ka laiendada. Olen juba silmas pidanud mootorsulge. Praegu pean boileri- ja linnavee vahetamiseks kolme kraani keerama raskesti ligipÀÀsetavas niĆĄis. Oluliselt mugavam oleks seda teha ĂŒhe nupuvajutusega koos vastava mĂ€rgutulega. Ja muidugi on mĂ”istlik rakendada ka lekkekaitset.
Artiklis kirjeldan oma varianti seadme kohta, mis pĂ”hineb ESP8266-l. Minu arvates on mul saanud ĂŒsna huvitav variant firmware'ist micropythoniga koos korutinate kasutamisega - lihtne ja kena. Olen pĂŒĂŒdnud kirjeldada paljusid nĂŒansse ja vigu, millega ma kohtusin. VĂ”ib-olla kirjeldasin ma kĂ”ike liiga detailselt, isiklikult nagu lugeja oleksin ma pigem soovinud ĂŒle vaadata liigset, kui pĂ€rast jĂ€reldama, mida öelda ei jĂ”utud.
Nagu alati olen ma avatud konstruktiivsetele kriitilistele mÀrkustele.
Allikas: habr.com
