IT-инфраструктура для бизнеса и творчества
Разработка
Selectel

Солнечно, без осадков: как создать Telegram-бота для самого точного прогноза погоды

Вокруг много сервисов, предоставляющих информацию о погоде. Но не всем можно верить. Старший технический писатель Selectel Николай Рубанов создал бота, который делится информацией с погодных датчиков пилотов гражданской авиации. Там от этих показателей зависят жизни людей, поэтому прогнозы максимально точные.

Частые велопрогулки стали причиной моего интереса к прогнозам погоды. Сначала хотел собрать небольшую DIY погодную станцию с датчиками и получать с нее данные. Но не стал «изобретать велосипед». В качестве источника проверенных данных выбрал погодную информацию, которую используют в гражданской авиации, а именно METAR или METeorological Aerodrome Report и TAF или Terminal Aerodrome Forecast.

Эта информация в круглосуточном режиме транслируется голосом на каждом современном аэродроме в виде ATIS (Automatic Terminal Information Service) и VOLMET (от франц. vol — полет и météo — погода). Первый предоставляет информацию о фактической погоде на аэродроме, а второй — прогноз на ближайшие 24-30 часов, причем не только на аэродроме трансляции, но и на других.

Каждый раз брать с собой радиосканер или трансивер на соответствующий диапазон неудобно. Решил создать бота в Telegram, который по нажатию кнопки выдает такой же прогноз. Выделять под это отдельный сервер нецелесообразно, как и гонять запросы на домашнюю Raspberry.

Поэтому в качестве бэкэнда решил использовать сервис Облачные функции Selectel. Количество запросов будет ничтожно малое, поэтому такой сервис обойдется фактически бесплатно. По моим подсчетам выйдет 22 рубля за 100 тыс. запросов.

Подготовка бэкэнда

Создание функции

В панели управления my.selectel.ru открываем представление Облачная платформа и создаем новый проект:

После того как проект создан, переходим в раздел Функции:

Нажимаем кнопку Создать функцию и задаем ей нужное имя:

После нажатия Создать функцию у нас появится представление созданной функции:

Перед тем, как приступить к созданию кода на Python, потребуется создать бота в Telegram. Расписывать, как это делается, я не буду — детальная инструкция есть в нашей базе знаний. Главное для нас — токен созданного бота.

Готовим код

В качестве источника надежных данных я выбрал Национальное управление океанических и атмосферных исследований США (англ. National Oceanic and Atmospheric Administration, NOAA). Это научное агентство в реальном времени обновляет данные на своем сервере в формате TXT.

Ссылка для получения данных METAR:

https://tgftp.nws.noaa.gov/data/observations/metar/stations/{код аэропорта по ICAO}.TXT

Обратите внимание на регистр.

В моем случае ближайшим аэропортом является Внуково, его код по ICAO — UUWW. Переход на сформированный URL выдаст следующее:

2020/08/10 11:30 UUWW 101130Z 31004MPS 9999 SCT048 24/13 Q1014 R01/000070 NOSIG

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

  • [UUWW] — Внуково, город Москва (Россия — RU);
  • [101130Z] — 10-й день месяца, 11 часов 30 минут по Гринвичу;
  • [31004MPS] — направление ветра 310 градусов, скорость 4 м/с;
  • [9999] — горизонтальная видимость 10 км и более;
  • [SCT048] — рассеянные/разбросанные облака на высоте 4800 футов (~1584м);
  • [24/13] — температура 24°C, точка росы 13°C;
  • [Q1014] — давление (QNH) 1014 гектопаскалей (750 мм рт. ст.);
  • [R01/000070] — коэффициент сцепления на полосе 01 — 0,70;
  • [NOSIG] — без существенных изменений.

Приступаем к написанию программного кода. Для начала потребуется импортировать функции request и pytaf:

from urllib import request import pytaf

Указать переменные и подготовить функцию декодирования:

URL_METAR = "https://tgftp.nws.noaa.gov/data/observations/metar/stations/UUWW.TXT" URL_TAF = "https://tgftp.nws.noaa.gov/data/forecasts/taf/stations/UUWW.TXT"
def parse_data(code): code = code.split('\n')[1] return pytaf.Decoder(pytaf.TAF(code)).decode_taf()

Перейдем к TAF.

https://tgftp.nws.noaa.gov/data/forecasts/taf/stations/{код аэропорта по ICAO}.TXT

Регистр также важен.

Как и в предыдущем примере, посмотрим прогноз в аэропорту Внуково:

2020/08/10 12:21 TAF UUWW 101050Z 1012/1112 28003G10MPS 9999 SCT030 TX25/1012Z TN15/1103Z TEMPO 1012/1020 -TSRA BKN020CB BECMG 1020/1021 FEW007 BKN016 TEMPO 1021/1106 -SHRA BKN020CB PROB40 TEMPO 1021/1106 -TSRA BKN020CB BECMG 1101/1103 34006G13MPS

Особенно обратим внимание на строки TEMPO и BECMG. TEMPO означает то, что фактическая погода в указанный промежуток будет периодически меняться. BECMG — погода постепенно изменится в указанный промежуток времени.

То есть строка:

TEMPO 1012/1020 -TSRA BKN020CB

Будет означать:

  • [1012/1020] — в промежуток с 12 до 20 часов (по Гринвичу);
  • [-TSRA] — гроза (TS = thunderstorm) с дождем (RA = rain) небольшой интенсивности (знак минус);
  • [BKN020CB] — значительная (BKN = broken), кучево-дождевая (CB = cumulonimbus) облачность на высоте 2000 футов (610 метров) над уровнем моря.

Терминов, означающих погодные явления, достаточно много, и запомнить их сложновато. Код для запроса TAF пишется аналогичным образом.

Заливаем код в облако

Чтобы не тратить зря время, возьмем шаблон телеграм-бота из нашего репозитория cloud-telegram-bot. Там есть предварительно подготовленный requirements.txt и setup.py с корректной структурой директорий.

Поскольку в коде мы будем обращаться к модулю pytaf, то его версию следует сразу добавить в requirements.txt.

pytaf~=1.2.1

Переходим к редактированию bot/tele_bot.py. Убираем все лишнее и дописываем наш код.

import os from urllib import request import telebot import pytaf TOKEN = os.environ.get('TOKEN') URL_METAR = "https://tgftp.nws.noaa.gov/data/observations/metar/stations/UUWW.TXT" URL_TAF = "https://tgftp.nws.noaa.gov/data/forecasts/taf/stations/UUWW.TXT" bot = telebot.TeleBot(token=TOKEN, threaded=False) keyboard = telebot.types.ReplyKeyboardMarkup(resize_keyboard=True) keyboard.row('/start', '/get_metar', '/get_taf') def start(message): msg = "Привет. Это бот для получения авиационного прогноза погоды " \ "с серверов NOAA. Бот настроен на аэропорт Внуково (UUWW)." bot.send_message(message.chat.id, msg, reply_markup=keyboard) def parse_data(code): code = code.split('\n')[1] return pytaf.Decoder(pytaf.TAF(code)).decode_taf() def get_metar(message): # Fetch info from server. code = request.urlopen(URL_METAR).read().decode('utf-8') # Send formatted answer. bot.send_message(message.chat.id, parse_data(code), reply_markup=keyboard) def get_taf(message): # Fetch info from server. code = request.urlopen(URL_TAF).read().decode('utf-8') # Send formatted answer. bot.send_message(message.chat.id, parse_data(code), reply_markup=keyboard) def route_command(command, message): """ Commands router. """ if command == '/start': return start(message) elif command == '/get_metar': return get_metar(message) elif command == '/get_taf': return get_taf(message) def main(**kwargs): """ Serverless environment entry point. """ print(f'Received: "{kwargs}"') message = telebot.types.Update.de_json(kwargs) message = message.message or message.edited_message if message and message.text and message.text[0] == '/': print(f'Echo on "{message.text}"') route_command(message.text.lower(), message)

Упаковываем всю директорию в ZIP-архив и переходим в панель управления к созданной функции.

Нажимаем Редактировать и загружаем архив с кодом.

Заполняем относительный путь в файлу tele_bot (расширение .py можно не указывать) и эндпойнт-функцию (в приведенном примере это main).

В разделе Переменные окружения пишем переменную TOKEN и присваиваем ей токен нужного телеграм-бота.

Нажимаем Сохранить и развернуть, после чего переходим в раздел Триггеры.

Ставим переключатель HTTP-запрос, чтобы сделать запрос публичным.

У нас появился URL для публичного вызова функции. Осталось лишь настроить вебхук. Найдите нашего бота @SelectelServerless_bot в Telegram и зарегистрируйте своего бота командой:

/setwebhook <you bot token> <public URL of your function>

Результат

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

Разумеется, код можно дорабатывать, однако даже в текущем состоянии его достаточно, чтобы узнать самую точную погоду и прогноз из проверенного источника.

Пока мы готовим новые материалы в блог Selectel, ищите полную версию кода в нашем репозитории на GitHub и делитесь впечатлениями о разработке.

{ "author_name": "Selectel", "author_type": "editor", "tags": ["\u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0430","\u0431\u043e\u0442","selecteldiy","selectel"], "comments": 0, "likes": 7, "favorites": 10, "is_advertisement": false, "subsite_label": "dev", "id": 164799, "is_wide": true, "is_ugc": false, "date": "Wed, 07 Oct 2020 14:52:21 +0300", "is_special": false }
(function () { let cdnUrl = `https://specialsf378ef5-a.akamaihd.net/SelectelBranding/images/` let previousArticleNumber = null let currentArticleNumber = 0 let platform = 'Desktop' let articles = [ // { // name: 'camera', // url: `${cdnUrl}CameraCat`, // text: 'умную камеру для\u00A0наблюдения за\u00A0котиками', // link: '1', // num: 3 // }, { name: 'chill', url: `${cdnUrl}ChillCat`, text: 'трекер, который подскажет, когда пора отдохнуть', link: 'https://vc.ru/promo/288561-eye-tracker', num: 1 }, { name: 'cloud', url: `${cdnUrl}CloudCat`, text: 'котика: даёшь ему «пять», а\u00A0он делает бэкап в облако', link: 'https://vc.ru/dev/294799-maneki-neko', num: 2 } ] let buttonCycle = document.querySelector('.button--cycle') let buttonChoose = document.querySelector('.button--choose') let buttonMobile = document.querySelector('.button--mobile') let textField = document.querySelector('.selectel-footer-subtitle') let imageAgent = document.querySelector('.image--agent') let banner = document.querySelector('.selectel-footer') buttonCycle.addEventListener('click', cycleClick) buttonChoose.addEventListener('click', () => sendEvent(`Promo ${articles[currentArticleNumber].num} Left`, 'Click')) buttonMobile.addEventListener('click', () => sendEvent(`Promo ${articles[currentArticleNumber].num} Left`, 'Click')) let media = window.matchMedia("(max-width: 570px)") media.addEventListener('change', matchMedia) function matchMedia() { if (media.matches) { platform = 'Mobile' } else { platform = 'Desktop' } update() } matchMedia() function cycleClick(event) { sendEvent(`Promo ${articles[currentArticleNumber].num} Right`, 'Click') if (event) { event.preventDefault() event.stopPropagation() } window.open('https://vc.ru/tag/selectelDIY', '_blank') //cycle(event) } function cycle(event) { // incrementArticleNumber() textField.innerHTML = generatedText() imageAgent.src = articles[currentArticleNumber].url + platform + '.svg?3' imageAgent.setAttribute("class", "") imageAgent.classList.add('image--agent', articles[currentArticleNumber].name) banner.href = articles[currentArticleNumber].link } function update() { banner.href = articles[currentArticleNumber].link imageAgent.src = articles[currentArticleNumber].url + platform + '.svg' textField.innerHTML = generatedText() } function incrementArticleNumber() { previousArticleNumber = currentArticleNumber if (currentArticleNumber >= articles.length - 1) { currentArticleNumber = 0 } else { currentArticleNumber++ } } const sendEvent = (label, action = 'Click') => { const value = `SelectelDIY — loc: Footer — ${label} — ${action}`; if (window.dataLayer !== undefined) { window.dataLayer.push({ event: 'data_event', data_description: value, }); } }; function generatedText() { let defaultText if (platform === 'Desktop') { defaultText = `Мы тут собрали %text%. Хотите научим?` } else { defaultText = `Мы тут собрали %text%.` } return defaultText.replace('%text%', articles[currentArticleNumber].text) } function getRandom(min, max) { min = Math.ceil(min) max = Math.floor(max) return Math.floor(Math.random() * (max - min + 1)) + min } (function create() { currentArticleNumber = getRandom(0, articles.length - 1) cycle() let page = document.querySelector('.page--entry') if (page) { function insertAfter() { let parents = page.querySelectorAll('[data-id="7"]') let referenceNode = parents[0] referenceNode.parentNode.insertBefore(banner, referenceNode.nextSibling); loaded() } setTimeout(() => insertAfter(), 0) } }()) function loaded() { banner.classList.add('loaded') } loadImages([ `${cdnUrl}CameraCatDesktop.svg`, `${cdnUrl}ChillCatDesktop.svg`, `${cdnUrl}CloudCatDesktop.svg`, `${cdnUrl}CameraCatMobile.svg`, `${cdnUrl}ChillCatMobile.svg`, `${cdnUrl}CloudCatMobile.svg?3`, ]) function loadImages(urls) { return Promise.all(urls.map(function (url) { return new Promise(function (resolve) { var img = document.createElement('img'); img.onload = resolve; img.onerror = resolve; img.src = url; }); })); } }())
0
0 комментариев
Популярные
По порядку
Читать все 0 комментариев
Прокрастинация: как перестать откладывать на потом и 4 способа которые мне помогли

Каждый раз, когда я начинаю думать о сложной или неприятной задаче, я чувствую внутреннее сопротивление. Например, когда нужно практиковать английский, писать сценарий или работать над сложным проектом.

«Российский рынок акций был и остаётся одним из самых привлекательных в мире»

Виталий Исаков, директор по инвестициям УК «Открытие» («Открытие Инвестиции»).

Недвижимость в разных странах: что там с арендой, риелторами и ипотекой

В разных статьях о релокейте мы касались темы недвижимости: наши герои рассказывали, за какую сумму арендуют квартиры и с чем столкнулись при аренде. Решили собрать в один материал информацию про несколько стран: Бразилию, Великобританию, Польшу и Словакию. Читайте и пишите в комментарии, про недвижимость в каких еще странах хотели бы узнать.

«Spotify: История продукта». Как мы разработали алгоритмы музыкальных рекомендаций

Из онлайн-библиотеки — в сервис персонализированных рекомендаций.

Таргет как волшебная палочка

Восхваление возможностей таргета в Инстаграм приводит к тому, что многие владельцы бизнеса уверены в его абсолютной способности быстро приводить к ним новых клиентов.

Объявлены победители Finlanding
ИИ помог мошенникам украсть $35 млн, Facebook согласился сотрудничать с регуляторами и другие новости из мира МО
Флаги Китая и США развеваются у здания компании в Шанхае, Китай, 14 апреля 2021 года. REUTERS / Aly Song
Будущее наступит во вторник на OneRetailConf
Принцип «Всё смотрю». Часть 1

Дисклеймер. Я подметил принцип, которым, как мне кажется, пользуются многие руководители проектов, продуктов, компаний. Решил записать своё наблюдение в виде наглого утверждения — мол, вот так вот всё устроено! — чтобы дать уважаемым читателям материал для обсуждения и узнать их мнение.

«Типографика в будущем»: дизайнер описывает, как дизайн и шрифты показывают футуризм в научной фантастике Статьи редакции

Разбирает «Валли», «Бегущего по лезвию» и другие фильмы, показывает, как создать свой шрифт «из будущего».

Дефицит цифровых кадров в России и их подготовка

Весь мир переходит в цифровую среду. Пока в ежегодном глобальном рейтинге конкурентоспособности Россия занимает 43-е место, но задерживаться на нем не намерена. Для этого правительство запустило программу «Цифровая экономика РФ», которая будет поддерживать цифровую экономику в стране и подготовку необходимых кадров.

null