Веб-сервер, интернет вещей
Есть у меня сервис по визуализации данных с моих датчиков и устройств. Лично я считаю, что интереснее реализованы некоторые функции, которые в пользовательском интерфейсе Grafana или ESPHome сделаны исходя из совсем других удобств.
Так, несмотря на появление принципов ООП еще в 1960 годах, о нем многие из нас начали слышать лишь в году так 2010. Многие программисты писали методы и функции без разделения кода, но оказалось, что намного интереснее ориентироваться на живой мир, когда с разработкой четко видишь и осознаешь, что вот этот текст в программе – код человека как законченного объекта исследований с его инкапсулированными методами, например, а вот тот фрагмент – это тот же человек, но с наследованием родительского класса… Он уже не просто человек, но специалист, например, в программировании.
Итак, в интерфейсе своего приложения стало интересно оттолкнуться от реального объекта в моем окружении – от фермы на балконе, где выращиваются клубника и микрозелень. И первым вопросом было явно не то, а как и каким инструментом визуализировать данные с датчиков на этом мини-огороде. Ну окей, самым первым вопросом было, конечно, это. Но когда я накидал график и get-запросом начал переключаться между датчиками, понял, что мне не очень понятно среди этого множества графиков, как вообще управлять всем этим делом и не запутаться.
Вот на подоконнике я точно понимаю, что есть коричневый ящик, а с его правого края находятся два датчика влаги. Но когда я забыл о ферме на полгода, а потом вдруг решил вспомнить, мне что, надо копаться и вспоминать, где именно эти два датчика?
Я пошел от другого подхода, который в целом можно реализовать и средствами визуализации Grafane, но нужно прямо красивое средство с самого начала сделать все правильным путем. И тут налицо парадигма из ООП. Ведь если есть горшок, то начать стоит с него. Это должно быть понятно и максимально прозрачно в интерфейсе. Ты понимаешь, где какой горшок и что там примерно сидит. И только тогда есть смысл уже перейти к тому, а что в горшке… А в нем – датчики, которые выглядят явно не голой надписью вида “датчик влаги №12”. Они – типовые элементы, у которых есть красивая фотография крупным планом.
Поэтому я реализовал механизм, где сначала ты обозначаешь проект, например, – ферма на подоконнике. Ферма на балконе – это другой мир, хоть и “братственный” тому, что возле окна. Но можно делать и в одном проекте, в котором выбирать подпроекты. Затем в проекте уже есть резон добавлять в проект конкретные датчики. Причем я должен видеть, в каком они устройстве, какие пины подключения к ESP32. Итого получается довольно интересный интерфейс.

Получаю с датчика значения, но сижу и полностью руками прописываю любую фичу и любой дополнительный функционал. Говорят, что при визуализации более 100000 точек появляются заметные лаги, поэтому для производительных графиков либо необходима оптимизация в своей системе, либо целесообразно использовать готовые решения и не изобретать велосипед.
Методы и инструменты для оптимизации очевидны: алгоритмы для временных рядов вида LTTB (Largest Triangle Three Buckets), Min/Max per Pixel (LTTB с сохранением экстремумов), правильный выбор библиотеки для отрисовки графиков, WebGL. Хотя все это не так важно, когда не охота строить прямо много точек, а хочется просто на разных масштабах хорошо понимать ситуацию без анализа выбросов и аномалий: работал ли датчик в течение недели, переставал ли функционировать за тот или иной день.
Все это решается тем, что можно и просто визуализировать порядка 10К точек и, при необходимости, принять во внимание те, где были флуктуации на пороговую величину. На рисунке ниже видно, что значения нестабильны, то есть я понимаю, что есть некоторая проблема с датчиком, но руки не доходят поправить, потому что интереснее ведь доработать теоретически сам сервис, чуть дописать удобные функции работы с масштабами по времени.

