☰ Оглавление

Создание домашнего погодного виджета с помощью Arduino и Raspberry

С вами снова Артем Лужецкий и мы продолжаем создавать свой собственный "Умный дом". На очереди - объединение устройств в сеть, часть 1.

Для создания умного дома, необходимо объединить все умные устройства в единую сеть. Для этого Arduino UNO не подойдет в своей обычной комплектации. Часто данная плата работает как периферийное устройство, которое связывает датчики или устройства вместе и уже отсылает данные в “мозг”. На роль данного “мозга” отлично подойдет Raspberry pi. Подробно рассказывать об этой плате я не буду. Она обладает всеми нужными функциями маломощного сервера, что для iot устройств отлично подходит.

Можно несколькими путями связать два устройства, но мы воспользуемся проводным подключением через UART – USB. На нашем сайте уже есть подробная статья, которая расскажет вам о данном способе подключения: https://arduinoplus.ru/podkluchenie-raspberry-arduino/ , и поэтому опустим инструкцию по подключению и применению.

Используем Open Weather Map

Итак, первое, что мы делаем — это используем уже готовый погодный сервис, на котором мы будем брать всю нужную нам информацию о всех городах мира. Сервис - 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}

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

В запросе есть несколько изменяемых элементов.

  1. Город: weather?q=Rostov-na-Donu
  2. Ключ: appid=b6fc7c896bb814a45e2ea7e2b44758f3
  3. Измерение: 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 и его реализации. Становитесь умнее вместе с вашим умным домом!

Ардуино+