Бид усны тоолуурыг ухаалаг гэрт холбодог

Нэгэн цагт гэрийн автоматжуулалтын системүүд буюу "ухаалаг гэр" нь маш үнэтэй байсан бөгөөд зөвхөн баячууд л үүнийг төлж чаддаг байв. Өнөөдөр зах зээл дээр гэрэлтүүлэг, залгуур, агааржуулалт, усан хангамж болон бусад хэрэглэгчдийг хянах мэдрэгч, товчлуур / унтраалга, идэвхжүүлэгч бүхий нэлээд хямд иж бүрдлийг олж болно. Хамгийн муруй DIY хүн ч гэсэн гоо сайханд оролцож, ухаалаг гэрт зориулсан төхөөрөмжийг хямд үнээр угсарч чадна.

Бид усны тоолуурыг ухаалаг гэрт холбодог

Ерөнхийдөө санал болгож буй төхөөрөмжүүд нь мэдрэгч эсвэл идэвхжүүлэгч юм. Тэд "хөдөлгөөн мэдрэгч асаалттай үед гэрлийг асаа" эсвэл "гарцын ойролцоох унтраалга нь орон сууцны гэрлийг бүхэлд нь унтраадаг" гэх мэт хувилбаруудыг хэрэгжүүлэхэд хялбар болгодог. Гэхдээ ямар нэгэн байдлаар телеметрийн хувьд бүх зүйл бүтсэнгүй. Хамгийн сайн нь энэ нь температур, чийгшлийн график эсвэл тодорхой гаралтын үед агшин зуурын эрчим хүчний график юм.

Би саяхан импульсийн гаралттай усны тоолуур суурилуулсан. Тоолуураар дамжин өнгөрөх литр тутамд зэгсэн унтраалга идэвхжиж, контактыг хаадаг. Ганцхан утастай зууралдаж ашиг шимийг нь хүртэх л үлдлээ. Жишээлбэл, усны хэрэглээг долоо хоногийн цаг, өдрөөр нь шинжил. Хэрэв орон сууцанд хэд хэдэн ус өргөх төхөөрөмж байгаа бол гар чийдэнгээр хүрэхэд хэцүү тор руу авирахаас илүү одоогийн бүх үзүүлэлтийг нэг дэлгэц дээр харах нь илүү тохиромжтой.

Зүсэлтийн доор усны тоолуурын импульсийг тоолж, MQTT-ээр дамжуулан ухаалаг гэрийн сервер рүү уншдаг ESP8266 дээр суурилсан төхөөрөмжийн миний хувилбар байна. Бид micropython дээр uasyncio номын санг ашиглан программчлах болно. Програм хангамжийг бүтээхдээ би хэд хэдэн сонирхолтой бэрхшээлтэй тулгарсан бөгөөд энэ нийтлэлд мөн хэлэлцэх болно. Яв!

Энэ схем

Бид усны тоолуурыг ухаалаг гэрт холбодог

Бүхэл бүтэн хэлхээний зүрх нь ESP8266 микроконтроллер дээрх модуль юм. ESP-12-г анх төлөвлөж байсан боловч минийх гэмтэлтэй болсон. Бид бэлэн байгаа ESP-07 модульд сэтгэл хангалуун байх ёстой байсан. Аз болоход тэдгээр нь тээглүүр болон функциональ байдлын хувьд адилхан бөгөөд цорын ганц ялгаа нь антенн дээр байдаг - ESP-12 нь суурилуулсан, ESP-07 нь гаднах антентай байдаг. Гэсэн хэдий ч WiFi антенгүй байсан ч угаалгын өрөөний дохиог хэвийн хүлээж авдаг.

Стандарт модулийн утаснууд:

  • татах ба конденсатор бүхий дахин тохируулах товчлуур (хэдийгээр хоёулаа модуль дотор байгаа)
  • Идэвхжүүлэх дохио (CH_PD) асаалттай байна
  • GPIO15 нь газарт татагдсан. Энэ нь зөвхөн эхэнд л хэрэгтэй, гэхдээ надад энэ хөлөнд хавсаргах зүйл алга, надад хэрэггүй болсон

Модулийг програм хангамжийн горимд оруулахын тулд та GPIO2-г газардуулах богино холболт хийх хэрэгтэй бөгөөд үүнийг илүү тохиромжтой болгохын тулд би ачаалах товчийг өгсөн. Хэвийн нөхцөлд энэ зүү хүчийг татахад татагдана.

GPIO2 шугамын төлөвийг зөвхөн ашиглалтын эхэнд - цахилгаан асаалттай үед эсвэл дахин тохируулсны дараа шалгадаг. Тиймээс модуль ердийнхөөрөө ачаалах эсвэл програм хангамжийн горимд шилждэг. Ачаалагдсаны дараа энэ зүүг ердийн GPIO болгон ашиглаж болно. За, тэнд аль хэдийн товчлуур байгаа тул та түүнд хэрэгтэй функцийг хавсаргаж болно.

Програмчлалын болон дибаг хийхдээ би сам руу гардаг UART-г ашиглах болно. Шаардлагатай үед би тэнд USB-UART адаптер холбодог. Та зүгээр л модуль нь 3.3V-ээр тэжээгддэг гэдгийг санах хэрэгтэй. Хэрэв та адаптераа энэ хүчдэлд шилжүүлж, 5V-ыг нийлүүлэхээ мартвал модуль шатах магадлалтай.

Би угаалгын өрөөнд цахилгаантай холбоотой ямар ч асуудалгүй - гаралтын хэсэг нь тоолуураас нэг метр орчим байрладаг тул би 220 В-оор тэжээгдэх болно. Эрчим хүчний эх үүсвэрийн хувьд би жижиг хэмжээтэй байх болно HLK-PM03 блок Tenstar робот. Би хувьдаа аналоги болон цахилгаан эрчим хүчний электроникийн хувьд хэцүү байдаг, гэхдээ энд жижиг хайрцагт бэлэн цахилгаан хангамж байна.

Үйлдлийн горимд дохио өгөхийн тулд би GPIO2-д холбогдсон LED-ийг өгсөн. Гэсэн хэдий ч би гагнаагүй, учир нь ... ESP-07 модуль нь аль хэдийн LED-тэй бөгөөд GPIO2-д холбогдсон байна. Гэхдээ би энэ LED-ийг хайрцагт гаргахыг хүсч байвал самбар дээр байг.

Хамгийн сонирхолтой хэсэг рүүгээ орцгооё. Усны тоолуур ямар ч логикгүй тул та тэднээс одоогийн заалтыг асуух боломжгүй. Бидэнд байгаа цорын ганц зүйл бол импульс юм - литр тутамд зэгс шилжүүлэгчийн контактуудыг хаадаг. Миний зэгсэн шилжүүлэгчийн гаралтууд GPIO12/GPIO13-д холбогдсон. Би модуль дотор татах резисторыг программ ёсоор идэвхжүүлнэ.

Эхэндээ би R8 ба R9 резисторуудыг өгөхөө мартсан бөгөөд миний самбарын хувилбарт тэдгээр нь байхгүй байна. Гэхдээ би хүн бүрт харагдахаар диаграммыг аль хэдийн нийтэлсэн тул энэ зөрчлийг засах нь зүйтэй юм. Програм хангамж нь доголдож, зүүг нэг болгож, зэгсэн унтраалга нь энэ шугамыг газардуулгатай холбосон тохиолдолд портыг шатаахгүйн тулд резистор хэрэгтэй (резистор нь хамгийн ихдээ 3.3V/1000Ohm = 3.3mA урсах болно).

Цахилгаан тасарвал яах вэ гэдгээ бодох цаг болжээ. Эхний сонголт бол эхэнд серверээс анхны тоолуурын утгыг хүсэх явдал юм. Гэхдээ энэ нь солилцооны протоколд ихээхэн хүндрэл учруулах шаардлагатай болно. Түүнээс гадна, энэ тохиолдолд төхөөрөмжийн гүйцэтгэл нь серверийн төлөв байдлаас хамаарна. Хэрэв цахилгааныг унтраасны дараа сервер эхлэхгүй (эсвэл дараа нь эхлүүлсэн) усны тоолуур анхны утгыг хүсэх боломжгүй бөгөөд зөв ажиллахгүй болно.

Тиймээс би I2C-ээр холбогдсон санах ойн чип дэх тоолуурын утгыг хадгалахаар шийдсэн. Надад флаш санах ойн хэмжээнд ямар нэгэн тусгай шаардлага байхгүй - та зөвхөн 2 тоог (халуун, хүйтэн усны тоолуурын дагуу литрийн тоо) хадгалах хэрэгтэй. Хамгийн жижиг модуль ч гэсэн үүнийг хийх болно. Гэхдээ та бичлэг хийх мөчлөгийн тоонд анхаарлаа хандуулах хэрэгтэй. Ихэнх модулиудын хувьд энэ нь 100 мянган цикл, заримд нь сая хүртэл байдаг.

Сая гэдэг бол их юм шиг санагддаг. Гэхдээ орон сууцанд 4 жил амьдрахдаа би 500 гаруй шоо метр ус хэрэглэсэн, энэ нь 500 мянган литр юм! Мөн флаш дээр 500 мянган бичлэг. Энэ бол зүгээр л хүйтэн ус юм. Мэдээжийн хэрэг та чипийг хоёр жил тутамд дахин гагнах боломжтой, гэхдээ FRAM чипүүд байдаг. Програмчлалын үүднээс авч үзвэл энэ нь маш олон тооны дахин бичих циклтэй (хэдэн зуун сая) I2C EEPROM юм. Би ийм микро схемээр дэлгүүрт орж чадахгүй хэвээр байгаа тул одоохондоо ердийн 24LC512 зогсож байх болно.

Цахилгаан гүйдлийн хавтан

Эхэндээ би самбарыг гэртээ хийхээр төлөвлөж байсан. Тиймээс самбарыг нэг талт байдлаар зохион бүтээсэн. Гэхдээ лазер индүү, гагнуурын масктай нэг цаг зарцуулсны дараа (түүнгүйгээр энэ нь ямар ч боломжгүй юм) Хятадуудаас самбар захиалахаар шийдсэн хэвээр байна.

Бид усны тоолуурыг ухаалаг гэрт холбодог

Самбарыг захиалахаасаа өмнө би флаш санах ойн чипээс гадна дэлгэц гэх мэт өөр хэрэгтэй зүйлийг I2C автобусанд холбож болохыг ойлгосон. Үүнд яг юу гаргах вэ гэдэг асуулт хэвээр байгаа ч самбар дээр чиглүүлэх шаардлагатай. За, би үйлдвэрээс самбар захиалах гэж байсан болохоор өөрийгөө нэг талт хавтангаар хязгаарлах нь утгагүй байсан тул I2C шугамууд нь зөвхөн самбарын ар талд байдаг.

