Русский
Русский
English
Статистика
Реклама

Погодная станция

Беспроводная мини погодная станция с e-paper экраном на батарейках

25.02.2021 12:15:55 | Автор: admin
Приветствую всех читателей Habr! В своей сегодняшней статье хочу поделиться с вами своим новым устройством датчиком температуры, влажности и давления с функцией предсказания погоды. Датчик работает на микроконтроллерах nRF52. Данный проект это логическое продолжение этого проекта. В новом датчике используется дисплей на электронных чернилах размером 2.9 дюймов. В датчике установлен сенсор BME280, так же есть место под установку датчиков SI7021, HTU21D. Работает от батареек CR2450. Может передавать данные в системы Умного Дома, так же может работать в режиме без сети.




Для этого проекта был выбрана модель дисплея на электронных чернилах GDEH029A1 размером экрана 2.9 дюймов. Примерно через 3 месяца тестирования на смену этому дисплею производители выпустили на рынок новую модель GDEM029T94(V2 по версии Waveshare).
Старую модель стало трудно купить, поэтому пришлось добавлять поддержку нового дисплея в проект.



Характеристики дисплеев:
Разрешение: 296х128
Диапазон рабочих температур: 0 50 C
Потребление в рабочем режиме: 3мА
Потребление в режиме глубокого сна: 1мкА
Минимальное время обновления экрана: 0.3 сек.

Разрабатывал сразу несколько вариантов плат под несколько вариантов радио модулей nRF52 от разных производителей. Остановился на модулях MINEW MS50SFA2 (nRF52832) и EBYTE E73 2G4M08S1C (nRF52840), E73 2G4M08S1E (nRF52833).



Модуль MINEW MS50SFA2 имеет небольшие размеры, но не очень большое количество выведенных ножек. В моем проекте были задействованы все доступные ножки MS50SFA2. У модулей E73 ножек на много больше, поэтому впоследствии была разработана расширенная версия датчика. В раcширеной версии добавлен активный биззер, датчик освещенности MAX44009, заменены батарейки с CR2450 на ААА.

Схема датчика



Корпус датчика печатается на FDM 3D принтере, что бы добиться более или менее приличного вида, корпус после печати необходимо отшлифовать наждачной бумагой и отполировать. Так как у датчика есть светодиод, а в расширенной версии датчик освещенности, то в корпусе необходимо было сделать два сквозных отверстия, после сверления отверстий, они были залиты полимерной смолой для SLA 3D принтера и засвечены УФ лампой, после этого отполированы.





ПО датчика было сделано для работы в сети MySENSORS, это открытый проект домашней автоматизации. К слову, датчик будет нормально работать и без сети. На данный момент в проекте поддерживается работа с двумя моделями дисплеев GDEH029A1, GDEM029T94. Возможно позднее будет добавлена поддержка трехцветных дисплеев.

Опишу немного функционал устройства. Устройство при подаче питания осуществляет попытку поиска сети, если сеть не найдена, то устройство переходит в основной режим работы без работы в сети (не шлет данные), но периодически делает короткие запросы на поиск сети(~раз в час). Интервал опроса сенсора один раз в минуту, обновление экрана и отправка данных(если сеть доступна) происходит при изменении данных температуры на 0.5C, влажности на 1%, давления на 1 единицу, уровня освещенности на 1 люкс, изменения прогноза по погоде. Интервал опроса батарейки задается пользователем в интервале от 1 часа до 24 часов, по умолчанию опрос один раз в 6 часов.

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

Описание алгоритма расчета прогноза погоды (NXP Application Note 3914 | John B. Young)

При работе в радиосети датчик передает данные:
  • Температура,
  • Влажность,
  • Атмосферное давление,
  • Уровень освещенности,
  • Прогноз погоды,
  • Уровень сигнала,
  • Уровень заряда батарейки,
  • Причина перезагрузки






Для компиляции нужной версии ПО необходимо сконфигурировать файл MyConfig.h.
В файле задаются:
  • Язык вывода информации (RU,ENG)
  • Режим оптимизации питания при передаче данных
  • Подключение датчика освещенности
  • Подключение активного биззера
  • Скорость передачи данных
  • Версия подключенного дисплея


//#define EINK_V1#define DCPOWER#define LIGHTSENS#define BIZZER#define LANG_EN//#define MY_DEBUG//#define MY_PASSIVE_NODE//#define MY_NODE_ID 101#define MY_RADIO_NRF5_ESB#define MY_NRF5_ESB_MODE (NRF5_1MBPS)//#define MY_NRF5_ESB_MODE (NRF5_250KBPS)#define MY_RESET_REASON_TEXT#define SN "EFEKTA WeatherStation 290"#define SV "0.45"


Потребление датчика в режиме сна составляет в среднем 3мкА (на nRF52840 больше), в режиме считывания сенсора и обновления экрана 5мА(среднее), в режиме передачи данных 8мА(среднее), время передачи одного сообщения 10мc (идеальные условия).

Проект датчика в варианте с модулем MINEW MS50SFA2 может быть легко повторен. Из сложных моментов можно выделить пайку разъема под шлейф экрана. Как это сделать проще рекомендую посмотреть мое короткое видео по пайке разъема. Так же датчик можно приобрести готовым, тем самым поддержав мои открытые разработки.

Видео пайки разъема



Фото датчика

















Видео с демонстрацией работы датчика



GitHub проекта github.com/smartboxchannel/

В файле readme находится инструкция по установке и настройке среды для редактирования и компиляции ПО для датчика.

В завершении сделаю небольшой фото анонс проектов с которыми в скором времени поделюсь и о которых раскажу.

Новые проекты на стадии тестирования
Датчик качества воздуха на батарейках с e-paper экраном(аналогов не нашел)









Мини датчик влажности почвы с e-paper дисплеем(аналогов не нашел)










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

Всем, кто хочет делать устройства, начать строить автоматизацию своего дома, я предлагаю познакомиться с простым в освоении протоколом Mysensors телеграм-чат MySensors

А тем кто ищет достаточно взрослые решения для домашней автоматизации приглашаю в телеграм-чат Open Thread. (что такое Thread?)

Спасибо за внимание, всем добра!

Подробнее..

Мультисенсорный беспроводной датчик с E-Ink дисплеем

08.04.2021 12:19:54 | Автор: admin
Приветствую всех читателей Хабра и особенно читателей раздела DIY или Сделай сам! В сегодняшней статье я расскажу о своем очередном DIY проекте из серии устройств с дисплеями на электронных чернилах(e-ink). Устройство о котором пойдет речь это беспроводной мультисенсорный датчик с e-paper дисплеем 2.13 дюймов. На датчик можно установить сенсор температуры и влажности SHT21, HTU21D, SI7021, сенсор температуры влажности и давления BME280, сенсор атмосферного давления BMP280, сенсор освещенности MAX44009. Датчик работает от одной батарейки CR2450. Но ничего не мешает напаять на датчик держатель под батарейки CR2430 или CR2477.




