Pernah sistem otomasi rumah, atau yang sering disebut "rumah pintar", sangat mahal dan hanya orang kaya yang mampu membelinya. Hari ini di pasaran Anda dapat menemukan kit yang cukup murah dengan sensor, tombol / sakelar dan aktuator untuk mengendalikan pencahayaan, soket, ventilasi, pasokan air, dan konsumen lainnya. Dan bahkan DIY-shnik yang
paling krivoruky dapat bergabung dengan perangkat cantik dan mengoleksi rumah pintar dengan harga murah.

Sebagai aturan, perangkat yang diusulkan adalah sensor atau aktuator. Mereka membuatnya mudah untuk menerapkan skenario seperti "nyalakan lampu ketika sensor gerak dipicu" atau "sakelar di pintu keluar memadamkan cahaya di seluruh apartemen". Tetapi telemetri entah bagaimana tidak berhasil. Paling-paling, ini adalah grafik suhu dan kelembaban, atau daya sesaat di outlet tertentu.
Baru-baru ini saya memasang meteran air dengan output pulsa. Saklar buluh dipicu melalui setiap liter melalui meter dan menutup kontak. Satu-satunya yang tersisa adalah menempel pada kabel dan mencoba untuk mendapatkan keuntungan darinya. Misalnya, analisis konsumsi air per jam dan hari dalam seminggu. Nah, jika ada beberapa riser untuk air di apartemen, maka akan lebih mudah untuk melihat semua indikator saat ini di satu layar daripada naik relung yang sulit dijangkau dengan senter.
Di bawah potongan, versi perangkat saya didasarkan pada ESP8266, yang menghitung pulsa dari meter air dan mengirimkan bacaan ke server rumah pintar melalui MQTT. Kami akan memprogram dalam micropython menggunakan perpustakaan uasyncio. Saat membuat firmware, saya menemukan beberapa kesulitan menarik, yang juga akan saya bahas di artikel ini. Ayo pergi!
Skema

Jantung dari seluruh rangkaian adalah modul pada mikrokontroler ESP8266. ESP-12 awalnya direncanakan, tetapi milik saya ternyata rusak. Saya harus puas dengan modul ESP-07, yang tersedia. Untungnya, keduanya sama dalam kesimpulan dan fungsionalitas, perbedaannya hanya pada antena - ESP-12 memiliki built-in, dan ESP-07 memiliki yang eksternal. Namun, bahkan tanpa antena WiFi, sinyal di kamar mandi saya tertangkap secara normal.
Penjilidan modul adalah standar:
- tombol reset dengan suspender dan kapasitor (meskipun keduanya sudah ada di dalam modul)
- Aktifkan sinyal (CH_PD) ditarik ke kekuasaan
- GPIO15 ditarik ke tanah. Ini hanya diperlukan pada awalnya, tetapi saya masih tidak memiliki apa-apa lagi untuk melekat pada kaki ini
Untuk meletakkan modul ke mode firmware, Anda harus menutup GPIO2 ke ground, dan agar lebih nyaman, saya telah menyediakan tombol Boot. Dalam kondisi normal, pin ini ditarik ke daya.
Status jalur GPIO2 diperiksa hanya pada awal pekerjaan - ketika daya diterapkan atau segera setelah reset. Jadi modul memuat seperti biasa, atau masuk ke mode firmware. Setelah memuat, output ini dapat digunakan sebagai GPIO biasa. Nah, karena sudah ada tombol di sana, Anda dapat meletakkan beberapa fungsi yang berguna di sana.
Untuk pemrograman dan debugging, saya akan menggunakan UART, yang dibawa ke sisir. Bila perlu - saya cukup mencolokkan adaptor USB-UART. Anda hanya perlu mengingat bahwa modul ini ditenagai oleh 3.3V. Jika Anda lupa mengganti adaptor ke tegangan ini dan menerapkan 5V, modul kemungkinan besar akan terbakar.
Saya tidak punya masalah dengan listrik di kamar mandi - stopkontak terletak sekitar satu meter dari meter, jadi saya akan menyalakannya dari 220V. Sebagai sumber daya, saya akan mengerjakan
blok kecil
HLK-PM03 dari Tenstar Robot. Secara pribadi, saya ketat dengan elektronik analog dan listrik, tapi di sini ada catu daya yang sudah jadi dalam wadah kecil.
Untuk memberi sinyal pada mode operasi, saya menyediakan LED yang terhubung ke GPIO2. Namun, saya tidak mulai menyoldernya, karena modul ESP-07 sudah memiliki LED, apalagi, terhubung ke GPIO2 yang sama. Tapi biarlah itu ada di papan - tiba-tiba saya ingin membawa LED ini ke kasing.
Kami lolos ke yang paling menarik. Meter air tidak memiliki logika, mereka tidak dapat diminta untuk pembacaan saat ini. Satu-satunya hal yang tersedia bagi kita adalah pulsa - menutup kontak saklar buluh setiap liter. Kesimpulan dari saklar buluh diatur di GPIO12 / GPIO13. Saya akan menyalakan resistor pull-up secara terprogram di dalam modul.
Awalnya, saya lupa memberikan resistor R8 dan R9, dan dalam versi board saya, mereka tidak. Tetapi karena saya sudah menempatkan skema ini pada tampilan publik, ada baiknya memperbaiki pengawasan ini. Resistor diperlukan agar tidak membakar port jika firmware bermasalah dan menempatkan unit pada pin, dan buluh beralih memendekkan baris ini ke ground (maksimum 3.3V / 1000Ohm = 3.3mA akan mengalir bersama resistor).
Inilah saatnya memikirkan apa yang harus dilakukan jika listrik padam. Opsi pertama adalah meminta server untuk penghitung awal di awal. Tetapi ini akan membutuhkan komplikasi signifikan dari protokol pertukaran. Selain itu, pengoperasian perangkat dalam hal ini tergantung pada status server. Jika, setelah mematikan lampu, server tidak memulai (atau mulai nanti), maka meter air tidak akan dapat meminta nilai awal dan akan bekerja dengan tidak benar.
Oleh karena itu, saya memutuskan untuk menerapkan penyimpanan nilai penghitung dalam chip memori yang terhubung melalui I2C. Saya tidak memiliki persyaratan khusus untuk ukuran memori flash - Saya hanya perlu menyimpan 2 angka (jumlah liter dengan meter air panas dan dingin). Bahkan modul terkecil akan dilakukan. Tetapi pada jumlah siklus rekaman Anda perlu memperhatikan. Untuk sebagian besar modul, ini adalah 100 ribu siklus, untuk beberapa hingga satu juta.
Tampaknya sejuta banyak. Tetapi selama 4 tahun tinggal di apartemen saya, saya mengonsumsi sedikit lebih dari 500 meter kubik air, itu 500 ribu liter! Dan 500 ribu entri dalam flash. Dan ini hanya air dingin. Anda bisa, tentu saja, menyolder chip setiap beberapa tahun, tetapi ternyata ada chip FRAM. Dari sudut pandang pemrograman, ini adalah EEPROM I2C yang sama, tetapi dengan sejumlah besar siklus penulisan ulang (ratusan juta). Itu hanya sampai semuanya mencapai toko dengan chip sedemikian rupa, jadi untuk saat ini 24LC512 biasa akan bertahan.
Papan sirkuit
Awalnya, saya berencana untuk membayar di rumah. Oleh karena itu, papan dirancang sebagai satu sisi. Tetapi setelah satu jam yang panjang dengan besi laser dan topeng solder (tanpanya itu tidak aman), saya memutuskan untuk memesan papan dari Cina.

