Kunagi olid koduautomaatika sĂŒsteemid, vĂ”i nagu neid sageli nimetatakse 'nutikodu', ÀÀrmiselt kallid ning neid said endale lubada vaid rikkaimad. TĂ€napĂ€eval on turul saadaval piisavalt taskukohaseid komplekte anduritega, nuppudega/lĂŒlititega ja tĂ€itemehhanismidega, et juhtida valgustust, pistikupesi, ventilatsiooni, veetarnet ja muid tarbijaid. I isegi kĂ”ige algajam DIY-entusiast saab endale lubada kokku panna seadmeid nutika kodu jaoks.

Tavaliselt pakutavad seadmed on kas andurid vĂ”i tĂ€itemehhanismid. Need vĂ”imaldavad hĂ”lpsasti teostada stsenaariume nagu 'liikumisanduri aktiveerimisel sĂŒĂŒdata tuli' vĂ”i 'vĂ€ljalĂŒlitaja ukselukus kustutab kogu korteri valguse'. Kuid telemeetria on olnud veidi kehv. Parimal juhul on see temperatuuri ja niiskuse graafik vĂ”i konkreetse pistikupesa hetkevĂ”imsus.
Hiljuti installisin endale veemÔÔturid impulssvĂ€ljundiga. Iga liitri juures, mis veemÔÔturist lĂ€bi voolab, aktiveerub magnetkontakt ja sulgeb kontakti. JÀÀnud on lihtsalt juhtmed ĂŒhendada ja proovida, kuidas sellest kasu saada. NĂ€iteks analĂŒĂŒsida veetarbimist tundide ja nĂ€dalapĂ€evade lĂ”ikes. Kui korteris on mitu veetĂ”stikut, on mugavam nĂ€ha kĂ”iki hetkeandmeid ĂŒhel ekraanil, kui roomata taskutest lamp kĂ€es.
Allpool on minu lahendus ESP8266 baasil, mis loendab impulsside arvu veemÔÔturitest ja edastab nÀidud MQTT kaudu nutikodu serverisse. Programmeerime micropythonis, kasutades uasyncio raamatukogu. Firmavahi loomisel olen kokku puutunud mitmete huvitavate probleemidega, millest rÀÀgin samuti selles artiklis. Alustame!
Skeem

