Ժամանակին տնային ավտոմատացման համակարգերը կամ, ինչպես հաճախ անվանում էին «խելացի տուն», սարսափելի թանկ էին, և միայն հարուստները կարող էին իրենց թույլ տալ: Այսօր շուկայում դուք կարող եք գտնել բավականին էժան կոմպլեկտներ՝ սենսորներով, կոճակներով/անջատիչներով և շարժիչներով՝ լուսավորության, վարդակների, օդափոխության, ջրամատակարարման և այլ սպառողների կառավարման համար: Եվ նույնիսկ ամենակուռ մարդը կարող է զբաղվել գեղեցկությամբ և խելացի տան համար սարքեր հավաքել էժան գնով:

Սովորաբար, առաջարկվող սարքերը կամ սենսորներ են, կամ շարժիչներ: Դրանք հեշտացնում են այնպիսի սցենարների իրականացումը, ինչպիսիք են «երբ շարժման սենսորը գործարկվում է, միացրեք լույսերը» կամ «Ելքի մոտ գտնվող անջատիչը անջատում է լույսերը ամբողջ բնակարանում»: Բայց ինչ-որ կերպ ամեն ինչ չստացվեց հեռաչափությամբ: Լավագույն դեպքում դա ջերմաստիճանի և խոնավության գրաֆիկ է կամ ակնթարթային հզորությունը կոնկրետ վարդակից:
Վերջերս տեղադրեցի ջրաչափեր իմպուլսային ելքով: Հաշվիչի միջով անցնող յուրաքանչյուր լիտրի համար եղեգի անջատիչն ակտիվանում է և փակում կոնտակտը: Մնում է միայն կառչել լարերից և փորձել դրանից օգուտ քաղել։ Օրինակ՝ վերլուծեք ջրի սպառումը ըստ շաբաթվա ժամերի և օրերի: Դե, եթե բնակարանում կան մի քանի ջրի բարձրացուցիչներ, ապա ավելի հարմար է տեսնել բոլոր ընթացիկ ցուցանիշները մեկ էկրանին, քան լապտերով բարձրանալ դեպի դժվար հասանելի խորշեր:
Կտրվածքի տակ դրված է ESP8266-ի վրա հիմնված սարքի իմ տարբերակը, որը հաշվում է իմպուլսները ջրաչափերից և ընթերցումներ ուղարկում MQTT-ի միջոցով խելացի տան սերվերին: Մենք կծրագրավորենք micropython-ում՝ օգտագործելով uasyncio գրադարանը։ Որոնվածը ստեղծելիս ես հանդիպեցի մի քանի հետաքրքիր դժվարությունների, որոնք ես նույնպես կքննարկեմ այս հոդվածում: Գնա՛
Ծրագիրը

