Eartiids wiene hûsautomatisaasjesystemen, of "smart home" sa't se faaks neamd waarden, ôfgryslik djoer en allinnich de riken koene se har betelje. Hjoed op 'e merk kinne jo fine frij goedkeape kits mei sensoren, knoppen / switches en actuators foar it kontrolearjen fan ferljochting, sockets, fentilaasje, wetter oanbod en oare konsuminten. En sels de meast krom DIY persoan kin belutsen wurde by skientme en gearstalle apparaten foar in tûke hûs tsjin in goedkeape priis.

Typysk binne de foarstelde apparaten of sensoren as actuators. Se meitsje it maklik om senario's út te fieren lykas "as in bewegingssensor wurdt trigger, de ljochten oan" of "de skeakel by de útgong docht de ljochten út yn it heule appartemint." Mar op ien of oare manier wurken dingen net mei telemetry. Op syn bêst is it in grafyk fan temperatuer en fochtigens, as instantane krêft by in spesifike outlet.
Ik haw koartlyn wettermeters ynstalleare mei pulsútfier. Foar elke liter dy't troch de meter giet, wurdt de reidschakelaar aktivearre en slút it kontakt. It iennichste dat oerbleaun is, is om oan 'e triedden te hingjen en te besykjen om der foardiel fan te krijen. Analysearje bygelyks wetterferbrûk troch oere en dei fan 'e wike. No, as d'r ferskate wetterriders yn it appartemint binne, dan is it handiger om alle aktuele yndikatoaren op ien skerm te sjen dan te klimmen yn dreech te berikken nissen mei in zaklamp.
Under de besuniging is myn ferzje fan in apparaat basearre op ESP8266, dat telt pulses fan wetter meter en stjoert lêzingen fia MQTT nei de smart home tsjinner. Wy sille programmearje yn mikropython mei de uasyncio-bibleteek. By it meitsjen fan de firmware kaam ik ferskate nijsgjirrige swierrichheden tsjin, dy't ik ek sil besprekke yn dit artikel. Go!
De regeling

