}
} else {
Serial.println("Json parsing error");
}
} else {
Serial.println("HTTP request error");
Serial.println(httpCode);
}
http.end();
// Пауза
delay(120000);
}
Для использования этого кода будет необходимо заменить имя и пароль WiFi-сети, а также вставить идентификатор канала и правильный ключ для доступа к Google API.
При запуске программы данные будут выведены в Serial Monitor:
Самостоятельная работа: По аналогии с вышенаписанным, получить число просмотров для видеоролика на youtube. Для этого воспользоваться следующей функцией из Google API:
https://www.googleapis.com/youtube/v3/videos?part=statistics&id=ID&key=XXXX
(здесь ID - это идентификатор видеоролика, XXXX - Google API Key)
Функция вернет json, который выглядит примерно так:
{
"kind": "youtube#videoListResponse",
"etag": "\"_gJQceDMxJ8gP-8T2HLXUoURK8c/mISbZAYZi79hOI4WFXbLXPUphmg\"",
"items": [
{
"kind": "youtube#video",
"etag": "\"_gJQceDMxJ8gP-8T2HLXUoURK8c/PoZyfbkQEtIkGNfjDsAafjr_R08\"",
"id": "CizKnuwvXXg",
"statistics": {
"viewCount": "184277",
"likeCount": "8585",
"dislikeCount": "212",
"favoriteCount": "0",
"commentCount": "1349"
}
}
]
}
Необходимо исправить в коде обработку json, чтобы получить интересующие нас поля.
При желании можно вывести данные на OLED-дисплей, сделав автономно работающее устройство.
3.8 Запускаем собственный Web-сервер
Мы уже умеем получать и обрабатывать данные с различных серверов. Настала пора двигаться дальше - мы запустим на ESP32 собственный сервер. Его можно будет открыть в браузере, введя IP-адрес платы, подключенной к WiFi-сети. А если настроить статический IP-адрес и перенаправление портов на маршрутизаторе, то можно будет получить доступ к нашему серверу через Интернет, с любой точки земного шара!
Итак, приступим. Для использования ESP32 в качестве сервера мы будем использовать уже готовый класс WiFiServer. Он “слушает” входящие соединения, а его единственным параметром является номер порта (мы будем использовать порт 8000).
Код сервера приведен ниже.
#include
const char* ssid = "TP-LINK_AB11";
const char* password = "12345678";
WiFiServer server(8000);
void setup()
{
Serial.begin(115200);
delay(10);
// We start by connecting to a WiFi network
Serial.print("Connecting to "); Serial.println(ssid);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
WiFi.begin(ssid, password);
Serial.print(".");
}
Serial.println("");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
server.begin();
}
void loop() {
WiFiClient client = server.available(); // Получение входящего “клиента”
if (client) {
Serial.println("New Client.");
String currentLine = "";
while (client.connected()) {
if (client.available()) { // Есть непрочитанные данные
char c = client.read(); // читаем 1 байт
Serial.write(c);
if (c == '\n') { // Символ перевода строки
// 2 пустые строки - признак конца запроса, посылаем ответ
if (currentLine.length() == 0) {
client.println("HTTP/1.1 200 OK");
client.println("Content-type:text/html");
client.println();
client.print("
client.print("
Hello world ");client.println();
break;
} else {
// Очистить строку
currentLine = "";
}
} else if (c != '\r') { // if you got anything else but a carriage return character,
currentLine += c; // add it to the end of the currentLine
}
}
}
// Закрыть соединение:
client.stop();
}
}
Рассмотрим код подробнее.
Мы создаем объект WiFiServer, который “слушает” входящие соединения на определенном порту (в нашем случае 8000). Тот, кто подсоединяется к нашему серверу, называется “клиентом”. За взаимодействие с ним отвечает класс WiFiClient.
Далее, мы побайтно читаем данные с помощью вызова функции client.read(), и складываем данные в строку currentLine. К примеру, строка запроса может быть такой - GET / HTTP/1.1, здесь “GET” это тип запроса, “/” - это адрес главной страницы, 1.1 - тип стандарта. Но сам запрос мы не анализируем, а просто посылаем всегда один и тот же ответ - заглавную страницу с HTML-текстом “Hello world”. Строки типа “HTTP/1.1 200 OK” - это служебная информация, которая нужна браузеру (клиенту) чтобы понять, что мы ответили (200 - это код возврата OK, говорящий о том что все нормально). Строка “Content-type:text/html” говорит клиенту о том, что возвращаться будет HTML-страница.
Наша страница имеет следующий вид:
Hello world
Как можно видеть, она содержит “заголовок” (header) и “тело” (body). Заголовок содержит тег title - в нем хранится строка, которая будет выведена в заголовке браузера.
Итак, компилируем данный код, загружаем его в плату, и в Serial Monitor смотрим какой IP-адрес получила ESP32 при доступе к WiFi-сети. Вводим этот адрес в строку браузера, не забыв указать номер порта, например http://192.168.0.104:8000.
Все готово! Через небольшое время ожидания мы видим страницу браузера с нашим текстом Hello world. Обращаем внимание на то, что и заголовок и текст соответствуют тому, что мы ввели.
Самостоятельная работа: изучить команды HTML, поэкспериментировать с разными вариантами разметки и форматирования текста.
3.9 Управляем светодиодом через Web
Мы уже умеем запустить свой Web-сервер, но пока он не делает ничего полезного, кроме отображения Hello world. Исправим это упущение, и сделаем кнопки для управления светодиодом через веб браузер.
Принцип простой - мы добавим в HTML-страницу 2 ссылки, нажатие которых и будет отслеживаться сервером.
Новая HTML-страница будет выглядеть так:
Turn /H\">LED ON
Turn /L\">LED OFF
В HTML-странице можно видеть 2 ссылки с значением адреса /H и /L, когда пользователь нажмет на них, на сервер будет отправлен GET-запрос с соответствующим адресом. Это нам и нужно.
Код программы целиком приведен ниже.
#include
const char* ssid = "TP-LINK_AB11";
const char* password = "12345678";
int ledPin = 2;
WiFiServer server(8000);
void setup() {
Serial.begin(115200);
pinMode(ledPin, OUTPUT);
delay(10);
Serial.print("Connecting to ");
Serial.println(ssid);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
WiFi.begin(ssid, password);
Serial.print(".");
}
Serial.println("WiFi connected.");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
server.begin();
}
void loop(){
WiFiClient client = server.available();
if (client) {
Serial.println("New Client.");
String currentLine = "";
while (client.connected()) {
if (client.available()) {
char c = client.read();
if (c == '\n') {
if (currentLine.length() == 0) {
client.println("HTTP/1.1 200 OK");
client.println("Content-type:text/html");
client.println();
client.print("");
client.print("
client.print("
"); client.print("Turn LED on
");
client.print("Turn LED off
");
client.print("");
client.println();
break;
} else {
currentLine = "";
}
} else if (c != '\r') {
currentLine += c;
}
// Проверка ссылок от клиента, может быть "GET /H" или "GET /L":
if (currentLine.endsWith("GET /H")) {
digitalWrite(ledPin, HIGH); // GET /H - включить светодиод
}
if (currentLine.endsWith("GET /L")) {
digitalWrite(ledPin, LOW); // GET /L выключить светодиод
}
}
}
// Закрыть соединение
client.stop();
}
}
Запускаем программу, обновляем страницу, и видим нашу “панель управления”:
Проверяем, что нажатие на одну из ссылок действительно включает или выключает светодиод.
Кстати, если у интернет-провайдера доступна услуга “статический IP”, то можно настроить перенаправление портов в роутере, и управлять светодиодом через Интернет, даже из другого города или другой страны.
Самостоятельная работа #1: изучить разные варианты форматирования текста в HTML, например, таблицы, шрифты, выравнивание. Это позволит делать более сложный и более красивый интерфейс.
Самостоятельная работа #1: подключить к плате “пищалку”, и настроить управление ею через web. Это позволит посылать сигналы родственникам или друзьям.
3.10 Выводим изображения на web-сервере
Вышеприведенный сервер конечно, работает, однако далеко не является шедевром web-дизайна. Немного улучшить его можно, добавив изображения. Однако, это не так просто, т.к. наш мини-сервер не имеет файловой системы, и просто положить нужные файлы мы не можем. Вместо этого используем так называемое base64-кодирование, позволяющее вставить изображение прямо в код страницы.
Чтобы вставить картинку в HTML, нужно.
1. Выбрать любую картинку. Возьмем картинку попроще, чтобы она не занимала много места, например изобразим светодиод:
2. Сконвертируем изображение в base64-формат, для этого можно воспользоваться любым онлайн-конвертором, например этим: https://www.base64-image.de.
Мы получим строку такого вида:
data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAeAB4AAD/4QBaRXhpZgAATU0AKgAAAAgABQMBAAUAAAABAAAASgMDAAEAAAABAAAAAFEQAAEAAAABAQAAAFERAAQAAAABAAASdFESAAQAAAABAAASdAAAAAAAAYagAACxj//bAEMAAgEBAgEBAgICAgICAgIDBQMDAwMDBgQEAwUHBgcHBwYHBwgJCwkICAoIBwcKDQoKCwwMDAwHCQ4PDQwOCwwMDP/bAEMBAgICAwMDBgMDBgwIBwgMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDP/AABEIAC4AGgMBIgACEQEDEQH/xAAfAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgv/xAC1EAACAQMDAgQDBQUEBAAAAX0BAgMABBEFEiExQQYTUWEHInEUMoGRoQgjQrHBFVLR8CQzYnKCCQoWFxgZGiUmJygpKjQ1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4eLj5OXm5+jp6vHy8/T19vf4+fr/xAAfAQADAQEBAQEBAQEBAAAAAAAAAQIDBAUGBwgJCgv/xAC1EQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/AP3B/aG/aD8P/s2/Da58SeIpZ1tonWGCGBQ013KwJWOMEgZwpJJIAAJJwK+DfEH/AAWi8e3niB30fwp4WtNJLZit7xbi4uCnH3pFljGfonfv39c/4Kx+DZfHd/4Gt9z/AGS1S+lKdUaQmFQSPUAHB7bj6mvk7T/gZZtaMZ5Njb8BsgD3xX8xeJ3iljsvzieW4Sq6apct7Wu20nq+2ux9rkeQ06+HVeor3ufop+xf+2fo/wC1v4Tu3is5NG8QaOUXUNMkl83YjZ2Sxvgb42KsOQCpBBHQn26vgL/gnz8N3+H37SFlc2c7mK80+5s7qLdjKbVkDYz2dE/P619+1+s+GHF0+IslWNq/HGTg+l2rO/3NHgZ3l6weKdJbaNfM+PP+CnOvf2f4g8HRI5Z/s12/lJIARmSFVYjnjOfrg155pniHwn4I/skP4Ss/EnjGV08m0jWWJvNb7rNHtaMk8dVyfapv+Cklw+nftYaTc+Xe28k2gRQ2R2bv7QMM8ssvkBSWkZPMjBRRuBYHGGBPhNv8fQ3xBnvIX1Y65pN0Ld42heO7huw5jljZtodJAvGw9wMg5xX8u+J+Ex8uLMRmFKlJQUuXm5XZtRjH7UbPb/gn2uSVKP8AZ0KEpq/a9up9Q/BSHW9G/ao8M6x4mgg0WbVri4iFuF8mOMy25CQgkkMSUXjJJIOAO32rX57S6r4t1v8Aae8E6Bq8Wp3mqaFrdvfW9ncIGu/KjlCyyll+VoQGJLcqhUgkc4/Qmv3HwGhWo5XisNVpyhaq2uePK9Urp9LprWx81xQ4yrwqRad4rZ32Pmj/AIKHeCp734eazqWjareeG/FGs22leCrXxBZY+26DZ6rrNtaXc1tuyEm8qTIYc7okOflFdP8AB3/gnt8Jfgh4ehsdI8JWt/co6zS6prUsuq6neTBVXzpbm4Z5C/yg5DAKfuhcCq37dd9bW/wwuvtCyssF/oN1hApyU12yK9f9vb+Gcc17qi7fzzX7fWw9KslGrFSXnr+Z8zGTjNtHzL+2RoP/AAq6HwdrOlarqwvrT4j+GLsG71Ge7e3S91OHTLqKIyuxWKW2uZVMSnaCcgA19PV8uf8ABVCddN+DXhu6AxInjPws6uBllZPEemBf1kP/ANevqOtlojOL95n/2Q==
3. Создадим текстовую переменную, которая будет хранить наше изображение и скопируем в нее всю строку:
const PROGMEM char *image01 = "data:image/jpeg;base64,/9j/4A….5n/2Q==”;
Тег PROGMEM указывает компилятору, что это константа, которую можно хранить во флеш-памяти, сэкономив тем самым место для других переменных. Это особенно актуально, если картинок много.
4. Теперь мы можем использовать наше изображение с помощью тега img следующим образом:
Для этого добавим следующий код в следующую строку после “Turn LED off”:
client.print("");
Наш веб-сервер вряд ли получит премию года за лучший дизайн, но по крайней мере, пользователь теперь видит, что он управляет именно светодиодами:
Кстати, в реальном проекте все изображения целесообразно вынести в отдельный файл, который можно назвать например, “images.h”. Тогда в основной программе можно будет написать #include "images.h". Это позволит отделить ресурсы от кода, и сделать текст программы более читаемым.
Самостоятельная работа: вывести разные типы изображений, например кнопки, светодиоды.
3.11 Удаленный мониторинг
С помощью web-сервера можно не только включать и выключать светодиод, но и получать информацию с удаленного объекта. Например, можно узнать температуру в квартире, находясь на каникулах или в отпуске. Как нетрудно догадаться, для этого достаточно вывести нужные нам параметры в HTML, который и отобразится на веб-странице.
К примеру, достаточно добавить такую строку в код формирования HTML:
if (digitalRead(buttonPin) == HIGH) {
client.print("Button state: ON
");
} else {
client.print("Button state: OFF
");
}
При обновлении страницы в браузере мы увидим соответствующий текст. Разумеется, это может быть не только кнопка, но и например, датчик закрывания двери или датчик освещенности (желающие могут перечитать главу 2.6 “Ввод аналоговых величин”).
Рассмотрим пример подробнее. Допустим, у нас есть данные с разных сенсоров (подробнее можно прочитать в главе 2.7-2.10). Чтобы не загромождать тестовый код, представим данные в виде уже готовых переменных, реальный код чтения желающие могут добавить самостоятельно.
float temperature = 22.5;
int humidity = 60;
int pressure = 1010;
bool doorClosed = true;
bool windowClosed = false;
Наша задача - отобразить эти данные на веб-сервере, для чего проще всего воспользоваться HTML-тегом “Table” (таблица).
Пример таблицы в HTML:
Row 1, Column 1 | Row 1, Column 2 |
Row 2, Column 1 | Row 2, Column 2 |
В браузере это будет выглядеть вот так:
Здесь tr - это table row, строка таблицы, а td - это элемент ячейки. Таким образом, мы можем группировать данные так, как нам удобно. Добавим таблицу в наш сервер. Для экономии места будет приведена только функция loop, остальное не изменилось.
void loop() {
// Возобновление соединения при необходимости
while (WiFi.status() != WL_CONNECTED) {
delay(500);
WiFi.begin(ssid, password);
Serial.print(".");
}
// Данные с датчиков
float temperature = 22.5;
int humidity = 60;
int pressure = 1010;
bool doorClosed = true;
bool windowClosed = false;
// Здесь можно добавить код чтения
// ...
WiFiClient client = server.available();
if (client) {
Serial.println("New Client.");
String currentLine = "";
while (client.connected()) {
if (client.available()) {
char c = client.read();
if (c == '\n') {
if (currentLine.length() == 0) {
client.println("HTTP/1.1 200 OK");
client.println("Content-type:text/html");
client.println();
client.print("");
client.print("
client.print("
");client.print("
client.print("
Temperature, C | ");
client.print(String(temperature)); client.print(" |
Humidity, percent | ");
client.print(String(humidity)); client.print(" |
Pressure, hPa | ");
client.print(String(pressure)); client.print(" |
Door closed: | ");
client.print(doorClosed ? "yes" : "no"); client.print(" |
Window closed: | ");
client.print(windowClosed ? "yes" : "no"); client.print(" |
client.print("");
client.print("");
client.println();
break;
} else {
currentLine = "";
}
} else if (c != '\r') {
currentLine += c;
}
}
}
client.stop();
Serial.println("Client Disconnected.");
}
}
Как можно видеть, код довольно прост. Мы сформировали таблицу с нужными нам полями. Для вывода строк yes/no мы использовали конструкцию языка С doorClosed ? "yes" : "no", она позволяет выбрать одно из двух значений в зависимости от условия.
Запускаем браузер, и видим данные с нашего сервера:
Теперь мы можем оставить устройство подключенным, и наблюдать состояние дома или квартиры удаленно, с любой точки земного шара.
Самостоятельная работа #1: подключить реальные датчики, например DS1820.
Самостоятельная работа #2: Поэкспериментировать с раскраской и стилями таблицы. Например, так можно добавить красный цвет к ячейке:
3.12 Делаем односторонний мессенджер
Плата ESP32 может быть включена круглосуточно, что легко позволяет использовать ее для приема входящих сообщений: достаточно лишь добавить в веб-сервер форму для отправки текстовых данных. Это позволит любому кто знает адрес сервера, зайти на страницу и оставить сообщение его владельцу.
На языке HTML код для отправки выглядит просто, достаточно добавить следующие строки:
При этом страница в браузере будет иметь следующий вид:
При этом, если пользователь введет текст, например “123 456”, и нажмет кнопку “Submit”, браузером будет отправлен запрос “GET /send?msg=123+456 HTTP/1.1”, который мы можем обработать на сервере. Дальше, как говорится, дело техники - надо извлечить из строки подстроку, убрав начальные и конечные части.
Добавим код там, где проверяется начало строки:
if (currentLine.startsWith("GET /send?msg=") && currentLine.endsWith(" HTTP/1.1")) {
// String looks like GET /send?msg=123+456 HTTP/1.1
String msg = currentLine.substring(14, currentLine.length()-8);
msg.replace("+", " ");
Serial.println(msg);
}
Как можно видеть, с помощью функции substring мы взяли подстроку, пропустив 14 символов сначала (длина “GET /send?msg=”) и 8 символов с конца (длина “ HTTP/1.1”). Функция replace заменяет символы “+” на пробелы, чтобы из строки “123+456” получить “123 456”.
Добавим вместо Serial.println вывод на дисплей, который мы рассмотрели в главе 3.4.
Готовый код “мессенджера” целиком приведен ниже.
#include
#include "SSD1306.h"
const char* ssid = "TP-LINK_AB11";
const char* password = "12345678";
int ledPin = 2;
WiFiServer server(8000);
SSD1306 display(0x3c, 21, 22);
void setup()
{
Serial.begin(115200);
pinMode(ledPin, OUTPUT);
// Инициализация дисплея
display.init();
display.flipScreenVertically();
display.clear();
display.setFont(ArialMT_Plain_10);
display.setTextAlignment(TEXT_ALIGN_LEFT);
display.drawString(0, 0, "App started");
display.display();
delay(10);
// Запуск WiFi
Serial.print("Connecting to ");
Serial.println(ssid);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
WiFi.begin(ssid, password);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected.");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
server.begin();
}
void loop(){
while (WiFi.status() != WL_CONNECTED) {
delay(500);
WiFi.begin(ssid, password);
Serial.print(".");
}
WiFiClient client = server.available();
if (client) {
Serial.println("New Client.");
String currentLine = "";
while (client.connected()) {
if (client.available()) {
char c = client.read();
if (c == '\n') { // if the byte is a newline character
if (currentLine.length() == 0) {
client.println("HTTP/1.1 200 OK");
client.println("Content-type:text/html");
client.println();
client.print("");
client.print("
client.print("
");client.print("
client.print("
");client.print("");
client.print("");
client.println();
break;
} else {
if (currentLine.startsWith("GET /send?msg=") &&
currentLine.endsWith(" HTTP/1.1")) {
// Строка выглядит как “GET /send?msg=123+456 HTTP/1.1”
String msg = currentLine.substring(14, currentLine.length()-8);
msg.replace("+", " ");
// Вывод строки на дисплей
display.clear();
display.setFont(ArialMT_Plain_10);
display.setTextAlignment(TEXT_ALIGN_LEFT);
display.drawString(0, 0, msg);
display.display();
}
currentLine = "";
}
} else if (c != '\r') {
currentLine += c;
}
}
}
client.stop();
Serial.println("Client Disconnected.");
}
}
Теперь можно запустить программу, открывать сервер в браузере и ввести текст - он появится на экране. Это можно использовать например, для отправки сообщений своим близким, находясь в другом городе, в каникулах, отпуске или командировке.
Разумеется, данный код может быть улучшен. Например, данная библиотека не поддерживает переносы строк, так что длинная веденная строка не поместится на экран. Читатели могут сделать это самостоятельно, для этого можно воспользоваться функциями length и substring для разбивки длинной строки на строки.
Самостоятельная работа #1: добавить к плате “пищалку” или светодиод, которая будет сигнализировать о наличии нового сообщения. При желании также можно добавить на плату кнопку “Сообщение прочитано”, которой получатель подтвердит получение. Для этого достаточно добавить переменную messageIsRead, и при получении нового сообщения устанавливать ее в False, а при нажатии кнопки менять значение на True. В общем, простор для творчества тут весьма велик.
Самостоятельная работа #2: Добавить вывод нескольких сообщений. Для этого можно добавить несколько переменных, например “String msg1, msg2, msg3”, а при получении нового сообщения добавлять его в “конец очереди”:
msg1 = msg2;
msg2 = msg3;
msg3 = msg;
Функцию вывода на экран также придется изменить.
3.13 Отправляем данные через Dropbox
Мы уже умеем выводить с ESP32 данные через встроенный веб-сервер. К сожалению, главный его недостаток - необходимость наличия статического IP-адреса, если мы хотим получать доступ извне. Это удобно, но статический IP-адрес - это обычно платная услуга, этого хотелось бы избежать.
Есть альтернативный способ - сохранять данные в бесплатном сервисе Dropbox. Это позволит ESP32 периодически сохранять логи или какую-либо другую информацию в файлах Dropbox, и они будет автоматически синхронизироваться и появляться на домашнем компьютере или смартфоне.
Сначала нужно создать так называемое “приложение dropbox”, сделать это можно по ссылке https://www.dropbox.com/developers/apps/create:
Когда приложение создано, мы можем выбрать его в разделе My apps на странице https://www.dropbox.com/developers/apps.
Приложение Dropbox имеет доступ только к определенной папке, которая будет синхронизироваться с домашним или рабочим компьютером, и будет иметь путь “Имя_пользователя\Dropbox\Приложения\Имя_приложения”.
Внутри настроек приложения есть параметр “access token”, он будет нужен нам для получения доступа.
Там же выбираем пункт Generate access token:
Нажимаем кнопку “Generate” рядом с “access token”, и получаем ключ вида V3z9NpYlRxEAAAAAAACG5cjBXXXXXXXXXXXXXX, его надо сохранить, мы будем использовать его в дальнейшем.
На этом подготовительная часть закончена. Сам сервис Dropbox имеет разнообразное API для работы с данными, посмотреть список функций можно по адресу https://www.dropbox.com/developers/documentation/http/documentation. Нам для отправки файлов нужна будет всего лишь одна функция upload.
Для того, чтобы отличать файлы друг от друга, мы будем создавать файлы по шаблону “год-месяц-день-чч-мм-сс.txt”. Dropbox требует защищенного соединения по https, поэтому мы используем класс WiFiClientSecure.
Код программы целиком приведен ниже.
#include
#include
#include
const char* ssid = "TP-LINK_AB11";
const char* password = "12345678";
WiFiClientSecure client;
void uploadData(String content) {
Serial.println("Dropbox connecting...");
if (client.connect("content.dropboxapi.com", 443)) {
Serial.println("Dropbox connected");
// Сформировать имя файла по шаблону времени
time_t now = time(nullptr);
struct tm timeinfo;
gmtime_r(&now, &timeinfo);
char file_name[64] = {0};
strftime(file_name, 64, "log-%Y-%m-%d-%H-%M-%S.txt", &timeinfo);
Serial.print("Upload "); Serial.println(file_name);
// Отправка запроса
client.println("POST /2/files/upload HTTP/1.1");
client.println("Host: content.dropboxapi.com");
client.println("Authorization: Bearer XXXXXXXXXXXXXXXXXXXXXXXXXXXX");
char dropbox_args[255] = {0};
sprintf(dropbox_args,
"{\"path\": \"/%s\", \"mode\": \"overwrite\", \"autorename\": true, \"mute\": false}", file_name);
client.print("Dropbox-API-Arg: "); client.println(dropbox_args);
client.println("Content-Type: application/octet-stream");
client.print("Content-Length: "); client.println(content.length());
client.println();
client.println(content);
delay(5000);
client.stop();
Serial.println("Disconnect");
Serial.println();
} else {
Serial.println("Error: cannot connect");
Serial.println();
}
}
void setup() {
Serial.begin(115200);
delay(10);
Serial.print("Connecting to "); Serial.println(ssid);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
WiFi.begin(ssid, password);
Serial.print(".");
}
Serial.println("");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
// Установка времени через SMTP
Serial.print("Setting time using SNTP");
configTime(8*3600, 0, "pool.ntp.org", "time.nist.gov");
time_t now = time(nullptr);
while (now < 8 * 3600 * 2) {
delay(500);
Serial.print(".");
now = time(nullptr);
}
Serial.println("done");
// Загрузка данных #1
uploadData(String("Data from ESP32 - this is a test 1"));
delay(5000);
// Загрузка данных #2
uploadData(String("Data from ESP32 - this is a test 2"));
delay(5000);
// Загрузка данных #3
uploadData(String("Data from ESP32 - this is a test 3"));
}
void loop(){
}
Как можно видеть, мы создали функцию uploadData, которую можно вызывать нужное число раз (имеет смысл делать это не очень часто, например раз в 5 или 30 минут). В данном примере функция вызывается только из setup(), чтобы файлы не создавались постоянно.
Вместо XXXXXXXXXXXXXXXXXXXXXXXXXXXX нужно будет вставить свой токен, полученный в панели управления https://www.dropbox.com/developers/apps.
Результат - запускаем программу, и через некоторое время видим в папке Dropbox 3 файла.
При использовании в реальном приложении, целесообразно отправлять лог лишь по каким-то редким событиям (например срабатывание датчика открывания двери). Если же нужно отправлять лог постоянно, имеет смысл накапливать данные в буфере памяти объемом несколько килобайт, и отправлять данные лишь по мере заполнения буфера.
Важно. Это наверное должно быть и так очевидно, но лучше повторить. Функции любого сервиса, такого как Dropbox, Google, Amazon, всегда поставляются на условиях “как есть” - создатели не гарантируют их абсолютно бесперебойной работы (тем более, если речь идет о бесплатных услугах). Также эти функции могут периодически совершенствоваться и меняться, так что гарантий многолетней и бесперебойной работы устройства, увы, нет. Поэтому подходы, аналогичные описанным выше, не стоит использовать там, где от этого зависит безопасность людей или имущества.
На этом мы закончим описание работы с платой ESP32. Как можно видеть, свою цену в 8$ она отрабатывает на 110%. В следующей части мы рассмотрим работу с Linux на базе одноплатного компьютера Raspberry Pi.
Часть 4. Осваиваем Linux: Raspberry Pi
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.
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 приведен ниже.
document.getElementById("btn_left").onmousedown = function() { mouseDownL() };
document.getElementById("btn_left").onmouseup = function() { mouseUpL() };
document.getElementById("btn_left").onmouseleave = function() { mouseUpL() };
document.getElementById("btn_right").onmousedown = function() { mouseDownR() };
document.getElementById("btn_right").onmouseup = function() { mouseUpR() };
document.getElementById("btn_right").onmouseleave = function() { mouseUpR() };
function httpGetAsync(theUrl, callback) {
console.log('httpGetAsync: ' + theUrl);
var xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
// callback(xmlHttp.responseText);
console.log(xmlHttp.responseText);
}
}
xmlHttp.open("GET", theUrl, true); // true for asynchronous
xmlHttp.send(null);
}
function mouseDownL() {
httpGetAsync(window.location.href + "?BtnLeftDown", null);
}
function mouseUpL() {
httpGetAsync(window.location.href + "?BtnLeftUp", null);
}
function mouseDownR() {
httpGetAsync(window.location.href + "?BtnRightDown", null);
}
function mouseUpR() {
httpGetAsync(window.location.href + "?BtnRightUp", null);
}
Разберем код подробнее. Как можно видеть, мы создали 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, чтобы он имел следующий вид:
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 "data1.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 минут.
4.17 Распознавание лиц с помощью OpenCV
Вычислительная мощность Raspberry Pi весьма высока, и позволяет использовать довольно сложные алгоритмы, недоступные на Arduino или ESP32. Одна из таких задач - это обработка изображений. Мы уже умеем получать изображения с Web-камеры, добавим теперь возможность распознавания лиц. Это позволит к примеру, отправлять изображение с охранной камеры только тогда, когда на нем есть человеческое лицо, или сделать робота, который будет поворачиваться к стоящему перед ним человеку.
Разумеется, сама по себе задача распознавания лиц на изображении, весьма сложна, ее программирование требует весьма сложной математики. К счастью для нас, такие алгоритмы уже написаны, потребуется лишь использовать готовые библиотеки. Для обработки изображений мы будем использовать библиотеку OpenCV, чтобы поставить ее, предварительно введем команду sudo apt-get install libopencv-dev python-opencv.
Для начала, рассмотрим задачу распознавание лиц на уже готовом изображении. Для распознавания лиц используется так называемый классификатор Хаара, реализация которого уже есть в OpenCV.
import cv2
face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
img = cv2.imread('faces.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(gray, 1.3, 5)
for (x,y,w,h) in faces:
cv2.rectangle(img, (x,y), (x+w,y+h), (255,0,0), 2)
print " ", x,y, x+w,y+h
cv2.imwrite('faces_out.png', img)
Как можно видеть, код весьма прост. Мы создаем объект “CascadeClassifier”, в качестве параметра которого указываем файл 'haarcascade_frontalface_default.xml'. Этот файл хранит заранее обученный на большом числе изображений, набор данных, его необходимо взять из дистрибутива OpenCV и положить в папку с программой. Далее, исходное изображение “faces.png” переводится в черно-белое, а вызов метода detectMultiScale собственно и делает всю работу по распознаванию - на выходе мы получаем массив прямоугольников faces, с которым можем делать что угодно, например вывести на экран. Для удобства просмотра результатов мы также рисуем прямоугольник поверх исходной картинки с помощью метода cv2.rectangle. Итоговая картинка сохраняется под именем faces_out.png.
В качестве теста был взят обычный скриншот из браузера, результаты работы программы показаны на картинке:
Как можно видеть, алгоритм работает вполне неплохо. В окне вывода мы также получаем координаты прямоугольников:
1092 256 1187 351
853 223 1024 394
30 255 236 461
579 231 791 443
298 238 515 455
Самостоятельная работа: добавить распознавание лиц в код чтения изображения с веб-камеры из главы 4.14. Опционально, можно сделать анализ координат полученного прямоугольника, что позволит определить, находится человек слева или справа от камеры.
На этом мы закончим изучение Raspberry Pi, хотя по сути, это только начало. На этой платформе можно сделать много чего интересного, по мере появления нового материала главы будут дополняться.
А сейчас мы перейдем к изучению платы для самых маленьких - микрокомпьютера BBC:Micro.
Часть 5. BBC Micro:bit - электроника для самых маленьких
5.1 Введение
Компьютеры BBC Micro имеют давную историю. Еще в 80х британская компания BBC запустила обучающий проект, целью которого было повышение уровня компьютерной грамотности. Компьютер по тем временам был действительно “микро”, и выглядел примерно так:
С тех пор прошло около 30 лет, и на свет появилась новая “модификация” на современной элементной базе - BBC Micro:bit.
Плата предназначена для обучения младших школьников программированию, причем всем школьникам Великобритании она поставляется … бесплатно. Цель устройства - в простой и понятной форме показать как создавать программы, с уклоном именно в современную электронику и “интернет вещей”.
«На борту» BBC Micro:bit есть процессор ARM Cortex-M0, 256Кб Flash ROM, 16Kб RAM, 16МГц тактовая частота. Также есть поддержка BTLE, 2.4ГГц-трансмиттер для одноранговой связи (101 канал), акселерометр, компас, термометр, и линейка пинов GPIO, среди которых довольно много всего:
Также есть 2 кнопки для ввода (еще для ввода доступен жест «встряхивание»), светодиодная матрица 5x5, и 4 «крупных» пина, рассчитанных на то, чтобы ребенок прикрутил проводами или «крокодилами» что-нибудь несложное, например датчик влажности для цветка или переменный резистор.
Гребенка пинов сделана плоской, так что ее можно вставить в плату расширения (цена вопроса 10-15 Евро).
Есть разные платы расширения, например плата управления моторами, что позволяет сделать такого робота:
Для тех, кто учится не в Англии, плата бесплатной, разумеется, не будет. Но заказать ее по вполне невысокой цене можно на eBay или на Amazon. Кстати, пока плата идет почтой, можно уже начинать писать программы - на сайте доступен полностью функциональный эмулятор, к которому мы и приступим.
Для тех же, кому возможностей платы станет мало, можно заказать “Набор изобретателя” (inventor’s kit), состоящий из платы расширения, макетной платы и различных деталей.
А мы пока начнем с самого начала.
5.2 Первая программа для BBC Micro:bit
Первое что удивляет, никакого софта на компьютер ставить не нужно вообще (кстати, это удобно и для школ - им не придется покупать лицензионное программное обеспечение). При подключении платы к компьютеру, она просто видна как съемный диск. Дальше достаточно зайти на сайт http://microbit.org/code/ и выбрать на каком языке мы хотим писать — Javascript или Python. Мы выберем блочный редактор Javascript, как самый простой для начинающих. Сам код писать не придется, все конструкции перетаскиваются мышью прямо на экране.
Редактор Javascript в «блочном» режиме открывается прямо в браузере и выглядит вот так:
Традиционно, начнем с самого простого - мигания светодиодом. Здесь у нас даже не один светодиод, а целая матрица 5х5. Выберем раздел Basic, из него “перетащим” на экран компоненты show leds и pause, как показано на рисунке справа.
Как можно видеть, принципиального отличия от того, что мы делали в Arduino, в общем и нет. Лишь названия немного другие, например, вместо setup используется функция on_start, а вместо функции loop, используется функция forever. Инициализация режима ввода-вывода делается автоматически, что проще.
Сам код очень прост. Мы выводим определенную картинку на матрицу светодиодов, затем делаем паузу на 500мс (0.5с), затем меняем картинку на другую, и процесс повторяется. Режим перетаскивания блоков мышью удобен тем, что не дает сделать синтаксических ошибок, это позволяет создавать программы даже самым маленьким детям. Кстати, желающие посмотреть, как код устроен внутри, могут переключиться из “блочного” редактора в “обычный”, нажав кнопку Javascript. При этом откроется окно с текстом программы:
basic.forever(() => {
basic.showLeds(`
. . # . .
. # . # .
# . . . #
. # . # .
. . # . .
`)
basic.pause(500)
basic.showLeds(`
. . . . .
. . # . .
. # . # .
. . # . .
. . . . .
`)
basic.pause(500)
})
Здесь, как можно видеть, задается лишь функция basic.forever, что делается внутри нее, вполне читабельно и понятно.
Еще один удобный момент - при добавлении компонентов, симулятор платы слева начинает работать автоматически, показывая что получится в результате. Таким образом, для написания и отладки программы даже не нужна сама плата BBC Micro:bit, все можно сделать прямо в симуляторе на странице.
Наконец, посмотрев на программу в симуляторе, можно загрузить ее в реальную плату. Подключаем Micro:bit к компьютеру USB-кабелем (она определится в системе как диск), на странице нажимаем Download, и сохраняем получившийся Hex-файл на новый диск. После сохранения, программа запустится автоматически, и мы увидим горящие светодиоды. Как и в случае с Arduino, программа сохраняется во флеш-памяти, и при следующем включении платы будет запущена автоматически. Это позволяет использовать BBC Micro:bit автономно, отдельно от компьютера.
5.3 Возможности ввода-вывода
Несмотря на небольшой размер, BBC Micro:bit имеет достаточно разнообразные способы ввода данных.
Пользователю доступны:
- нажатие кнопок А или В,
- одновременное нажатие кнопок А+В,
- прикосновение одной рукой к контакту 0, 1 или 2, другой к GND,
- встряхивание,
- использование акселерометра.
Для примера, сделаем простой счетчик, увеличивающий значение при нажатии кнопки “А” или “В”, одновременное нажатие будет сбрасывать счетчик в 0. Он может пригодиться например, при подсчете числа посетителей на спортивном соревновании, птиц, прилетающих к кормушке, и пр. Такие устройства, кстати, до сих пор производятся и продаются в “механическом” виде:
Ну а мы сделаем электронную версию такого счетчика. “Код” несложно создать мышью, блок-схема показана на рисунке.
Те, кто использовал кнопки в Arduino, наверняка увидят знакомые конструкции. В целом, код не нуждается в комментариях, все просто и очевидно. Ординарное нажатие кнопки используется для инкремента счетчика, двойное нажатие используется для его сброса. К сожалению, матрица светодиодов не позволяет отображать длинные цифры, поэтому если значение счетчика больше 10, то автоматически включается прокрутка.
Как говорилось выше, код можно протестировать прямо в браузере, с помощью симулятора:
Когда программа проверена и отлажена, ее можно загрузить в реальную плату. Кстати, в комплекте с платой идет блок питания от батареек, так что BBC Micro:bit можно брать с собой и использовать не только дома, но и с друзьями или на улице.
Самостоятельная работа: попробовать остальные способы ввода, доступные на BBC Micro:bit. Кстати, среди них есть довольно-таки экзотические, например в качестве события можно использовать “свободное падение”.
5.4 Управление наклонами платы - использование акселерометра
Рассмотрим режим, наиболее подходящий для несложных игр - выведем на экране пиксел, которым можно будет управлять, наклоняя плату в разную сторону. За наклоны платы “отвечает” акселерометр - сенсор, позволяющий получить значения относительно осей x, y и z.
Шаг-1: Узнаем, как меняются показания акселерометра при наклоне платы. Это можно найти в документации, но гораздо проще и быстрее просто проверить. Для этого создаем простую программу, выводящую значения на экран.
Запускаем программу, наклоняем плату и убеждаемся что значения варьируются в диапазоне -1023...1023.
Шаг-2. Выведем на экране точку, положение которой будет определяться двумя координатами x и y.
Матрица светодиодов имеет размерность 5х5, соответственно, координаты точек будут изменяться от (0,0) до (4,4).
Шаг-3. Изменим значение x, если наклон платы превышает определенную величину. Максимальный наклон равен 1023, возьмем нечто меньшее, например 200. Также необходимо будет добавить проверку на пересечение краев экрана: х не должно быть меньше 0 и больше 4. Также добавим паузу, чтобы точка не “убегала” слишком быстро. Получается немного громоздкая, но вполне понятная конструкция.
Сам код вполне понятен - если наклон платы превышает пороговую величину, мы гасим старый пиксел командой unplot, увеличиваем (или уменьшаем) значение координаты, и зажигаем пиксел снова командой plot. Кстати, в данном случае бывает полезно переключиться в режим прямого редактирования кода - однотипные блоки и логические выражения вводить так гораздо быстрее. Полностью код выглядит так:
let y = 2
let x = 2
led.plot(x, y)
basic.forever(() => {
if (input.acceleration(Dimension.X) < -200 && x > 0) {
led.unplot(x, y)
x = x - 1
led.plot(x, y)
basic.pause(100)
}
if (input.acceleration(Dimension.X) > 200 && x < 4) {
led.unplot(x, y)
x = x + 1
led.plot(x, y)
basic.pause(100)
}
})
Запускаем программу, убеждаемся что можем управлять точкой с помощью наклонов платы:
Самостоятельная работа #1: Добавить обработку второй координаты y, чтобы точка могла двигаться не только по горизонтали, но и по вертикали.
Самостоятельная работа #2: Продумать и реализовать сценарий мини-игры, где сверху будут падать объекты, а с помощью “каретки” их можно будет ловить. “Каретку” можно будет сделать их двух точек, находящихся на нижней плоскости (y-координата всегда будет = 4), наклонами платы ее можно будет двигать вправо-влево. Для хранения информации о падающих объектах нужно будет добавить отдельные переменные.
5.5 Игра “Змейка”
Мы уже умеем двигать точку по экрану. Рассмотрим более сложную задачу - будем двигать “змейку”, состоящую из нескольких точек.
Основная сложность здесь - в хранении координат, для чего мы используем структуру данных, называемую массив. Массив - это список объектов одного типа, как раз то что нам нужно.
Шаг 1. Продумаем структуру данных. В нашем случае, мы будем использовать два массива для хранения координат x и y. Наша “змея”, таким образом, будет представляться в памяти следующим образом:
Важно иметь в виду, что массивы нумеруются с нуля. Соответственно, элемент x[0], y[0] указывает на “хвост” змеи, элемент x[3], y[3] указывает на “голову”. Обозначим количество элементов как N=4, это позволит легко менять длину змеи, изменив всего один параметр. Тогда “головой” будет элемент x[N-1], y[N-1].
К сожалению, создавать массивы в визуальном редакторе не очень удобно. Проще переключиться в режим кода, и добавить следующий текст:
let N = 4
let x: number[] = [0, 1, 2, 3]
let y: number[] = [4, 4, 4, 4]
Мы создали массивы x и y и проинициализировали их начальными значениями. Теперь выведем точки на экран:
for (let p = 0; p < N; p++) {
led.plot(x[p], y[p])
}
Шаг-2. Движение “змеи”. Это пожалуй, самая сложная часть данной задачи.
Чтобы передвинуть змею, мы должны:
- выключить крайний элемент в “хвосте”,
- сдвинуть все остальные элементы массива влево (первый станет нулевым, второй первым и пр),
- изменить значение элемента в “голове” и включить новый светодиод.
Графически для массива X это выглядит так (для Y все аналогично):
Все это несложно по сути, главное не ошибиться с индексами и не перепутать, что куда сдвигается. Новое значение xN будет вычисляться из старого, в зависимости от того, куда движется змея. Напишем универсальную функцию moveSnake, которая сдвигает змею в любую сторону.
function moveSnake(dx: number, dy: number) {
if (x[N - 1] + dx < 0 || x[N - 1] + dx > 4 ||
y[N - 1] + dy < 0 || y[N - 1] + dy > 4) {
return
}
led.unplot(x[0], y[0])
for (let q = 0; q < N - 1; q++) {
x[q] = x[q + 1]
y[q] = y[q + 1]
}
x[N - 1] = x[N - 2] + dx
y[N - 1] = y[N - 2] + dy
led.plot(x[N - 1], y[N - 1])
}
Рассмотрим код подробнее. Параметры dx и dy - приращения координат: если змея движется вправо, то dx=1, если влево, то dx=-1, если вверх то dy=-1, если вниз то dy=1. Т.к. “змея” движется головой вперед, то эти приращения добавляются к координатам “головы”. Но вначале мы проверяем, что “змея” не уйдет за края экрана, и что новые значения не будут меньше 0 или больше 4. Команда led.unplot(x[0], y[0]) выключает последний светодиод в “хвосте” змеи. Затем в цикле for мы сдвигаем все элементы “змеи”, как было показано выше на рисунке. Последним шагом мы формируем новое значение координат “головы”, которое мы определяем как старое значение, плюс новое приращение координат dx, dy. Наконец, нам достаточно включить новый светодиод функцией led.plot(x[N - 1], y[N - 1]).
Шаг-3. Подключение акселерометра. В зависимости от наклона платы, будем двигать “змею” в нужную сторону.
if (input.acceleration(Dimension.X) > 200) {
moveSnake(1, 0)
basic.pause(200)
}
if (input.acceleration(Dimension.X) < -200) {
moveSnake(-1, 0)
basic.pause(200)
}
...
На этом программа готова. Код полностью приведен ниже, его можно просто скопировать в редактор для запуска на плате или в симуляторе.
let N = 4
let x: number[] = [0, 1, 2, 3]
let y: number[] = [4, 4, 4, 4]
for (let p = 0; p < N; p++) {
led.plot(x[p], y[p])
}
function moveSnake(dx: number, dy: number) {
if (x[N-1] + dx < 0 || x[N-1] + dx > 4 || y[N-1] + dy < 0 || y[N-1] + dy > 4)
{
return
}
led.unplot(x[0], y[0])
for (let q = 0; q < N - 1; q++) {
x[q] = x[q + 1]
y[q] = y[q + 1]
}
x[N - 1] = x[N - 2] + dx
y[N - 1] = y[N - 2] + dy
led.plot(x[N - 1], y[N - 1])
}
basic.forever(() => {
if (input.acceleration(Dimension.X) > 200) {
moveSnake(1, 0)
basic.pause(200)
}
if (input.acceleration(Dimension.X) < -200) {
moveSnake(-1, 0)
basic.pause(200)
}
if (input.acceleration(Dimension.Y) > 200) {
moveSnake(0, 1)
basic.pause(200)
}
if (input.acceleration(Dimension.Y) < -200) {
moveSnake(0, -1)
basic.pause(200)
}
})
К сожалению, такой код уже достаточно сложен для редактирования в блочном редакторе, хотя в принципе это и возможно. Если все-таки переключиться в редактор, мы увидим такую картину:
Также редактор формирует не совсем удобный для чтения код, например строки
let N = 4
let x: number[] = [0, 1, 2, 3]
let y: number[] = [4, 4, 4, 4]
при переключении “туда-обратно” автоматически заменяются на
let y: number[] = []
let x: number[] = []
let N = 0
N = 4
x = [0, 1, 2, 3]
y = [4, 4, 4, 4]
Это не влияет на работоспособность программы, но читать такой код становится менее удобно.
Самостоятельная работа #1: Изменить длину “змеи”, для этого достаточно заменить параметр N и изменить начальные значения массивов координат.
Самостоятельная работа #2: Разместить на экране 3-4 дополнительные точки, которые змея сможет “съедать”. Для проверки “съедения” достаточно убедиться что новые координаты “головы” совпадают с координатами точки. Когда точка “съедена”, ее координаты можно заменить на недействительные, например на -1,-1. Опционально, можно увеличивать длину змеи, если она “съела” одну точку, для этого придется добавить новые элементы в массивы x и y и увеличить значение N. Добавить элемент к массиву можно, используя функцию push, таким образом, код увеличения “змеи” будет выглядеть так:
x.push(x[N - 1])
y.push(y[N - 1])
N = N + 1
5.6 Воспроизведение звука
Весьма интересными и несложными являются опыты со звуком. Сначала нужно подключить к BBC:Micro наушники или внешнюю колонку, схема подключения показана на рисунке справа.
Кабель с зажимами можно сделать самостоятельно, или купить готовый. Само воспроизведения звука не представляет какой-либо сложности. Есть два варианта.
1) Можно воспроизвести несложную мелодию из списка уже предустановленных, это может пригодиться, например в игре. Вот такой несложный блок воспроизведет Happy Birthday при нажатии на кнопку “А”:
2) Можно воспроизвести собственную мелодию, для чего потребуется задать высоту и длительность каждой ноты. При нажатии на ноту даже открывается окно мини-клавиатуры:
Разумеется, можно воспроизводить ноту или мелодию не только при нажатии на кнопку, но и при каком-либо событии, например при начале или окончании игры.
5.7 Использование радиомодуля
Помимо обычных способов ввода-вывода, BBC Micro:bit имеет встроенный радиомодуль. Это позволяет например, делать игры, в которые можно играть вдвоем - можно обмениваться данными между устройствами (разумеется, для этого нужно иметь как минимум 2 платы Micro:bit).
Функции для использования радиомодуля весьма просты - можно посылать число, строку или пару “имя-значение”. Для приема данных обработчики сообщений с аналогичными названиями.
Простая программа, показанная ниже, отправляет число 42 по нажатию кнопки B, на второй плате в это же время загорится “смайлик”.
5.8 Используем serial-порт
В главе про Arduino мы уже рассматривали использование последовательного порта для отладки программ. То же самое можно сделать и на BBC Micro:bit.
Шаг 1. Поставить драйвер последовательного порта, ссылка на который есть на странице mbed.com. При подключении платы к компьютеру в системе должен появиться новый COM-порт.
Шаг 2. Добавить компонент Serial port в визуальном редакторе. К примеру, вот такая несложная программа отправляет в порт значения акселерометра:
Отправлять данные можно различными способами, весьма удобен формат CSV (comma separated values), который выглядит примерно так:
0,-240,608,-768;
1,-288,848,-384;
2,-288,912,-672;
3,-368,672,-640;
4,-224,320,-960;
5,-192,-240,-976;
6,-160,-544,-752;
Формат CSV удобен тем, что для его обработки есть много различных программ, например его можно открыть в Microsoft Excel.
Шаг 3. Поставить программу чтения данных. Для этого можно использовать любую программу, умеющую работать с СОМ-портом, например, Hercules. Предварительно в “Диспетчере устройств” нужно найти новый порт, появляющийся при подключении платы (если порт не появляется, нужно переустановить драйвер).
Данные при подключенной плате выглядят примерно так:
Получив принятые данные, их можно открыть на компьютере. К примеру, вот такой график значений акселерометра получается при покачивании платы в руке (график был построен онлайн с помощью https://plot.ly/create/):
5.9 Подключаем внешние устройства
Как было показано выше, BBC Micro:bit имеет практически все необходимое для автономной работы. Но разумеется, при желании, можно подключить и внешние устройства.
Аналоговые величины можно считывать с помощью функции analog read. Это позволит подключать большое количество аналоговых устройств - датчики температуры, освещенности, влажности и пр.
В простейшем случае, можно подключить переменный резистор между выводами GND и 3V, и при вращении ручки значение будет меняться от 0 до 1023. Справа приведен пример подключения фоторезистора, что позволит измерять освещенность.
Блок-схема для вывода аналоговой величины на экране показана ниже:
Кроме чтения, есть и аналогичная функция analog write, позволяющая выводить значения, используя ШИМ. Желающие могут перечитать главу 2.4, где это описывалось подробнее.
Функция servo write pin позволяет управлять серво-машинкой, которую можно повернуть за заданный угол. Схема подключения показана на рисунке справа.
При желании использовать различные устройства и датчики, проще купить специальную плату расширения, т.к. контакты Micro:bit не очень удобны для самостоятельного подключения.
Можно также приобрести и модуль расширения с макетной платой, как показано на рисунке ниже. Такая плата позволит легко подключать различные устройства - кнопки, переключатели, светодиоды, потенциометры.
Стоит лишь иметь в виду, что BBC Micro:bit изначально ориентирована на начинающих, и делать на ней более-менее сложные проекты нецелесообразно. В том случае, когда возможностей Micro:bit станет мало, проще перейти на Arduino или на ESP32.
На этом данная часть книги закончена. Разумеется, описать все невозможно, но можно надеяться, что приведенный материал станет неплохой точкой для старта. Что-то недостающее или непонятное, уже можно будет найти с помощью Гугла или Яндекса.
По мере появления нового материала, он будет добавляться, стоит следить за обновлением версии книги в начале текста. Последнюю версию можно скачать на странице https://cloud.mail.ru/public/F6Vf/nY6iSxXcd.