4.1 Общее знакомство

Одноплатные компьютеры Raspberry Pi появились на рынке несколько лет назад, и сразу получили признание у пользователей. Действительно, всего за 20-30$ покупатель получал практически полноценный компьютер с операционной системой Linux. Мы уже рассматривали платы Arduino, и как нетрудно было видеть, они весьма ограничены в своих возможностях. Linux позволяет использовать практически любые современные средства разработки, от Си или Python, до многопоточных или даже распределенных вычислений.

Компьютер Raspberry Pi - это небольшая плата длиной около 10см, которая выглядит примерно так:

На плате есть Ethernet и WiFi, 4 порта USB, видеовыход HDMI, порт для подключения камеры и ряд выводов, которыми можно управлять программно.

Доступен весьма большой набор различных интерфейсов (показан на картинке справа), от простого переключения пинов до уже рассмотренных нами I2C и SPI.

В этом пожалуй, одно из главных отличий - в отличие от обычных компьютеров, Raspberry Pi легко может подключаться к различным устройствам, ничуть не отличаясь в этом от Arduino. Работу с дисплеем, датчиками, сенсорами и различным оборудованием можно легко совмещать с гибкостью и настраиваемостью Linux. Например, Raspberry Pi легко подключить к домашней WiFi-сети. Или наоборот, Raspberry Pi может работать как точка доступа и не только “раздавать” интернет на другие устройства, но и например, мониторить температуру в помещении и посылать уведомления хозяевам. На Raspberry Pi вполне может работать полноценный Web или FTP-сервер, также можно подключить и USB-модем или иное оборудование. Вместо диска на Raspberry Pi используется карта MicroSD, с нее осуществляется загрузка системы.

Желающим сделать более компактное устройство, можно выбрать Raspberry Pi Zero W, которая по размеру еще вдвое меньше:

На момент написания этой главы (2018й год) последней версией является Raspberry Pi 3, более старые модели брать не рекомендуется, т.к. они имеют менее производительный процессор и не имеют WiFi.

Помимо оригинальной Raspberry Pi, на рынке появилось много клонов, плат такого же размера. Некоторые отличаются ценой, некоторые производительностью. Среди вполне удачных, можно отметить Asus Tinkerboard от известного производителя материнских плат Asus.

Asus Tinkerboard вдвое дороже Raspberry Pi, но имеет более мощный процессор, больше памяти, и может пригодиться для более ресурсоемких задач.

Еще одним плюсом популярности Raspberry Pi является большое количество аксессуаров - корпусов, экранов, реле и прочих дополнительных устройств. Это позволяет вполне гибко подобрать ресурсы под интересующие задачи. К примеру, можно докупить вот такой экран, позволяющий сделать из Raspberry Pi домашний мини-сервер, метеостанцию или пульт управления “умным домом”:

Важно заметить и то, что в отличие от обычного компьютера, Raspberry Pi потребляет мало энергии и не имеет вентиляторов, так что работает совершенно бесшумно.

Кстати о питании, для работы Raspberry Pi нужен блок питания с MicroUSB и максимальным током 2-2.5А, так что подойдет обычное зарядное устройство от любого современного смартфона.

Для первого включения нам понадобятся: Raspberry Pi, клавиатура, HDMI-кабель для подключения к монитору и кард-ридер для записи операционной системы Linux на карту памяти.

4.2 Настройка системы

Вначале нужно подготовить Raspberry Pi к первому использованию. В принципе, ничего не мешает использовать Raspberry Pi как обычный компьютер - подключить клавиатуру и мышь, и пользоваться. Но во-первых, это все же будет довольно-таки медленный компьютер, а во-вторых, это не так интересно, мы пойдем настоящим путем Linux, используя Raspberry Pi в режиме удаленного терминала через консоль.

Шаг-1. Подготовка карты памяти

Скачиваем дистрибутив Raspbian Stretch Lite со страницы на официальном сайте - https://www.raspberrypi.org/downloads/raspbian/. Мы выберем Lite-версию, т.к. она занимает меньше места, и для наших задач будет более эффективна.

Пока дистрибутив скачивается, устанавливаем программу Win32DiskImager. Вставляем карту памяти в кард-ридер, и с помощью win32diskimager записываем сохраненный дистрибутив на карту. По завершении записи карту памяти можно вставить обратно в Raspberry Pi.

Шаг-2. Настройка Raspbian

Подключаем Raspberry Pi к монитору и клавиатуре (мы будем использовать удаленный доступ, но один раз сделать это все-таки придется). Запускаем Raspberry Pi, дожидаемся приглашения ввода имени и пароля, вводим pi и raspberry.

Ура, мы в Linux! Никакого графического интерфейса, и ничего лишнего. Как настоящие хакеры (шутка) мы будем только вводить команды через Linux-терминал. Мышь можно не подключать, нам она не пригодится.

Вводим нашу первую Linux-команду: sudo raspi-config. Raspi-config это программа для настройки системы, а sudo говорит о том, что команда будет запущена с правами администратора, значит она может менять системные файлы и настройки.

Запустится текстовое окно конфигурации системы, в котором нужно будет выбрать следующие параметры:

- Активировать SSH (Advanced options - SSH). Это позволит нам иметь удаленный доступ к Raspberry Pi.

- Активировать I2C (кто забыл что это такое, может перечитать главу 2.8)

- Активировать SPI (через этот интерфейс могут подключаться некоторые дисплеи).

- При желании, можно настроить страну и часовой пояс (Internationalization options).

По завершении ввода настроек, перезагружаемся, если система не предложит сделать это сама, можно набрать команду sudo reboot.

Шаг-3. Подключаемся к Интернет

Следующим шагом мы должны подключить Raspberry Pi к Интернет, без этого на нее не удастся установить никаких программ. Здесь есть 2 варианта.

Самый простой способ - просто подключить Raspberry Pi к сетевому кабелю. При этом ничего настраивать не нужно, все заработает само. Однако, с точки зрения удобства использования платы, это не очень удобный вариант, лишний провод на столе будет только мешаться.