Проект датчика с e-ink дисплеем размером 2.13 дюймов начинался достаточно давно. Первый прототип был сделан более года назад. Та первая версия работала на двух батарейках cr2450, имела стабилизированное питание. Со временем проект изменялся и оптимизировался, уменьшались размеры, менялись радио модули и сенсоры.


Плата окончательной версии датчика имеет размеры 72 mm * 31 mm, толщина текстолита 1.2mm. Размеры датчика в корпусе 76mm * 35mm * 12mm.
Устройство работает на микроконтроллере nRF52840, используется радио модуль MS88SF3 от компании MINEW. Модуль не имеет боковых падов для пайки, они расположены снизу радио модуля. Эта особенность немного напрягала, но глаза боятся, а руки делают. В итоге модуль достаточно просто напаивается феном (плату устройства, на которую устанавливался радио модуль я грел снизу).


Устройство имеет две модификации платы. В модификации А на датчике установлен сенсор BME280, светодиод, в модификации B добавлен датчик освещенности, датчики температуры и влажности SHT21, HTU21D, SI7021, добавлена возможность установки сенсора BMP280, добавлена защита от переполюсовки на транзисторе.




В модификации B место под пайку сенсоров BME280 и BMP280 сделал универсальным, BMP280 отлично устанавливается на место BME280. Это изменение я сделал уже в крайней ревизии второй версии датчика. Причиной стало резкое удорожание сенсоров BME (в среднем на 70% на последние два месяца). Теперь появилась возможность заменить функционал BME280 установив на плату BMP280 + SHT21 (серия BMP пока не поднимается в цене). Если кому-то известна причина такого роста цен на сенсоры BME, расскажите об этом в комментариях.


Корпус датчика был напечатан на FDM 3D принтере. После печати корпус дополнительно шлифовался и полировался. Для светодиода в модификации А и сенсора освещенности в модификации B в верхней части корпуса на внутренней стороне имеются углубления для последующего сверления отверстий. Просверленные отверстия я заливал полимерной смолой для SLA принтера.


Программа датчика написана под опенсорс проект MySensors. Датчик выводит на дисплей данные с сенсоров, заряд батарейки, уровень сигнала, прогноз изменения погоды на ближайшие часы. Рядом с данными с сенсоров так же выводится стрелками направление изменения значений. Было несколько вариантов дизайна интерфейса, варианты 2 и 3 доступны на моем гитхаб.






Есть возможность по нажатию кнопки инвертировать экран. На кнопку добавлен функционал простого меню с пунктами: инвертирование цвета, конфигурация устройства, презентация устройства, сброс датчика. В режиме конфигурации датчик в течение 20 секунд слушает эфир, в это время можно с контроллера УД отправить на датчик конфигурационные команды: изменение интервала отправки данных с сенсоров(от 1 минуты до 1 часа с шагом в 1 минуту), изменение интервала отправки данных о состояния батареи и уровне сигнала(от 1 часа до 24 часов). В режиме презентации устройство отправляет на контроллер УД данные о себе(название, версия прошивки) и о сенсорах, делает запрос о том в какой системе(метрическая или имперская) работает сеть. Так же отправляет свои конфигурационные настройки.

При работе в радиосети датчик передает данные:
  • Температура,
  • Влажность,
  • Атмосферное давление,
  • Уровень освещенности,
  • Прогноз погоды,
  • Уровень сигнала,
  • Уровень заряда батарейки,
  • Причина перезагрузки


Если сеть работает в метрической системе, то данные о температуре отправляются и выводятся на экран в Цельсиях, а данные об атмосферном давлении в миллиметрах ртутного столба(только при компиляции RU версии), иначе температура выводится в Фарингейтах, а атмосферное давление в Паскалях.

Перед компиляцией программы необходимо внести изменения в конфигурационный файл aConfig.h.
Какая языковая версия будет скомпелированна(RU или ENG):
#define LANG_RU

Вывод дебага в сериал:
#define MY_DEBUG

Мощность радиопередатчика:
#define MY_NRF5_ESB_PA_LEVEL (0x8UL)

Скорость передачи данных:
#define MY_NRF5_ESB_MODE (NRF5_1MBPS)


Потребление устройства в режиме сна 5 мкА, в режиме чтения сенсоров и обновления экрана 2-3 мА. В режиме передачи данных 8 мА, в режиме прослушивания 5мА. Время обновления экрана 300мс, время передачи одного сообщения с данными сенсоров 10мс, время передачи сообщения о заряде батареи с ожиданием эхо 100-300мс. Срок работы на одной батарейке CR2450 год и более(с конфигурацией опроса сенсоров раз в минуту и отправкой данных при изменении, опросе напряжения батарейки один раз в 6 часов и обязательной отправкой без сравнения).

Видео с демонстрацией работы датчика:

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


Фото датчика:















GitHub проекта github.com/smartboxchannel/

В файле readme находится инструкция по установке и настройке среды для редактирования и компиляции ПО для датчика.

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

Всем, кто хочет делать устройства, начать строить автоматизацию своего дома, я предлагаю познакомиться с простым в освоении протоколом Mysensors телеграм-чат MySensors

А тем кто ищет достаточно взрослые решения для домашней автоматизации приглашаю в телеграм-чат Open Thread. (что такое Thread?)

Спасибо за внимание, всем добра!

Подробнее..

Миниатюрный датчик качества воздуха на батарейке с e-ink экраном

21.06.2021 12:17:59 | Автор: admin
Приветствую всех читателей Habr! В своей сегодняшней статье, хочу рассказать вам о своем новом DIY беспроводном устройстве датчике качества воздуха. Помимо оценки качества воздуха, датчик может оценивать уровень освещенности в помещении, температуру, влажность и атмосферное давление, на основе данных атмосферного давления, устройство может предсказывать прогноз погоды. Это полностью открытый проект.



Внутреннее устройство


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

Используемые в проекте модели радиомодулей:

  • основной MINEW MS88SF3 (nRF52833, nRF52840)
  • дополнительные: MINEW MS50SFA1 (nRF52810, nRF52811), MINEW MS50SFA2 (nRF52832), EBYTE E73-2G4M08S1C (nRF52840) и EBYTE E73-2G4M08S1E (nRF52833)

Используемые в проекте сенсоры:

  • сенсор качества воздуха в помещении для измерения ЛОС SGP40
  • сенсор давления, температуры и влажности BME280
  • сенсор освещенности MAX44009

Позднее, из-за проблем с производством электронных компонентов и невероятном росте цен на многие компоненты, в проект были добавлены сенсоры BMP280 и SHTC3 которые по функционалу способны заменить сенсор BME280. По этой причине, были сделаны три дополнительные модификации плат, так же добавлена поддержка дополнительных радиомодулей, добавлена защита от переполюсовки, доработан дизайн плат.