Мөн нэг талын утастай холбоотой нэг том асуудал байсан. Учир нь Самбарыг нэг талдаа зурсан тул зам, SMD эд ангиудыг нэг талд, гаралтын бүрэлдэхүүн хэсэг, холбогч, цахилгаан хангамжийг нөгөө талд байрлуулахаар төлөвлөжээ. Сарын дараа самбаруудыг хүлээн авахдаа би анхны төлөвлөгөөгөө мартаж, урд талын бүх эд ангиудыг гагнасан. Зөвхөн цахилгаан хангамжийг гагнах үед л нэмэх ба хасах утсыг урвуу утсаар холбосон байна. Би үсрэгчидтэй газар тариалан эрхлэх хэрэгтэй болсон. Дээрх зураг дээр би утсыг аль хэдийн өөрчилсөн боловч газар нь Ачаалах товчлуурын тээглүүрээр дамжин самбарын нэг хэсгээс нөгөөд шилждэг (хэдийгээр хоёр дахь давхарга дээр зам зурах боломжтой).

Энэ нь ийм болсон

Бид усны тоолуурыг ухаалаг гэрт холбодог

Орон сууц

Дараагийн алхам бол бие юм. Хэрэв танд 3D принтер байгаа бол энэ нь асуудал биш юм. Би тийм ч их санаа зовсонгүй - би зүгээр л зөв хэмжээтэй хайрцаг зурж, зөв ​​газарт хайчилбар хийсэн. Хавтас нь жижиг өөрөө түншдэг эрэг ашиглан биед бэхлэгддэг.

Бид усны тоолуурыг ухаалаг гэрт холбодог

Ачаалах товчлуурыг ерөнхий зориулалтын товчлуур болгон ашиглаж болно гэдгийг би аль хэдийн дурдсан тул бид үүнийг урд самбар дээр харуулах болно. Үүнийг хийхийн тулд би товчлуур байрладаг тусгай "худаг" зурсан.

Бид усны тоолуурыг ухаалаг гэрт холбодог

Хайрцаг дотор самбарыг суурилуулж, нэг M3 эрэг шургаар бэхэлсэн (самбар дээр зай байхгүй байсан)

Би хэргийн анхны хувилбарыг хэвлэхдээ дэлгэцийг аль хэдийн сонгосон. Стандарт хоёр мөрт уншигч энэ тохиолдолд тохирохгүй байсан ч доод хэсэгт OLED дэлгэц SSD1306 128 × 32 байсан. Энэ нь жаахан жижиг, гэхдээ би үүнийг өдөр бүр ширтэх шаардлагагүй - энэ нь надад хэтэрхий их байна.

Ингэж, утаснууд хэрхэн яаж дамжихыг олж мэдээд дэлгэцийг хайрцагны голд наахаар шийдлээ. Эргономик нь мэдээжийн хэрэг нэрлэсэн байдлаас доогуур байна - товчлуур нь дээд талд, дэлгэц нь доод талд байна. Гэхдээ дэлгэцийг хавсаргах санаа хэтэрхий оройтсон тул товчлуурыг зөөхийн тулд самбарыг дахин холбохоос залхуурсан гэж би аль хэдийн хэлсэн.

Төхөөрөмжийг угсарч байна. Дэлгэцийн модулийг халуун цавуугаар наасан байна

Бид усны тоолуурыг ухаалаг гэрт холбодог

Бид усны тоолуурыг ухаалаг гэрт холбодог

Эцсийн үр дүнг KDPV дээрээс харж болно

Firmware

Програм хангамжийн хэсэг рүү шилжье. Иймэрхүү жижиг гар урлалын хувьд би Python ашиглах дуртай (микропитон) - код нь маш нягт, ойлгомжтой болж хувирав. Аз болоход микросекундуудыг шахахын тулд бүртгэлийн түвшин рүү орох шаардлагагүй - бүх зүйлийг Python-ээс хийж болно.

Бүх зүйл энгийн, гэхдээ тийм ч энгийн биш юм шиг санагдаж байна - төхөөрөмж нь хэд хэдэн бие даасан функцтэй:

  • Хэрэглэгч товчлуурыг дарж дэлгэц рүү харна
  • Литрүүд флаш санах ойн утгыг тэмдэглэж, шинэчилнэ
  • Модуль нь WiFi дохиог хянаж, шаардлагатай бол дахин холбогдоно
  • За, анивчих чийдэнгүйгээр энэ нь боломжгүй юм

Хэрэв өөр нэг функц ямар нэг шалтгаанаар гацсан бол нэг функц ажиллахгүй байна гэж та үзэж болохгүй. Би аль хэдийн бусад төслүүдэд какти дүүргэж байсан бөгөөд одоо ч гэсэн би "тэр үед дэлгэц шинэчлэгдэж байсан тул дахин нэг литр алдсан" эсвэл "модуль холбогдож байх үед хэрэглэгч юу ч хийж чадахгүй" гэсэн хэв маягийн алдааг олж харсаар байна. WiFi.” Мэдээжийн хэрэг, зарим зүйлийг тасалдлаар хийж болно, гэхдээ та үргэлжлэх хугацаа, дуудлагын үүрлэх, эсвэл хувьсагчид атомын бус өөрчлөлт хийх зэрэг хязгаарлалттай тулгарч магадгүй юм. За, бүх зүйлийг хурдан хийдэг код нь мөөгөнцөр болж хувирдаг.