It hert fan it heule circuit is in module op 'e ESP8266 mikrocontroller. ESP-12 wie oarspronklik pland, mar mines die bliken te wêzen defect. Wy moasten tefreden wêze mei de ESP-07-module, dy't beskikber wie. Gelokkich binne se itselde sawol yn termen fan pinnen as funksjonaliteit, it ienige ferskil is yn 'e antenne - de ESP-12 hat in ynboude, wylst de ESP-07 in eksterne hat. Sels sûnder in WiFi-antenne wurdt it sinjaal yn myn badkeamer lykwols normaal ûntfongen.
Standert module wiring:
- reset knop mei pull-up en capacitor (hoewol't beide binne al binnen de module)
- It aktivearjen sinjaal (CH_PD) wurdt lutsen oan macht
- GPIO15 wurdt nei de grûn lutsen. Dit is allinnich nedich by it begjin, mar ik haw noch neat te heakjen oan dizze skonk Ik haw it net mear nedich
Om de module yn firmware-modus te setten, moatte jo GPIO2 koartslute nei grûn, en om it handiger te meitsjen, haw ik in Bootknop levere. Yn normale tastân wurdt dizze pin oan 'e macht lutsen.
De steat fan 'e GPIO2-line wurdt allinich kontrolearre oan it begjin fan' e operaasje - as macht wurdt tapast of fuortendaliks nei in reset. Dat de module bootet as gewoanlik of giet yn firmware-modus. Ien kear laden, kin dizze pin brûkt wurde as in gewoane GPIO. No, om't d'r al in knop is, kinne jo der wat nuttige funksje oan heakje.
Foar programmearring en debuggen sil ik de UART brûke, dy't wurdt útfierd nei in kam. As it nedich is, ferbine ik dêr gewoan in USB-UART-adapter. Jo moatte gewoan betinke dat de module wurdt oandreaun troch 3.3V. As jo ferjitte te wikseljen de adapter nei dizze spanning en leverje 5V, de module sil nei alle gedachten burn out.
Ik haw gjin problemen mei elektrisiteit yn 'e badkeamer - de outlet leit sawat in meter fan' e meters, dus ik sil oandreaun wurde troch 220V. As macht boarne Ik sil hawwe in lyts troch Tenstar Robot. Persoanlik haw ik in hurde tiid mei analoge en macht elektroanika, mar hjir is in klear makke macht oanbod yn in lyts gefal.
Foar it sinjalearjen fan bestjoeringsmodus, joech ik in LED ferbûn oan GPIO2. Ik haw it lykwols net losmakke, om't ... De module ESP-07 hat al in LED, en it is ek ferbûn mei GPIO2. Mar lit it wêze op it boerd, yn it gefal dat ik wol útfier dizze LED oan 'e saak.
Lit ús gean nei it meast nijsgjirrige diel. Wettermeters hawwe gjin logika, jo kinne se net freegje om aktuele lêzingen. It iennichste ding beskikber foar ús is ympulsen - it sluten fan de kontakten fan de reed switch elke liter. Myn reed switch útgongen binne ferbûn oan GPIO12 / GPIO13. Ik sil de pull-up wjerstân programmatysk ynskeakelje binnen de module.
Yn earste ynstânsje, ik fergeat te foarsjen wjerstannen R8 en R9 en myn ferzje fan it bestjoer hat se net. Mar om't ik it diagram al pleatse foar elkenien om te sjen, is it de muoite wurdich om dit tafersjoch te korrigearjen. Wjerstannen binne nedich om net te ferbaarnen de haven as de firmware glitches en set de pin oan ien, en de reed switch koarter dizze line oan grûn (mei de wjerstannen in maksimum fan 3.3V / 1000Ohm = 3.3mA sil streame).
It is tiid om te tinken oer wat te dwaan as de elektrisiteit útgiet. De earste opsje is om initial tellerwearden oan te freegjen fan 'e server by it begjin. Mar dit soe in wichtige komplikaasje fan it útwikselprotokol fereaskje. Boppedat hinget de prestaasjes fan it apparaat yn dit gefal ôf fan 'e steat fan' e tsjinner. As de tsjinner net begon nei't de macht útskeakele wie (of letter begon), soe de wettermeter gjin begjinwearden kinne oanfreegje en soe net goed wurkje.
Dêrom besleat ik besparjende tellerwearden yn te fieren yn in ûnthâldchip ferbûn fia I2C. Ik haw gjin spesjale easken foar de grutte fan it flash-ûnthâld - jo hoege allinich 2 nûmers te bewarjen (it oantal liters neffens de waarm- en kâldwettermeters). Sels de lytste module sil dwaan. Mar jo moatte omtinken jaan oan it oantal opname-syklusen. Foar de measte modules is dit 100 tûzen syklusen, foar guon oant in miljoen.
It soe lykje dat in miljoen in protte is. Mar yn 'e 4 jierren fan wenjen yn myn appartemint haw ik in bytsje mear as 500 kubike meter wetter konsumeare, dat is 500 tûzen liter! En 500 tûzen records yn flash. En dat is gewoan kâld wetter. Jo kinne de chip fansels elke pear jier opnij solderje, mar it docht bliken dat d'r FRAM-chips binne. Ut in programmearring eachpunt, dit is deselde I2C EEPROM, allinnich mei in hiel grut oantal rewrite syklusen (hûnderten miljoenen). It is gewoan dat ik mei sokke mikrocircuits noch net yn 'e winkel komme kin, dus foar no sil de gewoane 24LC512 stean.
Printe circuit board
Yn it earstoan wie ik fan plan om it boerd thús te meitsjen. Dêrom waard it boerd as iensidich ûntwurpen. Mar nei in oere mei in laserizer en in soldermasker (it is op ien of oare manier net comme il faut sûnder), besleat ik dochs de planken by de Sinezen te bestellen.

Hast foar it bestellen fan it bestjoer, realisearre ik dat ik neist de flash-ûnthâldchip wat oars nuttich kin ferbine mei de I2C-bus, lykas in display. Wat der krekt út te fieren is, is noch altyd in fraach, mar it moat op it boerd stjoerd wurde. No, om't ik planken fan it fabryk bestelle soe, hie it gjin punt om mysels te beheinen ta in iensidige board, sadat de I2C-linen de iennichste binne op 'e efterkant fan it bestjoer.
Der wie ek ien grut probleem mei de ienrjochting. Omdat It bestjoer waard tekene as iensidich, sadat de spoaren en SMD-komponinten pland waarden oan 'e iene kant te pleatsen, en de útfierkomponinten, Anschlüsse en Netzteil oan' e oare. Doe't ik in moanne letter de boerden krige, fergeat ik it orizjinele plan en soldere alle komponinten oan 'e foarkant. En pas as it kaam om it solderen fan 'e macht oanbod die bliken dat de plus en minus waarden bedrade yn omkearde. Ik moast buorkje mei springers. Yn 'e foto hjirboppe haw ik de bedrading al feroare, mar de grûn wurdt oerbrocht fan it iene diel fan it bestjoer nei it oare troch de pins fan' e Boot knop (hoewol't it mooglik wêze soe om in spoar op 'e twadde laach te tekenjen).
It draaide sa út

Housing
De folgjende stap is it lichem. As jo in 3D-printer hawwe, is dit gjin probleem. Ik makke net te folle muoite - ik tekene gewoan in doaze fan 'e juste grutte en makke útsnijingen op' e goede plakken. De omslach is oan it lichem befestige mei lytse selstappingsschroeven.

Ik haw al neamd dat de Boot-knop kin wurde brûkt as in knop foar algemiene doelen - dus wy sille it werjaan op it foarpaniel. Om dit te dwaan, tekene ik in spesjale "put" wêr't de knop libbet.

Binnen de saak binne d'r ek studs wêrop it bestjoer ynstalleare en befeilige is mei in inkele M3-skroeven (d'r wie gjin romte mear op it boerd)
Ik selektearre de werjefte al doe't ik printe de earste foarbyld ferzje fan de saak. In standert twa-line-lêzer paste net yn dit gefal, mar yn 'e boaiem wie in OLED-display SSD1306 128 × 32. It is in bytsje lyts, mar ik hoech der net alle dagen nei te stoarjen - it is my tefolle.
Troch dizze manier út te finen en dat hoe't de draden derút wurde lutsen, besleat ik it display yn 'e midden fan' e saak te plakjen. Ergonomie is fansels ûnder par - de knop is boppe, it display is oan 'e ûnderkant. Mar ik sei al dat it idee om it display te befestigjen te let kaam en ik wie te lui om it boerd opnij te draaien om de knop te ferpleatsen.
It apparaat wurdt gearstald. De displaymodule wurdt mei hite lym oan 'e snot lijm