Устройство может выводить данные на экране и передавать данные в системы Умного Дома, так же может работать в режиме без сети.

Для вывода информации использовался e-ink дисплей со сверхнизким потреблением и диагональю 2.13 дюймов компании WaveShare.



Характеристики дисплея:

  • Разрешение: 250x122
  • Диапазон рабочих температур: 0 50 C
  • Потребление в рабочем режиме: 3мА
  • Потребление в режиме глубокого сна: 1мкА
  • Минимальное время обновления экрана: 0.3 сек.

В ближайшее время в проект будет добавлена поддержка дисплея DES e-Ink 2.13 c рабочим температурным режимом -20C~60C (что такое DES).
..upd Пока статья писалась сделал драйвер, дисплей протестирован, в морозильнике работает :), из минусов разрешение 212х104, но зато морозов не боится, в общем рабочий вариант.


Основная версия PCB датчика:

Дополнительные версии:



Основным сенсором в данном проекте является сенсор качества воздуха в помещении SGP40. Можно сказать что это новинка на рынке от компании Sensorion c весьма неплохими характеристиками.


Сенсор измеряет общую концентрации летучих органических веществ (TVOC). В сравнении с предыдущим датчиком этой компании SGP30 потребление было значительно снижено, 48 мА при измерении у SGP30 и 2.6мА у SGP40. Правда предыдущий датчик мог отдавать уже готовые значения VOC и эквивалента СО2, в то время как новинка отдает сырые данные которые в дальнейшем надо обрабатывать на стороне МК при помощи поставляемой с датчиком библиотеки с алгоритмом расчета качества воздуха. Даташит на датчик SGP40.


Для работы в батарейном и низко потребляющем устройстве пришлось доработать библиотеку Adafruit_SGP40. Была добавлена работа с нагревателем сенсора, получение, сохранение, выгрузка текущих состояний алгоритма для быстрого старта датчика, например после замены батарейки, минуя режим обучения. Почему-то никто не озадачился этими моментами, найти готовую библиотеку поддерживающую полный функционал сенсора мне не удалось. Модифицированная библиотека находится на моем GitHub.

Схема устройства:



Передача датчиком данных с сенсоров в системы Умного Дома реализована на открытом проекте MySENSORS.




Функционал датчика


Устройство, при подаче питания, осуществляет попытку поиска сети, если сеть не найдена, то устройство переходит в основной режим работы без работы в сети (не шлет данные), но периодически делает короткие запросы на поиск сети(~раз в 2 часа). Интервал опроса сенсора SGP40 3 секунды, чтение остальных сенсоров, отправка данных, основное обновление экрана раз в 1 минуту. Обновление экрана и отправка данных(если сеть доступна) происходит при изменении данных уровня качества воздуха (TVOC) на 10 единиц, температуры на 0.5C, влажности на 5%, давления на 1 единицу, при изменении уровня освещенности на 10 люкс, при изменении прогноза по погоде. Интервал опроса батарейки задается пользователем в интервале от 1 часа до 24 часов, по умолчанию опрос один раз в 6 часов.
Так же есть дополнительная подпрограмма для обновления экрана и отправка данных при резком повышении уровня TVOC на 30 единиц, интервал проверки раз в 6 секунд.

При первом включении устройства, происходит цикл обучения алгоритма расчета качества воздуха, в моей реализации максимальное время обучения 12 часов. После обучения, датчик начинает сохранять в памяти МК текущие состояния алгоритма с четырехчасовым интервалом. При сбросе устройства, при возобновлении работы после выключения устройства, при замене батареек происходит проверка наличия записей состояний алгоритма в памяти, если они есть то эти данные выгружаются, и устройство минует период обучения. Для удаления сохраненных данных, необходимо нажать на кнопку меню на устройстве и выбрать соответствующий пункт меню. сброс устройства.

Доступный функционал кнопки меню:

  1. Инверсия экрана
  2. Отправка презентации
  3. Вход в режим конфигурации внешними командами по радио
  4. Поиск сети
  5. Сброс устройства

Так же, помимо кнопки меню, датчик может настраиваться внешними командами из интерфейса УД. Для этого необходимо активировать нужный пункт меню конфигурация датчика нажатием кнопки меню. После активации режима конфигурации, датчик перейдет в режим прослушивания на 20 секунд. В этот интервал необходимо отправить команду. Внешними командами можно настроить интервал проверки батарейки, изменить вывод информации на экран в инверсии, выбор режима работы: LP (чтение сенсора SGP40 раз в 3 секунды) или ULP (чтение сенсора SGP40 раз в 5 секунд).

Датчик умеет анализировать данные атмосферного давления и рассчитывать по ним прогноз погоды, выводить на экран данные о прогнозе погоды и отправлять эти значения в УД. Описание алгоритма расчета прогноза погоды (NXP Application Note 3914 | John B. Young)

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



Для компиляции нужной версии ПО необходимо сконфигурировать файл aConfig.h.

//#define MY_DEBUG#define LANG_RU // If this is not used the English localization will be displayed.#ifndef LANG_RU#define LANG_EN#endif#define SN "eON Air Quality Sensor"#define SV "0.99"#define MY_RADIO_NRF5_ESB#define MY_NRF5_ESB_PA_LEVEL (0x8UL)//#define MY_PASSIVE_NODE//#define MY_NODE_ID 151//#define MY_NRF5_ESB_MODE (NRF5_1MBPS)#define MY_NRF5_ESB_MODE (NRF5_250KBPS)#define ESPECIALLY#define SEND_RESET_REASON#define MY_RESET_REASON_TEXT

Потребление датчика в режиме сна составляет в среднем 33мкА (смотрите даташит на SGP40), в режиме считывания сенсоров и обновления экрана 4мА(среднее), в режиме передачи данных 8мА(среднее), время передачи одного сообщения 10мc (идеальные условия).
Датчик работает от батарейки CR2477 (950мА), среднее расчетное время работы устройства 1 год(зависит от конфигурации прошивки, установленных сенсорах на устройстве, больше сенсоров больше данных нужно будет отправлять, а передача по воздуху это основной потребитель), данных о реальном сроке работы пока нет, устройство пока работает 2 месяца.



Модели разработанного корпуса датчика я печатал на FDM 3D принтере, что бы добиться более или менее приличного вида, корпус после печати шлифовался и полировался. На задней крышке корпуса можно установить магниты.



GitHub проекта github.com/smartboxchannel/

В файле readme находится инструкция по установке и настройке среды для редактирования и компиляции ПО для датчика.

OPEN SOURCE HARDWARE CERTIFICATION
OSHWA UID: RU000004


В завершении, уже как обычно, сделаю небольшой фото анонс проектов с которыми в скором времени поделюсь и о которых расскажу (Датчики влажности почвы Zigbee, Уличный датчик температуры и влажности Zigbee Long Range, Датчик качества воздуха bme680 c e-ink3.7).

Новые проекты на стадии тестирования












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

