Noong unang panahon, ang mga sistema ng pag-aautomat ng bahay, o βmatalinong tahananβ na madalas na tawag sa kanila, ay napakamahal at ang mga mayayaman lamang ang kayang bilhin ang mga ito. Ngayon sa merkado ay makakahanap ka ng medyo murang mga kit na may mga sensor, button/switch at actuator para sa pagkontrol sa ilaw, socket, bentilasyon, supply ng tubig at iba pang mga consumer. At kahit na ang pinakabaluktot na taong DIY ay maaaring makisali sa pagpapaganda at mag-assemble ng mga device para sa isang matalinong tahanan sa murang presyo.

Karaniwan, ang mga iminungkahing device ay alinman sa mga sensor o actuator. Pinapadali nila ang pagpapatupad ng mga sitwasyon tulad ng "kapag na-trigger ang isang motion sensor, buksan ang mga ilaw" o "pinapatay ng switch malapit sa exit ang mga ilaw sa buong apartment." Ngunit kahit papaano ay hindi gumana ang mga bagay sa telemetry. Sa pinakamaganda, ito ay isang graph ng temperatura at halumigmig, o agarang kapangyarihan sa isang partikular na outlet.
Nag-install ako kamakailan ng mga metro ng tubig na may output ng pulso. Para sa bawat litro na dumadaan sa metro, ang switch ng tambo ay isinaaktibo at isinasara ang contact. Ang tanging bagay na natitira upang gawin ay kumapit sa mga wire at subukang makakuha ng benepisyo mula dito. Halimbawa, suriin ang pagkonsumo ng tubig ayon sa oras at araw ng linggo. Buweno, kung mayroong maraming mga pagtaas ng tubig sa apartment, kung gayon mas maginhawang makita ang lahat ng kasalukuyang mga tagapagpahiwatig sa isang screen kaysa umakyat sa mga niches na mahirap maabot gamit ang isang flashlight.
Sa ibaba ng cut ay ang aking bersyon ng isang device batay sa ESP8266, na nagbibilang ng mga pulso mula sa mga metro ng tubig at nagpapadala ng mga pagbabasa sa pamamagitan ng MQTT sa smart home server. Magpoprogram kami sa micropython gamit ang uasyncio library. Kapag lumilikha ng firmware, nakatagpo ako ng maraming mga kagiliw-giliw na paghihirap, na tatalakayin ko rin sa artikulong ito. Go!
Ang pamamaraan