Очевидно, что готовыми и, тем более, OpenSource-решениями пренебрегать не то чтобы не стоит, но их надо постоянно в таком самодельном приложении иметь в арсенале. Многие бы сказали, что “просто перейди на эти решения, добавь их в стек технологий проекта”. Но я поэтому несколькими абзацами ранее и расписал концепцию с тем, как я хочу видеть сам проект. И можно ли видеть его таким через Grafana – один из вопросов данной статьи.
Hello, Grafana! Визуализация в Grafana значений с виртуального датчика DHT22.
Итак, когда начал искать решение, наткнулся на Опенсорсный проект Grafana и сразу решил попробовать в действии.
Решение ставится как готовый сервис на твой компьютер, то есть я получаю просто дополнительный микросервис на порту своего компьютера, на который нужно заходить как на веб-приложение в браузере.
Пишут, что можно просто поставить решение в таком виде:
brew update
brew install grafana
Затем следует запустить сервис командой brew services start grafana, в браузере перейти по адресу localhost:3000 и ввести там типовые логин: admin, пароль: admin для перехода к интерфейсу веб-приложения по визуализации данных.
Но в итоге мне показалось более правильным и интересным расписать решение, которое будет с docker-compose.yaml файлом и которое можно будет видеть в папочке своих микросервисов и запускать командой
docker-compose up
Задал в конфигурации свой логин и пароль, настроил порты, так как и 8000, и 3000 уже заняты другими приложениями, например, RedMine, который использую для своих целей, уже висит на 3000 порту, он будет конфликтовать, если не сменить порт, например, на 3001.

В качестве источника данных можно выбрать хоть табличные данные из MySQL / Postgres, хоть Prometheus. Мой Hello World – это именно Prometheus, на который мой скрипт шлет данные, имитируя датчик.

Таким образом, можно вообще не привязываться к моему проекту и с нуля собирать данные через Prometeus, для этого надо перейти в Connections > Data Sources > Add Source и выбрать Prometeus.
Код полученного микросервиса состоит из 3 файлов:
1) docker-compose.yml
Показывает содержимое создаваемого Docker-контейнера. Используется сервер сбора метрик Prometheus, Grafana как средство визуализации данных. Обозначается скрипт, который имитирует датчик температуры (temp_explorer.py).
version: '3.8'
services:
prometheus:
image: prom/prometheus:latest
container_name: iot-prometheus
restart: unless-stopped
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--storage.tsdb.retention.time=30d'
grafana:
image: grafana/grafana-oss:latest
container_name: iot-grafana
restart: unless-stopped
ports:
- "3001:3001"
volumes:
- grafana-data:/var/lib/grafana
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin999
- GF_USERS_ALLOW_SIGN_UP=false
temp-exporter:
image: python:3.11-slim
container_name: iot-temp-exporter
restart: unless-stopped
working_dir: /app
volumes:
- ./temp_exporter.py:/app/temp_exporter.py
command: >
sh -c "pip install --no-cache-dir prometheus_client && python /app/temp_exporter.py"
ports:
- "8001:8001"
volumes:
prometheus-data:
Grafana-data:
2) Prometheus.yml
Конфигурация самого сервера Prometheus. Слушает результаты скрипта симуляции датчика температуры, опрашивает датчик раз в 5 секунд.
global:
scrape_interval: 5s # Как часто опрашивать датчики
evaluation_interval: 5s
scrape_configs:
# Сам Prometheus (его внутренние метрики)
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
# Наш виртуальный датчик температуры
- job_name: 'temperature_sensor'
static_configs:
- targets: ['temp-exporter:8001']
labels:
location: 'living_room'
sensor: 'dht22_virtual'
3) temp_exporter.py
Имитатор датчиков. Создает 2 метрики типа Gauge: температура и влажность (по аналогии со значениями, которые выдает реальный датчик DHT22). Формат как раз подходит для Prometheus.
#!/usr/bin/env python3
"""
🌡️ Простой экспортер температуры для Prometheus
Отдает метрики на http://localhost:8001/metrics
"""
import time
import math
import random
from datetime import datetime
from prometheus_client import start_http_server, Gauge
# Создаем метрику температуры
temperature = Gauge(
'room_temperature_celsius',
'Temperature in the living room',
['location', 'sensor']
)
humidity = Gauge(
'room_humidity_percent',
'Humidity in the living room',
['location', 'sensor']
)
def generate_realistic_values():
"""Генерирует реалистичные показания датчика"""
now = datetime.now()
hour = now.hour + now.minute / 60.0
# Суточный цикл: прохладно ночью, тепло днем
base_temp = 21.5 + 2.5 math.sin((hour - 9) math.pi / 12)
temp = base_temp + random.gauss(0, 0.15)
# Влажность обратно пропорциональна температуре
base_humidity = 55 - 2 math.sin((hour - 9) math.pi / 12)
hum = base_humidity + random.gauss(0, 1.5)
return round(temp, 2), round(max(20, min(90, hum)), 1)
if name == '__main__':
print("🌡️ Запуск экспортера температуры на порту 8001...")
print("📊 Prometheus будет забирать данные с http://temp-exporter:8001/metrics")
# Запускаем HTTP-сервер для Prometheus
start_http_server(8001)
while True:
temp, hum = generate_realistic_values()
temperature.labels(location='living_room', sensor='dht22_virtual').set(temp)
humidity.labels(location='living_room', sensor='dht22_virtual').set(hum)
print(f"[{datetime.now().strftime('%H:%M:%S')}] 🌡️ {temp}°C | 💧 {hum}%")
time.sleep(5) # Обновляем значения каждые 5 секунд
Запускается это все командой
docker-compose up -d
Логин и пароль при таком подходе можно сконфигурировать в коде:
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin999
И вот у Вас уже 3 сервиса внутри единого тестового решения вида “Hello, Grafana”, где эмулятор датчика опрашивается Prometheus раз в 5 секунд, в результате чего подхватываются значения, которые Grafana может визуализировать.

