Создание домашнего погодного виджета с помощью Arduino и Raspberry
Продолжаем развивать наш «Умный дом» — следующий этап посвящён объединению устройств в единую сеть, часть 1.
Используем Open Weather Map
Чтобы построить умный дом, все интеллектуальные устройства нужно объединить в одну сеть. Стандартная Arduino UNO для этих целей не подходит. Как правило, эта плата выступает в роли периферийного узла, который связывает датчики и компоненты друг с другом, а затем передаёт данные в центральный «мозг». Эту роль «мозга» прекрасно выполняет Raspberry Pi. Останавливаться на подробном описании этой платы не стану. Она располагает всеми необходимыми функциями маломощного сервера, что идеально для IoT-устройств.
Связать два устройства можно несколькими способами, однако мы воспользуемся проводным соединением через UART – USB. На нашем сайте уже опубликована подробная статья об этом методе подключения: /arduinoplus/podkluchenie-raspberry-arduino/index, поэтому инструкцию по подключению и настройке мы здесь опустим.
Итак, первым делом мы задействуем готовый погодный сервис, из которого будем получать всю необходимую метеоинформацию по любым городам мира. Этот сервис — Open Weather Map. Официальный сайт — https://openweathermap.org/. Чтобы работать с функциями сервиса, потребуется специальный ключ, который выдаётся после регистрации аккаунта. Создав учётную запись, переходите по ссылке «API keys».
Перед вами тот самый ключ, который мы будем применять в нашем коде.
Если ключ уже получен — отлично, можно переходить ко второму пункту.
Сейчас я постараюсь кратко, но содержательно описать весь процесс обмена данными между Raspberry Pi и сервисом OpenWeatherMap. Raspberry отправляет HTTP-запрос на сервер, который в ответ возвращает JSON-ответ (также возможен XML- и HTML-ответ, однако их мы рассматривать не будем — подробности доступны на официальном сайте).
Выглядит это как-то так:
Запрос на сайт - http://api.openweathermap.org/data/2.5/weather?q=Rostov-na-Donu&appid=b6fc7c896bb814a45e2ea7e2b44758f3&units=metric
Ответ:
{"coord":{"lon":39.71,"lat":47.24},
"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01n"}],"base":"stations",
"main":{"temp":23.24,"pressure":1017.01,"humidity":32,"temp_min":23.24,"temp_max":23.24,"sea_level":1025.15,"grnd_level":1017.01},
"wind":{"speed":3.42,"deg":43.0036},
"clouds":{"all":0},"dt":1534535099,
"sys":{"message":0.0033,"country":"RU","sunrise":1534472468,"sunset":1534523288},"id":501175,"name":"Rostov-na-Donu","cod":200}
На первый взгляд символов много и они кажутся непонятными, но в действительности каждый элемент запроса и ответа содержит полезную информацию.
Запрос включает несколько изменяемых элементов.
Город: weather?q=Rostov-na-Donu
Ключ: appid=b6fc7c896bb814a45e2ea7e2b44758f3
Измерение: units=metric
В запросе допускается менять «город» — тогда ответ будет содержать данные для указанного города. «Ключ» подставляется тот, что вы получили в пункте 1. «Измерение» определяет единицы измерения в полученном ответе. Доступны 3 варианта: standard; metric; imperial.
Различия между единицами измерения описаны по ссылке: https://openweathermap.org/weather-data.
JSON-ответ содержит ряд пунктов и подпунктов, каждый из которых можно отдельно вызвать в программе. Существуют также дополнительные пункты, которые могут отсутствовать в ответе, если соответствующее явление не наблюдается. Когда в городе нет дождя или снега, информация о них в ответе не появится. Полный перечень возможных полей в ответе доступен на официальном сайте.
Программа для Raspberry pi
Raspberry будет обращаться к погодному сервису по запросу от Arduino и затем отправлять полученные метеоданные по городу обратно на Arduino. С логической точки зрения в этом нет необходимости, ведь Raspberry справится с задачей самостоятельно, но подобный опыт взаимодействия двух плат окажется весьма полезным на практике.
Для начала организуем вывод полученных от сервера данных на экран, чтобы убедиться в корректной работе нашего кода и сервиса.
import requests
from pprint import pprint
city = input('Enter your city : ')
url = 'http://api.openweathermap.org/data/2.5/weather?q={}&appid='ваш ключ'&units=metric'.format(city)
res = requests.get(url)
data = res.json()
temp = data['main']['temp']
wind_speed = data['wind']['speed']
latitude = data['coord']['lat']
longitude = data['coord']['lon']
description = data['weather'][0]['description']
print('Temperature : {} degree celcius'.format(temp))
print('Wind Speed : {} m/s'.format(wind_speed))
print('Latitude : {}'.format(latitude))
print('Longitude : {}'.format(longitude))
print('Description : {}'.format(description))
После ввода города „Moscow“ программа выдала точные метеоданные по Москве на ближайшие 3 часа. Убедитесь, что название города введено правильно — на карте Open Weather Map можно посмотреть корректные названия. К примеру, мой родной город «Ростов-на-Дону» (англ. «Rostov-on-Don») на этом сервисе называется «Rostov-na-Donu», что поначалу может вызвать некоторое замешательство.
Если всё заработало, доработайте код таким образом, чтобы из serial-порта поступал текст — в нашем случае названия городов. Raspberry подключается к погодному сервису, запрашивает актуальные метеоданные по присланному городу и затем отправляет часть этих данных обратно.
mport serial
import requests
from pprint import pprint
ser=serial.Serial("/dev/ttyACM0",9600)
def send():
ser.write(temp.encode())
ser.write(wind_speed.encode())
ser.write(latitude.encode())
ser.write(longitude.encode())
ser.write(humidity.encode())
while True:
city=ser.readline().decode()
if(city!=""):
print(city)
url = 'http://api.openweathermap.org/data/2.5/weather?q={}&appid='ваш ключ'f3&units=metric'.format(city)
res = requests.get(url)
data = res.json()
temp = data['main']['temp']
temp = str(int(temp))+"\r\n"
wind_speed = data['wind']['speed']
wind_speed = str(int(wind_speed))+"\r\n"
latitude = data['coord']['lat']
latitude = str(int(latitude))+"\r\n"
longitude = data['coord']['lon']
longitude = str(int(longitude))+"\r\n"
humidity = data['main']['humidity']
humidity = str(int(humidity))+"\r\n"
send()
Программа на Arduino UNO
Эта плата будет выполнять функции, описанные в одной из предыдущих статей (управление на расстоянии): отправлять команды и принимать информацию, которую затем можно будет просмотреть на ЖК-дисплее.
При помощи потенциометра выбираем один из трёх городов (Ростов-на-Дону, Санкт-Петербург, Москва) и нажимаем кнопку для отправки данных о выбранном городе на Raspberry. При успешном получении данных на экране появится список погодных условий указанного города на ближайшее время, а также его географические координаты.
Полную электронную схему подключения модулей к Arduino вы найдёте в предыдущей статье «Управление умным домом на расстоянии (продолжение)» — она не отличается от нашей.
Ниже приведён код, который можно скачать или скопировать:
int data = 0; // присылаемые данные
int reading = 8; // пин, который считывает значение с кнопки `
int val = 0; // значение с реостата
boolean butwheather=0; // счетчик запуска меню
boolean but = 0; // значение кнопки до нажатия
boolean counter = 0; // еще один счетчик...
boolean currentButton = 0; // значение кнопки после нажатия
boolean zum = 0; // счетчик звука зуммера
String data1; // приходимые данные
String data2;
String data3;
String data4;
String data5;
#define BUZZER_PIN 7 // пин для зуммера
#include <LiquidCrystal.h> // библиотека для жк-дисплея
LiquidCrystal lcd(12, 11, 5, 4, 3, 2); // выбираем 6 пинов для подключения
void setup()
{
lcd.begin(16, 2);
Serial.begin(9600);
Serial.setTimeout(40); // При вызове parseInt() задержка ожидания последующего символа по умолчанию 1 секунда.
// Функцией setTimeout() можно уменьшить эту задержку. Например на 40 миллисекунд, setTimeout(40);.
pinMode(reading,INPUT); //
}
void signaling() // функция для звучания зуммера
{
tone(BUZZER_PIN, 50, 300);
delay(300);
zum+=1;
}
boolean Button( boolean got ) // функция, которая принимает предыдущее значение нажатия кнопки и отправляет текущие значение
{
boolean current = digitalRead(reading);
if( !got == current)
{
delay(10);
current = digitalRead(reading);
}
return(current);
}
void menu () // функция выводы на жк-дислей меню и отправки команды
{
while(butwheather==0)
{
if ( val>=0 && val<=270)
{
zum = 0;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Rostov-on-Don");
while (val>=0 && val<=270)
{
currentButton = Button(but);
if (currentButton == 0 && but ==0)
{
Serial.println("Rostov-na-Donu");
butwheather=1;
break;
}
but = currentButton;
val = analogRead(A0);
}
if (zum==0)
{
signaling();
}
}
if ( val>=271 && val<=700)
{
zum = 0;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Saint Petersburg");
while (val>=271 && val<=700)
{
currentButton = Button(but);
if (currentButton == 0 && but ==0)
{
Serial.println("Saint Petersburg");
butwheather=1;
break;
}
but = currentButton;
val = analogRead(A0);
}
if (zum==0)
{
signaling();
}
}
if ( val>=701 && val<=1023)
{
zum = 0;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Moscow");
while (val>=701 && val<=1023)
{
currentButton = Button(but);
if (currentButton == 0 && but ==0)
{
Serial.println("Moscow");
butwheather=1;
break;
}
but = currentButton;
val = analogRead(A0);
}
if (zum==0)
{
signaling();
}
}
}
}
void information() // функция приема данных с платы
{
data1=Serial.parseInt();
data2=Serial.parseInt();
data3=Serial.parseInt();
data4=Serial.parseInt();
data5=Serial.parseInt();
}
void readout() // функция вывода данных на жк-дисплей с помощью значения с потенциометра
{
val = analogRead(A0);
while(counter==0)
{
if(val>=0 && val<=204)
{
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Temp:");
lcd.print(data1);
lcd.print("C");
lcd.setCursor(0,1);
lcd.print("Wind:");
lcd.print(data2);
lcd.print("m/s");
while(val>=0 && val<=204)
val = analogRead(A0);
}
if(val>=205 && val<=409)
{
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Wind:");
lcd.print(data2);
lcd.print("m/s");
lcd.setCursor(0,1);
lcd.print("Lat:");
lcd.print(data3);
lcd.print("`");
while(val>=205 && val<=409)
val = analogRead(A0);
}
if(val>=410 && val<=614)
{
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Lat:");
lcd.print(data3);
lcd.print("`");
lcd.setCursor(0,1);
lcd.print("Lon:");
lcd.print(data4);
lcd.print("`");
while(val>=410 && val<=614)
val = analogRead(A0);
}
if(val>=615 && val<=819)
{
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Lon:");
lcd.print(data4);
lcd.print("`");
lcd.setCursor(0,1);
lcd.print("Humdi:");
lcd.print(data5);
lcd.print("%");
while(val>=615 && val<=819)
val = analogRead(A0);
}
if(val>=820 && val<=1023)
{
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Exit?");
while(val>=820 && val<=1023)
{
val = analogRead(A0);
currentButton = Button(but);
if (currentButton == 0 && but ==0)
{
counter+=1;
break;
}
but = currentButton;
}
}
}
counter=0;
}
void loop()
{
val = analogRead(A0); // читаем значение реостата и выбираем режим в зависимости от положения ручки
menu(); // выбираем один из трех городов
if (Serial.available()> 0) // ждем, пока данных придут
{
information(); // считываем данные
readout(); // показываем присланные данные
butwheather=0; // онулируем счетчик
delay(400); // немного ждем перед следующим запросом
}
}
Если всё прошло успешно, можете поэкспериментировать с погодным сервисом и расширить автоматизацию — подробности о возможностях погодного виджета легко найти в сети. На этом первая часть завершена, а скоро мы увидимся во второй части, где речь пойдёт о протоколе MQTT и его практической реализации. Развивайтесь вместе с вашим умным домом!