Способ сложнее, зато удобнее - подключиться к домашней сети WiFi. Если мы вдруг забыли имя своей WiFi-сети, можно набрать команду sudo iwlist wlan0 scan. Затем мы открываем текстовый редактор командой sudo nano /etc/wpa_supplicant/wpa_supplicant.conf. Nano - это текстовый редактор, а /etc/wpa_supplicant/wpa_supplicant.conf это путь к файлу, который мы будем редактировать.

Запустив редактор, добавляем в этот файл строки с именем сети и паролем:

network={

ssid="TP-LINK_AB11"

psk="12345678"

}

Для завершения редактирования нажимаем Ctrl+X, и на вопрос, сохранить ли файл, нажимаем Y (yes). Перезагружаемся.

В завершении, можно проверить наличие интернета с помощью команды ping www.google.com. Если все нормально, мы должны увидеть время ответа от сервера. Раз уж у нас появился интернет, запустим обновление системы, введем 2 команды sudo apt-get update и затем sudo apt-get upgrade. Процесс может занять минут 5-10, после чего Raspberry Pi можно выключить (ввести команду sudo halt). Теперь монитор и клавиатуру можно отключить.

Шаг-4. Настраиваем удаленный доступ

Теперь мы можем разместить Raspberry Pi там где нам удобно, и включить ее. Первым делом нужно узнать IP-адрес платы, его можно посмотреть в настройках домашнего маршрутизатора, либо в командной строке Windows (Win+R - cmd) ввести команду arp -a. В результате мы должны узнать IP-адрес, в моем случае это 192.168.0.104.

Если в качестве настольного компьютера мы используем OSX или Linux, то для коннекта к Raspberry Pi нам достаточно ввести команду ssh pi@192.168.0.104. Если же мы используем Windows, то сначала нужно скачать SSH-клиент, в качестве которого удобно использовать putty. Устанавливаем putty в какую-либо папку, затем вводим команду:

putty.exe pi@192.168.0.104

Если все было сделано правильно, появится окно ввода, примерно такое же, какое мы видели, когда подключали Raspberry Pi к монитору. Только теперь все те же действия мы можем делать удаленно. После ввода пароля raspberry, мы попадаем в уже знакомую нам командную строку:

На этом настройка завершена, мы можем использовать Raspberry Pi.

Кстати, вполне удобным является создание статического IP-адреса для Raspberry Pi в домашней сети (например 192.168.0.111), при этом он не будет меняться, что удобнее. Желающие могут найти соответствующие онлайн-руководства по настройке.

4.2 Основные команды Linux

Мы настроили удаленный доступ, и сначала нужно немного освоиться в среде Linux. Для начала целесообразно поставить файловый менеджер mc, для чего ввести команду sudo apt-get install mc. Apt-get это “менеджер программ”, а mc это название программы, которую мы устанавливаем. После завершения установки командой mc можно открыть файловый менеджер, напоминающий FAR или (если кто помнит) Norton Commander.

Здесь удобно то, что доступны как операции с файлами, так и доступ к консоли, просмотреть вывод которой можно с помощью клавиш Ctrl+O.

Также можно поэкспериментировать с командами ls (список файлов), mkdir (создание папки) и cd (смена текущей папки).

Чтобы создать текстовый файл, например текст программы, можно воспользоваться уже известным нам редактором nano. Например, чтобы создать программу на языке Python, наберем nano file1.py. В открывшемся окне введем текст программы (его можно просто скопировать через буфер обмена):

print “Hello world”

Завершить редактирование можно нажатием Ctrl+X, затем нужно нажать Y для подтверждения сохранения файла.

Сам редактор во время редактирования должен выглядеть примерно так:

Теперь можно выполнить программу, введя команду python test1.py.

Если все было сделано правильно, мы увидим результат выполнения:

Ура! Мы запустили нашу первую программу в среде Linux.

Установим также модуль для дополнительных библиотек языка Python, он нам пригодится в дальнейшем: sudo apt-get install python-pip. Также установим программу git, с помощью которой удобно скачивать исходные тексты программ: sudo apt-get install git.

Для копирования программ с “большого” компьютера можно использовать программу WinSCP, которая позволяет иметь прямой доступ к файловой системе Raspberry Pi. Это позволит редактировать программы на компьютере, затем копировать их в папку на Raspberry Pi, что достаточно удобно.

4.3. Основы языка Python

Код для Arduino мы писали на Си, здесь же мы будем использовать язык Python. Он удобен своей компактностью, отсутствием необходимости компиляции программ, и наличием большого количества дополнительных библиотек.

Для запуска Python-программы необходимо:

- Создать файл с текстом программы, например с помощью редактора nano:

nano test1.py

- Запустить программу на исполнение:

sudo python test1.py

Мы используем sudo т.к. наши программы будут работать с портами ввода-вывода, без этого они доступны не будут.

Кстати, чтобы запустить короткую программу на Python, ее необязательно сохранять в файл, код можно ввести непосредственно в интерпретатор. Для этого достаточно запустить python и просто ввести подряд соответствующие строки:

Закрыть интерпретатор Python можно нажатием Ctrl+D.

Рассмотрим простые примеры использования языка Python.

Объявление и вывод переменных

В Python достаточно ввести имя и значение.

x = 3

y = 10

print("x=", x)

print(x+y)

В отличие от языка С++, тип переменной будет определен автоматически, указывать его не нужно. Его можно при необходимости узнать, введя print (type(x)).

Циклы

В отличие от того же С++ или Java, циклы в Python задаются отступами, что после других языков программирования может быть непривычным. Часть кода, находящаяся внутри цикла, будет выполнена заданное количество раз.

Вывод чисел от 1 до 9:

for p in range(1,10):

print (p)

Вывод чисел от 1 до 9 с шагом 2:

for p in range(1,10,2):

print (p)

Для сравнения, вот такой код без отступов работать не будет, и это важно помнить, например при копировании кода из этой книги:

for p in range(1,10):

print (p)

Кстати, в Python 2.7 функция range возвращает объект “список”, и соответственно выделяет память заданного размера. Для большинства примеров в книге это не критично, но если нужно написать код типа for p in range(1,10000000), то range следует заменить на xrange, в противном случае программа вполне может занять гигабайт памяти даже на простом с виду цикле. Для Python 3.xx это не требуется.

Массивы

