Python речей: перші кроки

Ця стаття є адаптацією моєї доповіді «Python of things» з конференцій Web Camp 2016 і SE2016.

Інтернет речей (англ. Internet of Things, IoT) — концепція обчислювальної мережі фізичних предметів («речей»), оснащені вбудованими технологіями для взаємодії один з одним або з зовнішнім середовищем.

Якщо ви, як і я, любите Python, програмуєте на Python, і вас не залишає ідея застосувати ваші навички в світі фізичних речей, то ця стаття саме для вас. Вважаю за необхідне відразу зробити застереження, що мова піде не про суворий продакшн і випуск IoT гаджетів мільйонними тиражами, а радше про всілякі pet-проекти, які приємно створювати на вихідних в проміжку між дедлайнами.

Залізо: що пропонує ринок

Отже, ми хочемо мікроконтролер або одноплатний комп'ютер з можливістю програмування його на Python. Що нам може запропонувати ринок? Скажімо прямо, що ринок не надто рясніє пропозиціями для таких запитів, але дещо все ж є. А якщо точніше, то є 3 основних варіанти:

1) PyBoard — це плата з мікроконтролером STM32F405RG, програмована на MicroPython. MicroPython — це форк третього пітона, оптимізований для запуску на мікроконтролерах. Основний плюс цього варіанту — це те, що тут, на відміну від наступних двох варіантів, ми маємо можливість програмувати безпосередньо мікроконтролер, у нас немає ніяких операційних систем між нашим кодом і залізом. Тобто ми отримуємо швидке і — найголовніше — прогнозований час відгуку на будь-яку команду, не залежне від завантаженості операційної системи, запущених у тлі процесів тощо.

Мінуси — це, як видно на фото, відсутність будь-якої периферії з коробки, тобто Wi-Fi, Bluetooth, LAN etc потрібно докуповувати, озброюватися паяльником і припаювати самостійно. Також до мінусів можна віднести відносно високу ціну.

Якщо ви вже є власником плати з мікроконтролером інших виробників, то деякі з них також є можливість встановити MicroPython. З повним списком підтримуваних плат можна ознайомитися тут .

2) Intel GALILEO — це вже повноцінний одноплатний комп'ютер на базі процесора Intel® QuarkTM SoC X1000 (це 32-бітний процесор сімейства Pentium, хто ще пам'ятає таке сімейство). На цьому пристрої можна запускати різні складання Linux, а також Windows 8 в якості операційної системи.

Це пристрій не є мікроконтролером в прямому сенсі цього слова, але володіє всіма необхідними цифровими і аналоговими виходами і входами для підключення зовнішніх пристроїв. При цьому воно «Arduino certified», тобто підтримує всі Arduino shield та інші зовнішні пристрої, сумісні з Arduino, яких на ринку чимало. І що найбільш важливо для нас, доступ до всіх цих входів і виходами можна отримати за допомогою Python. Також з коробки ви отримуєте Mini-PCI Express, USB і LAN (RJ45) інтерфейси.

3) Raspberry /Orange /Banana Pis — останнє в нашому списку, але не останнє за значенням. Це плодово-ягідне сімейство, про який ми і будемо більш детально говорити в цій статті. Raspberry Pi — родоначальник даного сімейства, являє собою одноплатний комп'ютер на базі процесора ARM, на якому можна запустити різні складання Linux, Android і навіть Windows 10 (версія для Surface з процесором ARM). Конфігурація заліза (пам'ять, процесор), а також наявність зовнішніх інтерфейсів — різне у різних версіях даного продукту.

Популярність «малини» не змусила довго чекати появи натовпів наслідувачів і тут же з піднебесної поїхали всілякі її клони — найбільш відомі це Orange Pi і Banana Pi. Всі вони сумісні між собою і відрізняються лише конфігурацією заліза та наявністю/відсутністю тих або інших зовнішніх інтерфейсів з коробки. Далі мова в цій статті піде про програмування саме Raspberry Pi, але якщо у вас є будь-який з його клонів, все що ви тут прочитаєте з 99% ймовірністю буде доречна і для вас.

Робота з Raspberry Pi

Отже, ми хочемо використовувати Raspberry Pi для підключення всіляких зовнішніх датчиків, кнопок, моторів, лампочок, перемикачів та інших атрибутів реального світу. Давайте розберемося, що у нас для цього є.

Ми маємо 40-pin вихід для всіх наших пристроїв. Якщо хтось з читачів досить старий, щоб пам'ятати вінчестери і не тільки, що підключаються через Parallel ATA, може зауважити, що цей вихід абсолютно ідентичний, так що, якщо у вас залишилися старі IDE шлейфи, ви можете використовувати їх в якості подовжувачів.

Ще відразу ж впадає в око той факт, що на відміну від плат конкурентів, тут ні один pin не підписаний. Так що треба або запам'ятовувати, який з них що робить, або підписати (можна просто роздрукувати на папірці підпису і надіти зверху, а можна придбати спеціальні насадки з уже зробленими підписами для кожної ніжки).

По функціоналу усі ніжки можна розділити на 5 груп:
? GPIO (General-purpose input/output) — це програмовані піни, які доступні вам для роботи;
? Ground — земля;
? 3.3 v і 5v — харчування;
? EEPROM — для підключення мікросхем пам'яті EEPROM (в принципі, їх можна змінити і використовувати для інших потреб, але без особливої необхідності це робити не рекомендується).

Також кожен пін має свій порядковий номер, за яким можна до нього звертатися з програми. Причому є два способи нумерації — фізичний, де всі номери розставлені по порядку, і другий, за словами творців Raspberry, інтуїтивно зрозумілий, де всі номери розставлені за якогось недоступного мені принципом:


Який із способів використовувати, ви вирішуєте при ініціалізації GPIO у своїй програмі, після чого можете звертатися до пинам за їх номером.

Приклад: запалюємо діод

Пора переходити від слів до справи, так що давайте для прикладу розглянемо, мабуть, саму просту схему, яку можна зібрати — у нас є діод, кнопка, і ми хочемо, щоб поки кнопка була натиснута, діод світився, і щоб переставав, коли ми відпускаємо її. Схема буде виглядати так (на схемі зображена перша модель Raspberry, у якій було лише 26 пінів):

Кнопка підключена з одного боку до землі, а з іншого — до программируемому піну (номер 16). Діод підключений однією ніжкою до землі, а інший — до программируемому піну (номер 23). У даному прикладі при підключенні діода використаний 270? резистор, щоб не спалити діод (обчислити необхідний опір резистора ви завжди можете самі, згадавши нескладні закони з шкільної фізики). Теми коротких замикань і палів ми ще торкнемося, бо спалити все, в тому числі і Raspberry, досить просто, на жаль, про це я знаю не з чуток :)

Програму, що реалізує даний функціонал ми розіб'ємо на декілька логічних частин.

Ініціалізація GPIO:

import RPi.GPIO as GPIO //(1)

GPIO.setmode(GPIO.BOARD) //(2)
# or GPIO.setmode(GPIO.BCM)

GPIO.setup(18, GPIO.IN, pull_up_down=GPIO.PUD_UP) //(3)
# or pull_up_down=GPIO.PUD_DOWN

GPIO.setup(23, GPIO.OUT) //(4)

(1)Якщо ви використовуєте Raspbian — складання Debian для Raspberry, яку надають виробники, то прямо з коробки ви отримуєте Python бібліотеку RPi.GPIO, для доступу до PinBoard.

(2)Ініціалізуємо PinBoard, при цьому зазначаємо вид нумерації пінів, який хочемо використовувати. GPIO.BOARD — фізичний, тобто піни нумеруються по порядку, GPIO.BCM — з іншого, «інтуїтивно зрозумілий» варіант.

(3)Кожен з доступних для програмування пінів може бути визначений як вхід або вихід, причому його «роль» можна змінювати в процесі виконання програми. Для визначення стану піна використовується функція GPIO.setup, в яку ми передаємо номер піна (у відповідності з обраним нами раніше типом нумерації) і його стан (GPIO.IN — вхід, GPIO.OUT — вихід).

Також для «входів» є додатковий параметр pull_up_down, який може приймати два значення — GPIO.PUD_UP і GPIO.PUD_DOWN. Різниця в тому буде подано струм на цей вхід або немає, тобто ми можемо використовувати входи по-різному. В даному випадку кнопка одним контактом підключена до землі, а другим до входу на вхід постійно подається напруга (pull_up_down = GPIO.PUD_UP). При натисканні кнопки воно замикається з землею, і напруга на вході падає. Падіння напруги і буде індикацією натискання кнопки. Але ми можемо зробити і навпаки: підключити кнопку одним контактом до джерела струму, а другим до входу і не подавати на нього струм (pull_up_down = GPIO.PUD_DOWN). Тоді при натисканні кнопки струм на вході зросте, і індикацією натискання кнопки буде саме зростання напруги. Який з варіантів вибрати, залежить від конкретної ситуації.