Kogu sĂŒsteemi keskpunkt on ESP8266 mikrokontrollerimoodul. Alguses oli plaanis ESP-12, kuid minule jĂ€i defektne. pidin leppima ESP-07 mooduliga, mis oli saadaval. Ănneks on need nii vĂ€ljundite kui ka funktsionaalsuse poolest identsed, erinevus on ainult antennis â ESP-12-l on see sisse ehitatud, ESP-07-l aga vĂ€line. Isegi antennita pĂŒĂŒab WiFi signaal minu vannitoas normaalselt kinni.
Mooduli sidumine on standardne:
- reset-nupp koos tÔmbega ja kondensaatoriga (kuigi mÔlemad on juba mooduli sees olemas)
- enable-signaal (CH_PD) on ĂŒhendatud toite kĂŒlge
- GPIO15 on ĂŒhendatud maapinnaga. Seda on vaja ainult kĂ€ivitamishetkel, aga mul pole enam midagi sellele jalale kinnitada.
Mooduli jĂ”udmiseks programmeerimise reĆŸiimi tuleb GPIO2 ĂŒhendada maapinnaga ning mugavuse huvides on mul ette nĂ€htud Boot-nupp. Normaalolukorras tĂ”mmatakse see pinni toite kĂŒlge.
GPIO2 olekontrollitakse ainult töökĂ€igus alguses â toite sisselĂŒlitamisel vĂ”i kohe pĂ€rast taaskĂ€ivitamist. Sel juhul kas moodul kĂ€ivitub nagu tavaliselt vĂ”i lĂ€heb urchoogu reĆŸiimile. PĂ€rast kĂ€ivitamist saab seda vĂ€ljundit kasutada tavalise GPIO-na. Ja kuna seal juba on nupp, saab sellele kinnitada mĂ”ne kasuliku funktsiooni.
Programmeerimiseks ja tĂ”rkeotsinguks kasutan UART-i, mille viisin ribale. Kui on vaja, siis liitun lihtsalt USB-UART adapteriga. Peab lihtsalt meeles pidama, et moodul töötab 3.3V-l. Kui unustada adapteri lĂŒlitamine sellepingele ja anda 5V, siis moodul tĂ”enĂ€oliselt lĂ€heb katki.
Mul ei ole vannitoas elektriga probleeme â pistik on umbes ĂŒhe meetri kaugusel arvestitest, nii et toitan sealt 220V-st. Toiteallikana kasutab mul vĂ€ikest Tenstar Robotilt. Isiklikult on mul analoog- ja vĂ”imsuselektroonikaga keeruline, kuid siin on valmis toiteplokk vĂ€ikese korpuse sees.
Töötamise reĆŸiimide nĂ€itamiseks on mul ette nĂ€htud LED, mis on ĂŒhendatud GPIO2. Kuid ma ei hakanud seda paigaldama, kuna ESP-07 moodulis on juba LED, mis on ĂŒhendatud samuti GPIO2-le. Aga plaadil olgu see igaks juhuks â Ă€kki tahan selle LEDi korpusele viia.
Liigume kĂ”ige huvitavama juurde. Veearvestitel ei ole mingit loogikat, nende kĂ€est ei saa kĂŒsida praeguseid nĂ€ite. Ainus, mis meil saadaval on, on impulsid â kontaktide sulgemine reedimise korral iga liitri kohta. Reedid on mul ĂŒhendatud GPIO12/GPIO13. TĂ”mbamisresistori lĂŒlitan sisse programmikoodis moodulis.
Algselt unustasin ma ette nĂ€ha R8 ja R9 takistid ning minu plaadi versioonis ei ole neid. Aga kuna ma juba jagan skeemi ĂŒldiseks vaatamiseks, siis tasub see eksimus parandada. Takistid on vajalikud, et ei pĂ”letaks porti juhul, kui pĂŒsivara jĂ€tab vea ja seab pinni vÀÀrtuseks ĂŒhe, samas kui reed lĂŒhistab selle joone maapinnale (takistiga voolab maksimum 3.3V/1000Ω = 3.3mA).
On oluline mĂ”elda, mida teha, kui elekter peaks Ă€ra katkemas. Esimene variant oleks algvÀÀrtuste pĂ€rimine serverilt kĂ€ivitumise ajal. Kuid see tooks kaasa protokolli vahetuse oluliselt keerukamaks muutmise. Veelgi enam, seadme töökindlus sĂ”ltub sel juhul serveri olekust. Kui server elektri tĂ€ieliku kadumise jĂ€rel ei kĂ€ivituks (vĂ”i kĂ€ivituks hiljem), ei suudaks veemÔÔtja algvÀÀrtusi kĂŒsida ning töötaks valesti.
SeetĂ”ttu otsustasin realiseerida mÔÔtjate vÀÀrtuste salvestamise mĂ€lukiipis, mis on ĂŒhendatud I2C kaudu. Mul ei ole suuri nĂ”udeid mĂ€luflashi suuruse osas â tuleb salvestada vaid 2 numbrit (kuupmeetrid sooja ja kĂŒlma veemÔÔtjatelt). Isegi kĂ”ige vĂ€iksem moodul sobib. Kuid kirjutustsĂŒklite arvu osas tuleks tĂ€helepanu pöörata. Enamik mooduleid talub 100 tuhat tsĂŒklit, mĂ”ned kuni miljoni.
Tundub, et miljon on palju. Kuid nelja aasta jooksul, mil olen elanud oma korteris, olen tarbinud veidi ĂŒle 500 kuubi vett, see on 500 tuhat liitrit! Ja 500 tuhat kirjet mĂ€lupulgale. Ja see on ainult kĂŒlm vesi. VĂ”ib muidugi mikroskeemi iga paar aasta tagant ĂŒmber jootma hakata, aga selgus, et eksisteerivad FRAM mikroskeemid. Programmeerimise seisukohalt on see sama, mis I2C EEPROM, ainult et vĂ€ga suure arvu kirjutamisringidega (sadu miljonit). Ainult et olen siiani poodi, kus neid mikroskeeme mĂŒĂŒakse, jĂ”udnud, seega seisab tavaline 24LC512 veel edasi.
TrĂŒkkplaat
Alguses plaanisin plaati kodus teha. SeetĂ”ttu projekteeriti plaat ĂŒhesuunalisena. Aga pĂ€rast tundi toimetamist lasertriikraudi ja jootemaskiga (ilma selleta ei ole see kuidagi comme il faut), otsustasin ikkagi tellida plaadid hiinlastelt.

Peaaegu tellimuse esitamise hetkel mĂ”istsin, et lisaks flash-mĂ€chipile saab I2C bussile ĂŒhendada midagi muud kasulikku, nĂ€iteks ekraani. Mis tĂ€pselt sellele vĂ€lja tuua â on veel kĂŒsimus, kuid lahti tuleb see plaadil teha. Kui ma aga otsustasin tellida plaadid tehases, siis ei olnud mĂ”tet end piirata ĂŒhepoolse plaadiga, seega I2C read on ainsad plaadi tagakĂŒljel.
Ăhepoolse joonisega seondus ka ĂŒks suur viga. Kuna plaat oli joonistatud ĂŒhepoolne, siis kavandati, et joad ja SMD-komponendid paigutatakse ĂŒhele poole ning vĂ€ljundkomponendid, pesad ja toiteallikas teisele. Kui sain plaadid kĂ€tte kuu aega hiljem, unustasin ma algse plaani ja paigaldasin kĂ”ik komponendid esipaneelile. Ja ainult siis, kui jĂ”udsin toiteblokki jootma, selgus, et pluss ja miinus on vastupidiselt joonistatud. Tuli improviseerida hĂŒpikĂŒngastega. Ălaltoodud pildil olen ma juba joonistust muutnud, kuid maaĂŒhendust viiakse ĂŒhelt plaadi poolt teisele lĂ€bi Boot-nupu vĂ€ljundeid (kuigi oleks vĂ”inud ka teisel kihil joont tĂ”mmata).
Saime midagi sellist

Korpus
JĂ€rgmine samm on korpus. 3D-printeri olemasolul ei ole see probleem. Ei hakanud liiga keeruliseks minema â lihtsalt joonistasin vajaliku suurusega kasti ja tegin vajalikud lĂ”iked. Ălemine kaas kinnitub korpusele vĂ€ikeste kruvidega.

Olen juba maininud, et Boot-nuppu saab kasutada universaalse nupuna â seega toome selle esipaneelile. Selleks joonistasin spetsiaalse 'auk' nuppude jaoks.

Korpuse sees on ka jupid, kuhu plaat kinnitatakse ja fikseeritakse ĂŒhe M3 kruviga (plaadil ei olnud rohkem ruumi).
Valisin ekraani, kui olin vĂ€lja prindinud esimese nĂ€idisversiooni korpusest. Tavaline kaherealine ekraan ei mahtunud sellesse, kuid leidsin kogudest OLED-ekraani SSD1306 128Ă32. Veidi vĂ€ike, aga mulle ei meeldi sellega iga pĂ€ev vahtida â sobib kĂŒll.
MĂ”eldes ja pĂŒĂŒdes vĂ€lja mĂ”elda, kuidas saab juhtmeid juhtida, otsustasin kinnitada ekraani korpuse keskele. Ergonomika on muidugi madal â nupp on peal, ekraan all. Aga olen juba öelnud, et idee ekraani lisamiseks tuli liiga hilja ja ei viitsinud plaati ĂŒmber tĂ”mmata, et nuppu paigutada.
Kokku pandud seade. Ekraanimoodul on liimitud termokleebi abil.


LÔpptulemust on vÔimalik nÀha KDPV-s.
KĂ€ivitus
Liigume programmi osa juurde. Selliste vĂ€ikeste projekti jaoks meeldib mulle vĂ€ga kasutada Pythonit () - kood muutub vĂ€ga kompaktseks ja arusaadavaks. Ănneks ei ole siin vaja sĂŒveneda registritase, et vĂ€lja pigistada mikrosekunde â kĂ”ik on tehtav Pythonist.
Paistab, et kÔik on lihtne, aga mitte pÀris - seadmes on mitu iseseisvat funktsiooni:
- Kasutaja klikkib nuppu ja vaatab ekraanile.
- Liitrite vÀÀrtused uuenevad ja salvestatakse flash-mÀllu.
- Moodul jĂ€lgib WiFi signaali ja vajadusel uuesti ĂŒhendub.
- Ilma vilkuva lambita ei saa hakkama.
Ei saa lubada, et ĂŒks funktsioon ei tööta, kui teine mingil pĂ”hjusel takerdub. Olen juba ĂŒtlematult palju kaktusi söönud teistes projektides ja nĂ€en nĂŒĂŒd pidevalt vigu, nagu "hĂ€vis jĂ€rgmine liiter, sest sel ajal uuendati ekraani" vĂ”i "kasutaja ei saa midagi teha, kuni moodul on WiFi-ga ĂŒhendatud". Muidugi, mĂ”ningaid asju saab teha katkestustega, kuid vĂ”ib tekkida piiranguid kestuse, sĂŒgava lahenduse vĂ”i mitteatomaarsete muutuste osas muutujates. Ja kood, mis tegeleb kĂ”igega korraga, muutub kiiresti mĂ”nusaks pudruks.
V kasutasin klassikalist preemsidega korraga tĂ€itmist ja FreeRTOS-i, kuid antud juhul osutus sobivamaks mudel . Ning Python'i kooride teostus on lihtsalt suurepĂ€rane â kĂ”ik on programmerimisest lihtsalt ja mugavalt tehtud. Lihtsalt kirjuta oma loogika, lihtsalt ĂŒtle, kus kohtades saab voogude vahel vahetada.
VĂ”ite uurida erinevusi preemsidega ja konkurentsivĂ”imelisuse vahel valikuliselt. Kuid nĂŒĂŒd liigume lĂ”puks 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 loendur töödeldakse Counter klassi nĂ€itega. Esmalt loetakse EEPROM-ist (value_storage) vĂ€lja loenduri algvÀÀrtus â nii toimub taastamine pĂ€rast voolu katkestamist.
Pinna initsialiseeritakse sisseehitatud toite tĂ”mbe abil: kui andur on suletud, on joon null, kui avatud, tĂ”mbab joon toite poole ja kontroller loeb ĂŒhte.
Siin kĂ€ivitatakse ka eraldi ĂŒlesanne, mis viib lĂ€bi pinni kĂŒsitluse. Iga loendur kĂ€ivitab oma ĂŒlesande. Siin on selle kood:
""" KĂŒsi pinni ja suurenda vÀÀrtust, kui teine liiter on möödunud """
async def _switchcheck(self):
last_checked_pin_state = self._pin.value() # Hangi algne olek
# KĂŒsitle pinni muutust
while True:
state = self._pin.value()
if state != last_checked_pin_state:
# Oleku muutus: tegutse nĂŒĂŒd.
last_checked_pin_state = state
if state == 0:
self._another_litre_passed()
# Ignoreeri edasisi oleku muutusi, kuni lĂŒlitus on paika loksunud
await asyncio.sleep_ms(Counter.debounce_ms)25 ms viivitus on vajalik kontaktide mĂŒratĂ”rjumiseks ning see reguleerib ka seda, kui tihti ĂŒlesanne Ă€rkab (kui see ĂŒlesanne magab â töötavad teised ĂŒlesanded). Iga 25 ms jooksul Ă€rkab funktsioon, kontrollib pinda ja kui hermeetikud on suletud, tĂ€hendab see, et ĂŒle loendur on kulgenud jĂ€rgmine liiter, mis 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 loenduri vÀÀrtust. Ja oleks hea, kui uus vÀÀrtus salvestataks ka mĂ€lupulgale.
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 = FalseNĂŒĂŒd kasutame Python'i ja raamatukogu uasync eeliseid ning teeme loenduri objektist ootava (kuidas seda eesti keelde tĂ”lkida? Ootava objekti?).
def __await__(self):
while not self._value_changed:
yield from asyncio.sleep(0)
return self.value()
__iter__ = __await__ See on nii mugav funktsioon, mis ootab, kuni loenduri vÀÀrtus uuendatakse â funktsioon Ă€rkab aeg-ajalt ja kontrollib mĂ€rklauale _value_changed. Selle funktsiooni juures on see, et vĂ€ljakutsuv kood vĂ”ib jÀÀda magama, kui seda funktsiooni kutsutakse, ja magada kuni uue vÀÀrtuse saamiseni.
Aga kuidas on katkestustega?Jah, siin kohal vĂ”ite mind tĂŒssata, et ma ju ĂŒtlesin katkestustest, aga tegelikult korraldasin ma rumala pinni kĂŒsitluse. Tegelikult on katkestused esimesed, mida proovisin. ESP8266-s saab korraldada katkestuse ÀÀrtes, ja isegi kirjutada selle katkestuse töötlejat pythoni keeles. Selle katkestuse abil saab vĂ€rskendada muutuja vÀÀrtust. TĂ”enĂ€oliselt piisaks sellest, kui loendur oleks orienteeritud seadmest â sellisest, mis ootab, kuni temalt kĂŒsitakse seda vÀÀrtust.
Kahjuks (vĂ”i Ă”nneks?) on minu seade aktiivne, see peaks ise saatma sĂ”numeid MQTT protokolli kaudu ja salvestama andmeid EEPROM-i. Siin tulevad mĂ€ngu piirangud â katkestustes ei saa mĂ€lu eraldada ja kasutada suurt steki, seega vĂ”ime unustada sĂ”numite saatmise ĂŒle vĂ”rgu. On olemas sellised kasulikud funktsioonid nagu micropython.schedule(), mis vĂ”imaldavad kĂ€ivitada mingit funktsiooni 'niipea kui vĂ”imalik', kuid tekib kĂŒsimus 'mis mĂ”tet on?'. Ăkki saadame just praegu mingi sĂ”numi ja siia vahele tuleb katkestus, mis rikub muutujate vÀÀrtused. VĂ”i nĂ€iteks, serverilt tuli uus vÀÀrtus, enne kui me vanad kirjutamise lĂ”petasime. ĂhesĂ”naga, tuleb korraldada sĂŒnkroonimine vĂ”i leida mĂ”ni muu lahendus.
Ja aeg-ajalt ilmneb RuntimeError: schedule stack full ja kes seda teab, miks?
Selge kĂŒsimise ja uasync korral toimib see sel juhul kuidagi ilusamalt ja usaldusvÀÀrsemalt.
EEPROM-i töö olen viinud vÀiksemasse klassi.
class EEPROM():
i2c_addr = const(80)
def __init__(self, i2c):
self.i2c = i2c
self.i2c_buf = bytearray(4) # VÀltige puhverloomist / -hÀvitamist iga kutsumise ajal
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 otstarbel on otstarbel töötada baitidega, kuid just need baidid salvestatakse mÀllu. Pidin looma konversiooni tÀisarvu ja baitide vahel kasutades ustruct'i teeki.
Kuna ma ei tahtnud iga kord edasi anda I2C objekti ja mÀluaadressi, panin kÔik kokku vÀikseks ja mugavaks klassiks.
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 luuakse jÀrgmiste parameetritega
i2c = I2C(freq=400000, scl=Pin(5), sda=Pin(4))JĂ”uame kĂ”ige huvitavama juurde â suhtlemise elluviimine serveriga MQTT kaudu. Protokolli rakendamine pole vajalik â internetis leidub . Kasutame seda.
KÔik huvitav on kogutud klassis CounterMQTTClient, mis pÔhineb teegil MQTTClient. Algame perifeeriast
#####################################
# 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 lambipirnide ja nuppude jalad, samuti kĂŒlma ja sooja vee loendurite objektid.
Algatamine ei ole 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 seadistuste mÀÀramiseks kasutatakse suurt erinevate seadistuste sĂ”nastikku â config. Enamik vaike seadistusi sobib meile, kuid paljusid seadistusi tuleb seada selgelt. Et mitte kirjutada seadistusi otse koodi, hoian neid tekstifailis config.txt. See vĂ”imaldab muuta koodi sĂ”ltumatult seadistustest ja samuti paljundada mitmeid identseid seadmeid erinevate parameetritega.
Viimane koodiblokk kĂ€ivitab mitmeid koorute, mis teenindavad sĂŒsteemi erinevaid funktsioone. NĂ€iteks on siin kooruta, mis teenindab loendureid.
async def _counter_coro(self, counter, topic):
# Avalda algvÀÀrtus
value = counter.value()
await self.publish(topic, str(value))
# Avalda iga uus vÀÀrtus
while True:
value = await counter
await self.publish_msg(topic, str(value))Kooruta ootab tsĂŒklis uut loenduri vÀÀrtust ja kohe kui see ilmub â saadab sĂ”numi MQTT protokolli kaudu. Esimene koodilĂ”ik saadab algvÀÀrtuse isegi siis, kui vesi loenduri kaudu ei voola.
MQTTClient pĂ”hiklass hooldab end ise, alustades WiFi-ĂŒhenduse loomist ja taasalustades ĂŒhenduse katkemisel. WiFi-ĂŒhenduse oleku muutumise korral teavitab meid raamatukogu funktsioon wifi_connection_handler.
async def wifi_connection_handler(self, state):
self.internet_outage = not state
if state:
self.dprint('WiFi on ĂŒles.')
duration = ticks_diff(ticks_ms(), self.internet_outage_start) / 1000
await self.publish_debug_msg('Taasiseseisvumine', duration)
else:
self.internet_outages += 1
self.internet_outage_start = ticks_ms()
self.dprint('WiFi on all.')
await asyncio.sleep(0)Funktsioon on otse nĂ€idetest ĂŒle vĂ”etud. Antud juhul loendab see katkestuste (internet_outages) arv ja nende kestvuse. Ăhenduse taastumisel saadetakse serverisse seisaku aeg.
Muide, viimane sleep on vajalik ainult selleks, et funktsioon oleks asĂŒnkrooniline â raamatukogus kutsutakse seda vĂ€lja await kaudu, samas vĂ”ivad ainult need funktsioonid, mille kehas on teine await, vĂ€lja kutsuda.
Lisaks WiFi-ĂŒhendusele tuleb luua ka ĂŒhendus MQTT brokeriga (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 tellime mitu teadet â serveril on nĂŒĂŒd vĂ”imalus mÀÀrata praegused arvnĂ€itajad, saates vastava sĂ”numi.
def mqtt_msg_handler(self, topic, msg):
topicstr = str(topic, 'utf8')
self.dprint("Saadud MQTT sÔnumi teema={}, sÔnum={}".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 teema (sĂ”numi nimi) alusel uuendatakse ĂŒhe arvnĂ€itaja vÀÀrtusi.
MÔned abifunktsioonid
# 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 saadab sĂ”numi, kui ĂŒhendus on loodud. Kui ĂŒhendust ei ole â sĂ”numit eiratakse.
Ja see on lihtsalt mugav funktsioon, mis koostab ja saadab silumis-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, aga me pole ikka veel LED-tuld vilgutanud. Siin see on.
# 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)Ma olen loonud 2 vilkumise reĆŸiimi. Kui ĂŒhendus on kadunud (vĂ”i just loodi), vilgub seade kiiresti. Kui ĂŒhendus on loodud, vilgub seade iga 5 sekundi jĂ€rel. Vajadusel saab siia rakendada ka muid vilkumise reĆŸiime.
Aga LED on siiski vaid ĂŒks lĂ”bu. Me oleme ju plaaninud ka ekraani.
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 ongi see, millest ma rÀÀkisin â kui lihtne ja mugav on kasutada korutine. See vĂ€ike funktsioon kirjeldab KĂIKI kasutajaga suhtlemist. Korutine lihtsalt ootab nupu vajutamist ja lĂŒlitab ekraani sisse 3 sekundiks. Ekraanil kuvatakse praegused arvestite nĂ€idud.
JÀÀnud on veel paar pisiasja. Siin on funktsioon, mis kĂ”ike seda (taas)kĂ€ivitab. Peamine tsĂŒkkel saadab vaid erinevat silumisinfo kord kuus. ĂhesĂ”naga, toon nagu on â eraldi kommenteerimist, ma arvan, pole vaja.
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)Noh, veel paar seadistust ja konstantide tÀiendamiseks
#####################################
# 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)KÀivitame kÔik nii
client = CounterMQTTClient()
loop = asyncio.get_event_loop()
loop.run_until_complete(client.main())Minu mÀlu tundub kummaline
Nii, kogu kood on olemas. Failid laadisin ĂŒles ampy utiliidi abil â see vĂ”imaldab neid laadida ESP-07 sise-flash'isse ja seejĂ€rel pÀÀseda programmiga ligi nagu tavalistele failidele. Sinna laadisin ka oma kasutatavad raamatukogud mqtt_as, uasyncio, ssd1306 ja collections (kasutatakse mqtt_as sees).
KĂ€ivitame ja⊠Saame MemoryError'i. Mida rohkem ĂŒritasin vĂ€lja selgitada, kus tĂ€pselt mĂ€lu lekib, seda varem see viga ilmus. LĂŒhike googeldamine viis mind arusaamiseni, et mikrokontrolleris on kokku 30 kB mĂ€lu, kuhu 65 kB koodi (koos raamatukogudega) kuidagi ei mahtuda.
Kuid vĂ€ljapÀÀs on olemas. Selgub, et micropython ei tĂ€ida koodi otse .py failist â see fail kompileeritakse esmalt. Ja see kompileeritakse otse mikrokontrolleril, muundudes baitkoodiks, mis siis mĂ€lu salvestatakse. Noh, ja kompilatori tööks on samuti vajalik teatud kogus töömĂ€luga.
Trikk seisneb ressursimahukast kompileerimisest vabastamises. Saate faile kompileerida suurel arvutil ja laadida valmis bytecode mikrokontrollerisse. Selleks peate alla laadima Micropythoni pĂŒsivara ja koguma. .
Ma ei hakanud kirjutama Makefile'i, vaid lÀksin kÀsitsi ja kompileerisin kÔik vajalikud failid (sealhulgas raamatukogud) umbkaudu nii.
mpy-cross water_counter.pyJÀÀnud on ainult laadida failid .mpy laiendiga, unustamata eelnevalt eemaldada vastavad .py seadme failisĂŒsteemist.
Kogu arendust tegin programmis (IDE?) ESPlorer. See vÔimaldab laadida skripte mikrokontrollerisse ja neid kohe kÀivitada. Minu puhul asub kogu loogika ja kÔik objektide loomine failis water_counter.py (.mpy). Kuid selle automaatseks kÀivitamiseks 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 mĂ€lu on ohtlikult vĂ€he â ligikaudu 1 kb. Mul on veel plaanid seadme funktsionaalsuse laiendamiseks, ja see kilobait ei ole mulle selgelt piisav. Kuid selgub, et ka selle jaoks on lahendus.
Asi on selles. Isegi kui failid on kompileeritud baitkoodiks ja asuvad sisemisel failisĂŒsteemil, laaditakse nad ikkagi korralikult operatiivmĂ€lu ja tĂ€idetakse sealt. Kuid osutub, et micropython oskab tĂ€ita baitkoodi otse vĂ€lkmĂ€lust, kuid selleks tuleb see otse tarkvarale sisse ehitada. See ei ole keeruline, kuigi minu netbookis vĂ”ttis see mĂ€rkimisvÀÀrselt aega (ainult seal oli mul Linux).
Algoritm on selline:
- Laadi alla ja installi . See tööriist kogub kompilaatori ja raamatukogud ESP8266 programmide jaoks. Kogumine toimub projekti peamise lehe juhendi jÀrgi (valisin STANDALONE=yes installatsiooni).
- Laadi alla
- Vajadusel raamatukogud paigutada ports/esp8266/modules kausta micropythonis.
- Kogume tarkvara vastavalt juhendile failis.
- Laime tarkvara mikrokontrollerisse (teen seda Windowsis programmide ESP8266Flasher vĂ”i Pythoniga esptoolâiga).
NĂŒĂŒd, kui âimport ssd1306â koormab koodi otse pĂŒsivara, ei kulu operatiivmĂ€lu selleks. Nii sain pĂŒsivara lihtsalt raamatukogude koodi, samas kui programmi pĂ”hikood töötab failisĂŒsteemist. See vĂ”imaldab mul hĂ”lpsasti programmi muuta ilma pĂŒsivara uuesti kompileerimata. Praegu on mul vabalt umbes 8,5 kB RAM-i. See vĂ”imaldab tulevikus rakendada veel palju erinevaid kasulikke funktsioone. Aga kui mĂ€lu vĂ€heneb, siis vĂ”ib ka pĂ”hiprogrammi pĂŒsivara peale tĂ”ukuda.
Ja mida nĂŒĂŒd sellega teha?
Olgu, riistvara on kokku joodetud, pĂŒsivara on kirjutatud, kast on trĂŒkitud, seade on seinale kinnitatud ja vilgub rÔÔmsalt. Aga seni on see kĂ”ik must kast (otseses ja ĂŒlekantud tĂ€henduses) ja sellest on veel vĂ€he kasu. On aeg midagi teha MQTT sĂ”numitega, mis saadetakse serverisse.
Minu "nutikodu" töötab . MQTT moodul on kas juba olemas vĂ”i hĂ”lpsasti paigaldatav lisandite turult â ei mĂ€leta tĂ€pselt, kust see mul on. MQTT ei ole iseseisev â vajalik on nn vahendaja â server, mis vĂ”tab vastu, sorteerib ja suunab MQTT sĂ”numid klientidele. Kasutan mosquitto, mis (nagu ka majordomo) töötab samal netbookil.
PÀrast seda, kui seade on korra sÔnumi saatnud, kuvatakse vÀÀrtus kohe nimekirjas.

Neid vÀÀrtusi saab nĂŒĂŒd siduda sĂŒsteemi objektidega, neid saab kasutada automatiseerimise stsenaariumites ja erinevate analĂŒĂŒsideks â kĂ”ik see ei ole antud artikli teema. Neile, kes on huvitatud majordomo sĂŒsteemist, soovitan â sĂ”ber ehitab samuti nutikat kodu ja selgitab sĂŒsteemi seadistamist arusaadavalt.
NÀitan vaid paar graafikut. See on lihtne pÀevane vÀÀrtuste graafik.

On nĂ€ha, et öösel vett peaaegu ei kasutata. MĂ”ned korrad kĂ€idi vetsus ja nĂ€ib, et pöördfiltri kulutused on paar liitrit öö jooksul. Hommikul tarbimine tĂ”useb mĂ€rgatavalt. Tavaliselt kasutan ma vett boilerist, kuid seekord tahtsin vannis kĂ€ia ning lĂŒlitasin ajutiselt linnakuumavee peale â seda on ka alumiselt grafikult hĂ€sti nĂ€ha.
Sellest graafikust sain teada, et vetsus kÀimine tÀhendab 6-7 liitrit vett, duƥi all kÀimine 20-30 liitrit, nÔudepesemine umbes 20 liitrit, kuid vannis kÀimiseks kulub 160 liitrit. PÀeva jooksul tarbib mu pere kuskil 500-600 liitrit.
Eriti uudishimulikele on vÔimalik vaadata iga eraldi vÀÀrtuse kirjeid.

Siit sain teada, et avatud kraanist voolab vesi umbes 1 liitri kiirusel 5 sekundi jooksul.
Aga sellisel kujul on statistikat ilmselt ebamugav vaadata. Majordomos on ka vÔimalusi vaadata tarbimisgraafikuid pÀevade, nÀdalate ja kuude kaupa. NÀiteks, siin on tarbimisgraafik veergudena.

Praegu on mul andmeid ainult nĂ€dala kohta. Kuu pĂ€rast nĂ€eb see graafik vĂ€lja informatiivsem â iga pĂ€ev vastab eraldi baarile. Veidi rikub pilti vÀÀrtuste korrektuur, mida ma kĂ€sitsi sisestan (suurim baar). Ja praegu ei ole selge, kas ma mÀÀrasin esialgsed vÀÀrtused vale, peaaegu kuubiku vĂ”rra madalamaks, vĂ”i on see tarkvara tĂ”rge ja mitte kĂ”ik liitrid lĂ€ksid arvesse. Vajan rohkem aega.
Graadikud vajavad veel kohendamist, valgendamist, vĂ€rvimist. VĂ”ib-olla hakkan ka katsetamise eesmĂ€rgil looma mĂ€lu kasutamise graafikut â Ă€kki seal lekkib midagi. VĂ”ib-olla nĂ€itan kuidagi perioode, mil internet puudus. Praegu on kĂ”ik see veel idee tasemel.
KokkuvÔte
TĂ€na sai minu korter veidi nutikamaks. Sellise vĂ€ikese seadmega on mul mugavam jĂ€lgida vee tarbimist minu kodus. Kui varem olin ma hĂ€iritud, et 'taas oleme kuu jooksul liiga palju vett kasutanud', siis nĂŒĂŒd suudan ma leida selle tarbimise allika.
MÔnele vÔib tunduda veider vaadata nÀitu ekraanilt, kui ta on meetri kaugusel loendist. Kuid mitte vÀga kauges tulevikus plaanin kolida teise korterisse, kus on mitu veetoru ning loendid asuvad tÔenÀoliselt trepikojas. Seega on eemalolekute nÀitude eemaldamise seade vÀgagi teretulnud.
Seadme funktsionaalsust plaanin samuti laiendada. Olen juba silma peal hoidnud mootorradil ventiilidel. Praegu pean boileri ja linna vee vahetamiseks keerama 3 kraani raskesti ligipÀÀsetavas niĆĄis. Oluliselt mugavam oleks seda teha ĂŒhe nupuvajutusega koos vastava indikaatoriga. Loomulikult tasub samuti teostada lekke kaitse.
Artiklis tutvustan oma varianti seadme kohta, mis pĂ”hineb ESP8266-l. Minu arvates on mul vĂ€lja tulnud ĂŒsna huvitav mikropython versiooni, mis kasutab korutine â lihtne ja kena. Olen pĂŒĂŒdnud kirjeldada palju nĂŒansse ja vigu, millega kohtusin teel. VĂ”ib-olla kirjeldasin ma liiga detailselt, isiklikult oleks mul kui lugejal lihtsam ĂŒle vaadata liigne, kui hiljem mĂ”elda sellele, mis jĂ€i ĂŒtlemata.
Nagu alati, olen konstruktiivseks kriitikaks avatud.
Allikas: habr.com