Массив это линейный набор чисел, с которыми удобно выполнять однотипные операции, например вычисление суммы или среднего арифметического.

Объявляем массив чисел:

values = [1,2,3,5,10,15,20]

Добавляем элемент в массив:

values.append(7)

Выводим массив на экран:

print (values)

Выводим элементы массива построчно:

for p in values:

print (p)

Это же можно сделать с помощью индексов (нумерация элементов массива начинается с 0):

for i in range(0,len(values)):

print (values[i])

Можно создать массив определенного размера, заполненный определенными числами. Создадим массив из 100 элементов, заполненный нулями.

values = [0.0] * 100

print (values)

Есть немного более сложный, но и более гибкий вариант создания массива. Создадим массив из 100 элементов, заполненный нулями:

values = [0.0 for i in range(100)]

Создадим массив, заполненный числами 0,1,..,99:

values = [i for i in range(100)]

Создадим массив, заполненный квадратами чисел:

values = [i*i for i in range(100)]

Создать двухмерный массив в Python также несложно:

matrix4x4 = [ [0,0,0,0], [0,0,0,0], [0,0,0,0], [0,0,0,0]]

matrix4x4[0][0] = 1

print (matrix4x4)

Аналогично вышеописанному способу, можно создать 2х-мерный массив 100x100:

values100x100 = [ [0.0 for j in range(100)] for i in range(100)]

Арифметические операции

Сложение, умножение, деление:

x1 = 3

x2 = (2*x1*x1 + 10*x1 + 7)/x1

Возведение в степень:

x3 = x1**10

print (x1,x2,x3)

Переменную также можно увеличить или уменьшить:

x1 += 1

x1 -= 10

print (x1)

Остаток от деления:

x2 = x1 % 6

print (x2)

Условия в Python кстати, задаются отступами, аналогично циклам:

x1 = 123

print (x1)

if x1 % 2 == 0:

print("x1 even number")

else:

print("x1 odd number")

Подсчитаем сумму элементов массива:

values = [1,2,3,5,10,15,20]

s = 0

for p in values:

s += p

print(s)

Также для этого можно воспользоваться встроенной функцией sum:

values = [1,2,3,5,10,15,20]

print(sum(values))

Пожалуй, этого не хватит чтобы устроиться на работу программистом, но вполне достаточно для понимания примеров в книге. Теперь вернемся к Raspberry Pi.

Примечание

У языка Python есть одна неприятная особенность - по умолчанию он “не знает” русской кодировки, ее нужно принудительно указывать в начале файла:

# -*- coding: utf-8 -*-

print "Русский текст"

Без этого интерпретатор выдаст ошибку. Поэтому чтобы не загромождать код и не писать # -*- coding: utf-8 -*- каждый раз в каждом примере, в дальнейших программах все комментарии будут писаться на английском.

4.4. GPIO: порты ввода-вывода

Знакомство с любой новой платой, мы начнем с уже традиционного - подключим светодиод. Тем более, что как можно увидеть, принципы по сути ничем не отличаются от Arduino. В этом, как и в любых следующих проектах, будем считать что Raspberry Pi включена и готова к работе, а доступ осуществляется через putty.

Наша первая схема будет совсем простой:

Принцип, как можно видеть, точно такой же - мы подключаем светодиод через токоограничительный резистор (кто забыл как это работает, может перечитать главу 1.4). Когда резистор подключен, включим Raspberry Pi и напишем код.

Если мы делаем такой проект первый раз, то сначала нужно поставить необходимые библиотеки. Введем команду sudo apt-get install python-rpi.gpio. GPIO - это general-purpose input/output, или “общие порты ввода вывода”, как раз то что нам нужно.

Запустим редактор для создания или редактирования файла, введем команду:

nano led_blink.py

Скопируем и вставим туда следующий код:

import RPi.GPIO as GPIO

import time

LedPin = 31 # GPIO6

# Setup

GPIO.setmode(GPIO.BOARD)

GPIO.setup(LedPin, GPIO.OUT)

# Loop

try:

while True:

      GPIO.output(LedPin, GPIO.HIGH) # led on

      time.sleep(1.0)

      GPIO.output(LedPin, GPIO.LOW) # led off

      time.sleep(1.0)

except KeyboardInterrupt: # Ctrl+C stop

pass

# Close

GPIO.cleanup()

Нажмем Ctrl+X для выхода из редактора, на вопрос сохранения файла нажмем Y (yes).

Теперь можно запустить программу, и мы увидим мигающий светодиод:

sudo python led_blink.py

Для завершения работы нажмем Ctrl+C, и мы вернемся обратно в командную строку.

Ура, программа работает! Разберем код программы подробнее.

- Строка import RPi.GPIO as GPIO указывает интерпретатору, что надо загрузить модуль RPi.GPIO и использовать его под названием GPIO (писать каждый раз RPi.GPIO было бы слишком длинно). Команда import time таким же способом загружает модуль time. Строка LedPin = 31 создает переменную с нужным номером вывода, тут все просто.

- Строка GPIO.setmode(GPIO.BOARD) указывает, какую нумерацию выводов мы будем использовать. Есть два варианта: GPIO.BOARD “говорит” о том, что будет использоваться сквозная нумерация выводов на плате. В этом случае пин имеет номер 31 (см. картинку на следующей странице). Второй вариант, GPIO.BCM задает нумерацию в номерах GPIO, в этом случае наш вывод 31 имел бы номер 6, т.к. он называется GPIO06. В принципе, практически все равно, какой способ использовать - вся эта путаница пошла от старых плат Raspberry Pi, которые имели меньшее число выводов. Главное, придерживаться какого-то одного стандарта и не путать одни номера с другими. Разумеется, если номер будет неправильный, светодиод не загорится.

На картинке показаны оба варианта нумерации выводов Raspberry Pi:

- Строки GPIO.output(LedPin, GPIO.HIGH) и GPIO.output(LedPin, GPIO.LOW) устанавливают нужный логический уровень “1” или “0”, что соответствует 0 или 3.3В, также как на плате ESP32. Функция time.sleep(1.0) делает паузу в 1 секунду.

- try..except - это незнакомая нам до этого конструкция, называемая обработчиком исключений. Цикл while True выполняется бесконечное число раз, но если пользователь нажмет Ctrl+C, то система пошлет программе исключение KeyboardInterrupt, в котором мы можем выполнить какой-то код. В данном случае кода нет, строка pass просто указывает на то, что нужно перейти на следующую строку.

- В завершении работы программы, функция GPIO.cleanup() освобождает ресурсы GPIO, и программа закрывается.

Важно

В отличие от Arduino, наша программа не будет стартовать сама при старте Raspberry Pi. Если нужно, можно добавить это самостоятельно, отредактировав файл автозапуска rc.local. Введем команду: sudo nano /etc/rc.local

Внутрь файла rc.local нужно будет добавить команду запуска:

sudo python /home/pi/led_blink.py &

Команду sudo мы уже знаем, знак & говорит о том, что программа будет запущена в фоновом режиме. /home/pi/led_blink.py - это путь к программе, он может быть и другим. Кстати, узнать путь к текущему каталогу можно, введя команду pwd.

Сохранив файл, перезагрузим Paspberry Pi - мы увидим мигающий светодиод.

4.5. Используем I2C

Достаточно большое количество разнообразных устройств (экраны, датчики, АЦП) можно подключить к шине I2C. Но перед ее первым использованием, необходимо настроить Raspberry Pi.

Шаг-1. Запустить sudo raspi-config и активировать I2C, как показано на рисунке (это достаточно сделать только один раз):

После изменения настроек следует перезагрузить Raspberry Pi.

Шаг-2. Поставить программу i2c-tools, введя команду: sudo apt-get install i2c-tools

Теперь можно ввести команду и посмотреть доступные i2c-устройства:

sudo i2cdetect -y 1

Отобразится окно примерно такого вида:

Если ничего не было подключено, список будет пуст, иначе мы увидим номера подключенных устройств.

Шаг-3. Подключение устройства

Шина I2C использует 2 провода для передачи данных (пины “3” и “5” на Raspberry Pi), еще 2 будут нужны для “0” и питания. Пример для подключения I2C-дисплея показан на рисунке. Обратим внимание, что для питания устройства используется вывод “3.3В”.

После того как устройство подключено, следует еще раз набрать команду i2cdetect -y 1 и убедиться, что его адрес виден в списке. Без этого пытаться запустить какой-либо код бессмысленно.

4.6. Подключаем OLED-дисплей

Наличие дисплея безусловно актуально для многих устройств, так что и Raspberry Pi мы также не оставим без внимания. Тем более, что дисплей подойдет тот же самый, который мы уже рассматривали.

Подключим дисплей, как показано на предыдущей странице. Убедимся, что дисплей “виден” в системе, должен отображаться код 3С:

Теперь установим библиотеки, необходимые для работы дисплея. Введем следующие команды:

sudo apt-get install python-imaging python-smbus pillow

sudo apt-get install git

git clone https://github.com/adafruit/Adafruit_Python_SSD1306.git

На этом шаге будет создан каталог Adafruit_Python_SSD1306. Зайдем в него и запустим установку библиотек для Python:

cd Adafruit_Python_SSD1306

sudo python setup.py install

Все готово. Проверить работу дисплея можно, запустив программу stats.py, находящуюся в папке examples:

sudo python examples/stats.py

Если все было сделано правильно, дисплей покажет информацию о системе:

Рассмотрим пример использования такого дисплея:

from PIL import Image, ImageDraw, ImageFont

import Adafruit_SSD1306

import time

disp = Adafruit_SSD1306.SSD1306_128_64(rst=None, i2c_address=0x3C)

# disp = Adafruit_SSD1306.SSD1306_128_32(rst=RST, i2c_address=0x3C)

disp.begin()

# Load default font.

font = ImageFont.load_default()

# Create blank image for drawing. '1' - monochrome 1-bit color

image = Image.new('1', (disp.width, disp.height))

draw = ImageDraw.Draw(image)

value = 0

while True:

# Clear screen

draw.rectangle((0,0,width,height), outline=0, fill=0)

draw.text((x, top), "Hello world", font=font, fill=255)

draw.text((x, top+8), "Value "+ str(value), font=font, fill=255)

disp.image(image)

disp.display()

time.sleep(0.5)

Разберем код подробнее. Директивы import подключают необходимые библиотеки. Затем мы создаем в памяти объект disp класса Adafruit_SSD1306.SSD1306_128_64 (либо 128_32, если мы имеем такой дисплей). i2c_address - это адрес дисплея. Функция ImageFont.load_default, как нетрудно догадаться из названия, загружает в память шрифт. Затем, с помощью функции Image.new мы создаем в памяти монохромное изображение-буфер, в которое будем рисовать данные. Подготовленное изображение выводится с помощью метода disp.image(), метод disp.display() обновляет картинку на экране. Такая технология называется “двойной буфер” (double buffer), изображение сначала формируется в памяти, и только затем обновляется, это позволяет избежать мерцания.

Самостоятельная работа: изучить код примера examples/stats.py, в нем можно найти полезные функции, например для получения IP-адреса устройства. Исходный текст stats.py можно найти на github.

4.7. Подключаем кнопки

Раз уж мы подключили дисплей, остался последний шаг для создания автономного устройства на базе Raspberry Pi - добавить поддержку кнопок. Тем более, что для тех кто читал главу 3.2 про порты ввода-вывода на ESP32, все будет просто - принцип остается тот же.

Напомним общую идею, которая обсуждалась еще в первой части про Arduino - специальный резистор “подтягивает” напряжение до шины питания, а кнопка при необходимости замыкает его на землю:

На Raspberry Pi все то же самое, только резистор уже встроенный, и ставить его отдельно не надо. Ну и напряжение не 5В, а 3.3В, но для пользователя это ни на что не влияет. В итоге, схема получается проще некуда: просто подключаем кнопку, которая соединяет вывод с “землей”:

А вот код, в отличие от Arduino, будет немного отличаться.

Точнее говоря, есть два способа.

Способ-1. Код в стиле Arduino.

import RPi.GPIO as GPIO

import time

btn_pin = 24 # Button to GPIO24

# Setup

GPIO.setmode(GPIO.BCM)

GPIO.setup(btn_pin, GPIO.IN, pull_up_down=GPIO.PUD_UP)