Еще раз резюмирую: для создания графика в Grafana нужно добавить Dashboard, в источнике указывается “Prometheus”.

Можно добавлять разные Дашборды, по разному их называть, добавлять на них графики. Всю самописную логику вида “Если влажность низкая, включить помпу для полива” записывать нужно явно не в Grafana. Хотя там есть Alerting для этих целей, который может отправить Webhook на ваш самописный сервер. Здесь, однако, – именно мощный инструмент для просмотра значений в заданный период времени, интерфейс в целом позволяет удобно задавать этот диапазон.

Визуализация данных с сервера
Когда совсем не смотрел на то, что выдает датчик, либо в моменте он все показывал хорошо, но особо не заморачивался с его анализом в разных масштабах времени, интересно посмотреть на те данные, которые в итоге накопились.
Надо создать новый дашборд, но в источнике указать базу, у меня это – mySQL. Как добавить MySQL в Grafana: Connections → Data Sources → Add data source → MySQL

Но при этом еще нужно запустить запрос:
SELECT
datetime AS "time",
val AS "Значение датчика",
sensor_id AS "Датчик"
FROM vals
WHERE user_id = 44
AND sensor_id = 16
AND datetime >= NOW() - INTERVAL 365 DAY
ORDER BY datetime ASC
Здесь user_id – это идентификатор пользователя в базе данных, у которого есть датчики с разными ID. А sensor_id – именно тот датчик, значения которого надо взять из базы и визуализировать.
Что можно сказать по полученному графику: меня особо не беспокоила визуализация данных с этого сенсора, а зря. Если я делал упор на то, чтобы все работало здесь и сегодня, то с Grafana я сразу ощутил всю мощь визуализации тех данных, которые накопил. Не нужно тратить время на проработку содержимого самописных графиков, потому что готовый инструмент сразу их покажет, оставив больше времени на анализ самих значений.
Вердикт на тему “Стоит ли подключить Grafana как основной / дополнительный инструмент на моем сервере Интернета вещей”
Grafana показывает мощные функции OpenSource решения, с помощью которого можно быстро и красиво получить графики из табличных данных именно для задач контроля во времени значений с сенсоров. Но актуальным инструментом мне это показалось именно в качестве вспомогательной фичи.
Данный инструмент Grafana ориентирован на то, что нужно руками сидеть и подключать датчики, которые у тебя есть в базе данных, а не на то, что ты создашь цифрового клона своей домашней фермы на подоконнике, а тебе сразу магическим образом предоставляются графики и показатели этой фермы. В Grafana таких чудес нет, потому что как минимум надо прописывать нестандартный скрипт, который свяжет ее с показателями из базы данных, а ведь нужно еще и соображать, где какой датчик находится в реальности, а не на Дашборде. Есть удобные фичи вида “Alerting”, “Variables”, но они решают вопрос ценой совсем другого уровня удобства программирования.
Однако как вспомогательный инструмент Grafana позволяет валидировать данные, накопленные на ферме за относительно продолжительное время, весь функционал уже написан до нас.