It einresultaat is te sjen op KDPV
Firmware
Litte wy trochgean nei it softwarediel. Foar lytse ambachten lykas dit hâld ik echt fan Python () - de koade blykt heul kompakt en begryplik te wêzen. Gelokkich is it net nedich om nei it registernivo te gean om mikrosekonden út te squeeze - alles kin dien wurde fan Python.
It liket derop dat alles ienfâldich is, mar net heul ienfâldich - it apparaat hat ferskate ûnôfhinklike funksjes:
- De brûker stekt de knop en sjocht nei it display
- Liters tikje en bywurkje wearden yn flash ûnthâld
- De module kontrolearret it WiFi-sinjaal en ferbynt as it nedich is opnij
- No, sûnder in knipperende gloeilamp is it ûnmooglik
Jo kinne net oannimme dat de iene funksje net wurke as in oare om ien of oare reden fêst sit. Ik bin al fol mei kaktussen yn oare projekten en no sjoch ik noch glitches yn 'e styl fan "noch in liter miste omdat it display op dat stuit bywurke" of "de brûker kin neat dwaan wylst de module ferbûn is mei WiFi.” Fansels kinne guon dingen dien wurde troch ûnderbrekkingen, mar jo kinne yn beheiningen komme oer doer, nêsten fan petearen, of net-atomêre feroarings oan fariabelen. No, de koade dy't alles docht, feroaret gau yn mush.
В Ik brûkte klassike preemptive multitasking en FreeRTOS, mar yn dit gefal blykte it model folle mear geskikt te wêzen . Boppedat is de Python-ymplemintaasje fan koroutines gewoan geweldich - alles wurdt ienfâldich en maklik dien foar de programmeur. Skriuw gewoan jo eigen logika, fertel my gewoan op hokker plakken jo kinne wikselje tusken streamen.
Ik stel foar om de ferskillen te studearjen tusken preemptive en kompetitive multitasking as in opsjoneel fak. Litte wy no einlings trochgean nei de koade.
#####################################
# Counter class - implements a single water counter on specified pin
#####################################
class Counter():
debounce_ms = const(25)
def __init__(self, pin_num, value_storage):
self._value_storage = value_storage
self._value = self._value_storage.read()
self._value_changed = False
self._pin = Pin(pin_num, Pin.IN, Pin.PULL_UP)
loop = asyncio.get_event_loop()
loop.create_task(self._switchcheck()) # Thread runs forever
Elke teller wurdt behannele troch in eksimplaar fan 'e klasse Counter. Alderearst wurdt de earste tellerwearde fan 'e EEPROM (value_storage) lutsen - dit is hoe't herstel nei in stroomûnderbrekking wurdt realisearre.
De pin wurdt inisjalisearre mei in ynboude pull-up nei de macht oanbod: as de reed switch is sletten, de line is nul, as de line is iepen, it wurdt lutsen oant de macht oanbod en de controller lêst ien.
In aparte taak wurdt hjir ek lansearre, dy't de pin sil pollen. Elke teller sil syn eigen taak útfiere. Hjir is har koade
""" 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)
In fertraging fan 25ms is nedich om kontaktbounce te filterjen, en tagelyk regelet it hoe faak de taak wekker wurdt (wylst dizze taak sliept, rinne oare taken). Elke 25ms wurdt de funksje wekker, kontrolearret de pin en as de reedkontakten ticht binne, dan is der noch in liter troch de meter gongen en moat dit ferwurke wurde.
def _another_litre_passed(self):
self._value += 1
self._value_changed = True
self._value_storage.write(self._value)
It ferwurkjen fan de folgjende liter is triviaal - de teller wurdt gewoan grutter. No, it soe moai wêze om de nije wearde op in flash drive te skriuwen.
Foar gemak fan gebrûk wurde "aksessoires" foarsjoen
def value(self):
self._value_changed = False
return self._value
def set_value(self, value):
self._value = value
self._value_changed = False
No, lit ús no profitearje fan de wille fan Python en de uasync-bibleteek en in wachtber tsjinobjekt meitsje (hoe kinne wy dit oersette yn it Russysk? Dejinge dy't jo kinne ferwachtsje?)
def __await__(self):
while not self._value_changed:
yield from asyncio.sleep(0)
return self.value()
__iter__ = __await__
Dit is sa'n handige funksje dy't wachtet oant de tellerwearde is bywurke - de funksje wurdt fan tiid ta tiid wekker en kontrolearret de flagge _value_changed. It koele ding oer dizze funksje is dat de opropkoade yn 'e sliep kin falle by it roppen fan dizze funksje en sliepe oant in nije wearde wurdt ûntfongen.
Hoe sit it mei ûnderbrekkings?Ja, op dit punt kinne jo my trollje, sizze dat jo sels sein hawwe oer ûnderbrekkingen, mar yn 'e realiteit hawwe jo in domme pin-enkête dien. Yn feite binne interrupts it earste dat ik besocht. Yn 'e ESP8266 kinne jo in râneûnderbrekking organisearje, en sels in handler skriuwe foar dizze ûnderbrekking yn Python. Yn dizze ûnderbrekking kin de wearde fan in fariabele bywurke wurde. Wierskynlik soe dit genôch wêze as de teller in slaveapparaat wie - ien dy't wachtet oant it wurdt frege foar dizze wearde.
Spitigernôch (of gelokkich?) Myn apparaat is aktyf, it moat sels stjoere berjochten fia de MQTT protokol en skriuwe gegevens nei EEPROM. En hjir komme beheiningen yn spiel - jo kinne gjin ûnthâld tawize yn ûnderbrekkingen en in grutte stapel brûke, wat betsjut dat jo kinne ferjitte oer it ferstjoeren fan berjochten oer it netwurk. D'r binne buns lykas micropython.schedule () wêrmei jo in pear funksje "sa gau as mooglik" kinne útfiere, mar de fraach ûntstiet, "wat is it punt?" Wat as wy no in soarte fan berjocht ferstjoere, en dan komt der in ûnderbrekking en bedjert de wearden fan 'e fariabelen. Of, bygelyks, in nije tellerwearde kaam fan de tsjinner, wylst wy de âlde noch net opskreaun hiene. Yn 't algemien moatte jo syngronisaasje blokkearje of der op ien of oare manier oars út komme.
En fan tiid ta tiid RuntimeError: skema stack folsleine crashes en wa wit wêrom?
Mei eksplisite polling en uasync komt it yn dit gefal op ien of oare manier moaier en betrouberder út
Ik brocht wurk mei EEPROM nei in lytse klasse
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)
Yn Python is it dreech om direkt mei bytes te wurkjen, mar it binne de bytes dy't yn it ûnthâld skreaun wurde. Ik moast de konverzje tusken integer en bytes omheine mei de ustruct-bibleteek.
Om it I2C-objekt en it adres fan 'e ûnthâldsel net elke kear oer te bringen, ferpakte ik it allegear yn in lytse en handige klassiker
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)
It I2C-objekt sels wurdt makke mei dizze parameters
i2c = I2C(freq=400000, scl=Pin(5), sda=Pin(4))
Wy komme ta it meast nijsgjirrige diel - de ymplemintaasje fan kommunikaasje mei de tsjinner fia MQTT. No, it is net nedich om it protokol sels te ymplementearjen - ik fûn it op it ynternet . Dit is wat wy sille brûke.
Alle meast nijsgjirrige dingen wurde sammele yn 'e klasse CounterMQTTClient, dy't basearre is op' e bibleteek MQTTClient. Litte wy begjinne fan 'e perifery
#####################################
# 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))
Hjir kinne jo gloeilampen en knoppen oanmeitsje en konfigurearje, lykas objekten foar kâld en hyt wettermeter.
Mei inisjalisaasje is net alles sa triviaal
def __init__(self):
self.internet_outage = True
self.internet_outages = 0
self.internet_outage_start = ticks_ms()
with open("config.txt") as config_file:
config['ssid'] = config_file.readline().rstrip()
config['wifi_pw'] = config_file.readline().rstrip()
config['server'] = config_file.readline().rstrip()
config['client_id'] = config_file.readline().rstrip()
self._mqtt_cold_water_theme = config_file.readline().rstrip()
self._mqtt_hot_water_theme = config_file.readline().rstrip()
self._mqtt_debug_water_theme = config_file.readline().rstrip()
config['subs_cb'] = self.mqtt_msg_handler
config['wifi_coro'] = self.wifi_connection_handler
config['connect_coro'] = self.mqtt_connection_handler
config['clean'] = False
config['clean_init'] = False
super().__init__(config)
loop = asyncio.get_event_loop()
loop.create_task(self._heartbeat())
loop.create_task(self._counter_coro(self.cold_counter, self._mqtt_cold_water_theme))
loop.create_task(self._counter_coro(self.hot_counter, self._mqtt_hot_water_theme))
loop.create_task(self._display_coro())
Om de bestjoeringsparameters fan 'e bibleteek mqtt_as yn te stellen, wurdt in grut wurdboek fan ferskate ynstellings brûkt - config. De measte standertynstellingen binne goed foar ús, mar in protte ynstellings moatte eksplisyt ynsteld wurde. Om de ynstellings net direkt yn 'e koade te skriuwen, bewarje ik se yn it teksttriem config.txt. Hjirmei kinne jo de koade feroarje, nettsjinsteande de ynstellings, en ek ferskate identike apparaten mei ferskate parameters klinke.
It lêste blok fan koade begjint ferskate coroutines om ferskate funksjes fan it systeem te tsjinjen. Hjir is bygelyks in koroutine dy't tsjinnet tsjinnet
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))
De koroutine wachtet yn in lus op in nije tellerwearde en, sa gau as it ferskynt, stjoert in berjocht fia it MQTT-protokol. It earste stikje koade stjoert de begjinwearde sels as der gjin wetter troch de teller streamt.
De basisklasse MQTTClient tsjinnet himsels, inisjearret in WiFi-ferbining en ferbynt opnij as de ferbining ferlern is. Wannear't der feroarings yn de WiFi ferbining steat, de bibleteek ynformearret ús troch in oprop wifi_connection_handler
async def wifi_connection_handler(self, state):
self.internet_outage = not state
if state:
self.dprint('WiFi is up.')
duration = ticks_diff(ticks_ms(), self.internet_outage_start) // 1000
await self.publish_debug_msg('ReconnectedAfter', duration)
else:
self.internet_outages += 1
self.internet_outage_start = ticks_ms()
self.dprint('WiFi is down.')
await asyncio.sleep(0)
De funksje is earlik kopiearre út foarbylden. Yn dit gefal, it telt it oantal ûnderbrekkings (internet_outages) en harren doer. As de ferbining wersteld wurdt, wurdt in idle tiid nei de tsjinner stjoerd.
Trouwens, de lêste sliep is allinich nedich om de funksje asynchronous te meitsjen - yn 'e bibleteek wurdt it neamd fia await, en allinich funksjes wêrfan it lichem in oare wacht befettet kinne wurde neamd.
Neist it ferbinen mei WiFi, moatte jo ek in ferbining meitsje mei de MQTT-broker (tsjinner). De bibleteek docht dit ek, en wy krije de kâns om wat nuttichs te dwaan as de ferbining opsteld is
async def mqtt_connection_handler(self, client):
await client.subscribe(self._mqtt_cold_water_theme)
await client.subscribe(self._mqtt_hot_water_theme)
Hjir abonnearje wy op ferskate berjochten - de tsjinner hat no de mooglikheid om de hjoeddeistige tellerwearden yn te stellen troch it oerienkommende berjocht te ferstjoeren.
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))
Dizze funksje ferwurket ynkommende berjochten, en ôfhinklik fan it ûnderwerp (berjochttitel) wurde de wearden fan ien fan 'e tellers bywurke
In pear helpfunksjes
# 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")
Dizze funksje stjoert in berjocht as de ferbining is oprjochte. As der gjin ferbining is, wurdt it berjocht negearre.
En dit is gewoan in handige funksje dy't debuggen berjochten generearret en ferstjoert.
async def publish_debug_msg(self, subtopic, msg):
await self.publish_msg("{}/{}".format(self._mqtt_debug_water_theme, subtopic), str(msg))
Safolle tekst, en wy hawwe noch gjin LED knipperde. Hjir
# 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)
Ik haw foarsjoen 2 knipperende modi. As de ferbining ferlern is (of it wurdt gewoan oprjochte), sil it apparaat fluch knipperje. As de ferbining is oprjochte, blinkt it apparaat ien kear elke 5 sekonden. As it nedich is, kinne oare knippermodi hjir ymplementearre wurde.
Mar de LED is gewoan verwennen. Wy rjochte ek op it display.
async def _display_coro(self):
display = SSD1306_I2C(128,32, i2c)
while True:
display.poweron()
display.fill(0)
display.text("COLD: {:.3f}".format(self.cold_counter.value() / 1000), 16, 4)
display.text("HOT: {:.3f}".format(self.hot_counter.value() / 1000), 16, 20)
display.show()
await asyncio.sleep(3)
display.poweroff()
while self.button():
await asyncio.sleep_ms(20)
Dit is wêr't ik it oer hie - hoe ienfâldich en handich it is mei koroutines. Dizze lytse funksje beskriuwt de HELE brûkersûnderfining. De koroutine wachtet gewoan op 'e knop om te drukken en skeakelet it display foar 3 sekonden yn. It display lit de aktuele meterlêzingen sjen.
Der binne noch in pear lytse dingen oer. Hjir is de funksje dy't dizze hiele ûndernimming (wer)start. De haadloop ferstjoert gewoan ien kear yn 'e minút ferskate debuggen-ynformaasje. Yn 't algemien sitearje ik it sa't it is - ik tink net dat d'r te folle kommentaar hoecht te wêzen
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)
No, in pear mear ynstellingen en konstanten om de beskriuwing te foltôgjen
#####################################
# 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)
It begjint allegear sa
client = CounterMQTTClient()
loop = asyncio.get_event_loop()
loop.run_until_complete(client.main())
Der barde wat mei myn ûnthâld
Dat, alle koade is der. Ik haw de bestannen uploade mei it ampy-hulpprogramma - it lit jo se uploade nei it ynterne (dy yn 'e ESP-07 sels) flashdrive en dan tagong krije ta it fan it programma as gewoane bestannen. Dêr haw ik ek de bibleteken mqtt_as, uasyncio, ssd1306 en kolleksjes uploade dy't ik brûkte (brûkt binnen mqtt_as).
Wy lansearje en ... Wy krije in MemoryError. Boppedat, hoe mear ik besocht te begripen wêr't it ûnthâld krekt lekte, hoe mear debugprints ik pleatste, hoe earder dizze flater ferskynde. In koarte Google-sykopdracht late my ta it begryp dat de mikrocontroller yn prinsipe mar 30 kB oan ûnthâld hat, wêryn 65 kB koade (ynklusyf biblioteken) gewoan net passe kin.
Mar der is in útwei. It docht bliken dat micropython gjin koade direkt útfiert fan in .py-bestân - dit bestân wurdt earst kompilearre. Boppedat, it wurdt kompilearre direkt op 'e microcontroller, feroare yn bytecode, dat wurdt dan opslein yn it ûnthâld. No, om de kompilator te wurkjen, hawwe jo ek in bepaalde hoemannichte RAM nedich.
De trúk is om de mikrokontroller te bewarjen fan boarne-yntinsive kompilaasje. Jo kinne de bestannen op in grutte kompjûter kompilearje en de klearmakke bytekoade yn 'e mikrocontroller uploade. Om dit te dwaan, moatte jo de micropython-firmware downloade en bouwe .
Ik haw gjin Makefile skreaun, mar gie mei de hân troch en kompilearre alle nedige bestannen (ynklusyf bibleteken) soksawat as dit
mpy-cross water_counter.py
Alles wat oerbliuwt is om bestannen te uploaden mei de .mpy-útwreiding, net te ferjitten om earst de korrespondearjende .py fan it bestânsysteem fan it apparaat te wiskjen.
Ik die al de ûntwikkeling yn it programma (IDE?) ESPlorer. It lit jo skripts uploade nei de mikrokontroller en se fuortendaliks útfiere. Yn myn gefal, alle logika en skepping fan alle objekten leit yn de water_counter.py (.mpy) triem. Mar om dit alles automatysk te begjinnen, moat der ek in bestân wêze mei de namme main.py oan it begjin. Boppedat moat it krekt .py wêze, en net foarôf kompilearre .mpy. Hjir binne syn triviale ynhâld
import water_counter
Wy lansearje it - alles wurket. Mar fergees ûnthâld is alarmearjend lyts - sawat 1kb. Ik haw noch plannen om de funksjonaliteit fan it apparaat út te wreidzjen, en dizze kilobyte is dúdlik net genôch foar my. Mar it die bliken dat der ek foar dizze saak in útwei is.
Hjir is it ding. Sels hoewol de bestannen binne kompilearre yn bytecode en wenje op it ynterne bestânsysteem, wurde se yn 'e realiteit noch yn RAM laden en fan dêrút útfierd. Mar it docht bliken dat micropython kin útfiere bytecode direkt út flash ûnthâld, mar foar dit moatte jo bouwe it direkt yn de firmware. It is net dreech, hoewol it nochal wat tiid duorre op myn netbook (allinich dêr hie ik tafallich Linux).
It algoritme is sa:
- Download en ynstallearje . Dit ding sammelet in kompilator en bibleteken foar programma's foar de ESP8266. Sammele neffens de ynstruksjes op 'e haadside fan it projekt (ik keas de ynstelling STANDALONE = ja)
- Download
- Pleats de fereaske biblioteken yn havens / esp8266 / modules binnen de mikropythonbeam
- Wy sammelje de firmware neffens de ynstruksjes yn 'e triem
- Wy uploade de firmware nei de mikrocontroller (ik doch dit op Windows mei ESP8266Flasher-programma's of Python esptool)
Dat is it, no sil 'ymportearje ssd1306' de koade direkt fan 'e firmware opheffe en RAM sil hjirfoar net wurde konsumeare. Mei dizze trúk haw ik allinich de biblioteekkoade yn 'e firmware uploade, wylst de haadprogrammakoade wurdt útfierd fanút it bestânsysteem. Hjirmei kinne jo it programma maklik feroarje sûnder de firmware opnij te kompilearjen. Op it stuit haw ik sawat 8.5kb frije RAM. Dit sil ús yn 'e takomst in protte ferskillende nuttige funksjonaliteit kinne ymplementearje. No, as d'r hielendal net genôch ûnthâld is, dan kinne jo it haadprogramma yn 'e firmware drukke.
Dus wat moatte wy der no oan dwaan?
Ok, de hardware is soldered, de firmware is skreaun, de doaze wurdt printe, it apparaat is fêst oan 'e muorre en lokkich knippert in gloeilampe. Mar foar no is it allegear in swarte doaze (letterlik en figuerlik) en it is noch altyd fan lyts nut. It is tiid om wat te dwaan mei de MQTT-berjochten dy't nei de tsjinner stjoerd wurde.
Myn "tûke hûs" draait op . De MQTT-module komt út 'e doaze, of is maklik ynstalleare fan' e tafoegingsmerk - ik herinner my net wêr't ik it wei haw. MQTT is net in selsstannich ding - jo moatte in saneamde. broker - in tsjinner dy't ûntfangt, sortearret en trochstjoert MQTT-berjochten nei kliïnten. Ik brûk mosquitto, dy't (lykas majordomo) rint op deselde netbook.
Nei't it apparaat op syn minst ien kear in berjocht ferstjoert, sil de wearde fuortendaliks yn 'e list ferskine.

Dizze wearden kinne no wurde assosjearre mei systeemobjekten, se kinne brûkt wurde yn automatisearringsskripts en ûnderwurpen wurde oan ferskate analyzes - dat allegear bûten it berik fan dit artikel is. Ik kin it majordomo-systeem oanrikkemandearje oan elkenien dy't ynteressearre is - in freon bout ek in tûk hûs en praat dúdlik oer it ynstellen fan it systeem.
Ik sil dy mar in pear grafiken sjen litte. Dit is in ienfâldige grafyk fan deistige wearden

It is te sjen dat hast nimmen it wetter nachts brûkt hat. In pear kear gie ien nei it húske, en it liket derop dat de omkearde osmose filter in pear liter per nacht sûget. Moarns nimt it konsumpsje signifikant ta. Ik brûk meastentiids wetter út in boiler, mar doe woe ik nimme in bad en tydlik oerstapt nei stêd hyt wetter - dat is ek dúdlik te sjen yn de ûnderste grafyk.
Ut dizze grafyk learde ik dat nei it húske gean fereasket 6-7 liter wetter, it nimmen fan in dûs fereasket 20-30 liter, it waskjen fan skûtels fereasket sa'n 20 liter, en it nimmen fan in bad fereasket 160 liter. Myn famylje konsumearret earne sa'n 500-600 liter per dei.
Foar dyjingen dy't foaral nijsgjirrich binne, kinne jo de records sjen foar elke yndividuele wearde

Hjirwei learde ik dat as de kraan iepen is, it wetter streamt mei in snelheid fan likernôch 1 liter per 5 s.
Mar yn dizze foarm binne de statistiken wierskynlik net heul handich om nei te sjen. Majordomo hat ek de mooglikheid om konsumpsjediagrammen per dei, wike en moanne te besjen. Hjir is bygelyks in konsumpsjegraf yn bars

Oant no ta haw ik mar gegevens foar in wike. Yn in moanne sil dizze grafyk mear yndikatyf wêze - elke dei sil in aparte kolom hawwe. De ôfbylding is wat bedoarn troch de oanpassingen oan 'e wearden dy't ik manuell ynfiere (de grutste kolom). En it is noch net dúdlik oft ik de alderearste wearden ferkeard ynstelde, hast in kubus minder, of dat dit in flater is yn 'e firmware en net alle liters waarden teld. Mear tiid nedich.
De grafiken sels moatte noch wat magy, wytwaskjen, skilderjen. Miskien sil ik ek in grafyk bouwe fan ûnthâldferbrûk foar debuggen - as der wat lekt is. Miskien sil ik op ien of oare manier perioaden werjaan as d'r gjin ynternet wie. Foar no is dit alles op it nivo fan ideeën.
konklúzje
Hjoed is myn appartemint wat tûker wurden. Mei sa'n lyts apparaat sil it my handiger wêze om it wetterferbrûk yn 'e hûs te kontrolearjen. As ik earder fergriemd wie op "wer, wy konsumeare in protte wetter yn in moanne," no kin ik de boarne fan dizze konsumpsje fine.
Guon kinne it nuver fine om nei de lêzingen op it skerm te sjen as it in meter fan de meter sels is. Mar yn 'e net fiere takomst bin ik fan plan om te ferhúzjen nei in oar appartemint, wêr't ferskate wetterriders sille wêze, en de meters sels sille nei alle gedachten op' e lâning lizze. Dat in apparaat foar lês op ôfstân sil heul nuttich wêze.
Ik plan ek om de funksjonaliteit fan it apparaat út te wreidzjen. Ik sjoch al nei motorisearre kleppen. No, om de boiler oer te skeakeljen nei stedswetter, moat ik 3 kranen draaie yn in dreech te berikken nis. It soe folle handiger wêze om dit te dwaan mei ien knop mei de oerienkommende yndikaasje. No, fansels, it is it wurdich om beskerming tsjin lekken te ymplementearjen.
Yn it artikel beskreau ik myn ferzje fan in apparaat basearre op ESP8266. Yn myn miening kaam ik mei in heul ynteressante ferzje fan mikropython-firmware mei help fan koroutines - ienfâldich en moai. Ik besocht in protte fan 'e nuânses en tekoartkommingen te beskriuwen dy't ik tsjinkaam tidens de kampanje. Faaks haw ik persoanlik alles te gedetailleerd beskreaun, as lêzer is it foar my makliker om it ûnnedige guod oer te slaan as letter út te tinken wat net sein waard.
Lykas altyd stean ik iepen foar konstruktive krityk.
Boarne: www.habr.com