(4)Ну і 23-ї пін ми ініціалізував як вихід. Виходи в Raspberry мають два стани: True і False — небудь струм подається (3.3 v), або ні.

Життєвий цикл програми:

try:
while True:
 if GPIO.input(18) == False:
 GPIO.output(23, GPIO.HIGH)
else
 GPIO.output(23, False)
finally:
GPIO.cleanup()

Весь життєвий цикл програми — це нескінченний цикл, в процесі якого ми постійно опитуємо наш вхід і чекаємо False, тобто моменту, коли напруга на ньому впаде. Якщо б використовували варіант з pull_up_down = GPIO.PUD_DOWN, то ми чекали б True, тобто коли напруга зросте. І якщо напруга впало, то ми подаємо на наш вихід струм (GPIO.HIGH або True), тобто запалюємо діод. А коли кнопку відпускають, і напруга знову зростає, ми перестаємо подавати струм на діод (GPIO.LOW або False).

Дуже важливе зауваження з приводу cleanup: ця функція прибирає напругу зі всіх програмованих пінів, які були під напругою — інакше після завершення роботи вашої програми, всі піни, які були під напругою, залишаться під напругою, що може призвести до небажаних наслідків. Наприклад, банальне — ви самі випадково їх чим-то замкнете, або більш ймовірний сценарій — всередині вашої схеми сталося коротке замикання. В такому разі може бути два результати: або Raspberry встигне помітити по різко виросло споживання енергії вашою програмою і завершить її за exception, або не встигне помітити і згорить. У другому випадку ваше «малину» вже ніщо не врятує, а от у першому, якщо ви використовували cleanup, то подача струму припиниться. Так що рекомендую завжди закінчувати ваші програми цієї рядком.

Використання callback-ів і PWM

Давайте трохи ускладнимо завдання. Тепер я хочу, щоб лампочка горіла завжди, але по натисненню кнопки починала горіти не так яскраво. І — для різноманітності — оформимо це у вигляді callback-ів.

Як вже говорилося вище, аналогових виходів тут немає, і ми не може регулювати напругу, але ми можемо використовувати PWM (Pulse-Width Modulation) і таким чином регулювати частоту і життєвий цикл сигналів.

PWM — це генератор імпульсів, тобто струм подається не постійно, а імпульсами. І у нього є два основних параметри — це частота (скільки імпульсів буде подаватися протягом секунди) і життєвий цикл (час життя одного імпульсу, який може змінюватися від 0 до 100%). Наприклад, ми встановили частоту 500 Гц — це означає, що протягом 1 секунди буде вироблено 500 імпульсів, і життєвий цикл 100% — це означає, що імпульси повністю заповнять собою цю секунду, а отже, кожен з них буде тривати 1/500 секунди. А якщо я зміню життєвий цикл на 50%, життєвий цикл одного імпульсу скоротиться в два рази і складе лише 1/1000 секунди, а значить, півсекунди діод буде горіти, а півсекунди — ні. А якщо бути більш точним, то 1/500 секунди горіти, а потім 1/500 немає, і таке швидке миготіння створить у нас враження, що він почав горіти не так яскраво.

import RPi.GPIO as GPIO

GPIO.setmode(GPIO.BOARD) # or GPIO.setmode(GPIO.BCM)

GPIO.setup(12, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(16, GPIO.OUT)

pwm_led = GPIO.PWM(23, 500) //(1)
pwm_led.start(100)

GPIO.add_event_detect(18, GPIO.FALLING, callback=press, bouncetime=100) //(2)

GPIO.add_event_detect(18, GPIO.RISING, callback=unpress, bouncetime=100)

def press():
 pwm_led.ChangeDutyCycle(50) //(3)

def unpress():
pwm_led.ChangeDutyCycle(100)

try:
while True:
 pass //(4)
finally:
GPIO.cleanup()

Початок таку ж точно, як у попередньому прикладі, але далі починаються відмінності.

(1)Прив'язуємо PWM з частотою 500 Гц до піну 23 і запускаємо його з життєвим циклом, рівним 100%.

(2)Оголошуємо два callback для піна 18, один на падіння напруги (GPIO.FALLING) і один на зростання напруження (GPIO.RISING). Падіння і зростання напруги означає, що воно опустилося нижче або піднялося вище 1.65 v, тобто половини від максимального напруги на піне. Callback — це якийсь callable об'єкт, який буде викликаний в кожному конкретному випадку.

І є ще один аргумент — bouncetime, в нашому випадку він не потрібен, але я додав його спеціально, щоб була можливість про нього згадати. Уявіть собі, що у нас коштує трохи інша задача — потрібно на одне натискання включати лампочку, на наступне включати, а на наступне знову вимикати і так далі. Так як ми перейшли в світ реальних речей, тут діють закони фізики, і коли ви натискаєте на кнопку, вона не відразу замикається, а, насправді, злегка вібрує, тобто замість одного натискання ми отримуємо кілька поспіль, що може злегка зіпсувати роботу нашої програми. Так ось параметр bouncetime — це кількість мілісекунд після події, що викликав callback, протягом яких програма не буде реагувати на аналогічні події. Зазвичай 100 мілісекунд вистачає, щоб контакти перестали тремтіти і продукувати натискання.

(3)Оголошуємо дві функції, які будуть викликані в разі натискання і відпускання кнопки. Одна з них змінює життєвий цикл PWM на 50%, тим самим роблячи лампочку тьмяніше, інша повертає все назад.

(4)Без нескінченного циклу і тут нікуди, інакше програма закінчиться, так і не встигнувши початися. Ну і очищення напруги на всіх пинах після закінчення програми.

Додаткові зовнішні пристрої

Насправді це всі операції, які доступні нам як програмістам, але їх більш ніж достатньо, щоб реалізувати практично будь-який проект, в розумних межах, звичайно.

Проте будь-який інтернет речей, крім самого мікроконтролера, вимагає наявності будь-яких зовнішніх пристроїв — найчастіше це всілякі датчики світла, вологості, температури, радіації, шуму і так далі. Багато з цих датчиків являють собою складні реостати, на які подається струм, і за різниці напруг на вході і на виході можна визначити показання датчика.

І тут ми стикаємося з проблемою, що ми ніяк не можемо вимірювати струм, адже, як обговорювалося раніше, всі наші цифрові входи, а аналогового жодного немає. Але вихід є завжди, і в цьому випадку вихід виглядає так — adafruit.com .

Цю даму звуть LadyAda (за паспортом Лімор Фрід), вона закінчила MIT, після чого організувала свою фірму під назвою AdaFruit, яка займається випуском всілякої електроніки, в тому числі периферії для Raspberry Pi. Також вона стала першою жінкою, що потрапила на обкладинку журналу Wired, і отримала ще купу всіляких нагород. Звичайно, це не єдиний вихід з нашої ситуації, з таким успіхом вже на її місці могла бути фотографія якогось китайця з AliExpress, але те, що це самий вражаючий вихід — це вже точно :)

В каталозі її магазину близько 350 найменувань різноманітних пристроїв, які можна підключити до Raspberry Pi, в тому числі і аналого-цифровий перетворювач, який допоможе вам вирішити проблему відсутності аналогових портів.

Але це навіть не головне: такий же точно перетворювач можна купити де завгодно, а от той факт, що для кожного свого продукту вони випустили бібліотеку Python, дає цієї компанії багато очок фори в очі питонистов. Всі ці бібліотеки недоступні в pip, тому доводиться їх брати прямо у них на GitHub .

Окремо хочеться зупинитися на інтерфейси для підключення зовнішніх пристроїв. Raspberry апаратно підтримує кілька інтерфейсів підключення: SPI, I2C, а також 1-Wire interface — це сокет-подібне з'єднання, яке дозволяє пристрою (датчика) створювати файл в папці /sys/bus/w1/devices і записувати туди всі свої дані. Для роботи з кожним з цих інтерфейсів є як вбудовані, так і купа сторонніх бібліотек, так що навіть якщо ви не знайдете готову бібліотеку для роботи з якимось конкретним зовнішнім пристроєм, то при наявності нормальної документації до цього пристрою написати власну бібліотеку не складе великої праці.

Серверна частина: корисні сервіси

Ще пару слів про велосипеди, і як їх не винаходити :) у нас йшлося про інтернеті речей, тобто речі, підключеного до інтернету, значить, там в інтернеті є щось, куди річ відправляє якусь інформацію, будь то показання датчика або повідомлення в Твіттер. Хочу порадити два відмінних сервісу, які дозволять вам в 90% випадків не морочитися з написанням серверної частини вашого інтернету речей.

IFTTT (if this, than that) — сервіс з категорії «must have» для всіх, а не тільки для програмістів IoT. Сервіс дозволяє створювати прості, як вони їх називають, «рецепти», по типу «якщо сталося це, зроби те-то». Наприклад, якщо з'явилася нова картинка в Instagram, поклади її до мене в Dropbox; якщо прийшов email з Хогвартса, затвить про це тощо. На дані момент IFTTT підтримує роботу з декількома сотнями різноманітних сервісів і дозволяє будувати як прості, так і вкрай складні рецепти.

У нашому випадку він може бути корисний можливістю побудувати рецепт типу: якщо прийшов запит HTTP такий-то, зроби те-то. Тобто ви можете твітів, постити в Facebook, складати інформацію в Google drive, відправляти листи і робити ще купу всього, просто відправляючи запити зі свого Raspberry з відповідною інформацією. І вам не потрібно для цього вивчати API Твіттер, Вконтакте і всіх інших.

ThingSpeak — цей сервіс спрямований тільки на IoT. Він надає масу можливостей, які частково перетинаються з IFTTT, тобто тут теж можна налаштувати деякі дії у відповідь на ваші HTTP запити. Але, крім цього, ThingSpeak надає у ваше розпорядження хмара, які ви можете використовувати для збереження ваших даних, їх аналізу і подальшої реакції. Найпростіший варіант — ви використовуєте його лише як базу даних для зібраної інформації, наприклад, кожні 30 секунд відправляєте йому вологість повітря в приміщенні, а потім просто выгребаете всі дані у вигляді Excel або CSV файлу.

Також ThingSpeak надає доступ до Matlab, де ви можете налаштувати всілякий аналіз та візуалізацію ваших даних у вигляді графіків та діаграм. Плюс до цього ви можете налаштувати графік аналізу даних, щоб кожен день в кінці дня отримувати на email зведену таблицю зміни вологості за останню добу. І, на додаток, можна налаштувати тригери, що якщо середньодобова вологість впаде нижче від якогось рівня, то зробити що-небудь — наприклад, включати пожежну сигналізацію, щоб приїхали пожежники, все залили і таким чином підвищили вологість, наприклад :)

Замість висновку: історія з особистого досвіду

Закінчуючи з теоретичними прикладами сферичних IoT у вакуумі, хочу навести приклад з реального життя, як Raspberry досить полегшив життя особисто мені.

Досить тривалий час я працював вчителем у школі, і однією з головних проблем сучасної української педагогіки, крім зарплати, є велика кількість паперової роботи. Особливо мене напружувало заповнення журналу. Навіть не сам процес, а купа додаткових дій, які необхідно було здійснювати. У наше століття інтернету і високих технологій мені треба було ногами йти, брати якусь книгу, вписувати в неї руками... І головне, що помилятися ні в якому разі не можна, бо за кожне виправлення треба було писати пояснювальну.

Тому раз на два тижні спеціально навчені старанні учениці заповнювали журнал за мене, щоб все було красиво і без виправлень. Але інформацію протягом цих двох тижнів треба було збирати і десь зберігати. Особливо гостро постало питання обліку відвідуваності, адже, як я вже говорив, на дворі 21-е століття, і робити перекличку кожен урок — це якась архаїка.

І тут-то Raspberry і згодився — за допомогою нього і NFC reader було зібрано пристрій обліку відвідуваності. У розібраному вигляді воно виглядало приблизно так:

Кожен учень мав картку з чіпом NFC, благо в Харкові всі картки метро обладнані чіпами NFC, так що проблем з цим не було. Я використовував NFC reader на базі чіпа PN532, який підтримував підключення через інтерфейс SPI. Кожна картка мала свій унікальний ID, який считывался і відправлявся на сервер, після чого і я, і батьки бачили, прийшов їх дитина на урок інформатики вчасно, запізнився або ж не прийшов взагалі.

На жаль, тоді я ще не знав про існування LedyAda і того, що у неї є вже готова бібліотека для роботи з PN532 NFC reader, тому мені довелося самостійно читати документацію і винаходити велосипед. Загалом, не повторюйте моїх помилок.


На цьому, мабуть, все. Сподіваюся, що вийшло цікаво. Також хочеться вірити, що ця стаття допоможе тим, хто давно хотів спробувати зробити щось своє в цій області, але весь час відкладав, все-таки зважитися спробувати. А ті, хто навіть не думав про це, задумаються і, може бути, теж зважаться.

Опубліковано: 26/10/16 @ 10:00
Розділ Різне

Рекомендуємо:

Новорічний конкурс від #binpartner
Junior дайджест: курси, стажування, інтернатура. Листопад'16
Як побудувати кар'єру в ІТ-компанії
Android дайджест #20: Google Pixel, Nougat 7.1, JRebel, Android Studio і Firebase
.NET дайджест #13: C# 7.0, .NET Core 1.0.1, навчальні матеріали ASP.NET