Если вы как и я, хотите понять что такое Zigbee, попытаться сделать свои первые DIY Zigbee устройства, то приглашаю вас в чат для разработчиков zigbee девайсов/прошивок ZIGDEV

Всем, кто хочет делать устройства, начать строить автоматизацию своего дома, я предлагаю познакомиться с простым в освоении протоколом Mysensors телеграм-чат MySensors

А тех кто смотрит в будущее IOT приглашаю в телеграм-чат Open Thread (Matter, Project CHIP). (что такое Thread?, что такое Matter?)

Спасибо за внимание, всем добра!


Подробнее..

Метеостанция на максималках

05.03.2021 10:06:07 | Автор: admin

Про метеостанцию на Хабре писали не раз и не два, и наверное не с десяток раз. И вот настало моё время. Решил с вами поделиться своей.

Постановка задачи

Зачем вообще нужна метеостанция? Сегодня в мире хватает погодных сервисов, в том числе с локальными сводками погоды. Однако помимо контроля внешних параметров мне нужно было получить данные и с датчиков температуры и влажности внутри помещения. Помимо просто информационной составляющей, зная температуру в доме и снаружи, можно, например, управлять котлом или вентиляцией, и поддерживать комфортный микроклимат (погодозависимая автоматика). Кроме того, мне очень хотелось бы отслеживать тенденции и тренды погоды на длительных промежутках времени, например в год-два. То есть, данные нужно где-то хранить.

Из всего того делаем выводы:

  • Нужно хранилище данных (сервер)

  • Датчики будем использовать разные и в разных местах, поэтому проще сделать систему модульной (IoT)

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

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

Архитектура

Самым простым и популярным решением для метеостанции является Arduino, однако подружить с его домашней сетью - это дополнительные девайсы\шилды, лишние деньги и сложность, а значит - время. Поэтому из коробки проще использовать модуль уже со встроенным Wi-Fi, например ESP8266 (NodeMCU) с подключенными сенсорами. Это достаточно удобно, что один и тот же модуль можно использовать и дома, и за окном. При желании даже можно его использовать в качестве сервера.

Но почему бы не проставить в центр системы лучше что-то помощнее? Благо у меня пылится без дела Raspberry Pi первой ревизии (но и любая другая подойдёт). Внутренние датчики можно подключить, в принципе через GPIO и к малинке напрямую, но у меня роутер с малинкой в одной комнате установлен, а мониторить нужно другую. Если у вас такой проблемы нет - то можно от одной NodeMCU избавиться. Малинка будет получать данные от датчиков, сохранять их в базе данных и при необходимости отображать. Так же к GPIO Raspberry Pi можно подцепить LoRa - приёмник и получать данные от удалённых за пределами Wi-Fi сети датчиков (и вот они Arduino). Ну, и наконец, малинка будет отправлять данные в облако.

Итого, нам понадобится:

  • Raspberry PI

  • ESP8266 (2шт. + 1шт. опционально)

  • BME280 (2 шт.)

  • Часы реального времени DS1302 (опционально)

  • OLED-дисплей 128х64 на SH1106 (опционально)

  • Датчик дождя на компараторе LM373 (опционально)

  • УФ-датчик GY-VEML6070 (опционально)

  • Raspberry Pi Camera (опционально)

  • Arduino Nano (2 шт., опционально)

  • SX1278 (3 шт., опционально)

  • Магнитный компас с чипом QMC5883L/HMC5883L (опционально)

  • Датчик освещённости (светодиодный) с компаратором LM737 (опционально)

  • Датчики напряжения до 25V (опционально)

  • Датчики тока ACS712 (опционально)

Подключение SX1278 к Raspberry Pi

Для начала подключим к малинке радиомодуль.

Raspberry Pi

SX1278

3.3V

3.3V

GROUND

GROUND

GPIO10

MOSI

GPIO9

MISO

GPIO11

SCK

GPIO8

NSS/ENABLE

GPIO4

DIO0

GPIO22

RST

Соединяем пины Raspberry Pi и SX1278 как на картинке:

Замечание: для разных ревизий Raspberry Pi используются разное количество пинов, а значит и распиновки, смотрите документацию.

По поводу использования LoRa-модулей хочу обратить внимание на несколько моментов:

  • Перед подачей питания на модуль LoRa обязательно убедитесь, что к нему подключена антенна, иначе есть риск, что модуль сгорит!

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

Установка сервера

На Raspberry Pi загружаем Raspberry Pi OS Lite.

Далее устанавливаем статический адрес для нашей малинки:

sudo nano /etc/dhcpcd.conf

Добавляем\правим строки на наш желаемый IP и IP наш роутер

interfaceeth0# или wlan0 если малинка подключена по Wi-Fistaticip_address=192.168.0.4/24staticrouters=192.168.0.1staticdomain_name_servers=192.168.0.1. 8.8.8.8

Теперь включаем удалённый доступ через SSH, SPI (нужен для подключения LoRa), а так же Camera, если планируем её использовать.

sudo raspi-config

Включаем:

  • SSH (если собираемся подключаться по SSH, а не только через клавиатуру)

  • SPI (если собираемся использовать LoRa)

  • Camera (если собираемся использовать камеру)

Убеждаемся, что стоит автологин при загрузке:

Boot Options -> Console Autologin

Выходим из raspi-config, перезагружаем:

sudo shutdown -r now

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

Вся логика сервера написана на Python3, поэтому ставим его:

sudo apt-get install python3.7

Теперь осталось загрузить собственно мой проект H.O.M.E.:

cd ~git clone https://github.com/wwakabobik/home.git

В качестве веб-сервера я выбрал flask, на Хабре есть отличная серия статей, поэтому я не буду останавливаться на подробностях при работе с ним.

Копируем контент из папки с сервером:

mkdir web-servercp -r home/home_server/* /home/pi/web-server/

Устанавливаем все зависимости:

cd web-serversudo python3.7 -m pip install-rrequirements.txt

Создаём базу данных из шаблона:

cat db/schema.sql | sqlite3 flask_db

Собственно всё, теперь можем запустить сервер:

cd /home/pi/web-server && sudo python3.7 app.py

Но мы же хотим, чтобы сервер запускался при загрузке Raspberry Pi?

Тогда в конце /etc/rc.local, перед exit 0, добавляем вызов bash-скрипта:

/home/pi/flask_startup.sh &

Копируем этот скрипт на место:

cd ~cp ~/home/bash/flask_startup.sh .

Я так же считаю, что было бы неплохо иметь скрипт, который проверяет, жив ли ещё сервер, и если он мёртв, то перезапускает его. Копируем и его.

cp ~/home/bash/check_health.sh .

Добавляем в планировщик cron:

sudo crontab -e

задание:

1-59/5 * * * * /home/pi/check_health.sh

Немного о софте сервера

За запуск сервера отвечает app.py.

#!/usr/bin/env python3.7from multiprocessing.pool import ThreadPoolfrom flask import Flaskfrom db.db import init_appfrom lora_receiver import run_loraapp = Flask(__name__, template_folder='templates')  # firstly, start Flask# import all routesimport routes.apiimport routes.pagesimport routes.single_pageif __name__ == '__main__':    # Start LoRa receiver as subprocess    pool = ThreadPool(processes=1)    pool.apply_async(run_lora)    # Start Flask server    init_app(app)    app.run(debug=True, host='0.0.0.0', port='80')    # Teardown    pool.terminate()    pool.join()

Обратите внимание, что помимо запуска сервера, в отдельном процессе запускаем LoRa ресивер, который будет получать данные от датчиков и пересылать их на сервер.

В остальном архитектура типична для flaska: все возможные routes вынесены в отдельные файлы, все страницы хранятся в pages, а шаблоны в templates. Логика базы данных лежит в db, статичные файлы (картинки) в static, ну а в camera будем складывать картинки с камеры.

В итоге, текущие показания можно увидеть на dashboard страницах,

а графики и данные - на отдельных (графики рисует plotly).

Софт LoRa-ресивера

home_server/lora_receiver.py

from time import sleepimport requestsfrom SX127x.LoRa import *from SX127x.board_config import BOARDendpoint = "http://0.0.0.0:80/api/v1"class LoRaRcvCont(LoRa):    def __init__(self, verbose=False):        super(LoRaRcvCont, self).__init__(verbose)        self.set_mode(MODE.SLEEP)        self.set_dio_mapping([0] * 6)    def start(self):        self.reset_ptr_rx()        self.set_mode(MODE.RXCONT)        while True:            sleep(.5)            rssi_value = self.get_rssi_value()            status = self.get_modem_status()            sys.stdout.flush()    def on_rx_done(self):        self.clear_irq_flags(RxDone=1)        payload = self.read_payload(nocheck=True)        formatted_payload = bytes(payload).decode("utf-8", 'ignore')        status = self.send_to_home(formatted_payload)        if status:            sleep(1)  # we got the data, force sleep for a while to skip repeats        self.set_mode(MODE.SLEEP)        self.reset_ptr_rx()        self.set_mode(MODE.RXCONT)    def send_to_home(self, payload):        if str(payload[:2]) == '0,':            requests.post(url=f'{endpoint}/add_wind_data', json={'data': payload})        elif str(payload[:2]) == '1,':            requests.post(url=f'{endpoint}/add_power_data', json={'data': payload})        else:            print("Garbage collected, ignoring")  # debug            status = 1        return statusdef run_lora():    BOARD.setup()    lora = LoRaRcvCont(verbose=False)    lora.set_mode(MODE.STDBY)    # Medium Range  Defaults after init are 434.0MHz, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on 13 dBm    lora.set_pa_config(pa_select=1)    assert (lora.get_agc_auto_on() == 1)    try:        lora.start()    finally:        lora.set_mode(MODE.SLEEP)        BOARD.teardown()

В этом коде главное - это получение пакета в событии on_rx_done - если пакет получен, нужно его декодировать.

Проверяем в send_to_home что payload[:2] равен ожидаемому коду датчика (я для простоты использую значения 0, и 1,), то отсылаем на сервер и спим секунду, чтобы пропустить повторные пакеты.Если нет, продолжаем получать пакеты.

API


Ключевое, что делает 99% времени сервер - это простой. Но в остальные 1% он отдаёт и получает данные, и за это, помимо отображения страниц через веб-интерфейс отвечает API.

Именно через Flask REST API мы будем посылать или получать данные от сенсоров.

home_server/routes/api.py

@app.route('/api/v1/send_data')def send_weather_data():    return send_data()@app.route('/api/v1/add_weather_data', methods=['POST'])def store_weather_data():    if not request.json:        abort(400)    timestamp = str(datetime.now())    unix_timestamp = int(time())    data = request.json.get('data', "")    db_data = f'"{timestamp}", {unix_timestamp}, {data}'    store_weather_data(db_data)    return jsonify({'data': db_data}), 201

Данные пишутся в лог:

В моём случае, если мы получили данные от датчика (получили POST запрос с верным JSON), то мы их сохраняем в БД. Так же, если мы получили GET запрос на отправку данных (send_data), то данные отправляем данные на облако.

home_server/pages/weather_station/send_data.py

def send_data():    data = get_last_measurement_pack('0', '1')    image = take_photo()    wu_data = prepare_wu_format(data=data)    response = str(send_data_to_wu(wu_data))    response += str(send_data_to_pwsw(wu_data))    response += str(send_data_to_ow(data))    response += str(send_data_to_nardmon(data))    send_image_to_wu(image)    copyfile(image, f'{getcwd()}/camera/image.jpg')    return response

Ах да, забыл упомянуть камеру. Если мы подключили камеру к Raspberry Pi, то можем отправлять или сохранять изображения погоды за окном. Для этого есть отдельный метод:

home_server/pages/shared/tools.py

from picamera import PiCamera<...>camera = PiCamera()<...>def take_photo():  camera.resolution = (1280, 720) # lower resolution to fit in limits  camera.start_preview()  sleep(5)  image = f'{getcwd()}/camera/image_{int(time())}.jpg'  camera.capture(image)  camera.stop_preview()  return image  

Внешние датчики

полные скетчи можно найти в home/iot

Самым удобным и простым модулем для любительской метеостанции является модуль BME280, объединяющий в себе термометр, датчик влажности и давления. Подключаем его по I2C к ESP8266:

Прошивать будем через Arduino IDE (как добавить ESP8266 написано, например, в этой статье).

iot/esp8266/weatherstation_in/weatherstation_in.ino

#include <ESP8266WiFi.h>#include <ESP8266HTTPClient.h>#include <Wire.h>#include <SPI.h>#include <Adafruit_BME280.h>#include <Arduino_JSON.h>Adafruit_BME280 bme; // use I2C interfaceAdafruit_Sensor *bme_temp = bme.getTemperatureSensor();Adafruit_Sensor *bme_pressure = bme.getPressureSensor();Adafruit_Sensor *bme_humidity = bme.getHumiditySensor();// Датчик не сказать, чтобы очень точный, поэтому добавляем корректирующие значенияfloat correction_temperature = -0.5;float correction_pressure = 15;float correction_humidity = 10;// подключаем Wifivoid connect_to_WiFi(){ WiFi.mode(WIFI_STA); WiFi.begin(wifi_ssid, wifi_password); while (WiFi.status() != WL_CONNECTED) {   delay(500); } Serial.println("WiFi connected"); Serial.print("IP address: "); Serial.println(WiFi.localIP()); #endif}/* <> */// собираем данные с датчиковfloat get_temperature(){  sensors_event_t temp_event, pressure_event, humidity_event;  bme_temp->getEvent(&temp_event);  return temp_event.temperature + correction_temperature;}/* <> */// также точку росы можно вычислить до отправки на сервер, делаем это:float get_dew_point(){  float dew_point;  float temp = get_temperature();  float humi = get_humidity();  dew_point = (temp - (14.55 + 0.114 * temp) * (1 - (0.01 * humi)) - pow(((2.5 + 0.007 * temp) * (1 - (0.01 * humi))),3) - (15.9 + 0.117 * temp) * pow((1 - (0.01 * humi)), 14));  return dew_point;}/* <> */// Форматируем в строкуString get_csv_data(){  String ret_string = DEVICE_ID;  ret_string += delimiter + String(get_temperature());  ret_string += delimiter + String(get_humidity());  ret_string += delimiter + String(get_pressure());  ret_string += delimiter + String(get_dew_point());  return ret_string;}// Отправляем через HTTP, упаковав строку в JSON:void post_data(){  check_connection();  HTTPClient http;  //Declare object of class HTTPClient  String content = get_csv_data();  int http_code = 404;  int retries = 0;  while (http_code != 201)  {    http.begin(api_url); // connect to request destination    http.addHeader("Content-Type", "application/json");    // set content-type header    http_code = http.POST("{\"data\": \"" + content +"\"}");  // send the request    http.end();                        // close connection    retries++;    if (retries > max_retries)    {          Serial.println("Package lost!");      break;    }  }}// cобственно, повторяем это время от времени:void loop(){  post_data();  delay(cooldown);}