Hampir sebelum memesan papan, saya menyadari bahwa selain chip memori flash pada bus I2C, Anda dapat mengambil sesuatu yang berguna, seperti tampilan. Apa sebenarnya untuk menghasilkan itu masih menjadi pertanyaan, tetapi Anda harus berkembang biak di papan tulis. Nah, karena saya akan memesan papan di pabrik, tidak masuk akal membatasi diri pada papan satu sisi, jadi garis pada I2C adalah satu-satunya di bagian belakang papan.
Satu kusen besar juga dikaitkan dengan kabel satu sisi. Karena papan digambar di satu sisi, trek dan komponen SMD direncanakan untuk ditempatkan di satu sisi, dan komponen output, konektor dan catu daya di sisi lain. Ketika saya menerima papan dalam sebulan, saya lupa tentang rencana awal dan membuka ritsleting semua komponen di sisi depan. Dan hanya ketika datang untuk menyolder catu daya, ternyata plus dan minus bercerai sebaliknya. Saya harus ke pertanian kolektif dengan jumper. Pada gambar di atas, saya telah mengubah kabel, tetapi tanah dilempar dari satu bagian papan ke yang lain melalui output dari tombol Boot (meskipun mungkin untuk menggambar trek pada lapisan kedua).
Ternyata begini

Perumahan
Langkah selanjutnya adalah perumahan. Dengan printer 3D, ini bukan masalah. Saya tidak terlalu repot - saya hanya menggambar sekotak ukuran yang tepat dan membuat guntingan di tempat yang tepat. Penutup terpasang ke tubuh pada sekrup kecil.

Saya sudah menyebutkan bahwa tombol Boot dapat digunakan sebagai tombol untuk keperluan umum - di sini kita membawanya ke panel depan. Untuk melakukan ini, saya menggambar "sumur" khusus di mana tombol tinggal.

Di dalam case ada juga tunggul di mana papan dipasang dan diperbaiki dengan sekrup M3 tunggal (tidak ada lagi ruang di papan)
Layar dipilih ketika saya mencetak versi yang pas dari case. Dua baris standar tidak cocok dengan kasus ini, tetapi layar OLED SSD1306 128x32 ditemukan di gimbal. Ini kecil, tapi saya tidak harus melihatnya setiap hari - itu akan berjalan lancar.
Memperkirakan kedua cara dan bagaimana kabel akan diletakkan darinya, saya memutuskan untuk menempelkan layar di tengah kasing. Ergonomi, tentu saja, di bawah alas tiang - tombol di atas, tampilan di bawah. Tetapi saya sudah mengatakan bahwa ide untuk mengacaukan tampilan sudah terlambat dan itu terlalu malas untuk mengatur ulang papan untuk memindahkan tombol.
Perangkat selesai. Modul tampilan ditempelkan ke nozel hot-melt