Ամբողջ շղթայի սիրտը մի մոդուլ է ESP8266 միկրոկոնտրոլերի վրա: ESP-12-ը ի սկզբանե նախատեսված էր, բայց իմը թերի էր: Մենք պետք է բավարարվեինք ESP-07 մոդուլով, որը հասանելի էր: Բարեբախտաբար, դրանք նույնն են և՛ քորոցների, և՛ ֆունկցիոնալության առումով, տարբերությունը միայն ալեհավաքի մեջ է՝ ESP-12-ն ունի ներկառուցված, իսկ ESP-07-ը՝ արտաքին: Այնուամենայնիվ, նույնիսկ առանց WiFi ալեհավաքի, իմ լոգարանում ազդանշանը ստացվում է նորմալ:
Ստանդարտ մոդուլի լարերը.
- զրոյական կոճակ՝ ձգվող և կոնդենսատորով (չնայած երկուսն էլ արդեն մոդուլի ներսում են)
- Միացման ազդանշանը (CH_PD) միացված է սնուցման
- GPIO15-ը քաշվում է գետնին: Սա միայն սկզբում է պետք, բայց ես դեռ ոչինչ չունեմ այս ոտքին կապելու, ինձ այլևս պետք չէ
Մոդուլը որոնվածի ռեժիմի մեջ դնելու համար անհրաժեշտ է GPIO2-ը կարճ միացնել գետնին, իսկ այն ավելի հարմար դարձնելու համար ես տրամադրեցի Boot կոճակը: Նորմալ վիճակում այս քորոցը միացված է հոսանքի:
GPIO2 գծի վիճակը ստուգվում է միայն շահագործման սկզբում` հոսանքի կիրառման ժամանակ կամ վերականգնումից անմիջապես հետո: Այսպիսով, մոդուլը կա՛մ բեռնվում է սովորականի պես, կա՛մ անցնում է որոնվածի ռեժիմ: Բեռնվելուց հետո այս փին կարող է օգտագործվել որպես սովորական GPIO: Դե, քանի որ այնտեղ արդեն կա կոճակ, կարող եք դրան մի քանի օգտակար ֆունկցիա կցել։
Ծրագրավորման և վրիպազերծման համար ես կօգտագործեմ UART-ը, որը թողարկվում է սանրում: Անհրաժեշտության դեպքում ես այնտեղ պարզապես միացնում եմ USB-UART ադապտեր: Պարզապես պետք է հիշել, որ մոդուլը սնուցվում է 3.3 Վ-ով: Եթե մոռանաք ադապտորը միացնել այս լարման և մատակարարել 5 Վ, մոդուլը, ամենայն հավանականությամբ, կվառվի:
Լոգարանում հոսանքի հետ խնդիր չունեմ. վարդակից գտնվում է հաշվիչների հեռավորության վրա մոտ մեկ մետր հեռավորության վրա, այնպես որ ես այն կսնուցեմ 220 Վ-ից։ Որպես էներգիայի աղբյուր ես կունենամ փոքր Tenstar Robot-ի կողմից: Անձամբ ես դժվարանում եմ անալոգային և ուժային էլեկտրոնիկայի հետ կապված, բայց ահա պատրաստի սնուցման աղբյուրը փոքր պատյանով:
Գործառնական ռեժիմները ազդանշան տալու համար ես տրամադրեցի GPIO2-ին միացված լուսադիոդ: Այնուամենայնիվ, ես այն չզոդեցի, քանի որ... ESP-07 մոդուլն արդեն ունի լուսադիոդ, այն նաև միացված է GPIO2-ին։ Բայց թող լինի տախտակի վրա, եթե ես ուզում եմ այս լուսադիոդը դուրս բերել գործին:
Անցնենք ամենահետաքրքիր հատվածին։ Ջրաչափերը ոչ մի տրամաբանություն չունեն. Մեզ հասանելի միակ բանը իմպուլսներն են՝ եղեգի անջատիչի կոնտակտները փակել ամեն լիտրով։ Իմ եղեգի անջատիչի ելքերը միացված են GPIO12/GPIO13-ին: Ես ծրագրային կերպով միացնեմ քաշվող դիմադրությունը մոդուլի ներսում:
Սկզբում ես մոռացել էի R8 և R9 ռեզիստորներ տրամադրել, և տախտակի իմ տարբերակում դրանք չկան: Բայց քանի որ ես արդեն տեղադրում եմ դիագրամը, որպեսզի տեսնեն բոլորը, արժե շտկել այս անտեսումը: Ռեզիստորները անհրաժեշտ են, որպեսզի չվառեն պորտը, եթե որոնվածը խափանում է և պտտեցնում է մեկին, և եղեգի անջատիչը այս գիծը կտրում է գետնին (առավելագույնը 3.3 Վ/1000 Օհմ = 3.3 մԱ ռեզիստորի դեպքում կհոսի):
Ժամանակն է մտածել, թե ինչ անել, եթե հոսանքը անջատվի։ Առաջին տարբերակը սկզբում սերվերից սկզբնական հաշվիչների արժեքներ պահանջելն է: Բայց դա կպահանջի փոխանակման արձանագրության զգալի բարդացում: Ավելին, սարքի աշխատանքը տվյալ դեպքում կախված է սերվերի վիճակից։ Եթե սերվերը չգործարկվեր հոսանքն անջատվելուց հետո (կամ ավելի ուշ գործարկվել), ապա ջրաչափը չէր կարողանա պահանջել նախնական արժեքները և ճիշտ չէր աշխատի:
Հետևաբար, ես որոշեցի կիրառել հաշվիչի արժեքների պահպանումը I2C-ի միջոցով միացված հիշողության չիպի մեջ: Ֆլեշ հիշողության չափի համար հատուկ պահանջներ չունեմ. անհրաժեշտ է պահպանել միայն 2 համար (լիտրերի քանակը՝ ըստ տաք և սառը ջրաչափերի): Անգամ ամենափոքր մոդուլը կարող է անել: Բայց դուք պետք է ուշադրություն դարձնեք ձայնագրման ցիկլերի քանակին: Մոդուլների մեծ մասի համար սա 100 հազար ցիկլ է, որոշների համար՝ մինչև միլիոն:
Թվում է, թե միլիոնը շատ է։ Բայց իմ բնակարանում ապրելու 4 տարիների ընթացքում ես սպառել եմ 500 խմ-ից մի փոքր ավելի ջուր, այսինքն՝ 500 հազար լիտր: Եվ 500 հազար ձայնագրություն՝ ֆլեշում։ Եվ դա պարզապես սառը ջուր է: Դուք, իհարկե, կարող եք վերազոդել չիպը երկու տարին մեկ, բայց պարզվում է, որ կան FRAM չիպեր: Ծրագրավորման տեսանկյունից սա նույն I2C EEPROM-ն է՝ միայն շատ մեծ թվով վերագրանցման ցիկլերով (հարյուր միլիոններ): Պարզապես ես դեռ չեմ կարող խանութ հասնել նման միկրոսխեմաներով, ուստի առայժմ սովորական 24LC512-ը կկանգնի:
Տպագիր տպատախտակ
Սկզբում պլանավորել էի տախտակը պատրաստել տանը։ Հետեւաբար, խորհուրդը նախագծվել է որպես միակողմանի: Բայց մեկ ժամ լազերային արդուկի և զոդման դիմակի հետ անցկացնելուց հետո (առանց դրա ինչ-որ կերպ չի ստացվում), ես դեռ որոշեցի պատվիրել տախտակները չինացիներից:

Գրեթե նախքան տախտակը պատվիրելը, ես հասկացա, որ բացի ֆլեշ հիշողության չիպից, կարող եմ I2C ավտոբուսին միացնել մեկ այլ օգտակար բան, օրինակ՝ դիսփլեյ: Թե կոնկրետ ինչ արդյունք տալ դրան, դեռ հարց է, բայց այն պետք է ուղղորդվի տախտակի վրա: Դե, քանի որ ես պատրաստվում էի տախտակներ պատվիրել գործարանից, իմաստ չկար սահմանափակվել միայն միակողմանի տախտակով, ուստի I2C տողերը միակն են տախտակի հետևի մասում:
Մի մեծ խնդիր կար նաև միակողմանի լարերի հետ կապված. Որովհետեւ Տախտակը գծված էր որպես միակողմանի, ուստի պլանավորվում էր, որ հետքերը և SMD բաղադրիչները տեղադրվեին մի կողմում, իսկ ելքային բաղադրիչները, միակցիչները և էլեկտրամատակարարումը մյուս կողմից: Երբ ես ստացա տախտակները մեկ ամիս անց, ես մոռացա սկզբնական պլանի մասին և զոդեցի բոլոր բաղադրիչները առջևի կողմում: Եվ միայն այն ժամանակ, երբ խոսքը գնում էր էլեկտրամատակարարման զոդման մասին, պարզվեց, որ պլյուսն ու մինուսը լարերը հակառակն էին: Ես ստիպված էի ֆերմա վարել ցատկողներով: Վերևի նկարում ես արդեն փոխել եմ լարերը, բայց գետինը տեղափոխվում է տախտակի մի մասից մյուսը Boot կոճակի քորոցների միջոցով (չնայած երկրորդ շերտի վրա հնարավոր կլիներ հետք նկարել):
Ստացվեց այսպես

Բնակարանային պայմանները
Հաջորդ քայլը մարմինն է: Եթե դուք ունեք 3D տպիչ, դա խնդիր չէ: Ես շատ չանհանգստացա. ես պարզապես նկարեցի ճիշտ չափի տուփ և ճիշտ տեղերում կտրվածքներ արեցի: Կափարիչը ամրացվում է մարմնի վրա փոքր ինքնակպչուն պտուտակներով:

Ես արդեն նշեցի, որ Boot կոճակը կարող է օգտագործվել որպես ընդհանուր նշանակության կոճակ, այնպես որ մենք այն կցուցադրենք առջևի վահանակում: Դա անելու համար ես նկարեցի հատուկ «ջրհոր», որտեղ ապրում է կոճակը:

Գործի ներսում կան նաև գամասեղներ, որոնց վրա տախտակը տեղադրվում և ամրացվում է մեկ M3 պտուտակով (տախտակի վրա այլևս տեղ չկար)
Ես ընտրեցի էկրանը, երբ տպեցի գործի առաջին օրինակելի տարբերակը: Ստանդարտ երկտող ընթերցողը չէր տեղավորվում այս գործի մեջ, բայց ներքևում կար OLED էկրան SSD1306 128×32: Դա մի փոքր փոքր է, բայց ես չպետք է ամեն օր նայեմ դրան, դա չափազանց շատ է ինձ համար:
Պարզելով այս և այն մասին, թե ինչպես են հաղորդալարերը հեռանալու դրանից, ես որոշեցի էկրանը կպցնել պատյանի մեջտեղում: Էրգոնոմիկա, իհարկե, ցածր մակարդակի վրա է՝ կոճակը վերևում է, էկրանը՝ ներքևում: Բայց ես արդեն ասացի, որ էկրանը կցելու գաղափարը շատ ուշ եկավ, և ես չափազանց ծույլ էի տախտակը նորից լարել կոճակը տեղափոխելու համար:
Սարքը հավաքված է։ Ցուցադրման մոդուլը տաք սոսինձով կպչում է ճյուղին


Վերջնական արդյունքը կարելի է տեսնել KDPV-ում
Որոնվածը
Անցնենք ծրագրային մասին։ Նման փոքր արհեստների համար ես իսկապես սիրում եմ օգտագործել Python () - կոդը ստացվում է շատ կոմպակտ և հասկանալի: Բարեբախտաբար, կարիք չկա իջնել ռեգիստրի մակարդակին՝ միկրովայրկյանները սեղմելու համար. ամեն ինչ կարելի է անել Python-ից:
Թվում է, թե ամեն ինչ պարզ է, բայց ոչ շատ պարզ. սարքն ունի մի քանի անկախ գործառույթներ.
- Օգտագործողը սեղմում է կոճակը և նայում էկրանին
- Լիտրերը նշում և թարմացնում են արժեքները ֆլեշ հիշողության մեջ
- Մոդուլը վերահսկում է WiFi ազդանշանը և անհրաժեշտության դեպքում նորից միանում
- Դե, առանց թարթող լամպի դա անհնար է
Դուք չեք կարող ենթադրել, որ մի գործառույթ չի աշխատել, եթե մյուսը ինչ-ինչ պատճառներով խրված է: Ես արդեն կակտուսներով եմ լցվել այլ նախագծերում, և հիմա դեռ տեսնում եմ խափանումներ՝ «բաց թողեցի ևս մեկ լիտր, քանի որ էկրանն այդ պահին թարմացվում էր» կամ «օգտատերը չի կարող որևէ բան անել, մինչ մոդուլը միանում է»: WiFi»: Իհարկե, որոշ բաներ կարելի է անել ընդհատումների միջոցով, բայց դուք կարող եք բախվել տևողության, զանգերի տեղադրման կամ փոփոխականների ոչ ատոմային փոփոխությունների հետ կապված սահմանափակումների: Դե, ամեն ինչ կատարող կոդը արագ վերածվում է մուշի։
В Ես օգտագործեցի դասական կանխարգելիչ բազմաֆունկցիոնալ աշխատանք և FreeRTOS, բայց այս դեպքում մոդելը շատ ավելի հարմար է ստացվել . Ավելին, Python-ի կորուտինների իրականացումը պարզապես զարմանալի է. ամեն ինչ արվում է պարզ և հարմար ծրագրավորողի համար: Պարզապես գրեք ձեր սեփական տրամաբանությունը, պարզապես ասեք ինձ, թե որ վայրերում կարող եք անցնել հոսքերի միջև:
Առաջարկում եմ ուսումնասիրել կանխարգելիչ և մրցակցային բազմաբնույթ առաջադրանքների միջև եղած տարբերությունները որպես ընտրովի առարկա: Հիմա վերջապես անցնենք ծածկագրին։
#####################################
# 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 ms ֆունկցիան արթնանում է, ստուգում է քորոցը և եթե եղեգի անջատիչի կոնտակտները փակ են, ապա ևս մեկ լիտր անցել է հաշվիչի միջով, և այն պետք է մշակվի:
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-ին: Եվ այստեղ սահմանափակումները գործում են. դուք չեք կարող հիշողություն հատկացնել ընդհատումներով և օգտագործել մեծ կույտ, ինչը նշանակում է, որ կարող եք մոռանալ ցանցով հաղորդագրություններ ուղարկելու մասին: Կան buns, ինչպիսիք են 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-ի միջոցով սերվերի հետ կապի իրականացմանը: Դե, պրոտոկոլն ինքնին իրականացնելու կարիք չկա, ես այն գտա ինտերնետում . Սա այն է, ինչ մենք կօգտագործենք:
Բոլոր ամենահետաքրքիր բաները հավաքված են CounterMQTTClient դասում, որը հիմնված է MQTTClient գրադարանի վրա: Սկսենք ծայրամասից
#####################################
# 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))The Coroutine-ը սպասում է ցիկլով նոր հաշվիչի համար և, հենց որ այն հայտնվում է, հաղորդագրություն է ուղարկում 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))
Այնքան շատ տեքստ, և մենք դեռ չենք թարթել լուսադիոդը: Այստեղ
# 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 վայրկյանը մեկ անգամ: Անհրաժեշտության դեպքում այստեղ կարող են կիրառվել թարթման այլ ռեժիմներ:
Բայց լուսադիոդը պարզապես փայփայում է: Մենք նաև նպատակաուղղված ենք եղել ցուցադրությանը։
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)Ահա թե ինչի մասին էի խոսում՝ որքան պարզ և հարմար է դա կորուտինների հետ: Այս փոքրիկ ֆունկցիան նկարագրում է ՈՂՋ օգտատերերի փորձը: Կորուտինը պարզապես սպասում է կոճակը սեղմելուն և էկրանը միացնում է 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 որոնվածը և կառուցել .
Ես չեմ գրել Makefile, բայց ձեռքով անցել և հավաքել եմ բոլոր անհրաժեշտ ֆայլերը (ներառյալ գրադարանները) նման բան.
mpy-cross water_counter.pyՄնում է միայն վերբեռնել ֆայլեր .mpy ընդլայնմամբ՝ չմոռանալով նախ ջնջել համապատասխան .py-ը սարքի ֆայլային համակարգից։
Ես կատարել եմ բոլոր մշակումները ծրագրի (IDE?) ESPlorer-ում: Այն թույլ է տալիս վերբեռնել սկրիպտներ միկրոկոնտրոլեր և անմիջապես կատարել դրանք: Իմ դեպքում բոլոր օբյեկտների ողջ տրամաբանությունն ու ստեղծումը գտնվում է water_counter.py (.mpy) ֆայլում։ Բայց որպեսզի այս ամենն ինքնաբերաբար սկսվի, սկզբում պետք է լինի նաեւ main.py անունով ֆայլ։ Ավելին, այն պետք է լինի հենց .py, այլ ոչ թե նախապես կազմված .mpy: Ահա դրա չնչին բովանդակությունը
import water_counterՄենք գործարկում ենք այն, ամեն ինչ աշխատում է: Բայց ազատ հիշողությունը տագնապալի փոքր է` մոտ 1կբ: Ես դեռ պլաններ ունեմ ընդլայնելու սարքի ֆունկցիոնալությունը, և այս կիլոբայթն ինձ համար ակնհայտորեն բավարար չէ: Բայց պարզվեց, որ այս դեպքի համար էլ ելք կա։
Ահա բանը. Թեև ֆայլերը կազմվում են բայթկոդի մեջ և գտնվում են ներքին ֆայլային համակարգում, իրականում դրանք դեռևս բեռնվում են RAM-ում և գործարկվում այնտեղից: Բայց պարզվում է, որ micropython-ը կարող է բայթկոդ կատարել անմիջապես ֆլեշ հիշողությունից, բայց դրա համար անհրաժեշտ է այն ուղղակիորեն տեղադրել որոնվածի մեջ: Դժվար չէ, թեև իմ նեթբուքում բավականին ժամանակ պահանջվեց (միայն այնտեղ պատահաբար ունեի Linux):
Ալգորիթմն այսպիսին է.
- Ներբեռնեք և տեղադրեք . Այս բանը հավաքում է կոմպիլյատոր և գրադարաններ ESP8266-ի համար նախատեսված ծրագրերի համար: Հավաքվել է նախագծի գլխավոր էջի հրահանգների համաձայն (ես ընտրել եմ STANDALONE=այո պարամետրը)
- RЎRєR ° C ‡ P ° C SЊ
- Տեղադրեք անհրաժեշտ գրադարանները ports/esp8266/modules-ում micropython ծառի ներսում
- Մենք հավաքում ենք որոնվածը ըստ ֆայլի հրահանգների
- Մենք ներբեռնում ենք որոնվածը միկրոկառավարիչում (ես դա անում եմ Windows-ում՝ օգտագործելով ESP8266Flasher ծրագրերը կամ Python esptool)
Վերջ, հիմա «ներմուծում ssd1306»-ը կվերացնի կոդը անմիջապես որոնվածից, և RAM-ը դրա համար չի սպառվի: Այս հնարքով ես միայն գրադարանի կոդը վերբեռնեցի որոնվածը, մինչդեռ հիմնական ծրագրի կոդը կատարվում է ֆայլային համակարգից։ Սա թույլ է տալիս հեշտությամբ փոփոխել ծրագիրը՝ չվերակազմավորելով որոնվածը: Այս պահին ունեմ մոտ 8.5 կբ ազատ օպերատիվ հիշողություն։ Սա մեզ թույլ կտա ապագայում իրականացնել բավականին շատ տարբեր օգտակար գործառույթներ: Դե, եթե ընդհանրապես բավարար հիշողություն չկա, ապա կարող եք հիմնական ծրագիրը մղել որոնվածի մեջ:
Այսպիսով, ի՞նչ պետք է անենք դրա հետ կապված հիմա:
Ok, ապարատը զոդված է, որոնվածը գրված է, տուփը տպված է, սարքը խրված է պատին և ուրախությամբ թարթում է մի լամպ: Բայց առայժմ այս ամենը սև արկղ է (բառացի և փոխաբերական իմաստով) և դեռ քիչ օգուտ ունի: Ժամանակն է ինչ-որ բան անել MQTT հաղորդագրությունների հետ, որոնք ուղարկվում են սերվեր:
Իմ «խելացի տունը» պտտվում է . MQTT մոդուլը կամ դուրս է գալիս տուփից, կամ հեշտությամբ տեղադրվում է հավելումների շուկայից - չեմ հիշում, թե որտեղից եմ այն ստացել: MQTT-ն ինքնաբավ բան չէ՝ պետք է այսպես կոչված. բրոքեր - սերվեր, որը ստանում, տեսակավորում և փոխանցում է MQTT հաղորդագրությունները հաճախորդներին: Ես օգտագործում եմ mosquitto, որը (ինչպես majordomo) աշխատում է նույն նեթբուքով:
Սարքը առնվազն մեկ անգամ հաղորդագրություն ուղարկելուց հետո արժեքը անմիջապես կհայտնվի ցանկում:

Այս արժեքներն այժմ կարող են կապված լինել համակարգի օբյեկտների հետ, դրանք կարող են օգտագործվել ավտոմատացման սկրիպտներում և ենթարկվել տարբեր վերլուծությունների, որոնք բոլորը դուրս են այս հոդվածի շրջանակներից: Ես կարող եմ խորհուրդ տալ majordomo համակարգը բոլորին, ովքեր հետաքրքրված են — ընկերը նույնպես խելացի տուն է կառուցում և հստակ խոսում է համակարգը կարգավորելու մասին:
Ես պարզապես ցույց կտամ ձեզ մի երկու գրաֆիկ: Սա ամենօրյա արժեքների պարզ գրաֆիկ է

Երևում է, որ գիշերը գրեթե ոչ ոք ջուր չի օգտագործել։ Մի երկու անգամ ինչ-որ մեկը գնացել է զուգարան, և թվում է, թե հակադարձ օսմոսի ֆիլտրը գիշերը մի երկու լիտր է ծծում։ Առավոտյան սպառումը զգալիորեն ավելանում է։ Ես սովորաբար օգտագործում եմ ջուրը կաթսայից, բայց հետո ցանկացա լոգանք ընդունել և ժամանակավորապես անցա քաղաքային տաք ջրին. սա նաև հստակ երևում է ներքևի գրաֆիկում:
Այս գրաֆիկից ես իմացա, որ զուգարան գնալու համար պահանջվում է 6-7 լիտր ջուր, ցնցուղ ընդունելու համար՝ 20-30 լիտր, սպասք լվանալու համար՝ մոտ 20 լիտր, իսկ լոգանք ընդունելու համար՝ 160 լիտր ջուր: Իմ ընտանիքը օրական ինչ-որ տեղ 500-600 լիտր է սպառում։
Նրանց համար, ովքեր հատկապես հետաքրքրված են, կարող եք դիտել յուրաքանչյուր առանձին արժեքի գրառումները