По умолчанию у меня стоит интервал в 5 минут, и я считаю, что DEVICE_ID = "0" внутренний датчик, а DEVICE_ID = "1" внешний.

Датчик дождя LM393+YL83Датчик дождя LM393+YL83

К внешнему датчику можно подключить так же датчики ультрафиолета (
GY-VEML6070) и датчик дождя (на компараторе LM393). YL-83 достаточно игрушечный вариант для реального измерения уровня осадков, по крайней мере без калибровки, но, на какое-то время сгодиться, потому что мне актуальность по уровню осадкам не сильно интересует. Ну, точнее интересует на уровне "на улице дождь" или "сухо". Так же, альтернативно, можно использовать аналоговый датчик ультрафиолета GY-8511, но тогда придётся выбирать между ним и датчиком дождя, так как аналоговый вход на NodeMCU только один. Датчик ультрафиолета можно использовать, например, для оценки эффективности солнечных панелей. Ну и просто показывает дни, когда лучше воспользоваться солнцезащитным кремом во время покоса газона.

Схема подключения к ESP8266 ниже:

Для этих датчиков соответственно добавим три функции:

iot/esp8266/weatherstation_out/weatherstation_out.ino

#include "Adafruit_VEML6070.h"Adafruit_VEML6070 uv = Adafruit_VEML6070();#define VEML6070_ADDR_L   (0x38) ///< Low addressRAIN_SENSOR_PIN = A0;/* <...> */#ifdef UV_ANALOG_SENSORvoid get_uv_level(){    int uv_level = averageAnalogRead(UV_PIN);    float uv_intensity = mapfloat(uv_level, 0.99, 2.8, 0.0, 15.0);    return uv_intensity;}#endif#ifdef UV_I2C_SENSORvoid get_uv_level(){return uv.readUV();}#endif#ifdef RAIN_SENSORvoid get_rain_level(){    int rain_level = averageAnalogRead(RAIN_SENSOR_PIN);    return rain_level;}#endif

NodeMCU удобно использовать, когда есть устойчивый Wi-Fi в зоне их действия. Конечно, ставить внешние датчики для погодной станции на крыльце - плохая идея, но оборудованная точка, отнесённая пару-тройку метров от дома - то, что нужно, а сигнала роутера в доме хватает за глаза.

Правила установки погодной станции
  • Датчики температуры и влажности воздуха обязательно устанавливаются над естественной поверхностью земли (трава, грунт). Асфальта, бетона, щебня, камня, металла не должно быть.

  • Датчики температуры и влажности устанавливаются на высоте 2 м над землёй в метеорологической будке: это небольшой деревянный или пластиковый ящик (размером приблизительно 40х40х40 см) с белыми, отражающими свет перфорированными или жалюзийными стенками, а также солнцеводозащитным козырьком (крышка будки должна быть герметичной и иметь наклон для стекания осадков с будки).

  • Датчик ветра - на высоте 10-12 м над землёй (именно над землёй, а не на крышах зданий; в исключительном случае разрешается размещение дачтика ветра на крыше одноэтажного дома, так чтобы датчик возвышался над верхним краем крыши не менее чем на 2-3 м, а над поверхностью земли на 10-12 м).

  • В худшем случае (при этом велик риск погрешностей, особенно в ночное время) датчик Т и влажности может быть установлен с теневой стороны здания, на высоте 2 м над землёй, на штанге длиной от стены как минимум метра 3 , над газоном (не над асфальтом!). Ни в коем случае не рекомендуется устанавливать их поблизости от сильно нагревающихся поверхностей, например крыш, стен и т.п.

  • Датчик атмосферного давления устанавливается в помещении вдали от окон и отопительных приборов. Атмосферное давление зависит от высоты над уровнем моря места, где производится измерение; поэтому требуется калибровка датчика давления перед его использованием. Для правильной установки прибора необходимо воспользоваться показаниями другого барометра или данными ближайшей метеостанции (с учётом разности высот, определённой по подробной топографической карте; 10 м подъёма соответствует уменьшению давления примерно на 1 мм рт.ст. или 1.3 гПа (мБ)).

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

Затем загрузим на него скетч, который будет отображать текущее время (ЧЧ:ММ) и в бегущей строке данные с метеостанции:

iot/informer/esp8266/informer/informer.ino