try:

# Loop

while True:

button_state = GPIO.input(btn_pin)

if button_state == False:

print 'Button Pressed...'

time.sleep(0.2)

except:

GPIO.cleanup()

Как можно видеть, здесь состояние кнопок читается в бесконечном цикле while True, и если значение кнопки равно логическому нулю, мы делаем какое-то действие.

Чем плох этот код? Он нормально работает на Arduino, т.к. там используется простой процессор, умеющий выполнять только одну задачу. Но на Raspberry Pi используется многоядерный процессор и полноценная многозадачная операционная система Linux, и постоянно работающий бесконечный цикл будет лишь зря забирать ресурсы процессора.

Гораздо правильнее “подписаться” на системные уведомления об изменении состояния кнопки, тогда код автоматически вызовется когда нужно.

Способ-2. Код с использованием событий (events).

import RPi.GPIO as GPIO

import time

btn_pin = 24 # Button to GPIO24

# Setup

GPIO.setmode(GPIO.BCM)

GPIO.setup(btn_pin, GPIO.IN, pull_up_down=GPIO.PUD_UP)

def btn_callback(channel):

if GPIO.input(btn_pin) != False:

print "Rising edge detected"

else:

print "Falling edge detected"

GPIO.add_event_detect(btn_pin, GPIO.RISING, callback=btn_callback, bouncetime=300)

# Loop

try:

while True:

time.sleep(1.0)

except:

GPIO.cleanup()

Разница значительна - здесь в основном цикле программы не делается ничего, кроме вызова time.sleep, такой код будет гораздо меньше нагружать процессор. Когда кнопка будет нажата, сработает функция btn_callback. Функция здесь только выводит сообщение, в реальном коде может обновляться содержимое дисплея или выполняться какое-то другое действие. Как можно видеть из кода, можно подписаться как на срабатывание кнопки (GPIO.RISING), так и на ее отпускание.

Разумеется, в реальном проекте может быть более одной кнопки, достаточно будет лишь добавить соответствующие вызовы GPIO.setup и GPIO.add_event_detect.

Самостоятельная работа: изучить код вывода текста на дисплей из предыдущей части, и объединив его с чтением состояния 4х кнопок, сделать систему меню. Простор для творчества тут большой, можно например использовать кнопки “вверх”, “вниз”, “OK” и “Cancel”.

4.8. Выходим в Web: запросы к серверу

Мы уже рассматривали получение количества друзей в “Контакте” в главе 3.6. Сделаем тот же самый запрос на языке Python. Здесь мы видим всю красоту и мощность данного языка, позволяющего делать вполне сложные вещи очень коротко и лаконично. Весь код умещается в 6 строк:

import json, urllib2

url = "https://api.vk.com/method/friends.get?user_id=29744451"

try:

data = json.load(urllib2.urlopen(url))

cnt = len(data['response'])

print cnt

except:

pass

Кстати, одна из полезных особенностей Python - его кроссплатформенность. Этот код можно выполнить и на Raspberry Pi, и на Windows, и на OSX, он будет работать одинаково.

Практически, подобные фрагменты удобно вынести в отдельную функцию, это делает код читабельнее и понятнее.

def getFriends(friendID):

url = "https://api.vk.com/method/friends.get?user_id=" + str(friendID)

try:

data = json.load(urllib2.urlopen(url))

cnt = len(data['response'])

return cnt

except:

return -1

Тогда вызвать его можно так:

n = getFriends(29744451)

print “Number of friends:”, n

Аналогично с числом подписчиков канала Youtube, все это можно записать в виде функции:

apiKey = "AIzaSyC26UJw-ubU6NXXXXXXXXXXXXXXXXXX"

def getSubscribersCount(channelID):

url = "https://www.googleapis.com/youtube/v3/channels?id=" + channelID + "&part=statistics&key=" + apiKey

try:

data = json.load(urllib2.urlopen(url))

return data["items"][0]["statistics"]["subscriberCount"]

except:

return -1

Может возникнуть вопрос, как мы получили строчку data["items"][0]["statistics"]["subscriberCount"]? Это просто, если посмотреть на json в браузере. Выглядит он напомним, так:

{

"kind": "youtube#channelListResponse",

"items": [

{

"kind": "youtube#channel",

"etag": "\"_gJQceDMxJ8gP-8T2HLXUoURK8c/Ea_ipJwsnrECB064UURA_RcRR0Y\"",

"id": "UCzz4CoEgSgWNs9ZAvRMhW2A",

"statistics": {

"viewCount": "30872448",

"commentCount": "313",

"subscriberCount": "258797",

"hiddenSubscriberCount": false,

"videoCount": "303"

}

}

]

}

Фигурными скобками обозначается элемент dictionary, доступ к элементам которого осуществляется через квадратные скобки - data["items"] вернет раздел items. Дальше смотрим на сам items: квадратные скобки в json обозначают массив, таким образом "items": [{... }] это массив из одного элемента типа dictionary, обратиться к нему можно как data["items"][0]. Дальше все аналогично, внутри есть еще один вложенный dictionary statistics, внутри него есть поле subscriberCount. Кстати, что такое dictionary, хорошо видно по полю statistics. Это так называемый “словарь” из парных элементов вида “имя”-”значение”, например { "viewCount": "30872448", "commentCount": "313", "subscriberCount": "258797" }.

Формат json сейчас является де-факто стандартом в web, за счет возможности описывать сложные иерархические структуры данных. Примеры разных файлов json желающие могут посмотреть на http://json.org/example.html. Поэтому при желании получать данные с различных веб-сервисов, нужно будет понимать как этот формат устроен.

Кстати, для отладки удобно выводить json прямо из Python, для этого достаточно стандартной функции print:

data = json.load(urllib2.urlopen(url))

print data["items"]

print data["items"][0]

print data["items"]["statistics"]

Так легко видеть, не ошиблись ли мы где-нибудь с полями данных.

4.9. Выходим в Web: запускаем свой сервер

Теперь рассмотрим обратную задачу - запустим свой сервер, который пользователь сможет открыть в браузере. Разумеется, Raspberry Pi имеет полноценную ОС Linux, и здесь можно запустить хоть Apache, хоть FTP. Но это не так интересно - мы запустим собственный сервер, который напишем на языке Python.