Այստեղից ես իմացա, որ երբ ծորակը բաց է, ջուրը հոսում է մոտավորապես 1 լիտր 5 վրկ արագությամբ։
Բայց այս տեսքով վիճակագրությունը հավանաբար այնքան էլ հարմար չէ նայելու համար։ Majordomo-ն նաև հնարավորություն ունի դիտելու սպառման գծապատկերներն ըստ օրվա, շաբաթվա և ամսվա: Ահա, օրինակ, սպառման գրաֆիկը ձողերով

Առայժմ միայն մեկ շաբաթվա տվյալներ ունեմ։ Մեկ ամսից այս գրաֆիկը ավելի ցուցիչ կլինի՝ յուրաքանչյուր օր կունենա առանձին սյունակ: Նկարը փոքր-ինչ փչացած է այն արժեքների ճշգրտումներով, որոնք ես մուտքագրում եմ ձեռքով (ամենամեծ սյունակը): Եվ դեռ պարզ չէ, թե արդյոք ես սխալ եմ դրել հենց առաջին արժեքները, գրեթե մեկ խորանարդ պակաս, թե սա ծրագրաշարի վրիպակ է, և ոչ բոլոր լիտրերն են հաշվվել: Ավելի շատ ժամանակ է պետք:
Գրաֆիկները դեռ կարիք ունեն որոշ կախարդանքի, սպիտակեցման, ներկելու: Թերևս ես կկառուցեմ նաև հիշողության սպառման գրաֆիկ՝ վրիպազերծման նպատակով, եթե այնտեղ ինչ-որ բան արտահոսի: Միգուցե ես ինչ-որ կերպ ցուցադրեմ ժամանակաշրջաններ, երբ ինտերնետ չկար։ Առայժմ այս ամենը գաղափարների մակարդակում է։
Ամփոփում
Այսօր իմ բնակարանը մի փոքր խելացի է դարձել. Նման փոքր սարքով ինձ համար ավելի հարմար կլինի տանը ջրի սպառումը վերահսկել։ Եթե նախկինում ես վրդովվում էի «նորից մեկ ամսում շատ ջուր ենք խմել», հիմա կարող եմ գտնել այս սպառման աղբյուրը։
Ոմանք կարող են տարօրինակ թվալ էկրանի ցուցմունքներին նայելը, եթե այն գտնվում է բուն հաշվիչից մեկ մետր հեռավորության վրա: Բայց ոչ շատ հեռավոր ապագայում ես նախատեսում եմ տեղափոխվել մեկ այլ բնակարան, որտեղ կլինեն մի քանի ջրի բարձրացուցիչներ, և հաշվիչներն իրենք, ամենայն հավանականությամբ, կտեղակայվեն վայրէջքի վրա: Այսպիսով, հեռահար ընթերցման սարքը շատ օգտակար կլինի:
Նախատեսում եմ նաև ընդլայնել սարքի ֆունկցիոնալությունը։ Ես արդեն նայում եմ շարժիչով փականներին: Հիմա կաթսան քաղաքային ջրին անցնելու համար պետք է 3 ծորակ բացեմ դժվարամատչելի խորշում։ Շատ ավելի հարմար կլինի դա անել մեկ կոճակով՝ համապատասխան նշումով։ Դե, իհարկե, արժե իրականացնել պաշտպանություն արտահոսքից:
Հոդվածում ես նկարագրեցի ESP8266-ի վրա հիմնված սարքի իմ տարբերակը: Իմ կարծիքով, ես գտա micropython որոնվածի շատ հետաքրքիր տարբերակ՝ օգտագործելով coroutines՝ պարզ և գեղեցիկ: Փորձեցի նկարագրել բազմաթիվ նրբերանգներ ու թերություններ, որոնց հանդիպեցի քարոզարշավի ընթացքում։ Թերևս ես ամեն ինչ շատ մանրամասն նկարագրեցի, որպես ընթերցող, ինձ համար ավելի հեշտ է շրջանցել ավելորդ բաները, քան հետո մտածել, թե ինչ է մնացել չասված:
Ինչպես միշտ, ես բաց եմ կառուցողական քննադատության համար:
Source: www.habr.com
