В последние несколько лет число проданных интеллектуальных домашних устройств постоянно растёт. Ожидается, что в 2021 году будет продано 1,5 миллиарда таких устройств. Среднее количество этих устройств на одно «умное» домашнее хозяйство составляет 8,7. Поэтому вполне возможно то, что у вас дома есть хотя бы несколько подобных устройств.

В этой сфере у программистов есть некоторые преимущества перед другими людьми, так как программисты могут самостоятельно создавать устройства для умного дома.



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

Предварительные требования


Сразу хочу показать то, что получилось у меня.

image

Система мониторинга погоды в действии

Так как это — DIY-проект, вам, прежде чем его воспроизвести, понадобится кое-что раздобыть. А именно, речь идёт о следующем:

  • Raspberry Pi 3 (или выше).
  • LCD-дисплей.
  • Соединительные провода.
  • Переменный резистор (необязательно).
  • Макетная плата (необязательно).

Теперь поговорим о том, как всё это собрать и наладить.

Шаг 1. Подготовка к работе с API ClimaCell и подключение дисплея к плате


Первый шаг нашей работы заключается в получении ключа для доступа к API ClimaCell и в подключении дисплея к плате.

Здесь мы будем, в качестве источника данных о погоде, использовать API ClimaCell Weather. Этот API даёт доступ к множеству показателей, в число которых входят и показатели качества воздуха.

Для работы с API ClimaCell нужно создать на сайте проекта учётную запись и получить ключ API, который будет использоваться для подписывания запросов к системе.


Лимиты API ClimaCell

Регистрация в системе бесплатна, в час можно выполнять до 100 запросов, количество запросов, которые можно выполнять ежедневно, ограничено 1000. Для нашего проекта этого более чем достаточно.

Когда в нашем распоряжении окажется ключ API, мы можем переходить к работе с аппаратными компонентами и заняться подключением LCD-дисплея к Raspberry Pi. Перед подключением дисплея к плате выключите её питание.

Схема расположения GPIO-портов Raspberry Pi 3 показана на следующем рисунке.


Схема GPIO-портов Raspberry Pi 3

Вот схема подключения дисплея к плате.


Схема подключения дисплея к плате

При таком подключении дисплей будет работать на полной яркости и на полном контрасте. Уровень яркости — это не проблема, но контрастность нуждается в дополнительной настройке, иначе на экране нельзя будет ничего разобрать.

Именно поэтому нам понадобится по меньшей мере один переменный резистор, с помощью которого мы будем настраивать уровень контраста дисплея.


Схема подключения переменного резистора

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

Шаг 2. Подготовка Node.js-проекта


Программная часть нашего проекта будет основана на Node.js. Если на вашем Raspberry Pi эта платформа ещё не установлена — обратитесь к данному простому руководству.

Создайте новую папку и выполните в ней команду npm init -y для инициализации нового Node.js-проекта. Затем выполните команду npm install lcd node-fetch для установки двух зависимостей, которые нам понадобятся.

  • Пакет lcd будет использован для организации работы с LCD-дисплеем.
  • Пакет node-fetch нужен нам для того чтобы выполнять HTTP-запросы к API ClimaCell.

Выше было сказано, что нам, для взаимодействия с API ClimaCell, нужен ключ API. Этот ключ можно поместить либо в код программы, либо — в специальный файл, config.json, предназначенный для хранения настроек проекта.

В случае с использованием config.json речь идёт о добавлении в него следующего:

{  "cc_key": "<your_ClimaCell_API_key>"}

Теперь, завершая этап предварительной подготовки Node.js-проекта, внесём в его главный файл следующий код:

// * Зависимости
const Lcd = require("lcd");
const fs = require("fs");
const fetch = require("node-fetch");

// * Глобальная константа
const { cc_key } = JSON.parse(fs.readFileSync("./config.json"));

Шаг 3. Работа с LCD-дисплеем


Вывод данных на LCD-дисплей с использованием пакета lcd — элементарная задача. Пакет представляет собой слой абстракции, расположенный над низкоуровневыми механизмами, предназначенными для работы с дисплеем. Применение подобного пакета избавляет нас от необходимости решать массу мелких задач при выводе данных на дисплей.

Вот код, который отвечает за работу с дисплеем.

const lcd = new Lcd({ rs: 26, e: 19, data: [13, 6, 5, 11], cols: 16, rows: 2 });

function writeToLcd(col, row, data) {
  return new Promise((resolve, reject) => {
    lcd.setCursor(col, row);
    lcd.print(data, (err) => {
      if (err) {
        reject();
      }
      resolve();
    });
  });
}

Сначала мы создаём объект lcd, передавая соответствующему конструктору объект с параметрами, содержащий, кроме прочего, сведения о пинах, используемых для подключения дисплея к плате.

Свойства объекта с параметрами cols и rows задают количество столбцов и строк дисплея. Здесь используется дисплей 16x2. Если вы используете другой дисплей, например, имеющий 8 столбцов и 1 строку, замените числа 16 и 2, соответственно, на 8 и 1.

Для того чтобы вывести что-то на дисплей, нужно последовательно использовать следующие методы объекта lcd:

  • lcd.setCursor() — выбор позиции, начиная с которой будут выводиться данные.
  • lcd.print() — вывод данных.

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

Сейчас у вас уже должно получиться вывести что-то на дисплей. Например, выполнение команды writeToLcd(0,0,'Hello World') должно привести к выводу на дисплее, в первой строке, начиная с первого столбца, текста Hello World.

Шаг 4. Загрузка сведений о погоде и вывод их на дисплей


Займёмся загрузкой сведений о погоде и выводом их на дисплей.

Платформа ClimaCell даёт в наше распоряжение много погодных показателей и, кроме того, сведения о качестве воздуха, сведения об уровне пыльцы в воздухе, об уровне риска при дорожном движении, о пожарах. Данных у нас очень много, но нельзя забывать о том, что на нашем дисплее всего 16 столбцов и 2 строки, то есть — 32 символа.

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

image
Эффект прокрутки

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

  • Текущая дата (час, минута, секунда).
  • Температура.
  • Интенсивность осадков.

Вот код, который отвечает за загрузку данных и за вывод их на дисплей:

function getWeatherData(apiKey, lat, lon) {
  const url = `https://api.climacell.co/v3/weather/realtime?lat=${lat}&lon=${lon}&unit_system=si&fields=temp&fields=precipitation&apikey=${apiKey}`;

  const res = await fetch(url);
  const data = await res.json();
  return data;
}

async function printWeatherData() {
  const { temp, precipitation } = await getWeatherData(cc_key, 45.658, 25.6012);

  // * первая строка
  await writeToLcd(0, 0, Math.round(temp.value) + temp.units);

  // * вторая строка
  const precipitationMessage =
    "Precip.: " + precipitation.value + precipitation.units;
  await writeToLcd(0, 1, precipitationMessage);

Для получения погодных данных с ClimaCell, например, для некоего города, нужно передать API географические координаты — широту и долготу.

Для того чтобы найти координаты своего города, можно использовать какой-нибудь бесплатный сервис, вроде latlong.net, а затем сохранить координаты в файле config.json вместе с ключом API. Эти данные вполне можно и ввести прямо в код.

Данные, возвращаемые API, выглядят так:

{
  lat: 45.658,
  lon: 25.6012,
  temp: { value: 17.56, units: 'C' },
  precipitation: { value: 0.3478, units: 'mm/hr' },
  observation_time: { value: '2020-06-22T16:30:22.941Z' }
}

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

Шаг 5. Завершение работы над проектом


Нам осталось лишь доработать код и, при поступлении новых данных, обновить сведения, выводимые на экране.

async function main() {
  await printWeatherData();

  setInterval(() => {
    printWeatherData();
  }, 5 * 60 * 1000);

  setInterval(async () => {
    await writeToLcd(8, 0, new Date().toISOString().substring(11, 19));
  }, 1000);
}

lcd.on("ready", main);

// * Если нажата комбинация клавиш ctrl+c, освободим ресурсы и завершим работу.
process.on("SIGINT", (_) => {
  lcd.close();
  process.exit();
});

Данные о погоде обновляются каждые 5 минут. Но, так как ClimaCell ограничивает количество запросов, выполняемых к сервису, 100 запросами в минуту, мы можем пойти ещё дальше и обновлять данные каждую минуту.

Выводить время мы можем, воспользовавшись одним из двух подходов:

  • Можно пользоваться свойством observation_time объекта, приходящего от API, и выводить время прибытия данных.
  • Можно сделать настоящие часы и выводить на дисплее текущее время.

Я выбрал второй вариант, но вы вполне можете поступить иначе.

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

общее количество столбцов - длина текста, который планируется вывести

Длина сведений о времени составляет 8 символов, а так как длина строки дисплея составляет 16 символов, начинать выводить эти сведения нужно со столбца №8.

Работа с дисплеем организована асинхронно. Поэтому, чтобы узнать о том, когда дисплей будет инициализирован и готов к работе, мы должны использовать библиотечный метод lcd.on().

Ещё одним рекомендованным методом работы со встраиваемыми системами является освобождение ресурсов при завершении работы программы. Именно поэтому мы используем обработчик события SIGINT для освобождения ресурсов, занятых дисплеем, при выходе из программы.

Существуют и другие подобные события:

  • SIGUSR1 и SIGUSR2 — для перехвата kill PID, вроде перезапуска nodemon.
  • uncaughtException — для перехвата необработанных исключений.

Шаг 6. Организация непрерывной работы скрипта


Наш скрипт готов и мы уже можем запустить программу. Но нам, прежде чем признать проект завершённым, осталось ещё кое-что сделать.

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

В то же время, если вы выключите плату, а затем, сразу, или через некоторое время, снова включите, скрипт автоматически не запустится. Запускать его придётся вручную.

Для того чтобы решить эти проблемы, мы можем воспользоваться менеджером процессов наподобие pm2.

Вот что нужно сделать:

  • Установка pm2: sudo npm install pm2 -g
  • Создание скрипта запуска для pm2: sudo pm2 startup
  • Запуск приложения: pm2 start index.js
  • Сохранение списка процессов в промежутках между перезапусками сервера: pm2 save

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

Итоги


Сейчас в вашем распоряжении есть система для мониторинга погоды, которую вы можете настраивать так, как вам захочется. Если знание данных о погоде для вас очень важно (или если вы хотите быть в курсе других показателей, выдаваемых ClimaCell, вроде уровня загрязнения воздуха), вы можете создать корпус для Raspberry Pi, в котором можно закрепить и LCD-дисплей. А затем, оснастив эту конструкцию батареей, её можно будет где-нибудь аккуратно поставить.

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

Планируете ли вы сделать что-то подобное тому, о чём шла речь в этом материале?