Ang puso ng buong circuit ay isang module sa ESP8266 microcontroller. Ang ESP-12 ay orihinal na binalak, ngunit ang sa akin ay naging may depekto. Kailangan naming makuntento sa ESP-07 module, na available. Sa kabutihang palad, pareho sila sa mga tuntunin ng mga pin at pag-andar, ang pagkakaiba lamang ay nasa antenna - ang ESP-12 ay may built-in, habang ang ESP-07 ay may panlabas. Gayunpaman, kahit na walang WiFi antenna, normal na natatanggap ang signal sa aking banyo.
Karaniwang mga kable ng module:
- reset button na may pull-up at capacitor (bagaman pareho na ang nasa loob ng module)
- Ang enable signal (CH_PD) ay hinila pataas sa kapangyarihan
- Ang GPIO15 ay hinila sa lupa. Ito ay kailangan lamang sa simula, ngunit wala pa rin akong kalakip sa binti na ito;
Upang ilagay ang module sa mode ng firmware, kailangan mong i-short-circuit ang GPIO2 sa lupa, at para gawin itong mas maginhawa, nagbigay ako ng Boot button. Sa normal na kondisyon, ang pin na ito ay hinila sa kapangyarihan.
Ang estado ng linya ng GPIO2 ay nasuri lamang sa simula ng operasyon - kapag inilapat ang kapangyarihan o kaagad pagkatapos ng pag-reset. Kaya ang module ay maaaring mag-boot gaya ng dati o mapupunta sa firmware mode. Kapag na-load na, ang pin na ito ay maaaring gamitin bilang isang regular na GPIO. Well, dahil mayroon nang isang pindutan doon, maaari mong ilakip ang ilang kapaki-pakinabang na function dito.
Para sa programming at debugging gagamitin ko ang UART, na output sa isang suklay. Kung kinakailangan, kumonekta lang ako ng USB-UART adapter doon. Kailangan mo lang tandaan na ang module ay pinapagana ng 3.3V. Kung nakalimutan mong ilipat ang adaptor sa boltahe na ito at mag-supply ng 5V, malamang na masunog ang module.
Wala akong problema sa kuryente sa banyo - ang outlet ay matatagpuan halos isang metro mula sa mga metro, kaya papaganahin ako ng 220V. Bilang pinagmumulan ng kuryente ay magkakaroon ako ng maliit sa pamamagitan ng Tenstar Robot. Sa personal, nahihirapan ako sa analog at power electronics, ngunit narito ang isang handa na supply ng kuryente sa isang maliit na kaso.
Upang magsenyas ng mga operating mode, nagbigay ako ng LED na konektado sa GPIO2. Gayunpaman, hindi ko ito inalis, dahil... Ang ESP-07 module ay mayroon nang LED, at ito ay konektado din sa GPIO2. Ngunit hayaan ito sa board, kung sakaling gusto kong i-output ang LED na ito sa kaso.
Lumipat tayo sa pinakakawili-wiling bahagi. Ang mga metro ng tubig ay walang lohika; Ang tanging bagay na magagamit sa amin ay mga impulses - pagsasara ng mga contact ng reed switch bawat litro. Ang mga output ng reed switch ko ay konektado sa GPIO12/GPIO13. Paganahin ko ang pull-up na risistor sa programmatically sa loob ng module.
Sa una, nakalimutan kong magbigay ng resistors R8 at R9 at ang aking bersyon ng board ay wala sa kanila. Ngunit dahil nai-post ko na ang diagram para makita ng lahat, sulit na iwasto ang oversight na ito. Kinakailangan ang mga resistors upang hindi masunog ang port kung ang firmware ay nag-glitches at itinatakda ang pin sa isa, at ang reed switch ay i-short ang linyang ito sa lupa (na may pinakamataas na risistor na 3.3V/1000Ohm = 3.3mA ang dadaloy).
Panahon na para pag-isipan kung ano ang gagawin kung mawawalan ng kuryente. Ang unang pagpipilian ay humiling ng mga paunang halaga ng counter mula sa server sa simula. Ngunit mangangailangan ito ng makabuluhang komplikasyon ng exchange protocol. Bukod dito, ang pagganap ng device sa kasong ito ay nakasalalay sa estado ng server. Kung ang server ay hindi nagsimula pagkatapos na patayin ang kuryente (o nagsimula sa ibang pagkakataon), ang metro ng tubig ay hindi makakahiling ng mga paunang halaga at hindi gagana nang tama.
Samakatuwid, nagpasya akong ipatupad ang pag-save ng mga halaga ng counter sa isang memory chip na konektado sa pamamagitan ng I2C. Wala akong anumang mga espesyal na kinakailangan para sa laki ng flash memory - kailangan mo lamang mag-save ng 2 numero (ang bilang ng mga litro ayon sa mainit at malamig na metro ng tubig). Kahit na ang pinakamaliit na module ay gagawin. Ngunit kailangan mong bigyang-pansin ang bilang ng mga ikot ng pag-record. Para sa karamihan ng mga module ito ay 100 libong cycle, para sa ilan hanggang sa isang milyon.
Mukhang ang isang milyon ay marami. Ngunit sa loob ng 4 na taon ng paninirahan sa aking apartment, nakakonsumo ako ng higit sa 500 metro kubiko ng tubig, iyon ay 500 libong litro! At 500 thousand records sa flash. At malamig na tubig lang iyon. Maaari mong, siyempre, i-resolder ang chip bawat ilang taon, ngunit lumalabas na mayroong mga FRAM chips. Mula sa isang programming point of view, ito ay ang parehong I2C EEPROM, lamang na may isang napakalaking bilang ng mga muling pagsulat cycle (daan-daang milyon). Kaya lang hindi pa rin ako makapunta sa tindahan na may ganitong mga microcircuits, kaya sa ngayon ang karaniwang 24LC512 ay tatayo.
Naka-print na circuit board
Noong una, binalak kong gawin ang board sa bahay. Samakatuwid, ang board ay idinisenyo bilang isang panig. Ngunit pagkatapos na gumugol ng isang oras sa isang laser iron at isang solder mask (ito ay sa paanuman ay hindi comme il faut kung wala ito), nagpasya pa rin akong mag-order ng mga board mula sa mga Intsik.

Halos bago mag-order ng board, natanto ko na bilang karagdagan sa flash memory chip, maaari kong ikonekta ang ibang bagay na kapaki-pakinabang sa I2C bus, tulad ng isang display. Ano ang eksaktong ilalabas dito ay isang tanong pa rin, ngunit kailangan itong i-ruta sa board. Buweno, dahil mag-o-order ako ng mga board mula sa pabrika, walang punto na limitahan ang aking sarili sa isang single-sided board, kaya ang mga linya ng I2C ay ang tanging nasa likod na bahagi ng board.
Nagkaroon din ng isang malaking problema sa one-way na mga kable. kasi Ang board ay iginuhit bilang isang panig, kaya ang mga track at mga bahagi ng SMD ay binalak na ilagay sa isang gilid, at ang mga bahagi ng output, konektor at power supply sa kabilang panig. Nang matanggap ko ang mga board makalipas ang isang buwan, nakalimutan ko ang tungkol sa orihinal na plano at ibinenta ang lahat ng mga bahagi sa harap na bahagi. At pagdating lamang sa paghihinang ng power supply, lumabas na ang plus at minus ay naka-wire sa kabaligtaran. Kinailangan kong magsaka kasama ang mga jumper. Sa larawan sa itaas, binago ko na ang mga kable, ngunit ang lupa ay inilipat mula sa isang bahagi ng board patungo sa isa pa sa pamamagitan ng mga pin ng pindutan ng Boot (bagaman posible na gumuhit ng isang track sa pangalawang layer).
Ito ay naging ganito

Pabahay
Ang susunod na hakbang ay ang katawan. Kung mayroon kang 3D printer, hindi ito problema. Hindi ako masyadong nag-abala - gumuhit lang ako ng isang kahon na may tamang laki at gumawa ng mga ginupit sa mga tamang lugar. Ang takip ay nakakabit sa katawan na may maliliit na self-tapping screws.

Nabanggit ko na na ang Boot button ay maaaring gamitin bilang isang general-purpose button - kaya ipapakita namin ito sa front panel. Upang gawin ito, gumuhit ako ng isang espesyal na "well" kung saan nakatira ang pindutan.

Sa loob ng case ay mayroon ding mga stud kung saan naka-install at naka-secure ang board gamit ang isang M3 screw (wala nang espasyo sa board)
Pinili ko na ang display noong nai-print ko ang unang sample na bersyon ng case. Ang isang karaniwang dalawang-linya na mambabasa ay hindi magkasya sa kasong ito, ngunit sa ibaba ay mayroong isang OLED display SSD1306 128 Γ 32. Medyo maliit ito, ngunit hindi ko kailangang titigan ito araw-araw-ito ay sobra para sa akin.
Sa pag-uunawa sa ganitong paraan at kung paano iruruta ang mga wire mula rito, nagpasya akong idikit ang display sa gitna ng case. Ang ergonomya, siyempre, ay nasa ibaba ng par - ang pindutan ay nasa itaas, ang display ay nasa ibaba. Ngunit sinabi ko na na ang ideya na ilakip ang display ay dumating nang huli at tamad akong muling i-rewire ang board upang ilipat ang pindutan.
Ang aparato ay binuo. Ang display module ay nakadikit sa snot na may mainit na pandikit


Ang huling resulta ay makikita sa KDPV
Firmware
Lumipat tayo sa bahagi ng software. Para sa maliliit na crafts na tulad nito, gusto ko talagang gumamit ng Python () - ang code ay lumalabas na napaka-compact at naiintindihan. Sa kabutihang palad, hindi na kailangang bumaba sa antas ng rehistro upang i-squeeze out microseconds - lahat ay maaaring gawin mula sa Python.
Tila ang lahat ay simple, ngunit hindi masyadong simple - ang aparato ay may ilang mga independiyenteng pag-andar:
- Pinihit ng user ang button at tumitingin sa display
- Ang mga litro ay tiktikan at i-update ang mga halaga sa flash memory
- Sinusubaybayan ng module ang signal ng WiFi at muling kumonekta kung kinakailangan
- Buweno, imposibleng walang kumikislap na bombilya
Hindi mo maaaring ipagpalagay na ang isang function ay hindi gumana kung ang isa pa ay natigil sa ilang kadahilanan. Napuno na ako ng cacti sa iba pang mga proyekto at ngayon ay nakakakita pa rin ako ng mga glitches sa istilong "nalampasan ang isa pang litro dahil nag-a-update ang display sa sandaling iyon" o "walang magagawa ang user habang kumokonekta ang module sa WiFi.β Siyempre, ang ilang bagay ay maaaring gawin sa pamamagitan ng mga pagkaantala, ngunit maaari kang magkaroon ng mga limitasyon sa tagal, pagpupugad ng mga tawag, o hindi atomic na pagbabago sa mga variable. Well, ang code na gumagawa ng lahat ay mabilis na nagiging mush.
Π Gumamit ako ng klasikong preemptive multitasking at FreeRTOS, ngunit sa kasong ito ang modelo ay naging mas angkop . Bukod dito, ang pagpapatupad ng Python ng mga coroutine ay kamangha-mangha - lahat ay ginagawa nang simple at maginhawa para sa programmer. Sumulat lang ng sarili mong lohika, sabihin mo lang sa akin kung saang mga lugar ka maaaring lumipat sa pagitan ng mga stream.
Iminumungkahi kong pag-aralan ang mga pagkakaiba sa pagitan ng preemptive at competitive na multitasking bilang isang opsyonal na paksa. Ngayon, sa wakas ay lumipat tayo sa code.
#####################################
# 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
Ang bawat counter ay pinangangasiwaan ng isang instance ng Counter class. Una sa lahat, ang paunang halaga ng counter ay ibinabawas mula sa EEPROM (value_storage) - ito ay kung paano ang pagbawi pagkatapos ng power failure ay ipinatupad.
Ang pin ay sinisimulan gamit ang isang built-in na pull-up sa power supply: kung ang reed switch ay sarado, ang linya ay zero, kung ang linya ay bukas, ito ay hinila pataas sa power supply at ang controller ay nagbabasa ng isa.
Ang isang hiwalay na gawain ay inilunsad din dito, na magpo-poll sa pin. Ang bawat counter ay tatakbo ng sarili nitong gawain. Narito ang kanyang code
""" Poll pin and advance value when another litre passed """
async def _switchcheck(self):
last_checked_pin_state = self._pin.value() # Get initial state
# Poll for a pin change
while True:
state = self._pin.value()
if state != last_checked_pin_state:
# State has changed: act on it now.
last_checked_pin_state = state
if state == 0:
self._another_litre_passed()
# Ignore further state changes until switch has settled
await asyncio.sleep_ms(Counter.debounce_ms)
Kailangan ng pagkaantala ng 25ms upang i-filter ang bounce ng contact, at kasabay nito ay kinokontrol nito kung gaano kadalas nagising ang gawain (habang natutulog ang gawaing ito, tumatakbo ang iba pang mga gawain). Bawat 25ms nagigising ang function, sinusuri ang pin at kung sarado ang mga contact ng reed switch, pagkatapos ay isa pang litro ang dumaan sa metro at kailangan itong iproseso.
def _another_litre_passed(self):
self._value += 1
self._value_changed = True
self._value_storage.write(self._value)
Ang pagpoproseso ng susunod na litro ay walang halaga - ang counter ay tumataas lamang. Well, magiging maganda kung isulat ang bagong halaga sa isang flash drive.
Para sa kadalian ng paggamit, ang "mga accessor" ay ibinigay
def value(self):
self._value_changed = False
return self._value
def set_value(self, value):
self._value = value
self._value_changed = False
Kaya, ngayon, samantalahin natin ang mga kasiyahan ng Python at ang uasync library at gumawa ng isang naghihintay na counter object (paano natin ito isasalin sa Russian? Ang maaari mong asahan?)
def __await__(self):
while not self._value_changed:
yield from asyncio.sleep(0)
return self.value()
__iter__ = __await__
Ito ay isang maginhawang function na naghihintay hanggang sa ma-update ang counter value - ang function ay nagigising paminsan-minsan at sinusuri ang _value_changed flag. Ang cool na bagay tungkol sa function na ito ay ang calling code ay maaaring makatulog habang tinatawag ang function na ito at matulog hanggang sa isang bagong halaga ay matanggap.
Paano ang mga pagkagambala?Oo, sa puntong ito maaari mo akong troll, na sinasabi na ikaw mismo ang nagsabi tungkol sa mga pagkaantala, ngunit sa katotohanan ay gumawa ka ng isang hangal na pin poll. Actually interrupts ang una kong sinubukan. Sa ESP8266, maaari kang mag-ayos ng edge interrupt, at magsulat pa ng handler para sa interrupt na ito sa Python. Sa interrupt na ito, maaaring ma-update ang halaga ng isang variable. Malamang, ito ay sapat na kung ang counter ay isang slave device - isa na naghihintay hanggang sa ito ay hilingin para sa halagang ito.
Sa kasamaang palad (o sa kabutihang-palad?) ang aking aparato ay aktibo, ito ay dapat mismong magpadala ng mga mensahe sa pamamagitan ng MQTT protocol at magsulat ng data sa EEPROM. At dito pumapasok ang mga paghihigpit - hindi ka maaaring maglaan ng memorya sa mga interrupts at gumamit ng isang malaking stack, na nangangahulugang maaari mong kalimutan ang tungkol sa pagpapadala ng mga mensahe sa network. May mga buns tulad ng micropython.schedule() na nagbibigay-daan sa iyong patakbuhin ang ilang function "sa lalong madaling panahon," ngunit ang tanong ay lumitaw, "ano ang punto?" Paano kung nagpapadala kami ng ilang uri ng mensahe sa ngayon, at pagkatapos ay pumasok ang isang interrupt at sinisira ang mga halaga ng mga variable. O, halimbawa, may dumating na bagong counter value mula sa server habang hindi pa namin naisulat ang luma. Sa pangkalahatan, kailangan mong harangan ang pag-synchronize o alisin ito sa ibang paraan.
At paminsan-minsan RuntimeError: mag-iskedyul ng mga stack ng buong pag-crash at sino ang nakakaalam kung bakit?
Sa tahasang botohan at uasync, sa kasong ito, kahit papaano ay nagiging mas maganda at maaasahan
Nagdala ako ng trabaho kasama ang EEPROM sa isang maliit na klase
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)
Sa Python, mahirap gumana nang direkta sa mga byte, ngunit ito ay ang mga byte na nakasulat sa memorya. Kinailangan kong bakod ang conversion sa pagitan ng integer at bytes gamit ang ustruct library.
Upang hindi mailipat ang bagay na I2C at ang address ng memory cell sa bawat oras, binalot ko ang lahat ng ito sa isang maliit at maginhawang klasiko
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)
Ang I2C object mismo ay nilikha gamit ang mga parameter na ito
i2c = I2C(freq=400000, scl=Pin(5), sda=Pin(4))
Dumating kami sa pinaka-kagiliw-giliw na bahagi - ang pagpapatupad ng komunikasyon sa server sa pamamagitan ng MQTT. Well, hindi na kailangang ipatupad ang protocol mismo - natagpuan ko ito sa Internet . Ito ang ating gagamitin.
Ang lahat ng mga pinakakawili-wiling bagay ay kinokolekta sa klase ng CounterMQTTClient, na nakabatay sa library MQTTClient. Magsimula tayo sa paligid
#####################################
# 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))
Dito maaari kang lumikha at mag-configure ng mga pin at button ng light bulb, pati na rin ang malamig at mainit na mga bagay sa metro ng tubig.
Sa pagsisimula, hindi lahat ay napakaliit
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())
Upang itakda ang mga operating parameter ng mqtt_as library, isang malaking diksyunaryo ng iba't ibang mga setting ang ginagamit - config. Karamihan sa mga default na setting ay maayos para sa amin, ngunit maraming mga setting ang kailangang itakda nang tahasan. Upang hindi direktang isulat ang mga setting sa code, iniimbak ko ang mga ito sa text file na config.txt. Pinapayagan ka nitong baguhin ang code anuman ang mga setting, pati na rin ang pag-rivet ng ilang magkaparehong mga aparato na may iba't ibang mga parameter.
Ang huling bloke ng code ay magsisimula ng ilang coroutine upang maghatid ng iba't ibang mga function ng system. Halimbawa, narito ang isang coroutine na binibilang ng mga serbisyo
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))
Ang coroutine ay naghihintay sa isang loop para sa isang bagong counter value at, sa sandaling lumitaw ito, nagpapadala ng mensahe sa pamamagitan ng MQTT protocol. Ang unang piraso ng code ay nagpapadala ng paunang halaga kahit na walang tubig na dumadaloy sa counter.
Ang batayang klase na MQTTClient ay nagsisilbi mismo, nagpapasimula ng koneksyon sa WiFi at muling kumonekta kapag nawala ang koneksyon. Kapag may mga pagbabago sa estado ng koneksyon sa WiFi, ipinapaalam sa amin ng library sa pamamagitan ng pagtawag sa 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)
Ang function ay matapat na kinopya mula sa mga halimbawa. Sa kasong ito, binibilang nito ang bilang ng mga pagkawala (internet_outages) at ang kanilang tagal. Kapag naibalik ang koneksyon, isang idle time ang ipapadala sa server.
Sa pamamagitan ng paraan, ang huling pagtulog ay kailangan lamang upang gawing asynchronous ang function - sa library ito ay tinatawag sa pamamagitan ng paghihintay, at tanging ang mga function na ang katawan ay naglalaman ng isa pang paghihintay ay maaaring tawagan.
Bilang karagdagan sa pagkonekta sa WiFi, kailangan mo ring magtatag ng koneksyon sa MQTT broker (server). Ginagawa rin ito ng library, at nagkakaroon kami ng pagkakataong gumawa ng isang bagay na kapaki-pakinabang kapag naitatag ang koneksyon
async def mqtt_connection_handler(self, client):
await client.subscribe(self._mqtt_cold_water_theme)
await client.subscribe(self._mqtt_hot_water_theme)
Dito kami nag-subscribe sa ilang mga mensahe - ang server ay may kakayahan na ngayong itakda ang kasalukuyang mga halaga ng counter sa pamamagitan ng pagpapadala ng kaukulang mensahe.
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))
Pinoproseso ng function na ito ang mga papasok na mensahe, at depende sa paksa (pamagat ng mensahe), ina-update ang mga halaga ng isa sa mga counter
Isang pares ng mga function ng katulong
# 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")
Ang function na ito ay nagpapadala ng mensahe kung ang koneksyon ay naitatag. Kung walang koneksyon, ang mensahe ay hindi papansinin.
At isa lang itong maginhawang function na bumubuo at nagpapadala ng mga mensahe sa pag-debug.
async def publish_debug_msg(self, subtopic, msg):
await self.publish_msg("{}/{}".format(self._mqtt_debug_water_theme, subtopic), str(msg))
Napakaraming text, at hindi pa kami kumikislap ng LED. Dito
# 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)
Nagbigay ako ng 2 blinking mode. Kung ang koneksyon ay nawala (o ito ay itinatatag lamang), ang aparato ay mabilis na kumikislap. Kung naitatag ang koneksyon, kumikislap ang device isang beses bawat 5 segundo. Kung kinakailangan, maaaring ipatupad dito ang iba pang mga blinking mode.
Ngunit ang LED ay layaw lamang. Tinutukan din namin ang 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)
Ito ang aking pinag-uusapan - kung gaano kasimple at kaginhawaan ito sa mga coroutine. Inilalarawan ng maliit na function na ito ang BUONG karanasan ng user. Hinihintay lang ng coroutine na mapindot ang button at i-on ang display sa loob ng 3 segundo. Ipinapakita ng display ang kasalukuyang pagbabasa ng metro.
Mayroon pa ring ilang maliliit na bagay na natitira. Narito ang function na (muling) magsisimula sa buong enterprise na ito. Ang pangunahing loop ay nagpapadala lamang ng iba't ibang impormasyon sa pag-debug isang beses sa isang minuto. Sa pangkalahatan, sinipi ko ito kung ano ito - sa palagay ko ay hindi na kailangang magkomento nang labis
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, ilang higit pang mga setting at constants upang makumpleto ang paglalarawan
#####################################
# 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)
Nagsisimula ang lahat ng ganito
client = CounterMQTTClient()
loop = asyncio.get_event_loop()
loop.run_until_complete(client.main())
May nangyari sa aking alaala
Kaya, lahat ng code ay naroon. Nag-upload ako ng mga file gamit ang ampy utility - pinapayagan ka nitong i-upload ang mga ito sa panloob (ang isa sa ESP-07 mismo) flash drive at pagkatapos ay i-access ito mula sa programa bilang mga regular na file. Doon ko rin in-upload ang mqtt_as, uasyncio, ssd1306 at collections library na ginamit ko (ginamit sa loob ng mqtt_as).
Naglulunsad kami at... Nakakuha kami ng MemoryError. Bukod dito, mas sinubukan kong maunawaan kung saan eksaktong tumagas ang memorya, mas maraming mga debug na print ang inilagay ko, mas maagang lumitaw ang error na ito. Ang isang maikling paghahanap sa Google ay humantong sa akin sa pag-unawa na ang microcontroller ay may, sa prinsipyo, lamang ng 30 kB ng memorya, kung saan ang 65 kB ng code (kabilang ang mga aklatan) ay hindi magkasya.
Pero may paraan palabas. Lumalabas na ang micropython ay hindi direktang nagpapatupad ng code mula sa isang .py file - ang file na ito ay unang pinagsama-sama. Bukod dito, ito ay direktang pinagsama-sama sa microcontroller, naging bytecode, na pagkatapos ay naka-imbak sa memorya. Well, para gumana ang compiler, kailangan mo rin ng isang tiyak na halaga ng RAM.
Ang lansihin ay upang i-save ang microcontroller mula sa resource-intensive compilation. Maaari mong i-compile ang mga file sa isang malaking computer at i-upload ang handa na bytecode sa microcontroller. Upang gawin ito, kailangan mong i-download ang micropython firmware at bumuo .
Hindi ako sumulat ng Makefile, ngunit manu-manong dumaan at pinagsama-sama ang lahat ng kinakailangang mga file (kabilang ang mga aklatan) tulad nito
mpy-cross water_counter.py
Ang natitira na lang ay mag-upload ng mga file na may .mpy extension, hindi nakakalimutang tanggalin muna ang kaukulang .py mula sa file system ng device.
Ginawa ko ang lahat ng pag-unlad sa programa (IDE?) ESPlorer. Pinapayagan ka nitong mag-upload ng mga script sa microcontroller at agad na isagawa ang mga ito. Sa aking kaso, ang lahat ng lohika at paglikha ng lahat ng mga bagay ay matatagpuan sa water_counter.py (.mpy) na file. Ngunit para awtomatikong magsimula ang lahat ng ito, dapat mayroon ding file na tinatawag na main.py sa simula. Bukod dito, dapat itong eksaktong .py, at hindi na-pre-compiled na .mpy. Narito ang mga walang kabuluhang nilalaman nito
import water_counter
Inilunsad namin ito - gumagana ang lahat. Ngunit ang libreng memorya ay napakaliit - mga 1kb. Mayroon pa akong mga plano na palawakin ang pag-andar ng device, at ang kilobyte na ito ay malinaw na hindi sapat para sa akin. Pero may paraan din pala para sa kasong ito.
Narito ang bagay. Kahit na ang mga file ay pinagsama-sama sa bytecode at naninirahan sa panloob na sistema ng file, sa katotohanan sila ay na-load pa rin sa RAM at naisakatuparan mula doon. Ngunit lumalabas na ang micropython ay maaaring magsagawa ng bytecode nang direkta mula sa flash memory, ngunit para dito kailangan mong itayo ito nang direkta sa firmware. Ito ay hindi mahirap, bagaman ito ay tumagal ng medyo matagal sa aking netbook (doon lang ako nagkaroon ng Linux).
Ang algorithm ay ganito:
- I-download at i-install . Ang bagay na ito ay nagtitipon ng isang compiler at mga aklatan para sa mga programa para sa ESP8266. Pinagsama ayon sa mga tagubilin sa pangunahing pahina ng proyekto (Pinili ko ang STANDALONE=yes setting)
- I-download ang
- Ilagay ang mga kinakailangang aklatan sa mga port/esp8266/modules sa loob ng micropython tree
- Binubuo namin ang firmware ayon sa mga tagubilin sa file
- Ina-upload namin ang firmware sa microcontroller (ginagawa ko ito sa Windows gamit ang ESP8266Flasher programs o Python esptool)
Iyon lang, ngayon ang 'import ssd1306' ay mag-aangat ng code nang direkta mula sa firmware at hindi mauubos ang RAM para dito. Sa trick na ito, na-upload ko lamang ang code ng library sa firmware, habang ang pangunahing code ng programa ay naisakatuparan mula sa file system. Pinapayagan ka nitong madaling baguhin ang programa nang hindi muling kino-compile ang firmware. Sa ngayon mayroon akong humigit-kumulang 8.5kb ng libreng RAM. Magbibigay-daan ito sa amin na magpatupad ng napakaraming iba't ibang kapaki-pakinabang na paggana sa hinaharap. Buweno, kung walang sapat na memorya, maaari mong itulak ang pangunahing programa sa firmware.
Kaya ano ang dapat nating gawin tungkol dito ngayon?
Ok, ang hardware ay soldered, ang firmware ay nakasulat, ang kahon ay naka-print, ang aparato ay nakadikit sa dingding at masayang kumikislap ng isang bumbilya. Ngunit sa ngayon ang lahat ay isang itim na kahon (literal at matalinghaga) at ito ay wala pa ring pakinabang. Oras na para gumawa ng isang bagay sa mga mensahe ng MQTT na ipinadala sa server.
Ang aking "matalinong tahanan" ay umiikot . Ang MQTT module ay maaaring lumabas sa kahon, o madaling na-install mula sa add-on market - Hindi ko maalala kung saan ko ito nakuha. Ang MQTT ay hindi isang bagay na sapat sa sarili - kailangan mo ng tinatawag na. broker - isang server na tumatanggap, nag-uuri at nagpapasa ng mga mensahe ng MQTT sa mga kliyente. Gumagamit ako ng lamok, na (tulad ng majordomo) ay tumatakbo sa parehong netbook.
Pagkatapos magpadala ng mensahe ang device kahit isang beses lang, lalabas agad ang value sa listahan.

Ang mga halagang ito ay maaari na ngayong maiugnay sa mga bagay ng system, maaari silang magamit sa mga script ng automation at sumailalim sa iba't ibang mga pagsusuri - lahat ng ito ay lampas sa saklaw ng artikulong ito. Maaari kong irekomenda ang majordomo system sa sinumang interesado β ang isang kaibigan ay gumagawa din ng isang matalinong tahanan at malinaw na nagsasalita tungkol sa pag-set up ng system.
Magpapakita lang ako sa iyo ng ilang mga graph. Ito ay isang simpleng graph ng mga pang-araw-araw na halaga

Makikita na halos walang gumagamit ng tubig sa gabi. Ilang beses na may nagpunta sa banyo, at tila ang reverse osmosis filter ay humihigop ng ilang litro bawat gabi. Sa umaga, ang pagkonsumo ay tumataas nang malaki. Karaniwan akong gumagamit ng tubig mula sa isang boiler, ngunit pagkatapos ay gusto kong maligo at pansamantalang lumipat sa mainit na tubig ng lungsod - ito ay malinaw na nakikita sa ibabang graph.
Mula sa graph na ito nalaman ko na ang pagpunta sa banyo ay nangangailangan ng 6-7 litro ng tubig, ang pagligo ay nangangailangan ng 20-30 litro, ang paghuhugas ng mga pinggan ay nangangailangan ng mga 20 litro, at ang pagligo ay nangangailangan ng 160 litro. Ang aking pamilya ay kumukonsumo sa isang lugar sa paligid ng 500-600 litro bawat araw.
Para sa mga partikular na mausisa, maaari mong tingnan ang mga talaan para sa bawat indibidwal na halaga

Dito ko nalaman na kapag bukas ang gripo, umaagos ang tubig sa bilis na humigit-kumulang 1 litro kada 5 s.
Ngunit sa form na ito ang mga istatistika ay malamang na hindi masyadong maginhawang tingnan. May kakayahan din ang Majordomo na tingnan ang mga chart ng pagkonsumo ayon sa araw, linggo at buwan. Dito, halimbawa, ay isang graph ng pagkonsumo sa mga bar

Sa ngayon isang linggo pa lang ang data ko. Sa isang buwan, ang graph na ito ay magiging mas indicative - bawat araw ay magkakaroon ng hiwalay na column. Ang larawan ay bahagyang nasira ng mga pagsasaayos sa mga halaga na manu-manong ipinasok ko (ang pinakamalaking haligi). At hindi pa malinaw kung hindi ko naitakda nang tama ang pinakaunang mga halaga, halos isang cube na mas kaunti, o kung ito ay isang bug sa firmware at hindi lahat ng litro ay binilang. Kailangan ng karagdagang panahon.
Ang mga graph mismo ay nangangailangan pa rin ng ilang magic, whitewashing, pagpipinta. Marahil ay gagawa din ako ng isang graph ng pagkonsumo ng memorya para sa mga layunin ng pag-debug - kung sakaling may tumutulo doon. Marahil ay ipapakita ko kahit papaano ang mga panahon na walang Internet. Sa ngayon, ang lahat ng ito ay nasa antas ng mga ideya.
Konklusyon
Ngayon ang aking apartment ay naging mas matalino. Sa gayong maliit na aparato, magiging mas maginhawa para sa akin na subaybayan ang pagkonsumo ng tubig sa bahay. Kung kanina ay nagagalit ako sa βmuli, marami tayong nainom na tubig sa loob ng isang buwan,β ngayon ay mahahanap ko na ang pinagmumulan ng pagkonsumo na ito.
Maaaring kakaiba ang ilan na tingnan ang mga pagbasa sa screen kung ito ay isang metro ang layo mula sa mismong metro. Ngunit sa hindi masyadong malayong hinaharap, plano kong lumipat sa isa pang apartment, kung saan magkakaroon ng maraming mga pagtaas ng tubig, at ang mga metro mismo ay malamang na matatagpuan sa landing. Kaya ang isang malayuang aparato sa pagbabasa ay magiging lubhang kapaki-pakinabang.
Plano ko ring palawakin ang functionality ng device. Nakatingin na ako sa mga motorized valves. Ngayon, upang ilipat ang boiler sa tubig ng lungsod, kailangan kong i-on ang 3 gripo sa isang niche na mahirap maabot. Ito ay magiging mas maginhawang gawin ito sa isang pindutan na may kaukulang indikasyon. Well, siyempre, ito ay nagkakahalaga ng pagpapatupad ng proteksyon laban sa mga tagas.
Sa artikulong inilarawan ko ang aking bersyon ng isang device batay sa ESP8266. Sa aking opinyon, nakabuo ako ng isang napaka-kagiliw-giliw na bersyon ng micropython firmware gamit ang mga coroutine - simple at maganda. Sinubukan kong ilarawan ang marami sa mga nuances at pagkukulang na naranasan ko sa panahon ng kampanya. Marahil ay inilarawan ko nang personal ang lahat, bilang isang mambabasa, mas madali para sa akin na laktawan ang mga hindi kinakailangang bagay kaysa isipin kung ano ang hindi nasabi.
Gaya ng nakasanayan, bukas ako sa nakabubuo na pagpuna.
Pinagmulan: www.habr.com