В илүү ноцтой төсөл Би сонгодог олон үйлдэлт болон FreeRTOS ашигласан боловч энэ тохиолдолд загвар нь илүү тохиромжтой болсон. coroutines болон uasync номын сангууд . Түүгээр ч барахгүй Python-ийн coroutine-ийн хэрэгжилт нь ердөө л гайхалтай юм - програмистын хувьд бүх зүйл энгийн бөгөөд тохиромжтой байдлаар хийгддэг. Зүгээр л өөрийнхөө логикийг бичээд, ямар газруудад урсгалаа сольж болохыг хэлээрэй.

Би нэмэлт хичээл болгон урьдчилан сэргийлэх болон өрсөлдөх чадвартай олон үүрэгт ажлын хоорондын ялгааг судлахыг санал болгож байна. Одоо эцэст нь код руу шилжье.

#####################################
# 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

Тоолуур бүрийг Counter ангийн жишээгээр зохицуулдаг. Юуны өмнө, анхны тоолуурын утгыг EEPROM (утга_хадгалах) -аас хасдаг - цахилгаан тасалдсаны дараа сэргэлтийг ингэж хийдэг.

Зүүг тэжээлийн эх үүсвэрт суурилуулсан таталтаар эхлүүлнэ: хэрэв зэгсэн унтраалга хаалттай бол шугам нь тэг, хэрэв шугам нээлттэй бол цахилгаан тэжээл рүү татагдаж, хянагч нэгийг уншдаг.