#include <ESP8266WiFi.h>#include <ESP8266HTTPClient.h>#include <Wire.h>#include <U8g2lib.h>#include <virtuabotixRTC.h>  // https://ampermarket.kz/files/rtc_virtualbotix.zip// RTCvirtuabotixRTC myRTC(14, 12, 13);// OLEDU8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0);u8g2_uint_t offset;            // current offset for the scrolling textu8g2_uint_t width;             // pixel width of the scrolling text (must be lesser than 128 unless U8G2_16BIT is definedconst int string_length = 80;  // maximum count of symbols in marqueechar text[string_length];      // text buffer to scroll// Wi-Ficonst char* wifi_ssid = "YOUR_SSID";const char* wifi_password = "YOUR_PASSWORD";// APIconst String ip_address = "YOUR_IP_OF_SERVER";const String port = "YOUR_PORT";const String api_endpoint = "/api/v1/add_weather_data";const String api_url = "http://" + ip_address + ":" + port + api_endpoint;const int max_retries = 5;  // number of retries to send packet// Timers and delaysconst long data_retrieve_delay = 300000;const int cycle_delay = 5;unsigned long last_measurement = 0;void setup(void) {    Serial.begin(9600);    init_OLED();    init_RTC();}/* Init functions */void init_OLED(){    u8g2.begin();      u8g2.setFont(u8g2_font_inb30_mr); // set the target font to calculate the pixel width    u8g2.setFontMode(0);              // enable transparent mode, which is faster}void init_RTC(){    // seconds, minutes, hours, day of the week, day of the month, month, year    // раскомментируйте при прошивке, заполнив текущую дату и время, затем снова закомментируйте и прошейте ещё раз    //myRTC.setDS1302Time(30, 03, 22, 5, 19, 2, 2021); // set RTC time    myRTC.updateTime(); // update of variables for time or accessing the individual elements.}""" <...> """  String get_data(){    check_connection();    #ifdef DEBUG    Serial.println("Obtaining data from server");    #endif    HTTPClient http;    //Declare object of class HTTPClient      int http_code = 404;    int retries = 0;    String payload = "Data retrieve error";    while (http_code != 200)    {        http.begin(api_url);                // connect to request destination        http_code = http.GET();             // send the request        String answer = http.getString();   // get response payload        http.end();                         // close connection            retries++;        if (retries > max_retries)        {            break;            #ifdef DEBUG            Serial.println("Couldn't get the data!");            #endif        }                        if (http_code == 200)        {            payload = answer;        }    }    return payload;}void loop(void) {    // Check that new data is needed to be retrieved from server    if (((millis() - last_measurement) > data_retrieve_delay) or last_measurement == 0)    {        String stext = get_data();        stext.toCharArray(text, string_length);        last_measurement = millis();        width = u8g2.getUTF8Width(text);    // calculate the pixel width of the text        offset = 0;    }    // Update RTC    myRTC.updateTime();     // Now update OLED    u8g2_uint_t x;    u8g2.firstPage();    do     {        // draw the scrolling text at current offset        x = offset;        u8g2.setFont(u8g2_font_inb16_mr);       // set the target font        do         {                                       // repeated drawing of the scrolling text...            u8g2.drawUTF8(x, 58, text);         // draw the scrolling text            x += width;                         // add the pixel width of the scrolling text        } while (x < u8g2.getDisplayWidth());   // draw again until the complete display is filled            u8g2.setFont(u8g2_font_inb30_mr);       // choose big font for clock        u8g2.setCursor(0, 30);                  // set position of clock        char buf[8];                            // init bufer to formatted string        sprintf_P(buf, PSTR("%02d:%02d"), myRTC.hours, myRTC.minutes); // format clock with leading zeros        u8g2.print(buf);                        // display clock    } while (u8g2.nextPage());      offset-=2;                       // scroll by two pixels    if ((u8g2_uint_t)offset < ((u8g2_uint_t) - width))    {          offset = 0;                  // start over again    }      delay(cycle_delay);              // do some small delay}

В итоге результат работы выглядит так:

Соответственно в Raspberry Pi:

home_server/routes/api.py

@app.route('/api/v1/get_weather_data', methods=['GET'])def store_wind_data():    return send_data_to_informer()  

pages/weather_station/send.data

def send_data_to_informer():    data_in = get_last_measurement_pack('weather_data', '0', '0')    data_out = get_last_measurement_pack('weather_data', '0', '1')    pressure = int((data_in['pressure']+data_out['pressure'])/2)    formatted_string = f"IN: T={data_in['temperature']}*C, " \                       f"H={data_in['humidity']}% | " \                       f"OUT: T={data_out['temperature']}*C, " \                       f"H={data_out['humidity']}%, " \                       f"DP={data_out['dew_point']}*C | " \                       f"P={pressure} mmhg"    return formatted_string

Радиодатчики

