Как известно, что ни делай под Raspberry Pi, получится либо медиаплеер, либо метеостанция. Постигла эта участь и меня — когда после очередного ливня датчик метеостанции залило, настала пора или купить новую, или сделать самому.
От метеостанции нужны были следующие функции:
— отображение температуры
— отображение графика атмосферного давления
— прогноз дождя
— радиосинхронизация времени по DCF77 (если уж на метеостанции есть часы, они должны показывать точное время)
Из покупных, по сочетанию «дизайн-цена-функции» не понравилась ни одна — либо нет одного, либо другого, либо слишком громоздко и дорого. В итоге решено было задействовать Raspberry Pi с TFT-экраном, и сделать те функции, которые нужны.
Получилось примерно так:
Подробности реализации и готовый проект под катом.
Получение данных погоды
Первое, с чем нужно было определиться — это получение данных погоды. Здесь есть 2 варианта, либо использовать свои датчики, либо брать погоду из интернета. Первое интереснее, но есть несколько «но»:
— Сделать датчик «абы как» несложно, но сделать датчик хорошо, чтобы он например год работал от одного комплекта батареек, задача уже не столь тривиальная. Есть конечно сейчас и малопотребляющие процы, и радиомодули, но потратить на это месяц было бы лень.
— Нужно заморачиваться с корпусом, влагозащитой и прочими мелочами (3д-принтера у меня нет).
— Балкон выходит на восток, так что погрешность измерения температуры в первую половину дня была бы слишком большой.
Альтернативным вариантом была покупка готового метеомодуля с датчиками для Raspberry Pi.
Увы, поиск показал что в продаже есть всего 2 варианта:
— Raspberry Pi sense hat
Плата имеет «на борту» термометр, барометр, датчик влажности, гироскоп и акселерометр — но чем думали разработчики, ставя такой «экран» 8х8 светодиодов, непонятно — ничего внятного на нем вывести нельзя. Желаю разработчикам этого модуля всю жизнь UI под матрицу 8х8 писать :)
— Raspberry Pi weather hat
Ничего кроме светодиодов здесь нет вообще.
В общем, как ни странно, но нормального готового шилда для метеостанции с хорошим экраном и хорошим набором датчиков так никто и не сделал. Краудсорсеры, ау — рыночная ниша пропадает :)
В итоге, не паримся и делаем по-простому — берем погоду из Интернета и выводим на обычный TFT. Как подсказал гугл, самое развитое API сейчас у https://openweathermap.org/api, его и будем использовать.
Регистрация
Для получения данных погоды с openweathermap нужен ключ, его можно получить бесплатно, зарегистрировавшись на вышеупомянутом сайте. Ключ выглядит примерно так «dadef5765xxxxxxxxxxxxxx6dc8». Большинство функций доступны бесплатно, платные API нам не понадобятся. Для бесплатных функций есть ограничение на 60 запросов в минуту, нам этого достаточно.
Чтение данных
Чтение данных весьма просто благодаря библиотеке pyowm.
Получение погоды на данный момент (Python):
import pyowm
owm = pyowm.OWM(apiKey)
observation = owm.weather_at_coords(lat, lon)
w = observation.get_weather()
dtRef = w.get_reference_time(timeformat='date')
t = w.get_temperature('celsius')
temperatureVal = int(t['temp'])
p = w.get_pressure()
pVal = int(p['press'])
Получение прогноза погоды:
fc = owm.three_hours_forecast_at_coords(lat, lon)
rain = fc.will_have_rain()
snow = fc.will_have_snow()
rains = fc.when_rain()
На выходе получаем массив данных со списком дождей и их интенсивностью. Несмотря на название функции three_hours_forecast_at_coords, дожди прописаны на 2-3 дня вперед.
Можно использовать GET-запросы напрямую, например так. Это может пригодиться, например при портировании кода на MicroPython под ESP.
Получение координат пользователя
Как можно видеть выше, для получения данных нужны широта и долгота. Получение координат также весьма просто, и делается в 3 строчки кода:
import geocoder
g = geocoder.ip('me')
lat = g.latlng[0]
lon = g.latlng[1]
UI
Собственно, самая сложная часть. На Raspberry Pi используется TFT-дисплей от Adafruit, поддерживающий систему команд ILI9340. Библиотеки под него найти несложно, однако отлаживать код на Raspberry Pi не очень удобно. В итоге было принято решение написать высокоуровневый набор контролов, которых нужно было всего 3 — изображения, текст и линии. При запуске на Raspberry Pi контрол будет рисовать себя на TFT, при запуске на десктопе будет использоваться встроенная в Python библиотека tkinter. В итоге, код будет работать везде — и на Raspberry Pi, и на Windows, и на OSX.
Код одного контрола выглядит примерно так:
class UIImage:
def __init__(self, image = None, x = 0, y = 0, cId = 0):
self.x = x
self.y = y
self.width = 0
self.height = 0
self.cId = cId
self.tkID = None
self.tftImage = None
self.tkImage = None
self.useTk = utils.isRaspberryPi() is False
if image is not None:
self.setImage(image)
def setImage(self, image):
width, height = image.size
if self.useTk:
self.tkImage = ImageTk.PhotoImage(image)
self.tftImage = image
self.width = width
self.height = height
def draw(self, canvas = None, tft = None):
if tft != None:
tft.draw_img(self.tftImage, self.x, self.y, self.width, self.height)
elif canvas != None and self.tkImage != None:
if self.tkID == None or len(canvas.find_withtag(self.tkID)) == 0:
self.tkID = canvas.create_image(self.x, self.y, image=self.tkImage , anchor=tkinter.NW)
else:
canvas.itemconfigure(self.tkID, image=self.tkImage)
Класс «FakeTFT» создает обычное окно программы:
class FakeTFT:
def __init__(self):
self.tkRoot = tkinter.Tk()
self.tkRoot.geometry("500x300")
self.screenFrame = tkinter.Frame(self.tkRoot, width=330, height=250, bg="lightgray")
self.screenFrame.place(x=250 - 330 / 2, y=5)
self.tkScreenCanvas = tkinter.Canvas(self.tkRoot, bg = 'white', width = 320, height = 240, highlightthickness=0)
self.tkScreenCanvas.focus_set()
self.tkScreenCanvas.place(x=250 - 320 / 2, y=10)
self.controls = []
def draw(self):
for c in self.controls:
c.draw(self.tkScreenCanvas)
Класс «LCDTFT» использует «настоящий» дисплей (фрагмент кода):
class LCDTFT:
def __init__(self, spidev, dc_pin, rst_pin=0, led_pin=0, spi_speed=16000000):
# CE is 0 or 1 for RPI, but is actual CE pin for virtGPIO
# RST pin. 0 means soft reset (but reset pin still needs holding high (3V)
# LED pin, may be tied to 3V (abt 14mA) or used on a 3V logic pin (abt 7mA)
# and this object needs to be told the GPIO and SPIDEV objects to talk to
global GPIO
self.SPI = spidev
self.SPI.open(0, 0)
self.SPI.max_speed_hz = spi_speed
self.RST = rst_pin
self.DC = dc_pin
self.LED = led_pin
self.controls = []
def draw(self):
for c in self.controls:
c.draw(tft = self)
При инициализации автоматически выбирается нужный дисплей, в зависимости от того, где запускается программа:
def lcdInit():
if utils.isRaspberryPi():
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
DC = 25
LED = 18
RST = 0
return LCDTFT(spidev.SpiDev(), DC, RST, LED)
else:
return FakeTFT()
Все это позволяет полностью абстрагироваться от «железа», и писать код типа такого:
self.labelPressure = libTFT.UILabel("Pressure", 18,126, textColor=self.tft.BLACK, backColor=self.tft.WHITE, fontS = 7)
self.tft.controls.append(self.labelPressure)
self.labelRain = libTFT.UILabel("Rain", 270,126, textColor=self.tft.BLUE, backColor=self.tft.WHITE, fontS = 7)
self.tft.controls.append(self.labelRain)
Собственно UI выглядит так:
На экране отображаются текущая температура, график атмосферного давление за сегодняшний день, также в случае прогноза дождя, его время отмечается на графике вертикальной синей чертой (на данной картинке дождей нет). Также выводятся время последнего обновления данных и IP-адрес, если понадобится подключиться к устройству.
Желающие ознакомиться с исходником подробнее, могут посмотреть его на guthub.
Установка на Raspberry Pi
Для тех, кто не хочет заморачиваться описанным выше, короткая инструкция по установке под спойлером.
git clone github.com/dmitryelj/RPi-Weather-Station.git
— Если не установлен Python3, ставим:
sudo apt-get install python3
— Ставим дополнительные библиотеки (они нужны для работы с дисплеем):
sudo pip3 install numpy pillow spidev
— Добавляем в автозапуск (sudo nano /etc/rc.local)
python3 /home/pi/Documents/RPi-Weather-Station/weather.py &
— Пробуем запустить
python3 weather.py
Если все работает, то перезагружаемся (sudo reboot) и пользуемся.
В плане добавить еще что-нибудь полезное, например отображение карты облачности, API на openweathermap для этого есть.
Продолжение следует.
Автор: DmitrySpb79