Энд бас тусдаа ажил эхлүүлсэн бөгөөд энэ нь санал асуулга явуулах болно. Тоолуур бүр өөр өөрийн даалгаврыг гүйцэтгэх болно. Энд түүний код байна

    """ 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)

Контакт үүсэлтийг шүүхийн тулд 25 мс-ийн саатал шаардлагатай бөгөөд энэ нь тухайн ажил хэр олон удаа сэрдэгийг зохицуулдаг (энэ даалгавар унтаж байх үед бусад ажлууд ажиллаж байна). 25 мс тутамд функц сэрж, зүүг шалгаж, зэгс шилжүүлэгчийн контактууд хаагдсан бол тоолуураар дахин нэг литр дамжсан тул үүнийг боловсруулах шаардлагатай болно.

    def _another_litre_passed(self):
        self._value += 1
        self._value_changed = True

        self._value_storage.write(self._value)

Дараагийн литрийг боловсруулах нь өчүүхэн зүйл юм - тоолуур ердөө л нэмэгддэг. За, шинэ утгыг флаш диск дээр бичвэл сайхан байх болно.

Ашиглахад хялбар болгохын тулд "хангалт төхөөрөмж" -ийг өгсөн болно

    def value(self):
        self._value_changed = False
        return self._value

    def set_value(self, value):
        self._value = value
        self._value_changed = False

За, одоо Python болон uasync номын сангийн таашаалыг ашиглаж, хүлээгдэж буй тоолуур объект хийцгээе (бид үүнийг орос хэл рүү хэрхэн орчуулах вэ? Таны хүлээж чадах зүйл үү?)

    def __await__(self):
        while not self._value_changed:
            yield from asyncio.sleep(0)

        return self.value()

    __iter__ = __await__  

Энэ бол тоолуурын утгыг шинэчлэх хүртэл хүлээдэг ийм тохиромжтой функц юм - функц нь үе үе сэрж, _value_changed тугийг шалгадаг. Энэ функцийн гайхалтай зүйл бол дуудлагын код нь энэ функцийг дуудаж байх үед унтаж, шинэ утгыг хүлээн авах хүртэл унтдаг явдал юм.

Тасалдлын талаар юу хэлэх вэ?Тийм ээ, энэ үед та өөрөө тасалдал гэж хэлсэн гэж намайг тролль хийж болно, гэхдээ бодит байдал дээр та тэнэг санал асуулга хийсэн. Үнэндээ тасалдал бол миний оролдсон хамгийн эхний зүйл юм. ESP8266 дээр та захын тасалдлыг зохион байгуулж, Python дээр энэ тасалдлыг зохицуулагч хүртэл бичиж болно. Энэ тасалдалд хувьсагчийн утгыг шинэчилж болно. Хэрэв тоолуур нь энэ утгыг асуух хүртэл хүлээдэг боол төхөөрөмж байсан бол энэ нь хангалттай байх болов уу.

Харамсалтай нь (эсвэл азаар?) миний төхөөрөмж идэвхтэй байгаа тул өөрөө MQTT протоколоор мессеж илгээж, EEPROM руу өгөгдөл бичих ёстой. Энд хязгаарлалтууд гарч ирдэг - та тасалдалд санах ойг хуваарилж, том стек ашиглах боломжгүй бөгөөд энэ нь сүлжээгээр мессеж илгээхээ мартаж болно гэсэн үг юм. Micropython.schedule() гэх мэт зарим функцийг "аль болох хурдан" ажиллуулах боломжийг олгодог боов байдаг ч "ямар учиртай юм бэ?" Гэсэн асуулт гарч ирнэ. Хэрэв бид яг одоо ямар нэгэн мессеж илгээж байгаа бол тасалдал орж, хувьсагчийн утгыг сүйтгэвэл яах вэ. Эсвэл, жишээ нь, бид хуучин тоогоо бичиж амжаагүй байхад серверээс шинэ тоолуурын утга ирсэн. Ерөнхийдөө та синхрончлолыг хаах эсвэл үүнээс өөрөөр гарах хэрэгтэй.

Мөн үе үе RuntimeError: хуваарь гаргах, стек бүрэн гацах, яагаад гэдгийг хэн мэдэх вэ?

Тодорхой санал асуулга болон uasync-ийн тусламжтайгаар энэ тохиолдолд ямар нэгэн байдлаар илүү үзэсгэлэнтэй, найдвартай болж хувирдаг

Би жижиг ангид EEPROM-той ажил авчирсан

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)

Python дээр байтуудтай шууд ажиллахад хэцүү байдаг ч санах ойд бичдэг байтууд юм. Би ustruct номын санг ашиглан бүхэл тоо болон байт хоорондын хөрвүүлэлтийг хаах хэрэгтэй болсон.

I2C объект болон санах ойн үүрний хаягийг шилжүүлэхгүйн тулд би бүгдийг жижиг, тохиромжтой сонгодог хэлбэрээр боосон.

class EEPROMValue():
    def __init__(self, i2c, eeprom_addr):
        self._eeprom = EEPROM(i2c)
        self._eeprom_addr = eeprom_addr
        

    def read(self):
        return self._eeprom.read(self._eeprom_addr)


    def write(self, value):
        self._eeprom.write(self._eeprom_addr, value)

I2C объект нь өөрөө эдгээр параметрүүдээр үүсгэгддэг

i2c = I2C(freq=400000, scl=Pin(5), sda=Pin(4))

Бид хамгийн сонирхолтой хэсэг болох MQTT-ээр сервертэй харилцах харилцаанд хүрч байна. Протоколыг өөрөө хэрэгжүүлэх шаардлагагүй - би үүнийг интернетээс олсон бэлэн асинхрон хэрэгжилт. Үүнийг бид ашиглах болно.

Хамгийн сонирхолтой бүх зүйлийг MQTTClient номын санд суурилсан CounterMQTTClient ангид цуглуулдаг. Захын захаас эхэлцгээе

#####################################
# 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))

Энд та гэрлийн чийдэнгийн зүү, товчлуур, түүнчлэн хүйтэн, халуун усны тоолуурын объектуудыг үүсгэж, тохируулах боломжтой.

Эхлэх үед бүх зүйл тийм ч өчүүхэн биш юм

    def __init__(self):
        self.internet_outage = True
        self.internet_outages = 0
        self.internet_outage_start = ticks_ms()

        with open("config.txt") as config_file:
            config['ssid'] = config_file.readline().rstrip()
            config['wifi_pw'] = config_file.readline().rstrip()
            config['server'] = config_file.readline().rstrip()
            config['client_id'] = config_file.readline().rstrip()
            self._mqtt_cold_water_theme = config_file.readline().rstrip()
            self._mqtt_hot_water_theme = config_file.readline().rstrip()
            self._mqtt_debug_water_theme = config_file.readline().rstrip()

        config['subs_cb'] = self.mqtt_msg_handler
        config['wifi_coro'] = self.wifi_connection_handler
        config['connect_coro'] = self.mqtt_connection_handler
        config['clean'] = False
        config['clean_init'] = False
        super().__init__(config)

        loop = asyncio.get_event_loop()
        loop.create_task(self._heartbeat())
        loop.create_task(self._counter_coro(self.cold_counter, self._mqtt_cold_water_theme))
        loop.create_task(self._counter_coro(self.hot_counter, self._mqtt_hot_water_theme))
        loop.create_task(self._display_coro())

Mqtt_as номын сангийн үйлдлийн параметрүүдийг тохируулахын тулд янз бүрийн тохиргооны том толь бичгийг ашигладаг - config. Ихэнх өгөгдмөл тохиргоонууд нь бидэнд тохиромжтой, гэхдээ олон тохиргоог тодорхой тохируулах шаардлагатай. Тохиргоог кодонд шууд бичихгүйн тулд би config.txt текст файлд хадгалдаг. Энэ нь тохиргооноос үл хамааран кодыг өөрчлөх, мөн өөр өөр параметр бүхий хэд хэдэн ижил төхөөрөмжийг холбох боломжийг олгодог.

Кодын сүүлчийн блок нь системийн янз бүрийн функцийг гүйцэтгэх хэд хэдэн корутинуудыг эхлүүлдэг. Жишээлбэл, энд үйлчилгээ үзүүлдэг корутин байна

    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))

Корутин нь шинэ тоолуурын утгыг хүлээж, гарч ирмэгц MQTT протоколоор мессеж илгээдэг. Эхний код нь тоолуураар ус урсахгүй байсан ч анхны утгыг илгээдэг.

MQTTClient үндсэн анги нь өөрөө үйлчилж, WiFi холболтыг эхлүүлж, холболт тасарсан үед дахин холбогддог. WiFi холболтын төлөвт өөрчлөлт гарсан тохиолдолд номын сан 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)

Энэ функцийг жишээнүүдээс үнэн зөвөөр хуулбарласан болно. Энэ тохиолдолд тасалдлын тоо (интернетийн_тасралт) болон үргэлжлэх хугацааг тооцдог. Холболт сэргээгдэх үед сул зогсолтыг сервер рүү илгээдэг.

Дашрамд хэлэхэд, сүүлчийн унтлага нь зөвхөн функцийг асинхрон болгоход шаардлагатай байдаг - номын санд үүнийг via await гэж нэрлэдэг бөгөөд зөвхөн бие нь өөр хүлээлт агуулсан функцуудыг дуудаж болно.

WiFi-д холбогдохоос гадна MQTT брокер (сервер)-тэй холбогдох шаардлагатай. Номын сан ч үүнийг хийдэг бөгөөд холболт үүссэн үед бидэнд хэрэгтэй зүйл хийх боломж олддог

    async def mqtt_connection_handler(self, client):
        await client.subscribe(self._mqtt_cold_water_theme)
        await client.subscribe(self._mqtt_hot_water_theme)

Энд бид хэд хэдэн мессежийг захиалж байна - сервер одоо харгалзах мессежийг илгээх замаар одоогийн тоолуурын утгыг тохируулах боломжтой болсон.

    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))

Энэ функц нь ирж буй мессежийг боловсруулж, сэдвээс (мессежийн гарчиг) хамааран тоолуурын аль нэгний утгыг шинэчилдэг.

Хэд хэдэн туслах функцууд

    # 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")

Хэрэв холболт хийгдсэн бол энэ функц нь мессеж илгээдэг. Хэрэв холболт байхгүй бол мессежийг үл тоомсорлодог.

Мөн энэ нь дибаг хийх мессежийг үүсгэж, илгээдэг зүгээр л тохиромжтой функц юм.

    async def publish_debug_msg(self, subtopic, msg):
        await self.publish_msg("{}/{}".format(self._mqtt_debug_water_theme, subtopic), str(msg))

Маш их бичвэр, бид хараахан LED анивчаагүй байна. Энд

    # 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)

Би анивчдаг 2 горимыг өгсөн. Хэрэв холболт тасарвал (эсвэл дөнгөж байгуулагдаж байгаа бол) төхөөрөмж хурдан анивчих болно. Хэрэв холболт хийгдсэн бол төхөөрөмж 5 секунд тутамд нэг удаа анивчих болно. Шаардлагатай бол бусад анивчих горимыг энд хэрэгжүүлж болно.

Гэхдээ LED нь зүгээр л эрхлүүлж байна. Бид мөн дэлгэц рүү чиглэв.

    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)

Энэ бол миний ярьж байсан зүйл юм - coroutines нь ямар энгийн бөгөөд тохиромжтой вэ. Энэхүү жижиг функц нь хэрэглэгчийн туршлагыг БҮХЭН тайлбарладаг. Корутин нь товчлуурыг дарахыг хүлээж, дэлгэцийг 3 секундын турш асаана. Дэлгэц нь одоогийн тоолуурын заалтыг харуулна.

Хоёр жижиг зүйл үлдсэн хэвээр байна. Энэ бүхэл бүтэн аж ахуйн нэгжийг (дахин) эхлүүлдэг функц энд байна. Үндсэн давталт нь минутанд нэг удаа дибаг хийх янз бүрийн мэдээллийг илгээдэг. Ерөнхийдөө би үүнийг байгаагаар нь иш татсан - Би хэтэрхий их тайлбар хийх шаардлагагүй гэж бодож байна

   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)

За, тайлбарыг дуусгахын тулд хэд хэдэн тохиргоо, тогтмолууд

#####################################
# 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)

Бүх зүйл ингэж эхэлдэг

client = CounterMQTTClient()
loop = asyncio.get_event_loop()
loop.run_until_complete(client.main())

Миний ой санамжид ямар нэгэн зүйл тохиолдсон

Тэгэхээр бүх код тэнд байна. Би файлуудыг ampy хэрэгслийг ашиглан байршуулсан - энэ нь тэдгээрийг дотоод (ESP-07-д байдаг) флаш диск рүү байршуулж, дараа нь програмаас ердийн файл болгон ашиглах боломжийг олгодог. Тэнд би мөн ашигласан mqtt_as, uasyncio, ssd1306 болон цуглуулгын сангуудыг (mqtt_as дотор ашигладаг) байршуулсан.

Бид эхлүүлээд... Бид MemoryError гарлаа. Түүнээс гадна санах ой яг хаана алдагдаж байгааг ойлгохыг хичээх тусам дибаг хийх тусам энэ алдаа эрт гарч ирдэг. Google-ийн богино хайлт нь микроконтроллер нь зарчмын хувьд ердөө 30 кБ санах ойтой бөгөөд 65 кБ код (номын санг оруулаад) багтах боломжгүй гэдгийг ойлгоход хүргэсэн.

Гэхдээ гарах гарц бий. Micropython нь .py файлаас кодыг шууд гүйцэтгэдэггүй нь харагдаж байна - энэ файлыг эхлээд эмхэтгэсэн. Түүнээс гадна үүнийг микроконтроллер дээр шууд хөрвүүлж, байт код болгон хувиргаж, дараа нь санах ойд хадгалдаг. За хөрвүүлэгч ажиллахын тулд танд бас тодорхой хэмжээний RAM хэрэгтэй.

Энэ заль мэх нь микроконтроллерыг нөөц их шаарддаг эмхэтгэлээс аврах явдал юм. Та том компьютер дээр файлуудыг эмхэтгэж, бэлэн байт кодыг микроконтроллерт байршуулж болно. Үүнийг хийхийн тулд та micropython програмыг татаж аваад бүтээх хэрэгтэй mpy-cross хэрэгсэл.

Би Makefile бичээгүй, гэхдээ үүнтэй төстэй бүх шаардлагатай файлуудыг (номын санг оруулаад) гараар дамжуулж, эмхэтгэсэн.

mpy-cross water_counter.py

Үлдсэн зүйл бол .mpy өргөтгөлтэй файлуудыг байршуулах бөгөөд эхлээд төхөөрөмжийн файлын системээс холбогдох .py-г устгахаа мартуузай.

Би программын бүх хөгжүүлэлтийг хийсэн (IDE?) ESPlorer. Энэ нь танд скриптүүдийг микроконтроллерт байршуулж, шууд гүйцэтгэх боломжийг олгоно. Миний хувьд бүх объектын бүх логик, бүтээл нь water_counter.py (.mpy) файлд байрладаг. Гэхдээ энэ бүхэн автоматаар эхлэхийн тулд эхэндээ main.py нэртэй файл бас байх ёстой. Түүнээс гадна энэ нь яг .py байх ёстой бөгөөд урьдчилан эмхэтгэсэн .mpy биш юм. Түүний өчүүхэн агуулгыг энд оруулав

import water_counter

Бид үүнийг эхлүүлнэ - бүх зүйл ажилладаг. Гэхдээ үнэгүй санах ой нь аймшигтай бага байдаг - ойролцоогоор 1 кб. Би төхөөрөмжийн ажиллагааг өргөжүүлэх төлөвлөгөөтэй хэвээр байгаа бөгөөд энэ килобайт нь надад хангалттай биш байна. Гэхдээ энэ хэрэгт бас гарц байгаа нь тогтоогдсон.

Энэ нь энд байна. Хэдийгээр файлуудыг байт код болгон хөрвүүлж, дотоод файлын системд суулгасан ч бодит байдал дээр тэдгээрийг RAM-д ачаалж, тэндээс ажиллуулдаг. Гэхдээ micropython нь флаш санах ойноос байт кодыг шууд ажиллуулж чаддаг болох нь харагдаж байна, гэхдээ үүний тулд та үүнийг програм хангамжид шууд суулгах хэрэгтэй. Миний нэтбүүк дээр нэлээд хугацаа зарцуулсан ч (зөвхөн тэнд л Линукстэй болсон) энэ нь тийм ч хэцүү биш юм.

Алгоритм нь дараах байдалтай байна.

  • Татаж аваад суулгана уу ESP Open SDK. Энэ зүйл нь ESP8266 програмын хөрвүүлэгч, сангуудыг угсардаг. Төслийн үндсэн хуудсан дээрх зааврын дагуу угсарсан (би дан дан=тийм тохиргоог сонгосон)
  • Татаж авах микропитон төрлүүд
  • Шаардлагатай номын сангуудыг micropython модны доторх ports/esp8266/modules-д байрлуул.
  • Бид файлын зааврын дагуу програм хангамжийг угсардаг портууд/esp8266/README.md
  • Бид микроконтроллер руу програм хангамжийг байршуулдаг (би үүнийг Windows дээр ESP8266Flasher програм эсвэл Python esptool ашиглан хийдэг)

Ингээд л "import ssd1306" программ хангамжаас кодыг шууд гаргаж авах бөгөөд үүнд RAM зарцуулагдахгүй. Энэ заль мэхийн тусламжтайгаар би програмын үндсэн кодыг файлын системээс ажиллуулж байхад зөвхөн номын сангийн кодыг програм хангамж руу оруулсан. Энэ нь програм хангамжийг дахин хөрвүүлэхгүйгээр програмыг хялбархан өөрчлөх боломжийг танд олгоно. Одоогоор надад 8.5кб орчим үнэгүй RAM байна. Энэ нь ирээдүйд маш олон төрлийн ашигтай функцуудыг хэрэгжүүлэх боломжийг бидэнд олгоно. Хэрэв санах ой огт байхгүй бол та үндсэн програмыг програм хангамж руу түлхэж болно.

Тэгэхээр бид одоо энэ талаар юу хийх ёстой вэ?

За, техник хангамж гагнагдсан, програм хангамж бичигдсэн, хайрцаг хэвлэгдсэн, төхөөрөмж хананд наалдаж, гэрлийн чийдэнг баяртайгаар анивчдаг. Гэхдээ одоохондоо энэ бүхэн хар хайрцаг (шууд болон дүрслэлийн утгаараа) бөгөөд энэ нь бага зэрэг ашиггүй хэвээр байна. Сервер рүү илгээгдсэн MQTT мессежүүдээр ямар нэг зүйл хийх цаг болжээ.

Миний "ухаалаг гэр" эргэлдэж байна Мажордомо систем. MQTT модуль нь хайрцагнаас гарч ирдэг, эсвэл нэмэлт зах зээлээс амархан суулгадаг - би хаанаас авснаа санахгүй байна. MQTT бол бие даасан зүйл биш - танд ийм зүйл хэрэгтэй. брокер - MQTT мессежийг хүлээн авах, ангилах, үйлчлүүлэгчдэд дамжуулах сервер. Би нэг нэтбүүк дээр ажилладаг шумуул (majordomo гэх мэт) ашигладаг.

Төхөөрөмж дор хаяж нэг удаа мессеж илгээсний дараа утга нь жагсаалтад шууд гарч ирнэ.

Бид усны тоолуурыг ухаалаг гэрт холбодог

Эдгээр утгуудыг одоо системийн объектуудтай холбож болно, тэдгээрийг автоматжуулалтын скриптүүдэд ашиглаж, янз бүрийн шинжилгээнд хамруулж болно - эдгээр нь бүгд энэ нийтлэлийн хамрах хүрээнээс гадуур юм. Сонирхсон хүмүүст би majordomo системийг санал болгож чадна суваг Electronics In Lens - найз нь бас ухаалаг байшин барьж байгаа бөгөөд системийг тохируулах талаар тодорхой ярьж байна.

Би танд хэдхэн график үзүүлье. Энэ бол өдөр тутмын утгын энгийн график юм

Бид усны тоолуурыг ухаалаг гэрт холбодог
Шөнийн цагаар ус хэрэглэдэггүй хүн бараг харагдахгүй байна. Хэдэн удаа хэн нэгэн бие засах газар руу ороход урвуу осмос шүүлтүүр нь шөнөдөө хэдэн литр ус сордог бололтой. Өглөө нь хэрэглээ мэдэгдэхүйц нэмэгддэг. Би ихэвчлэн уурын зуухнаас ус хэрэглэдэг, гэхдээ дараа нь усанд орохыг хүсч, хотын халуун усанд түр шилжсэн - энэ нь доод графикаас тодорхой харагдаж байна.

Энэ графикаас жорлонд ороход 6-7 литр ус, шүршүүрт ороход 20-30 литр, аяга таваг угаахад 20 литр, усанд ороход 160 литр ус шаардагддагийг мэдсэн. Манай гэр бүл өдөрт 500-600 литр ус хэрэглэдэг.

Ялангуяа сониуч хүмүүсийн хувьд та тус бүрийн үнэ цэнийн бүртгэлийг харж болно

Бид усны тоолуурыг ухаалаг гэрт холбодог

Эндээс би цорго онгорхой байхад ус 1 секундэд ойролцоогоор 5 литр хурдтай урсдаг болохыг олж мэдсэн.

Гэхдээ энэ хэлбэрээр статистикийг харахад тийм ч тохиромжтой биш байж магадгүй юм. Majordomo нь хэрэглээний графикийг өдөр, долоо хоног, сараар харах чадвартай. Энд жишээ нь баар дахь хэрэглээний график байна

Бид усны тоолуурыг ухаалаг гэрт холбодог

Одоогоор надад долоо хоногийн л мэдээлэл байна. Сарын дараа энэ график нь илүү үзүүлэлттэй байх болно - өдөр бүр тусдаа баганатай байх болно. Миний гараар оруулсан утгыг тохируулснаар зураг бага зэрэг эвдэрсэн байна (хамгийн том багана). Би хамгийн анхны утгыг буруу тохируулсан уу, бараг шоо багассан уу, эсвэл энэ нь програм хангамжийн алдаа мөн үү, бүх литрийг тооцоогүй эсэх нь тодорхойгүй байна. Илүү их цаг хэрэгтэй.

Графикууд өөрсдөө ид шид, цайруулах, будах шаардлагатай хэвээр байна. Магадгүй би бас дибаг хийх зорилгоор санах ойн хэрэглээний графикийг бүтээх болно - хэрэв тэнд ямар нэг зүйл алдагдсан бол. Интернет байхгүй байсан үеийг би ямар нэгэн байдлаар харуулах байх. Одоохондоо энэ бүхэн санаа бодлын түвшинд байна.

дүгнэлт

Өнөөдөр миний байр арай ухаалаг болсон. Ийм жижиг төхөөрөмжтэй бол байшин дахь усны хэрэглээг хянах нь надад илүү тохиромжтой байх болно. Өмнө нь би “дахин нэг сарын дотор их хэмжээний ус уусан” гэж уурлаж байсан бол одоо энэ хэрэглээний эх үүсвэрийг олж чадна.

Зарим нь тоолуураас нэг метрийн зайд байгаа бол дэлгэцэн дээрх заалтыг харахад хачирхалтай санагдаж магадгүй юм. Гэхдээ ойрын ирээдүйд би өөр орон сууц руу нүүхээр төлөвлөж байгаа бөгөөд тэнд хэд хэдэн ус өргөх төхөөрөмж байх бөгөөд тоолуур нь өөрөө буух хэсэгт байрлах болно. Тиймээс алсаас унших төхөөрөмж маш хэрэгтэй болно.

Би мөн төхөөрөмжийн функцийг өргөжүүлэхээр төлөвлөж байна. Би моторт хавхлагуудыг аль хэдийн харж байна. Одоо бойлерыг хотын ус руу шилжүүлэхийн тулд би хүрэхэд хэцүү газарт 3 цоргыг эргүүлэх хэрэгтэй. Үүнийг харгалзах заалттай нэг товчлуураар хийх нь илүү тохиромжтой байх болно. Мэдээжийн хэрэг, гоожихоос хамгаалах хамгаалалтыг хэрэгжүүлэх нь зүйтэй.

Энэ нийтлэлд би ESP8266 дээр суурилсан төхөөрөмжийн хувилбарыг тайлбарласан. Миний бодлоор, би micropython програмын coroutines ашиглан маш сонирхолтой хувилбарыг гаргаж ирсэн - энгийн бөгөөд сайхан. Би сонгуулийн сурталчилгааны үеэр тохиолдсон олон нюанс, дутагдалтай талуудыг тайлбарлахыг хичээсэн. Магадгүй би бүх зүйлийг хэтэрхий дэлгэрэнгүй тайлбарласан байх; би хувьдаа, уншигчийн хувьд юу хэлээгүй үлдсэнийг дараа нь бодохоос илүү шаардлагагүй зүйлсийг алгасах нь илүү хялбар байдаг.

Би үргэлж бүтээлч шүүмжлэлд нээлттэй.

Эх код
Хэлхээ ба самбар
Кейс загвар

Эх сурвалж: www.habr.com

сэтгэгдэл нэмэх