Hasil akhirnya dapat dilihat pada KDPV
Firmware
Mari kita beralih ke bagian perangkat lunak. Untuk kerajinan kecil seperti itu, saya sangat suka menggunakan bahasa Python (
micropython ) - kode ini sangat kompak dan mudah dimengerti. Untungnya, tidak perlu turun ke tingkat register untuk memeras mikrodetik - semuanya dapat dilakukan dari python.
Tampaknya semua sederhana, tetapi tidak terlalu - beberapa fungsi independen diuraikan dalam perangkat:
- Pengguna menekan tombol dan melihat layar
- Centang centang dan perbarui nilai dalam memori flash
- Modul memonitor sinyal WiFi dan menghubungkan kembali jika perlu
- Nah, tanpa lampu yang berkedip, Anda tidak bisa
Tidak mungkin untuk menganggap bahwa satu fungsi tidak berfungsi jika yang lain bodoh karena suatu alasan. Saya sudah makan kaktus di proyek-proyek lain dan sekarang saya melihat gangguan dalam gaya "melewatkan satu liter lagi, karena pada saat itu tampilan diperbarui" atau "pengguna tidak dapat melakukan apa pun ketika modul terhubung ke WiFi". Tentu saja, beberapa hal dapat dilakukan melalui interupsi, tetapi Anda dapat mengalami batasan pada durasi, bersarang panggilan, atau perubahan variabel non-atom. Nah, kode yang melakukan segalanya dan segera dengan cepat berubah menjadi berantakan.
Dalam
proyek ini, saya menggunakan multitasking preemptive klasik dan FreeRTOS lebih serius, tetapi dalam hal ini model
coroutine dan perpustakaan uasync ternyata jauh lebih cocok. Terlebih lagi, implementasi Pitutinov corutin hanyalah sebuah bom - bagi programmer, semuanya dilakukan dengan sederhana dan mudah. Cukup tulis logika Anda sendiri, cukup beri tahu saya di tempat mana Anda dapat beralih di antara utas.
Saya sarankan mengeksplorasi perbedaan antara crowding out dan multitasking kompetitif secara opsional. Sekarang, mari kita beralih ke kode.
Setiap penghitung diproses oleh instance dari kelas Penghitung. Pertama-tama, nilai awal penghitung dikurangi dari EEPROM (value_storage) - ini adalah bagaimana pemulihan dari kegagalan daya diterapkan.
Pin diinisialisasi dengan built-in pull-up ke daya: jika saklar buluh ditutup, itu adalah nol pada saluran, jika saluran terbuka, saluran ditarik ke daya dan pengontrol membaca satu.
Juga, tugas terpisah diluncurkan di sini, yang akan melakukan polling pin. Setiap penghitung akan menjalankan tugasnya sendiri. Ini kodenya
""" Poll pin and advance value when another litre passed """ async def _switchcheck(self): last_checked_pin_state = self._pin.value()
Diperlukan waktu 25ms untuk memfilter kontak yang terpental, dan pada saat yang sama mengatur seberapa sering tugas bangun (saat tugas ini sedang tidur, tugas lain bekerja). Setiap 25ms fungsi bangun, periksa pin dan jika kontak saklar buluh ditutup, maka liter lain telah melewati konter dan ini perlu diproses.
def _another_litre_passed(self): self._value += 1 self._value_changed = True self._value_storage.write(self._value)
Memproses liter berikutnya sepele - penghitung hanya meningkat. Nah, nilai baru akan menyenangkan untuk menulis ke flash drive.
Untuk kemudahan penggunaan, "pengakses" disediakan.
def value(self): self._value_changed = False return self._value def set_value(self, value): self._value = value self._value_changed = False
Nah, sekarang kita akan mengambil keuntungan dari kesenangan python dan pustaka uasync dan membuat objek penghitung bisa ditunggu (bagaimana ini bisa diterjemahkan ke dalam bahasa Rusia? Yang bisa diharapkan?)
def __await__(self): while not self._value_changed: yield from asyncio.sleep(0) return self.value() __iter__ = __await__
Ini adalah fungsi yang nyaman yang menunggu hingga nilai penghitung diperbarui - fungsi bangun dari waktu ke waktu dan memeriksa tanda _value_changed. Lelucon dari fungsi ini adalah bahwa kode panggilan dapat tertidur pada panggilan ke fungsi ini dan tidur sampai nilai baru diterima.
Tapi bagaimana dengan interupsi?Ya, di tempat ini Anda dapat menjebak saya, mengatakan bahwa ia sendiri mengatakan tentang gangguan, tetapi sebenarnya ia mengatur jajak pendapat bodoh dari pin. Sebenarnya, interupsi adalah hal pertama yang saya coba. Dalam ESP8266, Anda dapat mengatur interrupt edge, dan bahkan menulis handler untuk interrupt ini dengan python. Dalam interupsi ini, Anda dapat memperbarui nilai suatu variabel. Mungkin, ini akan cukup jika penghitung adalah perangkat budak - yang menunggu sampai diminta untuk nilai ini.
Sayangnya (atau untungnya?) Perangkat saya aktif, ia harus mengirim pesan menggunakan protokol MQTT dan menulis data ke EEPROM. Dan di sini pembatasan sudah datang - Anda tidak dapat mengalokasikan memori dan menggunakan tumpukan besar dalam interupsi, yang berarti Anda bisa lupa mengirim pesan melalui jaringan. Ada roti seperti micropython.schedule () yang memungkinkan Anda menjalankan beberapa jenis fungsi "segera", tetapi pertanyaannya adalah "apa gunanya?". Tiba-tiba kami mengirim beberapa jenis pesan sekarang, dan di sini potongan interrupt dan merusak nilai-nilai variabel. Atau, misalnya, nilai penghitung baru tiba dari server sementara kami masih tidak menuliskan yang lama. Secara umum, Anda perlu pagar sinkronisasi atau keluar entah bagaimana berbeda.
Dan dari waktu ke waktu, RuntimeError crash: jadwal stack penuh dan siapa yang tahu mengapa?
Dengan jajak pendapat eksplisit dan uasync, dalam hal ini entah bagaimana lebih indah dan lebih dapat diandalkan.
Saya bekerja dengan EEPROM di kelas kecil
class EEPROM(): i2c_addr = const(80) def __init__(self, i2c): self.i2c = i2c self.i2c_buf = bytearray(4)
Dalam python, bekerja dengan byte secara langsung itu sulit, tetapi byte yang ditulis ke memori. Saya harus memperbaiki konversi antara integer dan byte menggunakan perpustakaan ustruct.
Agar tidak mentransfer objek I2C dan alamat sel memori setiap kali, saya membungkus semuanya dalam klasik kecil dan nyaman
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)
Objek I2C sendiri dibuat dengan parameter seperti itu
i2c = I2C(freq=400000, scl=Pin(5), sda=Pin(4))
Kami mendekati hal yang paling menarik - implementasi komunikasi dengan server melalui MQTT. Nah, protokol itu sendiri tidak perlu diimplementasikan -
implementasi asinkron siap pakai ditemukan di Internet. Di sini kita akan menggunakannya.
Semua yang paling menarik dikumpulkan di kelas CounterMQTTClient, yang didasarkan pada perpustakaan MQTTClient. Mari kita mulai dari pinggiran
Di sini, pin bola lampu dan tombol dibuat dan dikonfigurasikan, serta objek meter air dingin dan panas.
Dengan inisialisasi, tidak semuanya sepele
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())
Untuk mengatur parameter perpustakaan mqtt_as, kamus besar pengaturan yang berbeda digunakan - config. Sebagian besar pengaturan default sesuai dengan kita, tetapi banyak pengaturan perlu diatur secara eksplisit. Agar tidak mendaftarkan pengaturan langsung dalam kode, saya menyimpannya di file teks config.txt. Ini memungkinkan Anda untuk mengubah kode terlepas dari pengaturan, serta memusatkan beberapa perangkat identik dengan parameter yang berbeda.
Blok kode terakhir menjalankan beberapa coroutine untuk melayani berbagai fungsi sistem. Berikut ini contoh coroutine yang melayani penghitung
async def _counter_coro(self, counter, topic):
Dalam sebuah siklus, Corutin menunggu nilai penghitung baru dan segera setelah muncul, mengirim pesan menggunakan protokol MQTT. Potongan kode pertama mengirimkan nilai awal bahkan jika air tidak mengalir melalui penghitung.
Kelas dasar MQTTClient melayani dirinya sendiri, memulai koneksi WiFi dan menghubungkan kembali ketika koneksi terputus. Ketika status koneksi WiFi berubah, perpustakaan memberi tahu kami dengan memanggil 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)
Fungsi ini secara jujur dibungkam dari contoh. Dalam hal ini, ia mempertimbangkan jumlah pemutusan (internet_outages) dan durasinya. Ketika koneksi dipulihkan, downtime dikirim ke server.
Ngomong-ngomong, tidur terakhir diperlukan hanya untuk fungsi menjadi tidak sinkron - di perpustakaan itu dipanggil melalui menunggu, dan hanya fungsi dalam tubuh yang ada menunggu lain yang bisa dipanggil.
Selain menghubungkan ke WiFi, Anda juga perlu membuat koneksi dengan broker MQTT (server). Perpustakaan juga melakukan ini, tetapi kami diberi kesempatan untuk melakukan sesuatu yang berguna ketika koneksi dibuat.
async def mqtt_connection_handler(self, client): await client.subscribe(self._mqtt_cold_water_theme) await client.subscribe(self._mqtt_hot_water_theme)
Di sini kami berlangganan beberapa pesan - server sekarang memiliki kemampuan untuk menetapkan nilai penghitung saat ini dengan mengirim pesan yang sesuai.
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))
Fungsi ini memproses pesan masuk, dan tergantung pada topik (nama pesan), nilai salah satu penghitung diperbarui
Beberapa fungsi pembantu
Fungsi ini mengirim pesan jika koneksi dibuat. Jika tidak ada koneksi, pesan diabaikan.
Dan ini hanyalah fungsi praktis yang menghasilkan dan mengirim pesan debug.
async def publish_debug_msg(self, subtopic, msg): await self.publish_msg("{}/{}".format(self._mqtt_debug_water_theme, subtopic), str(msg))
Begitu banyak teks, tetapi kami belum mengedipkan LED. Di sini
Saya telah menyediakan 2 mode kedipan. Jika koneksi terputus (atau sedang dibangun), maka perangkat akan berkedip cepat. Jika koneksi tersambung, perangkat berkedip setiap 5 detik sekali. Jika perlu, di sini Anda dapat menerapkan mode berkedip lainnya.
Namun LED begitu memanjakan. Kami masih melambaikan tangan ke layar.
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)
Inilah yang saya bicarakan - betapa sederhana dan nyamannya dengan coroutine. Fungsi kecil ini menggambarkan SEMUA interaksi pengguna. Corutin hanya menunggu tombol untuk menekan dan menyalakan layar selama 3 detik. Layar menunjukkan pembacaan meter saat ini.
Masih ada beberapa hal kecil. Berikut adalah fungsi yang menjalankan seluruh pertanian (kembali). Siklus utama hanya membahas pengiriman berbagai informasi debug sekali dalam satu menit. Secara umum, saya mengutip apa adanya - terutama komentar, saya pikir, tidak perlu
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):
Nah, beberapa pengaturan dan konstanta lagi untuk menyelesaikan deskripsi
Semua dimulai dari sini
client = CounterMQTTClient() loop = asyncio.get_event_loop() loop.run_until_complete(client.main())
Sesuatu dengan ingatan saya telah menjadi
Jadi, semua kode ada di sana. Saya mengunggah file menggunakan utilitas ampy - memungkinkan mereka untuk diunggah ke flash drive internal (yang ada di ESP-07 sendiri) dan kemudian diakses dari program sebagai file normal. Di sana saya mengunggah mqtt_as, uasyncio, ssd1306 dan perpustakaan koleksi (digunakan di dalam mqtt_as) yang saya gunakan.
Kami mulai dan ... Kami menerima MemoryError. Selain itu, semakin saya mencoba memahami persis di mana memori bocor, semakin saya mengatur debug hasil cetak, semakin cepat kesalahan ini terjadi. Sebuah gugelezh singkat membuat saya mengerti bahwa, pada prinsipnya, mikrokontroler hanya memiliki 30kb memori di mana 65kb kode (bersama dengan perpustakaan) tidak cocok dengan cara apa pun.
Tapi ada cara. Ternyata micropython tidak mengeksekusi kode langsung dari file .py - file ini dikompilasi terlebih dahulu. Dan itu mengkompilasi langsung pada mikrokontroler, berubah menjadi bytecode, yang kemudian disimpan dalam memori. Nah, agar kompiler berfungsi, Anda juga membutuhkan sejumlah RAM.
Caranya adalah dengan menyelamatkan mikrokontroler dari kompilasi intensif sumber daya. Anda dapat mengkompilasi file di komputer besar, dan mengisi bytecode yang sudah selesai ke dalam mikrokontroler. Untuk melakukan ini, unduh firmware micropython dan bangun
utilitas mpy-cross .
Saya tidak menulis Makefile, tetapi secara manual melewati dan mengkompilasi semua file yang diperlukan (termasuk perpustakaan) seperti ini
mpy-cross water_counter.py
Tetap hanya mengunggah file dengan ekstensi .mpy, tidak lupa untuk menghapus dulu .py dari sistem file perangkat.
Saya melakukan semua pengembangan dalam program (IDE?) ESPlorer. Ini memungkinkan Anda untuk mengunggah skrip ke mikrokontroler dan segera menjalankannya. Dalam kasus saya, semua logika dan pembuatan semua objek terletak di file water_counter.py (.mpy). Tetapi agar semua ini dimulai secara otomatis, di awal harus ada file lain bernama main.py. Dan itu harus persis .py, dan bukan .mpy yang telah dikompilasi sebelumnya. Ini adalah kontennya yang sepele
import water_counter
Kami mulai - semuanya bekerja. Tapi ada sedikit memori bebas - sekitar 1kb. Saya masih punya rencana untuk memperluas fungsionalitas perangkat, dan kilobyte ini jelas tidak akan cukup bagi saya. Namun ternyata ada jalan keluar untuk kasus ini.
Ini masalahnya. Meskipun file dikompilasi menjadi bytecode dan terletak di sistem file internal, sebenarnya mereka masih dimuat ke dalam RAM dan dieksekusi dari sana. Tapi ternyata micropython mampu mengeksekusi bytecode langsung dari memori flash, tetapi untuk ini Anda perlu menanamkannya langsung ke dalam firmware. Ini tidak sulit, walaupun butuh waktu yang cukup lama di netbook saya (hanya di sana saya dapat Linux).
Algoritma adalah sebagai berikut:
- Unduh dan pasang ESP Open SDK . Hal ini membangun kompiler dan pustaka untuk program di bawah ESP8266. Itu dirakit sesuai dengan instruksi pada halaman utama proyek (saya memilih pengaturan STANDALONE = ya)
- Unduh Micropython Sorts
- Jatuhkan perpustakaan yang diperlukan ke port / esp8266 / modul di dalam pohon micropython
- Kami merakit firmware sesuai dengan instruksi dalam file ports / esp8266 / README.md
- Tuangkan firmware ke dalam mikrokontroler (saya melakukan ini pada Windows dengan program ESP8266Flasher atau Python esptool)
Itu saja, sekarang 'impor ssd1306' akan menaikkan kode langsung dari firmware dan RAM tidak akan dihabiskan untuk ini. Dengan trik ini, saya hanya mengunduh kode perpustakaan ke dalam firmware, sedangkan kode program utama dijalankan dari sistem file. Ini memungkinkan Anda untuk dengan mudah memodifikasi program tanpa mengkompilasi ulang firmware. Saat ini saya memiliki sekitar 8,5 kb RAM gratis. Ini akan memungkinkan untuk menerapkan cukup banyak fungsi yang berguna berbeda di masa depan. Nah, jika tidak ada cukup memori sama sekali, maka Anda juga bisa mendorong program utama ke dalam firmware.
Dan apa yang harus dilakukan dengan itu sekarang?
Ok, perangkat keras disolder, firmware ditulis, kotak dicetak, perangkat macet ke dinding dan berkedip gembira dengan bola lampu. Tetapi sementara ini semua adalah kotak hitam (dalam arti harfiah dan kiasan) dan pengertian itu masih belum cukup. Sudah waktunya untuk melakukan sesuatu dengan pesan MQTT yang dikirim ke server.
"Rumah pintar" saya berputar di
sistem Majordomo . Modul MQTT berada di luar kotak, atau dapat dengan mudah diinstal dari pasar add-ons - Saya tidak lagi ingat dari mana asalnya. Hal MQTT tidak swasembada - yang disebut broker - server yang menerima, mengurutkan dan mengalihkan pesan MQTT ke klien. Saya menggunakan mosquitto, yang (seperti majordomo) berjalan di netbook yang sama.
Setelah perangkat mengirim pesan setidaknya satu kali, nilainya akan segera muncul dalam daftar.

Nilai-nilai ini sekarang dapat dikaitkan dengan objek sistem, mereka dapat digunakan dalam skrip otomatisasi dan mengalami berbagai analisis - semua ini di luar ruang lingkup artikel ini. Siapa yang tertarik dengan sistem majordomo, saya dapat merekomendasikan
saluran Electronics In Lens - seorang teman juga membangun rumah pintar dan secara cerdik berbicara tentang pengaturan sistem.
Saya hanya akan menampilkan beberapa grafik. Ini adalah grafik sederhana dari nilai harian.

Dapat dilihat bahwa hampir tidak ada yang menggunakan air di malam hari. Beberapa kali seseorang pergi ke toilet, dan sepertinya filter osmosis terbalik menghisap beberapa liter semalam. Di pagi hari, konsumsi meningkat secara signifikan. Biasanya saya menggunakan air dari ketel, tetapi kemudian saya ingin mandi dan beralih sementara ke air panas kota - ini juga terlihat jelas di grafik bagian bawah.
Dari jadwal ini, saya mengetahui bahwa pergi ke toilet adalah 6-7 liter air, mandi - 20-30 liter, mencuci piring sekitar 20 liter, dan untuk mandi Anda membutuhkan 160 liter. Selama sehari, keluarga saya mengkonsumsi sekitar 500-600l.
Untuk yang paling ingin tahu, Anda dapat melihat entri untuk setiap nilai individual

Dari sini saya belajar bahwa dengan keran terbuka, air mengalir dengan kecepatan sekitar 1 liter dalam 5 detik.
Tetapi dalam bentuk ini, statistik mungkin tidak nyaman untuk ditonton. Di majordomo masih ada peluang untuk melihat grafik konsumsi berdasarkan hari, minggu dan bulan. Misalnya, grafik konsumsi dalam kolom

Sejauh ini saya hanya punya data selama seminggu. Dalam sebulan, bagan ini akan lebih indikatif - kolom terpisah akan sesuai dengan setiap hari. Penyesuaian nilai-nilai yang saya masukkan secara manual merusak sedikit gambar (kolom terbesar). Dan belum jelas apakah saya salah mengatur nilai pertama hampir kubus kurang, atau apakah ini bug dalam firmware dan tidak semua liter masuk ke set-off. Butuh lebih banyak waktu.
Masih perlu menyulap grafik sendiri, memutihkan, warna. Mungkin saya juga akan membangun grafik konsumsi memori untuk keperluan debugging - tiba-tiba ada sesuatu yang bocor di luar sana. Mungkin saya entah bagaimana akan menampilkan periode ketika Internet tidak tersedia. Sementara semua ini berputar pada level ide.
Kesimpulan
Hari ini, apartemen saya menjadi sedikit lebih pintar. Dengan perangkat sekecil itu, akan lebih nyaman bagi saya untuk memantau konsumsi air di rumah. Jika sebelumnya saya marah "lagi mereka mengkonsumsi banyak air dalam sebulan", sekarang saya dapat menemukan sumber konsumsi ini.
Akan terasa aneh bagi seseorang untuk menonton pembacaan di layar jika jaraknya satu meter dari meter itu sendiri. Tetapi dalam waktu yang tidak terlalu lama, saya berencana untuk pindah ke apartemen lain, di mana akan ada beberapa anak tangga, dan meter itu sendiri kemungkinan besar akan terletak di pendaratan. Jadi alat baca jarak jauh akan sangat membantu.
Saya juga berencana untuk memperluas fungsionalitas perangkat. Saya sudah melihat katup bermotor. Sekarang untuk mengganti air boiler-city saya perlu mengubah 3 keran di ceruk yang tidak dapat diakses. Akan jauh lebih mudah untuk melakukan ini dengan satu tombol dengan indikasi yang sesuai. Yah, tentu saja, ada baiknya menerapkan perlindungan terhadap kebocoran.
Dalam artikel tersebut saya memberi tahu versi perangkat saya berdasarkan ESP8266. Menurut pendapat saya, saya mendapatkan versi yang sangat menarik dari firmware micropython menggunakan coroutine - sederhana dan cantik.
Saya mencoba menggambarkan banyak nuansa dan sekolah yang saya temui dalam kampanye. Mungkin saya menggambarkan semuanya dengan terlalu detail, bagi saya pribadi, sebagai pembaca, lebih mudah untuk menyia-nyiakan terlalu banyak daripada kemudian memikirkan apa yang tidak terucapkan.Seperti biasa, saya terbuka untuk kritik yang membangun.Kode SumberSirkuit dan Model PapanKasus