Там, где не дотянуться Wi-Fi, нужно использовать альтернативные варианты передачи данных. В моём случае - это использование LoRa-модулей (в связке, например, с Arduino Nano.

Таких устройств у меня два - это датчик скорости и направления ветра (компас). Пока не буду останавливаться на этом в текущей статье, если будет интерес - напишу отдельно. Второе устройство - это вольтметр и два амперметра, для контроля работы ветряка, зарядки АКБ и потребления.

SX1278

Arduino Nano

3.3V

3.3V

GROUND

GROUND

MOSI

D10

MISO

D2

SCK

D13

NSS/ENABLE

D12

DIO0

D11

RST

D9

И, код, соответственно:

iot/arduino/*_meter/*_meter.ino

// Required includes#include <SPI.h>#include <LoRa.h>// LoRA configconst int LORA_SEND_RETRIES = 5; // сколько раз посылать сообщениеconst int LORA_SEND_DELAY = 20;  // задержка между пакетамиconst int LORA_POWER = 20;       // мощность передатчика на максимум const int LORA_RETRIES = 12;     // сколько раз пытаться инициализировать модульconst int LORA_DELAY = 500;      // задержка между попыткой инициализации// Инициализируем модульvoid init_LoRa() {    bool success = false;    for (int i=0; i < LORA_RETRIES; i++)        {        if (LoRa.begin(433E6)) // используем 433Мгц        {            success = true;            break;        }        delay(LORA_DELAY);    }    if (!success)    {        #ifdef DEBUG        Serial.println("LoRa init failed.");        #endif        stop(4);    }        LoRa.setTxPower(LORA_POWER);  // aplify TX power    #ifdef DEBUG    Serial.println("LoRa started!");    #endif  }#endif// Посылаем пакет с данными строкойvoid LoRa_send(power_data data){    String packet = DEVICE_ID + "," + String(data.avg_voltage,2) + ",";    packet += String(data.avg_current,2) + "," + String(data.avg_power,2) + "," +String(data.avg_consumption,2);    for (int i=0; i < LORA_SEND_RETRIES; i++)    {        LoRa.beginPacket();  // just open packet        LoRa.print(packet);  // send whole data        LoRa.endPacket();    // end packet        delay(LORA_SEND_DELAY);    }  }

Достаточно просто, не правда ли?

Облачные сервисы

Изначально у меня не было цели делиться данными со сторонними сервисами, но во время отладки появилась мысль о том, что неплохо было бы иметь референсные данные с местных метеостанций, для контроля корректности работы своей. Первым, и с самой большой коллекцией примеров и API оказалась WeatherUnderground.

from wunderground_pws import WUndergroundAPI, unitsfrom secure_data import wu_api_key, wu_reference_station_id""" ... """wu_current = wu.current()""" ... """wu_humidity=wu_current['observations'][0]['humidity'],wu_pressure=int(int(wu_current['observations'][0]['metric_si']['pressure'])/1.33),wu_dew_point=wu_current['observations'][0]['metric_si']['dewpt'],wu_wind_speed=wu_current['observations'][0]['metric_si']['windSpeed'],wu_wind_gust=wu_current['observations'][0]['metric_si']['windGust'],wu_wind_direction=wu_current['observations'][0]['winddir'],wu_wind_heading=deg_to_heading(int(wu_current['observations'][0]['winddir']))

Однако, если была возможность быстро получать данные, то почему бы ими не поделиться, подумал я? Данные в WU передаются через GET-запрос, поэтому для удобства предварительно подготавливаем данные

def prepare_wu_format(data, timestamp=None):    payload = f"&dateutc={timestamp}" if timestamp else "&dateutc=now"    payload += "&action=updateraw"    payload += "&humidity=" + "{0:.2f}".format(data['humidity'])    payload += "&tempf=" + str(celsius_to_fahrenheit(data['temperature']))    payload += "&baromin=" + str(mmhg_to_baromin(data['pressure']))    payload += "&dewptf=" + str(celsius_to_fahrenheit(data['dew_point']))    payload += "&heatindex=" + str(celsius_to_fahrenheit(heat_index(temp=data['temperature'], hum=data['humidity'])))    payload += "&humidex=" + str(celsius_to_fahrenheit(humidex(t=data['temperature'], d=data['dew_point'])))    payload += "&precip=" + str(data['precip'])    payload += "&uv" + str(data['uv'])    return payload

затем отправляем:

import requests""" ... """def send_data_to_wu(data):    wu_url = "https://weatherstation.wunderground.com/weatherstation/updateweatherstation.php?"    wu_creds = "ID=" + wu_station_id + "&PASSWORD=" + wu_station_pwd    response = requests.get(f'{wu_url}{wu_creds}{data}')    return response.content

В результате мы должны увидеть данные на своей метеостанции.

Тут нужно сделать ремарку, что все сервисы, включая WU требуют регистрации и часто получения API ключей. Вся конфиденциальная информация хранится в secure_data.py

# Geo Datalatitude =longitude =altitude =cur_location =# WEATHER UNDERGROUND DATAwu_api_key =wu_station_id =wu_station_pwd =wu_reference_station_id =# OPEN WEATHER DATAow_api_key =ow_station_id =# PWSWEATHER DATApwsw_station_id =pwsw_api_key =# NARODMON DATAnarodmon_name = narodmon_owner = narodmon_mac = narodmon_api_key = 

Заполняем значения и продолжаем :)

WeatherUnderground увы, работает на платной основе и полученный мной ключ действует всего лишь год. Поэтому, поискав альтернативы, я наткнулся на PWS Weather.

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

def send_data_to_pwsw(data):    pwsw_url = "http://www.pwsweather.com/pwsupdate/pwsupdate.php?"    pwsw_creds = "ID=" + pwsw_station_id + "&PASSWORD=" + pwsw_api_key    response = requests.get(f'{pwsw_url}{pwsw_creds}{data}')    return response.content

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

Ещё можно послать данные на OpenWeatherMap. Персональной страницы тут нет, а в ответ на исторические данные мы получим "средние" по больнице данные, но, почему бы и нет? Энтузиастам надо помогать. Для передачи показаний у OWM для PWS (personal weather station) своё API, но что-то я не нашёл готовой обёртки для него на python, поэтому написал свою.

В отличие американских WeatherUnderground и PWS Weather, использующих имперскую систему, разраработчики OpenWeatherMap из Латвии и используют метрическую систему (Си), поэтому для передачи показаний для них не используем конвертеры, а пишем данные сразу из базы данных, которые мы собрали с датчиков.

from openweather_pws import Stationdef send_data_to_ow(data):    pws = Station(api_key=ow_api_key, station_id=ow_station_id)    response = pws.measurements.set(temperature=data['temperature'], humidity=data['humidity'],                                    dew_point=data['dew_point'], pressure=data['pressure'],                                    heat_index=fahrenheit_to_celsius(heat_index(temp=data['temperature'],                                                                                hum=data['humidity'])),                                    humidex=humidex(t=data['temperature'], d=data['dew_point']))    return response

И, наконец, как вариант импортозамещения самый функциональный, позволяющий хранить в том числе данные с закрытых датчиков (например те же данные о температуре внутри дома) - Narodmon.

На сервисе достаточно богатое API, позволяющее не только передавать показания с датчиков, но и собирать информацию по геолокации, управлять самим устройством удалённо, так и социальные фишки вроде "поставить лайк" или отправить сообщение. Особо здорово, что сервис шлёт email'ы в случае проблем (например датчик не вышел на связь час), так и настраиваемые "проблемы" вроде превышения лимита на конкретном датчике. Но, как и в случае OWM я не нашёл полного API-wrapper для python, и опять написал свой. Теперь, чтобы отправить данные с датчиков, зовём:

def send_data_to_nardmon(data):    nm = Narodmon(mac=narodmon_mac, name=narodmon_name, owner=narodmon_owner,                  lat=latitude, lon=longitude, alt=altitude)    temperature = nm.via_json.prepare_sensor_data(id_in="TEMPC", value=data['temperature'])    pressure = nm.via_json.prepare_sensor_data(id_in="MMHG", value=(data['pressure']))    humidity = nm.via_json.prepare_sensor_data(id_in="HUM", value=data['humidity'])    dew_point = nm.via_json.prepare_sensor_data(id_in="DEW", value=data['dew_point'])    sensors = [temperature, pressure, humidity, dew_point]    response = nm.via_json.send_short_data(sensors=sensors)    return response

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

Немаловажным будет сказать, что для отправки данных следует "дёргать" ручку /api/v1/send_data пустым GET-запросом. Чтобы не изобретать велосипед, просто поручим это делать cron. Добавляем ещё одну строку:

*/5 * * * * /usr/bin/wget -O - -q -t 1 http://0.0.0.0:80/api/v1/send_data

А как же камера?

Пока никак. Сделанные фото можно передавать на WeatherUnderground. Это сделать несложно через ftp

from ftplib import FTPdef send_image_to_wu(image):    session = FTP('webcam.wunderground.com', wu_cam_id, wu_cam_pwd)    file = open(image, 'rb')    session.storbinary('image.jpg', file)    file.close()    session.quit()

Однако, даже просто вручную передать данные на WU у меня не получилось ни разу. Судя по форумам техподдержки, данная фича работает плохо и сбоит.

Альтернативной является передача изображения на narodmon.ru,

Собственно, время от времени (раз в полчаса) дёргаем ручку /api/v1/capture_photo (которая зовёт take_photo). Например, будем звать через cron этот bash-скрипт:

#!/bin/bashPATH_TO_PHOTO=`/usr/bin/wget -O - -q -t 1 http://0.0.0.0/api/v1/capture_photo`REQUEST='curl -F YOUR_CAM_KEY=@'$PATH_TO_PHOTO' http://narodmon.ru/post'RESULT=`$REQUEST` >/dev/null 2>&1

На сервисе сразу появится снимок:

Плюс, не забываем время от времени (например раз в семь дней) чистить старые изображения:

#!/bin/bash bash# Notes:# This file will remove all files in camera folder older than 7 days, just run in via cron periodically (i.e. daily).find /home/pi/web-server/camera/ -type f -mtime +7 -name '*.jpg' -execdir rm -- '{}' \;

Что дальше?

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

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

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru