Лампа, Показывающая Прогноз Погоды

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

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

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



Лампа, показывающая прогноз погоды

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



Демонстрация работы

Лампа может показывать прогноз погоды на 14 часов вперед. Технически внутри светильника 14 горизонтальных уровней (светодиодные ленты RGB по 20 светодиодов в каждой).

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

Каждый следующий уровень плюс 1 час.

Горизонтальное движение на каждом уровне — это скорость ветра.

Также присутствует эффект дождя — плавно мигающие части всех цветов в начале и конце каждого уровня.

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

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

Вы можете увидеть больше видео работы лампы без крышки И видео, демонстрирующее отображение осадков (края полосок светятся всеми цветами).



Требования и проектирование

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

    Нам понадобится получить прогноз погоды из Интернета.

  • Автономия.

    Лампа не должна зависеть от других устройств

  • Возможность отображения различных данных о погоде (температура, дождь, гроза, ветер)
Немного подумав, я решил использовать прямоугольную лампу с 12 горизонтальными уровнями по 20 светодиодов в каждом.

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

Позже количество уровней было увеличено до 14, поскольку остались дополнительные светодиоды.



Железо

Прежде всего нужно определиться с платформой: микроконтроллер реального времени типа Arduino или полноценный компьютер типа Raspberry Pi с операционной системой на борту.

Каждый из этих вариантов имеет свои плюсы и минусы.

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

Еще меня немного смутил вопрос об одновременной работе wifi шилда (при запросе прогноза на сервер) и работе светодиодной ленты.

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

Было решено использовать Raspberry Pi с USB-ключом EdiMax WiFi. Этот ключ использовался мной на других проектах и зарекомендовал себя достаточно хорошо.

Далее нужно найти подходящие источники света.

Если грубо прикинуть, то минимум нам понадобится около 240 светодиодов (12 уровней по 20 светодиодов в каждом).

Вариант, при котором их придется спаивать все по одному, даже не рассматривался.

У нас нет особого выбора: либо светодиодные панели, либо светодиодная лента.

Панели идеальны для тех, кто хочет небольшой светильник без различных искривлений на поверхности.

Я остановился на светодиодной ленте, так как хотел сделать светильник среднего размера.

Так, была заказана светодиодная лента RGB длиной 2 метра с плотностью пикселей 144 штуки на метр.

Эта лента имеет адресные светодиоды (тип светодиодной ленты с цифровой адресацией), что означает, что мы можем генерировать сигнал таким образом, что каждый светодиод получает свои данные и отображает тот цвет, который должен.

За это отвечает микросхема WS2811, которая находится в каждом светодиоде на ленте.

Так как всего в ленте 288 светодиодов, было решено использовать их по максимуму и сделать 14 уровней по 20 светодиодов в каждом) Следует отметить, что Raspberry Pi выдает на свои порты GPIO всего 3,3 Вольта, а для ленты нам нужен управляющий сигнал напряжением 5 Вольт. Таким образом, нам понадобится микросхема преобразователя напряжения.

Я использовал 74AHCT125. Схема подключения (взята из Учебное пособие по Адафруту ):

Лампа, показывающая прогноз погоды

В соседнем магазине мы присмотрели и приобрели лампу-донор размерами 60 на 20 см.

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

Поскольку в итоге у нас получилось 280 RGB-светодиодов + Raspberry Pi, мы заказали блок на 5 Вольт 10 Ампер в достаточно компактном корпусе.

Пришло время поместить все это в лампу.

С блоком питания и Raspberry Pi всё было понятно.

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

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

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

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

После этого я решил распечатать рамки на 3D-принтере.

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

В крайнем случае, можно сделать все вручную – вырезав из дерева или картона (после этого многократно склеив слои).

Печать на рамке: Первые тесты ленты на рамах:

Лампа, показывающая прогноз погоды

В общей сложности мы получили все необходимые компоненты и пришло время сборки.

Светодиодную ленту разрезали на кусочки по 20 светодиодов.

Детали были спаяны между собой и приклеены к рамке.

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

Блок питания, вся проводка и Raspberry Pi разместились в пространстве между рамками и корпусом.

Процесс сборки лампы:

Лампа, показывающая прогноз погоды

Результат ( Более высокое разрешение ):

Лампа, показывающая прогноз погоды



Служба прогноза погоды

Для работы лампы необходим сервис прогноза погоды.

Для этого существует большое количество бесплатных и условно-бесплатных сервисов.

Например openweathermap.org или прогноз.

io .

Все они имеют свои ограничения или какие-то особенности.

Одним из моих главных критериев была возможность получить почасовой прогноз погоды на ближайшие 12+ часов.

К сожалению, openweathermap может возвращать прогнозы погоды только на 3 часа в бесплатном режиме.

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

При этом не совсем бесплатный прогноз.

io порадовал меня своей скоростью и детализацией данных.

Это позволило мне получить в одном запросе все необходимые мне данные (температуру, скорость ветра и осадки) на 12 часов вперед и даже больше.

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

В итоге я выбрал этот ресурс, если честно, просто полагаясь на свою интуицию.

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

io. JSON-файл, который вернул сервис прогноза.

io, весил около 40 килобайт для моего местоположения, что мне показалось чрезмерным.

Мне нужно было всего 3 значения на каждый из 12 часов.

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

Учитывая, что нам нужно всего 3 значения (температура, скорость ветра и осадки) для каждого часа, всего необходимо передать 168 байт (14 * 3 * int size = 4).

Мой сервис также позволит вам установить координаты местоположения и минимальные и максимальные значения температуры для заданной области, чтобы избежать хранения этой информации на стороне Raspberry Pi. Я написал Java-сервлет для работы с прогнозом.

io, который умеет кэшировать значения между запросами и в случае слишком частых запросов возвращает значение из кэша (чтобы не превышать лимит в 1000 бесплатных запросов в день) .

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

io из свойств системы, поэтому, если нам нужно изменить местоположение, мы можем сделать это вне веб-приложения.

Код сервлета

  
  
  
  
  
  
   

public class ForecastServlet extends HttpServlet { private static final String API_KEY = System.getenv("AL_API_KEY"); private static final int REQUEST_PERIOD = 5 * 60 * 1000; private static final int START_HOUR = 0; private static final int END_HOUR = 14; private static final int DATA_SIZE = 3 * 4 * (END_HOUR - START_HOUR); private static final int TEMP_MULTIPLY = 100; private static final int WIND_MULTIPLY = 100; private static final int PRECIP_MULTIPLY = 1000; private final String mutex = ""; private final ByteArrayOutputStream data = new ByteArrayOutputStream(DATA_SIZE); private long lastRequestTime; @Override protected void doGet(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException { synchronized (mutex) { if ((System.currentTimeMillis() - lastRequestTime) > REQUEST_PERIOD) { try { updateForecast(); } catch (IOException e) { e.printStackTrace(); } } response.setHeader("Content-Type", "application/octet-stream"); response.setHeader("Content-Length", "" + data.size()); response.getOutputStream().

write(data.toByteArray()); response.getOutputStream().

flush(); response.getOutputStream().

close(); lastRequestTime = System.currentTimeMillis(); } } private void updateForecast() throws IOException { int maxTemp = Integer.valueOf(System.getenv("AL_MAX_TEMP")) * TEMP_MULTIPLY; int minTemp = Integer.valueOf(System.getenv("AL_MIN_TEMP")) * TEMP_MULTIPLY; BufferedReader reader = null; try { String urlTemplate = " https://api.forecast.io/forecast/%s/%s,%s "; URL url = new URL(String.format(urlTemplate, API_KEY, System.getenv("AL_LAT"), System.getenv("AL_LON"))); InputStreamReader streamReader = new InputStreamReader(url.openStream()); reader = new BufferedReader(streamReader); JSONParser jsonParser = new JSONParser(); try { JSONObject jsonObject = (JSONObject) jsonParser.parse(reader); JSONArray hourly = (JSONArray) ((JSONObject) jsonObject.get("hourly")).

get("data"); for (int i = START_HOUR; i < END_HOUR; i++) { JSONObject hour = (JSONObject) hourly.get(i); int temperature = safeIntFromJson(hour, "apparentTemperature", TEMP_MULTIPLY); if (temperature > maxTemp) { temperature = maxTemp; } else if (temperature < minTemp) { temperature = minTemp; } else { float tempFloat = (float) 100 / (maxTemp - minTemp) * (temperature - minTemp); temperature = (int) (tempFloat * TEMP_MULTIPLY); } int wind = safeIntFromJson(hour, "windSpeed", WIND_MULTIPLY); int precip = safeIntFromJson(hour, "precipIntensity", PRECIP_MULTIPLY); data.write(intToBytes(temperature)); data.write(intToBytes(wind)); data.write(intToBytes(precip)); } } catch (ParseException e) { e.printStackTrace(); } } finally { try { if (reader != null) reader.close(); } catch (IOException ioe) { ioe.printStackTrace(); } } } private byte[] intToBytes(int value) { return ByteBuffer.allocate(4).

putInt(value).

array(); } private int safeIntFromJson(final JSONObject data, final String dataKey, final int multiply) throws IOException { Object jsonAttrValue = data.get(dataKey); if (jsonAttrValue instanceof Long) { return (int) ((Long) jsonAttrValue * multiply); } else { return (int) ((Double) jsonAttrValue * multiply); } } }

Необходимо объяснить, что означают 5 свойств, значения которых мы запрашиваем во время выполнения: AL_API_KEY — Секретный ключ разработчика прогноза.

io AL_LAT, AL_LON — Координаты местности AL_MAX_TEMP, AL_MIN_TEMP — Минимальные и максимальные значения температуры для заданной области.

Это необходимо для того, чтобы не тратить зря некоторые области используемого цветового диапазона: скажем, в моем районе (Техас, США) — температура никогда не опускается ниже нуля, и мне бы хотелось, чтобы фиолетовый цвет (самый низкий в нашей палитре) представлял 0, а не -25, как можно было бы поставить для Москвы.

Таким образом, наш сервис не возвращает фактическую температуру — он возвращает одну сотую процента между AL_MIN_TEMP и AL_MAX_TEMP. Исходный код веб-приложения вместе с файлом сборки maven доступен в репозитории.

github.com/manusovich/aladdin-service Далее нам понадобится любой хостинг для нашего веб-приложения Java. Я воспользовался своим любимым геройку , но вы можете использовать любой другой.

Репозиторий уже содержит файл, необходимый для запуска приложения в среде Heroku, с именем Procfile. Итак, если мы используем героку, все, что нам нужно сделать, это:

  • Создать новое приложение
  • Определите 3 новых свойства системы
  • Свяжите его с нашим репозиторием Git.
  • Разверните приложение.

    Для этого вам нужно запустить Manual Deploy, и весь код будет автоматически скачан из репозитория github, скомпилирован и запущен.

Теперь наш сервлет можно выполнить, открыв ссылку в браузере.

https://aladdin-service.herokuapp.com/forecast .

Это вернет файл с прогнозом погоды (размером 168 байт) для заданной области (свойство приложения в Heroku)

Программное обеспечение на стороне лампы

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

Для управления светодиодом в ленте используется микросхема WS2811. После недолгих поисков я наткнулся на учебник от Adafruit — Learn.adafruit.com/neopixels-on-raspberry-pi где я нашел упоминание о библиотеке rpi_ws281x , который как раз и позволяет формировать сигнал для ленты на базе чипов WS281x. Я подключил библиотеку к своей репозиторий и добавил необходимый код в main.c (см.

ниже в разделе контроллер лампы), чтобы упростить разработку до минимума.

Мне следует сделать небольшое отступление и рассказать, как я обычно разрабатываю код для своих проектов на базе Raspberry Pi. Мне показалось, что редактировать код через ssh совсем не удобно.

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

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

Скрипт настроен на автозагрузку.

Это позволяет разрабатывать код удаленно и при этом ускоряет процесс проверки изменений на устройстве.

Но при этом загружает сеть Wi-Fi. Когда разработка программного обеспечения завершена, я увеличиваю период обновления — например, 60 минут — и оставляю его таким навсегда.

Алгоритм оказывается следующий:

  • Вытащить изменения в git
  • Если в репозитории есть изменения, то
    • Обновить код
    • Скомпилировать код
    • Если компиляция прошла успешно, то
      • Остановить работающее приложение
      • Запустить новое приложение


Настройка RaspberryPi

  • Прежде всего вам необходимо настроить Wi-Fi.
  • После этого нам нужно клонировать репозиторий в каталог /home/pi/rpi_ws281x (выполнить в каталоге /home/pi):

    git clone https://github.com/manusovich/rpi_ws281x

    Сценарий оболочки /home/pi/rpi_ws281x/forecast.sh необходимо добавить в автозагрузку /etc/rc.local:

    sudo sh /home/pi/rpi_ws281x/forecast.sh >> /home/pi/ws281.log &

Этот скрипт обновляет прогноз, запускает приложение, а также обновляет прогноз погоды в фоновом режиме каждые 10 минут и каждые 60 минут проверяет репозиторий проекта на наличие изменений.

Если там есть изменения, они берутся из репозитория, компилируются и запускаются.

Код сценария

#!/bin/bash echo "Read forecast" curl https://aladdin-service.herokuapp.com/forecast > /home/pi/rpi_ws281x/forecast echo "Kill old instance." pkill test echo "Run new instance." exec /home/pi/rpi_ws281x/test & echo "Start pooling for changes" C=0 while true; do C=$((C+1)) # once per 10 minutes if [ $((C%60)) -eq 0 ] then echo "Update forecast. " curl https://aladdin-service.herokuapp.com/forecast > /home/pi/rpi_ws281x/forecast fi # once per one hour if [ $((C%360)) -eq 0 ] then echo "Check repository. " cd /home/pi/rpi_ws281x git fetch > build_log.txt 2>&1 if [ -s build_log.txt ] then echo "Application code has been changed. Getting changes." cd /home/pi/rpi_ws281x git pull echo "Bulding application." scons echo "Kill old application." pkill test echo "Launch new application." exec /home/pi/rpi_ws281x/test & echo "Done" else echo "No changes in the repository ($N)" fi fi sleep 10s done

Следует уточнить некоторые моменты:

  1. Абсолютные пути — этот скрипт будет запускаться из автозапуска и нам нужно указать все пути.

    Таким образом получается, что на Raspberry Pi наш репозиторий должен быть клонирован в каталог /home/pi/rpi_ws281x. Если у вас другой путь, вам необходимо обновить этот сценарий оболочки.

  2. Этот сценарий необходимо запускать от имени администратора, поскольку код управления лентой использует прямой доступ к памяти и должен запускаться от имени администратора.



Контроллер лампы

Теперь рассмотрим код управления светодиодами на светодиодной ленте.

Этот код находится в файле main.c и представляет собой бесконечный цикл и набор процедур по изменению цвета светодиодов.

Основной метод программы содержит инициализацию библиотеки rpi_ws281x для работы со светодиодной лентой и запускает бесконечный цикл отрисовки состояний: Основной код метода

int main(int argc, char *argv[]) { int frames_per_second = 30; int ret = 0; setup_handlers(); if (ws2811_init(&ledstring)) { return -1; } long c = 0; update_forecast(); matrix_render_forecast(); while (1) { matrix_fade(); matrix_render_wind(); matrix_render_precip(c); matrix_render(); if (ws2811_render(&ledstring)) { ret = -1; break; } usleep((useconds_t) (1000000 / frames_per_second)); c++; if (c % (frames_per_second * 60 * 5) == 0) { // each 5 minutes update forecast update_forecast(); } } ws2811_fini(&ledstring); return ret; }

Метод update_forecast считывает текущий прогноз погоды из файла /home/pi/rpi_ws281x/forecast. Метод Matrix_render_forecast заполняет матрицу текущими значениями прогноза погоды.

При этом мы используем палитру из 23 цветов, взятую с сайта.

paletton.com :

ws2811_led_t dotcolors[] = { 0x882D61, 0x6F256F, 0x582A72, 0x4B2D73, 0x403075, 0x343477, 0x2E4272, 0x29506D, 0x226666, 0x277553, 0x2D882D, 0x609732, 0x7B9F35, 0x91A437, 0xAAAA39, 0xAAA039, 0xAA9739, 0xAA8E39, 0xAA8439, 0xAA7939, 0xAA6C39, 0xAA5939, 0xAA3939 };

Метод Matrix_fade устраняет любые отклонения цвета от прогнозируемой температуры.

Метод matrix_render_wind рисует возбуждение, которое перемещается по горизонтали взад и вперед со скоростью, которая в раз равна скорости ветра *.

Метод matrix_render_precip отображает осадки по краям уровней.

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

Итак, мы делаем это только 15 раз в секунду.

Весь рендеринг идет в матрицу XRGB[ШИРИНА][ВЫСОТА].

Нам нужна структура XRGB для хранения чисел с плавающей запятой вместо целых чисел для цветов.

Это позволяет нам повысить плавность переходов и напрямую конвертировать в RGB, мы делаем это в методе matrix_render. При запуске программа выводит на консоль текущие значения прогноза (температура, ветер и осадки).

Следует отметить, что значение температуры является базисным пунктом (одна сотая процента).



pi@raspberrypi ~/rpi_ws281x $ sudo .

/test Temp: 5978, Wind: 953, Precip: 0 Temp: 5847, Wind: 1099, Precip: 0 Temp: 5744, Wind: 1157, Precip: 0 Temp: 5657, Wind: 1267, Precip: 0 Temp: 5612, Wind: 1249, Precip: 1 Temp: 5534, Wind: 1357, Precip: 1 Temp: 5548, Wind: 1359, Precip: 0 Temp: 5605, Wind: 1378, Precip: 0 Temp: 5617, Wind: 1319, Precip: 0 Temp: 5597, Wind: 1281, Precip: 0 Temp: 5644, Wind: 1246, Precip: 0 Temp: 5667, Wind: 1277, Precip: 0



Альтернативные режимы работы

Полученный продукт можно рассматривать как платформу для отображения чего угодно.

В вашем распоряжении около 300 независимых светодиодов и вам решать, что там можно отображать.

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



Оценка и заключение

Блок питания 5 вольт 10 ампер — 25 долларов.

2 метра RGB ленты (144 светодиода на метр) — 78$ Малиновый Пи — 30 долларов.

Эдимакс Wi-Fi USB — 8 долларов США.

3D-печать рамок для светодиодной ленты — 15$ за PLA пластик.

Донорская лампа — 35$.

В общей сложности общая стоимость продукта составила около 200 долларов США при домашнем изготовлении.

Надеюсь, эта статья будет вам полезна.

Если у вас есть вопросы, не стесняйтесь задавать их в комментариях.

Теги: #Сделай сам или Сделай сам #сделай сам #лёд #прогноз погоды #погода #малина

Вместе с данным постом часто просматривают: