Показываем данные с MQTT на Grafana

Показываем данные с MQTT на Grafana

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

Для того чтобы начать отправлять данные, нам не обязательно иметь настроенный Homebridge, необходим только MQTT брокер и сервер с Docker-ом.

Данные мы будем хранить в InfluxDB, она специально была создана для подобных задач. Сервер БД можно завести и в домашней сети, но не советую это делать на RaspberryPi, большое количество чтение/записи может быстро израсходовать лимит вашей SD-карты.

Начнем с того, что зайдем на наш сервер и заведем папку где будем хранить файлы БД, например в /var/influxdb

sudo mkdir /var/influxdb

И создадим базу данных. Это можно сделать командой:

docker run --rm \
	-e INFLUXDB_DB=<имя базы данных> \
    -e INFLUXDB_ADMIN_ENABLED=true \
	-e INFLUXDB_ADMIN_USER=admin \
    -e INFLUXDB_ADMIN_PASSWORD=<Пароль администратора> \
	-e INFLUXDB_USER=<Имя пользователя> \
    -e INFLUXDB_USER_PASSWORD=<Пароль пользователя> \
	-v /var/influxdb:/var/lib/influxdb \
	influxdb /init-influxdb.sh

Теперь создадим папку для хранения файлов grafana. Например, /var/grafana

sudo mkdir /var/grafana

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

docker run -ti --user root --volume /var/grafana:/var/lib/grafana --entrypoint bash grafana/grafana

И поменять владельца

chown -R root:root /etc/grafana && \
  chmod -R a+r /etc/grafana && \
  chown -R grafana:grafana /var/lib/grafana && \
  chown -R grafana:grafana /usr/share/grafana

Исчерпывающую информацию к более детальной настройки Grafana можно найти в документации https://grafana.com/docs/grafana/latest/installation/docker/

Далее можно запускать DB и Grafana.

docker run -d \
    -p 8086:8086 \
    -v /var/influxdb:/var/lib/influxdb \
    --name influxdb \
    influxdb

docker run -d \
    -p 3000:3000 \
    -e GF_SERVER_ROOT_URL="<адрес хоста>" \
    -e GF_SECURITY_ADMIN_PASSWORD="<пароль администратора>" \
    -v /var/grafana:/var/lib/grafana \
    --name grafana \
    grafana/grafana

Все что нам осталось, так это зайти на сервер по порту 3000, авторизоваться с указанным вами паролем и выполнять инструкции по настройке указанные в сервисе.

Теперь как же отправить данные на InfluxDB с нашего домашнего сервера? Для этого необходимо маршрутизировать данные с MQTT брокера на сервер. Предлагаю воспользоваться уже готовым решением https://github.com/Kichix/WlanThermoMeterGrafana/tree/master/02-bridge

Для конфигурации которая была описана в предыдущем посте необходимо слегка видоизменить main.py:

#!/usr/bin/env python3

"""A MQTT to InfluxDB Bridge

This script receives MQTT data and saves those to InfluxDB.

"""

import re
from typing import NamedTuple

import paho.mqtt.client as mqtt
from influxdb import InfluxDBClient

INFLUXDB_ADDRESS = '<Адрес хоста с БД>'
INFLUXDB_USER = '<Имя пользователя от БД>'
INFLUXDB_PASSWORD = '<Пароль от БД>'
INFLUXDB_DATABASE = '<Имя базы данных>'

MQTT_ADDRESS = '<Адрес MQTT брокера>'
MQTT_TOPIC = '+/+'  # [temperature|humidity|battery|status]/[id]
MQTT_REGEX = '([^/]+)/([^/]+)'
MQTT_CLIENT_ID = 'MQTTInfluxDBBridge'

influxdb_client = InfluxDBClient(INFLUXDB_ADDRESS, 8086, INFLUXDB_USER, INFLUXDB_PASSWORD, None)


class SensorData(NamedTuple):
    location: str
    measurement: str
    value: float


def on_connect(client, userdata, flags, rc):
    """ The callback for when the client receives a CONNACK response from the server."""
    print('Connected with result code ' + str(rc))
    client.subscribe(MQTT_TOPIC)


def on_message(client, userdata, msg):
    """The callback for when a PUBLISH message is received from the server."""
    print(msg.topic + ' ' + str(msg.payload))
    sensor_data = _parse_mqtt_message(msg.topic, msg.payload.decode('utf-8'))
    if sensor_data is not None:
        _send_sensor_data_to_influxdb(sensor_data)


def _parse_mqtt_message(topic, payload):
    match = re.match(MQTT_REGEX, topic)
    if match:
        measurement = match.group(1)
        location = match.group(2)
        if measurement == 'status':
            return None
        return SensorData(location, measurement, float(payload))
    else:
        return None


def _send_sensor_data_to_influxdb(sensor_data):
    json_body = [
        {
            'measurement': sensor_data.measurement,
            'tags': {
                'location': sensor_data.location
            },
            'fields': {
                'value': sensor_data.value
            }
        }
    ]
    influxdb_client.write_points(json_body)


def _init_influxdb_database():
    databases = influxdb_client.get_list_database()
    if len(list(filter(lambda x: x['name'] == INFLUXDB_DATABASE, databases))) == 0:
        influxdb_client.create_database(INFLUXDB_DATABASE)
    influxdb_client.switch_database(INFLUXDB_DATABASE)


def main():
    _init_influxdb_database()

    mqtt_client = mqtt.Client(MQTT_CLIENT_ID)
    #mqtt_client.username_pw_set(MQTT_USER, MQTT_PASSWORD)
    mqtt_client.on_connect = on_connect
    mqtt_client.on_message = on_message

    mqtt_client.connect(MQTT_ADDRESS, 1883)
    mqtt_client.loop_forever()


if __name__ == '__main__':
    print('MQTT to InfluxDB bridge')

В оригинальном коде происходила авторизация на брокер по логин/пароль, возможно это и более безопасно, но почему то для своей сети я решил этим не заморачиваться и было изменено определение источника и типа измерения. Автор кода принимал сначала id источника, а потом идентификатор измерения, в моем же варианте наоборот. Так же автор предлагает запускать скрипт в докере, но память RaspberryPi не резиновая и устанавливать на него Docker мне лично кажется неразумным. Поэтому необходимо будет написать конфигурацию сервиса чтобы настроить автозапуск скрипта при старте RaspberryPi. Создадим файл mqtt-influxdb-bridge.service:

[Unit]
Description=MQTT InfluxDB Bridge
After=syslog.target network-online.target

[Service]
Type=simple
User=pi
ExecStart=/usr/bin/python3 /home/pi/mqtt-influxdb-bridge/main.py
Restart=on-failure
RestartSec=10
KillMode=process

[Install]
WantedBy=multi-user.target

Создадим папку для хранения бриджа на RaspberryPi

mkdir ~/mqtt-influxdb-bridge

И отправим requirements.txt, модифицированый main.py и mqtt-influxdb-bridge.service на наш RaspberryPi

scp main.py pi@raspberrypi:~/mqtt-influxdb-bridge/.
scp requirements.txt pi@raspberrypi:~/mqtt-influxdb-bridge/.
scp mqtt-influxdb-bridge.service pi@raspberrypi:~/mqtt-influxdb-bridge/.

Установите python3 если он еще не установлен на ваш RaspberryPi и начните забирать зависимости из папки ~/mqtt-influxdb-bridge

pip3 install -r requirements.txt

Далее скопируем конфигурацию сервиса в /lib/systemd/system/

sudo cp mqtt-influxdb-bridge.service /lib/systemd/system/.

Не забудьте исправить полный путь ExecStart в mqtt-influxdb-bridge.service к файлу скрипта main.py если он отличается от настоящего.

Наконец то можно запускать скрипт

systemctl daemon-reload
systemctl enable mqtt-influxdb-bridge
systemctl start mqtt-influxdb-bridge

Данные должны начать отправляться на вашу БД. Можете начать настраивать индикаторы в Grafana и наслаждаться плодами трудов.