Для начала - самое простое, чего в 95% случаев будет вполне достаточно. Если мы хотим просто дать пользователю доступ к текущей папке, то в консоли достаточно ввести команду:

python -m SimpleHTTPServer 8000

Пользователь сможет зайти из браузера и увидеть (и скачать) любой файл:

Если нужно что-то посложнее, например управлять каким-либо устройством, придется написать свой код.

Рассмотрим минимальный пример работающего web-сервера на Python. Для примера, будем управлять светодиодом, как и в случае c ESP32. Но в отличие от ESP32, Raspberry Pi полноценно может работать с файлами, так что хранить HTML прямо в коде не требуется, просто сохраним файл как index.html.

Raspberry Pi server

Turn LED ON

Turn LED OFF

Сам код сервера будет весьма прост. Естественно, это минимальный пример, реальный веб-сервер может поддерживать практически все современные технологии - POST и GET-запросы, Javascript и пр. Полет фантазии тут неограничен. Нам же будет достаточно следующего кода:

from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer

class Server(BaseHTTPRequestHandler):

def do_GET(self):

self.send_response(200)

self.send_header('Content-type', 'text/html')

self.end_headers()

with open('index.html', 'r') as myfile:

data = myfile.read()

self.wfile.write(data)

if "/H" in self.path:

print "LED ON"

if "/L" in self.path:

print "LED OFF"

if __name__ == "__main__":

server_address = ('', 8000)

httpd = HTTPServer(server_address, Server)

print 'Starting httpd...'

try:

httpd.serve_forever()

except:

pass

print 'Done'

Самостоятельная работа: дописать код включения и выключения светодиода вместо функции print "LED ON" и print "LED OFF". Перед вызовом сервера serve_forever также нужно будет дописать код инициализации GPIO.

4.10. Дистанционное управление со смартфона

С помощью портов ввода-вывода мы уже можем управлять внешними устройствами, например платой управления двигателем. Мы также можем запустить собственный сервер, как было показано выше. Добавим в наш сервер возможность реакции на нажатие и отпускание кнопок, это позволит использовать смартфон как полноценный пульт дистанционного управления. Такой интерфейс сейчас весьма популярен и используется, например, для управления мини-дронами.

Наш интерфейс, впрочем, будет попроще - сделаем страницу с всего двумя кнопками LEFT и RIGHT, при нажатии на которые на Raspberry Pi будут генерироваться соответствующие события.

Основная работа здесь будет в модификации файла index.html - в него мы добавим код, срабатывающий при нажатии и отпускании кнопок. Это позволит например, управлять радиоуправляемым танком, если поставить на него Raspberry Pi.

Для создания кнопки в HTML есть тег button, а для подписки на события нажатия и отпускания мы будем использовать Javascript, который поддерживается всеми браузерами.

Обновленный код index.html приведен ниже.

Raspberry Pi server

Разберем код подробнее. Как можно видеть, мы создали 2 кнопки с идентификаторами btn_left и btn_right. Далее мы добавили к каждой кнопке обработчики событий onmousedown, onmouseup и onmouseleave (на смартфоне нет мыши, но события нажатия и отпускания пальца называются также). Они будут вызываться когда пользователь нажимает или отпускает кнопку. Внутри каждого события вызывается соответствующая функция для левой (Left) или правой (Right) кнопки, например mouseDownL(). Наша задача - уведомить сервер, посылкой ему соответствующего GET-запроса. Для этого написана функция httpGetAsync. В обработчике каждой из кнопок мы вызываем разные функции, например BtnRightDown, BtnLeftDown. Переменная window.location.href хранит адрес сервера, таким образом при нажатии кнопки LEFT на сервер отправляется запрос http://192.168.0.124:8000/?BtnLeftDown.

На этом клиентская часть закончена. Сохраним этот файл как index.html. Сам сервер тоже необходимо дописать, чтобы он корректно обрабатывал новые запросы. Код сервера показан ниже, как можно видеть, изменения в нем минимальны.

from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer

class Server(BaseHTTPRequestHandler):

def do_GET(self):

print self.path

if "?BtnLeftDown" in self.path:

self.send_response(200)

self.send_header('Content-type', 'text/plain')

self.end_headers()

self.wfile.write("ok left start")

return

if "?BtnLeftUp" in self.path:

self.send_response(200)

self.send_header('Content-type', 'text/plain')

self.end_headers()

self.wfile.write("ok left end")

return

if "?BtnRightDown" in self.path:

self.send_response(200)

self.send_header('Content-type', 'text/plain')

self.end_headers()

self.wfile.write("ok right start")

return

if "?BtnRightUp" in self.path:

self.send_response(200)

self.send_header('Content-type', 'text/plain')

self.end_headers()

self.wfile.write("ok right end")

return

with open('index.html', 'r') as htmlfile:

self.send_response(200)

self.send_header('Content-type', 'text/html')

self.end_headers()

data = htmlfile.read()

self.wfile.write(data)

if __name__ == "__main__":

# Start server

server_address = ('', 8000)

httpd = HTTPServer(server_address, Server)

print 'Starting httpd...'

try:

httpd.serve_forever()

except:

pass

Мы проверяем, есть ли в запросе нужная строка (например BtnLeftDown), и если есть, то выполняем соответствующее действие. В данном случае мы отправляем клиенту подтверждение вида “ok left start”, это удобно для отладки. В реальности нужно будет добавить дополнительный код, например для активации соответствующих портов ввода-вывода.

Теперь все готово, сохраняем наш файл и запускаем сервер. Кстати, для тестирования можно использовать тот же компьютер, используя ip-адрес http://127.0.0.1:8000. Запускаем браузер, нажимаем кнопки, и в окне отладки браузера видим ответы сервера.

Теперь мы можем управлять чем угодно с помощью длинных или коротких нажатий кнопок. Можно кстати, приобрести на ebay специальную плату с гусеницами и моторами, и управлять ею удаленно.

Самостоятельная работа: добавить код еще для двух кнопок “вперед”, “назад”, это позволит сделать вполне полноценное управление.

4.11. Подключаем веб-камеру

Мы не будем рассматривать подключение к Raspberry Pi всех датчиков, которые рассматривались в главе про Arduino - по сути принцип тот же, и сделать “по аналогии” совсем не сложно. На крайний случай, подключение например, того же DS18S20 к Raspberry Pi можно найти в Гугле за 5 минут. Мы пойдем дальше, и рассмотрим то, что на Arduino или ESP32 сделать нельзя. Например, подключим к Raspberry Pi веб-камеру и посмотрим, что с ней можно сделать. Вычислительная мощность Raspberry Pi весьма неплоха, и позволяет решать интересные задачи, в том числе и по обработке изображений. Тем более, что камера вполне пригодится рассмотренном ранее на радиоуправляемом танке, если мы захотим сделать его на базе Raspberry Pi.

Рекомендуется использовать веб-камеры, которые уже были проверены на совместимость с Raspberry Pi, список таких камер можно найти здесь: https://elinux.org/RPi_USB_Webcams. Я использовал Logitech C920, которая в этом списке как раз есть.

Первым делом необходимо подключить камеру и убедиться, что она видна в системе. Наберем команду lsusb, мы должны увидеть список типа такого.

Если камера видна в системе, можно двигаться дальше. Самый простой способ получить изображение с камеры - установить программу fscam. Наберем команду sudo apt-get install fswebcam.

Получим картинку с камеры: введем команду fswebcam image.jpg. В текущем каталоге будет создано изображение с одноименным названием. В моем случае изображение оказалось пересвеченным, камера не успела настроиться на освещение. Для исправления этого можно добавить параметр задержки: fswebcam --delay=1 image.jpg.

Чтобы узнать список доступных разрешений, можно ввести команду v4l2-ctl --list-formats-ext. Для Logitech C920 максимальное разрешение составляет 1920x1080, что вполне неплохо. С fswebcam почему-то оно не заработало, максимальное разрешение составило 1280х720, что тоже неплохо. Для того чтобы указать разрешение, надо ввести команду: fswebcam -r 1280x720 image.jpg. Несложно сделать и так, чтобы изображения делались постоянно: fswebcam -r 1280x720 --loop 10 image.jpg. С помощью параметра loop задается время в секундах, в нашем примере оно равно 10 - каждые 10 секунд изображение будет перезаписываться на новое.

Несложно записать и видео, для этого нужно поставить программу avconv: sudo apt-get install libav-tools. Теперь можно записать видео, введя команду avconv -f video4linux2 -s 640x480 -i /dev/video0 video0.avi (для примера выбрано разрешение 640х480). Если в системе присутствует несколько камер, вместо /dev/video0 возможно придется указать другое устройство (список можно посмотреть, введя команду ls /dev/video*).

Самостоятельная работа: поэкспериментировать с различными настройками фото и видео, оценить требуемый объем данных для хранения или передачи таких данных.

4.12. Запускаем видеонаблюдение

Используя материал предыдущей страницы, несложно сделать на базе Raspberry Pi свой сервер видеонаблюдения. Для этого достаточно добавить на наш веб-сервер код отдачи изображения, и параллельно запустить fswebcam, который будет делать снимки. Правда, это будет не совсем “видео”, а скорее “фото” наблюдение, но тем не менее, это все равно интересно.

Шаг 1. Мы уже умеем запустить веб-сервер, который будет показывать страницу index.html из текущей папки. Поменяем файл index.html, чтобы он имел следующий вид:

Raspberry Pi server

Camera image:

Как нетрудно видеть, мы добавили туда элемент img, чтобы показывалась наша картинка.

Шаг 2. Сделаем чтобы программа fswebcam запускалась при старте, и закрывалась при выходе. Это не сложно сделать средствами Python:

cmd = 'fswebcam -r 640x480 --loop 10 image.jpg'

photoThread = subprocess.Popen("exec " + cmd, stdout=subprocess.PIPE, shell=True)

os.kill(photoThread.pid, signal.SIGINT)

Мы создаем поток photoThread командой subprocess.Popen, а в конце работы приложения посылаем потоку команду закрытия.

Шаг 3. Добавим в наш веб-сервер возможность “отдавать” файлы jpeg. Для этого нужно добавить новый тип Content-type = 'image/jpg', чтобы браузер “знал” что передаваемые данные это картинка jpg.

Готовый код показан ниже.

from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer

import os, subprocess, signal

class Server(BaseHTTPRequestHandler):

def do_GET(self):

if "image.jpg" in self.path:

try:

self.send_response(200)

self.send_header('Content-type', 'image/jpg')

self.end_headers()

f = open('image.jpg', 'rb')

self.wfile.write(f.read())

f.close()

except:

pass

return

with open('index.html', 'r') as imgfile:

self.send_response(200)

self.send_header('Content-type', 'text/html')

self.end_headers()

data = imgfile.read()

self.wfile.write(data)

if __name__ == "__main__":

# Start photos shooting

cmd = 'fswebcam -r 640x480 --loop 10 image.jpg'

photoThread = subprocess.Popen("exec " + cmd, stdout=subprocess.PIPE, shell=True)

# Start server

server_address = ('', 8000)

httpd = HTTPServer(server_address, Server)

print 'Starting httpd...'

try:

httpd.serve_forever()

except:

pass

# Close photo shooting

os.kill(photoThread.pid, signal.SIGINT)

Как можно видеть, все вполне просто. Если в строке запроса присутствует “image.jpg”, мы загружаем картинку, иначе “index.html”. Изменить частоту обновления изображений можно, поменяв параметр loop, там же можно поменять и разрешение.

Все готово - запускаем python web.py, и с любого браузера можем зайти на страницу http://192.168.0.104:8000 (адрес может быть другим) и увидеть картинку с камеры. Это можно будет сделать и удаленно, если настроить статический IP адрес и перенаправление порта 8000 на роутере.

Чтобы сервер автоматически стартовал при запуске Raspberry Pi, нужно будет добавить строку вызова в /etc/rc.local, например так:

cd /home/pi/Documents/Cam

python /home/pi/Documents/Cam/web.py &

Важно: поскольку сервер постоянно сохраняет изображения локально на карте памяти, это негативно скажется на ее ресурсе - количество операций записи для карт памяти ограничено. Если планируется постоянное использование такого сервера, рекомендуется создать RAM-диск в памяти и указать путь к нему в настройках fswebcam и index.html. Настройку RAM-диска желающие могут найти в онлайн-руководствах по Raspberry Pi.

4.13. Интервальная съемка (time-lapse photo)

С помощью fswebcam несложно делать серию фото, например каждые 5 секунд. Это позволяет делать интересные кадры, например для записи движения Луны или облаков.

Команда для запуска выглядит так:

fswebcam -l 10 -r 1280x720 test-%Y-%m-%d--%H-%M-%S.jpg

Здесь “-l 10” задает временной интервал, “-r 1280x720” задает разрешение, имя файла “test-%Y-%m-%d--%H-%M-%S.jpg” задает шаблон даты и времени.

Можно указывать разные форматы шаблонов даты-времени, воспользовавшись таблицей:

%d

День месяца [01..31]

%H

Час [00..23]

%I

Час в 12-часовом формате [00..12]

%j

День [001..366]

%m

Месяц [01..12]

%M

Минута [00..59]

%S

Секунда [00..59]

%W

Номер недели в году [00..53]

%w

День [0(Вс)..6]

%y

Год, 2х значное число [00..99]

%Y

Год, 4х значное число

Соответственно, при запуске вышеприведенной команды в текущей папке будут создаваться файлы вида test-2018-02-25--12-39-40.jpg, test-2018-02-25--12-40-50.jpg, test-2018-02-25--12-40-00.jpg и т.д. Потом их можно склеить в видео с помощью специальных утилит.

4.14. Подключаемся к камере с помощью OpenCV

Выше мы использовали fswebcam для записи изображений с камеры. Это хорошо работает, но если мы хотим большей гибкости настроек, то лучше написать собственный код. К тому же, как было сказано выше, постоянная запись файлов уменьшает ресурс карты памяти. Поэтому далее мы будем работать с камерой напрямую.

Для работы с изображениями есть полезная библиотека OpenCV. Для ее установки введем команду: sudo apt-get install python-opencv.

Теперь всего лишь в несколько строк кода, мы можем написать свой аналог fswebcam:

import cv2

import time

cam = cv2.VideoCapture(0) #set the port of the camera as before

try:

count = 0

while True:

retval, image = cam.read() #return a True bolean and and the image if all go right

print count

cv2.imwrite("frame-%d.jpg" % count, image)

count += 1

time.sleep(0.5)

except:

pass

cam.release()

Как можно видеть, мы создаем объект cam = VideoCapture и с помощью функции cam.read читаем изображение. Оно уже хранится в памяти, нам не нужен промежуточный файл, и это большой плюс. В частности, его можно сохранить в папке, вызовом функции cv2.imwrite. Мы также можем сохранять не все изображения, а лишь в случае срабатывания внешнего триггера, например кнопки или реле.

Помимо простого сохранения файлов, OpenCV имеет огромные возможности по обработке изображений - поворот, кадрирование, ресайз и прочие операции могут быть легко реализованы.

Самостоятельная работа: сделать селфи-камеру на Raspberry Pi. Для этого добавить в вышеприведенный код чтения состояния кнопки, и писать изображение в файл только в том случае, если кнопка нажата.

4.15. Запускаем сервер видеотрансляции

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

Введем команду: sudo apt-get install motion.

Откроем файл настроек, введем команду sudo nano /etc/motion/motion.conf.

Зададим следующие параметры:

daemon off

width 640

height 480

framerate 15

output_pictures off

ffmpeg_output_movies off

stream_port 8081

stream_quality 100

stream_localhost off

webcontrol_localhost off

Кстати, параметры output_pictures и ffmpeg_output_movies отвечают за сохранение изображений и видеороликов - программу motion также можно использовать как детектор движения. Файлы при этом будут складываться в папку /var/lib/motion. Нам эта функция не нужна, так что устанавливаем параметр output_pictures в off.

Наконец, введем команду sudo motion. Сервер работает - можно зайти в браузере на адрес http://192.168.0.104:8081 и увидеть изображение с камеры (из соображений приватности картинка не показана).

4.16 Отправляем данные через Dropbox

Мы уже рассматривали отправку данных из ESP32 в Dropbox в главе 3.13. Разумеется, то же самое можно сделать и на Raspberry Pi. Рассмотрим отправку обычного файла и текстовых данных в виде строки. Предварительно поставим библиотеки для Dropbox командой sudo apt-get install dropbox. Затем необходимо подключить приложение к Dropbox и получить ключ, как описано в главе 3.13.

Сам код весьма прост. В отличие от ESP32, здесь нам не нужно писать самостоятельно запросы к серверу, все уже реализовано в библиотеке.

import dropbox

import os

token = "V3z9NpYlRxEAAAAAAACHVdBdhVRCnXXXXXXXX"

dbx = dropbox.Dropbox(token)

# 1. Upload file "iter.txt" from the current folder

fullname = "data1.txt"

with open(fullname, 'rb') as f:

data = f.read()

try:

dbx.files_upload(data, "/" + fullname, dropbox.files.WriteMode.overwrite, mute=True)

except dropbox.exceptions.ApiError as err:

print 'Dropbox API error', err

# 2. Upload string as a file

try:

dbx.files_upload("1234567".encode(), "/data2.txt", dropbox.files.WriteMode.overwrite)

except dropbox.exceptions.ApiError as err:

print 'Dropbox API error', err

Отметим здесь 2 полезных параметра. WriteMode.overwrite указывает, что файл будет перезаписан, в противном случае мы получим ошибку, если файл был уже создан. Опциональный параметр mute=True указывает, что файл надо добавить “молча”, без уведомления пользователя (в противном случае на синхронизированном с Dropbox компьютере появляется всплывающее окно). Это бывает полезно, если файлы обновляются постоянно, например, картинка с камеры, которая сохраняется каждые 5 минут.

На этом мы закончим изучение Raspberry Pi, хотя по сути, это только начало. На этой платформе можно сделать много чего интересного, по мере появления нового материала главы будут дополняться.

А сейчас мы перейдем к изучению платы для самых маленьких - микрокомпьютера BBC:Micro.